Merge Android 12

Bug: 202323961
Merged-In: Ib2eab0f24b001763f292b8e5fa62a132ae63f9e6
Change-Id: I2f6777e91e934269c71c914db8621298a451fceb
diff --git a/OWNERS b/OWNERS
index 17a2c9e..ea90cbf 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,8 +1,3 @@
-set noparent
-
-dysu@google.com
-etancohen@google.com
-rpius@google.com
-satk@google.com
+include /WIFI_OWNERS
 
 include platform/packages/modules/common:/MODULES_OWNERS  # see go/mainline-owners-policy
diff --git a/OsuLogin/AndroidManifest.xml b/OsuLogin/AndroidManifest.xml
index a428cb3..730cd87 100644
--- a/OsuLogin/AndroidManifest.xml
+++ b/OsuLogin/AndroidManifest.xml
@@ -31,7 +31,8 @@
         <activity android:name="com.android.hotspot2.osulogin.OsuLoginActivity"
                   android:label="@string/action_bar_label"
                   android:theme="@style/AppTheme"
-                  android:configChanges="keyboardHidden|orientation|screenSize">
+                  android:configChanges="keyboardHidden|orientation|screenSize"
+                  android:exported="true">
             <intent-filter>
                 <action android:name="android.net.wifi.action.PASSPOINT_LAUNCH_OSU_VIEW"/>
                 <category android:name="android.intent.category.DEFAULT"/>
diff --git a/TEST_MAPPING b/TEST_MAPPING
new file mode 100644
index 0000000..c983f5c
--- /dev/null
+++ b/TEST_MAPPING
@@ -0,0 +1,30 @@
+{
+  "presubmit": [
+    {
+      "name": "MtsWifiTestCases"
+    }
+  ],
+  "presubmit-large": [
+    {
+      "name": "CtsWifiTestCases",
+      "options": [
+        {
+          "exclude-annotation": "android.net.wifi.cts.VirtualDeviceNotSupported"
+        }
+      ]
+    }
+  ],
+  "mainline-presubmit": [
+    {
+      "name": "MtsWifiTestCases[com.google.android.wifi.apex]"
+    },
+    {
+      "name": "CtsWifiTestCases[com.google.android.wifi.apex]",
+      "options": [
+        {
+          "exclude-annotation": "android.net.wifi.cts.VirtualDeviceNotSupported"
+        }
+      ]
+    }
+  ]
+}
diff --git a/WIFI_OWNERS b/WIFI_OWNERS
new file mode 100644
index 0000000..874d898
--- /dev/null
+++ b/WIFI_OWNERS
@@ -0,0 +1,3 @@
+etancohen@google.com
+arabawy@google.com
+satk@google.com
diff --git a/apex/Android.bp b/apex/Android.bp
index 5f364f3..be0767b 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -37,7 +37,11 @@
         "OsuLogin",
         "ServiceWifiResources",
     ],
-    updatable: false,
+    min_sdk_version: "30",
+    updatable: true,
+    // Indicates that pre-installed version of this apex can be compressed.
+    // Whether it actually will be compressed is controlled on per-device basis.
+    compressible: true,
 }
 
 filegroup {
diff --git a/apex/apex_manifest.json b/apex/apex_manifest.json
index 30e46b4..fd0de11 100644
--- a/apex/apex_manifest.json
+++ b/apex/apex_manifest.json
@@ -1,5 +1,5 @@
 {
   "name": "com.android.wifi",
-  "version": 300000000
+  "version": 319999900
 }
 
diff --git a/framework/Android.bp b/framework/Android.bp
index 2dc30fc..0730f28 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -19,7 +19,7 @@
 java_defaults {
     name: "wifi-module-sdk-version-defaults",
     min_sdk_version: "30",
-    target_sdk_version: "30",
+    target_sdk_version: "31",
 }
 
 filegroup {
@@ -44,6 +44,7 @@
     srcs: [
         ":framework-wifi-updatable-java-sources",
         ":framework-wifi-updatable-exported-aidl-sources",
+        ":module-utils-os-aidls",
     ],
 }
 
@@ -64,16 +65,18 @@
     "//external/robolectric-shadows:__subpackages__",
     "//frameworks/base/packages/SettingsLib/tests/integ",
     "//external/sl4a:__subpackages__",
-    "//packages/apps/Settings/tests/robotests", // TODO(b/161767237): remove
 ]
 
 java_library {
     name: "wifi-modules-utils",
     sdk_version: "module_current",
+    min_sdk_version: "30",
     static_libs: [
         "modules-utils-build",
         "modules-utils-handlerexecutor",
+        "modules-utils-list-slice",
         "modules-utils-preconditions",
+        "modules-utils-shell-command-handler",
         "modules-utils-statemachine",
     ],
     apex_available: [
@@ -91,6 +94,7 @@
     static_libs: [
         "framework-wifi-util-lib",
         "android.hardware.wifi-V1.0-java-constants",
+        "androidx.annotation_annotation",
         "wifi-modules-utils",
     ],
     libs: [
@@ -104,7 +108,7 @@
     },
     srcs: [
         ":framework-wifi-updatable-sources",
-        ":framework-wifi-util-lib-aidls",
+        ":module-utils-os-aidls",
     ],
 }
 
@@ -160,12 +164,15 @@
         // Created by jarjar rules.
         "com.android.wifi.x",
     ],
+    lint: {
+        strict_updatability_linting: true,
+    },
 }
 
 // defaults for tests that need to build against framework-wifi's @hide APIs
 java_defaults {
     name: "framework-wifi-test-defaults",
-    sdk_version: "core_platform", // tests can use @CorePlatformApi's
+    sdk_version: "core_current",
     libs: [
         // order matters: classes in framework-wifi are resolved before framework, meaning
         // @hide APIs in framework-wifi are resolved before @SystemApi stubs in framework
diff --git a/framework/TEST_MAPPING b/framework/TEST_MAPPING
index 95e57a7..1eb3a9a 100644
--- a/framework/TEST_MAPPING
+++ b/framework/TEST_MAPPING
@@ -1,12 +1,22 @@
 {
-  "presubmit": [
-    {
-      "name": "FrameworksWifiApiTests"
-    },
+  "presubmit-large": [
     {
       // run service unit tests for API changes (since API changes can break service, but not the
       // other way around)
       "name": "FrameworksWifiTests"
     }
+  ],
+  "presubmit": [
+    {
+      "name": "FrameworksWifiApiTests"
+    }
+  ],
+  "mainline-presubmit": [
+    {
+      "name": "FrameworksWifiApiTests[com.google.android.wifi.apex]"
+    },
+    {
+      "name": "FrameworksWifiTests[com.google.android.wifi.apex]"
+    }
   ]
 }
diff --git a/framework/java/android/net/wifi/ITxPacketCountListener.aidl b/framework/aidl-export/android/net/wifi/CoexUnsafeChannel.aidl
similarity index 60%
copy from framework/java/android/net/wifi/ITxPacketCountListener.aidl
copy to framework/aidl-export/android/net/wifi/CoexUnsafeChannel.aidl
index 9105bd0..cb359e9 100644
--- a/framework/java/android/net/wifi/ITxPacketCountListener.aidl
+++ b/framework/aidl-export/android/net/wifi/CoexUnsafeChannel.aidl
@@ -1,11 +1,11 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
+/**
+ * Copyright (c) 2020, The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -16,13 +16,4 @@
 
 package android.net.wifi;
 
-/**
- * Interface for tx packet counter callback.
- * @deprecated no longer used, remove once removed from BaseWifiService
- * @hide
- */
-oneway interface ITxPacketCountListener
-{
-    void onSuccess(int count);
-    void onFailure(int reason);
-}
+parcelable CoexUnsafeChannel;
diff --git a/framework/java/android/net/wifi/ITxPacketCountListener.aidl b/framework/aidl-export/android/net/wifi/WifiAvailableChannel.aidl
similarity index 60%
copy from framework/java/android/net/wifi/ITxPacketCountListener.aidl
copy to framework/aidl-export/android/net/wifi/WifiAvailableChannel.aidl
index 9105bd0..ecc5510 100644
--- a/framework/java/android/net/wifi/ITxPacketCountListener.aidl
+++ b/framework/aidl-export/android/net/wifi/WifiAvailableChannel.aidl
@@ -1,11 +1,11 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
+/**
+ * Copyright (c) 2021, The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -16,13 +16,4 @@
 
 package android.net.wifi;
 
-/**
- * Interface for tx packet counter callback.
- * @deprecated no longer used, remove once removed from BaseWifiService
- * @hide
- */
-oneway interface ITxPacketCountListener
-{
-    void onSuccess(int count);
-    void onFailure(int reason);
-}
+parcelable WifiAvailableChannel;
diff --git a/framework/java/android/net/wifi/ITxPacketCountListener.aidl b/framework/aidl-export/android/net/wifi/WifiConnectedSessionInfo.aidl
similarity index 60%
copy from framework/java/android/net/wifi/ITxPacketCountListener.aidl
copy to framework/aidl-export/android/net/wifi/WifiConnectedSessionInfo.aidl
index 9105bd0..d3de317 100644
--- a/framework/java/android/net/wifi/ITxPacketCountListener.aidl
+++ b/framework/aidl-export/android/net/wifi/WifiConnectedSessionInfo.aidl
@@ -1,11 +1,11 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
+/**
+ * Copyright (c) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -16,13 +16,4 @@
 
 package android.net.wifi;
 
-/**
- * Interface for tx packet counter callback.
- * @deprecated no longer used, remove once removed from BaseWifiService
- * @hide
- */
-oneway interface ITxPacketCountListener
-{
-    void onSuccess(int count);
-    void onFailure(int reason);
-}
+parcelable WifiConnectedSessionInfo;
diff --git a/framework/java/android/net/wifi/ITxPacketCountListener.aidl b/framework/aidl-export/android/net/wifi/aware/AwareResources.aidl
similarity index 63%
copy from framework/java/android/net/wifi/ITxPacketCountListener.aidl
copy to framework/aidl-export/android/net/wifi/aware/AwareResources.aidl
index 9105bd0..d0bd2dd 100644
--- a/framework/java/android/net/wifi/ITxPacketCountListener.aidl
+++ b/framework/aidl-export/android/net/wifi/aware/AwareResources.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,15 +14,6 @@
  * limitations under the License.
  */
 
-package android.net.wifi;
+package android.net.wifi.aware;
 
-/**
- * Interface for tx packet counter callback.
- * @deprecated no longer used, remove once removed from BaseWifiService
- * @hide
- */
-oneway interface ITxPacketCountListener
-{
-    void onSuccess(int count);
-    void onFailure(int reason);
-}
+parcelable AwareResources;
\ No newline at end of file
diff --git a/framework/api/current.txt b/framework/api/current.txt
index 870b4d9..c5d9c2f 100644
--- a/framework/api/current.txt
+++ b/framework/api/current.txt
@@ -7,6 +7,7 @@
     field public static final int EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK = -10; // 0xfffffff6
     field public static final int EASY_CONNECT_EVENT_FAILURE_CONFIGURATION = -4; // 0xfffffffc
     field public static final int EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION = -11; // 0xfffffff5
+    field public static final int EASY_CONNECT_EVENT_FAILURE_ENROLLEE_FAILED_TO_SCAN_NETWORK_CHANNEL = -14; // 0xfffffff2
     field public static final int EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION = -12; // 0xfffffff4
     field public static final int EASY_CONNECT_EVENT_FAILURE_GENERIC = -7; // 0xfffffff9
     field public static final int EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK = -9; // 0xfffffff7
@@ -14,11 +15,14 @@
     field public static final int EASY_CONNECT_EVENT_FAILURE_NOT_COMPATIBLE = -3; // 0xfffffffd
     field public static final int EASY_CONNECT_EVENT_FAILURE_NOT_SUPPORTED = -8; // 0xfffffff8
     field public static final int EASY_CONNECT_EVENT_FAILURE_TIMEOUT = -6; // 0xfffffffa
+    field public static final int EASY_CONNECT_EVENT_FAILURE_URI_GENERATION = -13; // 0xfffffff3
   }
 
   public final class ScanResult implements android.os.Parcelable {
     ctor public ScanResult(@NonNull android.net.wifi.ScanResult);
     ctor public ScanResult();
+    method public static int convertChannelToFrequencyMhzIfSupported(int, int);
+    method public static int convertFrequencyMhzToChannelIfSupported(int);
     method public int describeContents();
     method @NonNull public java.util.List<android.net.wifi.ScanResult.InformationElement> getInformationElements();
     method public int getWifiStandard();
@@ -33,7 +37,13 @@
     field public static final int CHANNEL_WIDTH_80MHZ_PLUS_MHZ = 4; // 0x4
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.ScanResult> CREATOR;
     field public String SSID;
+    field public static final int UNSPECIFIED = -1; // 0xffffffff
+    field public static final int WIFI_BAND_24_GHZ = 1; // 0x1
+    field public static final int WIFI_BAND_5_GHZ = 2; // 0x2
+    field public static final int WIFI_BAND_60_GHZ = 16; // 0x10
+    field public static final int WIFI_BAND_6_GHZ = 8; // 0x8
     field public static final int WIFI_STANDARD_11AC = 5; // 0x5
+    field public static final int WIFI_STANDARD_11AD = 7; // 0x7
     field public static final int WIFI_STANDARD_11AX = 6; // 0x6
     field public static final int WIFI_STANDARD_11N = 4; // 0x4
     field public static final int WIFI_STANDARD_LEGACY = 1; // 0x1
@@ -44,16 +54,19 @@
     field public int channelWidth;
     field public int frequency;
     field public int level;
-    field public CharSequence operatorFriendlyName;
+    field @Deprecated public CharSequence operatorFriendlyName;
     field public long timestamp;
-    field public CharSequence venueName;
+    field @Deprecated public CharSequence venueName;
   }
 
-  public static class ScanResult.InformationElement {
+  public static class ScanResult.InformationElement implements android.os.Parcelable {
     ctor public ScanResult.InformationElement(@NonNull android.net.wifi.ScanResult.InformationElement);
+    method public int describeContents();
     method @NonNull public java.nio.ByteBuffer getBytes();
     method public int getId();
     method public int getIdExt();
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.ScanResult.InformationElement> CREATOR;
   }
 
   public final class SoftApConfiguration implements android.os.Parcelable {
@@ -105,6 +118,8 @@
     field @Deprecated public String FQDN;
     field @Deprecated public static final int SECURITY_TYPE_EAP = 3; // 0x3
     field @Deprecated public static final int SECURITY_TYPE_EAP_SUITE_B = 5; // 0x5
+    field @Deprecated public static final int SECURITY_TYPE_EAP_WPA3_ENTERPRISE = 9; // 0x9
+    field @Deprecated public static final int SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT = 5; // 0x5
     field @Deprecated public static final int SECURITY_TYPE_OPEN = 0; // 0x0
     field @Deprecated public static final int SECURITY_TYPE_OWE = 6; // 0x6
     field @Deprecated public static final int SECURITY_TYPE_PSK = 2; // 0x2
@@ -144,6 +159,7 @@
 
   @Deprecated public static class WifiConfiguration.GroupCipher {
     field @Deprecated public static final int CCMP = 3; // 0x3
+    field @Deprecated public static final int GCMP_128 = 7; // 0x7
     field @Deprecated public static final int GCMP_256 = 5; // 0x5
     field @Deprecated public static final int SMS4 = 6; // 0x6
     field @Deprecated public static final int TKIP = 2; // 0x2
@@ -173,6 +189,7 @@
 
   @Deprecated public static class WifiConfiguration.PairwiseCipher {
     field @Deprecated public static final int CCMP = 2; // 0x2
+    field @Deprecated public static final int GCMP_128 = 5; // 0x5
     field @Deprecated public static final int GCMP_256 = 3; // 0x3
     field @Deprecated public static final int NONE = 0; // 0x0
     field @Deprecated public static final int SMS4 = 4; // 0x4
@@ -206,7 +223,9 @@
     method @Nullable public java.security.cert.X509Certificate[] getCaCertificates();
     method public java.security.cert.X509Certificate getClientCertificate();
     method @Nullable public java.security.cert.X509Certificate[] getClientCertificateChain();
+    method @Nullable public String getClientKeyPairAlias();
     method @Nullable public java.security.PrivateKey getClientPrivateKey();
+    method @Nullable public String getDecoratedIdentityPrefix();
     method public String getDomainSuffixMatch();
     method public int getEapMethod();
     method public String getIdentity();
@@ -216,12 +235,16 @@
     method public String getRealm();
     method @Deprecated public String getSubjectMatch();
     method public boolean isAuthenticationSimBased();
+    method public boolean isEapMethodServerCertUsed();
+    method public boolean isServerCertValidationEnabled();
     method public void setAltSubjectMatch(String);
     method public void setAnonymousIdentity(String);
     method public void setCaCertificate(@Nullable java.security.cert.X509Certificate);
     method public void setCaCertificates(@Nullable java.security.cert.X509Certificate[]);
     method public void setClientKeyEntry(java.security.PrivateKey, java.security.cert.X509Certificate);
     method public void setClientKeyEntryWithCertificateChain(java.security.PrivateKey, java.security.cert.X509Certificate[]);
+    method public void setClientKeyPairAlias(@NonNull String);
+    method public void setDecoratedIdentityPrefix(@Nullable String);
     method public void setDomainSuffixMatch(String);
     method public void setEapMethod(int);
     method public void setIdentity(String);
@@ -267,10 +290,12 @@
   public class WifiInfo implements android.os.Parcelable android.net.TransportInfo {
     method public int describeContents();
     method public String getBSSID();
+    method public int getCurrentSecurityType();
     method public static android.net.NetworkInfo.DetailedState getDetailedStateOf(android.net.wifi.SupplicantState);
     method public int getFrequency();
     method public boolean getHiddenSSID();
-    method public int getIpAddress();
+    method @Nullable public java.util.List<android.net.wifi.ScanResult.InformationElement> getInformationElements();
+    method @Deprecated public int getIpAddress();
     method public int getLinkSpeed();
     method @RequiresPermission(allOf={android.Manifest.permission.LOCAL_MAC_ADDRESS, android.Manifest.permission.ACCESS_FINE_LOCATION}) public String getMacAddress();
     method public int getMaxSupportedRxLinkSpeedMbps();
@@ -281,6 +306,7 @@
     method public int getRssi();
     method @IntRange(from=0xffffffff) public int getRxLinkSpeedMbps();
     method public String getSSID();
+    method public int getSubscriptionId();
     method public android.net.wifi.SupplicantState getSupplicantState();
     method @IntRange(from=0xffffffff) public int getTxLinkSpeedMbps();
     method public int getWifiStandard();
@@ -289,12 +315,27 @@
     field public static final String FREQUENCY_UNITS = "MHz";
     field public static final String LINK_SPEED_UNITS = "Mbps";
     field public static final int LINK_SPEED_UNKNOWN = -1; // 0xffffffff
+    field public static final int SECURITY_TYPE_EAP = 3; // 0x3
+    field public static final int SECURITY_TYPE_EAP_WPA3_ENTERPRISE = 9; // 0x9
+    field public static final int SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT = 5; // 0x5
+    field public static final int SECURITY_TYPE_OPEN = 0; // 0x0
+    field public static final int SECURITY_TYPE_OSEN = 10; // 0xa
+    field public static final int SECURITY_TYPE_OWE = 6; // 0x6
+    field public static final int SECURITY_TYPE_PASSPOINT_R1_R2 = 11; // 0xb
+    field public static final int SECURITY_TYPE_PASSPOINT_R3 = 12; // 0xc
+    field public static final int SECURITY_TYPE_PSK = 2; // 0x2
+    field public static final int SECURITY_TYPE_SAE = 4; // 0x4
+    field public static final int SECURITY_TYPE_UNKNOWN = -1; // 0xffffffff
+    field public static final int SECURITY_TYPE_WAPI_CERT = 8; // 0x8
+    field public static final int SECURITY_TYPE_WAPI_PSK = 7; // 0x7
+    field public static final int SECURITY_TYPE_WEP = 1; // 0x1
   }
 
   public static final class WifiInfo.Builder {
     ctor public WifiInfo.Builder();
     method @NonNull public android.net.wifi.WifiInfo build();
     method @NonNull public android.net.wifi.WifiInfo.Builder setBssid(@NonNull String);
+    method @NonNull public android.net.wifi.WifiInfo.Builder setCurrentSecurityType(int);
     method @NonNull public android.net.wifi.WifiInfo.Builder setNetworkId(int);
     method @NonNull public android.net.wifi.WifiInfo.Builder setRssi(int);
     method @NonNull public android.net.wifi.WifiInfo.Builder setSsid(@NonNull byte[]);
@@ -302,9 +343,11 @@
 
   public class WifiManager {
     method @Deprecated public int addNetwork(android.net.wifi.WifiConfiguration);
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_MANAGED_PROVISIONING}) public android.net.wifi.WifiManager.AddNetworkResult addNetworkPrivileged(@NonNull android.net.wifi.WifiConfiguration);
     method @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public int addNetworkSuggestions(@NonNull java.util.List<android.net.wifi.WifiNetworkSuggestion>);
     method public void addOrUpdatePasspointConfiguration(android.net.wifi.hotspot2.PasspointConfiguration);
     method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE}) public void addSuggestionConnectionStatusListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.SuggestionConnectionStatusListener);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void addSuggestionUserApprovalStatusListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.SuggestionUserApprovalStatusListener);
     method @Deprecated public static int calculateSignalLevel(int, int);
     method @IntRange(from=0) public int calculateSignalLevel(int);
     method @Deprecated public void cancelWps(android.net.wifi.WifiManager.WpsCallback);
@@ -315,41 +358,59 @@
     method @Deprecated public boolean disableNetwork(int);
     method @Deprecated public boolean disconnect();
     method @Deprecated public boolean enableNetwork(int, boolean);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_MANAGED_PROVISIONING, android.Manifest.permission.NETWORK_CARRIER_PROVISIONING}) public void flushPasspointAnqpCache();
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public java.util.List<android.net.wifi.WifiConfiguration> getCallerConfiguredNetworks();
     method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE}) public java.util.List<android.net.wifi.WifiConfiguration> getConfiguredNetworks();
     method @Deprecated public android.net.wifi.WifiInfo getConnectionInfo();
-    method public android.net.DhcpInfo getDhcpInfo();
+    method @Deprecated public android.net.DhcpInfo getDhcpInfo();
     method public int getMaxNumberOfNetworkSuggestionsPerApp();
     method @IntRange(from=0) public int getMaxSignalLevel();
     method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public java.util.List<android.net.wifi.WifiNetworkSuggestion> getNetworkSuggestions();
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.net.wifi.hotspot2.PasspointConfiguration> getPasspointConfigurations();
     method public java.util.List<android.net.wifi.ScanResult> getScanResults();
     method public int getWifiState();
+    method public boolean is24GHzBandSupported();
     method public boolean is5GHzBandSupported();
+    method public boolean is60GHzBandSupported();
     method public boolean is6GHzBandSupported();
     method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public boolean isAutoWakeupEnabled();
+    method public boolean isBridgedApConcurrencySupported();
+    method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public boolean isCarrierNetworkOffloadEnabled(int, boolean);
+    method public boolean isDecoratedIdentitySupported();
     method @Deprecated public boolean isDeviceToApRttSupported();
+    method public boolean isEasyConnectEnrolleeResponderModeSupported();
     method public boolean isEasyConnectSupported();
     method public boolean isEnhancedOpenSupported();
     method public boolean isEnhancedPowerReportingSupported();
+    method public boolean isMakeBeforeBreakWifiSwitchingSupported();
     method public boolean isP2pSupported();
+    method public boolean isPasspointTermsAndConditionsSupported();
     method public boolean isPreferredNetworkOffloadSupported();
     method @Deprecated public boolean isScanAlwaysAvailable();
     method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public boolean isScanThrottleEnabled();
     method public boolean isStaApConcurrencySupported();
+    method public boolean isStaBridgedApConcurrencySupported();
+    method public boolean isStaConcurrencyForLocalOnlyConnectionsSupported();
     method public boolean isTdlsSupported();
     method public boolean isWapiSupported();
+    method public boolean isWifiDisplayR2Supported();
     method public boolean isWifiEnabled();
     method public boolean isWifiStandardSupported(int);
+    method public boolean isWpa3SaeH2eSupported();
+    method public boolean isWpa3SaePublicKeySupported();
     method public boolean isWpa3SaeSupported();
     method public boolean isWpa3SuiteBSupported();
     method @Deprecated public boolean pingSupplicant();
     method @Deprecated public boolean reassociate();
     method @Deprecated public boolean reconnect();
     method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void registerScanResultsCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.ScanResultsCallback);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void registerSubsystemRestartTrackingCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.SubsystemRestartTrackingCallback);
     method @Deprecated public boolean removeNetwork(int);
     method @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public int removeNetworkSuggestions(@NonNull java.util.List<android.net.wifi.WifiNetworkSuggestion>);
+    method @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public boolean removeNonCallerConfiguredNetworks();
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_CARRIER_PROVISIONING}) public void removePasspointConfiguration(String);
     method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void removeSuggestionConnectionStatusListener(@NonNull android.net.wifi.WifiManager.SuggestionConnectionStatusListener);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void removeSuggestionUserApprovalStatusListener(@NonNull android.net.wifi.WifiManager.SuggestionUserApprovalStatusListener);
     method @Deprecated public boolean saveConfiguration();
     method public void setTdlsEnabled(java.net.InetAddress, boolean);
     method public void setTdlsEnabledWithMacAddress(String, boolean);
@@ -358,6 +419,7 @@
     method @Deprecated public boolean startScan();
     method @Deprecated public void startWps(android.net.wifi.WpsInfo, android.net.wifi.WifiManager.WpsCallback);
     method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void unregisterScanResultsCallback(@NonNull android.net.wifi.WifiManager.ScanResultsCallback);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void unregisterSubsystemRestartTrackingCallback(@NonNull android.net.wifi.WifiManager.SubsystemRestartTrackingCallback);
     method @Deprecated public int updateNetwork(android.net.wifi.WifiConfiguration);
     field public static final String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK";
     field public static final String ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE = "android.net.wifi.action.REQUEST_SCAN_ALWAYS_AVAILABLE";
@@ -388,6 +450,11 @@
     field public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL = 1; // 0x1
     field public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID = 5; // 0x5
     field public static final int STATUS_NETWORK_SUGGESTIONS_SUCCESS = 0; // 0x0
+    field public static final int STATUS_SUGGESTION_APPROVAL_APPROVED_BY_CARRIER_PRIVILEGE = 4; // 0x4
+    field public static final int STATUS_SUGGESTION_APPROVAL_APPROVED_BY_USER = 2; // 0x2
+    field public static final int STATUS_SUGGESTION_APPROVAL_PENDING = 1; // 0x1
+    field public static final int STATUS_SUGGESTION_APPROVAL_REJECTED_BY_USER = 3; // 0x3
+    field public static final int STATUS_SUGGESTION_APPROVAL_UNKNOWN = 0; // 0x0
     field public static final int STATUS_SUGGESTION_CONNECTION_FAILURE_ASSOCIATION = 1; // 0x1
     field public static final int STATUS_SUGGESTION_CONNECTION_FAILURE_AUTHENTICATION = 2; // 0x2
     field public static final int STATUS_SUGGESTION_CONNECTION_FAILURE_IP_PROVISIONING = 3; // 0x3
@@ -412,6 +479,26 @@
     field @Deprecated public static final int WPS_WEP_PROHIBITED = 4; // 0x4
   }
 
+  public static final class WifiManager.AddNetworkResult implements android.os.Parcelable {
+    ctor public WifiManager.AddNetworkResult(int, int);
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.WifiManager.AddNetworkResult> CREATOR;
+    field public static final int STATUS_ADD_PASSPOINT_FAILURE = 3; // 0x3
+    field public static final int STATUS_ADD_WIFI_CONFIG_FAILURE = 4; // 0x4
+    field public static final int STATUS_FAILURE_UNKNOWN = 1; // 0x1
+    field public static final int STATUS_FAILURE_UPDATE_NETWORK_KEYS = 9; // 0x9
+    field public static final int STATUS_INVALID_CONFIGURATION = 5; // 0x5
+    field public static final int STATUS_INVALID_CONFIGURATION_ENTERPRISE = 10; // 0xa
+    field public static final int STATUS_NO_PERMISSION = 2; // 0x2
+    field public static final int STATUS_NO_PERMISSION_MODIFY_CONFIG = 6; // 0x6
+    field public static final int STATUS_NO_PERMISSION_MODIFY_MAC_RANDOMIZATION = 8; // 0x8
+    field public static final int STATUS_NO_PERMISSION_MODIFY_PROXY_SETTING = 7; // 0x7
+    field public static final int STATUS_SUCCESS = 0; // 0x0
+    field public final int networkId;
+    field public final int statusCode;
+  }
+
   public static class WifiManager.LocalOnlyHotspotCallback {
     ctor public WifiManager.LocalOnlyHotspotCallback();
     method public void onFailed(int);
@@ -441,10 +528,20 @@
     method public abstract void onScanResultsAvailable();
   }
 
+  public abstract static class WifiManager.SubsystemRestartTrackingCallback {
+    ctor public WifiManager.SubsystemRestartTrackingCallback();
+    method public abstract void onSubsystemRestarted();
+    method public abstract void onSubsystemRestarting();
+  }
+
   public static interface WifiManager.SuggestionConnectionStatusListener {
     method public void onConnectionStatus(@NonNull android.net.wifi.WifiNetworkSuggestion, int);
   }
 
+  public static interface WifiManager.SuggestionUserApprovalStatusListener {
+    method public void onUserApprovalStatusChange(int);
+  }
+
   public class WifiManager.WifiLock {
     method public void acquire();
     method public boolean isHeld();
@@ -462,6 +559,7 @@
 
   public final class WifiNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
     method public int describeContents();
+    method public int getBand();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.WifiNetworkSpecifier> CREATOR;
   }
@@ -469,6 +567,7 @@
   public static final class WifiNetworkSpecifier.Builder {
     ctor public WifiNetworkSpecifier.Builder();
     method @NonNull public android.net.wifi.WifiNetworkSpecifier build();
+    method @NonNull public android.net.wifi.WifiNetworkSpecifier.Builder setBand(int);
     method @NonNull public android.net.wifi.WifiNetworkSpecifier.Builder setBssid(@NonNull android.net.MacAddress);
     method @NonNull public android.net.wifi.WifiNetworkSpecifier.Builder setBssidPattern(@NonNull android.net.MacAddress, @NonNull android.net.MacAddress);
     method @NonNull public android.net.wifi.WifiNetworkSpecifier.Builder setIsEnhancedOpen(boolean);
@@ -477,7 +576,9 @@
     method @NonNull public android.net.wifi.WifiNetworkSpecifier.Builder setSsidPattern(@NonNull android.os.PatternMatcher);
     method @NonNull public android.net.wifi.WifiNetworkSpecifier.Builder setWpa2EnterpriseConfig(@NonNull android.net.wifi.WifiEnterpriseConfig);
     method @NonNull public android.net.wifi.WifiNetworkSpecifier.Builder setWpa2Passphrase(@NonNull String);
-    method @NonNull public android.net.wifi.WifiNetworkSpecifier.Builder setWpa3EnterpriseConfig(@NonNull android.net.wifi.WifiEnterpriseConfig);
+    method @NonNull public android.net.wifi.WifiNetworkSpecifier.Builder setWpa3Enterprise192BitModeConfig(@NonNull android.net.wifi.WifiEnterpriseConfig);
+    method @Deprecated @NonNull public android.net.wifi.WifiNetworkSpecifier.Builder setWpa3EnterpriseConfig(@NonNull android.net.wifi.WifiEnterpriseConfig);
+    method @NonNull public android.net.wifi.WifiNetworkSpecifier.Builder setWpa3EnterpriseStandardModeConfig(@NonNull android.net.wifi.WifiEnterpriseConfig);
     method @NonNull public android.net.wifi.WifiNetworkSpecifier.Builder setWpa3Passphrase(@NonNull String);
   }
 
@@ -488,8 +589,11 @@
     method @Nullable public String getPassphrase();
     method @Nullable public android.net.wifi.hotspot2.PasspointConfiguration getPasspointConfig();
     method @IntRange(from=0) public int getPriority();
+    method @IntRange(from=0) public int getPriorityGroup();
     method @Nullable public String getSsid();
+    method public int getSubscriptionId();
     method public boolean isAppInteractionRequired();
+    method public boolean isCarrierMerged();
     method public boolean isCredentialSharedWithUser();
     method public boolean isEnhancedOpen();
     method public boolean isHiddenSsid();
@@ -499,12 +603,15 @@
     method public boolean isUserInteractionRequired();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.WifiNetworkSuggestion> CREATOR;
+    field public static final int RANDOMIZATION_NON_PERSISTENT = 1; // 0x1
+    field public static final int RANDOMIZATION_PERSISTENT = 0; // 0x0
   }
 
   public static final class WifiNetworkSuggestion.Builder {
     ctor public WifiNetworkSuggestion.Builder();
     method @NonNull public android.net.wifi.WifiNetworkSuggestion build();
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setBssid(@NonNull android.net.MacAddress);
+    method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setCarrierMerged(boolean);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setCredentialSharedWithUser(boolean);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsAppInteractionRequired(boolean);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsEnhancedOpen(boolean);
@@ -512,15 +619,21 @@
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsInitialAutojoinEnabled(boolean);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsMetered(boolean);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsUserInteractionRequired(boolean);
+    method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsWpa3SaeH2eOnlyModeEnabled(boolean);
+    method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setMacRandomizationSetting(int);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPasspointConfig(@NonNull android.net.wifi.hotspot2.PasspointConfiguration);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPriority(@IntRange(from=0) int);
+    method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPriorityGroup(@IntRange(from=0) int);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setSsid(@NonNull String);
+    method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setSubscriptionId(int);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setUntrusted(boolean);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setWapiEnterpriseConfig(@NonNull android.net.wifi.WifiEnterpriseConfig);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setWapiPassphrase(@NonNull String);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setWpa2EnterpriseConfig(@NonNull android.net.wifi.WifiEnterpriseConfig);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setWpa2Passphrase(@NonNull String);
-    method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setWpa3EnterpriseConfig(@NonNull android.net.wifi.WifiEnterpriseConfig);
+    method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setWpa3Enterprise192BitModeConfig(@NonNull android.net.wifi.WifiEnterpriseConfig);
+    method @Deprecated @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setWpa3EnterpriseConfig(@NonNull android.net.wifi.WifiEnterpriseConfig);
+    method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setWpa3EnterpriseStandardModeConfig(@NonNull android.net.wifi.WifiEnterpriseConfig);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setWpa3Passphrase(@NonNull String);
   }
 
@@ -550,12 +663,23 @@
     method public void onAttached(android.net.wifi.aware.WifiAwareSession);
   }
 
+  public final class AwareResources implements android.os.Parcelable {
+    ctor public AwareResources(@IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int);
+    method public int describeContents();
+    method @IntRange(from=0) public int getAvailableDataPathsCount();
+    method @IntRange(from=0) public int getAvailablePublishSessionsCount();
+    method @IntRange(from=0) public int getAvailableSubscribeSessionsCount();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.aware.AwareResources> CREATOR;
+  }
+
   public final class Characteristics implements android.os.Parcelable {
     method public int describeContents();
     method public int getMaxMatchFilterLength();
     method public int getMaxServiceNameLength();
     method public int getMaxServiceSpecificInfoLength();
     method public int getSupportedCipherSuites();
+    method public boolean isInstantCommunicationModeSupported();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.aware.Characteristics> CREATOR;
     field public static final int WIFI_AWARE_CIPHER_SUITE_NCS_SK_128 = 1; // 0x1
@@ -577,6 +701,7 @@
     method public void onPublishStarted(@NonNull android.net.wifi.aware.PublishDiscoverySession);
     method public void onServiceDiscovered(android.net.wifi.aware.PeerHandle, byte[], java.util.List<byte[]>);
     method public void onServiceDiscoveredWithinRange(android.net.wifi.aware.PeerHandle, byte[], java.util.List<byte[]>, int);
+    method public void onServiceLost(@NonNull android.net.wifi.aware.PeerHandle, int);
     method public void onSessionConfigFailed();
     method public void onSessionConfigUpdated();
     method public void onSessionTerminated();
@@ -650,11 +775,16 @@
   public class WifiAwareManager {
     method public void attach(@NonNull android.net.wifi.aware.AttachCallback, @Nullable android.os.Handler);
     method public void attach(@NonNull android.net.wifi.aware.AttachCallback, @NonNull android.net.wifi.aware.IdentityChangedListener, @Nullable android.os.Handler);
-    method public android.net.wifi.aware.Characteristics getCharacteristics();
+    method @Nullable public android.net.wifi.aware.AwareResources getAvailableAwareResources();
+    method @Nullable public android.net.wifi.aware.Characteristics getCharacteristics();
     method public boolean isAvailable();
+    method public boolean isDeviceAttached();
+    method public boolean isInstantCommunicationModeEnabled();
     field public static final String ACTION_WIFI_AWARE_STATE_CHANGED = "android.net.wifi.aware.action.WIFI_AWARE_STATE_CHANGED";
     field public static final int WIFI_AWARE_DATA_PATH_ROLE_INITIATOR = 0; // 0x0
     field public static final int WIFI_AWARE_DATA_PATH_ROLE_RESPONDER = 1; // 0x1
+    field public static final int WIFI_AWARE_DISCOVERY_LOST_REASON_PEER_NOT_VISIBLE = 1; // 0x1
+    field public static final int WIFI_AWARE_DISCOVERY_LOST_REASON_UNKNOWN = 0; // 0x0
   }
 
   public final class WifiAwareNetworkInfo implements android.os.Parcelable android.net.TransportInfo {
@@ -674,6 +804,7 @@
 
   public static final class WifiAwareNetworkSpecifier.Builder {
     ctor public WifiAwareNetworkSpecifier.Builder(@NonNull android.net.wifi.aware.DiscoverySession, @NonNull android.net.wifi.aware.PeerHandle);
+    ctor public WifiAwareNetworkSpecifier.Builder(@NonNull android.net.wifi.aware.PublishDiscoverySession);
     method @NonNull public android.net.wifi.aware.WifiAwareNetworkSpecifier build();
     method @NonNull public android.net.wifi.aware.WifiAwareNetworkSpecifier.Builder setPmk(@NonNull byte[]);
     method @NonNull public android.net.wifi.aware.WifiAwareNetworkSpecifier.Builder setPort(@IntRange(from=0, to=65535) int);
@@ -683,8 +814,8 @@
 
   public class WifiAwareSession implements java.lang.AutoCloseable {
     method public void close();
-    method public android.net.NetworkSpecifier createNetworkSpecifierOpen(int, @NonNull byte[]);
-    method public android.net.NetworkSpecifier createNetworkSpecifierPassphrase(int, @NonNull byte[], @NonNull String);
+    method @Deprecated public android.net.NetworkSpecifier createNetworkSpecifierOpen(int, @NonNull byte[]);
+    method @Deprecated public android.net.NetworkSpecifier createNetworkSpecifierPassphrase(int, @NonNull byte[], @NonNull String);
     method public void publish(@NonNull android.net.wifi.aware.PublishConfig, @NonNull android.net.wifi.aware.DiscoverySessionCallback, @Nullable android.os.Handler);
     method public void subscribe(@NonNull android.net.wifi.aware.SubscribeConfig, @NonNull android.net.wifi.aware.DiscoverySessionCallback, @Nullable android.os.Handler);
   }
@@ -702,11 +833,13 @@
     ctor public PasspointConfiguration(android.net.wifi.hotspot2.PasspointConfiguration);
     method public int describeContents();
     method public android.net.wifi.hotspot2.pps.Credential getCredential();
+    method @Nullable public String getDecoratedIdentityPrefix();
     method public android.net.wifi.hotspot2.pps.HomeSp getHomeSp();
     method public long getSubscriptionExpirationTimeMillis();
     method @NonNull public String getUniqueId();
     method public boolean isOsuProvisioned();
     method public void setCredential(android.net.wifi.hotspot2.pps.Credential);
+    method public void setDecoratedIdentityPrefix(@Nullable String);
     method public void setHomeSp(android.net.wifi.hotspot2.pps.HomeSp);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.PasspointConfiguration> CREATOR;
@@ -792,9 +925,15 @@
     method public int describeContents();
     method public String getFqdn();
     method public String getFriendlyName();
+    method @Nullable public long[] getMatchAllOis();
+    method @Nullable public long[] getMatchAnyOis();
+    method @NonNull public java.util.Collection<java.lang.String> getOtherHomePartnersList();
     method public long[] getRoamingConsortiumOis();
     method public void setFqdn(String);
     method public void setFriendlyName(String);
+    method public void setMatchAllOis(@Nullable long[]);
+    method public void setMatchAnyOis(@Nullable long[]);
+    method public void setOtherHomePartnersList(@NonNull java.util.Collection<java.lang.String>);
     method public void setRoamingConsortiumOis(long[]);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.HomeSp> CREATOR;
@@ -1010,23 +1149,46 @@
     ctor public WifiP2pWfdInfo(@Nullable android.net.wifi.p2p.WifiP2pWfdInfo);
     method public int describeContents();
     method public int getControlPort();
+    method public int getDeviceInfo();
     method public int getDeviceType();
     method public int getMaxThroughput();
+    method public int getR2DeviceInfo();
+    method public int getR2DeviceType();
     method public boolean isContentProtectionSupported();
+    method public boolean isCoupledSinkSupportedAtSink();
+    method public boolean isCoupledSinkSupportedAtSource();
     method public boolean isEnabled();
+    method public boolean isR2Supported();
     method public boolean isSessionAvailable();
     method public void setContentProtectionSupported(boolean);
     method public void setControlPort(@IntRange(from=0) int);
+    method public void setCoupledSinkSupportAtSink(boolean);
+    method public void setCoupledSinkSupportAtSource(boolean);
     method public boolean setDeviceType(int);
     method public void setEnabled(boolean);
     method public void setMaxThroughput(@IntRange(from=0) int);
+    method public boolean setR2DeviceType(int);
     method public void setSessionAvailable(boolean);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.p2p.WifiP2pWfdInfo> CREATOR;
+    field public static final int DEVICE_INFO_AUDIO_ONLY_SUPPORT_AT_SOURCE = 2048; // 0x800
+    field public static final int DEVICE_INFO_AUDIO_UNSUPPORTED_AT_PRIMARY_SINK = 1024; // 0x400
+    field public static final int DEVICE_INFO_CONTENT_PROTECTION_SUPPORT = 256; // 0x100
+    field public static final int DEVICE_INFO_COUPLED_SINK_SUPPORT_AT_SINK = 8; // 0x8
+    field public static final int DEVICE_INFO_COUPLED_SINK_SUPPORT_AT_SOURCE = 4; // 0x4
+    field public static final int DEVICE_INFO_DEVICE_TYPE_MASK = 3; // 0x3
+    field public static final int DEVICE_INFO_PREFERRED_CONNECTIVITY_MASK = 128; // 0x80
+    field public static final int DEVICE_INFO_SESSION_AVAILABLE_MASK = 48; // 0x30
+    field public static final int DEVICE_INFO_TDLS_PERSISTENT_GROUP = 4096; // 0x1000
+    field public static final int DEVICE_INFO_TDLS_PERSISTENT_GROUP_REINVOKE = 8192; // 0x2000
+    field public static final int DEVICE_INFO_TIME_SYNCHRONIZATION_SUPPORT = 512; // 0x200
+    field public static final int DEVICE_INFO_WFD_SERVICE_DISCOVERY_SUPPORT = 64; // 0x40
     field public static final int DEVICE_TYPE_PRIMARY_SINK = 1; // 0x1
     field public static final int DEVICE_TYPE_SECONDARY_SINK = 2; // 0x2
     field public static final int DEVICE_TYPE_SOURCE_OR_PRIMARY_SINK = 3; // 0x3
     field public static final int DEVICE_TYPE_WFD_SOURCE = 0; // 0x0
+    field public static final int PREFERRED_CONNECTIVITY_P2P = 0; // 0x0
+    field public static final int PREFERRED_CONNECTIVITY_TDLS = 1; // 0x1
   }
 
 }
@@ -1109,7 +1271,11 @@
 
   public final class RangingRequest implements android.os.Parcelable {
     method public int describeContents();
+    method public static int getDefaultRttBurstSize();
     method public static int getMaxPeers();
+    method public static int getMaxRttBurstSize();
+    method public static int getMinRttBurstSize();
+    method public int getRttBurstSize();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.rtt.RangingRequest> CREATOR;
   }
@@ -1118,9 +1284,12 @@
     ctor public RangingRequest.Builder();
     method public android.net.wifi.rtt.RangingRequest.Builder addAccessPoint(@NonNull android.net.wifi.ScanResult);
     method public android.net.wifi.rtt.RangingRequest.Builder addAccessPoints(@NonNull java.util.List<android.net.wifi.ScanResult>);
+    method @NonNull public android.net.wifi.rtt.RangingRequest.Builder addNon80211mcCapableAccessPoint(@NonNull android.net.wifi.ScanResult);
+    method @NonNull public android.net.wifi.rtt.RangingRequest.Builder addNon80211mcCapableAccessPoints(@NonNull java.util.List<android.net.wifi.ScanResult>);
     method public android.net.wifi.rtt.RangingRequest.Builder addWifiAwarePeer(@NonNull android.net.MacAddress);
     method public android.net.wifi.rtt.RangingRequest.Builder addWifiAwarePeer(@NonNull android.net.wifi.aware.PeerHandle);
     method public android.net.wifi.rtt.RangingRequest build();
+    method @NonNull public android.net.wifi.rtt.RangingRequest.Builder setRttBurstSize(int);
   }
 
   public final class RangingResult implements android.os.Parcelable {
@@ -1135,6 +1304,7 @@
     method public int getRssi();
     method public int getStatus();
     method @Nullable public android.net.wifi.rtt.ResponderLocation getUnverifiedResponderLocation();
+    method public boolean is80211mcMeasurement();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.rtt.RangingResult> CREATOR;
     field public static final int STATUS_FAIL = 1; // 0x1
diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt
index afc39b9..dda7481 100644
--- a/framework/api/system-current.txt
+++ b/framework/api/system-current.txt
@@ -1,8 +1,19 @@
 // Signature format: 2.0
 package android.net.wifi {
 
+  public final class CoexUnsafeChannel implements android.os.Parcelable {
+    ctor public CoexUnsafeChannel(int, int);
+    ctor public CoexUnsafeChannel(int, int, int);
+    method public int getBand();
+    method public int getChannel();
+    method public int getPowerCapDbm();
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.CoexUnsafeChannel> CREATOR;
+    field public static final int POWER_CAP_NONE = 2147483647; // 0x7fffffff
+  }
+
   public abstract class EasyConnectStatusCallback {
     ctor public EasyConnectStatusCallback();
+    method public void onBootstrapUriGenerated(@NonNull android.net.Uri);
     method public abstract void onConfiguratorSuccess(int);
     method public abstract void onEnrolleeSuccess(int);
     method public void onFailure(int);
@@ -209,7 +220,11 @@
   }
 
   public final class ScanResult implements android.os.Parcelable {
+    field public static final int CIPHER_BIP_CMAC_256 = 9; // 0x9
+    field public static final int CIPHER_BIP_GMAC_128 = 7; // 0x7
+    field public static final int CIPHER_BIP_GMAC_256 = 8; // 0x8
     field public static final int CIPHER_CCMP = 3; // 0x3
+    field public static final int CIPHER_GCMP_128 = 6; // 0x6
     field public static final int CIPHER_GCMP_256 = 4; // 0x4
     field public static final int CIPHER_NONE = 0; // 0x0
     field public static final int CIPHER_NO_GROUP_ADDRESSED = 1; // 0x1
@@ -241,27 +256,42 @@
     method public boolean areFeaturesSupported(long);
     method public int describeContents();
     method public int getMaxSupportedClients();
+    method @NonNull public int[] getSupportedChannelList(int);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.SoftApCapability> CREATOR;
     field public static final long SOFTAP_FEATURE_ACS_OFFLOAD = 1L; // 0x1L
+    field public static final long SOFTAP_FEATURE_BAND_24G_SUPPORTED = 32L; // 0x20L
+    field public static final long SOFTAP_FEATURE_BAND_5G_SUPPORTED = 64L; // 0x40L
+    field public static final long SOFTAP_FEATURE_BAND_60G_SUPPORTED = 256L; // 0x100L
+    field public static final long SOFTAP_FEATURE_BAND_6G_SUPPORTED = 128L; // 0x80L
     field public static final long SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT = 2L; // 0x2L
+    field public static final long SOFTAP_FEATURE_IEEE80211_AX = 16L; // 0x10L
+    field public static final long SOFTAP_FEATURE_MAC_ADDRESS_CUSTOMIZATION = 8L; // 0x8L
     field public static final long SOFTAP_FEATURE_WPA3_SAE = 4L; // 0x4L
   }
 
   public final class SoftApConfiguration implements android.os.Parcelable {
     method @NonNull public java.util.List<android.net.MacAddress> getAllowedClientList();
-    method public int getBand();
+    method @Deprecated public int getBand();
     method @NonNull public java.util.List<android.net.MacAddress> getBlockedClientList();
-    method public int getChannel();
+    method @Deprecated public int getChannel();
+    method @NonNull public android.util.SparseIntArray getChannels();
+    method public int getMacRandomizationSetting();
     method public int getMaxNumberOfClients();
     method public long getShutdownTimeoutMillis();
     method public boolean isAutoShutdownEnabled();
+    method public boolean isBridgedModeOpportunisticShutdownEnabled();
     method public boolean isClientControlByUserEnabled();
+    method public boolean isIeee80211axEnabled();
+    method public boolean isUserConfiguration();
     method @Nullable public android.net.wifi.WifiConfiguration toWifiConfiguration();
     field public static final int BAND_2GHZ = 1; // 0x1
     field public static final int BAND_5GHZ = 2; // 0x2
+    field public static final int BAND_60GHZ = 8; // 0x8
     field public static final int BAND_6GHZ = 4; // 0x4
-    field public static final int BAND_ANY = 7; // 0x7
+    field @Deprecated public static final int BAND_ANY = 7; // 0x7
+    field public static final int RANDOMIZATION_NONE = 0; // 0x0
+    field public static final int RANDOMIZATION_PERSISTENT = 1; // 0x1
   }
 
   public static final class SoftApConfiguration.Builder {
@@ -271,11 +301,16 @@
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setAllowedClientList(@NonNull java.util.List<android.net.MacAddress>);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setAutoShutdownEnabled(boolean);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setBand(int);
+    method @NonNull public android.net.wifi.SoftApConfiguration.Builder setBands(@NonNull int[]);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setBlockedClientList(@NonNull java.util.List<android.net.MacAddress>);
+    method @NonNull public android.net.wifi.SoftApConfiguration.Builder setBridgedModeOpportunisticShutdownEnabled(boolean);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setBssid(@Nullable android.net.MacAddress);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setChannel(int, int);
+    method @NonNull public android.net.wifi.SoftApConfiguration.Builder setChannels(@NonNull android.util.SparseIntArray);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setClientControlByUserEnabled(boolean);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setHiddenSsid(boolean);
+    method @NonNull public android.net.wifi.SoftApConfiguration.Builder setIeee80211axEnabled(boolean);
+    method @NonNull public android.net.wifi.SoftApConfiguration.Builder setMacRandomizationSetting(int);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setMaxNumberOfClients(@IntRange(from=0) int);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setPassphrase(@Nullable String, int);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setShutdownTimeoutMillis(@IntRange(from=0) long);
@@ -284,19 +319,41 @@
 
   public final class SoftApInfo implements android.os.Parcelable {
     method public int describeContents();
+    method public long getAutoShutdownTimeoutMillis();
     method public int getBandwidth();
+    method @Nullable public android.net.MacAddress getBssid();
     method public int getFrequency();
+    method public int getWifiStandard();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field public static final int CHANNEL_WIDTH_160MHZ = 6; // 0x6
     field public static final int CHANNEL_WIDTH_20MHZ = 2; // 0x2
     field public static final int CHANNEL_WIDTH_20MHZ_NOHT = 1; // 0x1
+    field public static final int CHANNEL_WIDTH_2160MHZ = 7; // 0x7
     field public static final int CHANNEL_WIDTH_40MHZ = 3; // 0x3
+    field public static final int CHANNEL_WIDTH_4320MHZ = 8; // 0x8
+    field public static final int CHANNEL_WIDTH_6480MHZ = 9; // 0x9
     field public static final int CHANNEL_WIDTH_80MHZ = 4; // 0x4
     field public static final int CHANNEL_WIDTH_80MHZ_PLUS_MHZ = 5; // 0x5
+    field public static final int CHANNEL_WIDTH_8640MHZ = 10; // 0xa
     field public static final int CHANNEL_WIDTH_INVALID = 0; // 0x0
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.SoftApInfo> CREATOR;
   }
 
+  public final class WifiAvailableChannel implements android.os.Parcelable {
+    ctor public WifiAvailableChannel(int, int);
+    method public int describeContents();
+    method public int getFrequencyMhz();
+    method public int getOperationalModes();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.WifiAvailableChannel> CREATOR;
+    field public static final int OP_MODE_SAP = 2; // 0x2
+    field public static final int OP_MODE_STA = 1; // 0x1
+    field public static final int OP_MODE_TDLS = 32; // 0x20
+    field public static final int OP_MODE_WIFI_AWARE = 16; // 0x10
+    field public static final int OP_MODE_WIFI_DIRECT_CLI = 4; // 0x4
+    field public static final int OP_MODE_WIFI_DIRECT_GO = 8; // 0x8
+  }
+
   public final class WifiClient implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public android.net.MacAddress getMacAddress();
@@ -306,30 +363,47 @@
 
   @Deprecated public class WifiConfiguration implements android.os.Parcelable {
     method @Deprecated public int getAuthType();
+    method @Deprecated public int getDeletionPriority();
     method @Deprecated @NonNull public android.net.IpConfiguration getIpConfiguration();
     method @Deprecated @NonNull public android.net.wifi.WifiConfiguration.NetworkSelectionStatus getNetworkSelectionStatus();
     method @Deprecated @NonNull public String getPrintableSsid();
+    method @Deprecated @NonNull public String getProfileKey();
     method @Deprecated public int getRecentFailureReason();
     method @Deprecated public boolean hasNoInternetAccess();
     method @Deprecated public boolean isEphemeral();
     method @Deprecated public static boolean isMetered(@Nullable android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiInfo);
     method @Deprecated public boolean isNoInternetAccessExpected();
+    method @Deprecated public void setDeletionPriority(int) throws java.lang.IllegalArgumentException;
     method @Deprecated public void setIpConfiguration(@Nullable android.net.IpConfiguration);
     method @Deprecated public void setNetworkSelectionStatus(@NonNull android.net.wifi.WifiConfiguration.NetworkSelectionStatus);
     field @Deprecated public static final int INVALID_NETWORK_ID = -1; // 0xffffffff
     field @Deprecated public static final int METERED_OVERRIDE_METERED = 1; // 0x1
     field @Deprecated public static final int METERED_OVERRIDE_NONE = 0; // 0x0
     field @Deprecated public static final int METERED_OVERRIDE_NOT_METERED = 2; // 0x2
+    field @Deprecated public static final int RANDOMIZATION_AUTO = 3; // 0x3
     field @Deprecated public static final int RANDOMIZATION_NONE = 0; // 0x0
+    field @Deprecated public static final int RANDOMIZATION_NON_PERSISTENT = 2; // 0x2
     field @Deprecated public static final int RANDOMIZATION_PERSISTENT = 1; // 0x1
     field @Deprecated public static final int RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA = 17; // 0x11
+    field @Deprecated public static final int RECENT_FAILURE_DISCONNECTION_AP_BUSY = 1004; // 0x3ec
+    field @Deprecated public static final int RECENT_FAILURE_MBO_ASSOC_DISALLOWED_AIR_INTERFACE_OVERLOADED = 1007; // 0x3ef
+    field @Deprecated public static final int RECENT_FAILURE_MBO_ASSOC_DISALLOWED_AUTH_SERVER_OVERLOADED = 1008; // 0x3f0
+    field @Deprecated public static final int RECENT_FAILURE_MBO_ASSOC_DISALLOWED_INSUFFICIENT_RSSI = 1009; // 0x3f1
+    field @Deprecated public static final int RECENT_FAILURE_MBO_ASSOC_DISALLOWED_MAX_NUM_STA_ASSOCIATED = 1006; // 0x3ee
+    field @Deprecated public static final int RECENT_FAILURE_MBO_ASSOC_DISALLOWED_UNSPECIFIED = 1005; // 0x3ed
+    field @Deprecated public static final int RECENT_FAILURE_NETWORK_NOT_FOUND = 1011; // 0x3f3
     field @Deprecated public static final int RECENT_FAILURE_NONE = 0; // 0x0
+    field @Deprecated public static final int RECENT_FAILURE_OCE_RSSI_BASED_ASSOCIATION_REJECTION = 1010; // 0x3f2
+    field @Deprecated public static final int RECENT_FAILURE_POOR_CHANNEL_CONDITIONS = 1003; // 0x3eb
+    field @Deprecated public static final int RECENT_FAILURE_REFUSED_TEMPORARILY = 1002; // 0x3ea
     field @Deprecated public boolean allowAutojoin;
     field @Deprecated public int carrierId;
+    field @Deprecated public boolean carrierMerged;
     field @Deprecated public String creatorName;
     field @Deprecated public int creatorUid;
     field @Deprecated public boolean fromWifiNetworkSpecifier;
     field @Deprecated public boolean fromWifiNetworkSuggestion;
+    field @Deprecated public int lastConnectUid;
     field @Deprecated public String lastUpdateName;
     field @Deprecated public int lastUpdateUid;
     field @Deprecated public int macRandomizationSetting;
@@ -340,6 +414,7 @@
     field @Deprecated public int numScorerOverrideAndSwitchedNetwork;
     field @Deprecated public boolean requirePmf;
     field @Deprecated public boolean shared;
+    field @Deprecated public int subscriptionId;
     field @Deprecated public boolean useExternalScores;
   }
 
@@ -362,9 +437,12 @@
     field @Deprecated public static final int DISABLED_AUTHENTICATION_FAILURE = 2; // 0x2
     field @Deprecated public static final int DISABLED_AUTHENTICATION_NO_CREDENTIALS = 5; // 0x5
     field @Deprecated public static final int DISABLED_AUTHENTICATION_NO_SUBSCRIPTION = 9; // 0x9
+    field @Deprecated public static final int DISABLED_AUTHENTICATION_PRIVATE_EAP_ERROR = 10; // 0xa
     field @Deprecated public static final int DISABLED_BY_WIFI_MANAGER = 7; // 0x7
     field @Deprecated public static final int DISABLED_BY_WRONG_PASSWORD = 8; // 0x8
+    field @Deprecated public static final int DISABLED_CONSECUTIVE_FAILURES = 12; // 0xc
     field @Deprecated public static final int DISABLED_DHCP_FAILURE = 3; // 0x3
+    field @Deprecated public static final int DISABLED_NETWORK_NOT_FOUND = 11; // 0xb
     field @Deprecated public static final int DISABLED_NONE = 0; // 0x0
     field @Deprecated public static final int DISABLED_NO_INTERNET_PERMANENT = 6; // 0x6
     field @Deprecated public static final int DISABLED_NO_INTERNET_TEMPORARY = 4; // 0x4
@@ -380,6 +458,20 @@
     method @Deprecated @NonNull public android.net.wifi.WifiConfiguration.NetworkSelectionStatus.Builder setNetworkSelectionStatus(int);
   }
 
+  public final class WifiConnectedSessionInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getSessionId();
+    method public boolean isUserSelected();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.WifiConnectedSessionInfo> CREATOR;
+  }
+
+  public static final class WifiConnectedSessionInfo.Builder {
+    ctor public WifiConnectedSessionInfo.Builder(int);
+    method @NonNull public android.net.wifi.WifiConnectedSessionInfo build();
+    method @NonNull public android.net.wifi.WifiConnectedSessionInfo.Builder setUserSelected(boolean);
+  }
+
   public class WifiEnterpriseConfig implements android.os.Parcelable {
     method @Nullable public String[] getCaCertificateAliases();
     method @NonNull public String getCaPath();
@@ -408,9 +500,14 @@
     method public int getScore();
     method public double getSuccessfulRxPacketsPerSecond();
     method public double getSuccessfulTxPacketsPerSecond();
+    method public boolean isCarrierMerged();
     method public boolean isEphemeral();
+    method public boolean isOemPaid();
+    method public boolean isOemPrivate();
     method public boolean isOsuAp();
     method public boolean isPasspointAp();
+    method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public boolean isPrimary();
+    method public boolean isTrusted();
     method @Nullable public static String sanitizeSsid(@Nullable String);
     field public static final String DEFAULT_MAC_ADDRESS = "02:00:00:00:00:00";
     field public static final int INVALID_RSSI = -127; // 0xffffff81
@@ -418,9 +515,11 @@
 
   public class WifiManager {
     method @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE) public void addOnWifiUsabilityStatsListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.OnWifiUsabilityStatsListener);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void addWifiVerboseLoggingStatusChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.WifiVerboseLoggingStatusChangedListener);
     method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void allowAutojoin(int, boolean);
     method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void allowAutojoinGlobal(boolean);
     method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void allowAutojoinPasspoint(@NonNull String, boolean);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_WIFI_COUNTRY_CODE) public void clearOverrideCountryCode();
     method @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE) public void clearWifiConnectedNetworkScorer();
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void connect(@NonNull android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiManager.ActionListener);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void connect(int, @Nullable android.net.wifi.WifiManager.ActionListener);
@@ -429,14 +528,18 @@
     method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void factoryReset();
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void forget(int, @Nullable android.net.wifi.WifiManager.ActionListener);
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.util.Pair<android.net.wifi.WifiConfiguration,java.util.Map<java.lang.Integer,java.util.List<android.net.wifi.ScanResult>>>> getAllMatchingWifiConfigs(@NonNull java.util.List<android.net.wifi.ScanResult>);
+    method @NonNull @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public java.util.List<android.net.wifi.WifiAvailableChannel> getAllowedChannels(int, int);
     method @Nullable @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public String getCountryCode();
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public android.net.Network getCurrentNetwork();
+    method public static int getEasyConnectMaxAllowedResponderDeviceInfoLength();
     method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public String[] getFactoryMacAddresses();
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.Map<android.net.wifi.hotspot2.OsuProvider,java.util.List<android.net.wifi.ScanResult>> getMatchingOsuProviders(@Nullable java.util.List<android.net.wifi.ScanResult>);
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.Map<android.net.wifi.hotspot2.OsuProvider,android.net.wifi.hotspot2.PasspointConfiguration> getMatchingPasspointConfigsForOsuProviders(@NonNull java.util.Set<android.net.wifi.hotspot2.OsuProvider>);
     method @NonNull @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE}) public java.util.Map<android.net.wifi.WifiNetworkSuggestion,java.util.List<android.net.wifi.ScanResult>> getMatchingScanResults(@NonNull java.util.List<android.net.wifi.WifiNetworkSuggestion>, @Nullable java.util.List<android.net.wifi.ScanResult>);
     method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.READ_WIFI_CREDENTIAL}) public java.util.List<android.net.wifi.WifiConfiguration> getPrivilegedConfiguredNetworks();
-    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public android.net.wifi.SoftApConfiguration getSoftApConfiguration();
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.OVERRIDE_WIFI_CONFIG}) public android.net.wifi.SoftApConfiguration getSoftApConfiguration();
+    method @NonNull @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public java.util.List<android.net.wifi.WifiAvailableChannel> getUsableChannels(int, int);
+    method public int getVerboseLoggingLevel();
     method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void getWifiActivityEnergyInfoAsync(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.OnWifiActivityEnergyInfoListener);
     method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public android.net.wifi.WifiConfiguration getWifiApConfiguration();
     method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public int getWifiApState();
@@ -445,13 +548,18 @@
     method public boolean isConnectedMacRandomizationSupported();
     method @Deprecated public boolean isDeviceToDeviceRttSupported();
     method public boolean isPortableHotspotSupported();
+    method public boolean isStaConcurrencyForRestrictedConnectionsSupported();
     method public boolean isVerboseLoggingEnabled();
     method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public boolean isWifiApEnabled();
     method public boolean isWifiScannerSupported();
+    method @RequiresPermission(android.Manifest.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS) public void registerCoexCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.CoexCallback);
     method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void registerNetworkRequestMatchCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.NetworkRequestMatchCallback);
-    method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void registerSoftApCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.SoftApCallback);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.OVERRIDE_WIFI_CONFIG}) public void registerSoftApCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.SoftApCallback);
     method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void registerTrafficStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.TrafficStateCallback);
+    method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void removeAppState(int, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE) public void removeOnWifiUsabilityStatsListener(@NonNull android.net.wifi.WifiManager.OnWifiUsabilityStatsListener);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void removeWifiVerboseLoggingStatusChangedListener(@NonNull android.net.wifi.WifiManager.WifiVerboseLoggingStatusChangedListener);
+    method @RequiresPermission(android.Manifest.permission.RESTART_WIFI_SUBSYSTEM) public void restartWifiSubsystem();
     method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void restoreBackupData(@NonNull byte[]);
     method @Nullable @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public android.net.wifi.SoftApConfiguration restoreSoftApBackupData(@NonNull byte[]);
     method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void restoreSupplicantBackupData(@NonNull byte[], @NonNull byte[]);
@@ -459,46 +567,66 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public byte[] retrieveSoftApBackupData();
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void save(@NonNull android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiManager.ActionListener);
     method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setAutoWakeupEnabled(boolean);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void setCarrierNetworkOffloadEnabled(int, boolean, boolean);
+    method @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS) public void setCoexUnsafeChannels(@NonNull java.util.List<android.net.wifi.CoexUnsafeChannel>, int);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_WIFI_COUNTRY_CODE) public void setDefaultCountryCode(@NonNull String);
     method @RequiresPermission(android.Manifest.permission.WIFI_SET_DEVICE_MOBILITY_STATE) public void setDeviceMobilityState(int);
     method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setMacRandomizationSettingPasspointEnabled(@NonNull String, boolean);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_WIFI_COUNTRY_CODE) public void setOverrideCountryCode(@NonNull String);
     method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setPasspointMeteredOverride(@NonNull String, int);
     method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setScanAlwaysAvailable(boolean);
     method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setScanThrottleEnabled(boolean);
-    method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public boolean setSoftApConfiguration(@NonNull android.net.wifi.SoftApConfiguration);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.OVERRIDE_WIFI_CONFIG}) public boolean setSoftApConfiguration(@NonNull android.net.wifi.SoftApConfiguration);
     method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setVerboseLoggingEnabled(boolean);
+    method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setVerboseLoggingLevel(int);
     method @Deprecated @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public boolean setWifiApConfiguration(android.net.wifi.WifiConfiguration);
     method @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE) public boolean setWifiConnectedNetworkScorer(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.WifiConnectedNetworkScorer);
+    method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public boolean setWifiScoringEnabled(boolean);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startEasyConnectAsConfiguratorInitiator(@NonNull String, int, int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.EasyConnectStatusCallback);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startEasyConnectAsEnrolleeInitiator(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.EasyConnectStatusCallback);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startEasyConnectAsEnrolleeResponder(@Nullable String, int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.EasyConnectStatusCallback);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startLocalOnlyHotspot(@NonNull android.net.wifi.SoftApConfiguration, @Nullable java.util.concurrent.Executor, @Nullable android.net.wifi.WifiManager.LocalOnlyHotspotCallback);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startRestrictingAutoJoinToSubscriptionId(int);
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public boolean startScan(android.os.WorkSource);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startSubscriptionProvisioning(@NonNull android.net.wifi.hotspot2.OsuProvider, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.hotspot2.ProvisioningCallback);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public boolean startTetheredHotspot(@Nullable android.net.wifi.SoftApConfiguration);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void stopEasyConnectSession();
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void stopRestrictingAutoJoinToSubscriptionId();
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public boolean stopSoftAp();
+    method @RequiresPermission(android.Manifest.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS) public void unregisterCoexCallback(@NonNull android.net.wifi.WifiManager.CoexCallback);
     method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void unregisterNetworkRequestMatchCallback(@NonNull android.net.wifi.WifiManager.NetworkRequestMatchCallback);
-    method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void unregisterSoftApCallback(@NonNull android.net.wifi.WifiManager.SoftApCallback);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.OVERRIDE_WIFI_CONFIG}) public void unregisterSoftApCallback(@NonNull android.net.wifi.WifiManager.SoftApCallback);
     method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void unregisterTrafficStateCallback(@NonNull android.net.wifi.WifiManager.TrafficStateCallback);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void updateInterfaceIpState(@Nullable String, int);
     method @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE) public void updateWifiUsabilityScore(int, int, int);
     field public static final String ACTION_LINK_CONFIGURATION_CHANGED = "android.net.wifi.LINK_CONFIGURATION_CHANGED";
     field @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING) public static final String ACTION_NETWORK_SETTINGS_RESET = "android.net.wifi.action.NETWORK_SETTINGS_RESET";
     field public static final String ACTION_PASSPOINT_LAUNCH_OSU_VIEW = "android.net.wifi.action.PASSPOINT_LAUNCH_OSU_VIEW";
+    field @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING) public static final String ACTION_REFRESH_USER_PROVISIONING = "android.net.wifi.action.REFRESH_USER_PROVISIONING";
     field public static final String ACTION_REQUEST_DISABLE = "android.net.wifi.action.REQUEST_DISABLE";
     field public static final String ACTION_REQUEST_ENABLE = "android.net.wifi.action.REQUEST_ENABLE";
     field public static final int CHANGE_REASON_ADDED = 0; // 0x0
     field public static final int CHANGE_REASON_CONFIG_CHANGE = 2; // 0x2
     field public static final int CHANGE_REASON_REMOVED = 1; // 0x1
+    field public static final int COEX_RESTRICTION_SOFTAP = 2; // 0x2
+    field public static final int COEX_RESTRICTION_WIFI_AWARE = 4; // 0x4
+    field public static final int COEX_RESTRICTION_WIFI_DIRECT = 1; // 0x1
     field public static final String CONFIGURED_NETWORKS_CHANGED_ACTION = "android.net.wifi.CONFIGURED_NETWORKS_CHANGE";
     field public static final int DEVICE_MOBILITY_STATE_HIGH_MVMT = 1; // 0x1
     field public static final int DEVICE_MOBILITY_STATE_LOW_MVMT = 2; // 0x2
     field public static final int DEVICE_MOBILITY_STATE_STATIONARY = 3; // 0x3
     field public static final int DEVICE_MOBILITY_STATE_UNKNOWN = 0; // 0x0
+    field public static final int EASY_CONNECT_CRYPTOGRAPHY_CURVE_BRAINPOOLP256R1 = 3; // 0x3
+    field public static final int EASY_CONNECT_CRYPTOGRAPHY_CURVE_BRAINPOOLP384R1 = 4; // 0x4
+    field public static final int EASY_CONNECT_CRYPTOGRAPHY_CURVE_BRAINPOOLP512R1 = 5; // 0x5
+    field public static final int EASY_CONNECT_CRYPTOGRAPHY_CURVE_PRIME256V1 = 0; // 0x0
+    field public static final int EASY_CONNECT_CRYPTOGRAPHY_CURVE_SECP384R1 = 1; // 0x1
+    field public static final int EASY_CONNECT_CRYPTOGRAPHY_CURVE_SECP521R1 = 2; // 0x2
     field public static final int EASY_CONNECT_NETWORK_ROLE_AP = 1; // 0x1
     field public static final int EASY_CONNECT_NETWORK_ROLE_STA = 0; // 0x0
     field public static final String EXTRA_CHANGE_REASON = "changeReason";
-    field public static final String EXTRA_LINK_PROPERTIES = "android.net.wifi.extra.LINK_PROPERTIES";
-    field public static final String EXTRA_MULTIPLE_NETWORKS_CHANGED = "multipleChanges";
+    field @Deprecated public static final String EXTRA_LINK_PROPERTIES = "android.net.wifi.extra.LINK_PROPERTIES";
+    field @Deprecated public static final String EXTRA_MULTIPLE_NETWORKS_CHANGED = "multipleChanges";
     field public static final String EXTRA_OSU_NETWORK = "android.net.wifi.extra.OSU_NETWORK";
     field public static final String EXTRA_PREVIOUS_WIFI_AP_STATE = "previous_wifi_state";
     field public static final String EXTRA_URL = "android.net.wifi.extra.URL";
@@ -506,7 +634,7 @@
     field public static final String EXTRA_WIFI_AP_INTERFACE_NAME = "android.net.wifi.extra.WIFI_AP_INTERFACE_NAME";
     field public static final String EXTRA_WIFI_AP_MODE = "android.net.wifi.extra.WIFI_AP_MODE";
     field public static final String EXTRA_WIFI_AP_STATE = "wifi_state";
-    field public static final String EXTRA_WIFI_CONFIGURATION = "wifiConfiguration";
+    field @Deprecated public static final String EXTRA_WIFI_CONFIGURATION = "wifiConfiguration";
     field public static final String EXTRA_WIFI_CREDENTIAL_EVENT_TYPE = "et";
     field public static final String EXTRA_WIFI_CREDENTIAL_SSID = "ssid";
     field public static final int IFACE_IP_MODE_CONFIGURATION_ERROR = 0; // 0x0
@@ -520,6 +648,9 @@
     field public static final int SAP_START_FAILURE_GENERAL = 0; // 0x0
     field public static final int SAP_START_FAILURE_NO_CHANNEL = 1; // 0x1
     field public static final int SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION = 2; // 0x2
+    field public static final int VERBOSE_LOGGING_LEVEL_DISABLED = 0; // 0x0
+    field public static final int VERBOSE_LOGGING_LEVEL_ENABLED = 1; // 0x1
+    field public static final int VERBOSE_LOGGING_LEVEL_ENABLED_SHOW_KEY = 2; // 0x2
     field public static final String WIFI_AP_STATE_CHANGED_ACTION = "android.net.wifi.WIFI_AP_STATE_CHANGED";
     field public static final int WIFI_AP_STATE_DISABLED = 11; // 0xb
     field public static final int WIFI_AP_STATE_DISABLING = 10; // 0xa
@@ -536,6 +667,11 @@
     method public void onSuccess();
   }
 
+  public abstract static class WifiManager.CoexCallback {
+    ctor public WifiManager.CoexCallback();
+    method public abstract void onCoexUnsafeChannelsChanged(@NonNull java.util.List<android.net.wifi.CoexUnsafeChannel>, int);
+  }
+
   public static interface WifiManager.NetworkRequestMatchCallback {
     method public default void onAbort();
     method public default void onMatch(@NonNull java.util.List<android.net.wifi.ScanResult>);
@@ -558,15 +694,20 @@
   }
 
   public static interface WifiManager.ScoreUpdateObserver {
+    method public default void blocklistCurrentBssid(int);
     method public void notifyScoreUpdate(int, int);
+    method public default void notifyStatusUpdate(int, boolean);
+    method public default void requestNudOperation(int);
     method public void triggerUpdateOfWifiUsabilityStats(int);
   }
 
   public static interface WifiManager.SoftApCallback {
     method public default void onBlockedClientConnecting(@NonNull android.net.wifi.WifiClient, int);
     method public default void onCapabilityChanged(@NonNull android.net.wifi.SoftApCapability);
-    method public default void onConnectedClientsChanged(@NonNull java.util.List<android.net.wifi.WifiClient>);
-    method public default void onInfoChanged(@NonNull android.net.wifi.SoftApInfo);
+    method @Deprecated public default void onConnectedClientsChanged(@NonNull java.util.List<android.net.wifi.WifiClient>);
+    method public default void onConnectedClientsChanged(@NonNull android.net.wifi.SoftApInfo, @NonNull java.util.List<android.net.wifi.WifiClient>);
+    method @Deprecated public default void onInfoChanged(@NonNull android.net.wifi.SoftApInfo);
+    method public default void onInfoChanged(@NonNull java.util.List<android.net.wifi.SoftApInfo>);
     method public default void onStateChanged(int, int);
   }
 
@@ -580,27 +721,37 @@
 
   public static interface WifiManager.WifiConnectedNetworkScorer {
     method public void onSetScoreUpdateObserver(@NonNull android.net.wifi.WifiManager.ScoreUpdateObserver);
-    method public void onStart(int);
+    method @Deprecated public default void onStart(int);
+    method public default void onStart(@NonNull android.net.wifi.WifiConnectedSessionInfo);
     method public void onStop(int);
   }
 
-  public class WifiNetworkConnectionStatistics implements android.os.Parcelable {
-    ctor public WifiNetworkConnectionStatistics(int, int);
-    ctor public WifiNetworkConnectionStatistics();
-    ctor public WifiNetworkConnectionStatistics(android.net.wifi.WifiNetworkConnectionStatistics);
-    method public int describeContents();
-    method public void writeToParcel(android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.WifiNetworkConnectionStatistics> CREATOR;
-    field public int numConnection;
-    field public int numUsage;
+  public static interface WifiManager.WifiVerboseLoggingStatusChangedListener {
+    method public void onWifiVerboseLoggingStatusChanged(boolean);
+  }
+
+  @Deprecated public class WifiNetworkConnectionStatistics implements android.os.Parcelable {
+    ctor @Deprecated public WifiNetworkConnectionStatistics(int, int);
+    ctor @Deprecated public WifiNetworkConnectionStatistics();
+    ctor @Deprecated public WifiNetworkConnectionStatistics(android.net.wifi.WifiNetworkConnectionStatistics);
+    method @Deprecated public int describeContents();
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
+    field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.WifiNetworkConnectionStatistics> CREATOR;
+    field @Deprecated public int numConnection;
+    field @Deprecated public int numUsage;
   }
 
   public final class WifiNetworkSuggestion implements android.os.Parcelable {
+    method public int getCarrierId();
     method @NonNull public android.net.wifi.WifiConfiguration getWifiConfiguration();
+    method public boolean isOemPaid();
+    method public boolean isOemPrivate();
   }
 
   public static final class WifiNetworkSuggestion.Builder {
     method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING) public android.net.wifi.WifiNetworkSuggestion.Builder setCarrierId(int);
+    method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setOemPaid(boolean);
+    method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setOemPrivate(boolean);
   }
 
   public class WifiScanner {
@@ -643,10 +794,14 @@
     field public static final int WIFI_BAND_5_GHZ = 2; // 0x2
     field public static final int WIFI_BAND_5_GHZ_DFS_ONLY = 4; // 0x4
     field public static final int WIFI_BAND_5_GHZ_WITH_DFS = 6; // 0x6
+    field public static final int WIFI_BAND_60_GHZ = 16; // 0x10
     field public static final int WIFI_BAND_6_GHZ = 8; // 0x8
     field public static final int WIFI_BAND_BOTH = 3; // 0x3
     field public static final int WIFI_BAND_BOTH_WITH_DFS = 7; // 0x7
     field public static final int WIFI_BAND_UNSPECIFIED = 0; // 0x0
+    field public static final int WIFI_RNR_ENABLED = 1; // 0x1
+    field public static final int WIFI_RNR_ENABLED_IF_WIFI_BAND_6_GHZ_SCANNED = 0; // 0x0
+    field public static final int WIFI_RNR_NOT_NEEDED = 2; // 0x2
   }
 
   public static interface WifiScanner.ActionListener {
@@ -696,6 +851,7 @@
     method public int getFlags();
     method public int getId();
     method public android.net.wifi.ScanResult[] getResults();
+    method public int getScannedBands();
   }
 
   public static interface WifiScanner.ScanListener extends android.net.wifi.WifiScanner.ActionListener {
@@ -706,6 +862,10 @@
 
   public static class WifiScanner.ScanSettings implements android.os.Parcelable {
     ctor public WifiScanner.ScanSettings();
+    method public int getRnrSetting();
+    method public boolean is6GhzPscOnlyEnabled();
+    method public void set6GhzPscOnlyEnabled(boolean);
+    method public void setRnrSetting(int);
     field public int band;
     field public android.net.wifi.WifiScanner.ChannelSpec[] channels;
     field @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public final java.util.List<android.net.wifi.WifiScanner.ScanSettings.HiddenNetwork> hiddenNetworks;
@@ -745,12 +905,16 @@
     method public int getCellularDataNetworkType();
     method public int getCellularSignalStrengthDb();
     method public int getCellularSignalStrengthDbm();
+    method @IntRange(from=0, to=255) public int getChannelUtilizationRatio();
+    method @NonNull public android.net.wifi.WifiUsabilityStatsEntry.ContentionTimeStats getContentionTimeStats(int);
     method public int getLinkSpeedMbps();
     method public int getProbeElapsedTimeSinceLastUpdateMillis();
     method public int getProbeMcsRateSinceLastUpdate();
     method public int getProbeStatusSinceLastUpdate();
+    method @NonNull public java.util.List<android.net.wifi.WifiUsabilityStatsEntry.RateStats> getRateStats();
     method public int getRssi();
     method public int getRxLinkSpeedMbps();
+    method @IntRange(from=0, to=100) public int getTimeSliceDutyCycleInPercent();
     method public long getTimeStampMillis();
     method public long getTotalBackgroundScanTimeMillis();
     method public long getTotalBeaconRx();
@@ -768,13 +932,85 @@
     method public long getTotalTxBad();
     method public long getTotalTxRetries();
     method public long getTotalTxSuccess();
+    method @NonNull public java.util.List<android.net.wifi.WifiUsabilityStatsEntry.RadioStats> getWifiLinkLayerRadioStats();
+    method public boolean isCellularDataAvailable();
     method public boolean isSameRegisteredCell();
+    method public boolean isThroughputSufficient();
+    method public boolean isWifiScoringEnabled();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.WifiUsabilityStatsEntry> CREATOR;
+    field public static final int NUM_WME_ACCESS_CATEGORIES = 4; // 0x4
     field public static final int PROBE_STATUS_FAILURE = 3; // 0x3
     field public static final int PROBE_STATUS_NO_PROBE = 1; // 0x1
     field public static final int PROBE_STATUS_SUCCESS = 2; // 0x2
     field public static final int PROBE_STATUS_UNKNOWN = 0; // 0x0
+    field public static final int WIFI_BANDWIDTH_10_MHZ = 6; // 0x6
+    field public static final int WIFI_BANDWIDTH_160_MHZ = 3; // 0x3
+    field public static final int WIFI_BANDWIDTH_20_MHZ = 0; // 0x0
+    field public static final int WIFI_BANDWIDTH_40_MHZ = 1; // 0x1
+    field public static final int WIFI_BANDWIDTH_5_MHZ = 5; // 0x5
+    field public static final int WIFI_BANDWIDTH_80P80_MHZ = 4; // 0x4
+    field public static final int WIFI_BANDWIDTH_80_MHZ = 2; // 0x2
+    field public static final int WIFI_BANDWIDTH_INVALID = -1; // 0xffffffff
+    field public static final int WIFI_PREAMBLE_CCK = 1; // 0x1
+    field public static final int WIFI_PREAMBLE_HE = 5; // 0x5
+    field public static final int WIFI_PREAMBLE_HT = 2; // 0x2
+    field public static final int WIFI_PREAMBLE_INVALID = -1; // 0xffffffff
+    field public static final int WIFI_PREAMBLE_OFDM = 0; // 0x0
+    field public static final int WIFI_PREAMBLE_VHT = 3; // 0x3
+    field public static final int WIFI_SPATIAL_STREAMS_FOUR = 4; // 0x4
+    field public static final int WIFI_SPATIAL_STREAMS_INVALID = -1; // 0xffffffff
+    field public static final int WIFI_SPATIAL_STREAMS_ONE = 1; // 0x1
+    field public static final int WIFI_SPATIAL_STREAMS_THREE = 3; // 0x3
+    field public static final int WIFI_SPATIAL_STREAMS_TWO = 2; // 0x2
+    field public static final int WME_ACCESS_CATEGORY_BE = 0; // 0x0
+    field public static final int WME_ACCESS_CATEGORY_BK = 1; // 0x1
+    field public static final int WME_ACCESS_CATEGORY_VI = 2; // 0x2
+    field public static final int WME_ACCESS_CATEGORY_VO = 3; // 0x3
+  }
+
+  public static final class WifiUsabilityStatsEntry.ContentionTimeStats implements android.os.Parcelable {
+    ctor public WifiUsabilityStatsEntry.ContentionTimeStats(long, long, long, long);
+    method public int describeContents();
+    method public long getContentionNumSamples();
+    method public long getContentionTimeAvgMicros();
+    method public long getContentionTimeMaxMicros();
+    method public long getContentionTimeMinMicros();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.WifiUsabilityStatsEntry.ContentionTimeStats> CREATOR;
+  }
+
+  public static final class WifiUsabilityStatsEntry.RadioStats implements android.os.Parcelable {
+    ctor public WifiUsabilityStatsEntry.RadioStats(int, long, long, long, long, long, long, long, long, long);
+    method public int describeContents();
+    method public long getRadioId();
+    method public long getTotalBackgroundScanTimeMillis();
+    method public long getTotalHotspot2ScanTimeMillis();
+    method public long getTotalNanScanTimeMillis();
+    method public long getTotalPnoScanTimeMillis();
+    method public long getTotalRadioOnTimeMillis();
+    method public long getTotalRadioRxTimeMillis();
+    method public long getTotalRadioTxTimeMillis();
+    method public long getTotalRoamScanTimeMillis();
+    method public long getTotalScanTimeMillis();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.WifiUsabilityStatsEntry.RadioStats> CREATOR;
+  }
+
+  public static final class WifiUsabilityStatsEntry.RateStats implements android.os.Parcelable {
+    ctor public WifiUsabilityStatsEntry.RateStats(int, int, int, int, int, int, int, int, int);
+    method public int describeContents();
+    method public int getBandwidthInMhz();
+    method public int getBitRateInKbps();
+    method public int getMpduLost();
+    method public int getNumberOfSpatialStreams();
+    method public int getPreamble();
+    method public int getRateMcsIdx();
+    method public int getRetries();
+    method public int getRxMpdu();
+    method public int getTxMpdu();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.WifiUsabilityStatsEntry.RateStats> CREATOR;
   }
 
 }
@@ -785,8 +1021,12 @@
     method @Deprecated public android.net.NetworkSpecifier createNetworkSpecifierPmk(@NonNull android.net.wifi.aware.PeerHandle, @NonNull byte[]);
   }
 
+  public class WifiAwareManager {
+    method public void enableInstantCommunicationMode(boolean);
+  }
+
   public class WifiAwareSession implements java.lang.AutoCloseable {
-    method public android.net.NetworkSpecifier createNetworkSpecifierPmk(int, @NonNull byte[], @NonNull byte[]);
+    method @Deprecated public android.net.NetworkSpecifier createNetworkSpecifierPmk(int, @NonNull byte[], @NonNull byte[]);
   }
 
 }
@@ -883,6 +1123,10 @@
 
 package android.net.wifi.rtt {
 
+  public final class RangingRequest implements android.os.Parcelable {
+    method @NonNull public java.util.List<android.net.wifi.rtt.ResponderConfig> getRttResponders();
+  }
+
   public static final class RangingRequest.Builder {
     method public android.net.wifi.rtt.RangingRequest.Builder addResponder(@NonNull android.net.wifi.rtt.ResponderConfig);
   }
diff --git a/framework/api/system-lint-baseline.txt b/framework/api/system-lint-baseline.txt
index 6547ee8..4fb6ff0 100644
--- a/framework/api/system-lint-baseline.txt
+++ b/framework/api/system-lint-baseline.txt
@@ -1,6 +1,15 @@
 // Baseline format: 1.0
 MissingGetterMatchingBuilder: android.net.wifi.rtt.RangingRequest.Builder#addResponder(android.net.wifi.rtt.ResponderConfig):
-    android.net.wifi.rtt.RangingRequest does not declare a `getResponders()` method matching method android.net.wifi.rtt.RangingRequest.Builder.addResponder(android.net.wifi.rtt.ResponderConfig)
+    
+
 
 MissingNullability: android.net.wifi.rtt.RangingRequest.Builder#addResponder(android.net.wifi.rtt.ResponderConfig):
+    
 
+
+MutableBareField: android.net.wifi.WifiConfiguration#carrierMerged:
+    
+MutableBareField: android.net.wifi.WifiConfiguration#lastConnectUid:
+    Bare field lastConnectUid must be marked final, or moved behind accessors if mutable
+MutableBareField: android.net.wifi.WifiConfiguration#subscriptionId:
+    Bare field subscriptionId must be marked final, or moved behind accessors if mutable
diff --git a/framework/jarjar-rules.txt b/framework/jarjar-rules.txt
index 8722a10..a36b453 100644
--- a/framework/jarjar-rules.txt
+++ b/framework/jarjar-rules.txt
@@ -110,8 +110,6 @@
 rule fi.iki.elonen.** com.android.wifi.x.@0
 
 ## used by both framework-wifi and service-wifi ##
-rule android.content.pm.BaseParceledListSlice* com.android.wifi.x.@0
-rule android.content.pm.ParceledListSlice* com.android.wifi.x.@0
 rule android.telephony.Annotation* com.android.wifi.x.@0
 rule com.android.internal.util.AsyncChannel* com.android.wifi.x.@0
 rule com.android.internal.util.AsyncService* com.android.wifi.x.@0
diff --git a/framework/java/android/net/wifi/CoexUnsafeChannel.java b/framework/java/android/net/wifi/CoexUnsafeChannel.java
new file mode 100644
index 0000000..66cd473
--- /dev/null
+++ b/framework/java/android/net/wifi/CoexUnsafeChannel.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import static android.net.wifi.WifiScanner.WIFI_BAND_24_GHZ;
+import static android.net.wifi.WifiScanner.WIFI_BAND_5_GHZ;
+import static android.net.wifi.WifiScanner.WIFI_BAND_6_GHZ;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.modules.utils.build.SdkLevel;
+
+import java.util.Objects;
+
+/**
+ * Data structure class representing a Wi-Fi channel that would cause interference to/receive
+ * interference from the active cellular channels and should be avoided.
+ *
+ * @hide
+ */
+@SystemApi
+@RequiresApi(Build.VERSION_CODES.S)
+public final class CoexUnsafeChannel implements Parcelable {
+    public static final int POWER_CAP_NONE = Integer.MAX_VALUE;
+
+    private @WifiAnnotations.WifiBandBasic int mBand;
+    private int mChannel;
+    private int mPowerCapDbm;
+
+    /**
+     * Constructor for a CoexUnsafeChannel with no power cap specified.
+     * @param band One of {@link WifiAnnotations.WifiBandBasic}
+     * @param channel Channel number
+     */
+    public CoexUnsafeChannel(@WifiAnnotations.WifiBandBasic int band, int channel) {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        mBand = band;
+        mChannel = channel;
+        mPowerCapDbm = POWER_CAP_NONE;
+    }
+
+    /**
+     * Constructor for a CoexUnsafeChannel with power cap specified.
+     * @param band One of {@link WifiAnnotations.WifiBandBasic}
+     * @param channel Channel number
+     * @param powerCapDbm Power cap in dBm
+     */
+    public CoexUnsafeChannel(@WifiAnnotations.WifiBandBasic int band, int channel,
+            int powerCapDbm) {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        mBand = band;
+        mChannel = channel;
+        mPowerCapDbm = powerCapDbm;
+    }
+
+    /** Returns the Wi-Fi band of this channel as one of {@link WifiAnnotations.WifiBandBasic} */
+    public @WifiAnnotations.WifiBandBasic int getBand() {
+        return mBand;
+    }
+
+    /** Returns the channel number of this channel. */
+    public int getChannel() {
+        return mChannel;
+    }
+
+    /**
+     * Returns the power cap of this channel in dBm or {@link CoexUnsafeChannel#POWER_CAP_NONE}
+     * if the power cap is not specified.
+     */
+    public int getPowerCapDbm() {
+        return mPowerCapDbm;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        CoexUnsafeChannel that = (CoexUnsafeChannel) o;
+        return mBand == that.mBand
+                && mChannel == that.mChannel
+                && mPowerCapDbm == that.mPowerCapDbm;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mBand, mChannel, mPowerCapDbm);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sj = new StringBuilder("CoexUnsafeChannel{");
+        if (mBand == WIFI_BAND_24_GHZ) {
+            sj.append("2.4GHz");
+        } else if (mBand == WIFI_BAND_5_GHZ) {
+            sj.append("5GHz");
+        } else if (mBand == WIFI_BAND_6_GHZ) {
+            sj.append("6GHz");
+        } else {
+            sj.append("UNKNOWN BAND");
+        }
+        sj.append(", ").append(mChannel);
+        if (mPowerCapDbm != POWER_CAP_NONE) {
+            sj.append(", ").append(mPowerCapDbm).append("dBm");
+        }
+        sj.append('}');
+        return sj.toString();
+    }
+
+    /** Implement the Parcelable interface {@hide} */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Implement the Parcelable interface {@hide} */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mBand);
+        dest.writeInt(mChannel);
+        dest.writeInt(mPowerCapDbm);
+    }
+
+    /** Implement the Parcelable interface */
+    public static final @NonNull Creator<CoexUnsafeChannel> CREATOR =
+            new Creator<CoexUnsafeChannel>() {
+                public CoexUnsafeChannel createFromParcel(Parcel in) {
+                    final int band = in.readInt();
+                    final int channel = in.readInt();
+                    final int powerCapDbm = in.readInt();
+                    return new CoexUnsafeChannel(band, channel, powerCapDbm);
+                }
+
+                public CoexUnsafeChannel[] newArray(int size) {
+                    return new CoexUnsafeChannel[size];
+                }
+            };
+}
diff --git a/framework/java/android/net/wifi/EasyConnectStatusCallback.java b/framework/java/android/net/wifi/EasyConnectStatusCallback.java
index 6c2e6dd..729dbfc 100644
--- a/framework/java/android/net/wifi/EasyConnectStatusCallback.java
+++ b/framework/java/android/net/wifi/EasyConnectStatusCallback.java
@@ -20,8 +20,12 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.net.Uri;
+import android.os.Build;
 import android.util.SparseArray;
 
+import androidx.annotation.RequiresApi;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.concurrent.Executor;
@@ -161,6 +165,20 @@
      */
     public static final int EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION = -12;
 
+    /**
+     * Easy Connect Failure event: System failed to generate DPP URI.
+     */
+    public static final int EASY_CONNECT_EVENT_FAILURE_URI_GENERATION = -13;
+
+    /**
+     * Easy Connect Failure event: Enrollee didn't scan the network's operating channel.
+     * This error is generated when framework finds that Network's operating channel
+     * is not included in the list of channels the Enrollee scanned in attempting to
+     * discover the network prior to connection.
+     */
+    public static final int EASY_CONNECT_EVENT_FAILURE_ENROLLEE_FAILED_TO_SCAN_NETWORK_CHANNEL =
+            -14;
+
     /** @hide */
     @IntDef(prefix = {"EASY_CONNECT_EVENT_FAILURE_"}, value = {
             EASY_CONNECT_EVENT_FAILURE_INVALID_URI,
@@ -175,6 +193,8 @@
             EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK,
             EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION,
             EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION,
+            EASY_CONNECT_EVENT_FAILURE_URI_GENERATION,
+            EASY_CONNECT_EVENT_FAILURE_ENROLLEE_FAILED_TO_SCAN_NETWORK_CHANNEL,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface EasyConnectFailureStatusCode {
@@ -264,4 +284,18 @@
      */
     @SystemApi
     public abstract void onProgress(@EasyConnectProgressStatusCode int code);
+
+    /**
+     * Called when local Easy Connect Responder successfully generates a DPP URI from
+     * the supplicant. This callback is the first successful outcome
+     * of a Easy Connect Responder flow starting with
+     * {@link WifiManager#startEasyConnectAsEnrolleeResponder(String, int, Executor,
+     * EasyConnectStatusCallback)} .
+     *
+     * @param dppUri DPP URI from the supplicant.
+     * @hide
+     */
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.S)
+    public void onBootstrapUriGenerated(@NonNull Uri dppUri) {};
 }
diff --git a/framework/java/android/net/wifi/ITxPacketCountListener.aidl b/framework/java/android/net/wifi/ICoexCallback.aidl
similarity index 68%
copy from framework/java/android/net/wifi/ITxPacketCountListener.aidl
copy to framework/java/android/net/wifi/ICoexCallback.aidl
index 9105bd0..d4e62c0 100644
--- a/framework/java/android/net/wifi/ITxPacketCountListener.aidl
+++ b/framework/java/android/net/wifi/ICoexCallback.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,13 +16,13 @@
 
 package android.net.wifi;
 
+import android.net.wifi.CoexUnsafeChannel;
+
 /**
- * Interface for tx packet counter callback.
- * @deprecated no longer used, remove once removed from BaseWifiService
+ * Interface for Wi-Fi/cellular coex callback.
  * @hide
  */
-oneway interface ITxPacketCountListener
+oneway interface ICoexCallback
 {
-    void onSuccess(int count);
-    void onFailure(int reason);
+    void onCoexUnsafeChannelsChanged(in List<CoexUnsafeChannel> unsafeChannels, int restrictions);
 }
diff --git a/framework/java/android/net/wifi/IDppCallback.aidl b/framework/java/android/net/wifi/IDppCallback.aidl
index d7a958a..dcbe846 100644
--- a/framework/java/android/net/wifi/IDppCallback.aidl
+++ b/framework/java/android/net/wifi/IDppCallback.aidl
@@ -45,4 +45,10 @@
      * to show progress.
      */
     void onProgress(int status);
+
+    /**
+     * Called when local DPP Responder successfully generates a URI.
+     */
+    void onBootstrapUriGenerated(String uri);
+
 }
diff --git a/framework/java/android/net/wifi/IScoreUpdateObserver.aidl b/framework/java/android/net/wifi/IScoreUpdateObserver.aidl
index 775fed7..00c1807 100644
--- a/framework/java/android/net/wifi/IScoreUpdateObserver.aidl
+++ b/framework/java/android/net/wifi/IScoreUpdateObserver.aidl
@@ -26,4 +26,10 @@
     void notifyScoreUpdate(int sessionId, int score);
 
     void triggerUpdateOfWifiUsabilityStats(int sessionId);
+
+    void notifyStatusUpdate(int sessionId, boolean isUsable);
+
+    void requestNudOperation(int sessionId);
+
+    void blocklistCurrentBssid(int sessionId);
 }
diff --git a/framework/java/android/net/wifi/ISoftApCallback.aidl b/framework/java/android/net/wifi/ISoftApCallback.aidl
index f81bcb9..3db0a5d 100644
--- a/framework/java/android/net/wifi/ISoftApCallback.aidl
+++ b/framework/java/android/net/wifi/ISoftApCallback.aidl
@@ -40,19 +40,16 @@
     void onStateChanged(int state, int failureReason);
 
     /**
-     * Service to manager callback providing connected client's information.
+     * Service to manager callback providing informations of softap.
      *
-     * @param clients the currently connected clients
+     * @param infos The currently {@link SoftApInfo} in each AP instance.
+     * @param clients The currently connected clients in each AP instance.
+     * @param isBridged whether or not the current AP enabled on bridged mode.
+     * @param isRegistration whether or not the callbackk was triggered when register.
      */
-    void onConnectedClientsChanged(in List<WifiClient> clients);
-
-    /**
-     * Service to manager callback providing information of softap.
-     *
-     * @param softApInfo is the softap information. {@link SoftApInfo}
-     */
-    void onInfoChanged(in SoftApInfo softApInfo);
-
+    void onConnectedClientsOrInfoChanged(in Map<String, SoftApInfo> infos,
+            in Map<String, List<WifiClient>> clients, boolean isBridged,
+	    boolean isRegistration);
 
     /**
      * Service to manager callback providing capability of softap.
diff --git a/framework/java/android/net/wifi/ITxPacketCountListener.aidl b/framework/java/android/net/wifi/ISubsystemRestartCallback.aidl
similarity index 69%
copy from framework/java/android/net/wifi/ITxPacketCountListener.aidl
copy to framework/java/android/net/wifi/ISubsystemRestartCallback.aidl
index 9105bd0..e9f800c 100644
--- a/framework/java/android/net/wifi/ITxPacketCountListener.aidl
+++ b/framework/java/android/net/wifi/ISubsystemRestartCallback.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,12 +17,12 @@
 package android.net.wifi;
 
 /**
- * Interface for tx packet counter callback.
- * @deprecated no longer used, remove once removed from BaseWifiService
+ * Interface for Wi-Fi subsystem restart callback.
  * @hide
  */
-oneway interface ITxPacketCountListener
+oneway interface ISubsystemRestartCallback
 {
-    void onSuccess(int count);
-    void onFailure(int reason);
+    void onSubsystemRestarting();
+
+    void onSubsystemRestarted();
 }
diff --git a/framework/java/android/net/wifi/ITxPacketCountListener.aidl b/framework/java/android/net/wifi/ISuggestionUserApprovalStatusListener.aidl
similarity index 69%
rename from framework/java/android/net/wifi/ITxPacketCountListener.aidl
rename to framework/java/android/net/wifi/ISuggestionUserApprovalStatusListener.aidl
index 9105bd0..24ceba5 100644
--- a/framework/java/android/net/wifi/ITxPacketCountListener.aidl
+++ b/framework/java/android/net/wifi/ISuggestionUserApprovalStatusListener.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,12 +17,11 @@
 package android.net.wifi;
 
 /**
- * Interface for tx packet counter callback.
- * @deprecated no longer used, remove once removed from BaseWifiService
+ * Interface for suggestion user approval status listener.
+ *
  * @hide
  */
-oneway interface ITxPacketCountListener
+oneway interface ISuggestionUserApprovalStatusListener
 {
-    void onSuccess(int count);
-    void onFailure(int reason);
+   void onUserApprovalStatusChange(int status);
 }
diff --git a/framework/java/android/net/wifi/IWifiConnectedNetworkScorer.aidl b/framework/java/android/net/wifi/IWifiConnectedNetworkScorer.aidl
index f96d037..b6fa85b 100644
--- a/framework/java/android/net/wifi/IWifiConnectedNetworkScorer.aidl
+++ b/framework/java/android/net/wifi/IWifiConnectedNetworkScorer.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
 package android.net.wifi;
 
 import android.net.wifi.IScoreUpdateObserver;
+import android.net.wifi.WifiConnectedSessionInfo;
 
 /**
  * Interface for Wi-Fi connected network scorer.
@@ -25,7 +26,7 @@
  */
 oneway interface IWifiConnectedNetworkScorer
 {
-    void onStart(int sessionId);
+    void onStart(in WifiConnectedSessionInfo sessionInfo);
 
     void onStop(int sessionId);
 
diff --git a/framework/java/android/net/wifi/IWifiManager.aidl b/framework/java/android/net/wifi/IWifiManager.aidl
index 3f79364..b5048db 100644
--- a/framework/java/android/net/wifi/IWifiManager.aidl
+++ b/framework/java/android/net/wifi/IWifiManager.aidl
@@ -16,15 +16,15 @@
 
 package android.net.wifi;
 
-import android.content.pm.ParceledListSlice;
-
 import android.net.wifi.hotspot2.OsuProvider;
 import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.net.wifi.hotspot2.IProvisioningCallback;
 
 import android.net.DhcpInfo;
 import android.net.Network;
+import android.net.wifi.CoexUnsafeChannel;
 import android.net.wifi.IActionListener;
+import android.net.wifi.ICoexCallback;
 import android.net.wifi.IDppCallback;
 import android.net.wifi.ILocalOnlyHotspotCallback;
 import android.net.wifi.INetworkRequestMatchCallback;
@@ -32,19 +32,26 @@
 import android.net.wifi.IOnWifiUsabilityStatsListener;
 import android.net.wifi.IScanResultsCallback;
 import android.net.wifi.ISoftApCallback;
+import android.net.wifi.ISubsystemRestartCallback;
 import android.net.wifi.ISuggestionConnectionStatusListener;
+import android.net.wifi.ISuggestionUserApprovalStatusListener;
 import android.net.wifi.ITrafficStateCallback;
 import android.net.wifi.IWifiConnectedNetworkScorer;
+import android.net.wifi.IWifiVerboseLoggingStatusChangedListener;
 import android.net.wifi.ScanResult;
 import android.net.wifi.SoftApConfiguration;
+import android.net.wifi.WifiAvailableChannel;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
 import android.net.wifi.WifiNetworkSuggestion;
 
 import android.os.Messenger;
 import android.os.ResultReceiver;
 import android.os.WorkSource;
 
+import com.android.modules.utils.ParceledListSlice;
+
 /**
  * Interface that allows controlling and querying Wi-Fi connectivity.
  *
@@ -56,7 +63,7 @@
 
     oneway void getWifiActivityEnergyInfoAsync(in IOnWifiActivityEnergyInfoListener listener);
 
-    ParceledListSlice getConfiguredNetworks(String packageName, String featureId);
+    ParceledListSlice getConfiguredNetworks(String packageName, String featureId, boolean callerNetworksOnly);
 
     ParceledListSlice getPrivilegedConfiguredNetworks(String packageName, String featureId);
 
@@ -68,6 +75,8 @@
 
     int addOrUpdateNetwork(in WifiConfiguration config, String packageName);
 
+    WifiManager.AddNetworkResult addOrUpdateNetworkPrivileged(in WifiConfiguration config, String packageName);
+
     boolean addOrUpdatePasspointConfiguration(in PasspointConfiguration config, String packageName);
 
     boolean removePasspointConfiguration(in String fqdn, String packageName);
@@ -80,10 +89,10 @@
 
     int matchProviderWithCurrentNetwork(String fqdn);
 
-    void deauthenticateNetwork(long holdoff, boolean ess);
-
     boolean removeNetwork(int netId, String packageName);
 
+    boolean removeNonCallerConfiguredNetworks(String packageName);
+
     boolean enableNetwork(int netId, boolean disableOthers, String packageName);
 
     boolean disableNetwork(int netId, String packageName);
@@ -116,15 +125,25 @@
 
     String getCountryCode();
 
+    void setOverrideCountryCode(String country);
+
+    void clearOverrideCountryCode();
+
+    void setDefaultCountryCode(String country);
+
+    boolean is24GHzBandSupported();
+
     boolean is5GHzBandSupported();
 
     boolean is6GHzBandSupported();
 
+    boolean is60GHzBandSupported();
+
     boolean isWifiStandardSupported(int standard);
 
-    DhcpInfo getDhcpInfo();
+    DhcpInfo getDhcpInfo(String packageName);
 
-    void setScanAlwaysAvailable(boolean isAvailable);
+    void setScanAlwaysAvailable(boolean isAvailable, String packageName);
 
     boolean isScanAlwaysAvailable();
 
@@ -144,9 +163,17 @@
 
     void updateInterfaceIpState(String ifaceName, int mode);
 
-    boolean startSoftAp(in WifiConfiguration wifiConfig);
+    boolean isDefaultCoexAlgorithmEnabled();
 
-    boolean startTetheredHotspot(in SoftApConfiguration softApConfig);
+    void setCoexUnsafeChannels(in List<CoexUnsafeChannel> unsafeChannels, int mandatoryRestrictions);
+
+    void registerCoexCallback(in ICoexCallback callback);
+
+    void unregisterCoexCallback(in ICoexCallback callback);
+
+    boolean startSoftAp(in WifiConfiguration wifiConfig, String packageName);
+
+    boolean startTetheredHotspot(in SoftApConfiguration softApConfig, String packageName);
 
     boolean stopSoftAp();
 
@@ -202,21 +229,25 @@
 
     void startSubscriptionProvisioning(in OsuProvider provider, in IProvisioningCallback callback);
 
-    void registerSoftApCallback(in IBinder binder, in ISoftApCallback callback, int callbackIdentifier);
+    void registerSoftApCallback(in ISoftApCallback callback);
 
-    void unregisterSoftApCallback(int callbackIdentifier);
+    void unregisterSoftApCallback(in ISoftApCallback callback);
 
-    void addOnWifiUsabilityStatsListener(in IBinder binder, in IOnWifiUsabilityStatsListener listener, int listenerIdentifier);
+    void addWifiVerboseLoggingStatusChangedListener(in IWifiVerboseLoggingStatusChangedListener listener);
 
-    void removeOnWifiUsabilityStatsListener(int listenerIdentifier);
+    void removeWifiVerboseLoggingStatusChangedListener(in IWifiVerboseLoggingStatusChangedListener listener);
 
-    void registerTrafficStateCallback(in IBinder binder, in ITrafficStateCallback callback, int callbackIdentifier);
+    void addOnWifiUsabilityStatsListener(in IOnWifiUsabilityStatsListener listener);
 
-    void unregisterTrafficStateCallback(int callbackIdentifier);
+    void removeOnWifiUsabilityStatsListener(in IOnWifiUsabilityStatsListener listener);
 
-    void registerNetworkRequestMatchCallback(in IBinder binder, in INetworkRequestMatchCallback callback, int callbackIdentifier);
+    void registerTrafficStateCallback(in ITrafficStateCallback callback);
 
-    void unregisterNetworkRequestMatchCallback(int callbackIdentifier);
+    void unregisterTrafficStateCallback(in ITrafficStateCallback callback);
+
+    void registerNetworkRequestMatchCallback(in INetworkRequestMatchCallback callback);
+
+    void unregisterNetworkRequestMatchCallback(in INetworkRequestMatchCallback callback);
 
     int addNetworkSuggestions(in List<WifiNetworkSuggestion> networkSuggestions, in String packageName,
         in String featureId);
@@ -229,29 +260,32 @@
 
     void setDeviceMobilityState(int state);
 
-    void startDppAsConfiguratorInitiator(in IBinder binder, in String enrolleeUri,
-        int selectedNetworkId, int netRole, in IDppCallback callback);
+    void startDppAsConfiguratorInitiator(in IBinder binder, in String packageName,
+        in String enrolleeUri, int selectedNetworkId, int netRole, in IDppCallback callback);
 
     void startDppAsEnrolleeInitiator(in IBinder binder, in String configuratorUri,
         in IDppCallback callback);
 
+    void startDppAsEnrolleeResponder(in IBinder binder, in String deviceInfo, int curve,
+        in IDppCallback callback);
+
     void stopDppSession();
 
     void updateWifiUsabilityScore(int seqNum, int score, int predictionHorizonSec);
 
-    oneway void connect(in WifiConfiguration config, int netId, in IBinder binder, in IActionListener listener, int callbackIdentifier);
+    oneway void connect(in WifiConfiguration config, int netId, in IActionListener listener);
 
-    oneway void save(in WifiConfiguration config, in IBinder binder, in IActionListener listener, int callbackIdentifier);
+    oneway void save(in WifiConfiguration config, in IActionListener listener);
 
-    oneway void forget(int netId, in IBinder binder, in IActionListener listener, int callbackIdentifier);
+    oneway void forget(int netId, in IActionListener listener);
 
     void registerScanResultsCallback(in IScanResultsCallback callback);
 
     void unregisterScanResultsCallback(in IScanResultsCallback callback);
 
-    void registerSuggestionConnectionStatusListener(in IBinder binder, in ISuggestionConnectionStatusListener listener, int listenerIdentifier, String packageName, String featureId);
+    void registerSuggestionConnectionStatusListener(in ISuggestionConnectionStatusListener listener, String packageName, String featureId);
 
-    void unregisterSuggestionConnectionStatusListener(int listenerIdentifier, String packageName);
+    void unregisterSuggestionConnectionStatusListener(in ISuggestionConnectionStatusListener listener, String packageName);
 
     int calculateSignalLevel(int rssi);
 
@@ -275,4 +309,32 @@
     void setAutoWakeupEnabled(boolean enable);
 
     boolean isAutoWakeupEnabled();
+
+    void startRestrictingAutoJoinToSubscriptionId(int subId);
+
+    void stopRestrictingAutoJoinToSubscriptionId();
+
+    void setCarrierNetworkOffloadEnabled(int subscriptionId, boolean merged, boolean enabled);
+
+    boolean isCarrierNetworkOffloadEnabled(int subscriptionId, boolean merged);
+
+    void registerSubsystemRestartCallback(in ISubsystemRestartCallback callback);
+
+    void unregisterSubsystemRestartCallback(in ISubsystemRestartCallback callback);
+
+    void restartWifiSubsystem();
+
+    void addSuggestionUserApprovalStatusListener(in ISuggestionUserApprovalStatusListener listener, String packageName);
+
+    void removeSuggestionUserApprovalStatusListener(in ISuggestionUserApprovalStatusListener listener, String packageName);
+
+    void setEmergencyScanRequestInProgress(boolean inProgress);
+
+    void removeAppState(int targetAppUid, String targetApppackageName);
+
+    boolean setWifiScoringEnabled(boolean enabled);
+
+    void flushPasspointAnqpCache(String packageName);
+
+    List<WifiAvailableChannel> getUsableChannels(int band, int mode, int filter);
 }
diff --git a/framework/java/android/net/wifi/ITxPacketCountListener.aidl b/framework/java/android/net/wifi/IWifiVerboseLoggingStatusChangedListener.aidl
similarity index 69%
copy from framework/java/android/net/wifi/ITxPacketCountListener.aidl
copy to framework/java/android/net/wifi/IWifiVerboseLoggingStatusChangedListener.aidl
index 9105bd0..bec6370 100644
--- a/framework/java/android/net/wifi/ITxPacketCountListener.aidl
+++ b/framework/java/android/net/wifi/IWifiVerboseLoggingStatusChangedListener.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,12 +17,11 @@
 package android.net.wifi;
 
 /**
- * Interface for tx packet counter callback.
- * @deprecated no longer used, remove once removed from BaseWifiService
+ * Interface for wifi verbose logging status listener.
+ *
  * @hide
  */
-oneway interface ITxPacketCountListener
+oneway interface IWifiVerboseLoggingStatusChangedListener
 {
-    void onSuccess(int count);
-    void onFailure(int reason);
+   void onStatusChanged(in boolean enabled);
 }
diff --git a/framework/java/android/net/wifi/ScanResult.java b/framework/java/android/net/wifi/ScanResult.java
index 5589bd1..efdf09b 100644
--- a/framework/java/android/net/wifi/ScanResult.java
+++ b/framework/java/android/net/wifi/ScanResult.java
@@ -16,6 +16,7 @@
 
 package android.net.wifi;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -26,6 +27,10 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.modules.utils.build.SdkLevel;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -82,6 +87,12 @@
     public String capabilities;
 
     /**
+     * The interface name on which the scan result was received.
+     * @hide
+     */
+    public String ifaceName;
+
+    /**
      * @hide
      * No security protocol.
      */
@@ -219,6 +230,11 @@
     public static final int KEY_MGMT_FILS_SHA384 = 16;
     /**
      * @hide
+     * Security key management scheme: any unknown AKM.
+     */
+    public static final int KEY_MGMT_UNKNOWN = 17;
+    /**
+     * @hide
      * No cipher suite.
      */
     @SystemApi
@@ -253,6 +269,30 @@
      */
     @SystemApi
     public static final int CIPHER_SMS4 = 5;
+    /**
+     * @hide
+     * Cipher suite: GCMP_128
+     */
+    @SystemApi
+    public static final int CIPHER_GCMP_128 = 6;
+    /**
+     * @hide
+     * Cipher suite: BIP_GMAC_128
+     */
+    @SystemApi
+    public static final int CIPHER_BIP_GMAC_128 = 7;
+    /**
+     * @hide
+     * Cipher suite: BIP_GMAC_256
+     */
+    @SystemApi
+    public static final int CIPHER_BIP_GMAC_256 = 8;
+    /**
+     * @hide
+     * Cipher suite: BIP_CMAC_256
+     */
+    @SystemApi
+    public static final int CIPHER_BIP_CMAC_256 = 9;
 
     /**
      * The detected signal level in dBm, also known as the RSSI.
@@ -314,6 +354,43 @@
     public static final int WIFI_STANDARD_11AX = 6;
 
     /**
+     * Wi-Fi 802.11ad
+     */
+    public static final int WIFI_STANDARD_11AD = 7;
+
+    /**
+     * Wi-Fi 2.4 GHz band.
+     */
+    public static final int WIFI_BAND_24_GHZ = WifiScanner.WIFI_BAND_24_GHZ;
+
+    /**
+     * Wi-Fi 5 GHz band.
+     */
+    public static final int WIFI_BAND_5_GHZ = WifiScanner.WIFI_BAND_5_GHZ;
+
+    /**
+     * Wi-Fi 6 GHz band.
+     */
+    public static final int WIFI_BAND_6_GHZ = WifiScanner.WIFI_BAND_6_GHZ;
+
+    /**
+     * Wi-Fi 60 GHz band.
+     */
+    public static final int WIFI_BAND_60_GHZ = WifiScanner.WIFI_BAND_60_GHZ;
+
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"WIFI_BAND_"}, value = {
+            UNSPECIFIED,
+            WIFI_BAND_24_GHZ,
+            WIFI_BAND_5_GHZ,
+            WIFI_BAND_6_GHZ,
+            WIFI_BAND_60_GHZ})
+    public @interface WifiBand {};
+
+    /**
      * AP wifi standard.
      */
     private @WifiStandard int mWifiStandard;
@@ -346,6 +423,8 @@
                 return "11ac";
             case WIFI_STANDARD_11AX:
                 return "11ax";
+            case WIFI_STANDARD_11AD:
+                return "11ad";
             case WIFI_STANDARD_UNKNOWN:
                 return "unknown";
         }
@@ -453,7 +532,7 @@
 
     /**
      * The approximate distance to the AP in centimeter, if available.  Else
-     * {@link UNSPECIFIED}.
+     * {@link #UNSPECIFIED}.
      * {@hide}
      */
     @UnsupportedAppUsage
@@ -461,7 +540,7 @@
 
     /**
      * The standard deviation of the distance to the access point, if available.
-     * Else {@link UNSPECIFIED}.
+     * Else {@link #UNSPECIFIED}.
      * {@hide}
      */
     @UnsupportedAppUsage
@@ -513,16 +592,20 @@
     /**
      * Indicates venue name (such as 'San Francisco Airport') published by access point; only
      * available on Passpoint network and if published by access point.
+     * @deprecated - This information is not provided
      */
+    @Deprecated
     public CharSequence venueName;
 
     /**
      * Indicates Passpoint operator name published by access point.
+     * @deprecated - Use {@link WifiInfo#getPasspointProviderFriendlyName()}
      */
+    @Deprecated
     public CharSequence operatorFriendlyName;
 
     /**
-     * {@hide}
+     * The unspecified value.
      */
     public final static int UNSPECIFIED = -1;
 
@@ -556,7 +639,7 @@
      * 5 GHz band last channel number
      * @hide
      */
-    public static final int BAND_5_GHZ_LAST_CH_NUM = 173;
+    public static final int BAND_5_GHZ_LAST_CH_NUM = 177;
     /**
      * 5 GHz band frequency of first channel in MHz
      * @hide
@@ -566,7 +649,7 @@
      * 5 GHz band frequency of last channel in MHz
      * @hide
      */
-    public static final int BAND_5_GHZ_END_FREQ_MHZ = 5865;
+    public static final int BAND_5_GHZ_END_FREQ_MHZ = 5885;
 
     /**
      * 6 GHz band first channel number
@@ -588,6 +671,18 @@
      * @hide
      */
     public static final int BAND_6_GHZ_END_FREQ_MHZ = 7115;
+    /**
+     * The center frequency of the first 6Ghz preferred scanning channel, as defined by
+     * IEEE802.11ax draft 7.0 section 26.17.2.3.3.
+     * @hide
+     */
+    public static final int BAND_6_GHZ_PSC_START_MHZ = 5975;
+    /**
+     * The number of MHz to increment in order to get the next 6Ghz preferred scanning channel
+     * as defined by IEEE802.11ax draft 7.0 section 26.17.2.3.3.
+     * @hide
+     */
+    public static final int BAND_6_GHZ_PSC_STEP_SIZE_MHZ = 80;
 
     /**
      * 6 GHz band operating class 136 channel 2 center frequency in MHz
@@ -596,6 +691,27 @@
     public static final int BAND_6_GHZ_OP_CLASS_136_CH_2_FREQ_MHZ = 5935;
 
     /**
+     * 60 GHz band first channel number
+     * @hide
+     */
+    public static final int BAND_60_GHZ_FIRST_CH_NUM = 1;
+    /**
+     * 60 GHz band last channel number
+     * @hide
+     */
+    public static final int BAND_60_GHZ_LAST_CH_NUM = 6;
+    /**
+     * 60 GHz band frequency of first channel in MHz
+     * @hide
+     */
+    public static final int BAND_60_GHZ_START_FREQ_MHZ = 58320;
+    /**
+     * 60 GHz band frequency of last channel in MHz
+     * @hide
+     */
+    public static final int BAND_60_GHZ_END_FREQ_MHZ = 70200;
+
+    /**
      * Utility function to check if a frequency within 2.4 GHz band
      * @param freqMhz frequency in MHz
      * @return true if within 2.4GHz, false otherwise
@@ -632,15 +748,46 @@
     }
 
     /**
-     * Utility function to convert channel number/band to frequency in MHz
-     * @param channel number to convert
-     * @param band of channel to convert
-     * @return center frequency in Mhz of the channel, {@link UNSPECIFIED} if no match
+     * Utility function to check if a frequency is 6Ghz PSC channel.
+     * @param freqMhz
+     * @return true if the frequency is 6GHz PSC, false otherwise
      *
      * @hide
      */
-    public static int convertChannelToFrequencyMhz(int channel, @WifiScanner.WifiBand int band) {
-        if (band == WifiScanner.WIFI_BAND_24_GHZ) {
+    public static boolean is6GHzPsc(int freqMhz) {
+        if (!ScanResult.is6GHz(freqMhz)) {
+            return false;
+        }
+        return (freqMhz - BAND_6_GHZ_PSC_START_MHZ) % BAND_6_GHZ_PSC_STEP_SIZE_MHZ == 0;
+    }
+
+    /**
+     * Utility function to check if a frequency within 60 GHz band
+     * @param freqMhz
+     * @return true if within 60GHz, false otherwise
+     *
+     * @hide
+     */
+    public static boolean is60GHz(int freqMhz) {
+        return freqMhz >= BAND_60_GHZ_START_FREQ_MHZ && freqMhz <= BAND_60_GHZ_END_FREQ_MHZ;
+    }
+
+    /**
+     * Utility function to convert Wi-Fi channel number to frequency in MHz.
+     *
+     * Reference the Wi-Fi channel numbering and the channelization in IEEE 802.11-2016
+     * specifications, section 17.3.8.4.2, 17.3.8.4.3 and Table 15-6.
+     *
+     * See also {@link #convertFrequencyMhzToChannelIfSupported(int)}.
+     *
+     * @param channel number to convert.
+     * @param band of channel to convert. One of the following bands:
+     *        {@link #WIFI_BAND_24_GHZ},  {@link #WIFI_BAND_5_GHZ},
+     *        {@link #WIFI_BAND_6_GHZ},  {@link #WIFI_BAND_60_GHZ}.
+     * @return center frequency in Mhz of the channel, {@link #UNSPECIFIED} if no match
+     */
+    public static int convertChannelToFrequencyMhzIfSupported(int channel, @WifiBand int band) {
+        if (band == WIFI_BAND_24_GHZ) {
             // Special case
             if (channel == 14) {
                 return 2484;
@@ -650,14 +797,14 @@
                 return UNSPECIFIED;
             }
         }
-        if (band == WifiScanner.WIFI_BAND_5_GHZ) {
+        if (band == WIFI_BAND_5_GHZ) {
             if (channel >= BAND_5_GHZ_FIRST_CH_NUM && channel <= BAND_5_GHZ_LAST_CH_NUM) {
                 return ((channel - BAND_5_GHZ_FIRST_CH_NUM) * 5) + BAND_5_GHZ_START_FREQ_MHZ;
             } else {
                 return UNSPECIFIED;
             }
         }
-        if (band == WifiScanner.WIFI_BAND_6_GHZ) {
+        if (band == WIFI_BAND_6_GHZ) {
             if (channel >= BAND_6_GHZ_FIRST_CH_NUM && channel <= BAND_6_GHZ_LAST_CH_NUM) {
                 if (channel == 2) {
                     return BAND_6_GHZ_OP_CLASS_136_CH_2_FREQ_MHZ;
@@ -667,17 +814,25 @@
                 return UNSPECIFIED;
             }
         }
+        if (band == WIFI_BAND_60_GHZ) {
+            if (channel >= BAND_60_GHZ_FIRST_CH_NUM && channel <= BAND_60_GHZ_LAST_CH_NUM) {
+                return ((channel - BAND_60_GHZ_FIRST_CH_NUM) * 2160) + BAND_60_GHZ_START_FREQ_MHZ;
+            } else {
+                return UNSPECIFIED;
+            }
+        }
         return UNSPECIFIED;
     }
 
     /**
-     * Utility function to convert frequency in MHz to channel number
-     * @param freqMhz frequency in MHz
-     * @return channel number associated with given frequency, {@link UNSPECIFIED} if no match
+     * Utility function to convert frequency in MHz to channel number.
      *
-     * @hide
+     * See also {@link #convertChannelToFrequencyMhzIfSupported(int, int)}.
+     *
+     * @param freqMhz frequency in MHz
+     * @return channel number associated with given frequency, {@link #UNSPECIFIED} if no match
      */
-    public static int convertFrequencyMhzToChannel(int freqMhz) {
+    public static int convertFrequencyMhzToChannelIfSupported(int freqMhz) {
         // Special case
         if (freqMhz == 2484) {
             return 14;
@@ -690,6 +845,8 @@
                 return 2;
             }
             return ((freqMhz - BAND_6_GHZ_START_FREQ_MHZ) / 5) + BAND_6_GHZ_FIRST_CH_NUM;
+        } else if (is60GHz(freqMhz)) {
+            return ((freqMhz - BAND_60_GHZ_START_FREQ_MHZ) / 2160) + BAND_60_GHZ_FIRST_CH_NUM;
         }
 
         return UNSPECIFIED;
@@ -717,6 +874,20 @@
     }
 
     /**
+     * @hide
+     */
+    public boolean is6GhzPsc() {
+        return ScanResult.is6GHzPsc(frequency);
+    }
+
+    /**
+     * @hide
+     */
+    public boolean is60GHz() {
+        return ScanResult.is60GHz(frequency);
+    }
+
+    /**
      *  @hide
      * anqp lines from supplicant BSS response
      */
@@ -726,7 +897,7 @@
     /**
      * information elements from beacon.
      */
-    public static class InformationElement {
+    public static class InformationElement implements Parcelable {
         /** @hide */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         public static final int EID_SSID = 0;
@@ -793,6 +964,13 @@
         public InformationElement() {
         }
 
+        /** @hide */
+        public InformationElement(int id, int idExt, byte[] bytes) {
+            this.id = id;
+            this.idExt = idExt;
+            this.bytes = bytes.clone();
+        }
+
         public InformationElement(@NonNull InformationElement rhs) {
             this.id = rhs.id;
             this.idExt = rhs.idExt;
@@ -822,6 +1000,57 @@
         public ByteBuffer getBytes() {
             return ByteBuffer.wrap(bytes).asReadOnlyBuffer();
         }
+
+        /** Implement the Parcelable interface {@hide} */
+        public int describeContents() {
+            return 0;
+        }
+
+        /** Implement the Parcelable interface {@hide} */
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(id);
+            dest.writeInt(idExt);
+            dest.writeByteArray(bytes);
+        }
+
+        /** Implement the Parcelable interface */
+        public static final @NonNull Creator<InformationElement> CREATOR =
+                new Creator<InformationElement>() {
+                    public InformationElement createFromParcel(Parcel in) {
+                        InformationElement informationElement = new InformationElement();
+                        informationElement.id = in.readInt();
+                        informationElement.idExt = in.readInt();
+                        informationElement.bytes = in.createByteArray();
+                        return informationElement;
+                    }
+
+                    public InformationElement[] newArray(int size) {
+                        return new InformationElement[size];
+                    }
+                };
+
+        @Override
+        public boolean equals(Object that) {
+            if (this == that) return true;
+
+            // Potential API behavior change, so don't change behavior on older devices.
+            if (!SdkLevel.isAtLeastS()) return false;
+
+            if (!(that instanceof InformationElement)) return false;
+
+            InformationElement thatIE = (InformationElement) that;
+            return id == thatIE.id
+                    && idExt == thatIE.idExt
+                    && Arrays.equals(bytes, thatIE.bytes);
+        }
+
+        @Override
+        public int hashCode() {
+            // Potential API behavior change, so don't change behavior on older devices.
+            if (!SdkLevel.isAtLeastS()) return System.identityHashCode(this);
+
+            return Objects.hash(id, idExt, Arrays.hashCode(bytes));
+        }
     }
 
     /**
@@ -955,6 +1184,7 @@
             flags = source.flags;
             radioChainInfos = source.radioChainInfos;
             this.mWifiStandard = source.mWifiStandard;
+            this.ifaceName = source.ifaceName;
         }
     }
 
@@ -993,6 +1223,7 @@
         sb.append(", 80211mcResponder: ");
         sb.append(((flags & FLAG_80211mc_RESPONDER) != 0) ? "is supported" : "is not supported");
         sb.append(", Radio Chain Infos: ").append(Arrays.toString(radioChainInfos));
+        sb.append(", interface name: ").append(ifaceName);
         return sb.toString();
     }
 
@@ -1029,18 +1260,7 @@
         dest.writeString((venueName != null) ? venueName.toString() : "");
         dest.writeString((operatorFriendlyName != null) ? operatorFriendlyName.toString() : "");
         dest.writeLong(this.flags);
-
-        if (informationElements != null) {
-            dest.writeInt(informationElements.length);
-            for (int i = 0; i < informationElements.length; i++) {
-                dest.writeInt(informationElements[i].id);
-                dest.writeInt(informationElements[i].idExt);
-                dest.writeInt(informationElements[i].bytes.length);
-                dest.writeByteArray(informationElements[i].bytes);
-            }
-        } else {
-            dest.writeInt(0);
-        }
+        dest.writeTypedArray(informationElements, flags);
 
         if (anqpLines != null) {
             dest.writeInt(anqpLines.size());
@@ -1072,6 +1292,7 @@
         } else {
             dest.writeInt(0);
         }
+        dest.writeString((ifaceName != null) ? ifaceName.toString() : "");
     }
 
     /** Implement the Parcelable interface */
@@ -1108,20 +1329,9 @@
                 sr.venueName = in.readString();
                 sr.operatorFriendlyName = in.readString();
                 sr.flags = in.readLong();
-                int n = in.readInt();
-                if (n != 0) {
-                    sr.informationElements = new InformationElement[n];
-                    for (int i = 0; i < n; i++) {
-                        sr.informationElements[i] = new InformationElement();
-                        sr.informationElements[i].id = in.readInt();
-                        sr.informationElements[i].idExt = in.readInt();
-                        int len = in.readInt();
-                        sr.informationElements[i].bytes = new byte[len];
-                        in.readByteArray(sr.informationElements[i].bytes);
-                    }
-                }
+                sr.informationElements = in.createTypedArray(InformationElement.CREATOR);
 
-                n = in.readInt();
+                int n = in.readInt();
                 if (n != 0) {
                     sr.anqpLines = new ArrayList<String>();
                     for (int i = 0; i < n; i++) {
@@ -1150,6 +1360,7 @@
                         sr.radioChainInfos[i].level = in.readInt();
                     }
                 }
+                sr.ifaceName = in.readString();
                 return sr;
             }
 
diff --git a/framework/java/android/net/wifi/SecurityParams.java b/framework/java/android/net/wifi/SecurityParams.java
new file mode 100644
index 0000000..0e9f091
--- /dev/null
+++ b/framework/java/android/net/wifi/SecurityParams.java
@@ -0,0 +1,897 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.net.wifi.WifiConfiguration.AuthAlgorithm;
+import android.net.wifi.WifiConfiguration.GroupCipher;
+import android.net.wifi.WifiConfiguration.GroupMgmtCipher;
+import android.net.wifi.WifiConfiguration.KeyMgmt;
+import android.net.wifi.WifiConfiguration.PairwiseCipher;
+import android.net.wifi.WifiConfiguration.Protocol;
+import android.net.wifi.WifiConfiguration.SecurityType;
+import android.net.wifi.WifiConfiguration.SuiteBCipher;
+import android.os.Parcel;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.BitSet;
+import java.util.Objects;
+
+/**
+ * A class representing a security configuration.
+ * @hide
+ */
+public class SecurityParams {
+    private static final String TAG = "SecurityParams";
+
+    /** Passpoint Release 1 */
+    public static final int PASSPOINT_R1 = 1;
+
+    /** Passpoint Release 2 */
+    public static final int PASSPOINT_R2 = 2;
+
+    /** Passpoint Release 3 */
+    public static final int PASSPOINT_R3 = 3;
+
+    @IntDef(prefix = { "PASSPOINT_" }, value = {
+        PASSPOINT_R1,
+        PASSPOINT_R2,
+        PASSPOINT_R3,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PasspointRelease {}
+
+    private @SecurityType int mSecurityType = WifiConfiguration.SECURITY_TYPE_PSK;
+
+    /**
+     * This indicates that this security type is enabled or disabled.
+     * Ex. While receiving Transition Disable Indication, older
+     * security should be disabled.
+     */
+    private boolean mEnabled = true;
+
+    /**
+     * The set of key management protocols supported by this configuration.
+     * See {@link KeyMgmt} for descriptions of the values.
+     * This is set automatically based on the security type.
+     */
+    private BitSet mAllowedKeyManagement = new BitSet();
+
+    /**
+     * The set of security protocols supported by this configuration.
+     * See {@link Protocol} for descriptions of the values.
+     * This is set automatically based on the security type.
+     */
+    private BitSet mAllowedProtocols = new BitSet();
+
+    /**
+     * The set of authentication protocols supported by this configuration.
+     * See {@link AuthAlgorithm} for descriptions of the values.
+     * This is set automatically based on the security type.
+     */
+    private BitSet mAllowedAuthAlgorithms = new BitSet();
+
+    /**
+     * The set of pairwise ciphers for WPA supported by this configuration.
+     * See {@link PairwiseCipher} for descriptions of the values.
+     * This is set automatically based on the security type.
+     */
+    private BitSet mAllowedPairwiseCiphers = new BitSet();
+
+    /**
+     * The set of group ciphers supported by this configuration.
+     * See {@link GroupCipher} for descriptions of the values.
+     * This is set automatically based on the security type.
+     */
+    private BitSet mAllowedGroupCiphers = new BitSet();
+
+    /**
+     * The set of group management ciphers supported by this configuration.
+     * See {@link GroupMgmtCipher} for descriptions of the values.
+     */
+    private BitSet mAllowedGroupManagementCiphers = new BitSet();
+
+    /**
+     * The set of SuiteB ciphers supported by this configuration.
+     * To be used for WPA3-Enterprise mode. Set automatically by the framework based on the
+     * certificate type that is used in this configuration.
+     */
+    private BitSet mAllowedSuiteBCiphers = new BitSet();
+
+    /**
+     * True if the network requires Protected Management Frames (PMF), false otherwise.
+     */
+    private boolean mRequirePmf = false;
+
+    private @PasspointRelease int mPasspointRelease = PASSPOINT_R2;
+
+    /** Indicate that this SAE security type only accepts H2E (Hash-to-Element) mode. */
+    private boolean mIsSaeH2eOnlyMode = false;
+
+    /** Indicate that this SAE security type only accepts PK (Public Key) mode. */
+    private boolean mIsSaePkOnlyMode = false;
+
+    /** Indicate whether this is added by auto-upgrade or not. */
+    private boolean mIsAddedByAutoUpgrade = false;
+
+    /** Constructor */
+    private SecurityParams() {
+    }
+
+    /** Copy constructor */
+    public SecurityParams(@NonNull SecurityParams source) {
+        this.mSecurityType = source.mSecurityType;
+        this.mEnabled = source.mEnabled;
+        this.mAllowedKeyManagement = (BitSet) source.mAllowedKeyManagement.clone();
+        this.mAllowedProtocols = (BitSet) source.mAllowedProtocols.clone();
+        this.mAllowedAuthAlgorithms = (BitSet) source.mAllowedAuthAlgorithms.clone();
+        this.mAllowedPairwiseCiphers = (BitSet) source.mAllowedPairwiseCiphers.clone();
+        this.mAllowedGroupCiphers = (BitSet) source.mAllowedGroupCiphers.clone();
+        this.mAllowedGroupManagementCiphers =
+                (BitSet) source.mAllowedGroupManagementCiphers.clone();
+        this.mAllowedSuiteBCiphers =
+                (BitSet) source.mAllowedSuiteBCiphers.clone();
+        this.mRequirePmf = source.mRequirePmf;
+        this.mIsSaeH2eOnlyMode = source.mIsSaeH2eOnlyMode;
+        this.mIsSaePkOnlyMode = source.mIsSaePkOnlyMode;
+        this.mIsAddedByAutoUpgrade = source.mIsAddedByAutoUpgrade;
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof SecurityParams)) {
+            return false;
+        }
+        SecurityParams that = (SecurityParams) thatObject;
+
+        if (this.mSecurityType != that.mSecurityType) return false;
+        if (this.mEnabled != that.mEnabled) return false;
+        if (!this.mAllowedKeyManagement.equals(that.mAllowedKeyManagement)) return false;
+        if (!this.mAllowedProtocols.equals(that.mAllowedProtocols)) return false;
+        if (!this.mAllowedAuthAlgorithms.equals(that.mAllowedAuthAlgorithms)) return false;
+        if (!this.mAllowedPairwiseCiphers.equals(that.mAllowedPairwiseCiphers)) return false;
+        if (!this.mAllowedGroupCiphers.equals(that.mAllowedGroupCiphers)) return false;
+        if (!this.mAllowedGroupManagementCiphers.equals(that.mAllowedGroupManagementCiphers)) {
+            return false;
+        }
+        if (!this.mAllowedSuiteBCiphers.equals(that.mAllowedSuiteBCiphers)) return false;
+        if (this.mRequirePmf != that.mRequirePmf) return false;
+        if (this.mIsSaeH2eOnlyMode != that.mIsSaeH2eOnlyMode) return false;
+        if (this.mIsSaePkOnlyMode != that.mIsSaePkOnlyMode) return false;
+        if (this.mIsAddedByAutoUpgrade != that.mIsAddedByAutoUpgrade) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mSecurityType, mEnabled,
+                mAllowedKeyManagement, mAllowedProtocols, mAllowedAuthAlgorithms,
+                mAllowedPairwiseCiphers, mAllowedGroupCiphers, mAllowedGroupManagementCiphers,
+                mAllowedSuiteBCiphers, mRequirePmf,
+                mIsSaeH2eOnlyMode, mIsSaePkOnlyMode, mIsAddedByAutoUpgrade);
+    }
+
+    /**
+     * Get the security type of this params.
+     *
+     * @return The security type defined in {@link WifiConfiguration}.
+     */
+    public @SecurityType int getSecurityType() {
+        return mSecurityType;
+    }
+
+    /**
+     * Check the security type of this params.
+     *
+     * @param type the testing security type.
+     * @return true if this is for the corresponiding type.
+     */
+    public boolean isSecurityType(@SecurityType int type) {
+        return type == mSecurityType;
+    }
+
+    /**
+     * Check whether the security of given params is the same as this one.
+     *
+     * @param params the testing security params.
+     * @return true if their security types are the same.
+     */
+    public boolean isSameSecurityType(SecurityParams params) {
+        return params.mSecurityType == mSecurityType;
+    }
+
+    /**
+     * Update security params to legacy WifiConfiguration object.
+     *
+     * @param config the target configuration.
+     */
+    public void updateLegacyWifiConfiguration(WifiConfiguration config) {
+        config.allowedKeyManagement = (BitSet) mAllowedKeyManagement.clone();
+        config.allowedProtocols = (BitSet) mAllowedProtocols.clone();
+        config.allowedAuthAlgorithms = (BitSet) mAllowedAuthAlgorithms.clone();
+        config.allowedPairwiseCiphers = (BitSet) mAllowedPairwiseCiphers.clone();
+        config.allowedGroupCiphers = (BitSet) mAllowedGroupCiphers.clone();
+        config.allowedGroupManagementCiphers = (BitSet) mAllowedGroupManagementCiphers.clone();
+        config.allowedSuiteBCiphers = (BitSet) mAllowedSuiteBCiphers.clone();
+        config.requirePmf = mRequirePmf;
+    }
+
+    /**
+     * Set this params enabled.
+     *
+     * @param enable enable a specific security type.
+     */
+    public void setEnabled(boolean enable) {
+        mEnabled = enable;
+    }
+
+    /**
+     * Indicate this params is enabled or not.
+     */
+    public boolean isEnabled() {
+        return mEnabled;
+    }
+
+    /**
+     * Set the supporting Fast Initial Link Set-up (FILS) key management.
+     *
+     * FILS can be applied to all security types.
+     * @param enableFilsSha256 Enable FILS SHA256.
+     * @param enableFilsSha384 Enable FILS SHA256.
+     */
+    public void enableFils(boolean enableFilsSha256, boolean enableFilsSha384) {
+        if (enableFilsSha256) {
+            mAllowedKeyManagement.set(KeyMgmt.FILS_SHA256);
+        }
+
+        if (enableFilsSha384) {
+            mAllowedKeyManagement.set(KeyMgmt.FILS_SHA384);
+        }
+    }
+
+    /**
+     * Get the copy of allowed key management.
+     */
+    public BitSet getAllowedKeyManagement() {
+        return (BitSet) mAllowedKeyManagement.clone();
+    }
+
+    /**
+     * Get the copy of allowed protocols.
+     */
+    public BitSet getAllowedProtocols() {
+        return (BitSet) mAllowedProtocols.clone();
+    }
+
+    /**
+     * Get the copy of allowed auth algorithms.
+     */
+    public BitSet getAllowedAuthAlgorithms() {
+        return (BitSet) mAllowedAuthAlgorithms.clone();
+    }
+
+    /**
+     * Get the copy of allowed pairwise ciphers.
+     */
+    public BitSet getAllowedPairwiseCiphers() {
+        return (BitSet) mAllowedPairwiseCiphers.clone();
+    }
+
+    /**
+     * Get the copy of allowed group ciphers.
+     */
+    public BitSet getAllowedGroupCiphers() {
+        return (BitSet) mAllowedGroupCiphers.clone();
+    }
+
+    /**
+     * Get the copy of allowed group management ciphers.
+     */
+    public BitSet getAllowedGroupManagementCiphers() {
+        return (BitSet) mAllowedGroupManagementCiphers.clone();
+    }
+
+    /**
+     * Enable Suite-B ciphers.
+     *
+     * @param enableEcdheEcdsa enable Diffie-Hellman with Elliptic Curve ECDSA cipher support.
+     * @param enableEcdheRsa enable Diffie-Hellman with RSA cipher support.
+     */
+    public void enableSuiteBCiphers(boolean enableEcdheEcdsa, boolean enableEcdheRsa) {
+        if (enableEcdheEcdsa) {
+            mAllowedSuiteBCiphers.set(SuiteBCipher.ECDHE_ECDSA);
+        } else {
+            mAllowedSuiteBCiphers.clear(SuiteBCipher.ECDHE_ECDSA);
+        }
+
+        if (enableEcdheRsa) {
+            mAllowedSuiteBCiphers.set(SuiteBCipher.ECDHE_RSA);
+        } else {
+            mAllowedSuiteBCiphers.clear(SuiteBCipher.ECDHE_RSA);
+        }
+    }
+
+    /**
+     * Get the copy of allowed suite-b ciphers.
+     */
+    public BitSet getAllowedSuiteBCiphers() {
+        return (BitSet) mAllowedSuiteBCiphers.clone();
+    }
+
+    /**
+     * Set PMF is required or not.
+     *
+     * @param required indicates whether PMF is required or not.
+     */
+    public void setRequirePmf(boolean required) {
+        mRequirePmf = required;
+    }
+
+    /**
+     * Indicate PMF is required or not.
+     */
+    public boolean isRequirePmf() {
+        return mRequirePmf;
+    }
+
+    /**
+     * Indicate that this is open security type.
+     */
+    public boolean isOpenSecurityType() {
+        return isSecurityType(WifiConfiguration.SECURITY_TYPE_OPEN)
+                || isSecurityType(WifiConfiguration.SECURITY_TYPE_OWE);
+    }
+
+    /**
+     * Indicate that this is enterprise security type.
+     */
+    public boolean isEnterpriseSecurityType() {
+        return mAllowedKeyManagement.get(KeyMgmt.WPA_EAP)
+                || mAllowedKeyManagement.get(KeyMgmt.IEEE8021X)
+                || mAllowedKeyManagement.get(KeyMgmt.SUITE_B_192)
+                || mAllowedKeyManagement.get(KeyMgmt.WAPI_CERT);
+    }
+
+    /**
+     * Enable Hash-to-Element only mode.
+     *
+     * @param enable set H2E only mode enabled or not.
+     */
+    public void enableSaeH2eOnlyMode(boolean enable) {
+        mIsSaeH2eOnlyMode = enable;
+    }
+
+    /**
+     * Indicate whether this params is H2E only mode.
+     *
+     * @return true if this is H2E only mode params.
+     */
+    public boolean isSaeH2eOnlyMode() {
+        return mIsSaeH2eOnlyMode;
+    }
+    /**
+     * Enable Pubilc-Key only mode.
+     *
+     * @param enable set PK only mode enabled or not.
+     */
+    public void enableSaePkOnlyMode(boolean enable) {
+        mIsSaePkOnlyMode = enable;
+    }
+
+    /**
+     * Indicate whether this params is PK only mode.
+     *
+     * @return true if this is PK only mode params.
+     */
+    public boolean isSaePkOnlyMode() {
+        return mIsSaePkOnlyMode;
+    }
+
+    /**
+     * Set whether this is added by auto-upgrade.
+     *
+     * @param addedByAutoUpgrade true if added by auto-upgrade.
+     */
+    public void setIsAddedByAutoUpgrade(boolean addedByAutoUpgrade) {
+        mIsAddedByAutoUpgrade = addedByAutoUpgrade;
+    }
+
+    /**
+     * Indicate whether this is added by auto-upgrade or not.
+     *
+     * @return true if added by auto-upgrade; otherwise, false.
+     */
+    public boolean isAddedByAutoUpgrade() {
+        return mIsAddedByAutoUpgrade;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sbuf = new StringBuilder();
+        sbuf.append("Security Parameters:\n");
+        sbuf.append(" Type: ").append(mSecurityType).append("\n");
+        sbuf.append(" Enabled: ").append(mEnabled).append("\n");
+        sbuf.append(" KeyMgmt:");
+        for (int k = 0; k < mAllowedKeyManagement.size(); k++) {
+            if (mAllowedKeyManagement.get(k)) {
+                sbuf.append(" ");
+                if (k < KeyMgmt.strings.length) {
+                    sbuf.append(KeyMgmt.strings[k]);
+                } else {
+                    sbuf.append("??");
+                }
+            }
+        }
+        sbuf.append('\n');
+        sbuf.append(" Protocols:");
+        for (int p = 0; p < mAllowedProtocols.size(); p++) {
+            if (mAllowedProtocols.get(p)) {
+                sbuf.append(" ");
+                if (p < Protocol.strings.length) {
+                    sbuf.append(Protocol.strings[p]);
+                } else {
+                    sbuf.append("??");
+                }
+            }
+        }
+        sbuf.append('\n');
+        sbuf.append(" AuthAlgorithms:");
+        for (int a = 0; a < mAllowedAuthAlgorithms.size(); a++) {
+            if (mAllowedAuthAlgorithms.get(a)) {
+                sbuf.append(" ");
+                if (a < AuthAlgorithm.strings.length) {
+                    sbuf.append(AuthAlgorithm.strings[a]);
+                } else {
+                    sbuf.append("??");
+                }
+            }
+        }
+        sbuf.append('\n');
+        sbuf.append(" PairwiseCiphers:");
+        for (int pc = 0; pc < mAllowedPairwiseCiphers.size(); pc++) {
+            if (mAllowedPairwiseCiphers.get(pc)) {
+                sbuf.append(" ");
+                if (pc < PairwiseCipher.strings.length) {
+                    sbuf.append(PairwiseCipher.strings[pc]);
+                } else {
+                    sbuf.append("??");
+                }
+            }
+        }
+        sbuf.append('\n');
+        sbuf.append(" GroupCiphers:");
+        for (int gc = 0; gc < mAllowedGroupCiphers.size(); gc++) {
+            if (mAllowedGroupCiphers.get(gc)) {
+                sbuf.append(" ");
+                if (gc < GroupCipher.strings.length) {
+                    sbuf.append(GroupCipher.strings[gc]);
+                } else {
+                    sbuf.append("??");
+                }
+            }
+        }
+        sbuf.append('\n');
+        sbuf.append(" GroupMgmtCiphers:");
+        for (int gmc = 0; gmc < mAllowedGroupManagementCiphers.size(); gmc++) {
+            if (mAllowedGroupManagementCiphers.get(gmc)) {
+                sbuf.append(" ");
+                if (gmc < GroupMgmtCipher.strings.length) {
+                    sbuf.append(GroupMgmtCipher.strings[gmc]);
+                } else {
+                    sbuf.append("??");
+                }
+            }
+        }
+        sbuf.append('\n');
+        sbuf.append(" SuiteBCiphers:");
+        for (int sbc = 0; sbc < mAllowedSuiteBCiphers.size(); sbc++) {
+            if (mAllowedSuiteBCiphers.get(sbc)) {
+                sbuf.append(" ");
+                if (sbc < SuiteBCipher.strings.length) {
+                    sbuf.append(SuiteBCipher.strings[sbc]);
+                } else {
+                    sbuf.append("??");
+                }
+            }
+        }
+        sbuf.append('\n');
+        sbuf.append(" RequirePmf: ").append(mRequirePmf).append('\n');
+        sbuf.append(" IsAddedByAutoUpgrade: ").append(mIsAddedByAutoUpgrade).append("\n");
+        sbuf.append(" IsSaeH2eOnlyMode: ").append(mIsSaeH2eOnlyMode).append("\n");
+        sbuf.append(" IsSaePkOnlyMode: ").append(mIsSaePkOnlyMode).append("\n");
+        return sbuf.toString();
+    }
+
+    private static BitSet readBitSet(Parcel src) {
+        int cardinality = src.readInt();
+
+        BitSet set = new BitSet();
+        for (int i = 0; i < cardinality; i++) {
+            set.set(src.readInt());
+        }
+
+        return set;
+    }
+
+    private static void writeBitSet(Parcel dest, BitSet set) {
+        int nextSetBit = -1;
+
+        dest.writeInt(set.cardinality());
+
+        while ((nextSetBit = set.nextSetBit(nextSetBit + 1)) != -1) {
+            dest.writeInt(nextSetBit);
+        }
+    }
+
+    /** Write this object to the parcel. */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mSecurityType);
+        dest.writeBoolean(mEnabled);
+        writeBitSet(dest, mAllowedKeyManagement);
+        writeBitSet(dest, mAllowedProtocols);
+        writeBitSet(dest, mAllowedAuthAlgorithms);
+        writeBitSet(dest, mAllowedPairwiseCiphers);
+        writeBitSet(dest, mAllowedGroupCiphers);
+        writeBitSet(dest, mAllowedGroupManagementCiphers);
+        writeBitSet(dest, mAllowedSuiteBCiphers);
+        dest.writeBoolean(mRequirePmf);
+        dest.writeBoolean(mIsAddedByAutoUpgrade);
+        dest.writeBoolean(mIsSaeH2eOnlyMode);
+        dest.writeBoolean(mIsSaePkOnlyMode);
+
+    }
+
+    /** Create a SecurityParams object from the parcel. */
+    public static final @NonNull SecurityParams createFromParcel(Parcel in) {
+        SecurityParams params = new SecurityParams();
+        params.mSecurityType = in.readInt();
+        params.mEnabled = in.readBoolean();
+        params.mAllowedKeyManagement = readBitSet(in);
+        params.mAllowedProtocols = readBitSet(in);
+        params.mAllowedAuthAlgorithms = readBitSet(in);
+        params.mAllowedPairwiseCiphers = readBitSet(in);
+        params.mAllowedGroupCiphers = readBitSet(in);
+        params.mAllowedGroupManagementCiphers = readBitSet(in);
+        params.mAllowedSuiteBCiphers = readBitSet(in);
+        params.mRequirePmf = in.readBoolean();
+        params.mIsAddedByAutoUpgrade = in.readBoolean();
+        params.mIsSaeH2eOnlyMode = in.readBoolean();
+        params.mIsSaePkOnlyMode = in.readBoolean();
+        return params;
+    }
+
+    /**
+     * Create a params according to the security type.
+     *
+     * @param securityType One of the following security types:
+     * {@link WifiConfiguration#SECURITY_TYPE_OPEN},
+     * {@link WifiConfiguration#SECURITY_TYPE_WEP},
+     * {@link WifiConfiguration#SECURITY_TYPE_PSK},
+     * {@link WifiConfiguration#SECURITY_TYPE_EAP},
+     * {@link WifiConfiguration#SECURITY_TYPE_SAE},
+     * {@link WifiConfiguration#SECURITY_TYPE_OWE},
+     * {@link WifiConfiguration#SECURITY_TYPE_WAPI_PSK},
+     * {@link WifiConfiguration#SECURITY_TYPE_WAPI_CERT},
+     * {@link WifiConfiguration#SECURITY_TYPE_EAP_WPA3_ENTERPRISE},
+     * {@link WifiConfiguration#SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT},
+     *
+     * @return the corresponding security params if the security type is valid;
+     *         otherwise, throw IllegalArgumentException.
+     */
+    public static @NonNull SecurityParams createSecurityParamsBySecurityType(
+            @WifiConfiguration.SecurityType int securityType) {
+        switch (securityType) {
+            case WifiConfiguration.SECURITY_TYPE_OPEN:
+                return createOpenParams();
+            case WifiConfiguration.SECURITY_TYPE_WEP:
+                return createWepParams();
+            case WifiConfiguration.SECURITY_TYPE_PSK:
+                return createWpaWpa2PersonalParams();
+            case WifiConfiguration.SECURITY_TYPE_EAP:
+                return createWpaWpa2EnterpriseParams();
+            case WifiConfiguration.SECURITY_TYPE_SAE:
+                return createWpa3PersonalParams();
+            // The value of {@link WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B} is the same as
+            // {@link #WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT}, remove it
+            // to avoid duplicate case label errors.
+            case WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT:
+                return createWpa3Enterprise192BitParams();
+            case WifiConfiguration.SECURITY_TYPE_OWE:
+                return createEnhancedOpenParams();
+            case WifiConfiguration.SECURITY_TYPE_WAPI_PSK:
+                return createWapiPskParams();
+            case WifiConfiguration.SECURITY_TYPE_WAPI_CERT:
+                return createWapiCertParams();
+            case WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE:
+                return createWpa3EnterpriseParams();
+            case WifiConfiguration.SECURITY_TYPE_OSEN:
+                return createOsenParams();
+            case WifiConfiguration.SECURITY_TYPE_PASSPOINT_R1_R2:
+                return SecurityParams.createPasspointParams(PASSPOINT_R2);
+            case WifiConfiguration.SECURITY_TYPE_PASSPOINT_R3:
+                return SecurityParams.createPasspointParams(PASSPOINT_R3);
+            default:
+                throw new IllegalArgumentException("unknown security type " + securityType);
+        }
+    }
+
+    /**
+     * Create EAP security params.
+     */
+    private static @NonNull SecurityParams createWpaWpa2EnterpriseParams() {
+        SecurityParams params = new SecurityParams();
+        params.mSecurityType = WifiConfiguration.SECURITY_TYPE_EAP;
+
+        params.mAllowedKeyManagement.set(KeyMgmt.WPA_EAP);
+        params.mAllowedKeyManagement.set(KeyMgmt.IEEE8021X);
+
+        params.mAllowedProtocols.set(Protocol.RSN);
+        params.mAllowedProtocols.set(Protocol.WPA);
+
+        params.mAllowedPairwiseCiphers.set(PairwiseCipher.CCMP);
+        params.mAllowedPairwiseCiphers.set(PairwiseCipher.TKIP);
+
+        params.mAllowedGroupCiphers.set(GroupCipher.CCMP);
+        params.mAllowedGroupCiphers.set(GroupCipher.TKIP);
+        return params;
+    }
+
+    /**
+     * Create Passpoint security params.
+     */
+    private static @NonNull SecurityParams createPasspointParams(@PasspointRelease int release) {
+        SecurityParams params = new SecurityParams();
+        switch (release) {
+            case PASSPOINT_R1:
+            case PASSPOINT_R2:
+                params.mSecurityType = WifiConfiguration.SECURITY_TYPE_PASSPOINT_R1_R2;
+                break;
+            case PASSPOINT_R3:
+                params.mSecurityType = WifiConfiguration.SECURITY_TYPE_PASSPOINT_R3;
+                params.mRequirePmf = true;
+                break;
+            default:
+                throw new IllegalArgumentException("invalid passpoint release " + release);
+        }
+
+        params.mAllowedKeyManagement.set(KeyMgmt.WPA_EAP);
+        params.mAllowedKeyManagement.set(KeyMgmt.IEEE8021X);
+
+        params.mAllowedProtocols.set(Protocol.RSN);
+
+        params.mAllowedPairwiseCiphers.set(PairwiseCipher.CCMP);
+
+        params.mAllowedGroupCiphers.set(GroupCipher.CCMP);
+
+        return params;
+    }
+
+    /**
+     * Create Enhanced Open params.
+     */
+    private static @NonNull SecurityParams createEnhancedOpenParams() {
+        SecurityParams params = new SecurityParams();
+        params.mSecurityType = WifiConfiguration.SECURITY_TYPE_OWE;
+
+        params.mAllowedKeyManagement.set(KeyMgmt.OWE);
+
+        params.mAllowedProtocols.set(Protocol.RSN);
+
+        params.mAllowedPairwiseCiphers.set(PairwiseCipher.CCMP);
+        params.mAllowedPairwiseCiphers.set(PairwiseCipher.GCMP_128);
+        params.mAllowedPairwiseCiphers.set(PairwiseCipher.GCMP_256);
+
+        params.mAllowedGroupCiphers.set(GroupCipher.CCMP);
+        params.mAllowedGroupCiphers.set(GroupCipher.GCMP_128);
+        params.mAllowedGroupCiphers.set(GroupCipher.GCMP_256);
+
+        params.mRequirePmf = true;
+        return params;
+    }
+
+    /**
+     * Create Open params.
+     */
+    private static @NonNull SecurityParams createOpenParams() {
+        SecurityParams params = new SecurityParams();
+        params.mSecurityType = WifiConfiguration.SECURITY_TYPE_OPEN;
+
+        params.mAllowedKeyManagement.set(KeyMgmt.NONE);
+
+        params.mAllowedProtocols.set(Protocol.RSN);
+        params.mAllowedProtocols.set(Protocol.WPA);
+        return params;
+    }
+
+    /**
+     * Create OSEN params.
+     */
+    private static @NonNull SecurityParams createOsenParams() {
+        SecurityParams params = new SecurityParams();
+        params.mSecurityType = WifiConfiguration.SECURITY_TYPE_OSEN;
+
+        params.mAllowedKeyManagement.set(KeyMgmt.OSEN);
+
+        params.mAllowedProtocols.set(Protocol.OSEN);
+
+        params.mAllowedPairwiseCiphers.set(PairwiseCipher.CCMP);
+        params.mAllowedPairwiseCiphers.set(PairwiseCipher.TKIP);
+
+        params.mAllowedGroupCiphers.set(GroupCipher.CCMP);
+        params.mAllowedGroupCiphers.set(GroupCipher.TKIP);
+        return params;
+    }
+
+    /**
+     * Create WAPI-CERT params.
+     */
+    private static @NonNull SecurityParams createWapiCertParams() {
+        SecurityParams params = new SecurityParams();
+        params.mSecurityType = WifiConfiguration.SECURITY_TYPE_WAPI_CERT;
+
+        params.mAllowedKeyManagement.set(KeyMgmt.WAPI_CERT);
+
+        params.mAllowedProtocols.set(Protocol.WAPI);
+
+        params.mAllowedPairwiseCiphers.set(PairwiseCipher.SMS4);
+
+        params.mAllowedGroupCiphers.set(GroupCipher.SMS4);
+        return params;
+    }
+
+    /**
+     * Create WAPI-PSK params.
+     */
+    private static @NonNull SecurityParams createWapiPskParams() {
+        SecurityParams params = new SecurityParams();
+        params.mSecurityType = WifiConfiguration.SECURITY_TYPE_WAPI_PSK;
+
+        params.mAllowedKeyManagement.set(KeyMgmt.WAPI_PSK);
+
+        params.mAllowedProtocols.set(Protocol.WAPI);
+
+        params.mAllowedPairwiseCiphers.set(PairwiseCipher.SMS4);
+
+        params.mAllowedGroupCiphers.set(GroupCipher.SMS4);
+        return params;
+    }
+
+    /**
+     * Create WEP params.
+     */
+    private static @NonNull SecurityParams createWepParams() {
+        SecurityParams params = new SecurityParams();
+        params.mSecurityType = WifiConfiguration.SECURITY_TYPE_WEP;
+
+        params.mAllowedKeyManagement.set(KeyMgmt.NONE);
+
+        params.mAllowedProtocols.set(Protocol.RSN);
+
+        params.mAllowedAuthAlgorithms.set(AuthAlgorithm.OPEN);
+        params.mAllowedAuthAlgorithms.set(AuthAlgorithm.SHARED);
+
+        params.mAllowedPairwiseCiphers.set(PairwiseCipher.CCMP);
+        params.mAllowedPairwiseCiphers.set(PairwiseCipher.TKIP);
+
+        params.mAllowedGroupCiphers.set(GroupCipher.CCMP);
+        params.mAllowedGroupCiphers.set(GroupCipher.TKIP);
+        params.mAllowedGroupCiphers.set(GroupCipher.WEP40);
+        params.mAllowedGroupCiphers.set(GroupCipher.WEP104);
+        return params;
+    }
+
+    /**
+     * Create WPA3 Enterprise 192-bit params.
+     */
+    private static @NonNull SecurityParams createWpa3Enterprise192BitParams() {
+        SecurityParams params = new SecurityParams();
+        params.mSecurityType = WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT;
+
+        params.mAllowedKeyManagement.set(KeyMgmt.WPA_EAP);
+        params.mAllowedKeyManagement.set(KeyMgmt.IEEE8021X);
+        params.mAllowedKeyManagement.set(KeyMgmt.SUITE_B_192);
+
+        params.mAllowedProtocols.set(Protocol.RSN);
+
+        params.mAllowedPairwiseCiphers.set(PairwiseCipher.GCMP_128);
+        params.mAllowedPairwiseCiphers.set(PairwiseCipher.GCMP_256);
+
+        params.mAllowedGroupCiphers.set(GroupCipher.GCMP_128);
+        params.mAllowedGroupCiphers.set(GroupCipher.GCMP_256);
+
+        params.mAllowedGroupManagementCiphers.set(GroupMgmtCipher.BIP_GMAC_256);
+
+        // Note: allowedSuiteBCiphers bitset will be set by the service once the
+        // certificates are attached to this profile
+
+        params.mRequirePmf = true;
+        return params;
+    }
+
+    /**
+     * Create WPA3 Enterprise params.
+     */
+    private static @NonNull SecurityParams createWpa3EnterpriseParams() {
+        SecurityParams params = new SecurityParams();
+        params.mSecurityType = WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE;
+
+        params.mAllowedKeyManagement.set(KeyMgmt.WPA_EAP);
+        params.mAllowedKeyManagement.set(KeyMgmt.IEEE8021X);
+
+        params.mAllowedProtocols.set(Protocol.RSN);
+
+        params.mAllowedPairwiseCiphers.set(PairwiseCipher.CCMP);
+        params.mAllowedPairwiseCiphers.set(PairwiseCipher.GCMP_256);
+
+        params.mAllowedGroupCiphers.set(GroupCipher.CCMP);
+        params.mAllowedGroupCiphers.set(GroupCipher.GCMP_256);
+
+        params.mRequirePmf = true;
+        return params;
+    }
+
+    /**
+     * Create WPA3 Personal params.
+     */
+    private static @NonNull SecurityParams createWpa3PersonalParams() {
+        SecurityParams params = new SecurityParams();
+        params.mSecurityType = WifiConfiguration.SECURITY_TYPE_SAE;
+
+        params.mAllowedKeyManagement.set(KeyMgmt.SAE);
+
+        params.mAllowedProtocols.set(Protocol.RSN);
+
+        params.mAllowedPairwiseCiphers.set(PairwiseCipher.CCMP);
+        params.mAllowedPairwiseCiphers.set(PairwiseCipher.GCMP_128);
+        params.mAllowedPairwiseCiphers.set(PairwiseCipher.GCMP_256);
+
+        params.mAllowedGroupCiphers.set(GroupCipher.CCMP);
+        params.mAllowedGroupCiphers.set(GroupCipher.GCMP_128);
+        params.mAllowedGroupCiphers.set(GroupCipher.GCMP_256);
+
+        params.mRequirePmf = true;
+        return params;
+    }
+
+    /**
+     * Create WPA/WPA2 Personal params.
+     */
+    private static @NonNull SecurityParams createWpaWpa2PersonalParams() {
+        SecurityParams params = new SecurityParams();
+        params.mSecurityType = WifiConfiguration.SECURITY_TYPE_PSK;
+
+        params.mAllowedKeyManagement.set(KeyMgmt.WPA_PSK);
+
+        params.mAllowedProtocols.set(Protocol.RSN);
+        params.mAllowedProtocols.set(Protocol.WPA);
+
+        params.mAllowedPairwiseCiphers.set(PairwiseCipher.CCMP);
+        params.mAllowedPairwiseCiphers.set(PairwiseCipher.TKIP);
+
+        params.mAllowedGroupCiphers.set(GroupCipher.CCMP);
+        params.mAllowedGroupCiphers.set(GroupCipher.TKIP);
+        params.mAllowedGroupCiphers.set(GroupCipher.WEP40);
+        params.mAllowedGroupCiphers.set(GroupCipher.WEP104);
+        return params;
+    }
+}
diff --git a/framework/java/android/net/wifi/SoftApCapability.java b/framework/java/android/net/wifi/SoftApCapability.java
index dcb57ec..de85da6 100644
--- a/framework/java/android/net/wifi/SoftApCapability.java
+++ b/framework/java/android/net/wifi/SoftApCapability.java
@@ -20,11 +20,14 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.net.MacAddress;
+import android.net.wifi.SoftApConfiguration.BandType;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
 import java.util.Objects;
 
 /**
@@ -36,22 +39,24 @@
 @SystemApi
 public final class SoftApCapability implements Parcelable {
 
+    private static final String TAG = "SoftApCapability";
+    private static final int[] EMPTY_INT_ARRAY = new int[0];
     /**
      * Support for automatic channel selection in driver (ACS).
      * Driver will auto select best channel based on interference to optimize performance.
      *
-     * flag when {@link R.bool.config_wifi_softap_acs_supported)} is true.
+     * flag when {@code R.bool.config_wifi_softap_acs_supported} is true.
      *
      * <p>
      * Use {@link WifiManager.SoftApCallback#onInfoChanged(SoftApInfo)} and
-     * {@link SoftApInfo#getFrequency} and {@link SoftApInfo#getBandwidth} to get
+     * {@link SoftApInfo#getFrequency()} and {@link SoftApInfo#getBandwidth()} to get
      * driver channel selection result.
      */
     public static final long SOFTAP_FEATURE_ACS_OFFLOAD = 1 << 0;
 
     /**
      * Support for client force disconnect.
-     * flag when {@link R.bool.config_wifi_sofap_client_force_disconnect_supported)} is true
+     * flag when {@code R.bool.config_wifiSofapClientForceDisconnectSupported} is true
      *
      * <p>
      * Several Soft AP client control features, e.g. specifying the maximum number of
@@ -61,20 +66,71 @@
      */
     public static final long SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT = 1 << 1;
 
-
     /**
      * Support for WPA3 Simultaneous Authentication of Equals (WPA3-SAE).
      *
-     * flag when {@link config_wifi_softap_sae_supported)} is true.
+     * flag when {@code config_wifi_softap_sae_supported} is true.
      */
     public static final long SOFTAP_FEATURE_WPA3_SAE = 1 << 2;
 
+    /**
+     * Support for MAC address customization.
+     * flag when {@code R.bool.config_wifiSoftapMacAddressCustomizationSupported} is true
+     *
+     * <p>
+     * Check feature support before invoking
+     * {@link SoftApConfiguration.Builder#setBssid(MacAddress)} or
+     * {@link SoftApConfiguration.Builder#setMacRandomizationSetting(int)} with
+     * {@link SoftApConfiguration#RANDOMIZATION_PERSISTENT}
+     */
+    public static final long SOFTAP_FEATURE_MAC_ADDRESS_CUSTOMIZATION = 1 << 3;
+
+    /**
+     * Support for 802.11ax SAP.
+     * flag when {@code R.bool.config_wifiSoftapIeee80211axSupported} is true
+     *
+     * <p>
+     * Check feature support before invoking
+     * {@link SoftApConfiguration.Builder#setIeee80211axEnabled(boolean)}
+     */
+    public static final long SOFTAP_FEATURE_IEEE80211_AX = 1 << 4;
+
+    /**
+     * Support for 2.4G Band.
+     * flag when {@code R.bool.config_wifiSoftap24ghzSupported} is true
+     */
+    public static final long SOFTAP_FEATURE_BAND_24G_SUPPORTED = 1 << 5;
+
+    /**
+     * Support for 5G Band.
+     * flag when {@code R.bool.config_wifiSoftap5ghzSupported} is true
+     */
+    public static final long SOFTAP_FEATURE_BAND_5G_SUPPORTED = 1 << 6;
+
+    /**
+     * Support for 6G Band.
+     * flag when {@code R.bool.config_wifiSoftap6ghzSupported} is true
+     */
+    public static final long SOFTAP_FEATURE_BAND_6G_SUPPORTED = 1 << 7;
+
+    /**
+     * Support for 60G Band.
+     * flag when {@code R.bool.config_wifiSoftap60ghzSupported} is true
+     */
+    public static final long SOFTAP_FEATURE_BAND_60G_SUPPORTED = 1 << 8;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @LongDef(flag = true, prefix = { "SOFTAP_FEATURE_" }, value = {
             SOFTAP_FEATURE_ACS_OFFLOAD,
             SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT,
             SOFTAP_FEATURE_WPA3_SAE,
+            SOFTAP_FEATURE_MAC_ADDRESS_CUSTOMIZATION,
+            SOFTAP_FEATURE_IEEE80211_AX,
+            SOFTAP_FEATURE_BAND_24G_SUPPORTED,
+            SOFTAP_FEATURE_BAND_5G_SUPPORTED,
+            SOFTAP_FEATURE_BAND_6G_SUPPORTED,
+            SOFTAP_FEATURE_BAND_60G_SUPPORTED
     })
     public @interface HotspotFeatures {}
 
@@ -83,6 +139,26 @@
     private int mMaximumSupportedClientNumber;
 
     /**
+     * A list storing supported 2.4G channels.
+     */
+    private int[] mSupportedChannelListIn24g = EMPTY_INT_ARRAY;
+
+    /**
+     * A list storing supported 5G channels.
+     */
+    private int[] mSupportedChannelListIn5g = EMPTY_INT_ARRAY;
+
+    /**
+     * A list storing supported 6G channels.
+     */
+    private int[] mSupportedChannelListIn6g = EMPTY_INT_ARRAY;
+
+    /**
+     * A list storing supported 60G channels.
+     */
+    private int[] mSupportedChannelListIn60g = EMPTY_INT_ARRAY;
+
+    /**
      * Get the maximum supported client numbers which AP resides on.
      */
     public int getMaxSupportedClients() {
@@ -102,7 +178,7 @@
     /**
      * Returns true when all of the queried features are supported, otherwise false.
      *
-     * @param features One or combination of the following features:
+     * @param features One or combination of {@code SOFTAP_FEATURE_}, for instance:
      * {@link #SOFTAP_FEATURE_ACS_OFFLOAD}, {@link #SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT} or
      * {@link #SOFTAP_FEATURE_WPA3_SAE}.
      */
@@ -111,12 +187,79 @@
     }
 
     /**
+     * Set supported channel list in target band type.
+     *
+     * @param band One of the following band types:
+     * {@link SoftApConfiguration#BAND_2GHZ}, {@link SoftApConfiguration#BAND_5GHZ},
+     * {@link SoftApConfiguration#BAND_6GHZ}, or {@link SoftApConfiguration#BAND_60GHZ}.
+     * @param supportedChannelList supported channel list in target band
+     * @return true if band and supportedChannelList are valid, otherwise false.
+     *
+     * @throws IllegalArgumentException when band type is invalid.
+     * @hide
+     */
+    public boolean setSupportedChannelList(@BandType int band,
+            @Nullable int[] supportedChannelList) {
+        if (supportedChannelList == null)  return false;
+        switch (band) {
+            case SoftApConfiguration.BAND_2GHZ:
+                mSupportedChannelListIn24g = supportedChannelList;
+                break;
+            case SoftApConfiguration.BAND_5GHZ:
+                mSupportedChannelListIn5g = supportedChannelList;
+                break;
+            case SoftApConfiguration.BAND_6GHZ:
+                mSupportedChannelListIn6g = supportedChannelList;
+                break;
+            case SoftApConfiguration.BAND_60GHZ:
+                mSupportedChannelListIn60g = supportedChannelList;
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid band: " + band);
+        }
+        return true;
+    }
+
+    /**
+     * Returns a list of the supported channels in the given band.
+     * The result depends on the on the country code that has been set.
+     * Can be used to set the channel of the AP with the
+     * {@link SoftApConfiguration.Builder#setChannel(int, int)} API.
+     *
+     * @param band One of the following band types:
+     * {@link SoftApConfiguration#BAND_2GHZ}, {@link SoftApConfiguration#BAND_5GHZ},
+     * {@link SoftApConfiguration#BAND_6GHZ}, {@link SoftApConfiguration#BAND_60GHZ}.
+     * @return List of supported channels for the band.
+     *
+     * @throws IllegalArgumentException when band type is invalid.
+     */
+    @NonNull
+    public int[] getSupportedChannelList(@BandType int band) {
+        switch (band) {
+            case SoftApConfiguration.BAND_2GHZ:
+                return mSupportedChannelListIn24g;
+            case SoftApConfiguration.BAND_5GHZ:
+                return mSupportedChannelListIn5g;
+            case SoftApConfiguration.BAND_6GHZ:
+                return mSupportedChannelListIn6g;
+            case SoftApConfiguration.BAND_60GHZ:
+                return mSupportedChannelListIn60g;
+            default:
+                throw new IllegalArgumentException("Invalid band: " + band);
+        }
+    }
+
+    /**
      * @hide
      */
     public SoftApCapability(@Nullable SoftApCapability source) {
         if (source != null) {
             mSupportedFeatures = source.mSupportedFeatures;
             mMaximumSupportedClientNumber = source.mMaximumSupportedClientNumber;
+            mSupportedChannelListIn24g = source.mSupportedChannelListIn24g;
+            mSupportedChannelListIn5g = source.mSupportedChannelListIn5g;
+            mSupportedChannelListIn6g = source.mSupportedChannelListIn6g;
+            mSupportedChannelListIn60g = source.mSupportedChannelListIn60g;
         }
     }
 
@@ -124,7 +267,7 @@
      * Constructor with combination of the feature.
      * Zero to no supported feature.
      *
-     * @param features One or combination of the following features:
+     * @param features One or combination of {@code SOFTAP_FEATURE_}, for instance:
      * {@link #SOFTAP_FEATURE_ACS_OFFLOAD}, {@link #SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT} or
      * {@link #SOFTAP_FEATURE_WPA3_SAE}.
      * @hide
@@ -144,15 +287,22 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeLong(mSupportedFeatures);
         dest.writeInt(mMaximumSupportedClientNumber);
+        dest.writeIntArray(mSupportedChannelListIn24g);
+        dest.writeIntArray(mSupportedChannelListIn5g);
+        dest.writeIntArray(mSupportedChannelListIn6g);
+        dest.writeIntArray(mSupportedChannelListIn60g);
     }
 
     @NonNull
     /** Implement the Parcelable interface */
     public static final Creator<SoftApCapability> CREATOR = new Creator<SoftApCapability>() {
         public SoftApCapability createFromParcel(Parcel in) {
-            long supportedFeatures = in.readLong();
-            SoftApCapability capability = new SoftApCapability(supportedFeatures);
+            SoftApCapability capability = new SoftApCapability(in.readLong());
             capability.mMaximumSupportedClientNumber = in.readInt();
+            capability.setSupportedChannelList(SoftApConfiguration.BAND_2GHZ, in.createIntArray());
+            capability.setSupportedChannelList(SoftApConfiguration.BAND_5GHZ, in.createIntArray());
+            capability.setSupportedChannelList(SoftApConfiguration.BAND_6GHZ, in.createIntArray());
+            capability.setSupportedChannelList(SoftApConfiguration.BAND_60GHZ, in.createIntArray());
             return capability;
         }
 
@@ -167,6 +317,12 @@
         StringBuilder sbuf = new StringBuilder();
         sbuf.append("SupportedFeatures=").append(mSupportedFeatures);
         sbuf.append("MaximumSupportedClientNumber=").append(mMaximumSupportedClientNumber);
+        sbuf.append("SupportedChannelListIn24g")
+                .append(Arrays.toString(mSupportedChannelListIn24g));
+        sbuf.append("SupportedChannelListIn5g").append(Arrays.toString(mSupportedChannelListIn5g));
+        sbuf.append("SupportedChannelListIn6g").append(Arrays.toString(mSupportedChannelListIn6g));
+        sbuf.append("SupportedChannelListIn60g")
+                .append(Arrays.toString(mSupportedChannelListIn60g));
         return sbuf.toString();
     }
 
@@ -176,11 +332,19 @@
         if (!(o instanceof SoftApCapability)) return false;
         SoftApCapability capability = (SoftApCapability) o;
         return mSupportedFeatures == capability.mSupportedFeatures
-                && mMaximumSupportedClientNumber == capability.mMaximumSupportedClientNumber;
+                && mMaximumSupportedClientNumber == capability.mMaximumSupportedClientNumber
+                && Arrays.equals(mSupportedChannelListIn24g, capability.mSupportedChannelListIn24g)
+                && Arrays.equals(mSupportedChannelListIn5g, capability.mSupportedChannelListIn5g)
+                && Arrays.equals(mSupportedChannelListIn6g, capability.mSupportedChannelListIn6g)
+                && Arrays.equals(mSupportedChannelListIn60g, capability.mSupportedChannelListIn60g);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mSupportedFeatures, mMaximumSupportedClientNumber);
+        return Objects.hash(mSupportedFeatures, mMaximumSupportedClientNumber,
+                Arrays.hashCode(mSupportedChannelListIn24g),
+                Arrays.hashCode(mSupportedChannelListIn5g),
+                Arrays.hashCode(mSupportedChannelListIn6g),
+                Arrays.hashCode(mSupportedChannelListIn60g));
     }
 }
diff --git a/framework/java/android/net/wifi/SoftApConfiguration.java b/framework/java/android/net/wifi/SoftApConfiguration.java
index d2ff658..b1aa97b 100644
--- a/framework/java/android/net/wifi/SoftApConfiguration.java
+++ b/framework/java/android/net/wifi/SoftApConfiguration.java
@@ -22,17 +22,21 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.net.MacAddress;
+import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.SparseIntArray;
+
+import androidx.annotation.RequiresApi;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
+import com.android.modules.utils.build.SdkLevel;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.nio.charset.CharsetEncoder;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
@@ -87,9 +91,21 @@
     public static final int BAND_6GHZ = 1 << 2;
 
     /**
-     * Device is allowed to choose the optimal band (2Ghz, 5Ghz, 6Ghz) based on device capability,
+     * 60GHz band.
+     * @hide
+     */
+    @SystemApi
+    public static final int BAND_60GHZ = 1 << 3;
+
+    /**
+     * Device is allowed to choose the optimal band (2GHz, 5GHz, 6GHz) based on device capability,
      * operating country code and current radio conditions.
      * @hide
+     *
+     * @deprecated This is no longer supported. The value is fixed at
+     * (BAND_2GHZ | BAND_5GHZ | BAND_6GHZ) even if a new band is supported in the future, for
+     * instance {@code BAND_60GHZ}. The bands are a bit mask - use any combination of
+     * {@code BAND_}, for instance {@code BAND_2GHZ | BAND_5GHZ}.
      */
     @SystemApi
     public static final int BAND_ANY = BAND_2GHZ | BAND_5GHZ | BAND_6GHZ;
@@ -100,11 +116,19 @@
             BAND_2GHZ,
             BAND_5GHZ,
             BAND_6GHZ,
+            BAND_60GHZ,
     })
     public @interface BandType {}
 
+    /**
+     * All of the supported band types.
+     * @hide
+     */
+    public static int[] BAND_TYPES = {BAND_2GHZ, BAND_5GHZ, BAND_6GHZ, BAND_60GHZ};
+
     private static boolean isBandValid(@BandType int band) {
-        return ((band != 0) && ((band & ~BAND_ANY) == 0));
+        int bandAny = BAND_2GHZ | BAND_5GHZ | BAND_6GHZ | BAND_60GHZ;
+        return ((band != 0) && ((band & ~bandAny) == 0));
     }
 
     private static final int MIN_CH_2G_BAND = 1;
@@ -113,6 +137,8 @@
     private static final int MAX_CH_5G_BAND = 196;
     private static final int MIN_CH_6G_BAND = 1;
     private static final int MAX_CH_6G_BAND = 253;
+    private static final int MIN_CH_60G_BAND = 1;
+    private static final int MAX_CH_60G_BAND = 6;
 
 
 
@@ -135,6 +161,13 @@
                     return false;
                 }
                 break;
+
+            case BAND_60GHZ:
+                if (channel < MIN_CH_60G_BAND || channel >  MAX_CH_60G_BAND) {
+                    return false;
+                }
+                break;
+
             default:
                 return false;
         }
@@ -164,16 +197,12 @@
     private final boolean mHiddenSsid;
 
     /**
-     * The operating band of the AP.
-     * One or combination of the following band type:
-     * {@link #BAND_2GHZ}, {@link #BAND_5GHZ}, {@link #BAND_6GHZ}.
+     * The operating channels of the dual APs.
+     *
+     * The SparseIntArray that consists the band and the channel of matching the band.
      */
-    private final @BandType int mBand;
-
-    /**
-     * The operating channel of the AP.
-     */
-    private final int mChannel;
+    @NonNull
+    private final SparseIntArray mChannels;
 
     /**
      * The maximim allowed number of clients that can associate to the AP.
@@ -217,6 +246,49 @@
      */
     private final long mShutdownTimeoutMillis;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"RANDOMIZATION_"}, value = {
+            RANDOMIZATION_NONE,
+            RANDOMIZATION_PERSISTENT})
+    public @interface MacRandomizationSetting {}
+
+    /**
+     * Use factory MAC as BSSID for the AP
+     * @hide
+     */
+    @SystemApi
+    public static final int RANDOMIZATION_NONE = 0;
+    /**
+     * Generate a randomized MAC as BSSID for the AP
+     * @hide
+     */
+    @SystemApi
+    public static final int RANDOMIZATION_PERSISTENT = 1;
+
+    /**
+     * Level of MAC randomization for the AP BSSID.
+     */
+    @MacRandomizationSetting
+    private int mMacRandomizationSetting;
+
+
+    /**
+     * Whether opportunistic shutdown of an instance in bridged AP is enabled or not.
+     */
+    private boolean mBridgedModeOpportunisticShutdownEnabled;
+
+    /**
+     * Whether 802.11ax AP is enabled or not.
+     */
+    private boolean mIeee80211axEnabled;
+
+    /**
+     * Whether the current configuration is configured by user or not.
+     */
+    private boolean mIsUserConfiguration;
+
+
     /**
      * THe definition of security type OPEN.
      */
@@ -249,16 +321,22 @@
 
     /** Private constructor for Builder and Parcelable implementation. */
     private SoftApConfiguration(@Nullable String ssid, @Nullable MacAddress bssid,
-            @Nullable String passphrase, boolean hiddenSsid, @BandType int band, int channel,
+            @Nullable String passphrase, boolean hiddenSsid, @NonNull SparseIntArray channels,
             @SecurityType int securityType, int maxNumberOfClients, boolean shutdownTimeoutEnabled,
             long shutdownTimeoutMillis, boolean clientControlByUser,
-            @NonNull List<MacAddress> blockedList, @NonNull List<MacAddress> allowedList) {
+            @NonNull List<MacAddress> blockedList, @NonNull List<MacAddress> allowedList,
+            int macRandomizationSetting, boolean bridgedModeOpportunisticShutdownEnabled,
+            boolean ieee80211axEnabled, boolean isUserConfiguration) {
         mSsid = ssid;
         mBssid = bssid;
         mPassphrase = passphrase;
         mHiddenSsid = hiddenSsid;
-        mBand = band;
-        mChannel = channel;
+        if (channels.size() != 0) {
+            mChannels = channels.clone();
+        } else {
+            mChannels = new SparseIntArray(1);
+            mChannels.put(BAND_2GHZ, 0);
+        }
         mSecurityType = securityType;
         mMaxNumberOfClients = maxNumberOfClients;
         mAutoShutdownEnabled = shutdownTimeoutEnabled;
@@ -266,6 +344,10 @@
         mClientControlByUser = clientControlByUser;
         mBlockedClientList = new ArrayList<>(blockedList);
         mAllowedClientList = new ArrayList<>(allowedList);
+        mMacRandomizationSetting = macRandomizationSetting;
+        mBridgedModeOpportunisticShutdownEnabled = bridgedModeOpportunisticShutdownEnabled;
+        mIeee80211axEnabled = ieee80211axEnabled;
+        mIsUserConfiguration = isUserConfiguration;
     }
 
     @Override
@@ -281,42 +363,52 @@
                 && Objects.equals(mBssid, other.mBssid)
                 && Objects.equals(mPassphrase, other.mPassphrase)
                 && mHiddenSsid == other.mHiddenSsid
-                && mBand == other.mBand
-                && mChannel == other.mChannel
+                && mChannels.toString().equals(other.mChannels.toString())
                 && mSecurityType == other.mSecurityType
                 && mMaxNumberOfClients == other.mMaxNumberOfClients
                 && mAutoShutdownEnabled == other.mAutoShutdownEnabled
                 && mShutdownTimeoutMillis == other.mShutdownTimeoutMillis
                 && mClientControlByUser == other.mClientControlByUser
                 && Objects.equals(mBlockedClientList, other.mBlockedClientList)
-                && Objects.equals(mAllowedClientList, other.mAllowedClientList);
+                && Objects.equals(mAllowedClientList, other.mAllowedClientList)
+                && mMacRandomizationSetting == other.mMacRandomizationSetting
+                && mBridgedModeOpportunisticShutdownEnabled
+                == other.mBridgedModeOpportunisticShutdownEnabled
+                && mIeee80211axEnabled == other.mIeee80211axEnabled
+                && mIsUserConfiguration == other.mIsUserConfiguration;
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mSsid, mBssid, mPassphrase, mHiddenSsid,
-                mBand, mChannel, mSecurityType, mMaxNumberOfClients, mAutoShutdownEnabled,
+                mChannels.toString(), mSecurityType, mMaxNumberOfClients, mAutoShutdownEnabled,
                 mShutdownTimeoutMillis, mClientControlByUser, mBlockedClientList,
-                mAllowedClientList);
+                mAllowedClientList, mMacRandomizationSetting,
+                mBridgedModeOpportunisticShutdownEnabled, mIeee80211axEnabled,
+                mIsUserConfiguration);
     }
 
     @Override
     public String toString() {
         StringBuilder sbuf = new StringBuilder();
-        sbuf.append("ssid=").append(mSsid);
-        if (mBssid != null) sbuf.append(" \n bssid=").append(mBssid.toString());
-        sbuf.append(" \n Passphrase =").append(
+        sbuf.append("ssid = ").append(mSsid);
+        if (mBssid != null) sbuf.append(" \n bssid = ").append(mBssid.toString());
+        sbuf.append(" \n Passphrase = ").append(
                 TextUtils.isEmpty(mPassphrase) ? "<empty>" : "<non-empty>");
-        sbuf.append(" \n HiddenSsid =").append(mHiddenSsid);
-        sbuf.append(" \n Band =").append(mBand);
-        sbuf.append(" \n Channel =").append(mChannel);
-        sbuf.append(" \n SecurityType=").append(getSecurityType());
-        sbuf.append(" \n MaxClient=").append(mMaxNumberOfClients);
-        sbuf.append(" \n AutoShutdownEnabled=").append(mAutoShutdownEnabled);
-        sbuf.append(" \n ShutdownTimeoutMillis=").append(mShutdownTimeoutMillis);
-        sbuf.append(" \n ClientControlByUser=").append(mClientControlByUser);
-        sbuf.append(" \n BlockedClientList=").append(mBlockedClientList);
-        sbuf.append(" \n AllowedClientList=").append(mAllowedClientList);
+        sbuf.append(" \n HiddenSsid = ").append(mHiddenSsid);
+        sbuf.append(" \n Channels = ").append(mChannels);
+        sbuf.append(" \n SecurityType = ").append(getSecurityType());
+        sbuf.append(" \n MaxClient = ").append(mMaxNumberOfClients);
+        sbuf.append(" \n AutoShutdownEnabled = ").append(mAutoShutdownEnabled);
+        sbuf.append(" \n ShutdownTimeoutMillis = ").append(mShutdownTimeoutMillis);
+        sbuf.append(" \n ClientControlByUser = ").append(mClientControlByUser);
+        sbuf.append(" \n BlockedClientList = ").append(mBlockedClientList);
+        sbuf.append(" \n AllowedClientList= ").append(mAllowedClientList);
+        sbuf.append(" \n MacRandomizationSetting = ").append(mMacRandomizationSetting);
+        sbuf.append(" \n BridgedModeInstanceOpportunisticEnabled = ")
+                .append(mBridgedModeOpportunisticShutdownEnabled);
+        sbuf.append(" \n Ieee80211axEnabled = ").append(mIeee80211axEnabled);
+        sbuf.append(" \n isUserConfiguration = ").append(mIsUserConfiguration);
         return sbuf.toString();
     }
 
@@ -326,8 +418,7 @@
         dest.writeParcelable(mBssid, flags);
         dest.writeString(mPassphrase);
         dest.writeBoolean(mHiddenSsid);
-        dest.writeInt(mBand);
-        dest.writeInt(mChannel);
+        writeSparseIntArray(dest, mChannels);
         dest.writeInt(mSecurityType);
         dest.writeInt(mMaxNumberOfClients);
         dest.writeBoolean(mAutoShutdownEnabled);
@@ -335,8 +426,48 @@
         dest.writeBoolean(mClientControlByUser);
         dest.writeTypedList(mBlockedClientList);
         dest.writeTypedList(mAllowedClientList);
+        dest.writeInt(mMacRandomizationSetting);
+        dest.writeBoolean(mBridgedModeOpportunisticShutdownEnabled);
+        dest.writeBoolean(mIeee80211axEnabled);
+        dest.writeBoolean(mIsUserConfiguration);
     }
 
+    /* Reference from frameworks/base/core/java/android/os/Parcel.java */
+    private static void writeSparseIntArray(@NonNull Parcel dest,
+            @Nullable SparseIntArray val) {
+        if (val == null) {
+            dest.writeInt(-1);
+            return;
+        }
+        int n = val.size();
+        dest.writeInt(n);
+        int i = 0;
+        while (i < n) {
+            dest.writeInt(val.keyAt(i));
+            dest.writeInt(val.valueAt(i));
+            i++;
+        }
+    }
+
+
+    /* Reference from frameworks/base/core/java/android/os/Parcel.java */
+    @NonNull
+    private static SparseIntArray readSparseIntArray(@NonNull Parcel in) {
+        int n = in.readInt();
+        if (n < 0) {
+            return new SparseIntArray();
+        }
+        SparseIntArray sa = new SparseIntArray(n);
+        while (n > 0) {
+            int key = in.readInt();
+            int value = in.readInt();
+            sa.append(key, value);
+            n--;
+        }
+        return sa;
+    }
+
+
     @Override
     public int describeContents() {
         return 0;
@@ -349,10 +480,11 @@
             return new SoftApConfiguration(
                     in.readString(),
                     in.readParcelable(MacAddress.class.getClassLoader()),
-                    in.readString(), in.readBoolean(), in.readInt(), in.readInt(), in.readInt(),
+                    in.readString(), in.readBoolean(), readSparseIntArray(in), in.readInt(),
                     in.readInt(), in.readBoolean(), in.readLong(), in.readBoolean(),
                     in.createTypedArrayList(MacAddress.CREATOR),
-                    in.createTypedArrayList(MacAddress.CREATOR));
+                    in.createTypedArrayList(MacAddress.CREATOR), in.readInt(), in.readBoolean(),
+                    in.readBoolean(), in.readBoolean());
         }
 
         @Override
@@ -363,7 +495,7 @@
 
     /**
      * Return String set to be the SSID for the AP.
-     * {@link Builder#setSsid(String)}.
+     * See also {@link Builder#setSsid(String)}.
      */
     @Nullable
     public String getSsid() {
@@ -372,7 +504,7 @@
 
     /**
      * Returns MAC address set to be BSSID for the AP.
-     * {@link Builder#setBssid(MacAddress)}.
+     * See also {@link Builder#setBssid(MacAddress)}.
      */
     @Nullable
     public MacAddress getBssid() {
@@ -381,7 +513,7 @@
 
     /**
      * Returns String set to be passphrase for current AP.
-     * {@link Builder#setPassphrase(String, int)}.
+     * See also {@link Builder#setPassphrase(String, int)}.
      */
     @Nullable
     public String getPassphrase() {
@@ -391,7 +523,7 @@
     /**
      * Returns Boolean set to be indicate hidden (true: doesn't broadcast its SSID) or
      * not (false: broadcasts its SSID) for the AP.
-     * {@link Builder#setHiddenSsid(boolean)}.
+     * See also {@link Builder#setHiddenSsid(boolean)}.
      */
     public boolean isHiddenSsid() {
         return mHiddenSsid;
@@ -400,27 +532,80 @@
     /**
      * Returns band type set to be the band for the AP.
      *
-     * One or combination of the following band type:
-     * {@link #BAND_2GHZ}, {@link #BAND_5GHZ}, {@link #BAND_6GHZ}.
+     * One or combination of {@code BAND_}, for instance
+     * {@link #BAND_2GHZ}, {@link #BAND_5GHZ}, or {@code BAND_2GHZ | BAND_5GHZ}.
      *
-     * {@link Builder#setBand(int)}.
+     * Note: Returns the lowest band when more than one band is set.
+     * Use {@link #getChannels()} to get dual bands setting.
+     *
+     * See also {@link Builder#setBand(int)}.
+     *
+     * @deprecated This API is deprecated. Use {@link #getChannels()} instead.
+     * @hide
+     */
+    @Deprecated
+    @SystemApi
+    public @BandType int getBand() {
+        return mChannels.keyAt(0);
+    }
+
+    /**
+     * Returns a sorted array in ascending order that consists of the configured band types
+     * for the APs.
+     *
+     * The band type is one or combination of {@code BAND_}, for instance
+     * {@link #BAND_2GHZ}, {@link #BAND_5GHZ}, or {@code BAND_2GHZ | BAND_5GHZ}.
+     *
+     * Note: return array may only include one band when current setting is single AP mode.
+     * See also {@link Builder#setBands(int[])}.
      *
      * @hide
      */
-    @SystemApi
-    public @BandType int getBand() {
-        return mBand;
+    public @NonNull int[] getBands() {
+        int[] bands = new int[mChannels.size()];
+        for (int i = 0; i < bands.length; i++) {
+            bands[i] = mChannels.keyAt(i);
+        }
+        return bands;
     }
 
     /**
      * Returns Integer set to be the channel for the AP.
-     * {@link Builder#setChannel(int)}.
+     *
+     * Note: Returns the channel which associated to the lowest band if more than one channel
+     * is set. Use {@link Builder#getChannels()} to get dual channel setting.
+     * See also {@link Builder#setChannel(int, int)}.
+     *
+     * @deprecated This API is deprecated. Use {@link #getChannels()} instead.
+     * @hide
+     */
+    @Deprecated
+    @SystemApi
+    public int getChannel() {
+        return mChannels.valueAt(0);
+    }
+
+
+    /**
+     * Returns SparseIntArray (key: {@code BandType} , value: channel) that consists of
+     * the configured bands and channels for the AP(s).
+     *
+     * The returned channel value is Wi-Fi channel numbering.
+     * Reference the Wi-Fi channel numbering and the channelization in IEEE 802.11-2016
+     * specifications, section 17.3.8.4.2, 17.3.8.4.3 and Table 15-6.
+     *
+     * Note: return array may only include one channel when current setting is single AP mode.
+     * See also {@link Builder#setChannels(SparseIntArray)}.
      *
      * @hide
      */
+    @RequiresApi(Build.VERSION_CODES.S)
     @SystemApi
-    public int getChannel() {
-        return mChannel;
+    public @NonNull SparseIntArray getChannels() {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        return mChannels.clone();
     }
 
     /**
@@ -438,7 +623,7 @@
 
     /**
      * Returns the maximum number of clients that can associate to the AP.
-     * {@link Builder#setMaxNumberOfClients(int)}.
+     * See also {@link Builder#setMaxNumberOfClients(int)}.
      *
      * @hide
      */
@@ -450,7 +635,7 @@
     /**
      * Returns whether auto shutdown is enabled or not.
      * The Soft AP will shutdown when there are no devices associated to it for
-     * the timeout duration. See {@link Builder#setAutoShutdownEnabled(boolean)}.
+     * the timeout duration. See also {@link Builder#setAutoShutdownEnabled(boolean)}.
      *
      * @hide
      */
@@ -462,7 +647,7 @@
     /**
      * Returns the shutdown timeout in milliseconds.
      * The Soft AP will shutdown when there are no devices associated to it for
-     * the timeout duration. See {@link Builder#setShutdownTimeoutMillis(long)}.
+     * the timeout duration. See also {@link Builder#setShutdownTimeoutMillis(long)}.
      *
      * @hide
      */
@@ -474,7 +659,7 @@
     /**
      * Returns a flag indicating whether clients need to be pre-approved by the user.
      * (true: authorization required) or not (false: not required).
-     * {@link Builder#setClientControlByUserEnabled(Boolean)}.
+     * See also {@link Builder#setClientControlByUserEnabled(Boolean)}.
      *
      * @hide
      */
@@ -509,6 +694,103 @@
     }
 
     /**
+     * Returns the level of MAC randomization for the AP BSSID.
+     * See also {@link Builder#setMacRandomizationSetting(int)}.
+     *
+     * @hide
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    @SystemApi
+    @MacRandomizationSetting
+    public int getMacRandomizationSetting() {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        return getMacRandomizationSettingInternal();
+    }
+
+    /**
+     * @hide
+     */
+    @MacRandomizationSetting
+    public int getMacRandomizationSettingInternal() {
+        return mMacRandomizationSetting;
+    }
+
+    /**
+     * Returns whether opportunistic shutdown of an instance in bridged AP is enabled or not.
+     *
+     * See also {@link Builder#setBridgedModeOpportunisticShutdownEnabled(boolean}}
+     * @hide
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    @SystemApi
+    public boolean isBridgedModeOpportunisticShutdownEnabled() {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        return isBridgedModeOpportunisticShutdownEnabledInternal();
+    }
+
+    /**
+     * @see #isBridgedModeOpportunisticShutdownEnabled()
+     * @hide
+     */
+    public boolean isBridgedModeOpportunisticShutdownEnabledInternal() {
+        return mBridgedModeOpportunisticShutdownEnabled;
+    }
+
+    /**
+     * @see #isIeee80211axEnabled()
+     * @hide
+     */
+    public boolean isIeee80211axEnabledInternal() {
+        return mIeee80211axEnabled;
+    }
+
+    /**
+     * Returns whether or not 802.11ax is enabled on the SoftAP.
+     * This is an indication that if the device support 802.11ax AP then to enable or disable
+     * that feature. If the device does not support 802.11ax AP then this flag is ignored.
+     * See also {@link Builder#setIeee80211axEnabled(boolean}}
+     * @hide
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    @SystemApi
+    public boolean isIeee80211axEnabled() {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        return isIeee80211axEnabledInternal();
+    }
+
+    /**
+     * Returns whether or not the {@link SoftApConfiguration} was configured by the user
+     * (as opposed to the default system configuration).
+     * <p>
+     * The {@link SoftApConfiguration} is considered user edited once the
+     * {@link WifiManager#setSoftApConfiguration(SoftApConfiguration)} is called
+     * - whether or not that configuration is the same as the default system configuration!
+     *
+     * @hide
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    @SystemApi
+    public boolean isUserConfiguration() {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        return isUserConfigurationInternal();
+    }
+
+    /**
+     * @hide
+     */
+    public boolean isUserConfigurationInternal() {
+        return mIsUserConfiguration;
+    }
+
+    /**
      * Returns a {@link WifiConfiguration} representation of this {@link SoftApConfiguration}.
      * Note that SoftApConfiguration may contain configuration which is cannot be represented
      * by the legacy WifiConfiguration, in such cases a null will be returned.
@@ -527,7 +809,7 @@
         wifiConfig.SSID = mSsid;
         wifiConfig.preSharedKey = mPassphrase;
         wifiConfig.hiddenSSID = mHiddenSsid;
-        wifiConfig.apChannel = mChannel;
+        wifiConfig.apChannel = getChannel();
         switch (mSecurityType) {
             case SECURITY_TYPE_OPEN:
                 wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
@@ -541,7 +823,7 @@
                 return null;
         }
 
-        switch (mBand) {
+        switch (getBand()) {
             case BAND_2GHZ:
                 wifiConfig.apBand  = WifiConfiguration.AP_BAND_2GHZ;
                 break;
@@ -555,7 +837,7 @@
                 wifiConfig.apBand  = WifiConfiguration.AP_BAND_ANY;
                 break;
             default:
-                Log.e(TAG, "Convert fail, unsupported band setting :" + mBand);
+                Log.e(TAG, "Convert fail, unsupported band setting :" + getBand());
                 return null;
         }
         return wifiConfig;
@@ -576,8 +858,7 @@
         private MacAddress mBssid;
         private String mPassphrase;
         private boolean mHiddenSsid;
-        private int mBand;
-        private int mChannel;
+        private SparseIntArray mChannels;
         private int mMaxNumberOfClients;
         private int mSecurityType;
         private boolean mAutoShutdownEnabled;
@@ -585,6 +866,10 @@
         private boolean mClientControlByUser;
         private List<MacAddress> mBlockedClientList;
         private List<MacAddress> mAllowedClientList;
+        private int mMacRandomizationSetting;
+        private boolean mBridgedModeOpportunisticShutdownEnabled;
+        private boolean mIeee80211axEnabled;
+        private boolean mIsUserConfiguration;
 
         /**
          * Constructs a Builder with default values (see {@link Builder}).
@@ -594,8 +879,8 @@
             mBssid = null;
             mPassphrase = null;
             mHiddenSsid = false;
-            mBand = BAND_2GHZ;
-            mChannel = 0;
+            mChannels = new SparseIntArray(1);
+            mChannels.put(BAND_2GHZ, 0);
             mMaxNumberOfClients = 0;
             mSecurityType = SECURITY_TYPE_OPEN;
             mAutoShutdownEnabled = true; // enabled by default.
@@ -603,6 +888,10 @@
             mClientControlByUser = false;
             mBlockedClientList = new ArrayList<>();
             mAllowedClientList = new ArrayList<>();
+            mMacRandomizationSetting = RANDOMIZATION_PERSISTENT;
+            mBridgedModeOpportunisticShutdownEnabled = true;
+            mIeee80211axEnabled = true;
+            mIsUserConfiguration = true;
         }
 
         /**
@@ -615,8 +904,7 @@
             mBssid = other.mBssid;
             mPassphrase = other.mPassphrase;
             mHiddenSsid = other.mHiddenSsid;
-            mBand = other.mBand;
-            mChannel = other.mChannel;
+            mChannels = other.mChannels.clone();
             mMaxNumberOfClients = other.mMaxNumberOfClients;
             mSecurityType = other.mSecurityType;
             mAutoShutdownEnabled = other.mAutoShutdownEnabled;
@@ -624,6 +912,11 @@
             mClientControlByUser = other.mClientControlByUser;
             mBlockedClientList = new ArrayList<>(other.mBlockedClientList);
             mAllowedClientList = new ArrayList<>(other.mAllowedClientList);
+            mMacRandomizationSetting = other.mMacRandomizationSetting;
+            mBridgedModeOpportunisticShutdownEnabled =
+                    other.mBridgedModeOpportunisticShutdownEnabled;
+            mIeee80211axEnabled = other.mIeee80211axEnabled;
+            mIsUserConfiguration = other.mIsUserConfiguration;
         }
 
         /**
@@ -639,9 +932,11 @@
                 }
             }
             return new SoftApConfiguration(mSsid, mBssid, mPassphrase,
-                    mHiddenSsid, mBand, mChannel, mSecurityType, mMaxNumberOfClients,
+                    mHiddenSsid, mChannels, mSecurityType, mMaxNumberOfClients,
                     mAutoShutdownEnabled, mShutdownTimeoutMillis, mClientControlByUser,
-                    mBlockedClientList, mAllowedClientList);
+                    mBlockedClientList, mAllowedClientList, mMacRandomizationSetting,
+                    mBridgedModeOpportunisticShutdownEnabled, mIeee80211axEnabled,
+                    mIsUserConfiguration);
         }
 
         /**
@@ -670,17 +965,41 @@
          * Specifies a BSSID for the AP.
          * <p>
          * <li>If not set, defaults to null.</li>
+         *
+         * If multiple bands are requested via {@link #setBands(int[])} or
+         * {@link #setChannels(SparseIntArray)}, HAL will derive 2 MAC addresses since framework
+         * only sends down 1 MAC address.
+         *
+         * An example (but different implementation may perform a different mapping):
+         * <li>MAC address 1: copy value of MAC address,
+         * and set byte 1 = (0xFF - BSSID[1])</li>
+         * <li>MAC address 2: copy value of MAC address,
+         * and set byte 2 = (0xFF - BSSID[2])</li>
+         *
+         * Example BSSID argument: e2:38:60:c4:0e:b7
+         * Derived MAC address 1: e2:c7:60:c4:0e:b7
+         * Derived MAC address 2: e2:38:9f:c4:0e:b7
+         *
+         * <p>
+         * Use {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)} and
+         * {@link SoftApCapability#areFeaturesSupported(long)}
+         * with {@link SoftApCapability.SOFTAP_FEATURE_MAC_ADDRESS_CUSTOMIZATION} to determine
+         * whether or not this feature is supported.
+         *
          * @param bssid BSSID, or null to have the BSSID chosen by the framework. The caller is
          *              responsible for avoiding collisions.
          * @return Builder for chaining.
-         * @throws IllegalArgumentException when the given BSSID is the all-zero or broadcast MAC
-         *                                  address.
+         * @throws IllegalArgumentException when the given BSSID is the all-zero
+         *                                  , multicast or broadcast MAC address.
          */
         @NonNull
         public Builder setBssid(@Nullable MacAddress bssid) {
             if (bssid != null) {
                 Preconditions.checkArgument(!bssid.equals(WifiManager.ALL_ZEROS_MAC_ADDRESS));
-                Preconditions.checkArgument(!bssid.equals(MacAddress.BROADCAST_ADDRESS));
+                if (bssid.getAddressType() != MacAddress.TYPE_UNICAST) {
+                    throw new IllegalArgumentException("bssid doesn't support "
+                            + "multicast or broadcast mac address");
+                }
             }
             mBssid = bssid;
             return this;
@@ -712,10 +1031,6 @@
                 }
             } else {
                 Preconditions.checkStringNotEmpty(passphrase);
-                final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder();
-                if (!asciiEncoder.canEncode(passphrase)) {
-                    throw new IllegalArgumentException("passphrase not ASCII encodable");
-                }
                 if (securityType == SECURITY_TYPE_WPA2_PSK
                         || securityType == SECURITY_TYPE_WPA3_SAE_TRANSITION) {
                     if (passphrase.length() < PSK_MIN_LEN || passphrase.length() > PSK_MAX_LEN) {
@@ -754,53 +1069,177 @@
          * @param band One or combination of the following band type:
          * {@link #BAND_2GHZ}, {@link #BAND_5GHZ}, {@link #BAND_6GHZ}.
          * @return Builder for chaining.
+         * @throws IllegalArgumentException when an invalid band type is provided.
          */
         @NonNull
         public Builder setBand(@BandType int band) {
             if (!isBandValid(band)) {
-                throw new IllegalArgumentException("Invalid band type");
+                throw new IllegalArgumentException("Invalid band type: " + band);
             }
-            mBand = band;
-            // Since band preference is specified, no specific channel is selected.
-            mChannel = 0;
+            mChannels = new SparseIntArray(1);
+            mChannels.put(band, 0);
             return this;
         }
 
         /**
+         * Specifies the bands for the APs.
+         * If more than 1 band is set, this will bring up concurrent APs.
+         * on the requested bands (if possible).
+         * <p>
+         *
+         * Use {@link WifiManager#isBridgedApConcurrencySupported()} to determine
+         * whether or not concurrent APs are supported.
+         *
+         * Requires the driver to support {@link SoftApCapability.SOFTAP_FEATURE_ACS_OFFLOAD}
+         * when multiple bands are configured. Otherwise,
+         * {@link WifiManager#startTetheredHotspot(SoftApConfiguration)} will report error code
+         * {@link WifiManager#SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}.
+         *
+         * Note: Only supports 2.4GHz + 5GHz bands. If any other band is set, will report error
+         * {@link WifiManager#SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}.
+         *
+         * @param bands Array of the {@link #BandType}.
+         * @return Builder for chaining.
+         * @throws IllegalArgumentException when more than 2 bands are set or an invalid band type
+         *                                  is provided.
+         */
+        @RequiresApi(Build.VERSION_CODES.S)
+        @NonNull
+        public Builder setBands(@NonNull int[] bands) {
+            if (!SdkLevel.isAtLeastS()) {
+                throw new UnsupportedOperationException();
+            }
+            if (bands.length == 0 || bands.length > 2) {
+                throw new IllegalArgumentException("Unsupported number of bands("
+                        + bands.length + ") configured");
+            }
+            SparseIntArray channels = new SparseIntArray(bands.length);
+            for (int val : bands) {
+                if (!isBandValid(val)) {
+                    throw new IllegalArgumentException("Invalid band type: " + val);
+                }
+                channels.put(val, 0);
+            }
+            mChannels = channels;
+            return this;
+        }
+
+
+        /**
          * Specifies the channel and associated band for the AP.
          *
          * The channel which AP resides on. Valid channels are country dependent.
+         * The {@link SoftApCapability#getSupportedChannelList(int)} can be used to obtain
+         * valid channels.
+         *
          * <p>
-         * The default for the channel is a the special value 0 to have the framework
-         * auto-select a valid channel from the band configured with
+         * If not set, the default for the channel is the special value 0 which has the
+         * framework auto-select a valid channel from the band configured with
          * {@link #setBand(int)}.
          *
-         * The channel auto selection will offload to driver when
-         * {@link SoftApCapability#areFeaturesSupported(
-         * SoftApCapability.SOFTAP_FEATURE_ACS_OFFLOAD)}
-         * return true. Driver will auto select best channel which based on environment
-         * interference to get best performance. Check {@link SoftApCapability} to get more detail.
+         * The channel auto selection will be offloaded to driver when
+         * {@link SoftApCapability#areFeaturesSupported(long)}
+         * with {@link SoftApCapability.SOFTAP_FEATURE_ACS_OFFLOAD}
+         * return true. The driver will auto select the best channel (e.g. best performance)
+         * based on environment interference. Check {@link SoftApCapability} for more detail.
          *
-         * Note, since 6GHz band use the same channel numbering of 2.4GHz and 5GHZ bands,
-         * the caller needs to pass the band containing the selected channel.
+         * The API contains (band, channel) input since the 6GHz band uses the same channel
+         * numbering scheme as is used in the 2.4GHz and 5GHz band. Therefore, both are needed to
+         * uniquely identify individual channels.
          *
          * <p>
-         * <li>If not set, defaults to 0.</li>
          * @param channel operating channel of the AP.
          * @param band containing this channel.
          * @return Builder for chaining.
+         * @throws IllegalArgumentException when the invalid channel or band type is configured.
          */
         @NonNull
         public Builder setChannel(int channel, @BandType int band) {
             if (!isChannelBandPairValid(channel, band)) {
-                throw new IllegalArgumentException("Invalid band type");
+                throw new IllegalArgumentException("Invalid channel(" + channel
+                        + ") & band (" + band + ") configured");
             }
-            mBand = band;
-            mChannel = channel;
+            mChannels = new SparseIntArray(1);
+            mChannels.put(band, channel);
             return this;
         }
 
         /**
+         * Specifies the channels and associated bands for the APs.
+         *
+         * When more than 1 channel is set, this will bring up concurrent APs on the requested
+         * channels and bands (if possible).
+         *
+         * Valid channels are country dependent.
+         * The {@link SoftApCapability#getSupportedChannelList(int)} can be used to obtain
+         * valid channels in each band.
+         *
+         * Use {@link WifiManager#isBridgedApConcurrencySupported()} to determine
+         * whether or not concurrent APs are supported.
+         *
+         * <p>
+         * If not set, the default for the channel is the special value 0 which has the framework
+         * auto-select a valid channel from the band configured with {@link #setBands(int[])}.
+         *
+         * The channel auto selection will be offloaded to driver when
+         * {@link SoftApCapability#areFeaturesSupported(long)}
+         * with {@link SoftApCapability.SOFTAP_FEATURE_ACS_OFFLOAD}
+         * returns true. The driver will auto select the best channel (e.g. best performance)
+         * based on environment interference. Check {@link SoftApCapability} for more detail.
+         *
+         * Requires the driver to support {@link SoftApCapability.SOFTAP_FEATURE_ACS_OFFLOAD}
+         * when multiple bands are configured without specified channel value (i.e. channel is
+         * the special value 0). Otherwise,
+         * {@link WifiManager#startTetheredHotspot(SoftApConfiguration)} will report error code
+         * {@link WifiManager#SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}.
+         *
+         * Note: Only supports 2.4GHz + 5GHz bands. If any other band is set, will report error
+         * {@link WifiManager#SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}.
+         *
+         * The API contains (band, channel) input since the 6GHz band uses the same channel
+         * numbering scheme as is used in the 2.4GHz and 5GHz band. Therefore, both are needed to
+         * uniquely identify individual channels.
+         *
+         * Reference the Wi-Fi channel numbering and the channelization in IEEE 802.11-2016
+         * specifications, section 17.3.8.4.2, 17.3.8.4.3 and Table 15-6.
+         *
+         * <p>
+         * @param channels SparseIntArray (key: {@code #BandType} , value: channel) consists of
+         *                 {@code BAND_} and corresponding channel.
+         * @return Builder for chaining.
+         * @throws IllegalArgumentException when more than 2 channels are set or the invalid
+         *                                  channel or band type is configured.
+         */
+        @RequiresApi(Build.VERSION_CODES.S)
+        @NonNull
+        public Builder setChannels(@NonNull SparseIntArray channels) {
+            if (!SdkLevel.isAtLeastS()) {
+                throw new UnsupportedOperationException();
+            }
+            if (channels.size() == 0 || channels.size() > 2) {
+                throw new IllegalArgumentException("Unsupported number of channels("
+                        + channels.size() + ") configured");
+            }
+            for (int i = 0; i < channels.size(); i++) {
+                int channel = channels.valueAt(i);
+                int band = channels.keyAt(i);
+                if (channel == 0) {
+                    if (!isBandValid(band)) {
+                        throw new IllegalArgumentException("Invalid band type: " + band);
+                    }
+                } else {
+                    if (!isChannelBandPairValid(channel, band)) {
+                        throw new IllegalArgumentException("Invalid channel(" + channel
+                                + ") & band (" + band + ") configured");
+                    }
+                }
+            }
+            mChannels = channels.clone();
+            return this;
+        }
+
+
+        /**
          * Specifies the maximum number of clients that can associate to the AP.
          *
          * The maximum number of clients (STAs) which can associate to the AP.
@@ -815,14 +1254,14 @@
          * <p>
          * <li>If not set, defaults to 0.</li>
          *
-         * This method requires hardware support. If the method is used to set a
+         * This method requires HAL support. If the method is used to set a
          * non-zero {@code maxNumberOfClients} value then
-         * {@link WifiManager#startTetheredHotspot} will report error code
+         * {@link WifiManager#startTetheredHotspot(SoftApConfiguration)} will report error code
          * {@link WifiManager#SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}.
          *
          * <p>
          * Use {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)} and
-         * {@link SoftApCapability#areFeaturesSupported(int)}
+         * {@link SoftApCapability#areFeaturesSupported(long)}
          * with {@link SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT} to determine whether
          * or not this feature is supported.
          *
@@ -896,13 +1335,13 @@
          * {@link #setBlockedClientList(List)} and {@link #setAllowedClientList(List)}.
          *
          * <p>
-         * This method requires hardware support. Hardware support can be determined using
+         * This method requires HAL support. HAL support can be determined using
          * {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)} and
-         * {@link SoftApCapability#areFeaturesSupported(int)}
+         * {@link SoftApCapability#areFeaturesSupported(long)}
          * with {@link SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT}
          *
          * <p>
-         * If the method is called on a device without hardware support then starting the soft AP
+         * If the method is called on a device without HAL support then starting the soft AP
          * using {@link WifiManager#startTetheredHotspot(SoftApConfiguration)} will fail with
          * {@link WifiManager#SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}.
          *
@@ -951,13 +1390,13 @@
          * to the Soft AP.
          *
          * <p>
-         * This method requires hardware support. Hardware support can be determined using
+         * This method requires HAL support. HAL support can be determined using
          * {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)} and
-         * {@link SoftApCapability#areFeaturesSupported(int)}
+         * {@link SoftApCapability#areFeaturesSupported(long)}
          * with {@link SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT}
          *
          * <p>
-         * If the method is called on a device without hardware support then starting the soft AP
+         * If the method is called on a device without HAL support then starting the soft AP
          * using {@link WifiManager#startTetheredHotspot(SoftApConfiguration)} will fail with
          * {@link WifiManager#SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}.
          *
@@ -969,5 +1408,121 @@
             mBlockedClientList = new ArrayList<>(blockedClientList);
             return this;
         }
+
+        /**
+         * Specifies the level of MAC randomization for the AP BSSID.
+         * The Soft AP BSSID will be randomized only if the BSSID isn't set
+         * {@link #setBssid(MacAddress)} and this method is either uncalled
+         * or called with {@link #RANDOMIZATION_PERSISTENT}.
+         *
+         * <p>
+         * <li>If not set, defaults to {@link #RANDOMIZATION_PERSISTENT}</li>
+         *
+         * <p>
+         * Requires HAL support when set to {@link #RANDOMIZATION_PERSISTENT}.
+         * Use {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)} and
+         * {@link SoftApCapability#areFeaturesSupported(long)}
+         * with {@link SoftApCapability.SOFTAP_FEATURE_MAC_ADDRESS_CUSTOMIZATION} to determine
+         * whether or not this feature is supported.
+         *
+         * @param macRandomizationSetting One of the following setting:
+         * {@link #RANDOMIZATION_NONE} or {@link #RANDOMIZATION_PERSISTENT}.
+         * @return Builder for chaining.
+         *
+         * @see #setBssid(MacAddress)
+         */
+        @RequiresApi(Build.VERSION_CODES.S)
+        @NonNull
+        public Builder setMacRandomizationSetting(
+                @MacRandomizationSetting int macRandomizationSetting) {
+            if (!SdkLevel.isAtLeastS()) {
+                throw new UnsupportedOperationException();
+            }
+            mMacRandomizationSetting = macRandomizationSetting;
+            return this;
+        }
+
+
+        /**
+         * Specifies whether or not opportunistic shut down of an AP instance in bridged mode
+         * is enabled.
+         *
+         * <p>
+         * If enabled, the framework will shutdown one of the AP instances if it is idle for
+         * the timeout duration - meaning there are no devices connected to it.
+         * If both AP instances are idle for the timeout duration then the framework will
+         * shut down the AP instance operating on the higher frequency. For instance,
+         * if the AP instances operate at 2.4GHz and 5GHz and are both idle for the
+         * timeout duration then the 5GHz AP instance will be shut down.
+         * <p>
+         *
+         * Note: the opportunistic timeout only applies to one AP instance of the bridge AP.
+         * If one of the AP instances has already been disabled for any reason, including due to
+         * an opportunistic timeout or hardware issues or coexistence issues,
+         * then the opportunistic timeout is no longer active.
+         *
+         * <p>
+         * The shutdown timer specified by {@link #setShutdownTimeoutMillis(long)} controls the
+         * overall shutdown of the bridged AP and is still in use independently of the opportunistic
+         * timer controlled by this AP.
+         *
+         * <p>
+         * <li>If not set, defaults to true</li>
+         *
+         * @param enable true to enable, false to disable.
+         * @return Builder for chaining.
+         *
+         */
+        @RequiresApi(Build.VERSION_CODES.S)
+        @NonNull
+        public Builder setBridgedModeOpportunisticShutdownEnabled(boolean enable) {
+            if (!SdkLevel.isAtLeastS()) {
+                throw new UnsupportedOperationException();
+            }
+            mBridgedModeOpportunisticShutdownEnabled = enable;
+            return this;
+        }
+
+        /**
+         * Specifies whether or not to enable 802.11ax on the Soft AP.
+         *
+         * <p>
+         * Note: Only relevant when the device supports 802.11ax on the Soft AP.
+         * If enabled on devices that do not support 802.11ax then ignored.
+         * Use {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)} and
+         * {@link SoftApCapability#areFeaturesSupported(long)}
+         * with {@link SoftApCapability.SOFTAP_FEATURE_IEEE80211_AX} to determine
+         * whether or not 802.11ax is supported on the Soft AP.
+         * <p>
+         * <li>If not set, defaults to true - which will be ignored on devices
+         * which do not support 802.11ax</li>
+         *
+         * @param enable true to enable, false to disable.
+         * @return Builder for chaining.
+         *
+         */
+        @RequiresApi(Build.VERSION_CODES.S)
+        @NonNull
+        public Builder setIeee80211axEnabled(boolean enable) {
+            if (!SdkLevel.isAtLeastS()) {
+                throw new UnsupportedOperationException();
+            }
+            mIeee80211axEnabled = enable;
+            return this;
+        }
+
+        /**
+         * Specifies whether or not the configuration is configured by user.
+         *
+         * @param isUserConfigured true to user configuration, false otherwise.
+         * @return Builder for chaining.
+         *
+         * @hide
+         */
+        @NonNull
+        public Builder setUserConfiguration(boolean isUserConfigured) {
+            mIsUserConfiguration = isUserConfigured;
+            return this;
+        }
     }
 }
diff --git a/framework/java/android/net/wifi/SoftApInfo.java b/framework/java/android/net/wifi/SoftApInfo.java
index 24ed8ef..b1b1fe0 100644
--- a/framework/java/android/net/wifi/SoftApInfo.java
+++ b/framework/java/android/net/wifi/SoftApInfo.java
@@ -19,9 +19,16 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.net.MacAddress;
+import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import androidx.annotation.RequiresApi;
+
+import com.android.internal.util.Preconditions;
+import com.android.modules.utils.build.SdkLevel;
+
 import java.util.Objects;
 
 /**
@@ -82,7 +89,33 @@
      */
     public static final int CHANNEL_WIDTH_160MHZ = 6;
 
+    /**
+     * AP Channel bandwidth is 2160 MHZ.
+     *
+     * @see #getBandwidth()
+     */
+    public static final int CHANNEL_WIDTH_2160MHZ = 7;
 
+    /**
+     * AP Channel bandwidth is 4320 MHZ.
+     *
+     * @see #getBandwidth()
+     */
+    public static final int CHANNEL_WIDTH_4320MHZ = 8;
+
+    /**
+     * AP Channel bandwidth is 6480 MHZ.
+     *
+     * @see #getBandwidth()
+     */
+    public static final int CHANNEL_WIDTH_6480MHZ = 9;
+
+    /**
+     * AP Channel bandwidth is 8640 MHZ.
+     *
+     * @see #getBandwidth()
+     */
+    public static final int CHANNEL_WIDTH_8640MHZ = 10;
 
     /** The frequency which AP resides on.  */
     private int mFrequency = 0;
@@ -90,6 +123,24 @@
     @WifiAnnotations.Bandwidth
     private int mBandwidth = CHANNEL_WIDTH_INVALID;
 
+    /** The MAC Address which AP resides on. */
+    @Nullable
+    private MacAddress mBssid;
+
+    /** The identifier of the AP instance which AP resides on with current info. */
+    @Nullable
+    private String mApInstanceIdentifier;
+
+    /**
+     * The operational mode of the AP.
+     */
+    private @WifiAnnotations.WifiStandard int mWifiStandard = ScanResult.WIFI_STANDARD_UNKNOWN;
+
+    /**
+     * The current shutdown timeout millis which applied on Soft AP.
+     */
+    private long mIdleShutdownTimeoutMillis;
+
     /**
      * Get the frequency which AP resides on.
      */
@@ -110,7 +161,9 @@
      *
      * @return One of {@link #CHANNEL_WIDTH_20MHZ}, {@link #CHANNEL_WIDTH_40MHZ},
      * {@link #CHANNEL_WIDTH_80MHZ}, {@link #CHANNEL_WIDTH_160MHZ},
-     * {@link #CHANNEL_WIDTH_80MHZ_PLUS_MHZ} or {@link #CHANNEL_WIDTH_INVALID}.
+     * {@link #CHANNEL_WIDTH_80MHZ_PLUS_MHZ}, {@link #CHANNEL_WIDTH_2160MHZ},
+     * {@link #CHANNEL_WIDTH_4320MHZ}, {@link #CHANNEL_WIDTH_6480MHZ},
+     * {@link #CHANNEL_WIDTH_8640MHZ}, or {@link #CHANNEL_WIDTH_INVALID}.
      */
     @WifiAnnotations.Bandwidth
     public int getBandwidth() {
@@ -126,12 +179,128 @@
     }
 
     /**
+     * Get the MAC address (BSSID) of the AP. Null when AP disabled.
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    @Nullable
+    public MacAddress getBssid() {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        return getBssidInternal();
+    }
+
+    /**
+     * @hide
+     */
+    @Nullable
+    public MacAddress getBssidInternal() {
+        return mBssid;
+    }
+
+    /**
+      * Set the MAC address which AP resides on.
+      * <p>
+      * <li>If not set, defaults to null.</li>
+      * @param bssid BSSID, The caller is responsible for avoiding collisions.
+      * @throws IllegalArgumentException when the given BSSID is the all-zero or broadcast MAC
+      *                                  address.
+      *
+      * @hide
+      */
+    public void setBssid(@Nullable MacAddress bssid) {
+        if (bssid != null) {
+            Preconditions.checkArgument(!bssid.equals(WifiManager.ALL_ZEROS_MAC_ADDRESS));
+            Preconditions.checkArgument(!bssid.equals(MacAddress.BROADCAST_ADDRESS));
+        }
+        mBssid = bssid;
+    }
+
+    /**
+     * Set the operational mode of the AP.
+     *
+     * @param wifiStandard values from {@link ScanResult}'s {@code WIFI_STANDARD_}
+     * @hide
+     */
+    public void setWifiStandard(@WifiAnnotations.WifiStandard int wifiStandard) {
+        mWifiStandard = wifiStandard;
+    }
+
+    /**
+     * Get the operational mode of the AP.
+     * @return valid values from {@link ScanResult}'s {@code WIFI_STANDARD_}
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    public @WifiAnnotations.WifiStandard int getWifiStandard() {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        return getWifiStandardInternal();
+    }
+
+    /**
+     * @hide
+     */
+    public @WifiAnnotations.WifiStandard int getWifiStandardInternal() {
+        return mWifiStandard;
+    }
+
+    /**
+     * Set the AP instance identifier.
+     * @hide
+     */
+    public void setApInstanceIdentifier(@NonNull String apInstanceIdentifier) {
+        mApInstanceIdentifier = apInstanceIdentifier;
+    }
+
+    /**
+     * Get the AP instance identifier.
+     *
+     * The AP instance identifier is a unique identity which can be used to
+     * associate the {@link SoftApInfo} to a specific {@link WifiClient}
+     * - see {@link WifiClient#getApInstanceIdentifier()}
+     *
+     * @hide
+     */
+    @Nullable
+    public String getApInstanceIdentifier() {
+        return mApInstanceIdentifier;
+    }
+
+
+    /**
+     * Set current shutdown timeout millis which applied on Soft AP.
+     * @hide
+     */
+    public void setAutoShutdownTimeoutMillis(long idleShutdownTimeoutMillis) {
+        mIdleShutdownTimeoutMillis = idleShutdownTimeoutMillis;
+    }
+
+    /**
+     * Get auto shutdown timeout in millis.
+     *
+     * The shutdown timeout value is configured by
+     * {@link SoftApConfiguration.Builder#setAutoShutdownEnabled(int)} or
+     * the default timeout setting defined in device overlays.
+     *
+     * A value of 0 means that auto shutdown is disabled.
+     * {@see SoftApConfiguration#isAutoShutdownEnabled()}
+     */
+    public long getAutoShutdownTimeoutMillis() {
+        return mIdleShutdownTimeoutMillis;
+    }
+
+    /**
      * @hide
      */
     public SoftApInfo(@Nullable SoftApInfo source) {
         if (source != null) {
             mFrequency = source.mFrequency;
             mBandwidth = source.mBandwidth;
+            mBssid = source.mBssid;
+            mWifiStandard = source.mWifiStandard;
+            mApInstanceIdentifier = source.mApInstanceIdentifier;
+            mIdleShutdownTimeoutMillis = source.mIdleShutdownTimeoutMillis;
         }
     }
 
@@ -152,6 +321,10 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(mFrequency);
         dest.writeInt(mBandwidth);
+        dest.writeParcelable(mBssid, flags);
+        dest.writeInt(mWifiStandard);
+        dest.writeString(mApInstanceIdentifier);
+        dest.writeLong(mIdleShutdownTimeoutMillis);
     }
 
     @NonNull
@@ -161,6 +334,10 @@
             SoftApInfo info = new SoftApInfo();
             info.mFrequency = in.readInt();
             info.mBandwidth = in.readInt();
+            info.mBssid = in.readParcelable(MacAddress.class.getClassLoader());
+            info.mWifiStandard = in.readInt();
+            info.mApInstanceIdentifier = in.readString();
+            info.mIdleShutdownTimeoutMillis = in.readLong();
             return info;
         }
 
@@ -172,10 +349,16 @@
     @NonNull
     @Override
     public String toString() {
-        return "SoftApInfo{"
-                + "bandwidth= " + mBandwidth
-                + ",frequency= " + mFrequency
-                + '}';
+        StringBuilder sbuf = new StringBuilder();
+        sbuf.append("SoftApInfo{");
+        sbuf.append("bandwidth= ").append(mBandwidth);
+        sbuf.append(", frequency= ").append(mFrequency);
+        if (mBssid != null) sbuf.append(",bssid=").append(mBssid.toString());
+        sbuf.append(", wifiStandard= ").append(mWifiStandard);
+        sbuf.append(", mApInstanceIdentifier= ").append(mApInstanceIdentifier);
+        sbuf.append(", mIdleShutdownTimeoutMillis= ").append(mIdleShutdownTimeoutMillis);
+        sbuf.append("}");
+        return sbuf.toString();
     }
 
     @Override
@@ -184,11 +367,16 @@
         if (!(o instanceof SoftApInfo)) return false;
         SoftApInfo softApInfo = (SoftApInfo) o;
         return mFrequency == softApInfo.mFrequency
-                && mBandwidth == softApInfo.mBandwidth;
+                && mBandwidth == softApInfo.mBandwidth
+                && Objects.equals(mBssid, softApInfo.mBssid)
+                && mWifiStandard == softApInfo.mWifiStandard
+                && Objects.equals(mApInstanceIdentifier, softApInfo.mApInstanceIdentifier)
+                && mIdleShutdownTimeoutMillis == softApInfo.mIdleShutdownTimeoutMillis;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mFrequency, mBandwidth);
+        return Objects.hash(mFrequency, mBandwidth, mBssid, mWifiStandard, mApInstanceIdentifier,
+                mIdleShutdownTimeoutMillis);
     }
 }
diff --git a/framework/java/android/net/wifi/WifiAnnotations.java b/framework/java/android/net/wifi/WifiAnnotations.java
index acda7e0..580a638 100644
--- a/framework/java/android/net/wifi/WifiAnnotations.java
+++ b/framework/java/android/net/wifi/WifiAnnotations.java
@@ -57,6 +57,10 @@
             SoftApInfo.CHANNEL_WIDTH_80MHZ,
             SoftApInfo.CHANNEL_WIDTH_80MHZ_PLUS_MHZ,
             SoftApInfo.CHANNEL_WIDTH_160MHZ,
+            SoftApInfo.CHANNEL_WIDTH_2160MHZ,
+            SoftApInfo.CHANNEL_WIDTH_4320MHZ,
+            SoftApInfo.CHANNEL_WIDTH_6480MHZ,
+            SoftApInfo.CHANNEL_WIDTH_8640MHZ,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Bandwidth {}
@@ -77,6 +81,7 @@
             ScanResult.WIFI_STANDARD_11N,
             ScanResult.WIFI_STANDARD_11AC,
             ScanResult.WIFI_STANDARD_11AX,
+            ScanResult.WIFI_STANDARD_11AD,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface WifiStandard{}
@@ -117,7 +122,11 @@
         ScanResult.CIPHER_TKIP,
         ScanResult.CIPHER_CCMP,
         ScanResult.CIPHER_GCMP_256,
-        ScanResult.CIPHER_SMS4
+        ScanResult.CIPHER_SMS4,
+        ScanResult.CIPHER_GCMP_128,
+        ScanResult.CIPHER_BIP_GMAC_128,
+        ScanResult.CIPHER_BIP_GMAC_256,
+        ScanResult.CIPHER_BIP_CMAC_256,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Cipher {}
diff --git a/framework/java/android/net/wifi/WifiAvailableChannel.java b/framework/java/android/net/wifi/WifiAvailableChannel.java
new file mode 100644
index 0000000..06de4ab
--- /dev/null
+++ b/framework/java/android/net/wifi/WifiAvailableChannel.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.wifi;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Contains information about a Wifi channel and bitmask of Wifi operational modes allowed on that
+ * channel. Use {@link WifiManager#getAllowedChannels(int, int)} to retrieve the list of channels
+ * filtered by regulatory constraints. Use {@link WifiManager#getUsableChannels(int, int)} to
+ * retrieve the list of channels filtered by regulatory and dynamic constraints like concurrency and
+ * interference due to other radios.
+ *
+ * @hide
+ */
+@SystemApi
+public final class WifiAvailableChannel implements Parcelable {
+
+    /**
+     * Wifi Infrastructure client (STA) operational mode.
+     */
+    public static final int OP_MODE_STA = 1 << 0;
+
+    /**
+     * Wifi SoftAp (Mobile Hotspot) operational mode.
+     */
+    public static final int OP_MODE_SAP = 1 << 1;
+
+    /**
+     * Wifi Direct client (CLI) operational mode.
+     */
+    public static final int OP_MODE_WIFI_DIRECT_CLI = 1 << 2;
+
+    /**
+     * Wifi Direct Group Owner (GO) operational mode.
+     */
+    public static final int OP_MODE_WIFI_DIRECT_GO = 1 << 3;
+
+    /**
+     * Wifi Aware (NAN) operational mode.
+     */
+    public static final int OP_MODE_WIFI_AWARE = 1 << 4;
+
+    /**
+     * Wifi Tunneled Direct Link Setup (TDLS) operational mode.
+     */
+    public static final int OP_MODE_TDLS = 1 << 5;
+
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = {"OP_MODE_"}, value = {
+            OP_MODE_STA,
+            OP_MODE_SAP,
+            OP_MODE_WIFI_DIRECT_CLI,
+            OP_MODE_WIFI_DIRECT_GO,
+            OP_MODE_WIFI_AWARE,
+            OP_MODE_TDLS,
+    })
+    public @interface OpMode {}
+
+    /**
+     * Filter channel based on regulatory constraints.
+     * @hide
+     */
+    public static final int FILTER_REGULATORY = 0;
+
+    /**
+     * Filter channel based on interference from cellular radio.
+     * @hide
+     */
+    public static final int FILTER_CELLULAR_COEXISTENCE = 1 << 0;
+
+    /**
+     * Filter channel based on current concurrency state.
+     * @hide
+     */
+    public static final int FILTER_CONCURRENCY = 1 << 1;
+
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = {"FILTER_"}, value = {
+            FILTER_REGULATORY,
+            FILTER_CELLULAR_COEXISTENCE,
+            FILTER_CONCURRENCY,
+    })
+    public @interface Filter {}
+
+    /**
+     * Wifi channel frequency in MHz.
+     */
+    private int mFrequency;
+
+    /**
+     * Bitwise OR of modes (OP_MODE_*) allowed on this channel.
+     */
+    private @OpMode int mOpModes;
+
+    public WifiAvailableChannel(int freq, @OpMode int opModes) {
+        mFrequency = freq;
+        mOpModes = opModes;
+    }
+
+    private WifiAvailableChannel(@NonNull Parcel in) {
+        readFromParcel(in);
+    }
+
+    private void readFromParcel(@NonNull Parcel in) {
+        mFrequency = in.readInt();
+        mOpModes = in.readInt();
+    }
+
+    /**
+     * Get the channel frequency in MHz.
+     */
+    public int getFrequencyMhz() {
+        return mFrequency;
+    }
+
+    /**
+     * Get the operational modes allowed on a channel.
+     */
+    public @OpMode int getOperationalModes() {
+        return mOpModes;
+    }
+
+    /**
+     * Usable filter implies filter channels by regulatory constraints and
+     * other dynamic constraints like concurrency state and interference due
+     * to other radios like cellular.
+     * @hide
+     */
+    public static @Filter int getUsableFilter() {
+        return FILTER_REGULATORY
+                | FILTER_CONCURRENCY
+                | FILTER_CELLULAR_COEXISTENCE;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        WifiAvailableChannel that = (WifiAvailableChannel) o;
+        return mFrequency == that.mFrequency
+                && mOpModes == that.mOpModes;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mFrequency, mOpModes);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sbuf = new StringBuilder();
+        sbuf.append("mFrequency = ")
+            .append(mFrequency)
+            .append(", mOpModes = ")
+            .append(String.format("%x", mOpModes));
+        return sbuf.toString();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mFrequency);
+        dest.writeInt(mOpModes);
+    }
+
+    public static final @android.annotation.NonNull Creator<WifiAvailableChannel> CREATOR =
+            new Creator<WifiAvailableChannel>() {
+                @Override
+                public WifiAvailableChannel createFromParcel(@NonNull Parcel in) {
+                    return new WifiAvailableChannel(in);
+                }
+
+                @Override
+                public WifiAvailableChannel[] newArray(int size) {
+                    return new WifiAvailableChannel[size];
+                }
+            };
+}
diff --git a/framework/java/android/net/wifi/WifiClient.java b/framework/java/android/net/wifi/WifiClient.java
index 3794566..85e2b33 100644
--- a/framework/java/android/net/wifi/WifiClient.java
+++ b/framework/java/android/net/wifi/WifiClient.java
@@ -30,6 +30,9 @@
 
     private final MacAddress mMacAddress;
 
+    /** The identifier of the AP instance which the client connected. */
+    private final String mApInstanceIdentifier;
+
     /**
      * The mac address of this client.
      */
@@ -38,15 +41,30 @@
         return mMacAddress;
     }
 
+    /**
+     * Get AP instance identifier.
+     *
+     * The AP instance identifier is a unique identity which can be used to
+     * associate the {@link SoftApInfo} to a specific {@link WifiClient}
+     * - see {@link SoftApInfo#getApInstanceIdentifier()}
+     * @hide
+     */
+    @NonNull
+    public String getApInstanceIdentifier() {
+        return mApInstanceIdentifier;
+    }
+
     private WifiClient(Parcel in) {
         mMacAddress = in.readParcelable(null);
+        mApInstanceIdentifier = in.readString();
     }
 
     /** @hide */
-    public WifiClient(@NonNull MacAddress macAddress) {
+    public WifiClient(@NonNull MacAddress macAddress, @NonNull String apInstanceIdentifier) {
         Objects.requireNonNull(macAddress, "mMacAddress must not be null.");
 
         this.mMacAddress = macAddress;
+        this.mApInstanceIdentifier = apInstanceIdentifier;
     }
 
     @Override
@@ -57,6 +75,7 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeParcelable(mMacAddress, flags);
+        dest.writeString(mApInstanceIdentifier);
     }
 
     @NonNull
@@ -75,6 +94,7 @@
     public String toString() {
         return "WifiClient{"
                 + "mMacAddress=" + mMacAddress
+                + "mApInstanceIdentifier=" + mApInstanceIdentifier
                 + '}';
     }
 
@@ -83,13 +103,12 @@
         if (this == o) return true;
         if (!(o instanceof WifiClient)) return false;
         WifiClient client = (WifiClient) o;
-        return mMacAddress.equals(client.mMacAddress);
+        return Objects.equals(mMacAddress, client.mMacAddress)
+                && mApInstanceIdentifier.equals(client.mApInstanceIdentifier);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mMacAddress);
+        return Objects.hash(mMacAddress, mApInstanceIdentifier);
     }
 }
-
-
diff --git a/framework/java/android/net/wifi/WifiConfiguration.java b/framework/java/android/net/wifi/WifiConfiguration.java
index 93c6358..924cd33 100644
--- a/framework/java/android/net/wifi/WifiConfiguration.java
+++ b/framework/java/android/net/wifi/WifiConfiguration.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.PackageManager;
@@ -34,28 +35,36 @@
 import android.os.Parcelable;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.MacAddressUtils;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.BitSet;
 import java.util.Calendar;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * A class representing a configured Wi-Fi network, including the
  * security configuration.
  *
  * @deprecated Use {@link WifiNetworkSpecifier.Builder} to create {@link NetworkSpecifier} and
- * {@link WifiNetworkSuggestion.Builder} to create {@link WifiNetworkSuggestion}. This will become a
- * system use only object in the future.
+ * {@link WifiNetworkSuggestion.Builder} to create {@link WifiNetworkSuggestion}. This class can
+ * still be used with privileged APIs such as
+ * {@link WifiManager#addNetwork(WifiConfiguration)}.
  */
 @Deprecated
 public class WifiConfiguration implements Parcelable {
@@ -248,6 +257,11 @@
          */
         public static final int WAPI = 3;
 
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(value = {WPA, RSN, OSEN, WAPI})
+        public @interface ProtocolScheme {};
+
         public static final String varName = "proto";
 
         public static final String[] strings = { "WPA", "RSN", "OSEN", "WAPI" };
@@ -272,6 +286,11 @@
         /** SAE (Used only for WPA3-Personal) */
         public static final int SAE = 3;
 
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(value = {OPEN, SHARED, LEAP, SAE})
+        public @interface AuthAlgorithmScheme {};
+
         public static final String varName = "auth_alg";
 
         public static final String[] strings = { "OPEN", "SHARED", "LEAP", "SAE" };
@@ -301,9 +320,20 @@
          */
         public static final int SMS4 = 4;
 
+        /**
+         * AES in Galois/Counter Mode with a 128-bit integrity key
+         */
+        public static final int GCMP_128 = 5;
+
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(value = {NONE, TKIP, CCMP, GCMP_256, SMS4, GCMP_128})
+        public @interface PairwiseCipherScheme {};
+
         public static final String varName = "pairwise";
 
-        public static final String[] strings = { "NONE", "TKIP", "CCMP", "GCMP_256", "SMS4" };
+        public static final String[] strings = { "NONE", "TKIP", "CCMP", "GCMP_256", "SMS4",
+                "GCMP_128" };
     }
 
     /**
@@ -345,13 +375,22 @@
          * SMS4 cipher for WAPI
          */
         public static final int SMS4 = 6;
+        /**
+         * AES in Galois/Counter Mode with a 128-bit integrity key
+         */
+        public static final int GCMP_128 = 7;
+
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(value = {WEP40, WEP104, TKIP, CCMP, GTK_NOT_USED, GCMP_256, SMS4, GCMP_128})
+        public @interface GroupCipherScheme {};
 
         public static final String varName = "group";
 
         public static final String[] strings =
                 { /* deprecated */ "WEP40", /* deprecated */ "WEP104",
                         "TKIP", "CCMP", "GTK_NOT_USED", "GCMP_256",
-                        "SMS4" };
+                        "SMS4", "GCMP_128" };
     }
 
     /**
@@ -374,9 +413,16 @@
         /** GMAC-256 = Galois Message Authentication Code */
         public static final int BIP_GMAC_256 = 2;
 
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(value = {BIP_CMAC_256, BIP_GMAC_128, BIP_GMAC_256})
+        public @interface GroupMgmtCipherScheme {};
+
         private static final String varName = "groupMgmt";
 
-        private static final String[] strings = { "BIP_CMAC_256",
+        /** @hide */
+        @SuppressLint("AllUpper")
+        public static final @NonNull String[] strings = { "BIP_CMAC_256",
                 "BIP_GMAC_128", "BIP_GMAC_256"};
     }
 
@@ -397,9 +443,16 @@
         /** Diffie-Hellman with_RSA signature */
         public static final int ECDHE_RSA = 1;
 
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(value = {ECDHE_ECDSA, ECDHE_RSA})
+        public @interface SuiteBCipherScheme {};
+
         private static final String varName = "SuiteB";
 
-        private static final String[] strings = { "ECDHE_ECDSA", "ECDHE_RSA" };
+        /** @hide */
+        @SuppressLint("AllUpper")
+        public static final String[] strings = { "ECDHE_ECDSA", "ECDHE_RSA" };
     }
 
     /** Possible status of a network configuration. */
@@ -426,14 +479,52 @@
     public static final int SECURITY_TYPE_EAP = 3;
     /** Security type for an SAE network. */
     public static final int SECURITY_TYPE_SAE = 4;
-    /** Security type for an EAP Suite B network. */
-    public static final int SECURITY_TYPE_EAP_SUITE_B = 5;
+    /**
+     * Security type for a WPA3-Enterprise in 192-bit security network.
+     * This is the same as {@link #SECURITY_TYPE_EAP_SUITE_B} and uses the same value.
+     */
+    public static final int SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT = 5;
+    /**
+     * Security type for a WPA3-Enterprise in 192-bit security network.
+     * @deprecated Use the {@link #SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT} constant
+     * (which is the same value).
+     */
+    @Deprecated
+    public static final int SECURITY_TYPE_EAP_SUITE_B =
+            SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT;
     /** Security type for an OWE network. */
     public static final int SECURITY_TYPE_OWE = 6;
     /** Security type for a WAPI PSK network. */
     public static final int SECURITY_TYPE_WAPI_PSK = 7;
     /** Security type for a WAPI Certificate network. */
     public static final int SECURITY_TYPE_WAPI_CERT = 8;
+    /** Security type for a WPA3-Enterprise network. */
+    public static final int SECURITY_TYPE_EAP_WPA3_ENTERPRISE = 9;
+    /**
+     * Security type for an OSEN network.
+     * @hide
+     */
+    public static final int SECURITY_TYPE_OSEN = 10;
+    /**
+     * Security type for a Passpoint R1/R2 network.
+     * Passpoint R1/R2 uses Enterprise security, where TKIP and WEP are not allowed.
+     * @hide
+     */
+    public static final int SECURITY_TYPE_PASSPOINT_R1_R2 = 11;
+
+    /**
+     * Security type for a Passpoint R3 network.
+     * Passpoint R3 uses Enterprise security, where TKIP and WEP are not allowed,
+     * and PMF must be set to Required.
+     * @hide
+     */
+    public static final int SECURITY_TYPE_PASSPOINT_R3 = 12;
+
+    /**
+     * This is used for the boundary check and should be the same as the last type.
+     * @hide
+     */
+    public static final int SECURITY_TYPE_NUM = SECURITY_TYPE_PASSPOINT_R3;
 
     /**
      * Security types we support.
@@ -449,13 +540,101 @@
             SECURITY_TYPE_EAP_SUITE_B,
             SECURITY_TYPE_OWE,
             SECURITY_TYPE_WAPI_PSK,
-            SECURITY_TYPE_WAPI_CERT
+            SECURITY_TYPE_WAPI_CERT,
+            SECURITY_TYPE_EAP_WPA3_ENTERPRISE,
+            SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT,
+            SECURITY_TYPE_PASSPOINT_R1_R2,
+            SECURITY_TYPE_PASSPOINT_R3,
     })
     public @interface SecurityType {}
 
+    private static final String[] SECURITY_TYPE_NAMES = {
+        "open", "wep", "wpa2-psk", "wpa2-enterprise",
+        "wpa3-sae", "wpa3 enterprise 192-bit", "owe",
+        "wapi-psk", "wapi-cert", "wpa3 enterprise",
+        "wpa3 enterprise 192-bit", "passpoint r1/r2",
+        "passpoint r3"};
+
+    private List<SecurityParams> mSecurityParamsList = new ArrayList<>();
+
+    private void updateLegacySecurityParams() {
+        if (mSecurityParamsList.isEmpty()) return;
+        mSecurityParamsList.get(0).updateLegacyWifiConfiguration(this);
+    }
+
     /**
      * Set the various security params to correspond to the provided security type.
      * This is accomplished by setting the various BitSets exposed in WifiConfiguration.
+     * <br>
+     * This API would clear existing security types and add a default one.
+     *
+     * @param securityType One of the following security types:
+     * {@link #SECURITY_TYPE_OPEN},
+     * {@link #SECURITY_TYPE_WEP},
+     * {@link #SECURITY_TYPE_PSK},
+     * {@link #SECURITY_TYPE_EAP},
+     * {@link #SECURITY_TYPE_SAE},
+     * {@link #SECURITY_TYPE_OWE},
+     * {@link #SECURITY_TYPE_WAPI_PSK},
+     * {@link #SECURITY_TYPE_WAPI_CERT},
+     * {@link #SECURITY_TYPE_EAP_WPA3_ENTERPRISE},
+     * {@link #SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT},
+     */
+    public void setSecurityParams(@SecurityType int securityType) {
+        // Clear existing data.
+        mSecurityParamsList.clear();
+        addSecurityParams(securityType);
+    }
+
+    /**
+     * Set security params by the given key management mask.
+     *
+     * @param givenAllowedKeyManagement the given allowed key management mask.
+     * @hide
+     */
+    public void setSecurityParams(@NonNull BitSet givenAllowedKeyManagement) {
+        if (givenAllowedKeyManagement == null) {
+            throw new IllegalArgumentException("Invalid allowed key management mask.");
+        }
+        // Clear existing data.
+        mSecurityParamsList.clear();
+
+        allowedKeyManagement = (BitSet) givenAllowedKeyManagement.clone();
+        convertLegacyFieldsToSecurityParamsIfNeeded();
+    }
+
+    /**
+     * Add the various security params.
+     * <br>
+     * This API would clear existing security types and add a default one.
+     * @hide
+     */
+    public void setSecurityParams(SecurityParams params) {
+        // Clear existing data.
+        mSecurityParamsList.clear();
+        addSecurityParams(params);
+    }
+
+    /**
+     * Set the security params by the given security params list.
+     *
+     * This will overwrite existing security params list directly.
+     *
+     * @param securityParamsList the desired security params list.
+     * @hide
+     */
+    public void setSecurityParams(@NonNull List<SecurityParams> securityParamsList) {
+        if (securityParamsList == null || securityParamsList.isEmpty()) {
+            throw new IllegalArgumentException("An empty security params list is invalid.");
+        }
+        mSecurityParamsList = securityParamsList.stream()
+                .map(p -> new SecurityParams(p)).collect(Collectors.toList());
+        updateLegacySecurityParams();
+    }
+
+    /**
+     * Add the various security params to correspond to the provided security type.
+     * This is accomplished by setting the various BitSets exposed in WifiConfiguration.
      *
      * @param securityType One of the following security types:
      * {@link #SECURITY_TYPE_OPEN},
@@ -463,82 +642,315 @@
      * {@link #SECURITY_TYPE_PSK},
      * {@link #SECURITY_TYPE_EAP},
      * {@link #SECURITY_TYPE_SAE},
-     * {@link #SECURITY_TYPE_EAP_SUITE_B},
      * {@link #SECURITY_TYPE_OWE},
-     * {@link #SECURITY_TYPE_WAPI_PSK}, or
-     * {@link #SECURITY_TYPE_WAPI_CERT}
+     * {@link #SECURITY_TYPE_WAPI_PSK},
+     * {@link #SECURITY_TYPE_WAPI_CERT},
+     * {@link #SECURITY_TYPE_EAP_WPA3_ENTERPRISE},
+     * {@link #SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT},
+     *
+     * @hide
      */
-    public void setSecurityParams(@SecurityType int securityType) {
-        // Clear all the bitsets.
-        allowedKeyManagement.clear();
-        allowedProtocols.clear();
-        allowedAuthAlgorithms.clear();
-        allowedPairwiseCiphers.clear();
-        allowedGroupCiphers.clear();
-        allowedGroupManagementCiphers.clear();
-        allowedSuiteBCiphers.clear();
-
-        switch (securityType) {
-            case SECURITY_TYPE_OPEN:
-                allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
-                break;
-            case SECURITY_TYPE_WEP:
-                allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
-                allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
-                allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED);
-                break;
-            case SECURITY_TYPE_PSK:
-                allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
-                break;
-            case SECURITY_TYPE_EAP:
-                allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
-                allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
-                break;
-            case SECURITY_TYPE_SAE:
-                allowedProtocols.set(WifiConfiguration.Protocol.RSN);
-                allowedKeyManagement.set(WifiConfiguration.KeyMgmt.SAE);
-                allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
-                allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.GCMP_256);
-                allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
-                allowedGroupCiphers.set(WifiConfiguration.GroupCipher.GCMP_256);
-                requirePmf = true;
-                break;
-            case SECURITY_TYPE_EAP_SUITE_B:
-                allowedProtocols.set(WifiConfiguration.Protocol.RSN);
-                allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
-                allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
-                allowedKeyManagement.set(WifiConfiguration.KeyMgmt.SUITE_B_192);
-                allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.GCMP_256);
-                allowedGroupCiphers.set(WifiConfiguration.GroupCipher.GCMP_256);
-                allowedGroupManagementCiphers.set(WifiConfiguration.GroupMgmtCipher.BIP_GMAC_256);
-                // Note: allowedSuiteBCiphers bitset will be set by the service once the
-                // certificates are attached to this profile
-                requirePmf = true;
-                break;
-            case SECURITY_TYPE_OWE:
-                allowedProtocols.set(WifiConfiguration.Protocol.RSN);
-                allowedKeyManagement.set(WifiConfiguration.KeyMgmt.OWE);
-                allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
-                allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.GCMP_256);
-                allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
-                allowedGroupCiphers.set(WifiConfiguration.GroupCipher.GCMP_256);
-                requirePmf = true;
-                break;
-            case SECURITY_TYPE_WAPI_PSK:
-                allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WAPI_PSK);
-                allowedProtocols.set(WifiConfiguration.Protocol.WAPI);
-                allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.SMS4);
-                allowedGroupCiphers.set(WifiConfiguration.GroupCipher.SMS4);
-                break;
-            case SECURITY_TYPE_WAPI_CERT:
-                allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WAPI_CERT);
-                allowedProtocols.set(WifiConfiguration.Protocol.WAPI);
-                allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.SMS4);
-                allowedGroupCiphers.set(WifiConfiguration.GroupCipher.SMS4);
-                break;
-            default:
-                throw new IllegalArgumentException("unknown security type " + securityType);
+    public void addSecurityParams(@SecurityType int securityType) {
+        // This ensures that there won't be duplicate security types.
+        if (mSecurityParamsList.stream().anyMatch(params -> params.isSecurityType(securityType))) {
+            throw new IllegalArgumentException("duplicate security type " + securityType);
         }
+        addSecurityParams(SecurityParams.createSecurityParamsBySecurityType(securityType));
+    }
+
+    /** @hide */
+    public void addSecurityParams(@NonNull SecurityParams newParams) {
+        if (mSecurityParamsList.stream().anyMatch(params -> params.isSameSecurityType(newParams))) {
+            throw new IllegalArgumentException("duplicate security params " + newParams);
+        }
+        if (!mSecurityParamsList.isEmpty()) {
+            if (newParams.isEnterpriseSecurityType() && !isEnterprise()) {
+                throw new IllegalArgumentException(
+                        "An enterprise security type cannot be added to a personal configuation.");
+            }
+            if (!newParams.isEnterpriseSecurityType() && isEnterprise()) {
+                throw new IllegalArgumentException(
+                        "A personal security type cannot be added to an enterprise configuation.");
+            }
+            if (newParams.isOpenSecurityType() && !isOpenNetwork()) {
+                throw new IllegalArgumentException(
+                        "An open security type cannot be added to a non-open configuation.");
+            }
+            if (!newParams.isOpenSecurityType() && isOpenNetwork()) {
+                throw new IllegalArgumentException(
+                        "A non-open security type cannot be added to an open configuation.");
+            }
+            if (newParams.isSecurityType(SECURITY_TYPE_OSEN)) {
+                throw new IllegalArgumentException(
+                        "An OSEN security type must be the only one type.");
+            }
+        }
+        mSecurityParamsList.add(new SecurityParams(newParams));
+        updateLegacySecurityParams();
+    }
+
+    /**
+     * If there is no security params, generate one according to legacy fields.
+     * @hide
+     */
+    public void convertLegacyFieldsToSecurityParamsIfNeeded() {
+        if (!mSecurityParamsList.isEmpty()) return;
+
+        if (allowedKeyManagement.get(KeyMgmt.WAPI_CERT)) {
+            setSecurityParams(SECURITY_TYPE_WAPI_CERT);
+        } else if (allowedKeyManagement.get(KeyMgmt.WAPI_PSK)) {
+            setSecurityParams(SECURITY_TYPE_WAPI_PSK);
+        } else if (allowedKeyManagement.get(KeyMgmt.SUITE_B_192)) {
+            setSecurityParams(SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT);
+        } else if (allowedKeyManagement.get(KeyMgmt.OWE)) {
+            setSecurityParams(SECURITY_TYPE_OWE);
+        } else if (allowedKeyManagement.get(KeyMgmt.SAE)) {
+            setSecurityParams(SECURITY_TYPE_SAE);
+        } else if (allowedKeyManagement.get(KeyMgmt.OSEN)) {
+            setSecurityParams(SECURITY_TYPE_OSEN);
+        } else if (allowedKeyManagement.get(KeyMgmt.WPA2_PSK)) {
+            setSecurityParams(SECURITY_TYPE_PSK);
+        } else if (allowedKeyManagement.get(KeyMgmt.WPA_EAP)) {
+            if (requirePmf) {
+                setSecurityParams(SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
+            } else {
+                setSecurityParams(SECURITY_TYPE_EAP);
+            }
+        } else if (allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
+            setSecurityParams(SECURITY_TYPE_PSK);
+        } else if (allowedKeyManagement.get(KeyMgmt.NONE)) {
+            if (hasWepKeys()) {
+                setSecurityParams(SECURITY_TYPE_WEP);
+            } else {
+                setSecurityParams(SECURITY_TYPE_OPEN);
+            }
+        } else {
+            setSecurityParams(SECURITY_TYPE_OPEN);
+        }
+    }
+
+    /**
+     * Disable the various security params to correspond to the provided security type.
+     * This is accomplished by setting the various BitSets exposed in WifiConfiguration.
+     *
+     * @param securityType One of the following security types:
+     * {@link #SECURITY_TYPE_OPEN},
+     * {@link #SECURITY_TYPE_WEP},
+     * {@link #SECURITY_TYPE_PSK},
+     * {@link #SECURITY_TYPE_EAP},
+     * {@link #SECURITY_TYPE_SAE},
+     * {@link #SECURITY_TYPE_OWE},
+     * {@link #SECURITY_TYPE_WAPI_PSK},
+     * {@link #SECURITY_TYPE_WAPI_CERT},
+     * {@link #SECURITY_TYPE_EAP_WPA3_ENTERPRISE},
+     * {@link #SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT},
+     *
+     * @hide
+     */
+    public void setSecurityParamsEnabled(@SecurityType int securityType, boolean enable) {
+        mSecurityParamsList.stream()
+                .filter(params -> params.isSecurityType(securityType))
+                .findAny()
+                .ifPresent(params -> params.setEnabled(enable));
+    }
+
+    /**
+     * Set whether a type is added by auto-upgrade.
+     *
+     * @param securityType One of the following security types:
+     * {@link #SECURITY_TYPE_OPEN},
+     * {@link #SECURITY_TYPE_WEP},
+     * {@link #SECURITY_TYPE_PSK},
+     * {@link #SECURITY_TYPE_EAP},
+     * {@link #SECURITY_TYPE_SAE},
+     * {@link #SECURITY_TYPE_OWE},
+     * {@link #SECURITY_TYPE_WAPI_PSK},
+     * {@link #SECURITY_TYPE_WAPI_CERT},
+     * {@link #SECURITY_TYPE_EAP_WPA3_ENTERPRISE},
+     * {@link #SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT},
+     *
+     * @hide
+     */
+    public void setSecurityParamsIsAddedByAutoUpgrade(
+            @SecurityType int securityType, boolean isAddedByAutoUpgrade) {
+        mSecurityParamsList.stream()
+                .filter(params -> params.isSecurityType(securityType))
+                .findAny()
+                .ifPresent(params -> params.setIsAddedByAutoUpgrade(isAddedByAutoUpgrade));
+    }
+
+    /**
+     * Get the specific security param.
+     *
+     * @param securityType One of the following security types:
+     * {@link #SECURITY_TYPE_OPEN},
+     * {@link #SECURITY_TYPE_WEP},
+     * {@link #SECURITY_TYPE_PSK},
+     * {@link #SECURITY_TYPE_EAP},
+     * {@link #SECURITY_TYPE_SAE},
+     * {@link #SECURITY_TYPE_OWE},
+     * {@link #SECURITY_TYPE_WAPI_PSK},
+     * {@link #SECURITY_TYPE_WAPI_CERT},
+     * {@link #SECURITY_TYPE_EAP_WPA3_ENTERPRISE},
+     * {@link #SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT},
+     *
+     * @return the copy of specific security params if found; otherwise null.
+     * @hide
+     */
+    public @Nullable SecurityParams getSecurityParams(@SecurityType int securityType) {
+        SecurityParams p = mSecurityParamsList.stream()
+                .filter(params -> params.isSecurityType(securityType))
+                .findAny()
+                .orElse(null);
+        return (p != null) ? new SecurityParams(p) : null;
+    }
+
+    /**
+     * Indicate whether this configuration is the specific security type.
+     *
+     * @param securityType One of the following security types:
+     * {@link #SECURITY_TYPE_OPEN},
+     * {@link #SECURITY_TYPE_WEP},
+     * {@link #SECURITY_TYPE_PSK},
+     * {@link #SECURITY_TYPE_EAP},
+     * {@link #SECURITY_TYPE_SAE},
+     * {@link #SECURITY_TYPE_OWE},
+     * {@link #SECURITY_TYPE_WAPI_PSK},
+     * {@link #SECURITY_TYPE_WAPI_CERT},
+     * {@link #SECURITY_TYPE_EAP_WPA3_ENTERPRISE},
+     * {@link #SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT},
+     *
+     * @return true if there is a security params matches the type.
+     * @hide
+     */
+    public boolean isSecurityType(@SecurityType int securityType) {
+        return mSecurityParamsList.stream()
+                .anyMatch(params -> params.isSecurityType(securityType));
+    }
+
+    /**
+     * Get the security params list of this configuration.
+     *
+     * The returning list is a priority list, the first is the lowest priority and default one.
+     *
+     * @return this list of security params.
+     * @hide
+     */
+    public List<SecurityParams> getSecurityParamsList() {
+        return Collections.unmodifiableList(mSecurityParamsList);
+    }
+
+    /**
+     * Get the default params which is the same as the legacy fields.
+     *
+     * @return the default security params.
+     * @hide
+     */
+    public @NonNull SecurityParams getDefaultSecurityParams() {
+        return new SecurityParams(mSecurityParamsList.get(0));
+    }
+
+    /**
+     * Enable the support of Fast Initial Link Set-up (FILS).
+     *
+     * FILS can be applied to all security types.
+     * @param enableFilsSha256 Enable FILS SHA256.
+     * @param enableFilsSha384 Enable FILS SHA256.
+     * @hide
+     */
+    public void enableFils(boolean enableFilsSha256, boolean enableFilsSha384) {
+        mSecurityParamsList.stream()
+                .forEach(params -> params.enableFils(enableFilsSha256, enableFilsSha384));
+        updateLegacySecurityParams();
+    }
+
+    /**
+     * Indicate FILS SHA256 is enabled.
+     *
+     * @return true if FILS SHA256 is enabled.
+     * @hide
+     */
+    public boolean isFilsSha256Enabled() {
+        return mSecurityParamsList.stream()
+                .anyMatch(params -> params.getAllowedKeyManagement().get(KeyMgmt.FILS_SHA256));
+    }
+
+    /**
+     * Indicate FILS SHA384 is enabled.
+     *
+     * @return true if FILS SHA384 is enabled.
+     * @hide
+     */
+    public boolean isFilsSha384Enabled() {
+        return mSecurityParamsList.stream()
+                .anyMatch(params -> params.getAllowedKeyManagement().get(KeyMgmt.FILS_SHA384));
+    }
+
+    /**
+     * Enable Suite-B ciphers.
+     *
+     * @param enableEcdheEcdsa enable Diffie-Hellman with Elliptic Curve ECDSA cipher support.
+     * @param enableEcdheRsa enable Diffie-Hellman with RSA cipher support.
+     * @hide
+     */
+    public void enableSuiteBCiphers(boolean enableEcdheEcdsa, boolean enableEcdheRsa) {
+        mSecurityParamsList.stream()
+                .filter(params -> params.isSecurityType(SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT))
+                .findAny()
+                .ifPresent(params -> params.enableSuiteBCiphers(enableEcdheEcdsa, enableEcdheRsa));
+        updateLegacySecurityParams();
+    }
+
+    /**
+     * Indicate ECDHE_ECDSA is enabled.
+     *
+     * @return true if enabled.
+     * @hide
+     */
+    public boolean isSuiteBCipherEcdheEcdsaEnabled() {
+        return mSecurityParamsList.stream()
+                .anyMatch(params -> params.getAllowedSuiteBCiphers().get(SuiteBCipher.ECDHE_ECDSA));
+    }
+
+    /**
+     * Indicate ECDHE_RSA is enabled.
+     *
+     * @return true if enabled.
+     * @hide
+     */
+    public boolean isSuiteBCipherEcdheRsaEnabled() {
+        return mSecurityParamsList.stream()
+                .anyMatch(params -> params.getAllowedSuiteBCiphers().get(SuiteBCipher.ECDHE_RSA));
+    }
+
+    /**
+     * Set SAE Hash-toElement only mode enabled.
+     * Before calling this API, call {@link WifiManager#isWpa3SaeH2eSupported()
+     * to know whether WPA3 SAE Hash-toElement is supported or not.
+     *
+     * @param enable true if enabled; false otherwise.
+     * @hide
+     */
+    public void enableSaeH2eOnlyMode(boolean enable) {
+        mSecurityParamsList.stream()
+                .filter(params -> params.isSecurityType(SECURITY_TYPE_SAE))
+                .findAny()
+                .ifPresent(params -> params.enableSaeH2eOnlyMode(enable));
+    }
+
+    /**
+     * Set SAE Public-Key only mode enabled.
+     * Before calling this API, call {@link WifiManager#isWpa3SaePkSupported()
+     * to know whether WPA3 SAE Public-Key is supported or not.
+     *
+     * @param enable true if enabled; false otherwise.
+     * @hide
+     */
+    public void enableSaePkOnlyMode(boolean enable) {
+        mSecurityParamsList.stream()
+                .filter(params -> params.isSecurityType(SECURITY_TYPE_SAE))
+                .findAny()
+                .ifPresent(params -> params.enableSaePkOnlyMode(enable));
     }
 
     /** @hide */
@@ -596,6 +1008,12 @@
     public static final int AP_BAND_5GHZ = 1;
 
     /**
+     * 60GHz band
+     * @hide
+     */
+    public static final int AP_BAND_60GHZ = 2;
+
+    /**
      * Device is allowed to choose the optimal band (2Ghz or 5Ghz) based on device capability,
      * operating country code and current radio conditions.
      * @hide
@@ -664,6 +1082,47 @@
     public int priority;
 
     /**
+     * The deletion priority of this configuration.
+     *
+     * Deletion priority is a non-negative value (default 0) indicating the priority for deletion
+     * when auto-pruning the amount of saved configurations. Networks with a lower value will be
+     * pruned before networks with a higher value.
+     */
+    private int mDeletionPriority;
+
+    /**
+     * Sets the deletion priority of this configuration.
+     *
+     * Deletion priority is a non-negative value (default 0) indicating the priority for deletion
+     * when auto-pruning the amount of saved configurations. Networks with a lower value will be
+     * pruned before networks with a higher value.
+     *
+     * @param priority non-negative deletion priority
+     * @hide
+     */
+    @SystemApi
+    public void setDeletionPriority(int priority) throws IllegalArgumentException {
+        if (priority < 0) {
+            throw new IllegalArgumentException("Deletion priority must be non-negative");
+        }
+        mDeletionPriority = priority;
+    }
+
+    /**
+     * Returns the deletion priority of this configuration.
+     *
+     * Deletion priority is a non-negative value (default 0) indicating the priority for deletion
+     * when auto-pruning the amount of saved configurations. Networks with a lower value will be
+     * pruned before networks with a higher value.
+     *
+     * @hide
+     */
+    @SystemApi
+    public int getDeletionPriority() {
+        return mDeletionPriority;
+    }
+
+    /**
      * This is a network that does not broadcast its SSID, so an
      * SSID-specific probe request must be used for scans.
      */
@@ -777,31 +1236,31 @@
     private IpConfiguration mIpConfiguration;
 
     /**
-     * @hide
      * dhcp server MAC address if known
+     * @hide
      */
     public String dhcpServer;
 
     /**
-     * @hide
      * default Gateway MAC address if known
+     * @hide
      */
     @UnsupportedAppUsage
     public String defaultGwMacAddress;
 
     /**
-     * @hide
      * last time we connected, this configuration had validated internet access
+     * @hide
      */
     @UnsupportedAppUsage
     public boolean validatedInternetAccess;
 
     /**
-     * @hide
      * The number of beacon intervals between Delivery Traffic Indication Maps (DTIM)
      * This value is populated from scan results that contain Beacon Frames, which are infrequent.
      * The value is not guaranteed to be set or current (Although it SHOULDNT change once set)
      * Valid values are from 1 - 255. Initialized here as 0, use this to check if set.
+     * @hide
      */
     public int dtimInterval = 0;
 
@@ -813,38 +1272,38 @@
      */
     public boolean isLegacyPasspointConfig = false;
     /**
-     * @hide
      * Uid of app creating the configuration
+     * @hide
      */
     @SystemApi
     public int creatorUid;
 
     /**
-     * @hide
      * Uid of last app issuing a connection related command
+     * @hide
      */
-    @UnsupportedAppUsage
+    @SystemApi
     public int lastConnectUid;
 
     /**
-     * @hide
      * Uid of last app modifying the configuration
+     * @hide
      */
     @SystemApi
     public int lastUpdateUid;
 
     /**
-     * @hide
      * Universal name for app creating the configuration
      *    see {@link PackageManager#getNameForUid(int)}
+     * @hide
      */
     @SystemApi
     public String creatorName;
 
     /**
-     * @hide
      * Universal name for app updating the configuration
      *    see {@link PackageManager#getNameForUid(int)}
+     * @hide
      */
     @SystemApi
     public String lastUpdateName;
@@ -858,9 +1317,17 @@
     public int carrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
 
     /**
+     * The subscription ID identifies the SIM card for which this network configuration is valid.
+     * See {@link SubscriptionInfo#getSubscriptionId()}
      * @hide
+     */
+    @SystemApi
+    public int subscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+    /**
      * Auto-join is allowed by user for this network.
      * Default true.
+     * @hide
      */
     @SystemApi
     public boolean allowAutojoin = true;
@@ -870,17 +1337,17 @@
     public static int INVALID_RSSI = -127;
 
     /**
-     * @hide
      * Number of reports indicating no Internet Access
+     * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public int numNoInternetAccessReports;
 
     /**
-     * @hide
      * The WiFi configuration is considered to have no internet access for purpose of autojoining
      * if there has been a report of it having no internet access, and, it never have had
      * internet access in the past.
+     * @hide
      */
     @SystemApi
     public boolean hasNoInternetAccess() {
@@ -914,18 +1381,32 @@
     public boolean osu;
 
     /**
-     * @hide
      * Last time the system was connected to this configuration.
+     * @hide
      */
     public long lastConnected;
 
     /**
-     * @hide
      * Last time the system was disconnected to this configuration.
+     * @hide
      */
     public long lastDisconnected;
 
     /**
+     * Last time this configuration was updated or created.
+     * Note: This field only exists in-memory and is not persisted in WifiConfigStore.xml for
+     *       privacy reasons.
+     * @hide
+     */
+    public long lastUpdated;
+
+    /**
+     * Number of reboots since this config was last used (either connected or updated).
+     * @hide
+     */
+    public int numRebootsSinceLastUse;
+
+    /**
      * Set if the configuration was self added by the framework
      * This boolean is cleared if we get a connect/save/ update or
      * any wifiManager command that indicate the user interacted with the configuration
@@ -943,16 +1424,16 @@
     public String peerWifiConfiguration;
 
     /**
-     * @hide
      * Indicate that a WifiConfiguration is temporary and should not be saved
      * nor considered by AutoJoin.
+     * @hide
      */
     public boolean ephemeral;
 
     /**
-     * @hide
      * Indicate that a WifiConfiguration is temporary and should not be saved
      * nor considered by AutoJoin.
+     * @hide
      */
     @SystemApi
     public boolean isEphemeral() {
@@ -969,6 +1450,36 @@
     public boolean trusted;
 
     /**
+     * Indicate whether the network is oem paid or not. Networks are considered oem paid
+     * if the corresponding connection is only available to system apps.
+     *
+     * This bit can only be used by suggestion network, see
+     * {@link WifiNetworkSuggestion.Builder#setOemPaid(boolean)}
+     * @hide
+     */
+    public boolean oemPaid;
+
+
+    /**
+     * Indicate whether the network is oem private or not. Networks are considered oem private
+     * if the corresponding connection is only available to system apps.
+     *
+     * This bit can only be used by suggestion network, see
+     * {@link WifiNetworkSuggestion.Builder#setOemPrivate(boolean)}
+     * @hide
+     */
+    public boolean oemPrivate;
+
+    /**
+     * Indicate whether or not the network is a carrier merged network.
+     * This bit can only be used by suggestion network, see
+     * {@link WifiNetworkSuggestion.Builder#setCarrierMerged(boolean)}
+     * @hide
+     */
+    @SystemApi
+    public boolean carrierMerged;
+
+    /**
      * True if this Wifi configuration is created from a {@link WifiNetworkSuggestion},
      * false otherwise.
      *
@@ -1068,60 +1579,58 @@
         return metered;
     }
 
-    /**
-     * @hide
-     * Returns true if this WiFi config is for an open network.
-     */
-    public boolean isOpenNetwork() {
-        final int cardinality = allowedKeyManagement.cardinality();
-        final boolean hasNoKeyMgmt = cardinality == 0
-                || (cardinality == 1 && (allowedKeyManagement.get(KeyMgmt.NONE)
-                || allowedKeyManagement.get(KeyMgmt.OWE)));
-
-        boolean hasNoWepKeys = true;
-        if (wepKeys != null) {
-            for (int i = 0; i < wepKeys.length; i++) {
-                if (wepKeys[i] != null) {
-                    hasNoWepKeys = false;
-                    break;
-                }
+    /** Check whether wep keys exist. */
+    private boolean hasWepKeys() {
+        if (wepKeys == null) return false;
+        for (int i = 0; i < wepKeys.length; i++) {
+            if (wepKeys[i] != null) {
+                return true;
             }
         }
-
-        return hasNoKeyMgmt && hasNoWepKeys;
+        return false;
     }
 
     /**
+     * Returns true if this WiFi config is for an Open or Enhanced Open network.
      * @hide
+     */
+    public boolean isOpenNetwork() {
+        boolean hasNonOpenSecurityType = mSecurityParamsList.stream()
+                .anyMatch(params -> !params.isOpenSecurityType());
+        return !hasNonOpenSecurityType && !hasWepKeys();
+    }
+
+    /**
      * Setting this value will force scan results associated with this configuration to
      * be included in the bucket of networks that are externally scored.
      * If not set, associated scan results will be treated as legacy saved networks and
      * will take precedence over networks in the scored category.
+     * @hide
      */
     @SystemApi
     public boolean useExternalScores;
 
     /**
-     * @hide
      * Number of time the scorer overrode a the priority based choice, when comparing two
      * WifiConfigurations, note that since comparing WifiConfiguration happens very often
      * potentially at every scan, this number might become very large, even on an idle
      * system.
+     * @hide
      */
     @SystemApi
     public int numScorerOverride;
 
     /**
-     * @hide
      * Number of time the scorer overrode a the priority based choice, and the comparison
      * triggered a network switch
+     * @hide
      */
     @SystemApi
     public int numScorerOverrideAndSwitchedNetwork;
 
     /**
+     * Number of times we associated to this configuration.
      * @hide
-     * Number of time we associated to this configuration.
      */
     @SystemApi
     public int numAssociation;
@@ -1130,7 +1639,9 @@
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"RANDOMIZATION_"}, value = {
             RANDOMIZATION_NONE,
-            RANDOMIZATION_PERSISTENT})
+            RANDOMIZATION_PERSISTENT,
+            RANDOMIZATION_NON_PERSISTENT,
+            RANDOMIZATION_AUTO})
     public @interface MacRandomizationSetting {}
 
     /**
@@ -1147,35 +1658,57 @@
     public static final int RANDOMIZATION_PERSISTENT = 1;
 
     /**
+     * Use a randomly generated MAC address for connections to this network.
+     * This option does not persist the randomized MAC address.
+     * @hide
+     */
+    @SystemApi
+    public static final int RANDOMIZATION_NON_PERSISTENT = 2;
+
+    /**
+     * Let the wifi framework automatically decide the MAC randomization strategy.
+     * @hide
+     */
+    @SystemApi
+    public static final int RANDOMIZATION_AUTO = 3;
+
+    /**
      * Level of MAC randomization for this network.
-     * One of {@link #RANDOMIZATION_NONE} or {@link #RANDOMIZATION_PERSISTENT}.
-     * By default this field is set to {@link #RANDOMIZATION_PERSISTENT}.
+     * One of {@link #RANDOMIZATION_NONE}, {@link #RANDOMIZATION_AUTO},
+     * {@link #RANDOMIZATION_PERSISTENT} or {@link #RANDOMIZATION_NON_PERSISTENT}.
+     * By default this field is set to {@link #RANDOMIZATION_AUTO}.
      * @hide
      */
     @SystemApi
     @MacRandomizationSetting
-    public int macRandomizationSetting = RANDOMIZATION_PERSISTENT;
+    public int macRandomizationSetting = RANDOMIZATION_AUTO;
 
     /**
-     * @hide
      * Randomized MAC address to use with this particular network
+     * @hide
      */
     @NonNull
     private MacAddress mRandomizedMacAddress;
 
     /**
+     * The wall clock time of when |mRandomizedMacAddress| should be re-randomized in enhanced
+     * MAC randomization mode.
      * @hide
-     * The wall clock time of when |mRandomizedMacAddress| should be re-randomized in aggressive
-     * randomization mode.
      */
     public long randomizedMacExpirationTimeMs = 0;
 
     /**
+     * The wall clock time of when |mRandomizedMacAddress| is last modified.
      * @hide
+     */
+    public long randomizedMacLastModifiedTimeMs = 0;
+
+    /**
      * Checks if the given MAC address can be used for Connected Mac Randomization
      * by verifying that it is non-null, unicast, locally assigned, and not default mac.
      * @param mac MacAddress to check
      * @return true if mac is good to use
+     * @hide
      */
     public static boolean isValidMacAddressForRandomization(MacAddress mac) {
         return mac != null && !MacAddressUtils.isMulticastAddress(mac) && mac.isLocallyAssigned()
@@ -1196,8 +1729,8 @@
     }
 
     /**
-     * @hide
      * @param mac MacAddress to change into
+     * @hide
      */
     public void setRandomizedMacAddress(@NonNull MacAddress mac) {
         if (mac == null) {
@@ -1271,7 +1804,10 @@
                 DISABLED_NO_INTERNET_PERMANENT,
                 DISABLED_BY_WIFI_MANAGER,
                 DISABLED_BY_WRONG_PASSWORD,
-                DISABLED_AUTHENTICATION_NO_SUBSCRIPTION})
+                DISABLED_AUTHENTICATION_NO_SUBSCRIPTION,
+                DISABLED_AUTHENTICATION_PRIVATE_EAP_ERROR,
+                DISABLED_NETWORK_NOT_FOUND,
+                DISABLED_CONSECUTIVE_FAILURES})
         public @interface NetworkSelectionDisableReason {}
 
         // Quality Network disabled reasons
@@ -1282,42 +1818,46 @@
          * @hide
          */
         public static final int NETWORK_SELECTION_DISABLED_STARTING_INDEX = 1;
-        /**
-         * The starting index for network selection temporarily disabled reasons.
-         * @hide
-         */
-        public static final int TEMPORARILY_DISABLED_STARTING_INDEX = 1;
-        /** This network is disabled because of multiple association rejections. */
+        /** This network is temporarily disabled because of multiple association rejections. */
         public static final int DISABLED_ASSOCIATION_REJECTION = 1;
-        /** This network is disabled because of multiple authentication failure. */
+        /** This network is temporarily disabled because of multiple authentication failure. */
         public static final int DISABLED_AUTHENTICATION_FAILURE = 2;
-        /** This network is disabled because of multiple DHCP failure. */
+        /** This network is temporarily disabled because of multiple DHCP failure. */
         public static final int DISABLED_DHCP_FAILURE = 3;
         /** This network is temporarily disabled because it has no Internet access. */
         public static final int DISABLED_NO_INTERNET_TEMPORARY = 4;
-        /**
-         * The starting index for network selection permanently disabled reasons.
-         * @hide
-         */
-        public static final int PERMANENTLY_DISABLED_STARTING_INDEX = 5;
-        /** This network is disabled due to absence of user credentials */
+        /** This network is permanently disabled due to absence of user credentials */
         public static final int DISABLED_AUTHENTICATION_NO_CREDENTIALS = 5;
         /**
          * This network is permanently disabled because it has no Internet access and the user does
          * not want to stay connected.
          */
         public static final int DISABLED_NO_INTERNET_PERMANENT = 6;
-        /** This network is disabled due to WifiManager disabling it explicitly. */
+        /** This network is permanently disabled due to WifiManager disabling it explicitly. */
         public static final int DISABLED_BY_WIFI_MANAGER = 7;
-        /** This network is disabled due to wrong password. */
+        /** This network is permanently disabled due to wrong password. */
         public static final int DISABLED_BY_WRONG_PASSWORD = 8;
-        /** This network is disabled because service is not subscribed. */
+        /** This network is permanently disabled because service is not subscribed. */
         public static final int DISABLED_AUTHENTICATION_NO_SUBSCRIPTION = 9;
+        /** This network is disabled due to provider-specific (private) EAP failure. */
+        public static final int DISABLED_AUTHENTICATION_PRIVATE_EAP_ERROR = 10;
+        /**
+         * This network is disabled because supplicant failed to find a network in scan result
+         * which matches the network requested by framework for connection
+         * (including network capabilities).
+         */
+        public static final int DISABLED_NETWORK_NOT_FOUND = 11;
+        /**
+         * This code is used to disable a network when a high number of consecutive connection
+         * failures are detected. The exact reasons of why these consecutive failures occurred is
+         * included but not limited to the reasons described by failure codes above.
+         */
+        public static final int DISABLED_CONSECUTIVE_FAILURES = 12;
         /**
          * All other disable reasons should be strictly less than this value.
          * @hide
          */
-        public static final int NETWORK_SELECTION_DISABLED_MAX = 10;
+        public static final int NETWORK_SELECTION_DISABLED_MAX = 13;
 
         /**
          * Get an integer that is equal to the maximum integer value of all the
@@ -1346,6 +1886,11 @@
          */
         public static final class DisableReasonInfo {
             /**
+             * A special constant which indicates the network should be permanently disabled.
+             * @hide
+             */
+            public static final int PERMANENT_DISABLE_TIMEOUT = -1;
+            /**
              * String representation for the disable reason.
              * Note that these strings are persisted in
              * {@link
@@ -1361,6 +1906,8 @@
             /**
              * Network Selection disable timeout for the error. After the timeout milliseconds,
              * enable the network again.
+             * If this is set to PERMANENT_DISABLE_TIMEOUT, the network will be permanently disabled
+             * until the next time the user manually connects to it.
              */
             public final int mDisableTimeoutMillis;
 
@@ -1389,6 +1936,12 @@
         private static SparseArray<DisableReasonInfo> buildDisableReasonInfos() {
             SparseArray<DisableReasonInfo> reasons = new SparseArray<>();
 
+            // Note that some of these disable thresholds are overridden in
+            // WifiBlocklistMonitor#loadCustomConfigsForDisableReasonInfos using overlays.
+            // TODO(b/180148727): For a few of these disable reasons, we provide defaults here
+            //  and in the overlay XML, which is confusing. Clean this up so we only define the
+            //  default in one place.
+
             reasons.append(DISABLED_NONE,
                     new DisableReasonInfo(
                             // Note that these strings are persisted in
@@ -1397,26 +1950,26 @@
                             // compatibility.
                             "NETWORK_SELECTION_ENABLE",
                             -1,
-                            Integer.MAX_VALUE));
+                            0));
 
             reasons.append(DISABLED_ASSOCIATION_REJECTION,
                     new DisableReasonInfo(
                             // Note that there is a space at the end of this string. Cannot fix
                             // since this string is persisted.
                             "NETWORK_SELECTION_DISABLED_ASSOCIATION_REJECTION ",
-                            5,
+                            3,
                             5 * 60 * 1000));
 
             reasons.append(DISABLED_AUTHENTICATION_FAILURE,
                     new DisableReasonInfo(
                             "NETWORK_SELECTION_DISABLED_AUTHENTICATION_FAILURE",
-                            5,
+                            3,
                             5 * 60 * 1000));
 
             reasons.append(DISABLED_DHCP_FAILURE,
                     new DisableReasonInfo(
                             "NETWORK_SELECTION_DISABLED_DHCP_FAILURE",
-                            5,
+                            2,
                             5 * 60 * 1000));
 
             reasons.append(DISABLED_NO_INTERNET_TEMPORARY,
@@ -1428,32 +1981,49 @@
             reasons.append(DISABLED_AUTHENTICATION_NO_CREDENTIALS,
                     new DisableReasonInfo(
                             "NETWORK_SELECTION_DISABLED_AUTHENTICATION_NO_CREDENTIALS",
-                            1,
-                            Integer.MAX_VALUE));
+                            3,
+                            DisableReasonInfo.PERMANENT_DISABLE_TIMEOUT));
 
             reasons.append(DISABLED_NO_INTERNET_PERMANENT,
                     new DisableReasonInfo(
                             "NETWORK_SELECTION_DISABLED_NO_INTERNET_PERMANENT",
                             1,
-                            Integer.MAX_VALUE));
+                            DisableReasonInfo.PERMANENT_DISABLE_TIMEOUT));
 
             reasons.append(DISABLED_BY_WIFI_MANAGER,
                     new DisableReasonInfo(
                             "NETWORK_SELECTION_DISABLED_BY_WIFI_MANAGER",
                             1,
-                            Integer.MAX_VALUE));
+                            DisableReasonInfo.PERMANENT_DISABLE_TIMEOUT));
 
             reasons.append(DISABLED_BY_WRONG_PASSWORD,
                     new DisableReasonInfo(
                             "NETWORK_SELECTION_DISABLED_BY_WRONG_PASSWORD",
                             1,
-                            Integer.MAX_VALUE));
+                            DisableReasonInfo.PERMANENT_DISABLE_TIMEOUT));
 
             reasons.append(DISABLED_AUTHENTICATION_NO_SUBSCRIPTION,
                     new DisableReasonInfo(
                             "NETWORK_SELECTION_DISABLED_AUTHENTICATION_NO_SUBSCRIPTION",
                             1,
-                            Integer.MAX_VALUE));
+                            DisableReasonInfo.PERMANENT_DISABLE_TIMEOUT));
+
+            reasons.append(DISABLED_AUTHENTICATION_PRIVATE_EAP_ERROR,
+                    new DisableReasonInfo(
+                            "NETWORK_SELECTION_DISABLED_AUTHENTICATION_PRIVATE_EAP_ERROR",
+                            1,
+                            DisableReasonInfo.PERMANENT_DISABLE_TIMEOUT));
+
+            reasons.append(DISABLED_NETWORK_NOT_FOUND,
+                    new DisableReasonInfo(
+                            "NETWORK_SELECTION_DISABLED_NETWORK_NOT_FOUND",
+                            2,
+                            5 * 60 * 1000));
+
+            reasons.append(DISABLED_CONSECUTIVE_FAILURES,
+                    new DisableReasonInfo("NETWORK_SELECTION_DISABLED_CONSECUTIVE_FAILURES",
+                            1,
+                            5 * 60 * 1000));
 
             return reasons;
         }
@@ -1529,6 +2099,11 @@
         private String mConnectChoice;
 
         /**
+         * The RSSI when the user made the connectChoice.
+         */
+        private int mConnectChoiceRssi;
+
+        /**
          * Used to cache the temporary candidate during the network selection procedure. It will be
          * kept updating once a new scan result has a higher score than current one
          */
@@ -1541,6 +2116,11 @@
         private int mCandidateScore;
 
         /**
+         * Used to cache the select security params from the candidate.
+         */
+        private SecurityParams mCandidateSecurityParams;
+
+        /**
          * Indicate whether this network is visible in latest Qualified Network Selection. This
          * means there is scan result found related to this Configuration and meet the minimum
          * requirement. The saved network need not join latest Qualified Network Selection. For
@@ -1559,6 +2139,14 @@
         private boolean mHasEverConnected;
 
         /**
+         * Boolean indicating if captive portal has never been detected on this network.
+         *
+         * This should be true by default, for newly created WifiConfigurations until a captive
+         * portal is detected.
+         */
+        private boolean mHasNeverDetectedCaptivePortal = true;
+
+        /**
          * set whether this network is visible in latest Qualified Network Selection
          * @param seen value set to candidate
          * @hide
@@ -1614,6 +2202,24 @@
         }
 
         /**
+         * set the security type of the temporary candidate of current network selection procedure
+         * @param params value to set to mCandidateSecurityParams
+         * @hide
+         */
+        public void setCandidateSecurityParams(SecurityParams params) {
+            mCandidateSecurityParams = params;
+        }
+
+        /**
+         * get the security type of the temporary candidate of current network selection procedure
+         * @return return the security params
+         * @hide
+         */
+        public SecurityParams getCandidateSecurityParams() {
+            return mCandidateSecurityParams;
+        }
+
+        /**
          * get user preferred choice over this configuration
          * @return returns configKey of user preferred choice over this configuration
          * @hide
@@ -1631,6 +2237,23 @@
             mConnectChoice = newConnectChoice;
         }
 
+        /**
+         * Associate a RSSI with the user connect choice network.
+         * @param rssi signal strength
+         * @hide
+         */
+        public void setConnectChoiceRssi(int rssi) {
+            mConnectChoiceRssi = rssi;
+        }
+
+        /**
+         * @return returns the RSSI of the last time the user made the connect choice.
+         * @hide
+         */
+        public int getConnectChoiceRssi() {
+            return mConnectChoiceRssi;
+        }
+
         /** Get the current Quality network selection status as a String (for debugging). */
         @NonNull
         public String getNetworkStatusString() {
@@ -1647,6 +2270,19 @@
             return mHasEverConnected;
         }
 
+        /**
+         * Set whether a captive portal has never been detected on this network.
+         * @hide
+         */
+        public void setHasNeverDetectedCaptivePortal(boolean value) {
+            mHasNeverDetectedCaptivePortal = value;
+        }
+
+        /** @hide */
+        public boolean hasNeverDetectedCaptivePortal() {
+            return mHasNeverDetectedCaptivePortal;
+        }
+
         /** @hide */
         public NetworkSelectionStatus() {
             // previously stored configs will not have this parameter, so we default to false.
@@ -1920,8 +2556,11 @@
             setSeenInLastQualifiedNetworkSelection(source.getSeenInLastQualifiedNetworkSelection());
             setCandidate(source.getCandidate());
             setCandidateScore(source.getCandidateScore());
+            setCandidateSecurityParams(source.getCandidateSecurityParams());
             setConnectChoice(source.getConnectChoice());
+            setConnectChoiceRssi(source.getConnectChoiceRssi());
             setHasEverConnected(source.hasEverConnected());
+            setHasNeverDetectedCaptivePortal(source.hasNeverDetectedCaptivePortal());
         }
 
         /** @hide */
@@ -1937,10 +2576,12 @@
             if (getConnectChoice() != null) {
                 dest.writeInt(CONNECT_CHOICE_EXISTS);
                 dest.writeString(getConnectChoice());
+                dest.writeInt(getConnectChoiceRssi());
             } else {
                 dest.writeInt(CONNECT_CHOICE_NOT_EXISTS);
             }
             dest.writeInt(hasEverConnected() ? 1 : 0);
+            dest.writeInt(hasNeverDetectedCaptivePortal() ? 1 : 0);
         }
 
         /** @hide */
@@ -1955,16 +2596,18 @@
             setNetworkSelectionBSSID(in.readString());
             if (in.readInt() == CONNECT_CHOICE_EXISTS) {
                 setConnectChoice(in.readString());
+                setConnectChoiceRssi(in.readInt());
             } else {
                 setConnectChoice(null);
             }
             setHasEverConnected(in.readInt() != 0);
+            setHasNeverDetectedCaptivePortal(in.readInt() != 0);
         }
     }
 
     /**
-     * @hide
      * network selection related member
+     * @hide
      */
     private NetworkSelectionStatus mNetworkSelectionStatus = new NetworkSelectionStatus();
 
@@ -1983,27 +2626,41 @@
          */
         @RecentFailureReason
         private int mAssociationStatus = RECENT_FAILURE_NONE;
+        private long mLastUpdateTimeSinceBootMillis;
 
         /**
          * @param status the association status code for the recent failure
          */
-        public void setAssociationStatus(@RecentFailureReason int status) {
+        public void setAssociationStatus(@RecentFailureReason int status,
+                long updateTimeSinceBootMs) {
             mAssociationStatus = status;
+            mLastUpdateTimeSinceBootMillis = updateTimeSinceBootMs;
         }
         /**
          * Sets the RecentFailure to NONE
          */
         public void clear() {
             mAssociationStatus = RECENT_FAILURE_NONE;
+            mLastUpdateTimeSinceBootMillis = 0;
         }
         /**
-         * Get the recent failure code. One of {@link #RECENT_FAILURE_NONE} or
-         * {@link #RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA}.
+         * Get the recent failure code. One of {@link #RECENT_FAILURE_NONE},
+         * {@link #RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA},
+         * {@link #RECENT_FAILURE_REFUSED_TEMPORARILY},
+         * {@link #RECENT_FAILURE_POOR_CHANNEL_CONDITIONS}.
+         * {@link #RECENT_FAILURE_DISCONNECTION_AP_BUSY}
          */
         @RecentFailureReason
         public int getAssociationStatus() {
             return mAssociationStatus;
         }
+
+        /**
+         * Get the timestamp the failure status is last updated, in milliseconds since boot.
+         */
+        public long getLastUpdateTimeSinceBootMillis() {
+            return mLastUpdateTimeSinceBootMillis;
+        }
     }
 
     /**
@@ -2019,7 +2676,19 @@
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = "RECENT_FAILURE_", value = {
             RECENT_FAILURE_NONE,
-            RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA})
+            RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA,
+            RECENT_FAILURE_REFUSED_TEMPORARILY,
+            RECENT_FAILURE_POOR_CHANNEL_CONDITIONS,
+            RECENT_FAILURE_DISCONNECTION_AP_BUSY,
+            RECENT_FAILURE_MBO_ASSOC_DISALLOWED_UNSPECIFIED,
+            RECENT_FAILURE_MBO_ASSOC_DISALLOWED_MAX_NUM_STA_ASSOCIATED,
+            RECENT_FAILURE_MBO_ASSOC_DISALLOWED_AIR_INTERFACE_OVERLOADED,
+            RECENT_FAILURE_MBO_ASSOC_DISALLOWED_AUTH_SERVER_OVERLOADED,
+            RECENT_FAILURE_MBO_ASSOC_DISALLOWED_INSUFFICIENT_RSSI,
+            RECENT_FAILURE_OCE_RSSI_BASED_ASSOCIATION_REJECTION,
+            RECENT_FAILURE_NETWORK_NOT_FOUND
+
+    })
     public @interface RecentFailureReason {}
 
     /**
@@ -2037,12 +2706,107 @@
     public static final int RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA = 17;
 
     /**
+     * Failed to connect because the association is rejected by the AP.
+     * IEEE 802.11 association status code 30.
+     * @hide
+     */
+    @SystemApi
+    public static final int RECENT_FAILURE_REFUSED_TEMPORARILY = 1002;
+
+    /**
+     * Failed to connect because of excess frame loss and/or poor channel conditions.
+     * IEEE 802.11 association status code 34.
+     * @hide
+     */
+    @SystemApi
+    public static final int RECENT_FAILURE_POOR_CHANNEL_CONDITIONS = 1003;
+
+    /**
+     * Disconnected by the AP because the AP can't handle all the associated stations.
+     * IEEE 802.11 disconnection reason code 5.
+     * @hide
+     */
+    @SystemApi
+    public static final int RECENT_FAILURE_DISCONNECTION_AP_BUSY = 1004;
+
+    /**
+     * Failed to connect because the association is rejected by the AP with
+     * MBO association disallowed Reason code: 1 - Unspecified or 0/6-255 - Reserved.
+     * Details in MBO spec v1.2, 4.2.4 Table 13: MBO Association Disallowed attribute
+     * @hide
+     */
+    @SystemApi
+    public static final int RECENT_FAILURE_MBO_ASSOC_DISALLOWED_UNSPECIFIED = 1005;
+
+    /**
+     * Failed to connect because the association is rejected by the AP with
+     * MBO association disallowed Reason code: 2 - Maximum number of associated stations reached.
+     * Details in MBO spec v1.2, 4.2.4 Table 13: MBO Association Disallowed attribute
+     * @hide
+     */
+    @SystemApi
+    public static final int RECENT_FAILURE_MBO_ASSOC_DISALLOWED_MAX_NUM_STA_ASSOCIATED = 1006;
+
+    /**
+     * Failed to connect because the association is rejected by the AP with
+     * MBO association disallowed Reason code: 3 - Air interface is overloaded.
+     * Details in MBO spec v1.2, 4.2.4 Table 13: MBO Association Disallowed attribute
+     * @hide
+     */
+    @SystemApi
+    public static final int RECENT_FAILURE_MBO_ASSOC_DISALLOWED_AIR_INTERFACE_OVERLOADED = 1007;
+
+    /**
+     * Failed to connect because the association is rejected by the AP with
+     * MBO association disallowed Reason code: 4 - Authentication server overloaded.
+     * Details in MBO spec v1.2, 4.2.4 Table 13: MBO Association Disallowed attribute
+     * @hide
+     */
+    @SystemApi
+    public static final int RECENT_FAILURE_MBO_ASSOC_DISALLOWED_AUTH_SERVER_OVERLOADED = 1008;
+
+    /**
+     * Failed to connect because the association is rejected by the AP with
+     * MBO association disallowed Reason code: 5 - Insufficient RSSI.
+     * Details in MBO spec v1.2, 4.2.4 Table 13: MBO Association Disallowed attribute
+     * @hide
+     */
+    @SystemApi
+    public static final int RECENT_FAILURE_MBO_ASSOC_DISALLOWED_INSUFFICIENT_RSSI = 1009;
+
+    /**
+     * Failed to connect because the association is rejected by the AP with
+     * OCE rssi based association rejection attribute.
+     * Details in OCE spec v1.0, 3.14 Presence of OCE rssi based association rejection attribute.
+     * @hide
+     */
+    @SystemApi
+    public static final int RECENT_FAILURE_OCE_RSSI_BASED_ASSOCIATION_REJECTION = 1010;
+
+    /**
+     * Failed to connect because supplicant failed to find a network in scan result which
+     * matches the network requested by framework for connection (including network capabilities).
+     * @hide
+     */
+    @SystemApi
+    public static final int RECENT_FAILURE_NETWORK_NOT_FOUND = 1011;
+
+    /**
      * Get the failure reason for the most recent connection attempt, or
      * {@link #RECENT_FAILURE_NONE} if there was no failure.
      *
      * Failure reasons include:
      * {@link #RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA}
-     *
+     * {@link #RECENT_FAILURE_REFUSED_TEMPORARILY}
+     * {@link #RECENT_FAILURE_POOR_CHANNEL_CONDITIONS}
+     * {@link #RECENT_FAILURE_DISCONNECTION_AP_BUSY}
+     * {@link #RECENT_FAILURE_MBO_ASSOC_DISALLOWED_UNSPECIFIED}
+     * {@link #RECENT_FAILURE_MBO_ASSOC_DISALLOWED_MAX_NUM_STA_ASSOCIATED}
+     * {@link #RECENT_FAILURE_MBO_ASSOC_DISALLOWED_AIR_INTERFACE_OVERLOADED}
+     * {@link #RECENT_FAILURE_MBO_ASSOC_DISALLOWED_AUTH_SERVER_OVERLOADED}
+     * {@link #RECENT_FAILURE_MBO_ASSOC_DISALLOWED_INSUFFICIENT_RSSI}
+     * {@link #RECENT_FAILURE_OCE_RSSI_BASED_ASSOCIATION_REJECTION}
+     * {@link #RECENT_FAILURE_NETWORK_NOT_FOUND}
      * @hide
      */
     @RecentFailureReason
@@ -2071,12 +2835,12 @@
     }
 
     /**
-     * @hide
      * Linked Configurations: represent the set of Wificonfigurations that are equivalent
      * regarding roaming and auto-joining.
      * The linked configuration may or may not have same SSID, and may or may not have same
      * credentials.
      * For instance, linked configurations will have same defaultGwMacAddress or same dhcp server.
+     * @hide
      */
     public HashMap<String, Integer>  linkedConfigurations;
 
@@ -2087,6 +2851,7 @@
         FQDN = null;
         roamingConsortiumIds = new long[0];
         priority = 0;
+        mDeletionPriority = 0;
         hiddenSSID = false;
         allowedKeyManagement = new BitSet();
         allowedProtocols = new BitSet();
@@ -2103,6 +2868,9 @@
         ephemeral = false;
         osu = false;
         trusted = true; // Networks are considered trusted by default.
+        oemPaid = false;
+        oemPrivate = false;
+        carrierMerged = false;
         fromWifiNetworkSuggestion = false;
         fromWifiNetworkSpecifier = false;
         meteredHint = false;
@@ -2115,6 +2883,7 @@
         shared = true;
         dtimInterval = 0;
         mRandomizedMacAddress = MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS);
+        numRebootsSinceLastUse = 0;
     }
 
     /**
@@ -2150,12 +2919,11 @@
      */
     @UnsupportedAppUsage
     public boolean isEnterprise() {
-        return (allowedKeyManagement.get(KeyMgmt.WPA_EAP)
-                || allowedKeyManagement.get(KeyMgmt.IEEE8021X)
-                || allowedKeyManagement.get(KeyMgmt.SUITE_B_192)
-                || allowedKeyManagement.get(KeyMgmt.WAPI_CERT))
+        boolean hasEnterpriseSecurityType = mSecurityParamsList.stream()
+                .anyMatch(params -> params.isEnterpriseSecurityType());
+        return (hasEnterpriseSecurityType
                 && enterpriseConfig != null
-                && enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE;
+                && enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE);
     }
 
     private static String logTimeOfDay(long millis) {
@@ -2184,6 +2952,7 @@
                 .append(" HIDDEN: ").append(this.hiddenSSID)
                 .append(" PMF: ").append(this.requirePmf)
                 .append("CarrierId: ").append(this.carrierId)
+                .append("SubscriptionId").append(this.subscriptionId)
                 .append('\n');
 
 
@@ -2208,9 +2977,13 @@
         }
         if (mNetworkSelectionStatus.getConnectChoice() != null) {
             sbuf.append(" connect choice: ").append(mNetworkSelectionStatus.getConnectChoice());
+            sbuf.append(" connect choice rssi: ")
+                    .append(mNetworkSelectionStatus.getConnectChoiceRssi());
         }
         sbuf.append(" hasEverConnected: ")
                 .append(mNetworkSelectionStatus.hasEverConnected()).append("\n");
+        sbuf.append(" hasNeverDetectedCaptivePortal: ")
+                .append(mNetworkSelectionStatus.hasNeverDetectedCaptivePortal()).append("\n");
 
         if (this.numAssociation > 0) {
             sbuf.append(" numAssociation ").append(this.numAssociation).append("\n");
@@ -2220,16 +2993,24 @@
             sbuf.append(this.numNoInternetAccessReports).append("\n");
         }
         if (this.validatedInternetAccess) sbuf.append(" validatedInternetAccess");
+        if (this.shared) {
+            sbuf.append(" shared");
+        } else {
+            sbuf.append(" not-shared");
+        }
         if (this.ephemeral) sbuf.append(" ephemeral");
         if (this.osu) sbuf.append(" osu");
         if (this.trusted) sbuf.append(" trusted");
+        if (this.oemPaid) sbuf.append(" oemPaid");
+        if (this.oemPrivate) sbuf.append(" oemPrivate");
+        if (this.carrierMerged) sbuf.append(" carrierMerged");
         if (this.fromWifiNetworkSuggestion) sbuf.append(" fromWifiNetworkSuggestion");
         if (this.fromWifiNetworkSpecifier) sbuf.append(" fromWifiNetworkSpecifier");
         if (this.meteredHint) sbuf.append(" meteredHint");
         if (this.useExternalScores) sbuf.append(" useExternalScores");
-        if (this.validatedInternetAccess || this.ephemeral || this.trusted
-                || this.fromWifiNetworkSuggestion || this.fromWifiNetworkSpecifier
-                || this.meteredHint || this.useExternalScores) {
+        if (this.validatedInternetAccess || this.ephemeral || this.trusted || this.oemPaid
+                || this.oemPrivate || this.carrierMerged || this.fromWifiNetworkSuggestion
+                || this.fromWifiNetworkSpecifier || this.meteredHint || this.useExternalScores) {
             sbuf.append("\n");
         }
         if (this.meteredOverride != METERED_OVERRIDE_NONE) {
@@ -2240,6 +3021,10 @@
         sbuf.append(" randomizedMacExpirationTimeMs: ")
                 .append(randomizedMacExpirationTimeMs == 0 ? "<none>"
                         : logTimeOfDay(randomizedMacExpirationTimeMs)).append("\n");
+        sbuf.append(" randomizedMacLastModifiedTimeMs: ")
+                .append(randomizedMacLastModifiedTimeMs == 0 ? "<none>"
+                        : logTimeOfDay(randomizedMacLastModifiedTimeMs)).append("\n");
+        sbuf.append(" deletionPriority: ").append(mDeletionPriority).append("\n");
         sbuf.append(" KeyMgmt:");
         for (int k = 0; k < this.allowedKeyManagement.size(); k++) {
             if (this.allowedKeyManagement.get(k)) {
@@ -2327,6 +3112,10 @@
             sbuf.append('*');
         }
 
+        sbuf.append("\nSecurityParams List:\n");
+        mSecurityParamsList.stream()
+                .forEach(params -> sbuf.append(params.toString()));
+
         sbuf.append("\nEnterprise config:\n");
         sbuf.append(enterpriseConfig);
 
@@ -2366,6 +3155,13 @@
             sbuf.append(" ");
         }
         sbuf.append('\n');
+        if (this.lastUpdated != 0) {
+            sbuf.append('\n');
+            sbuf.append("lastUpdated: ").append(logTimeOfDay(this.lastUpdated));
+            sbuf.append(" ");
+        }
+        sbuf.append('\n');
+        sbuf.append("numRebootsSinceLastUse: ").append(numRebootsSinceLastUse).append('\n');
         if (this.linkedConfigurations != null) {
             for (String key : this.linkedConfigurations.keySet()) {
                 sbuf.append(" linked: ").append(key);
@@ -2373,7 +3169,8 @@
             }
         }
         sbuf.append("recentFailure: ").append("Association Rejection code: ")
-                .append(recentFailure.getAssociationStatus()).append("\n");
+                .append(recentFailure.getAssociationStatus()).append(", last update time: ")
+                .append(recentFailure.getLastUpdateTimeSinceBootMillis()).append("\n");
         return sbuf.toString();
     }
 
@@ -2491,7 +3288,18 @@
     @KeyMgmt.KeyMgmtScheme
     public int getAuthType() {
         if (allowedKeyManagement.cardinality() > 1) {
-            throw new IllegalStateException("More than one auth type set");
+            if (allowedKeyManagement.get(KeyMgmt.WPA_EAP)) {
+                if (allowedKeyManagement.cardinality() == 2
+                        && allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
+                    return KeyMgmt.WPA_EAP;
+                }
+                if (allowedKeyManagement.cardinality() == 3
+                        && allowedKeyManagement.get(KeyMgmt.IEEE8021X)
+                        && allowedKeyManagement.get(KeyMgmt.SUITE_B_192)) {
+                    return KeyMgmt.SUITE_B_192;
+                }
+            }
+            throw new IllegalStateException("Invalid auth type set: " + allowedKeyManagement);
         }
         if (allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
             return KeyMgmt.WPA_PSK;
@@ -2536,6 +3344,26 @@
         return key;
     }
 
+    /**
+     * Get a unique key which represent this Wi-Fi network. If two profiles are for
+     * the same Wi-Fi network, but from different provider, they would have the same key.
+     * @hide
+     */
+    public String getNetworkKey() {
+        // Passpoint ephemeral networks have their unique identifier set. Return it as is to be
+        // able to match internally.
+        if (mPasspointUniqueId != null) {
+            return mPasspointUniqueId;
+        }
+
+        String key = SSID + getDefaultSecurityType();
+        if (!shared) {
+            key += "-" + UserHandle.getUserHandleForUid(creatorUid).getIdentifier();
+        }
+
+        return key;
+    }
+
     /** @hide
      *  return the SSID + security type in String format.
      */
@@ -2545,7 +3373,11 @@
             key = SSID + KeyMgmt.strings[KeyMgmt.WPA_PSK];
         } else if (allowedKeyManagement.get(KeyMgmt.WPA_EAP)
                 || allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
-            key = SSID + KeyMgmt.strings[KeyMgmt.WPA_EAP];
+            if (!requirePmf) {
+                key = SSID + KeyMgmt.strings[KeyMgmt.WPA_EAP];
+            } else {
+                key = SSID + "WPA3_EAP";
+            }
         } else if (wepTxKeyIndex >= 0 && wepTxKeyIndex < wepKeys.length
                 && wepKeys[wepTxKeyIndex] != null) {
             key = SSID + "WEP";
@@ -2735,6 +3567,7 @@
 
             wepTxKeyIndex = source.wepTxKeyIndex;
             priority = source.priority;
+            mDeletionPriority = source.mDeletionPriority;
             hiddenSSID = source.hiddenSSID;
             allowedKeyManagement   = (BitSet) source.allowedKeyManagement.clone();
             allowedProtocols       = (BitSet) source.allowedProtocols.clone();
@@ -2743,6 +3576,8 @@
             allowedGroupCiphers    = (BitSet) source.allowedGroupCiphers.clone();
             allowedGroupManagementCiphers = (BitSet) source.allowedGroupManagementCiphers.clone();
             allowedSuiteBCiphers    = (BitSet) source.allowedSuiteBCiphers.clone();
+            mSecurityParamsList = source.mSecurityParamsList.stream()
+                    .map(p -> new SecurityParams(p)).collect(Collectors.toList());
             enterpriseConfig = new WifiEnterpriseConfig(source.enterpriseConfig);
 
             defaultGwMacAddress = source.defaultGwMacAddress;
@@ -2759,6 +3594,9 @@
             ephemeral = source.ephemeral;
             osu = source.osu;
             trusted = source.trusted;
+            oemPaid = source.oemPaid;
+            oemPrivate = source.oemPrivate;
+            carrierMerged = source.carrierMerged;
             fromWifiNetworkSuggestion = source.fromWifiNetworkSuggestion;
             fromWifiNetworkSpecifier = source.fromWifiNetworkSpecifier;
             meteredHint = source.meteredHint;
@@ -2774,6 +3612,8 @@
 
             lastConnected = source.lastConnected;
             lastDisconnected = source.lastDisconnected;
+            lastUpdated = source.lastUpdated;
+            numRebootsSinceLastUse = source.numRebootsSinceLastUse;
             numScorerOverride = source.numScorerOverride;
             numScorerOverrideAndSwitchedNetwork = source.numScorerOverrideAndSwitchedNetwork;
             numAssociation = source.numAssociation;
@@ -2781,13 +3621,16 @@
             numNoInternetAccessReports = source.numNoInternetAccessReports;
             noInternetAccessExpected = source.noInternetAccessExpected;
             shared = source.shared;
-            recentFailure.setAssociationStatus(source.recentFailure.getAssociationStatus());
+            recentFailure.setAssociationStatus(source.recentFailure.getAssociationStatus(),
+                    source.recentFailure.getLastUpdateTimeSinceBootMillis());
             mRandomizedMacAddress = source.mRandomizedMacAddress;
             macRandomizationSetting = source.macRandomizationSetting;
             randomizedMacExpirationTimeMs = source.randomizedMacExpirationTimeMs;
+            randomizedMacLastModifiedTimeMs = source.randomizedMacLastModifiedTimeMs;
             requirePmf = source.requirePmf;
             updateIdentifier = source.updateIdentifier;
             carrierId = source.carrierId;
+            subscriptionId = source.subscriptionId;
             mPasspointUniqueId = source.mPasspointUniqueId;
         }
     }
@@ -2815,6 +3658,7 @@
         }
         dest.writeInt(wepTxKeyIndex);
         dest.writeInt(priority);
+        dest.writeInt(mDeletionPriority);
         dest.writeInt(hiddenSSID ? 1 : 0);
         dest.writeInt(requirePmf ? 1 : 0);
         dest.writeString(updateIdentifier);
@@ -2827,6 +3671,10 @@
         writeBitSet(dest, allowedGroupManagementCiphers);
         writeBitSet(dest, allowedSuiteBCiphers);
 
+        dest.writeInt(mSecurityParamsList.size());
+        mSecurityParamsList.stream()
+                .forEach(params -> params.writeToParcel(dest, flags));
+
         dest.writeParcelable(enterpriseConfig, flags);
 
         dest.writeParcelable(mIpConfiguration, flags);
@@ -2836,6 +3684,9 @@
         dest.writeInt(isLegacyPasspointConfig ? 1 : 0);
         dest.writeInt(ephemeral ? 1 : 0);
         dest.writeInt(trusted ? 1 : 0);
+        dest.writeInt(oemPaid ? 1 : 0);
+        dest.writeInt(oemPrivate ? 1 : 0);
+        dest.writeInt(carrierMerged ? 1 : 0);
         dest.writeInt(fromWifiNetworkSuggestion ? 1 : 0);
         dest.writeInt(fromWifiNetworkSpecifier ? 1 : 0);
         dest.writeInt(meteredHint ? 1 : 0);
@@ -2855,12 +3706,15 @@
         dest.writeInt(shared ? 1 : 0);
         dest.writeString(mPasspointManagementObjectTree);
         dest.writeInt(recentFailure.getAssociationStatus());
+        dest.writeLong(recentFailure.getLastUpdateTimeSinceBootMillis());
         dest.writeParcelable(mRandomizedMacAddress, flags);
         dest.writeInt(macRandomizationSetting);
         dest.writeInt(osu ? 1 : 0);
         dest.writeLong(randomizedMacExpirationTimeMs);
+        dest.writeLong(randomizedMacLastModifiedTimeMs);
         dest.writeInt(carrierId);
         dest.writeString(mPasspointUniqueId);
+        dest.writeInt(subscriptionId);
     }
 
     /** Implement the Parcelable interface {@hide} */
@@ -2890,6 +3744,7 @@
                 }
                 config.wepTxKeyIndex = in.readInt();
                 config.priority = in.readInt();
+                config.mDeletionPriority = in.readInt();
                 config.hiddenSSID = in.readInt() != 0;
                 config.requirePmf = in.readInt() != 0;
                 config.updateIdentifier = in.readString();
@@ -2902,6 +3757,11 @@
                 config.allowedGroupManagementCiphers = readBitSet(in);
                 config.allowedSuiteBCiphers   = readBitSet(in);
 
+                int numSecurityParams = in.readInt();
+                for (int i = 0; i < numSecurityParams; i++) {
+                    config.mSecurityParamsList.add(SecurityParams.createFromParcel(in));
+                }
+
                 config.enterpriseConfig = in.readParcelable(null);
                 config.setIpConfiguration(in.readParcelable(null));
                 config.dhcpServer = in.readString();
@@ -2910,6 +3770,9 @@
                 config.isLegacyPasspointConfig = in.readInt() != 0;
                 config.ephemeral = in.readInt() != 0;
                 config.trusted = in.readInt() != 0;
+                config.oemPaid = in.readInt() != 0;
+                config.oemPrivate = in.readInt() != 0;
+                config.carrierMerged = in.readInt() != 0;
                 config.fromWifiNetworkSuggestion =  in.readInt() != 0;
                 config.fromWifiNetworkSpecifier =  in.readInt() != 0;
                 config.meteredHint = in.readInt() != 0;
@@ -2928,13 +3791,15 @@
                 config.noInternetAccessExpected = in.readInt() != 0;
                 config.shared = in.readInt() != 0;
                 config.mPasspointManagementObjectTree = in.readString();
-                config.recentFailure.setAssociationStatus(in.readInt());
+                config.recentFailure.setAssociationStatus(in.readInt(), in.readLong());
                 config.mRandomizedMacAddress = in.readParcelable(null);
                 config.macRandomizationSetting = in.readInt();
                 config.osu = in.readInt() != 0;
                 config.randomizedMacExpirationTimeMs = in.readLong();
+                config.randomizedMacLastModifiedTimeMs = in.readLong();
                 config.carrierId = in.readInt();
                 config.mPasspointUniqueId = in.readString();
+                config.subscriptionId = in.readInt();
                 return config;
             }
 
@@ -2979,9 +3844,99 @@
      * @hide
      */
     public boolean needsPreSharedKey() {
-        return allowedKeyManagement.get(KeyMgmt.WPA_PSK)
-                || allowedKeyManagement.get(KeyMgmt.SAE)
-                || allowedKeyManagement.get(KeyMgmt.WAPI_PSK);
+        return mSecurityParamsList.stream()
+                .anyMatch(params -> params.isSecurityType(SECURITY_TYPE_PSK)
+                        || params.isSecurityType(SECURITY_TYPE_SAE)
+                        || params.isSecurityType(SECURITY_TYPE_WAPI_PSK));
+    }
+
+    /**
+     * Get a unique key which represent this Wi-Fi configuration profile. If two profiles are for
+     * the same Wi-Fi network, but from different providers (apps, carriers, or data subscriptions),
+     * they would have different keys.
+     * @return a unique key which represent this profile.
+     * @hide
+     */
+    @SystemApi
+    @NonNull public String getProfileKey() {
+        if (!SdkLevel.isAtLeastS()) {
+            return getKey();
+        }
+        if (mPasspointUniqueId != null) {
+            return mPasspointUniqueId;
+        }
+
+        String key = SSID + getDefaultSecurityType();
+        if (!shared) {
+            key += "-" + UserHandle.getUserHandleForUid(creatorUid).getIdentifier();
+        }
+        if (fromWifiNetworkSuggestion) {
+            key += "_" + creatorName + "-" + carrierId + "-" + subscriptionId;
+        }
+
+        return key;
+    }
+
+    /**
+     * Get the default security type string.
+     * @hide
+     */
+    public String getDefaultSecurityType() {
+        String key;
+        if (allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
+            key = KeyMgmt.strings[KeyMgmt.WPA_PSK];
+        } else if (allowedKeyManagement.get(KeyMgmt.WPA_EAP)
+                || allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
+            if (!requirePmf) {
+                key = KeyMgmt.strings[KeyMgmt.WPA_EAP];
+            } else {
+                key = "WPA3_EAP";
+            }
+        } else if (wepTxKeyIndex >= 0 && wepTxKeyIndex < wepKeys.length
+                && wepKeys[wepTxKeyIndex] != null) {
+            key = "WEP";
+        } else if (allowedKeyManagement.get(KeyMgmt.OWE)) {
+            key = KeyMgmt.strings[KeyMgmt.OWE];
+        } else if (allowedKeyManagement.get(KeyMgmt.SAE)) {
+            key = KeyMgmt.strings[KeyMgmt.SAE];
+        } else if (allowedKeyManagement.get(KeyMgmt.SUITE_B_192)) {
+            key = KeyMgmt.strings[KeyMgmt.SUITE_B_192];
+        } else if (allowedKeyManagement.get(KeyMgmt.WAPI_PSK)) {
+            key = KeyMgmt.strings[KeyMgmt.WAPI_PSK];
+        } else if (allowedKeyManagement.get(KeyMgmt.WAPI_CERT)) {
+            key = KeyMgmt.strings[KeyMgmt.WAPI_CERT];
+        } else if (allowedKeyManagement.get(KeyMgmt.OSEN)) {
+            key = KeyMgmt.strings[KeyMgmt.OSEN];
+        } else {
+            key = KeyMgmt.strings[KeyMgmt.NONE];
+        }
+        return key;
+    }
+
+    /**
+     * Get the security type name.
+     *
+     * @param securityType One of the following security types:
+     * {@link #SECURITY_TYPE_OPEN},
+     * {@link #SECURITY_TYPE_WEP},
+     * {@link #SECURITY_TYPE_PSK},
+     * {@link #SECURITY_TYPE_EAP},
+     * {@link #SECURITY_TYPE_SAE},
+     * {@link #SECURITY_TYPE_OWE},
+     * {@link #SECURITY_TYPE_WAPI_PSK},
+     * {@link #SECURITY_TYPE_WAPI_CERT},
+     * {@link #SECURITY_TYPE_EAP_WPA3_ENTERPRISE},
+     * {@link #SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT},
+     * {@link #SECURITY_TYPE_PASSPOINT_R1_R2},
+     * or {@link #SECURITY_TYPE_PASSPOINT_R3}.
+     * @return the name of the given type.
+     * @hide
+     */
+    public static String getSecurityTypeName(@SecurityType int securityType) {
+        if (securityType < SECURITY_TYPE_OPEN || SECURITY_TYPE_NUM < securityType) {
+            return "unknown";
+        }
+        return SECURITY_TYPE_NAMES[securityType];
     }
 
 }
diff --git a/framework/java/android/net/wifi/WifiConnectedSessionInfo.java b/framework/java/android/net/wifi/WifiConnectedSessionInfo.java
new file mode 100644
index 0000000..48377c7
--- /dev/null
+++ b/framework/java/android/net/wifi/WifiConnectedSessionInfo.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class representing the session information of a connected Wi-Fi network for external Wi-Fi
+ * scorer to identify the Wi-Fi network.
+ *
+ * @hide
+ */
+@SystemApi
+public final class WifiConnectedSessionInfo implements Parcelable {
+    private final int mSessionId;
+    private final boolean mIsUserSelected;
+
+    /** Create a new WifiConnectedSessionInfo object */
+    private WifiConnectedSessionInfo(int sessionId, boolean isUserSelected) {
+        mSessionId = sessionId;
+        mIsUserSelected = isUserSelected;
+    }
+
+    /** Builder for WifiConnectedSessionInfo */
+    public static final class Builder {
+        private final int mSessionId;
+        private boolean mIsUserSelected = false;
+
+        /** Create a new builder */
+        public Builder(int sessionId) {
+            mSessionId = sessionId;
+        }
+
+        /** Set whether this network is user selected */
+        @NonNull public Builder setUserSelected(boolean isUserSelected) {
+            mIsUserSelected = isUserSelected;
+            return this;
+        }
+
+        /** Build the WifiConnectedSessionInfo object represented by this builder */
+        @NonNull public WifiConnectedSessionInfo build() {
+            return new WifiConnectedSessionInfo(mSessionId, mIsUserSelected);
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mSessionId);
+        dest.writeBoolean(mIsUserSelected);
+    }
+
+    /** Implement the Parcelable interface */
+    public static final @NonNull Creator<WifiConnectedSessionInfo> CREATOR =
+            new Creator<WifiConnectedSessionInfo>() {
+        public WifiConnectedSessionInfo createFromParcel(Parcel in) {
+            return new WifiConnectedSessionInfo(in.readInt(), in.readBoolean());
+        }
+
+        public WifiConnectedSessionInfo[] newArray(int size) {
+            return new WifiConnectedSessionInfo[size];
+        }
+    };
+
+    /** The ID to indicate current Wi-Fi network connection */
+    public int getSessionId() {
+        return mSessionId;
+    }
+
+    /** Indicate whether current Wi-Fi network is selected by the user */
+    public boolean isUserSelected() {
+        return mIsUserSelected;
+    }
+}
diff --git a/framework/java/android/net/wifi/WifiEnterpriseConfig.java b/framework/java/android/net/wifi/WifiEnterpriseConfig.java
index 90edc45..49831bc 100644
--- a/framework/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/framework/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -20,11 +20,16 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.RequiresApi;
+
+import com.android.modules.utils.build.SdkLevel;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.nio.charset.StandardCharsets;
@@ -98,6 +103,8 @@
     public static final String EAP_ERP             = "eap_erp";
     /** @hide */
     public static final String OCSP                = "ocsp";
+    /** @hide */
+    public static final String DECORATED_IDENTITY_PREFIX_KEY = "decorated_username_prefix";
 
     /**
      * String representing the keystore OpenSSL ENGINE's ID.
@@ -194,7 +201,9 @@
 
     /**
      * Require valid OCSP stapling response for all not-trusted certificates in the server
-     * certificate chain
+     * certificate chain.
+     * @apiNote This option is not supported by most SSL libraries and should not be used.
+     * Specifying this option will most likely cause connection failures.
      * @hide
      */
     @SystemApi
@@ -247,6 +256,7 @@
     private int mPhase2Method = Phase2.NONE;
     private boolean mIsAppInstalledDeviceKeyAndCert = false;
     private boolean mIsAppInstalledCaCert = false;
+    private String mKeyChainAlias;
 
     private static final String TAG = "WifiEnterpriseConfig";
 
@@ -287,6 +297,7 @@
         } else {
             mClientCertificateChain = null;
         }
+        mKeyChainAlias = source.mKeyChainAlias;
         mEapMethod = source.mEapMethod;
         mPhase2Method = source.mPhase2Method;
         mIsAppInstalledDeviceKeyAndCert = source.mIsAppInstalledDeviceKeyAndCert;
@@ -335,6 +346,7 @@
         ParcelUtil.writeCertificates(dest, mCaCerts);
         ParcelUtil.writePrivateKey(dest, mClientPrivateKey);
         ParcelUtil.writeCertificates(dest, mClientCertificateChain);
+        dest.writeString(mKeyChainAlias);
         dest.writeBoolean(mIsAppInstalledDeviceKeyAndCert);
         dest.writeBoolean(mIsAppInstalledCaCert);
         dest.writeInt(mOcsp);
@@ -357,6 +369,7 @@
                     enterpriseConfig.mCaCerts = ParcelUtil.readCertificates(in);
                     enterpriseConfig.mClientPrivateKey = ParcelUtil.readPrivateKey(in);
                     enterpriseConfig.mClientCertificateChain = ParcelUtil.readCertificates(in);
+                    enterpriseConfig.mKeyChainAlias = in.readString();
                     enterpriseConfig.mIsAppInstalledDeviceKeyAndCert = in.readBoolean();
                     enterpriseConfig.mIsAppInstalledCaCert = in.readBoolean();
                     enterpriseConfig.mOcsp = in.readInt();
@@ -531,8 +544,7 @@
 
     /**
      * Set the EAP authentication method.
-     * @param  eapMethod is one {@link Eap#PEAP}, {@link Eap#TLS}, {@link Eap#TTLS} or
-     *                   {@link Eap#PWD}
+     * @param  eapMethod is one of {@link Eap}, except for {@link Eap#NONE}
      * @throws IllegalArgumentException on an invalid eap method
      */
     public void setEapMethod(int eapMethod) {
@@ -571,11 +583,8 @@
     /**
      * Set Phase 2 authentication method. Sets the inner authentication method to be used in
      * phase 2 after setting up a secure channel
-     * @param phase2Method is the inner authentication method and can be one of {@link Phase2#NONE},
-     *                     {@link Phase2#PAP}, {@link Phase2#MSCHAP}, {@link Phase2#MSCHAPV2},
-     *                     {@link Phase2#GTC}
+     * @param phase2Method is the inner authentication method and can be one of {@link Phase2}
      * @throws IllegalArgumentException on an invalid phase2 method
-     *
      */
     public void setPhase2Method(int phase2Method) {
         switch (phase2Method) {
@@ -1010,6 +1019,41 @@
     }
 
     /**
+     * Specify a key pair via KeyChain alias for client authentication.
+     *
+     * The alias should refer to a key pair in KeyChain that is allowed for WiFi authentication.
+     *
+     * @param alias key pair alias
+     * @see android.app.admin.DevicePolicyManager#grantKeyPairToWifiAuth(String)
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    public void setClientKeyPairAlias(@NonNull String alias) {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        mKeyChainAlias = alias;
+    }
+
+    /**
+     * Get KeyChain alias to use for client authentication.
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    public @Nullable String getClientKeyPairAlias() {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        return mKeyChainAlias;
+    }
+
+    /**
+     * Get KeyChain alias to use for client authentication without SDK check.
+     * @hide
+     */
+    public @Nullable String getClientKeyPairAliasInternal() {
+        return mKeyChainAlias;
+    }
+
+    /**
      * Get client certificate
      *
      * @return X.509 client certificate
@@ -1335,6 +1379,16 @@
     }
 
     /**
+     * Initialize the value of the app installed device key and cert flag.
+     *
+     * @param isAppInstalledDeviceKeyAndCert true or false
+     * @hide
+     */
+    public void initIsAppInstalledDeviceKeyAndCert(boolean isAppInstalledDeviceKeyAndCert) {
+        mIsAppInstalledDeviceKeyAndCert = isAppInstalledDeviceKeyAndCert;
+    }
+
+    /**
      * Check if CA certificate was installed by an app, or manually (not by an app). If true,
      * CA certificate will be removed from key storage when this network is removed. If not,
      * then certificates and keys remain persistent until the user manually removes them.
@@ -1348,6 +1402,16 @@
     }
 
     /**
+     * Initialize the value of the app installed root CA cert flag.
+     *
+     * @param isAppInstalledCaCert true or false
+     * @hide
+     */
+    public void initIsAppInstalledCaCert(boolean isAppInstalledCaCert) {
+        mIsAppInstalledCaCert = isAppInstalledCaCert;
+    }
+
+    /**
      * Set the OCSP type.
      * @param ocsp is one of {@link ##OCSP_NONE}, {@link #OCSP_REQUEST_CERT_STATUS},
      *                   {@link #OCSP_REQUIRE_CERT_STATUS} or
@@ -1416,34 +1480,56 @@
     }
 
     /**
-     * Method determines whether the Enterprise configuration is insecure. An insecure
-     * configuration is one where EAP method requires a CA certification, i.e. PEAP, TLS, or
-     * TTLS, and any of the following conditions are met:
-     * - Both certificate and CA path are not configured.
-     * - Both alternative subject match and domain suffix match are not set.
-     *
-     * Note: this method does not exhaustively check security of the configuration - i.e. a return
-     * value of {@code false} is not a guarantee that the configuration is secure.
+     * Determines whether an Enterprise configuration's EAP method requires a Root CA certification
+     * to validate the authentication server i.e. PEAP, TLS, or TTLS.
+     * @return True if configuration requires a CA certification, false otherwise.
+     */
+    public boolean isEapMethodServerCertUsed() {
+        return mEapMethod == Eap.PEAP || mEapMethod == Eap.TLS || mEapMethod == Eap.TTLS;
+    }
+    /**
+     * Determines whether an Enterprise configuration enables server certificate validation.
+     * <p>
+     * The caller can determine, along with {@link #isEapMethodServerCertUsed()}, if an
+     * Enterprise configuration enables server certificate validation, which is a mandatory
+     * requirement for networks that use TLS based EAP methods. A configuration that does not
+     * enable server certificate validation will be ignored and will not be considered for
+     * network selection. A network suggestion with such a configuration will cause an
+     * IllegalArgumentException to be thrown when suggested.
+     * Server validation is achieved by the following:
+     * - Either certificate or CA path is configured.
+     * - Either alternative subject match or domain suffix match is set.
+     * @return True for server certificate validation is enabled, false otherwise.
+     * @throws IllegalStateException on configuration which doesn't use server certificate.
+     * @see #isEapMethodServerCertUsed()
+     */
+    public boolean isServerCertValidationEnabled() {
+        if (!isEapMethodServerCertUsed()) {
+            throw new IllegalStateException("Configuration doesn't use server certificates for "
+                    + "authentication");
+        }
+        return isMandatoryParameterSetForServerCertValidation();
+    }
+
+    /**
+     * Helper method to check if mandatory parameter for server cert validation is set.
      * @hide
      */
-    public boolean isInsecure() {
-        if (mEapMethod != Eap.PEAP && mEapMethod != Eap.TLS && mEapMethod != Eap.TTLS) {
-            return false;
-        }
+    public boolean isMandatoryParameterSetForServerCertValidation() {
         if (TextUtils.isEmpty(getAltSubjectMatch())
                 && TextUtils.isEmpty(getDomainSuffixMatch())) {
-            // Both subject and domain match are not set, it's insecure.
-            return true;
+            // Both subject and domain match are not set, validation is not enabled.
+            return false;
         }
         if (mIsAppInstalledCaCert) {
-            // CA certificate is installed by App, it's secure.
-            return false;
+            // CA certificate is installed by App, validation is enabled.
+            return true;
         }
         if (getCaCertificateAliases() != null) {
-            // CA certificate alias from keyStore is set, it's secure.
-            return false;
+            // CA certificate alias from keyStore is set, validation is enabled.
+            return true;
         }
-        return TextUtils.isEmpty(getCaPath());
+        return !TextUtils.isEmpty(getCaPath());
     }
 
     /**
@@ -1491,4 +1577,42 @@
         }
         return false;
     }
+
+    /**
+     * Set a prefix for a decorated identity as per RFC 7542.
+     * This prefix must contain a list of realms (could be a list of 1) delimited by a '!'
+     * character. e.g. homerealm.example.org! or proxyrealm.example.net!homerealm.example.org!
+     * A prefix of "homerealm.example.org!" will generate a decorated identity that
+     * looks like: homerealm.example.org!user@otherrealm.example.net
+     * Calling with a null parameter will clear the decorated prefix.
+     * Note: Caller must verify that the device supports this feature by calling
+     * {@link WifiManager#isDecoratedIdentitySupported()}
+     *
+     * @param decoratedIdentityPrefix The prefix to add to the outer/anonymous identity
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    public void setDecoratedIdentityPrefix(@Nullable String decoratedIdentityPrefix) {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        if (!TextUtils.isEmpty(decoratedIdentityPrefix) && !decoratedIdentityPrefix.endsWith("!")) {
+            throw new IllegalArgumentException(
+                    "Decorated identity prefix must be delimited by '!'");
+        }
+        setFieldValue(DECORATED_IDENTITY_PREFIX_KEY, decoratedIdentityPrefix);
+    }
+
+    /**
+     * Get the decorated identity prefix.
+     *
+     * @return The decorated identity prefix
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    public @Nullable String getDecoratedIdentityPrefix() {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        final String decoratedId = getFieldValue(DECORATED_IDENTITY_PREFIX_KEY);
+        return decoratedId.isEmpty() ? null : decoratedId;
+    }
 }
diff --git a/framework/java/android/net/wifi/WifiInfo.java b/framework/java/android/net/wifi/WifiInfo.java
index d1e8bf0..8b99294 100644
--- a/framework/java/android/net/wifi/WifiInfo.java
+++ b/framework/java/android/net/wifi/WifiInfo.java
@@ -19,27 +19,39 @@
 import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
 
 import android.Manifest;
+import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.LinkProperties;
+import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo.DetailedState;
 import android.net.TransportInfo;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.telephony.SubscriptionManager;
 import android.text.TextUtils;
 
+import androidx.annotation.RequiresApi;
+
 import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.Inet4AddressUtils;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.net.Inet4Address;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
+import java.util.ArrayList;
 import java.util.EnumMap;
+import java.util.List;
 import java.util.Locale;
 import java.util.Objects;
 
@@ -54,6 +66,8 @@
  * {@link #getNetworkId()} will return {@code -1}.
  * {@link #getPasspointFqdn()} will return null.
  * {@link #getPasspointProviderFriendlyName()} will return null.
+ * {@link #getInformationElements()} will return null.
+ * {@link #getMacAddress()} will return {@code "02:00:00:00:00:00"}.
  */
 public class WifiInfo implements TransportInfo, Parcelable {
     private static final String TAG = "WifiInfo";
@@ -90,14 +104,13 @@
         stateMap.put(SupplicantState.INVALID, DetailedState.FAILED);
     }
 
-    private final long mRedactions;
-
     private SupplicantState mSupplicantState;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private String mBSSID;
     @UnsupportedAppUsage
     private WifiSsid mWifiSsid;
     private int mNetworkId;
+    private int mSecurityType;
 
     /**
      * Used to indicate that the RSSI is invalid, for example if no RSSI measurements are available
@@ -113,6 +126,73 @@
     /** @hide **/
     public static final int MAX_RSSI = 200;
 
+    /** Unknown security type. */
+    public static final int SECURITY_TYPE_UNKNOWN = -1;
+    /** Security type for an open network. */
+    public static final int SECURITY_TYPE_OPEN = 0;
+    /** Security type for a WEP network. */
+    public static final int SECURITY_TYPE_WEP = 1;
+    /** Security type for a PSK network. */
+    public static final int SECURITY_TYPE_PSK = 2;
+    /** Security type for an EAP network. */
+    public static final int SECURITY_TYPE_EAP = 3;
+    /** Security type for an SAE network. */
+    public static final int SECURITY_TYPE_SAE = 4;
+    /** Security type for a WPA3-Enterprise in 192-bit security network. */
+    public static final int SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT = 5;
+    /** Security type for an OWE network. */
+    public static final int SECURITY_TYPE_OWE = 6;
+    /** Security type for a WAPI PSK network. */
+    public static final int SECURITY_TYPE_WAPI_PSK = 7;
+    /** Security type for a WAPI Certificate network. */
+    public static final int SECURITY_TYPE_WAPI_CERT = 8;
+    /** Security type for a WPA3-Enterprise network. */
+    public static final int SECURITY_TYPE_EAP_WPA3_ENTERPRISE = 9;
+    /** Security type for an OSEN network. */
+    public static final int SECURITY_TYPE_OSEN = 10;
+    /** Security type for a Passpoint R1/R2 network, where TKIP and WEP are not allowed. */
+    public static final int SECURITY_TYPE_PASSPOINT_R1_R2 = 11;
+    /**
+     * Security type for a Passpoint R3 network, where TKIP and WEP are not allowed,
+     * and PMF must be set to Required.
+     */
+    public static final int SECURITY_TYPE_PASSPOINT_R3 = 12;
+
+    /**
+     * Security type of current connection.
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "SECURITY_TYPE_" }, value = {
+            SECURITY_TYPE_UNKNOWN,
+            SECURITY_TYPE_OPEN,
+            SECURITY_TYPE_WEP,
+            SECURITY_TYPE_PSK,
+            SECURITY_TYPE_EAP,
+            SECURITY_TYPE_SAE,
+            SECURITY_TYPE_OWE,
+            SECURITY_TYPE_WAPI_PSK,
+            SECURITY_TYPE_WAPI_CERT,
+            SECURITY_TYPE_EAP_WPA3_ENTERPRISE,
+            SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT,
+            SECURITY_TYPE_PASSPOINT_R1_R2,
+            SECURITY_TYPE_PASSPOINT_R3,
+    })
+    public @interface SecurityType {}
+
+    /** @see #isPrimary() - No permission to access the field.  */
+    private static final int IS_PRIMARY_NO_PERMISSION = -1;
+    /** @see #isPrimary() - false */
+    private static final int IS_PRIMARY_FALSE = 0;
+    /** @see #isPrimary() - true */
+    private static final int IS_PRIMARY_TRUE = 1;
+    /** Tri state to store {@link #isPrimary()} field. */
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "IS_PRIMARY_" }, value = {
+            IS_PRIMARY_NO_PERMISSION, IS_PRIMARY_FALSE, IS_PRIMARY_TRUE
+    })
+    public @interface IsPrimaryValues {}
 
     /**
      * Received Signal Strength Indicator
@@ -177,6 +257,21 @@
     private boolean mTrusted;
 
     /**
+     * Whether the network is oem paid or not.
+     */
+    private boolean mOemPaid;
+
+    /**
+     * Whether the network is oem private or not.
+     */
+    private boolean mOemPrivate;
+
+    /**
+     * Whether the network is a carrier merged network.
+     */
+    private boolean mCarrierMerged;
+
+    /**
      * OSU (Online Sign Up) AP for Passpoint R2.
      */
     private boolean mOsuAp;
@@ -198,6 +293,11 @@
     private String mRequestingPackageName;
 
     /**
+     * Identify which Telephony subscription provides this network.
+     */
+    private int mSubscriptionId;
+
+    /**
      * Running total count of lost (not ACKed) transmitted unicast data packets.
      * @hide
      */
@@ -313,10 +413,21 @@
      */
     private String mPasspointUniqueId;
 
+    /**
+     * information elements found in the beacon of the connected bssid.
+     */
+    @Nullable
+    private List<ScanResult.InformationElement> mInformationElements;
+
+    /**
+     * @see #isPrimary()
+     * The field is stored as an int since is a tristate internally -  true, false, no permission.
+     */
+    private @IsPrimaryValues int mIsPrimary;
+
     /** @hide */
     @UnsupportedAppUsage
     public WifiInfo() {
-        mRedactions = NetworkCapabilities.REDACT_ALL;
         mWifiSsid = null;
         mBSSID = null;
         mNetworkId = -1;
@@ -324,16 +435,13 @@
         mRssi = INVALID_RSSI;
         mLinkSpeed = LINK_SPEED_UNKNOWN;
         mFrequency = -1;
+        mSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+        mSecurityType = -1;
+        mIsPrimary = IS_PRIMARY_FALSE;
     }
 
     /** @hide */
     public void reset() {
-        if (mRedactions != NetworkCapabilities.REDACT_ALL) {
-            // To ensure that we don't accidentally set this bit on the master copy of WifiInfo
-            // (reset is only invoked in the master copy)
-            throw new UnsupportedOperationException(
-                    "Cannot clear WifiInfo when mRedactions is set");
-        }
         setInetAddress(null);
         setBSSID(null);
         setSSID(null);
@@ -347,11 +455,18 @@
         setFrequency(-1);
         setMeteredHint(false);
         setEphemeral(false);
+        setTrusted(false);
+        setOemPaid(false);
+        setOemPrivate(false);
+        setCarrierMerged(false);
         setOsuAp(false);
         setRequestingPackageName(null);
         setFQDN(null);
         setProviderFriendlyName(null);
         setPasspointUniqueId(null);
+        setSubscriptionId(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        setInformationElements(null);
+        setIsPrimary(false);
         txBad = 0;
         txSuccess = 0;
         rxSuccess = 0;
@@ -361,6 +476,7 @@
         mSuccessfulRxPacketsPerSecond = 0;
         mTxRetriedTxPacketsPerSecond = 0;
         score = 0;
+        mSecurityType = -1;
     }
 
     /**
@@ -368,7 +484,7 @@
      * @hide
      */
     public WifiInfo(WifiInfo source) {
-        this(source, NetworkCapabilities.REDACT_ALL);
+        this(source, NetworkCapabilities.REDACT_NONE);
     }
 
     /**
@@ -376,27 +492,37 @@
      * @hide
      */
     private WifiInfo(WifiInfo source, long redactions) {
-        mRedactions = redactions;
         if (source != null) {
             mSupplicantState = source.mSupplicantState;
-            mBSSID = source.mBSSID;
-            mWifiSsid = source.mWifiSsid;
-            mNetworkId = source.mNetworkId;
+            mBSSID = shouldRedactLocationSensitiveFields(redactions)
+                    ? DEFAULT_MAC_ADDRESS : source.mBSSID;
+            mWifiSsid = shouldRedactLocationSensitiveFields(redactions)
+                    ? WifiSsid.createFromHex(null) : source.mWifiSsid;
+            mNetworkId = shouldRedactLocationSensitiveFields(redactions)
+                    ? INVALID_NETWORK_ID : source.mNetworkId;
             mRssi = source.mRssi;
             mLinkSpeed = source.mLinkSpeed;
             mTxLinkSpeed = source.mTxLinkSpeed;
             mRxLinkSpeed = source.mRxLinkSpeed;
             mFrequency = source.mFrequency;
             mIpAddress = source.mIpAddress;
-            mMacAddress = source.mMacAddress;
+            mMacAddress = (shouldRedactLocalMacAddressFields(redactions)
+                    || shouldRedactLocationSensitiveFields(redactions))
+                            ? DEFAULT_MAC_ADDRESS : source.mMacAddress;
             mMeteredHint = source.mMeteredHint;
             mEphemeral = source.mEphemeral;
             mTrusted = source.mTrusted;
+            mOemPaid = source.mOemPaid;
+            mOemPrivate = source.mOemPrivate;
+            mCarrierMerged = source.mCarrierMerged;
             mRequestingPackageName =
                     source.mRequestingPackageName;
             mOsuAp = source.mOsuAp;
-            mFqdn = source.mFqdn;
-            mProviderFriendlyName = source.mProviderFriendlyName;
+            mFqdn = shouldRedactLocationSensitiveFields(redactions)
+                    ? null : source.mFqdn;
+            mProviderFriendlyName = shouldRedactLocationSensitiveFields(redactions)
+                    ? null : source.mProviderFriendlyName;
+            mSubscriptionId = source.mSubscriptionId;
             txBad = source.txBad;
             txRetries = source.txRetries;
             txSuccess = source.txSuccess;
@@ -409,7 +535,15 @@
             mWifiStandard = source.mWifiStandard;
             mMaxSupportedTxLinkSpeed = source.mMaxSupportedTxLinkSpeed;
             mMaxSupportedRxLinkSpeed = source.mMaxSupportedRxLinkSpeed;
-            mPasspointUniqueId = source.mPasspointUniqueId;
+            mPasspointUniqueId = shouldRedactLocationSensitiveFields(redactions)
+                    ? null : source.mPasspointUniqueId;
+            if (source.mInformationElements != null
+                    && !shouldRedactLocationSensitiveFields(redactions)) {
+                mInformationElements = new ArrayList<>(source.mInformationElements);
+            }
+            mIsPrimary = shouldRedactNetworkSettingsFields(redactions)
+                    ? IS_PRIMARY_NO_PERMISSION : source.mIsPrimary;
+            mSecurityType = source.mSecurityType;
         }
     }
 
@@ -458,6 +592,16 @@
         }
 
         /**
+         * Set the current security type
+         * @see WifiInfo#getCurrentSecurityType()
+         */
+        @NonNull
+        public Builder setCurrentSecurityType(@WifiConfiguration.SecurityType int securityType) {
+            mWifiInfo.setCurrentSecurityType(securityType);
+            return this;
+        }
+
+        /**
          * Build a WifiInfo object.
          */
         @NonNull
@@ -759,12 +903,77 @@
         mTrusted = trusted;
     }
 
-    /** {@hide} */
+    /**
+     * Returns true if the current Wifi network is a trusted network, false otherwise.
+     * @see WifiNetworkSuggestion.Builder#setUntrusted(boolean).
+     * {@hide}
+     */
+    @SystemApi
     public boolean isTrusted() {
         return mTrusted;
     }
 
     /** {@hide} */
+    public void setOemPaid(boolean oemPaid) {
+        mOemPaid = oemPaid;
+    }
+
+    /**
+     * Returns true if the current Wifi network is an oem paid network, false otherwise.
+     * @see WifiNetworkSuggestion.Builder#setOemPaid(boolean).
+     * {@hide}
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    @SystemApi
+    public boolean isOemPaid() {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        return mOemPaid;
+    }
+
+    /** {@hide} */
+    public void setOemPrivate(boolean oemPrivate) {
+        mOemPrivate = oemPrivate;
+    }
+
+    /**
+     * Returns true if the current Wifi network is an oem private network, false otherwise.
+     * @see WifiNetworkSuggestion.Builder#setOemPrivate(boolean).
+     * {@hide}
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    @SystemApi
+    public boolean isOemPrivate() {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        return mOemPrivate;
+    }
+
+    /**
+     * {@hide}
+     */
+    public void setCarrierMerged(boolean carrierMerged) {
+        mCarrierMerged = carrierMerged;
+    }
+
+    /**
+     * Returns true if the current Wifi network is a carrier merged network, false otherwise.
+     * @see WifiNetworkSuggestion.Builder#setCarrierMerged(boolean).
+     * {@hide}
+     */
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.S)
+    public boolean isCarrierMerged() {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        return mCarrierMerged;
+    }
+
+
+    /** {@hide} */
     public void setOsuAp(boolean osuAp) {
         mOsuAp = osuAp;
     }
@@ -832,6 +1041,27 @@
         return mRequestingPackageName;
     }
 
+    /** {@hide} */
+    public void setSubscriptionId(int subId) {
+        mSubscriptionId = subId;
+    }
+
+    /**
+     * If this network is provisioned by a carrier, returns subscription Id corresponding to the
+     * associated SIM on the device. If this network is not provisioned by a carrier, returns
+     * {@link android.telephony.SubscriptionManager#INVALID_SUBSCRIPTION_ID}
+     *
+     * @see WifiNetworkSuggestion.Builder#setSubscriptionId(int)
+     * @see android.telephony.SubscriptionInfo#getSubscriptionId()
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    public int getSubscriptionId() {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        return mSubscriptionId;
+    }
+
 
     /** @hide */
     @UnsupportedAppUsage
@@ -873,6 +1103,12 @@
         mIpAddress = address;
     }
 
+    /**
+     * @deprecated Use the methods on {@link android.net.LinkProperties} which can be obtained
+     * either via {@link NetworkCallback#onLinkPropertiesChanged(Network, LinkProperties)} or
+     * {@link ConnectivityManager#getLinkProperties(Network)}.
+     */
+    @Deprecated
     public int getIpAddress() {
         int result = 0;
         if (mIpAddress instanceof Inet4Address) {
@@ -953,6 +1189,7 @@
         sb.append("SSID: ").append(getSSID())
                 .append(", BSSID: ").append(mBSSID == null ? none : mBSSID)
                 .append(", MAC: ").append(mMacAddress == null ? none : mMacAddress)
+                .append(", Security type: ").append(mSecurityType)
                 .append(", Supplicant state: ")
                 .append(mSupplicantState == null ? none : mSupplicantState)
                 .append(", Wi-Fi standard: ").append(mWifiStandard)
@@ -967,7 +1204,10 @@
                 .append(", Frequency: ").append(mFrequency).append(FREQUENCY_UNITS)
                 .append(", Net ID: ").append(mNetworkId)
                 .append(", Metered hint: ").append(mMeteredHint)
-                .append(", score: ").append(Integer.toString(score));
+                .append(", score: ").append(Integer.toString(score))
+                .append(", CarrierMerged: ").append(mCarrierMerged)
+                .append(", SubscriptionId: ").append(mSubscriptionId)
+                .append(", IsPrimary: ").append(mIsPrimary);
         return sb.toString();
     }
 
@@ -976,19 +1216,21 @@
         return 0;
     }
 
-    private boolean shouldParcelLocationSensitiveFields() {
-        return (mRedactions & NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION) == 0;
+    private boolean shouldRedactLocationSensitiveFields(long redactions) {
+        return (redactions & NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION) != 0;
     }
 
-    private boolean shouldParcelLocalMacAddressFields() {
-        return (mRedactions & NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS) == 0;
+    private boolean shouldRedactLocalMacAddressFields(long redactions) {
+        return (redactions & NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS) != 0;
+    }
+
+    private boolean shouldRedactNetworkSettingsFields(long redactions) {
+        return (redactions & NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS) != 0;
     }
 
     /** Implement the Parcelable interface {@hide} */
     public void writeToParcel(Parcel dest, int flags) {
-        // TODO (b/162602799): Should we proactively redact instance fields in memory instead of
-        // current approach of redacting while parceling.
-        dest.writeInt(shouldParcelLocationSensitiveFields() ? mNetworkId : INVALID_NETWORK_ID);
+        dest.writeInt(mNetworkId);
         dest.writeInt(mRssi);
         dest.writeInt(mLinkSpeed);
         dest.writeInt(mTxLinkSpeed);
@@ -1002,23 +1244,18 @@
         }
         if (mWifiSsid != null) {
             dest.writeInt(1);
-            final WifiSsid ssid;
-            if (shouldParcelLocationSensitiveFields()) {
-                ssid = mWifiSsid;
-            } else {
-                ssid = WifiSsid.createFromHex(null);
-            }
-            ssid.writeToParcel(dest, flags);
+            mWifiSsid.writeToParcel(dest, flags);
         } else {
             dest.writeInt(0);
         }
-        dest.writeString(shouldParcelLocationSensitiveFields() ? mBSSID : DEFAULT_MAC_ADDRESS);
-        dest.writeString(
-                shouldParcelLocalMacAddressFields() && shouldParcelLocationSensitiveFields()
-                        ? mMacAddress : DEFAULT_MAC_ADDRESS);
+        dest.writeString(mBSSID);
+        dest.writeString(mMacAddress);
         dest.writeInt(mMeteredHint ? 1 : 0);
         dest.writeInt(mEphemeral ? 1 : 0);
         dest.writeInt(mTrusted ? 1 : 0);
+        dest.writeInt(mOemPaid ? 1 : 0);
+        dest.writeInt(mOemPrivate ? 1 : 0);
+        dest.writeInt(mCarrierMerged ? 1 : 0);
         dest.writeInt(score);
         dest.writeLong(txSuccess);
         dest.writeDouble(mSuccessfulTxPacketsPerSecond);
@@ -1031,12 +1268,18 @@
         mSupplicantState.writeToParcel(dest, flags);
         dest.writeInt(mOsuAp ? 1 : 0);
         dest.writeString(mRequestingPackageName);
-        dest.writeString(shouldParcelLocationSensitiveFields() ? mFqdn : null);
-        dest.writeString(shouldParcelLocationSensitiveFields() ? mProviderFriendlyName : null);
+        dest.writeString(mFqdn);
+        dest.writeString(mProviderFriendlyName);
         dest.writeInt(mWifiStandard);
         dest.writeInt(mMaxSupportedTxLinkSpeed);
         dest.writeInt(mMaxSupportedRxLinkSpeed);
-        dest.writeString(shouldParcelLocationSensitiveFields() ? mPasspointUniqueId : null);
+        dest.writeString(mPasspointUniqueId);
+        dest.writeInt(mSubscriptionId);
+        dest.writeTypedList(mInformationElements);
+        if (SdkLevel.isAtLeastS()) {
+            dest.writeInt(mIsPrimary);
+        }
+        dest.writeInt(mSecurityType);
     }
 
     /** Implement the Parcelable interface {@hide} */
@@ -1064,6 +1307,9 @@
                 info.mMeteredHint = in.readInt() != 0;
                 info.mEphemeral = in.readInt() != 0;
                 info.mTrusted = in.readInt() != 0;
+                info.mOemPaid = in.readInt() != 0;
+                info.mOemPrivate = in.readInt() != 0;
+                info.mCarrierMerged = in.readInt() != 0;
                 info.score = in.readInt();
                 info.txSuccess = in.readLong();
                 info.mSuccessfulTxPacketsPerSecond = in.readDouble();
@@ -1082,6 +1328,13 @@
                 info.mMaxSupportedTxLinkSpeed = in.readInt();
                 info.mMaxSupportedRxLinkSpeed = in.readInt();
                 info.mPasspointUniqueId = in.readString();
+                info.mSubscriptionId = in.readInt();
+                info.mInformationElements = in.createTypedArrayList(
+                        ScanResult.InformationElement.CREATOR);
+                if (SdkLevel.isAtLeastS()) {
+                    info.mIsPrimary = in.readInt();
+                }
+                info.mSecurityType = in.readInt();
                 return info;
             }
 
@@ -1110,6 +1363,71 @@
         return mPasspointUniqueId;
     }
 
+    /**
+     * Set the information elements found in the becaon of the connected bssid.
+     * @hide
+     */
+    public void setInformationElements(@Nullable List<ScanResult.InformationElement> infoElements) {
+        if (infoElements == null) {
+            mInformationElements = null;
+            return;
+        }
+        mInformationElements = new ArrayList<>(infoElements);
+    }
+
+    /**
+     * Get all information elements found in the beacon of the connected bssid.
+     * <p>
+     * The information elements will be {@code null} if there is no network currently connected or
+     * if the caller has insufficient permissions to access the info elements.
+     * </p>
+     *
+     * @return List of information elements {@link ScanResult.InformationElement} or null.
+     */
+    @Nullable
+    @SuppressWarnings("NullableCollection")
+    public List<ScanResult.InformationElement> getInformationElements() {
+        if (mInformationElements == null) return null;
+        return new ArrayList<>(mInformationElements);
+    }
+
+    /**
+     * @see #isPrimary()
+     * @hide
+     */
+    public void setIsPrimary(boolean isPrimary) {
+        mIsPrimary = isPrimary ? IS_PRIMARY_TRUE : IS_PRIMARY_FALSE;
+    }
+
+    /**
+     * Returns whether this is the primary wifi connection or not.
+     *
+     * Wifi service considers this connection to be the best among all Wifi connections, and this
+     * connection should be the one surfaced to the user if only one can be displayed.
+     *
+     * Note that the default route (chosen by Connectivity Service) may not correspond to the
+     * primary Wifi connection e.g. when there exists a better cellular network, or if the
+     * primary Wifi connection doesn't have internet access.
+     *
+     * @return whether this is the primary connection or not.
+     *
+     * @hide
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    @RequiresPermission(Manifest.permission.NETWORK_SETTINGS)
+    @SystemApi
+    public boolean isPrimary() {
+        if (!SdkLevel.isAtLeastS()) {
+            // Intentional - since we don't support STA + STA on older devices, this field
+            // is redundant. Don't allow anyone to use this.
+            throw new UnsupportedOperationException();
+        }
+        if (mIsPrimary == IS_PRIMARY_NO_PERMISSION) {
+            throw new SecurityException("Not allowed to access this field");
+        }
+        return mIsPrimary == IS_PRIMARY_TRUE;
+    }
+
     @Override
     public boolean equals(Object that) {
         if (this == that) return true;
@@ -1134,10 +1452,14 @@
                 && Objects.equals(mMeteredHint, thatWifiInfo.mMeteredHint)
                 && Objects.equals(mEphemeral, thatWifiInfo.mEphemeral)
                 && Objects.equals(mTrusted, thatWifiInfo.mTrusted)
+                && Objects.equals(mOemPaid, thatWifiInfo.mOemPaid)
+                && Objects.equals(mOemPrivate, thatWifiInfo.mOemPrivate)
+                && Objects.equals(mCarrierMerged, thatWifiInfo.mCarrierMerged)
                 && Objects.equals(mRequestingPackageName, thatWifiInfo.mRequestingPackageName)
                 && Objects.equals(mOsuAp, thatWifiInfo.mOsuAp)
                 && Objects.equals(mFqdn, thatWifiInfo.mFqdn)
                 && Objects.equals(mProviderFriendlyName, thatWifiInfo.mProviderFriendlyName)
+                && Objects.equals(mSubscriptionId, thatWifiInfo.mSubscriptionId)
                 && Objects.equals(txBad, thatWifiInfo.txBad)
                 && Objects.equals(txRetries, thatWifiInfo.txRetries)
                 && Objects.equals(txSuccess, thatWifiInfo.txSuccess)
@@ -1153,7 +1475,10 @@
                 && Objects.equals(mWifiStandard, thatWifiInfo.mWifiStandard)
                 && Objects.equals(mMaxSupportedTxLinkSpeed, thatWifiInfo.mMaxSupportedTxLinkSpeed)
                 && Objects.equals(mMaxSupportedRxLinkSpeed, thatWifiInfo.mMaxSupportedRxLinkSpeed)
-                && Objects.equals(mPasspointUniqueId, thatWifiInfo.mPasspointUniqueId);
+                && Objects.equals(mPasspointUniqueId, thatWifiInfo.mPasspointUniqueId)
+                && Objects.equals(mInformationElements, thatWifiInfo.mInformationElements)
+                && Objects.equals(mIsPrimary, thatWifiInfo.mIsPrimary)
+                && Objects.equals(mSecurityType, thatWifiInfo.mSecurityType);
     }
 
     @Override
@@ -1175,10 +1500,14 @@
                 mMeteredHint,
                 mEphemeral,
                 mTrusted,
+                mOemPaid,
+                mOemPrivate,
+                mCarrierMerged,
                 mRequestingPackageName,
                 mOsuAp,
                 mFqdn,
                 mProviderFriendlyName,
+                mSubscriptionId,
                 txBad,
                 txRetries,
                 txSuccess,
@@ -1191,7 +1520,10 @@
                 mWifiStandard,
                 mMaxSupportedTxLinkSpeed,
                 mMaxSupportedRxLinkSpeed,
-                mPasspointUniqueId);
+                mPasspointUniqueId,
+                mInformationElements,
+                mIsPrimary,
+                mSecurityType);
     }
 
     /**
@@ -1219,4 +1551,61 @@
                 | NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS
                 | NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS;
     }
+
+    /**
+     * Set the security type of the current connection
+     * @hide
+     */
+    public void setCurrentSecurityType(@WifiConfiguration.SecurityType int securityType) {
+        mSecurityType = convertSecurityTypeToWifiInfo(securityType);
+    }
+
+    /**
+     * Clear the last set security type
+     * @hide
+     */
+    public void clearCurrentSecurityType() {
+        mSecurityType = SECURITY_TYPE_UNKNOWN;
+    }
+
+    /**
+     * Returns the security type of the current 802.11 network connection.
+     *
+     * @return the security type, or {@link #SECURITY_TYPE_UNKNOWN} if not currently connected.
+     */
+    public @SecurityType int getCurrentSecurityType() {
+        return mSecurityType;
+    }
+
+    private @SecurityType int convertSecurityTypeToWifiInfo(
+            @WifiConfiguration.SecurityType int securityType) {
+        switch (securityType) {
+            case WifiConfiguration.SECURITY_TYPE_OPEN:
+                return SECURITY_TYPE_OPEN;
+            case WifiConfiguration.SECURITY_TYPE_WEP:
+                return SECURITY_TYPE_WEP;
+            case WifiConfiguration.SECURITY_TYPE_PSK:
+                return SECURITY_TYPE_PSK;
+            case WifiConfiguration.SECURITY_TYPE_EAP:
+                return SECURITY_TYPE_EAP;
+            case WifiConfiguration.SECURITY_TYPE_SAE:
+                return SECURITY_TYPE_SAE;
+            case WifiConfiguration.SECURITY_TYPE_OWE:
+                return SECURITY_TYPE_OWE;
+            case WifiConfiguration.SECURITY_TYPE_WAPI_PSK:
+                return SECURITY_TYPE_WAPI_PSK;
+            case WifiConfiguration.SECURITY_TYPE_WAPI_CERT:
+                return SECURITY_TYPE_WAPI_CERT;
+            case WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE:
+                return SECURITY_TYPE_EAP_WPA3_ENTERPRISE;
+            case WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT:
+                return SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT;
+            case WifiConfiguration.SECURITY_TYPE_PASSPOINT_R1_R2:
+                return SECURITY_TYPE_PASSPOINT_R1_R2;
+            case WifiConfiguration.SECURITY_TYPE_PASSPOINT_R3:
+                return SECURITY_TYPE_PASSPOINT_R3;
+            default:
+                return SECURITY_TYPE_UNKNOWN;
+        }
+    }
 }
diff --git a/framework/java/android/net/wifi/ITxPacketCountListener.aidl b/framework/java/android/net/wifi/WifiManager.aidl
similarity index 66%
copy from framework/java/android/net/wifi/ITxPacketCountListener.aidl
copy to framework/java/android/net/wifi/WifiManager.aidl
index 9105bd0..3079356 100644
--- a/framework/java/android/net/wifi/ITxPacketCountListener.aidl
+++ b/framework/java/android/net/wifi/WifiManager.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,13 +16,4 @@
 
 package android.net.wifi;
 
-/**
- * Interface for tx packet counter callback.
- * @deprecated no longer used, remove once removed from BaseWifiService
- * @hide
- */
-oneway interface ITxPacketCountListener
-{
-    void onSuccess(int count);
-    void onFailure(int reason);
-}
+parcelable WifiManager.AddNetworkResult;
diff --git a/framework/java/android/net/wifi/WifiManager.java b/framework/java/android/net/wifi/WifiManager.java
index 2bd72c9..df2aa51 100644
--- a/framework/java/android/net/wifi/WifiManager.java
+++ b/framework/java/android/net/wifi/WifiManager.java
@@ -34,14 +34,15 @@
 import android.app.ActivityManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
-import android.content.pm.ParceledListSlice;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
 import android.net.DhcpInfo;
+import android.net.LinkProperties;
 import android.net.MacAddress;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkStack;
+import android.net.Uri;
 import android.net.wifi.hotspot2.IProvisioningCallback;
 import android.net.wifi.hotspot2.OsuProvider;
 import android.net.wifi.hotspot2.PasspointConfiguration;
@@ -51,18 +52,26 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.WorkSource;
 import android.os.connectivity.WifiActivityEnergyInfo;
+import android.telephony.SubscriptionInfo;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.CloseGuard;
 import android.util.Log;
 import android.util.Pair;
 import android.util.SparseArray;
 
+import androidx.annotation.RequiresApi;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.modules.utils.HandlerExecutor;
+import com.android.modules.utils.ParceledListSlice;
+import com.android.modules.utils.build.SdkLevel;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -265,6 +274,44 @@
     public @interface SuggestionConnectionStatusCode {}
 
     /**
+     * Status code if suggestion approval status is unknown, an App which hasn't made any
+     * suggestions will get this code.
+     */
+    public static final int STATUS_SUGGESTION_APPROVAL_UNKNOWN = 0;
+
+    /**
+     * Status code if the calling app is still pending user approval for suggestions.
+     */
+    public static final int STATUS_SUGGESTION_APPROVAL_PENDING = 1;
+
+    /**
+     * Status code if the calling app got the user approval for suggestions.
+     */
+    public static final int STATUS_SUGGESTION_APPROVAL_APPROVED_BY_USER = 2;
+
+    /**
+     * Status code if the calling app suggestions were rejected by the user.
+     */
+    public static final int STATUS_SUGGESTION_APPROVAL_REJECTED_BY_USER = 3;
+
+    /**
+     * Status code if the calling app was approved by virtue of being a carrier privileged app.
+     * @see TelephonyManager#hasCarrierPrivileges().
+     */
+    public static final int STATUS_SUGGESTION_APPROVAL_APPROVED_BY_CARRIER_PRIVILEGE = 4;
+
+    /** @hide */
+    @IntDef(prefix = {"STATUS_SUGGESTION_APPROVAL_"},
+            value = {STATUS_SUGGESTION_APPROVAL_UNKNOWN,
+                    STATUS_SUGGESTION_APPROVAL_PENDING,
+                    STATUS_SUGGESTION_APPROVAL_APPROVED_BY_USER,
+                    STATUS_SUGGESTION_APPROVAL_REJECTED_BY_USER,
+                    STATUS_SUGGESTION_APPROVAL_APPROVED_BY_CARRIER_PRIVILEGE
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SuggestionUserApprovalStatus {}
+
+    /**
      * Broadcast intent action indicating whether Wi-Fi scanning is currently available.
      * Available extras:
      * - {@link #EXTRA_SCAN_AVAILABLE}
@@ -830,6 +877,20 @@
             "android.net.wifi.action.NETWORK_SETTINGS_RESET";
 
     /**
+     * Broadcast intent action indicating that the wifi network profiles provisioned
+     * may need refresh.
+     *
+     * Note: This intent is sent as a directed broadcast to each manifest registered receiver;
+     * And restricted to those apps which have the NETWORK_CARRIER_PROVISIONING permission.
+     * Intent will not be received by dynamically registered receivers.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING)
+    public static final String ACTION_REFRESH_USER_PROVISIONING =
+            "android.net.wifi.action.REFRESH_USER_PROVISIONING";
+
+    /**
      * Broadcast intent action indicating that a connection to the supplicant has
      * been established (and it is now possible
      * to perform Wi-Fi operations) or the connection to the supplicant has been
@@ -933,9 +994,11 @@
      * This can be as a result of adding/updating/deleting a network.
      * <br />
      * {@link #EXTRA_CHANGE_REASON} contains whether the configuration was added/changed/removed.
-     * {@link #EXTRA_WIFI_CONFIGURATION} is never set starting in Android 11.
+     * {@link #EXTRA_WIFI_CONFIGURATION} is never set beginning in
+     * {@link android.os.Build.VERSION_CODES#R}.
      * {@link #EXTRA_MULTIPLE_NETWORKS_CHANGED} is set for backwards compatibility reasons, but
-     * its value is always true, even if only a single network changed.
+     * its value is always true beginning in {@link android.os.Build.VERSION_CODES#R}, even if only
+     * a single network changed.
      * <br />
      * The {@link android.Manifest.permission#ACCESS_WIFI_STATE ACCESS_WIFI_STATE} permission is
      * required to receive this broadcast.
@@ -949,17 +1012,22 @@
      * The lookup key for a {@link android.net.wifi.WifiConfiguration} object representing
      * the changed Wi-Fi configuration when the {@link #CONFIGURED_NETWORKS_CHANGED_ACTION}
      * broadcast is sent.
-     * Note: this extra is never set starting in Android 11.
+     * @deprecated This extra is never set beginning in {@link android.os.Build.VERSION_CODES#R},
+     * regardless of the target SDK version. Use {@link #getConfiguredNetworks} to get the full list
+     * of configured networks.
      * @hide
      */
+    @Deprecated
     @SystemApi
     public static final String EXTRA_WIFI_CONFIGURATION = "wifiConfiguration";
     /**
      * Multiple network configurations have changed.
      * @see #CONFIGURED_NETWORKS_CHANGED_ACTION
-     * Note: this extra is always true starting in Android 11.
+     * @deprecated This extra's value is always true beginning in
+     * {@link android.os.Build.VERSION_CODES#R}, regardless of the target SDK version.
      * @hide
      */
+    @Deprecated
     @SystemApi
     public static final String EXTRA_MULTIPLE_NETWORKS_CHANGED = "multipleChanges";
     /**
@@ -1024,7 +1092,7 @@
      * A batch of access point scans has been completed and the results areavailable.
      * Call {@link #getBatchedScanResults()} to obtain the results.
      * @deprecated This API is nolonger supported.
-     * Use {@link android.net.wifi.WifiScanner} API
+     * Use {@link WifiScanner} API
      * @hide
      */
     @Deprecated
@@ -1055,9 +1123,6 @@
 
     /**
      * Broadcast intent action indicating that the link configuration changed on wifi.
-     * <br />Included Extras:
-     * <br />{@link #EXTRA_LINK_PROPERTIES}: may not be set starting in Android 11. Check for
-     * <br /> null before reading its value.
      * <br /> No permissions are required to listen to this broadcast.
      * @hide
      */
@@ -1074,11 +1139,11 @@
      *
      * Retrieve with {@link android.content.Intent#getParcelableExtra(String)}.
      *
-     * Note: this extra may not be set starting in Android 11. Check for null before reading its
-     * value.
+     * @deprecated this extra is no longer populated.
      *
      * @hide
      */
+    @Deprecated
     @SystemApi
     public static final String EXTRA_LINK_PROPERTIES = "android.net.wifi.extra.LINK_PROPERTIES";
 
@@ -1323,6 +1388,20 @@
     @GuardedBy("mLock")
     private LocalOnlyHotspotObserverProxy mLOHSObserverProxy;
 
+    private static final SparseArray<IOnWifiUsabilityStatsListener>
+            sOnWifiUsabilityStatsListenerMap = new SparseArray();
+    private static final SparseArray<ISuggestionConnectionStatusListener>
+            sSuggestionConnectionStatusListenerMap = new SparseArray();
+    private static final SparseArray<ISuggestionUserApprovalStatusListener>
+            sSuggestionUserApprovalStatusListenerMap = new SparseArray();
+    private static final SparseArray<IWifiVerboseLoggingStatusChangedListener>
+            sWifiVerboseLoggingStatusChangedListenerMap = new SparseArray();
+    private static final SparseArray<INetworkRequestMatchCallback>
+            sNetworkRequestMatchCallbackMap = new SparseArray();
+    private static final SparseArray<ITrafficStateCallback>
+            sTrafficStateCallbackMap = new SparseArray();
+    private static final SparseArray<ISoftApCallback> sSoftApCallbackMap = new SparseArray();
+
     /**
      * Create a new WifiManager instance.
      * Applications will almost always want to use
@@ -1388,7 +1467,7 @@
         try {
             ParceledListSlice<WifiConfiguration> parceledList =
                     mService.getConfiguredNetworks(mContext.getOpPackageName(),
-                            mContext.getAttributionTag());
+                            mContext.getAttributionTag(), false);
             if (parceledList == null) {
                 return Collections.emptyList();
             }
@@ -1398,6 +1477,32 @@
         }
     }
 
+    /**
+     * Return a list of all the networks previously configured by the calling app. Can
+     * be called by Device Owner (DO), Profile Owner (PO), Callers with Carrier privilege and
+     * system apps.
+     *
+     * @return a list of network configurations in the form of a list
+     * of {@link WifiConfiguration} objects.
+     * @throws {@link java.lang.SecurityException} if the caller is allowed to call this API
+     */
+    @RequiresPermission(ACCESS_WIFI_STATE)
+    @NonNull
+    public List<WifiConfiguration> getCallerConfiguredNetworks() {
+        try {
+            ParceledListSlice<WifiConfiguration> parceledList =
+                    mService.getConfiguredNetworks(mContext.getOpPackageName(),
+                            mContext.getAttributionTag(), true);
+            if (parceledList == null) {
+                return Collections.emptyList();
+            }
+            return parceledList.getList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+
     /** @hide */
     @SystemApi
     @RequiresPermission(allOf = {ACCESS_FINE_LOCATION, ACCESS_WIFI_STATE, READ_WIFI_CREDENTIAL})
@@ -1416,10 +1521,11 @@
     }
 
     /**
-     * Returns a list of all matching WifiConfigurations for a given list of ScanResult.
+     * Returns a list of all matching WifiConfigurations of PasspointConfiguration for a given list
+     * of ScanResult.
      *
-     * An empty list will be returned when no configurations are installed or if no configurations
-     * match the ScanResult.
+     * An empty list will be returned when no PasspointConfiguration are installed or if no
+     * PasspointConfiguration match the ScanResult.
      *
      * @param scanResults a list of scanResult that represents the BSSID
      * @return List that consists of {@link WifiConfiguration} and corresponding scanResults per
@@ -1446,7 +1552,7 @@
                             new ArrayList<>(results.keySet()));
             for (WifiConfiguration configuration : wifiConfigurations) {
                 Map<Integer, List<ScanResult>> scanResultsPerNetworkType =
-                        results.get(configuration.getKey());
+                        results.get(configuration.getProfileKey());
                 if (scanResultsPerNetworkType != null) {
                     configs.add(Pair.create(configuration, scanResultsPerNetworkType));
                 }
@@ -1585,6 +1691,148 @@
     }
 
     /**
+     * This is a new version of {@link #addNetwork(WifiConfiguration)} which returns more detailed
+     * failure codes. The usage of this API is limited to Device Owner (DO), Profile Owner (PO),
+     * system app, and privileged apps.
+     * <p>
+     * Add a new network description to the set of configured networks. The {@code networkId}
+     * field of the supplied configuration object is ignored. The new network will be marked
+     * DISABLED by default. To enable it, call {@link #enableNetwork}.
+     * <p>
+     * @param config the set of variables that describe the configuration,
+     *            contained in a {@link WifiConfiguration} object.
+     *            If the {@link WifiConfiguration} has an Http Proxy set
+     *            the calling app must be System, or be provisioned as the Profile or Device Owner.
+     * @return A {@link AddNetworkResult} Object.
+     * @throws {@link SecurityException} if the calling app is not a Device Owner (DO),
+     *                           Profile Owner (PO), system app, or a privileged app that has one of
+     *                           the permissions required by this API.
+     * @throws {@link IllegalArgumentException} if the input configuration is null.
+     */
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_SETTINGS,
+            android.Manifest.permission.NETWORK_STACK,
+            android.Manifest.permission.NETWORK_SETUP_WIZARD,
+            android.Manifest.permission.NETWORK_MANAGED_PROVISIONING
+    })
+    @NonNull
+    public AddNetworkResult addNetworkPrivileged(@NonNull WifiConfiguration config) {
+        if (config == null) throw new IllegalArgumentException("config cannot be null");
+        config.networkId = -1;
+        try {
+            return mService.addOrUpdateNetworkPrivileged(config, mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Provides the results of a call to {@link #addNetworkPrivileged(WifiConfiguration)}
+     */
+    public static final class AddNetworkResult implements Parcelable {
+        /**
+         * The operation has completed successfully.
+         */
+        public static final int STATUS_SUCCESS = 0;
+        /**
+         * The operation has failed due to an unknown reason.
+         */
+        public static final int STATUS_FAILURE_UNKNOWN = 1;
+        /**
+         * The calling app does not have permission to call this API.
+         */
+        public static final int STATUS_NO_PERMISSION = 2;
+        /**
+         * Generic failure code for adding a passpoint network.
+         */
+        public static final int STATUS_ADD_PASSPOINT_FAILURE = 3;
+        /**
+         * Generic failure code for adding a non-passpoint network.
+         */
+        public static final int STATUS_ADD_WIFI_CONFIG_FAILURE = 4;
+        /**
+         * The network configuration is invalid.
+         */
+        public static final int STATUS_INVALID_CONFIGURATION = 5;
+        /**
+         * The calling app has no permission to modify the configuration.
+         */
+        public static final int STATUS_NO_PERMISSION_MODIFY_CONFIG = 6;
+        /**
+         * The calling app has no permission to modify the proxy setting.
+         */
+        public static final int STATUS_NO_PERMISSION_MODIFY_PROXY_SETTING = 7;
+        /**
+         * The calling app has no permission to modify the MAC randomization setting.
+         */
+        public static final int STATUS_NO_PERMISSION_MODIFY_MAC_RANDOMIZATION = 8;
+        /**
+         * Internal failure in updating network keys..
+         */
+        public static final int STATUS_FAILURE_UPDATE_NETWORK_KEYS = 9;
+        /**
+         * The enterprise network is missing either the root CA or domain name.
+         */
+        public static final int STATUS_INVALID_CONFIGURATION_ENTERPRISE = 10;
+
+        /** @hide */
+        @IntDef(prefix = { "STATUS_" }, value = {
+                STATUS_SUCCESS,
+                STATUS_FAILURE_UNKNOWN,
+                STATUS_NO_PERMISSION,
+                STATUS_ADD_PASSPOINT_FAILURE,
+                STATUS_ADD_WIFI_CONFIG_FAILURE,
+                STATUS_INVALID_CONFIGURATION,
+                STATUS_NO_PERMISSION_MODIFY_CONFIG,
+                STATUS_NO_PERMISSION_MODIFY_PROXY_SETTING,
+                STATUS_NO_PERMISSION_MODIFY_MAC_RANDOMIZATION,
+                STATUS_FAILURE_UPDATE_NETWORK_KEYS,
+                STATUS_INVALID_CONFIGURATION_ENTERPRISE,
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface AddNetworkStatusCode {}
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeInt(statusCode);
+            dest.writeInt(networkId);
+        }
+
+        /** Implement the Parcelable interface */
+        public static final @android.annotation.NonNull Creator<AddNetworkResult> CREATOR =
+                new Creator<AddNetworkResult>() {
+                    public AddNetworkResult createFromParcel(Parcel in) {
+                        return new AddNetworkResult(in.readInt(), in.readInt());
+                    }
+
+                    public AddNetworkResult[] newArray(int size) {
+                        return new AddNetworkResult[size];
+                    }
+                };
+
+        /**
+         * One of the {@code STATUS_} values. If the operation is successful this field
+         * will be set to {@code STATUS_SUCCESS}.
+         */
+        public final @AddNetworkStatusCode int statusCode;
+        /**
+         * The identifier of the added network, which could be used in other operations. This field
+         * will be set to {@code -1} if the operation failed.
+         */
+        public final int networkId;
+
+        public AddNetworkResult(@AddNetworkStatusCode int statusCode, int networkId) {
+            this.statusCode = statusCode;
+            this.networkId = networkId;
+        }
+    }
+
+    /**
      * Update the network description of an existing configured network.
      *
      * @param config the set of variables that describe the configuration,
@@ -1869,11 +2117,14 @@
         Log.v(TAG, "registerNetworkRequestMatchCallback: callback=" + callback
                 + ", executor=" + executor);
 
-        Binder binder = new Binder();
         try {
-            mService.registerNetworkRequestMatchCallback(
-                    binder, new NetworkRequestMatchCallbackProxy(executor, callback),
-                    callback.hashCode());
+            synchronized (sNetworkRequestMatchCallbackMap) {
+                INetworkRequestMatchCallback.Stub binderCallback =
+                        new NetworkRequestMatchCallbackProxy(executor, callback);
+                sNetworkRequestMatchCallbackMap.put(System.identityHashCode(callback),
+                        binderCallback);
+                mService.registerNetworkRequestMatchCallback(binderCallback);
+            }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1898,13 +2149,45 @@
         Log.v(TAG, "unregisterNetworkRequestMatchCallback: callback=" + callback);
 
         try {
-            mService.unregisterNetworkRequestMatchCallback(callback.hashCode());
+            synchronized (sNetworkRequestMatchCallbackMap) {
+                int callbackIdentifier = System.identityHashCode(callback);
+                if (!sNetworkRequestMatchCallbackMap.contains(callbackIdentifier)) {
+                    Log.w(TAG, "Unknown external callback " + callbackIdentifier);
+                    return;
+                }
+                mService.unregisterNetworkRequestMatchCallback(
+                        sNetworkRequestMatchCallbackMap.get(callbackIdentifier));
+                sNetworkRequestMatchCallbackMap.remove(callbackIdentifier);
+            }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
     /**
+     * Privileged API to revoke all app state from wifi stack (equivalent to operations that the
+     * wifi stack performs to clear state for an app that was uninstalled.
+     * This removes:
+     * <li> All saved networks or passpoint profiles added by the app </li>
+     * <li> All previously approved peer to peer connection to access points initiated by the app
+     * using {@link WifiNetworkSpecifier}</li>
+     * <li> All network suggestions and approvals provided using {@link WifiNetworkSuggestion}</li>
+     * <p>
+     * @param targetAppUid UID of the app.
+     * @param targetAppPackageName Package name of the app.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+    public void removeAppState(int targetAppUid, @NonNull String targetAppPackageName) {
+        try {
+            mService.removeAppState(targetAppUid, targetAppPackageName);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
      * Provide a list of network suggestions to the device. See {@link WifiNetworkSuggestion}
      * for a detailed explanation of the parameters.
      * When the device decides to connect to one of the provided network suggestions, platform sends
@@ -2017,10 +2300,6 @@
      * first required to remove it using {@link WifiManager#removePasspointConfiguration(String)}.
      * Otherwise, a new profile will be added with both configuration.
      *
-     * @param config The Passpoint configuration to be added
-     * @throws IllegalArgumentException if configuration is invalid or Passpoint is not enabled on
-     *                                  the device.
-     *
      * Deprecated for general app usage - except DO/PO apps.
      * See {@link WifiNetworkSuggestion.Builder#setPasspointConfig(PasspointConfiguration)} to
      * create a passpoint suggestion.
@@ -2034,6 +2313,10 @@
      * <ul>
      * <li>Device Owner (DO), Profile Owner (PO) and system apps.
      * </ul>
+     *
+     * @param config The Passpoint configuration to be added
+     * @throws IllegalArgumentException if configuration is invalid or Passpoint is not enabled on
+     *                                  the device.
      */
     public void addOrUpdatePasspointConfiguration(PasspointConfiguration config) {
         try {
@@ -2125,22 +2408,6 @@
     }
 
     /**
-     * Deauthenticate and set the re-authentication hold off time for the current network
-     * @param holdoff hold off time in milliseconds
-     * @param ess set if the hold off pertains to an ESS rather than a BSS
-     * @hide
-     *
-     * TODO (140167680): This needs to be removed, the implementation is empty!
-     */
-    public void deauthenticateNetwork(long holdoff, boolean ess) {
-        try {
-            mService.deauthenticateNetwork(holdoff, ess);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Remove the specified network from the list of configured networks.
      * This may result in the asynchronous delivery of state change
      * events.
@@ -2177,6 +2444,21 @@
     }
 
     /**
+     * Remove all configured networks that were not created by the calling app. Can only
+     * be called by a Device Owner (DO) app.
+     *
+     * @return {@code true} if at least one network is removed, {@code false} otherwise
+     * @throws {@link java.lang.SecurityException} if the caller is not a Device Owner app
+     */
+    @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE)
+    public boolean removeNonCallerConfiguredNetworks() {
+        try {
+            return mService.removeNonCallerConfiguredNetworks(mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+    /**
      * Allow a previously configured network to be associated with. If
      * <code>attemptConnect</code> is true, an attempt to connect to the selected
      * network is initiated. This may result in the asynchronous delivery
@@ -2355,6 +2637,7 @@
         return isWifiEnabled();
     }
 
+    /** TODO(b/181364583): Convert all of these to 1 << X form. */
     /** @hide */
     public static final long WIFI_FEATURE_INFRA            = 0x0001L;  // Basic infrastructure mode
     /** @hide */
@@ -2376,7 +2659,7 @@
     /** @hide */
     public static final long WIFI_FEATURE_PNO              = 0x0400L;  // Preferred network offload
     /** @hide */
-    public static final long WIFI_FEATURE_ADDITIONAL_STA   = 0x0800L;  // Support for two STAs
+    public static final long WIFI_FEATURE_ADDITIONAL_STA   = 0x0800L;  // (unused)
     /** @hide */
     public static final long WIFI_FEATURE_TDLS             = 0x1000L;  // Tunnel directed link setup
     /** @hide */
@@ -2431,10 +2714,64 @@
     public static final long WIFI_FEATURE_WAPI             = 0x2000000000L; // WAPI
 
     /** @hide */
-    public static final long WIFI_FEATURE_FILS_SHA256     = 0x4000000000L; // FILS-SHA256
+    public static final long WIFI_FEATURE_FILS_SHA256      = 0x4000000000L; // FILS-SHA256
 
     /** @hide */
-    public static final long WIFI_FEATURE_FILS_SHA384     = 0x8000000000L; // FILS-SHA384
+    public static final long WIFI_FEATURE_FILS_SHA384      = 0x8000000000L; // FILS-SHA384
+
+    /** @hide */
+    public static final long WIFI_FEATURE_SAE_PK           = 0x10000000000L; // SAE-PK
+
+    /** @hide */
+    public static final long WIFI_FEATURE_STA_BRIDGED_AP   = 0x20000000000L; // STA + Bridged AP
+
+    /** @hide */
+    public static final long WIFI_FEATURE_BRIDGED_AP       = 0x40000000000L; // Bridged AP
+
+    /** @hide */
+    public static final long WIFI_FEATURE_INFRA_60G        = 0x80000000000L; // 60 GHz Band Support
+
+    /**
+     * Support for 2 STA's for the local-only (peer to peer) connection + internet connection
+     * concurrency.
+     * @hide
+     */
+    public static final long WIFI_FEATURE_ADDITIONAL_STA_LOCAL_ONLY = 0x100000000000L;
+
+    /**
+     * Support for 2 STA's for the make before break concurrency.
+     * @hide
+     */
+    public static final long WIFI_FEATURE_ADDITIONAL_STA_MBB = 0x200000000000L;
+
+    /**
+     * Support for 2 STA's for the restricted connection + internet connection concurrency.
+     * @hide
+     */
+    public static final long WIFI_FEATURE_ADDITIONAL_STA_RESTRICTED = 0x400000000000L;
+
+    /**
+     * DPP (Easy-Connect) Enrollee Responder mode support
+     * @hide
+     */
+    public static final long WIFI_FEATURE_DPP_ENROLLEE_RESPONDER = 0x800000000000L;
+
+    /**
+     * Passpoint Terms and Conditions feature support
+     * @hide
+     */
+    public static final long WIFI_FEATURE_PASSPOINT_TERMS_AND_CONDITIONS = 0x1000000000000L;
+
+     /** @hide */
+    public static final long WIFI_FEATURE_SAE_H2E          = 0x2000000000000L; // Hash-to-Element
+
+     /** @hide */
+    public static final long WIFI_FEATURE_WFD_R2           = 0x4000000000000L; // Wi-Fi Display R2
+
+    /**
+     * RFC 7542 decorated identity support
+     * @hide */
+    public static final long WIFI_FEATURE_DECORATED_IDENTITY = 0x8000000000000L;
 
     private long getSupportedFeatures() {
         try {
@@ -2490,7 +2827,7 @@
     }
 
     /**
-     * Query whether the device supports Station (STA) + Access point (AP) concurrency or not.
+     * Query whether or not the device supports Station (STA) + Access point (AP) concurrency.
      *
      * @return true if this device supports STA + AP concurrency, false otherwise.
      */
@@ -2499,6 +2836,44 @@
     }
 
     /**
+     * Query whether or not the device supports concurrent station (STA) connections for local-only
+     * connections using {@link WifiNetworkSpecifier}.
+     *
+     * @return true if this device supports multiple STA concurrency for this use-case, false
+     * otherwise.
+     */
+    public boolean isStaConcurrencyForLocalOnlyConnectionsSupported() {
+        return isFeatureSupported(WIFI_FEATURE_ADDITIONAL_STA_LOCAL_ONLY);
+    }
+
+    /**
+     * Query whether or not the device supports concurrent station (STA) connections for
+     * make-before-break wifi to wifi switching.
+     *
+     * Note: This is an internal feature which is not available to apps.
+     *
+     * @return true if this device supports multiple STA concurrency for this use-case, false
+     * otherwise.
+     */
+    public boolean isMakeBeforeBreakWifiSwitchingSupported() {
+        return isFeatureSupported(WIFI_FEATURE_ADDITIONAL_STA_MBB);
+    }
+
+    /**
+     * Query whether or not the device supports concurrent station (STA) connections for restricted
+     * connections using {@link WifiNetworkSuggestion.Builder#setOemPaid(boolean)} /
+     * {@link WifiNetworkSuggestion.Builder#setOemPrivate(boolean)}.
+     *
+     * @return true if this device supports multiple STA concurrency for this use-case, false
+     * otherwise.
+     * @hide
+     */
+    @SystemApi
+    public boolean isStaConcurrencyForRestrictedConnectionsSupported() {
+        return isFeatureSupported(WIFI_FEATURE_ADDITIONAL_STA_RESTRICTED);
+    }
+
+    /**
      * @deprecated Please use {@link android.content.pm.PackageManager#hasSystemFeature(String)}
      * with {@link android.content.pm.PackageManager#FEATURE_WIFI_RTT} and
      * {@link android.content.pm.PackageManager#FEATURE_WIFI_AWARE}.
@@ -2531,14 +2906,6 @@
     }
 
     /**
-     * @return true if this adapter supports multiple simultaneous connections
-     * @hide
-     */
-    public boolean isAdditionalStaSupported() {
-        return isFeatureSupported(WIFI_FEATURE_ADDITIONAL_STA);
-    }
-
-    /**
      * @return true if this adapter supports Tunnel Directed Link Setup
      */
     public boolean isTdlsSupported() {
@@ -2570,7 +2937,7 @@
     }
 
     /**
-     * @return true if this device supports connected MAC randomization.
+     * @return true if this device supports AP MAC randomization.
      * @hide
      */
     @SystemApi
@@ -2579,6 +2946,18 @@
     }
 
     /**
+     * Check if the chipset supports 2.4GHz band.
+     * @return {@code true} if supported, {@code false} otherwise.
+     */
+    public boolean is24GHzBandSupported() {
+        try {
+            return mService.is24GHzBandSupported();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Check if the chipset supports 5GHz band.
      * @return {@code true} if supported, {@code false} otherwise.
      */
@@ -2591,6 +2970,20 @@
     }
 
     /**
+     * Check if the chipset supports the 60GHz frequency band.
+     *
+     * @return {@code true} if supported, {@code false} otherwise.
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    public boolean is60GHzBandSupported() {
+        try {
+            return mService.is60GHzBandSupported();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Check if the chipset supports 6GHz band.
      * @return {@code true} if supported, {@code false} otherwise.
      */
@@ -2617,6 +3010,36 @@
     }
 
     /**
+     * Query whether or not the device supports concurrency of Station (STA) + multiple access
+     * points (AP) (where the APs bridged together).
+     *
+     * See {@link SoftApConfiguration.Builder#setBands(int[])}
+     * or {@link SoftApConfiguration.Builder#setChannels(android.util.SparseIntArray)} to configure
+     * bridged AP when the bridged AP supported.
+     *
+     * @return true if this device supports concurrency of STA + multiple APs which are bridged
+     *         together, false otherwise.
+     */
+    public boolean isStaBridgedApConcurrencySupported() {
+        return isFeatureSupported(WIFI_FEATURE_STA_BRIDGED_AP);
+    }
+
+    /**
+     * Query whether or not the device supports multiple Access point (AP) which are bridged
+     * together.
+     *
+     * See {@link SoftApConfiguration.Builder#setBands(int[])}
+     * or {@link SoftApConfiguration.Builder#setChannels(android.util.SparseIntArray)} to configure
+     * bridged AP when the bridged AP supported.
+     *
+     * @return true if this device supports concurrency of multiple AP which bridged together,
+     *         false otherwise.
+     */
+    public boolean isBridgedApConcurrencySupported() {
+        return isFeatureSupported(WIFI_FEATURE_BRIDGED_AP);
+    }
+
+    /**
      * Interface for Wi-Fi activity energy info listener. Should be implemented by applications and
      * set when calling {@link WifiManager#getWifiActivityEnergyInfoAsync}.
      *
@@ -2779,10 +3202,18 @@
      * connectivityManager.registerNetworkCallback(request, networkCallback); // For listen
      * }</pre>
      * <p>
-     * <b>Compatibility Note:</b>
+     * <b>Compatibility Notes:</b>
      * <li>Apps can continue using this API, however newer features
      * such as ability to mask out location sensitive data in WifiInfo will not be supported
      * via this API. </li>
+     * <li>On devices supporting concurrent connections (indicated via
+     * {@link #isStaConcurrencyForLocalOnlyConnectionsSupported()}, etc) this API will return
+     * the details of the internet providing connection (if any) to all apps, except for the apps
+     * that triggered the creation of the concurrent connection. For such apps, this API will return
+     * the details of the connection they created. e.g. apps using {@link WifiNetworkSpecifier} will
+     * trigger a concurrent connection on supported devices and hence this API will provide
+     * details of their peer to peer connection (not the internet providing connection). This
+     * is to maintain backwards compatibility with behavior on single STA devices.</li>
      * </p>
      */
     @Deprecated
@@ -2855,7 +3286,7 @@
     @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
     public void setScanAlwaysAvailable(boolean isAvailable) {
         try {
-            mService.setScanAlwaysAvailable(isAvailable);
+            mService.setScanAlwaysAvailable(isAvailable, mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2898,9 +3329,20 @@
     }
 
     /**
-     * Get the country code.
-     * @return the country code in ISO 3166 alpha-2 (2-letter) uppercase format, or null if
-     * there is no country code configured.
+     * Get the country code as resolved by the Wi-Fi framework.
+     * The Wi-Fi framework uses multiple sources to resolve a country code
+     * - in order of priority (high to low):
+     * 1. Override country code set by {@link WifiManager#setOverrideCountryCode(String)}
+     * and cleared by {@link WifiManager#clearOverrideCountryCode()}. Typically only used
+     * for testing.
+     * 2. Country code supplied by the telephony module. Typically provided from the
+     * current network or from emergency cell information.
+     * 3. Default country code set either via {@code ro.boot.wificountrycode}
+     * or the {@link WifiManager#setDefaultCountryCode(String)}.
+     *
+     * @return the country code in ISO 3166 alpha-2 (2-letter) upper format,
+     * or null if there is no country code configured.
+     *
      * @hide
      */
     @Nullable
@@ -2915,13 +3357,86 @@
     }
 
     /**
+     * Set the override country code - may be used for testing. See the country code resolution
+     * order and format in {@link #getCountryCode()}.
+     * @param country A 2-Character alphanumeric country code.
+     * @see #getCountryCode().
+     *
+     * @hide
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_WIFI_COUNTRY_CODE)
+    public void setOverrideCountryCode(@NonNull String country) {
+        try {
+            mService.setOverrideCountryCode(country);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * This clears the override country code which was previously set by
+     * {@link WifiManager#setOverrideCountryCode(String)} method.
+     * @see #getCountryCode().
+     *
+     * @hide
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_WIFI_COUNTRY_CODE)
+    public void clearOverrideCountryCode() {
+        try {
+            mService.clearOverrideCountryCode();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+    /**
+     * Used to configure the default country code. See {@link #getCountryCode()} for resolution
+     * method of the country code.
+     * @param country A 2-character alphanumeric country code.
+     * @see #getCountryCode().
+     *
+     * @hide
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_WIFI_COUNTRY_CODE)
+    public void setDefaultCountryCode(@NonNull String country) {
+        try {
+            mService.setDefaultCountryCode(country);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Return the DHCP-assigned addresses from the last successful DHCP request,
      * if any.
+     *
      * @return the DHCP information
+     *
+     * @deprecated Use the methods on {@link android.net.LinkProperties} which can be obtained
+     * either via {@link NetworkCallback#onLinkPropertiesChanged(Network, LinkProperties)} or
+     * {@link ConnectivityManager#getLinkProperties(Network)}.
+     *
+     * <p>
+     * <b>Compatibility Notes:</b>
+     * <li>On devices supporting concurrent connections (indicated via
+     * {@link #isStaConcurrencyForLocalOnlyConnectionsSupported()}, etc), this API will return
+     * the details of the internet providing connection (if any) to all apps, except for the apps
+     * that triggered the creation of the concurrent connection. For such apps, this API will return
+     * the details of the connection they created. e.g. apps using {@link WifiNetworkSpecifier} will
+     * trigger a concurrent connection on supported devices and hence this API will provide
+     * details of their peer to peer connection (not the internet providing connection). This
+     * is to maintain backwards compatibility with behavior on single STA devices.</li>
+     * </p>
      */
+    @Deprecated
     public DhcpInfo getDhcpInfo() {
         try {
-            return mService.getDhcpInfo();
+            return mService.getDhcpInfo(mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2960,6 +3475,170 @@
     }
 
     /**
+     * Abstract callback class for applications to receive updates about the Wi-Fi subsystem
+     * restarting. The Wi-Fi subsystem can restart due to internal recovery mechanisms or via user
+     * action.
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    public abstract static class SubsystemRestartTrackingCallback {
+        private final SubsystemRestartTrackingCallback.SubsystemRestartCallbackProxy mProxy;
+
+        public SubsystemRestartTrackingCallback() {
+            mProxy = new SubsystemRestartTrackingCallback.SubsystemRestartCallbackProxy();
+        }
+
+        /*package*/ @NonNull
+        SubsystemRestartTrackingCallback.SubsystemRestartCallbackProxy getProxy() {
+            return mProxy;
+        }
+
+        /**
+         * Indicates that the Wi-Fi subsystem is about to restart.
+         */
+        public abstract void onSubsystemRestarting();
+
+        /**
+         * Indicates that the Wi-Fi subsystem has restarted.
+         */
+        public abstract void onSubsystemRestarted();
+
+        private static class SubsystemRestartCallbackProxy extends ISubsystemRestartCallback.Stub {
+            private final Object mLock = new Object();
+            @Nullable
+            @GuardedBy("mLock")
+            private Executor mExecutor;
+            @Nullable
+            @GuardedBy("mLock")
+            private SubsystemRestartTrackingCallback mCallback;
+
+            SubsystemRestartCallbackProxy() {
+                mExecutor = null;
+                mCallback = null;
+            }
+
+            /*package*/ void initProxy(@NonNull Executor executor,
+                    @NonNull SubsystemRestartTrackingCallback callback) {
+                synchronized (mLock) {
+                    mExecutor = executor;
+                    mCallback = callback;
+                }
+            }
+
+            /*package*/ void cleanUpProxy() {
+                synchronized (mLock) {
+                    mExecutor = null;
+                    mCallback = null;
+                }
+            }
+
+            @Override
+            public void onSubsystemRestarting() {
+                Executor executor;
+                SubsystemRestartTrackingCallback callback;
+                synchronized (mLock) {
+                    executor = mExecutor;
+                    callback = mCallback;
+                }
+                if (executor == null || callback == null) {
+                    return;
+                }
+                Binder.clearCallingIdentity();
+                executor.execute(callback::onSubsystemRestarting);
+            }
+
+            @Override
+            public void onSubsystemRestarted() {
+                Executor executor;
+                SubsystemRestartTrackingCallback callback;
+                synchronized (mLock) {
+                    executor = mExecutor;
+                    callback = mCallback;
+                }
+                if (executor == null || callback == null) {
+                    return;
+                }
+                Binder.clearCallingIdentity();
+                executor.execute(callback::onSubsystemRestarted);
+            }
+        }
+    }
+
+    /**
+     * Registers a {@link SubsystemRestartTrackingCallback} to listen to Wi-Fi subsystem restarts.
+     * The subsystem may restart due to internal recovery mechanisms or via user action.
+     *
+     * @see #unregisterSubsystemRestartTrackingCallback(SubsystemRestartTrackingCallback)
+     *
+     * @param executor Executor to execute callback on
+     * @param callback {@link SubsystemRestartTrackingCallback} to register
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE)
+    public void registerSubsystemRestartTrackingCallback(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull SubsystemRestartTrackingCallback callback) {
+        if (executor == null) throw new IllegalArgumentException("executor must not be null");
+        if (callback == null) throw new IllegalArgumentException("callback must not be null");
+        SubsystemRestartTrackingCallback.SubsystemRestartCallbackProxy proxy = callback.getProxy();
+        proxy.initProxy(executor, callback);
+        try {
+            mService.registerSubsystemRestartCallback(proxy);
+        } catch (RemoteException e) {
+            proxy.cleanUpProxy();
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Unregisters a {@link SubsystemRestartTrackingCallback} registered with
+     * {@link #registerSubsystemRestartTrackingCallback(Executor, SubsystemRestartTrackingCallback)}
+     *
+     * @param callback {@link SubsystemRestartTrackingCallback} to unregister
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE)
+    public void unregisterSubsystemRestartTrackingCallback(
+            @NonNull SubsystemRestartTrackingCallback callback) {
+        if (callback == null) throw new IllegalArgumentException("callback must not be null");
+        SubsystemRestartTrackingCallback.SubsystemRestartCallbackProxy proxy = callback.getProxy();
+        try {
+            mService.unregisterSubsystemRestartCallback(proxy);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } finally {
+            proxy.cleanUpProxy();
+        }
+    }
+
+    /**
+     * Restart the Wi-Fi subsystem.
+     *
+     * Restarts the Wi-Fi subsystem - effectively disabling it and re-enabling it. All existing
+     * Access Point (AP) associations are torn down, all Soft APs are disabled, Wi-Fi Direct and
+     * Wi-Fi Aware are disabled.
+     *
+     * The state of the system after restart is not guaranteed to match its state before the API is
+     * called - for instance the device may associate to a different Access Point (AP), and tethered
+     * hotspots may or may not be restored.
+     *
+     * Use the
+     * {@link #registerSubsystemRestartTrackingCallback(Executor, SubsystemRestartTrackingCallback)}
+     * to track the operation.
+     *
+     * @hide
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.RESTART_WIFI_SUBSYSTEM)
+    public void restartWifiSubsystem() {
+        try {
+            mService.restartWifiSubsystem();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Gets the Wi-Fi enabled state.
      * @return One of {@link #WIFI_STATE_DISABLED},
      *         {@link #WIFI_STATE_DISABLING}, {@link #WIFI_STATE_ENABLED},
@@ -3071,6 +3750,226 @@
         }
     }
 
+    /* Wi-Fi/Cellular Coex */
+
+    /**
+     * Mandatory coex restriction flag for Wi-Fi Direct.
+     *
+     * @see #setCoexUnsafeChannels(List, int)
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.S)
+    public static final int COEX_RESTRICTION_WIFI_DIRECT = 0x1 << 0;
+
+    /**
+     * Mandatory coex restriction flag for SoftAP
+     *
+     * @see #setCoexUnsafeChannels(List, int)
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.S)
+    public static final int COEX_RESTRICTION_SOFTAP = 0x1 << 1;
+
+    /**
+     * Mandatory coex restriction flag for Wi-Fi Aware.
+     *
+     * @see #setCoexUnsafeChannels(List, int)
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.S)
+    public static final int COEX_RESTRICTION_WIFI_AWARE = 0x1 << 2;
+
+    /** @hide */
+    @RequiresApi(Build.VERSION_CODES.S)
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = {"COEX_RESTRICTION_"}, value = {
+            COEX_RESTRICTION_WIFI_DIRECT,
+            COEX_RESTRICTION_SOFTAP,
+            COEX_RESTRICTION_WIFI_AWARE
+    })
+    public @interface CoexRestriction {}
+
+    /**
+     * @return {@code true} if the default coex algorithm is enabled. {@code false} otherwise.
+     *
+     * @hide
+     */
+    public boolean isDefaultCoexAlgorithmEnabled() {
+        try {
+            return mService.isDefaultCoexAlgorithmEnabled();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Specify the list of {@link CoexUnsafeChannel} to propagate through the framework for
+     * Wi-Fi/Cellular coex channel avoidance if the default algorithm is disabled via overlay
+     * (i.e. config_wifiCoexDefaultAlgorithmEnabled = false). Otherwise do nothing.
+     *
+     * @param unsafeChannels List of {@link CoexUnsafeChannel} to avoid.
+     * @param restrictions Bitmap of {@code COEX_RESTRICTION_*} constants specifying the mode
+     *                     restrictions on the specified channels. If any restrictions are set,
+     *                     then the supplied CoexUnsafeChannels should be completely avoided for
+     *                     the specified modes, rather than be avoided with best effort.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS)
+    @RequiresApi(Build.VERSION_CODES.S)
+    public void setCoexUnsafeChannels(
+            @NonNull List<CoexUnsafeChannel> unsafeChannels, @CoexRestriction int restrictions) {
+        if (unsafeChannels == null) {
+            throw new IllegalArgumentException("unsafeChannels must not be null");
+        }
+        try {
+            mService.setCoexUnsafeChannels(unsafeChannels, restrictions);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Registers a CoexCallback to listen on the current CoexUnsafeChannels and restrictions being
+     * used for Wi-Fi/cellular coex channel avoidance. The callback method
+     * {@link CoexCallback#onCoexUnsafeChannelsChanged(List, int)} will be called immediately after
+     * registration to return the current values.
+     *
+     * @param executor Executor to execute listener callback on
+     * @param callback CoexCallback to register
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS)
+    @RequiresApi(Build.VERSION_CODES.S)
+    public void registerCoexCallback(
+            @NonNull @CallbackExecutor Executor executor, @NonNull CoexCallback callback) {
+        if (executor == null) throw new IllegalArgumentException("executor must not be null");
+        if (callback == null) throw new IllegalArgumentException("callback must not be null");
+        CoexCallback.CoexCallbackProxy proxy = callback.getProxy();
+        proxy.initProxy(executor, callback);
+        try {
+            mService.registerCoexCallback(proxy);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Unregisters a CoexCallback from listening on the current CoexUnsafeChannels and restrictions
+     * being used for Wi-Fi/cellular coex channel avoidance.
+     * @param callback CoexCallback to unregister
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS)
+    @RequiresApi(Build.VERSION_CODES.S)
+    public void unregisterCoexCallback(@NonNull CoexCallback callback) {
+        if (callback == null) throw new IllegalArgumentException("callback must not be null");
+        CoexCallback.CoexCallbackProxy proxy = callback.getProxy();
+        try {
+            mService.unregisterCoexCallback(proxy);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } finally {
+            proxy.cleanUpProxy();
+        }
+    }
+
+    /**
+     * Abstract callback class for applications to receive updates about current CoexUnsafeChannels
+     * for Wi-Fi/Cellular coex channel avoidance.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.S)
+    public abstract static class CoexCallback {
+        private final CoexCallbackProxy mCoexCallbackProxy;
+
+        public CoexCallback() {
+            if (!SdkLevel.isAtLeastS()) {
+                throw new UnsupportedOperationException();
+            }
+            mCoexCallbackProxy = new CoexCallbackProxy();
+        }
+
+        /*package*/ @NonNull
+        CoexCallbackProxy getProxy() {
+            return mCoexCallbackProxy;
+        }
+
+        /**
+         * This indicates the current CoexUnsafeChannels and restrictions calculated by the default
+         * coex algorithm if config_wifiCoexDefaultAlgorithmEnabled is {@code true}. Otherwise, the
+         * values will match the ones supplied to {@link #setCoexUnsafeChannels(List, int)}.
+         *
+         * @param unsafeChannels List of {@link CoexUnsafeChannel} to avoid.
+         * @param restrictions Bitmap of {@code COEX_RESTRICTION_*} constants specifying the mode
+         *                     restrictions on the specified channels. If any restrictions are set,
+         *                     then the supplied CoexUnsafeChannels should be completely avoided for
+         *                     the specified modes, rather than be avoided with best effort.
+         */
+        public abstract void onCoexUnsafeChannelsChanged(
+                @NonNull List<CoexUnsafeChannel> unsafeChannels, @CoexRestriction int restrictions);
+
+        /**
+         * Callback proxy for CoexCallback objects.
+         */
+        private static class CoexCallbackProxy extends ICoexCallback.Stub {
+            private final Object mLock = new Object();
+            @Nullable @GuardedBy("mLock") private Executor mExecutor;
+            @Nullable @GuardedBy("mLock") private CoexCallback mCallback;
+
+            CoexCallbackProxy() {
+                mExecutor = null;
+                mCallback = null;
+            }
+
+            /*package*/ void initProxy(@NonNull Executor executor,
+                    @NonNull CoexCallback callback) {
+                synchronized (mLock) {
+                    mExecutor = executor;
+                    mCallback = callback;
+                }
+            }
+
+            /*package*/ void cleanUpProxy() {
+                synchronized (mLock) {
+                    mExecutor = null;
+                    mCallback = null;
+                }
+            }
+
+            @Override
+            public void onCoexUnsafeChannelsChanged(
+                    @NonNull List<CoexUnsafeChannel> unsafeChannels,
+                    @CoexRestriction int restrictions) {
+                Executor executor;
+                CoexCallback callback;
+                synchronized (mLock) {
+                    executor = mExecutor;
+                    callback = mCallback;
+                }
+                if (executor == null || callback == null) {
+                    return;
+                }
+                Binder.clearCallingIdentity();
+                executor.execute(() ->
+                        callback.onCoexUnsafeChannelsChanged(unsafeChannels, restrictions));
+            }
+        }
+    }
+
     /**
      * Start Soft AP (hotspot) mode for tethering purposes with the specified configuration.
      * Note that starting Soft AP mode may disable station mode operation if the device does not
@@ -3088,7 +3987,7 @@
     })
     public boolean startSoftAp(@Nullable WifiConfiguration wifiConfig) {
         try {
-            return mService.startSoftAp(wifiConfig);
+            return mService.startSoftAp(wifiConfig, mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -3112,7 +4011,7 @@
     })
     public boolean startTetheredHotspot(@Nullable SoftApConfiguration softApConfig) {
         try {
-            return mService.startTetheredHotspot(softApConfig);
+            return mService.startTetheredHotspot(softApConfig, mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -3182,7 +4081,7 @@
      * unexpectedly, but they will receive a notification if they have properly registered.
      * <p>
      * Applications should also be aware that this network will be shared with other applications.
-     * Applications are responsible for protecting their data on this network (e.g., TLS).
+     * Applications are responsible for protecting their data on this network (e.g. TLS).
      * <p>
      * Applications need to have the following permissions to start LocalOnlyHotspot: {@link
      * android.Manifest.permission#CHANGE_WIFI_STATE} and {@link
@@ -3429,7 +4328,10 @@
      */
     @NonNull
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_SETTINGS,
+            android.Manifest.permission.OVERRIDE_WIFI_CONFIG
+    })
     public SoftApConfiguration getSoftApConfiguration() {
         try {
             return mService.getSoftApConfiguration();
@@ -3467,6 +4369,8 @@
      * or {@link SoftApConfiguration.Builder#setClientControlByUserEnabled(boolean)}
      * or {@link SoftApConfiguration.Builder#setBlockedClientList(List)}
      * or {@link SoftApConfiguration.Builder#setAllowedClientList(List)}
+     * or {@link SoftApConfiguration.Builder#setAutoShutdownEnabled(boolean)}
+     * or {@link SoftApConfiguration.Builder#setBridgedModeOpportunisticShutdownEnabled(boolean)}
      *
      * Otherwise, the configuration changes will be applied when the Soft AP is next started
      * (the framework will not stop/start the AP).
@@ -3477,7 +4381,10 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_SETTINGS,
+            android.Manifest.permission.OVERRIDE_WIFI_CONFIG
+    })
     public boolean setSoftApConfiguration(@NonNull SoftApConfiguration softApConfig) {
         try {
             return mService.setSoftApConfiguration(
@@ -3660,22 +4567,85 @@
          * Called when the connected clients to soft AP changes.
          *
          * @param clients the currently connected clients
+         *
+         * @deprecated This API is deprecated.
+         * Use {@link #onConnectedClientsChanged(SoftApInfo, List<WifiClient>)} instead.
          */
+        @Deprecated
         default void onConnectedClientsChanged(@NonNull List<WifiClient> clients) {}
 
+
         /**
-         * Called when information of softap changes.
+         * Called when the connected clients for a soft AP instance change.
          *
-         * @param softApInfo is the softap information. {@link SoftApInfo}
+         * When the Soft AP is configured in single AP mode, this callback is invoked
+         * with the same {@link SoftApInfo} for all connected clients changes.
+         * When the Soft AP is configured as multiple Soft AP instances (using
+         * {@link SoftApConfiguration.Builder#setBands(int[])} or
+         * {@link SoftApConfiguration.Builder#setChannels(android.util.SparseIntArray)}), this
+         * callback is invoked with the corresponding {@link SoftApInfo} for the instance in which
+         * the connected clients changed.
+         *
+         * @param info The {@link SoftApInfo} of the AP.
+         * @param clients The currently connected clients on the AP instance specified by
+         *                {@code info}.
          */
+        default void onConnectedClientsChanged(@NonNull SoftApInfo info,
+                @NonNull List<WifiClient> clients) {}
+
+        /**
+         * Called when the Soft AP information changes.
+         *
+         * Note: this API remains valid only when the Soft AP is configured as a single AP -
+         * not as multiple Soft APs (which are bridged to each other). When multiple Soft APs are
+         * configured (using {@link SoftApConfiguration.Builder#setBands(int[])} or
+         * {@link SoftApConfiguration.Builder#setChannels(android.util.SparseIntArray)})
+         * this callback will not be triggered -  use the
+         * {@link #onInfoChanged(List<SoftApInfo>)} callback in that case.
+         *
+         * @param softApInfo is the Soft AP information. {@link SoftApInfo}
+         *
+         * @deprecated This API is deprecated. Use {@link #onInfoChanged(List<SoftApInfo>)}
+         * instead.
+         */
+        @Deprecated
         default void onInfoChanged(@NonNull SoftApInfo softApInfo) {
             // Do nothing: can be updated to add SoftApInfo details (e.g. channel) to the UI.
         }
 
         /**
-         * Called when capability of softap changes.
+         * Called when the Soft AP information changes.
          *
-         * @param softApCapability is the softap capability. {@link SoftApCapability}
+         * Returns information on all configured Soft AP instances. The number of the elements in
+         * the list depends on Soft AP configuration and state:
+         * <ul>
+         * <li>An empty list will be returned when the Soft AP is disabled.
+         * <li>One information element will be returned in the list when the Soft AP is configured
+         *     as a single AP or when a single Soft AP remains active.
+         * <li>Two information elements will be returned in the list when the multiple Soft APs are
+         *     configured and are active.
+         *     (configured using {@link SoftApConfiguration.Builder#setBands(int[])} or
+         *     {@link SoftApConfiguration.Builder#setChannels(android.util.SparseIntArray)}).
+         * </ul>
+         *
+         * Note: When multiple Soft AP instances are configured, one of the Soft APs may
+         * be shut down independently of the other by the framework. This can happen if no devices
+         * are connected to it for some duration. In that case, one information element will be
+         * returned.
+         *
+         * See {@link #isBridgedApConcurrencySupported()} for support info of multiple (bridged) AP.
+         *
+         * @param softApInfoList is the list of the Soft AP information elements -
+         *        {@link SoftApInfo}.
+         */
+        default void onInfoChanged(@NonNull List<SoftApInfo> softApInfoList) {
+            // Do nothing: can be updated to add SoftApInfo details (e.g. channel) to the UI.
+        }
+
+        /**
+         * Called when capability of Soft AP changes.
+         *
+         * @param softApCapability is the Soft AP capability. {@link SoftApCapability}
          */
         default void onCapabilityChanged(@NonNull SoftApCapability softApCapability) {
             // Do nothing: can be updated to add SoftApCapability details (e.g. meximum supported
@@ -3707,6 +4677,16 @@
     private class SoftApCallbackProxy extends ISoftApCallback.Stub {
         private final Executor mExecutor;
         private final SoftApCallback mCallback;
+        private Map<String, List<WifiClient>> mCurrentClients = new HashMap<>();
+        private Map<String, SoftApInfo> mCurrentInfos = new HashMap<>();
+
+        private List<WifiClient> getConnectedClientList(Map<String, List<WifiClient>> clientsMap) {
+            List<WifiClient> connectedClientList = new ArrayList<>();
+            for (List<WifiClient> it : clientsMap.values()) {
+                connectedClientList.addAll(it);
+            }
+            return connectedClientList;
+        }
 
         SoftApCallbackProxy(Executor executor, SoftApCallback callback) {
             mExecutor = executor;
@@ -3727,28 +4707,101 @@
         }
 
         @Override
-        public void onConnectedClientsChanged(List<WifiClient> clients) {
+        public void onConnectedClientsOrInfoChanged(Map<String, SoftApInfo> infos,
+                Map<String, List<WifiClient>> clients, boolean isBridged, boolean isRegistration) {
             if (mVerboseLoggingEnabled) {
-                Log.v(TAG, "SoftApCallbackProxy: onConnectedClientsChanged: clients="
-                        + clients.size() + " clients");
+                Log.v(TAG, "SoftApCallbackProxy: onConnectedClientsOrInfoChanged: clients: "
+                        + clients + ", infos: " + infos + ", isBridged is " + isBridged
+                        + ", isRegistration is " + isRegistration);
             }
 
-            Binder.clearCallingIdentity();
-            mExecutor.execute(() -> {
-                mCallback.onConnectedClientsChanged(clients);
-            });
-        }
+            List<SoftApInfo> changedInfoList = new ArrayList<>(infos.values());
+            Map<SoftApInfo, List<WifiClient>> changedInfoClients = new HashMap<>();
+            // Some devices may not support infos callback, allow them to support client
+            // connection changed callback.
+            boolean areClientsChangedWithoutInfosChanged =
+                    infos.size() == 0 && getConnectedClientList(clients).size()
+                    != getConnectedClientList(mCurrentClients).size();
+            boolean isInfoChanged = infos.size() != mCurrentInfos.size();
 
-        @Override
-        public void onInfoChanged(SoftApInfo softApInfo) {
-            if (mVerboseLoggingEnabled) {
-                Log.v(TAG, "SoftApCallbackProxy: onInfoChange: softApInfo=" + softApInfo);
+            if (isRegistration) {
+                // Check if there are clients connected, put it to changedInfoClients
+                for (SoftApInfo currentInfo : infos.values()) {
+                    String instance = currentInfo.getApInstanceIdentifier();
+                    if (clients.getOrDefault(instance, Collections.emptyList()).size() > 0) {
+                        changedInfoClients.put(currentInfo, clients.get(instance));
+                    }
+                }
             }
 
+            // Check if old info removed or not (client changed case)
+            for (SoftApInfo info : mCurrentInfos.values()) {
+                String changedInstance = info.getApInstanceIdentifier();
+                if (!changedInfoList.contains(info)) {
+                    isInfoChanged = true;
+                    if (mCurrentClients.getOrDefault(changedInstance,
+                              Collections.emptyList()).size() > 0) {
+                        Log.d(TAG, "SoftApCallbackProxy: info changed on client connected"
+                                + " instance(Shut Down case)");
+                        //Here should notify client changed on old info
+                        changedInfoClients.put(info, Collections.emptyList());
+                    }
+                } else {
+                    // info doesn't change, check client list
+                    List<WifiClient> changedClientList = clients.getOrDefault(
+                            changedInstance, Collections.emptyList());
+                    if (changedClientList.size()
+                            != mCurrentClients
+                            .getOrDefault(changedInstance, Collections.emptyList()).size()) {
+                        // Here should notify client changed on new info(same as old info)
+                        changedInfoClients.put(info, changedClientList);
+                    }
+                }
+            }
+
+            mCurrentClients = clients;
+            mCurrentInfos = infos;
+            if (!isInfoChanged && changedInfoClients.isEmpty()
+                    && !isRegistration && !areClientsChangedWithoutInfosChanged) {
+                Log.v(TAG, "SoftApCallbackProxy: No changed & Not Registration,"
+                        + " don't need to notify the client");
+                return;
+            }
             Binder.clearCallingIdentity();
-            mExecutor.execute(() -> {
-                mCallback.onInfoChanged(softApInfo);
-            });
+            // Notify the clients changed first for old info shutdown case
+            for (SoftApInfo changedInfo : changedInfoClients.keySet()) {
+                Log.v(TAG, "SoftApCallbackProxy: send onConnectedClientsChanged, changedInfo is "
+                        + changedInfo + " and clients are " + changedInfoClients.get(changedInfo));
+                mExecutor.execute(() -> {
+                    mCallback.onConnectedClientsChanged(
+                            changedInfo, changedInfoClients.get(changedInfo));
+                });
+            }
+
+            if (isInfoChanged || isRegistration) {
+                if (!isBridged) {
+                    SoftApInfo newInfo = changedInfoList.isEmpty()
+                            ? new SoftApInfo() : changedInfoList.get(0);
+                    Log.v(TAG, "SoftApCallbackProxy: send InfoChanged, newInfo: " + newInfo);
+                    mExecutor.execute(() -> {
+                        mCallback.onInfoChanged(newInfo);
+                    });
+                }
+                Log.v(TAG, "SoftApCallbackProxy: send InfoChanged, changedInfoList: "
+                        + changedInfoList);
+                mExecutor.execute(() -> {
+                    mCallback.onInfoChanged(changedInfoList);
+                });
+            }
+
+            if (isRegistration || !changedInfoClients.isEmpty()
+                    || areClientsChangedWithoutInfosChanged) {
+                Log.v(TAG, "SoftApCallbackProxy: send onConnectedClientsChanged(clients): "
+                        + getConnectedClientList(clients));
+                mExecutor.execute(() -> {
+                    mCallback.onConnectedClientsChanged(getConnectedClientList(clients));
+                });
+            }
         }
 
         @Override
@@ -3785,8 +4838,18 @@
      * <li> {@link SoftApCallback#onStateChanged(int, int)}</li>
      * <li> {@link SoftApCallback#onConnectedClientsChanged(List<WifiClient>)}</li>
      * <li> {@link SoftApCallback#onInfoChanged(SoftApInfo)}</li>
+     * <li> {@link SoftApCallback#onInfoChanged(List<SoftApInfo>)}</li>
      * <li> {@link SoftApCallback#onCapabilityChanged(SoftApCapability)}</li>
      * </ul>
+     *
+     * Use {@link SoftApCallback#onConnectedClientsChanged(SoftApInfo, List<WifiClient>)} to know
+     * if there are any clients connected to a specific bridged instance of this AP
+     * (if bridged AP is enabled).
+     *
+     * Note: Caller will receive the callback
+     * {@link SoftApCallback#onConnectedClientsChanged(SoftApInfo, List<WifiClient>)}
+     * on registration when there are clients connected to AP.
+     *
      * These will be dispatched on registration to provide the caller with the current state
      * (and are not an indication of any current change). Note that receiving an immediate
      * WIFI_AP_STATE_FAILED value for soft AP state indicates that the latest attempt to start
@@ -3804,17 +4867,23 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_SETTINGS,
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.OVERRIDE_WIFI_CONFIG
+    })
     public void registerSoftApCallback(@NonNull @CallbackExecutor Executor executor,
             @NonNull SoftApCallback callback) {
         if (executor == null) throw new IllegalArgumentException("executor cannot be null");
         if (callback == null) throw new IllegalArgumentException("callback cannot be null");
         Log.v(TAG, "registerSoftApCallback: callback=" + callback + ", executor=" + executor);
 
-        Binder binder = new Binder();
         try {
-            mService.registerSoftApCallback(
-                    binder, new SoftApCallbackProxy(executor, callback), callback.hashCode());
+            synchronized (sSoftApCallbackMap) {
+                ISoftApCallback.Stub binderCallback = new SoftApCallbackProxy(executor, callback);
+                sSoftApCallbackMap.put(System.identityHashCode(callback), binderCallback);
+                mService.registerSoftApCallback(binderCallback);
+            }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -3829,13 +4898,25 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_SETTINGS,
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.OVERRIDE_WIFI_CONFIG
+    })
     public void unregisterSoftApCallback(@NonNull SoftApCallback callback) {
         if (callback == null) throw new IllegalArgumentException("callback cannot be null");
         Log.v(TAG, "unregisterSoftApCallback: callback=" + callback);
 
         try {
-            mService.unregisterSoftApCallback(callback.hashCode());
+            synchronized (sSoftApCallbackMap) {
+                int callbackIdentifier = System.identityHashCode(callback);
+                if (!sSoftApCallbackMap.contains(callbackIdentifier)) {
+                    Log.w(TAG, "Unknown external callback " + callbackIdentifier);
+                    return;
+                }
+                mService.unregisterSoftApCallback(sSoftApCallbackMap.get(callbackIdentifier));
+                sSoftApCallbackMap.remove(callbackIdentifier);
+            }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -4181,14 +5262,11 @@
     private void connectInternal(@Nullable WifiConfiguration config, int networkId,
             @Nullable ActionListener listener) {
         ActionListenerProxy listenerProxy = null;
-        Binder binder = null;
         if (listener != null) {
             listenerProxy = new ActionListenerProxy("connect", mLooper, listener);
-            binder = new Binder();
         }
         try {
-            mService.connect(config, networkId, binder, listenerProxy,
-                    listener == null ? 0 : listener.hashCode());
+            mService.connect(config, networkId, listenerProxy);
         } catch (RemoteException e) {
             if (listenerProxy != null) listenerProxy.onFailure(ERROR);
         } catch (SecurityException e) {
@@ -4250,6 +5328,51 @@
     }
 
     /**
+     * Temporarily disable autojoin for all currently visible and provisioned (saved, suggested)
+     * wifi networks except merged carrier networks from the provided subscription ID.
+     *
+     * Disabled networks will get automatically re-enabled when they are out of range for a period
+     * of time, or after the maximum disable duration specified in the framework.
+     *
+     * Calling {@link #stopRestrictingAutoJoinToSubscriptionId()} will immediately re-enable
+     * autojoin on all disabled networks.
+     *
+     * @param subscriptionId the subscription ID of the carrier whose merged wifi networks won't be
+     *                       disabled {@link android.telephony.SubscriptionInfo#getSubscriptionId()}
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_SETTINGS,
+            android.Manifest.permission.NETWORK_SETUP_WIZARD})
+    @RequiresApi(Build.VERSION_CODES.S)
+    public void startRestrictingAutoJoinToSubscriptionId(int subscriptionId) {
+        try {
+            mService.startRestrictingAutoJoinToSubscriptionId(subscriptionId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Re-enable autojoin for all non carrier merged wifi networks temporarily disconnected by
+     * {@link #startRestrictingAutoJoinToSubscriptionId(int)}.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_SETTINGS,
+            android.Manifest.permission.NETWORK_SETUP_WIZARD})
+    @RequiresApi(Build.VERSION_CODES.S)
+    public void stopRestrictingAutoJoinToSubscriptionId() {
+        try {
+            mService.stopRestrictingAutoJoinToSubscriptionId();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Save the given network to the list of configured networks for the
      * foreground user. If the network already exists, the configuration
      * is updated. Any new network is enabled by default.
@@ -4279,14 +5402,11 @@
     public void save(@NonNull WifiConfiguration config, @Nullable ActionListener listener) {
         if (config == null) throw new IllegalArgumentException("config cannot be null");
         ActionListenerProxy listenerProxy = null;
-        Binder binder = null;
         if (listener != null) {
             listenerProxy = new ActionListenerProxy("save", mLooper, listener);
-            binder = new Binder();
         }
         try {
-            mService.save(config, binder, listenerProxy,
-                    listener == null ? 0 : listener.hashCode());
+            mService.save(config, listenerProxy);
         } catch (RemoteException e) {
             if (listenerProxy != null) listenerProxy.onFailure(ERROR);
         } catch (SecurityException e) {
@@ -4316,14 +5436,11 @@
     public void forget(int netId, @Nullable ActionListener listener) {
         if (netId < 0) throw new IllegalArgumentException("Network id cannot be negative");
         ActionListenerProxy listenerProxy = null;
-        Binder binder = null;
         if (listener != null) {
             listenerProxy = new ActionListenerProxy("forget", mLooper, listener);
-            binder = new Binder();
         }
         try {
-            mService.forget(netId, binder, listenerProxy,
-                    listener == null ? 0 : listener.hashCode());
+            mService.forget(netId, listenerProxy);
         } catch (RemoteException e) {
             if (listenerProxy != null) listenerProxy.onFailure(ERROR);
         } catch (SecurityException e) {
@@ -4948,7 +6065,23 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
     public void setVerboseLoggingEnabled(boolean enable) {
-        enableVerboseLogging(enable ? 1 : 0);
+        enableVerboseLogging(enable ? VERBOSE_LOGGING_LEVEL_ENABLED
+                : VERBOSE_LOGGING_LEVEL_DISABLED);
+    }
+
+    /**
+     * Set Wi-Fi verbose logging level from developer settings.
+     *
+     * @param verbose the verbose logging mode which could be
+     * {@link #VERBOSE_LOGGING_LEVEL_DISABLED}, {@link #VERBOSE_LOGGING_LEVEL_ENABLED}, or
+     * {@link #VERBOSE_LOGGING_LEVEL_ENABLED_SHOW_KEY}.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+    public void setVerboseLoggingLevel(@VerboseLoggingLevel int verbose) {
+        enableVerboseLogging(verbose);
     }
 
     /** @hide */
@@ -4957,18 +6090,17 @@
             publicAlternatives = "Use {@code #setVerboseLoggingEnabled(boolean)} instead."
     )
     @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
-    public void enableVerboseLogging (int verbose) {
+    public void enableVerboseLogging(@VerboseLoggingLevel int verbose) {
         try {
             mService.enableVerboseLogging(verbose);
-        } catch (Exception e) {
-            //ignore any failure here
-            Log.e(TAG, "enableVerboseLogging " + e.toString());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
     }
 
     /**
      * Get the persisted Wi-Fi verbose logging level, set by
-     * {@link #setVerboseLoggingEnabled(boolean)}.
+     * {@link #setVerboseLoggingEnabled(boolean)} or {@link #setVerboseLoggingLevel(int)}.
      * No permissions are required to call this method.
      *
      * @return true to indicate that verbose logging is enabled, false to indicate that verbose
@@ -4981,13 +6113,19 @@
         return getVerboseLoggingLevel() > 0;
     }
 
-    /** @hide */
-    // TODO(b/145484145): remove once SUW stops calling this via reflection
-    @UnsupportedAppUsage(
-            maxTargetSdk = Build.VERSION_CODES.Q,
-            publicAlternatives = "Use {@code #isVerboseLoggingEnabled()} instead."
-    )
-    public int getVerboseLoggingLevel() {
+    /**
+     * Get the persisted Wi-Fi verbose logging level, set by
+     * {@link #setVerboseLoggingEnabled(boolean)} or {@link #setVerboseLoggingLevel(int)}.
+     * No permissions are required to call this method.
+     *
+     * @return one of {@link #VERBOSE_LOGGING_LEVEL_DISABLED},
+     *         {@link #VERBOSE_LOGGING_LEVEL_ENABLED},
+     *         or {@link #VERBOSE_LOGGING_LEVEL_ENABLED_SHOW_KEY}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public @VerboseLoggingLevel int getVerboseLoggingLevel() {
         try {
             return mService.getVerboseLoggingLevel();
         } catch (RemoteException e) {
@@ -5278,10 +6416,13 @@
         if (callback == null) throw new IllegalArgumentException("callback cannot be null");
         Log.v(TAG, "registerTrafficStateCallback: callback=" + callback + ", executor=" + executor);
 
-        Binder binder = new Binder();
         try {
-            mService.registerTrafficStateCallback(
-                    binder, new TrafficStateCallbackProxy(executor, callback), callback.hashCode());
+            synchronized (sTrafficStateCallbackMap) {
+                ITrafficStateCallback.Stub binderCallback = new TrafficStateCallbackProxy(executor,
+                        callback);
+                sTrafficStateCallbackMap.put(System.identityHashCode(callback), binderCallback);
+                mService.registerTrafficStateCallback(binderCallback);
+            }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -5301,7 +6442,16 @@
         Log.v(TAG, "unregisterTrafficStateCallback: callback=" + callback);
 
         try {
-            mService.unregisterTrafficStateCallback(callback.hashCode());
+            synchronized (sTrafficStateCallbackMap) {
+                int callbackIdentifier = System.identityHashCode(callback);
+                if (!sTrafficStateCallbackMap.contains(callbackIdentifier)) {
+                    Log.w(TAG, "Unknown external callback " + callbackIdentifier);
+                    return;
+                }
+                mService.unregisterTrafficStateCallback(
+                        sTrafficStateCallbackMap.get(callbackIdentifier));
+                sTrafficStateCallbackMap.remove(callbackIdentifier);
+            }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -5349,6 +6499,13 @@
     }
 
     /**
+     * @return true if this device supports Wi-Fi Easy Connect (DPP) Enrollee Responder mode.
+     */
+    public boolean isEasyConnectEnrolleeResponderModeSupported() {
+        return isFeatureSupported(WIFI_FEATURE_DPP_ENROLLEE_RESPONDER);
+    }
+
+    /**
      * @return true if this device supports WAPI.
      */
     public boolean isWapiSupported() {
@@ -5356,6 +6513,44 @@
     }
 
     /**
+     * @return true if this device supports WPA3 SAE Public Key.
+     */
+    public boolean isWpa3SaePublicKeySupported() {
+        // This feature is not fully implemented in the framework yet.
+        // After the feature complete, it returns whether WIFI_FEATURE_SAE_PK
+        // is supported or not directly.
+        return false;
+    }
+
+    /**
+     * @return true if this device supports Wi-Fi Passpoint Terms and Conditions feature.
+     */
+    public boolean isPasspointTermsAndConditionsSupported() {
+        return isFeatureSupported(WIFI_FEATURE_PASSPOINT_TERMS_AND_CONDITIONS);
+    }
+
+    /**
+     * @return true if this device supports WPA3 SAE Hash-to-Element.
+     */
+    public boolean isWpa3SaeH2eSupported() {
+        return isFeatureSupported(WIFI_FEATURE_SAE_H2E);
+    }
+
+    /**
+     * @return true if this device supports Wi-Fi Display R2.
+     */
+    public boolean isWifiDisplayR2Supported() {
+        return isFeatureSupported(WIFI_FEATURE_WFD_R2);
+    }
+
+    /**
+     * @return true if this device supports RFC 7542 decorated identity.
+     */
+    public boolean isDecoratedIdentitySupported() {
+        return isFeatureSupported(WIFI_FEATURE_DECORATED_IDENTITY);
+    }
+
+    /**
      * Gets the factory Wi-Fi MAC addresses.
      * @return Array of String representing Wi-Fi MAC addresses sorted lexically or an empty Array
      * if failed.
@@ -5468,6 +6663,110 @@
     public @interface EasyConnectNetworkRole {
     }
 
+    /** Easy Connect Device information maximum allowed length */
+    private static final int EASY_CONNECT_DEVICE_INFO_MAXIMUM_LENGTH = 40;
+
+    /**
+     * Easy Connect Cryptography Curve name: prime256v1
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int EASY_CONNECT_CRYPTOGRAPHY_CURVE_PRIME256V1 = 0;
+
+    /**
+     * Easy Connect Cryptography Curve name: secp384r1
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int EASY_CONNECT_CRYPTOGRAPHY_CURVE_SECP384R1 = 1;
+
+    /**
+     * Easy Connect Cryptography Curve name: secp521r1
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int EASY_CONNECT_CRYPTOGRAPHY_CURVE_SECP521R1 = 2;
+
+
+    /**
+     * Easy Connect Cryptography Curve name: brainpoolP256r1
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int EASY_CONNECT_CRYPTOGRAPHY_CURVE_BRAINPOOLP256R1 = 3;
+
+
+    /**
+     * Easy Connect Cryptography Curve name: brainpoolP384r1
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int EASY_CONNECT_CRYPTOGRAPHY_CURVE_BRAINPOOLP384R1 = 4;
+
+
+    /**
+     * Easy Connect Cryptography Curve name: brainpoolP512r1
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int EASY_CONNECT_CRYPTOGRAPHY_CURVE_BRAINPOOLP512R1 = 5;
+
+    /** @hide */
+    @IntDef(prefix = {"EASY_CONNECT_CRYPTOGRAPHY_CURVE_"}, value = {
+            EASY_CONNECT_CRYPTOGRAPHY_CURVE_PRIME256V1,
+            EASY_CONNECT_CRYPTOGRAPHY_CURVE_SECP384R1,
+            EASY_CONNECT_CRYPTOGRAPHY_CURVE_SECP521R1,
+            EASY_CONNECT_CRYPTOGRAPHY_CURVE_BRAINPOOLP256R1,
+            EASY_CONNECT_CRYPTOGRAPHY_CURVE_BRAINPOOLP384R1,
+            EASY_CONNECT_CRYPTOGRAPHY_CURVE_BRAINPOOLP512R1,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface EasyConnectCryptographyCurve {
+    }
+
+    /**
+     * Verbose logging mode: DISABLED.
+     * @hide
+     */
+    @SystemApi
+    public static final int VERBOSE_LOGGING_LEVEL_DISABLED = 0;
+
+    /**
+     * Verbose logging mode: ENABLED.
+     * @hide
+     */
+    @SystemApi
+    public static final int VERBOSE_LOGGING_LEVEL_ENABLED = 1;
+
+    /**
+     * Verbose logging mode: ENABLED_SHOW_KEY.
+     * This mode causes the Wi-Fi password and encryption keys to be output to the logcat.
+     * This is security sensitive information useful for debugging.
+     * This configuration is enabled for 30 seconds and then falls back to
+     * the regular verbose mode (i.e. to {@link VERBOSE_LOGGING_LEVEL_ENABLED}).
+     * Show key mode is not persistent, i.e. rebooting the device would fallback to
+     * the regular verbose mode.
+     * @hide
+     */
+    @SystemApi
+    public static final int VERBOSE_LOGGING_LEVEL_ENABLED_SHOW_KEY = 2;
+
+    /** @hide */
+    @IntDef(prefix = {"VERBOSE_LOGGING_LEVEL_"}, value = {
+            VERBOSE_LOGGING_LEVEL_DISABLED,
+            VERBOSE_LOGGING_LEVEL_ENABLED,
+            VERBOSE_LOGGING_LEVEL_ENABLED_SHOW_KEY,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface VerboseLoggingLevel {
+    }
+
     /**
      * Start Easy Connect (DPP) in Configurator-Initiator role. The current device will initiate
      * Easy Connect bootstrapping with a peer, and configure the peer with the SSID and password of
@@ -5490,8 +6789,9 @@
             @NonNull EasyConnectStatusCallback callback) {
         Binder binder = new Binder();
         try {
-            mService.startDppAsConfiguratorInitiator(binder, enrolleeUri, selectedNetworkId,
-                    enrolleeNetworkRole, new EasyConnectCallbackProxy(executor, callback));
+            mService.startDppAsConfiguratorInitiator(binder, mContext.getOpPackageName(),
+                    enrolleeUri, selectedNetworkId, enrolleeNetworkRole,
+                    new EasyConnectCallbackProxy(executor, callback));
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -5524,6 +6824,65 @@
     }
 
     /**
+     * Start Easy Connect (DPP) in Enrollee-Responder role.
+     * The device will:
+     * 1. Generate a DPP bootstrap URI and return it using the
+     * {@link EasyConnectStatusCallback#onBootstrapUriGenerated(Uri)} method.
+     * 2. Start DPP as a Responder, waiting for an Initiator device to start the DPP
+     * authentication process.
+     * The caller should use the URI provided in step #1, for instance display it as a QR code
+     * or communicate it in some other way to the initiator device.
+     *
+     * @param deviceInfo      Device specific information to add to the DPP URI. This field allows
+     *                        the users of the configurators to identify the device.
+     *                        Optional - if not provided or in case of an empty string,
+     *                        Info field (I:) will be skipped in the generated DPP URI.
+     *                        Allowed Range of ASCII characters in deviceInfo - %x20-7E.
+     *                        semicolon and space are not allowed. Due to the limitation of maximum
+     *                        allowed characters in QR code, framework adds a limit to maximum
+     *                        characters in deviceInfo. Users must call
+     *                        {@link WifiManager#getEasyConnectMaxAllowedResponderDeviceInfoLength()
+     *                        } method to know max allowed length. Violation of these rules will
+     *                        result in an exception.
+     * @param curve           Elliptic curve cryptography used to generate DPP
+     *                        public/private key pair. If application is not interested in a
+     *                        specific curve, use specification mandated NIST P-256 elliptic curve,
+     *                        {@link WifiManager#EASY_CONNECT_CRYPTOGRAPHY_CURVE_PRIME256V1}.
+     * @param callback        Callback for status updates
+     * @param executor        The Executor on which to run the callback.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_SETTINGS,
+            android.Manifest.permission.NETWORK_SETUP_WIZARD})
+    @RequiresApi(Build.VERSION_CODES.S)
+    public void startEasyConnectAsEnrolleeResponder(@Nullable String deviceInfo,
+            @EasyConnectCryptographyCurve int curve,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull EasyConnectStatusCallback callback) {
+        Binder binder = new Binder();
+        try {
+            mService.startDppAsEnrolleeResponder(binder, deviceInfo, curve,
+                    new EasyConnectCallbackProxy(executor, callback));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Maximum allowed length of Device specific information that can be added to the URI of
+     * Easy Connect responder device.
+     * @see #startEasyConnectAsEnrolleeResponder(String, int, Executor, EasyConnectStatusCallback)}
+     *
+     * @hide
+     */
+    @SystemApi
+    public static int getEasyConnectMaxAllowedResponderDeviceInfoLength() {
+        return EASY_CONNECT_DEVICE_INFO_MAXIMUM_LENGTH;
+    }
+
+    /**
      * Stop or abort a current Easy Connect (DPP) session. This call, once processed, will
      * terminate any ongoing transaction, and clean up all associated resources. Caller should not
      * expect any callbacks once this call is made. However, due to the asynchronous nature of
@@ -5597,6 +6956,19 @@
                 mEasyConnectStatusCallback.onProgress(status);
             });
         }
+
+        @Override
+        public void onBootstrapUriGenerated(@NonNull String uri) {
+            Log.d(TAG, "Easy Connect onBootstrapUriGenerated callback");
+            if (!SdkLevel.isAtLeastS()) {
+                Log.e(TAG, "Easy Connect bootstrap URI callback supported only on S+");
+                return;
+            }
+            Binder.clearCallingIdentity();
+            mExecutor.execute(() -> {
+                mEasyConnectStatusCallback.onBootstrapUriGenerated(Uri.parse(uri));
+            });
+        }
     }
 
     /**
@@ -5624,10 +6996,28 @@
     }
 
     /**
+     * Interface for Wi-Fi verbose logging status listener. Should be implemented by applications
+     * and set when calling {@link WifiManager#addWifiVerboseLoggingStatusListener(Executor,
+     * WifiVerboseLoggingStatusListener)}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public interface WifiVerboseLoggingStatusChangedListener {
+        /**
+         * Called when Wi-Fi verbose logging setting is updated.
+         *
+         * @param enabled true if verbose logging is enabled, false if verbose logging is disabled.
+         */
+        void onWifiVerboseLoggingStatusChanged(boolean enabled);
+    }
+
+    /**
      * Adds a listener for Wi-Fi usability statistics. See {@link OnWifiUsabilityStatsListener}.
      * Multiple listeners can be added. Callers will be invoked periodically by framework to
      * inform clients about the current Wi-Fi usability statistics. Callers can remove a previously
-     * added listener using {@link removeOnWifiUsabilityStatsListener}.
+     * added listener using
+     * {@link #removeOnWifiUsabilityStatsListener(OnWifiUsabilityStatsListener)}.
      *
      * @param executor The executor on which callback will be invoked.
      * @param listener Listener for Wifi usability statistics.
@@ -5644,22 +7034,25 @@
             Log.v(TAG, "addOnWifiUsabilityStatsListener: listener=" + listener);
         }
         try {
-            mService.addOnWifiUsabilityStatsListener(new Binder(),
-                    new IOnWifiUsabilityStatsListener.Stub() {
-                        @Override
-                        public void onWifiUsabilityStats(int seqNum, boolean isSameBssidAndFreq,
-                                WifiUsabilityStatsEntry stats) {
-                            if (mVerboseLoggingEnabled) {
-                                Log.v(TAG, "OnWifiUsabilityStatsListener: "
-                                        + "onWifiUsabilityStats: seqNum=" + seqNum);
+            synchronized (sOnWifiUsabilityStatsListenerMap) {
+                IOnWifiUsabilityStatsListener.Stub binderCallback =
+                        new IOnWifiUsabilityStatsListener.Stub() {
+                            @Override
+                            public void onWifiUsabilityStats(int seqNum, boolean isSameBssidAndFreq,
+                                    WifiUsabilityStatsEntry stats) {
+                                if (mVerboseLoggingEnabled) {
+                                    Log.v(TAG, "OnWifiUsabilityStatsListener: "
+                                            + "onWifiUsabilityStats: seqNum=" + seqNum);
+                                }
+                                Binder.clearCallingIdentity();
+                                executor.execute(() -> listener.onWifiUsabilityStats(
+                                        seqNum, isSameBssidAndFreq, stats));
                             }
-                            Binder.clearCallingIdentity();
-                            executor.execute(() -> listener.onWifiUsabilityStats(
-                                    seqNum, isSameBssidAndFreq, stats));
-                        }
-                    },
-                    listener.hashCode()
-            );
+                        };
+                sOnWifiUsabilityStatsListenerMap.put(System.identityHashCode(listener),
+                        binderCallback);
+                mService.addOnWifiUsabilityStatsListener(binderCallback);
+            }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -5681,7 +7074,16 @@
             Log.v(TAG, "removeOnWifiUsabilityStatsListener: listener=" + listener);
         }
         try {
-            mService.removeOnWifiUsabilityStatsListener(listener.hashCode());
+            synchronized (sOnWifiUsabilityStatsListenerMap) {
+                int listenerIdentifier = System.identityHashCode(listener);
+                if (!sOnWifiUsabilityStatsListenerMap.contains(listenerIdentifier)) {
+                    Log.w(TAG, "Unknown external callback " + listenerIdentifier);
+                    return;
+                }
+                mService.removeOnWifiUsabilityStatsListener(
+                        sOnWifiUsabilityStatsListenerMap.get(listenerIdentifier));
+                sOnWifiUsabilityStatsListenerMap.remove(listenerIdentifier);
+            }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -5771,7 +7173,6 @@
                 executor.execute(callback::onScanResultsAvailable);
             }
         }
-
     }
 
     /**
@@ -5873,6 +7274,91 @@
     }
 
     /**
+     * Add a listener listening to wifi verbose logging changes.
+     * See {@link WifiVerboseLoggingStatusChangedListener}.
+     * Caller can remove a previously registered listener using
+     * {@link WifiManager#removeWifiVerboseLoggingStatusChangedListener(
+     * WifiVerboseLoggingStatusChangedListener)}
+     * Same caller can add multiple listeners to monitor the event.
+     * <p>
+     * Applications should have the
+     * {@link android.Manifest.permission#ACCESS_WIFI_STATE}.
+     * Callers without the permission will trigger a {@link java.lang.SecurityException}.
+     * <p>
+     * @param executor The executor to execute the listener of the {@code listener} object.
+     * @param listener listener for changes in wifi verbose logging.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(ACCESS_WIFI_STATE)
+    public void addWifiVerboseLoggingStatusChangedListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull WifiVerboseLoggingStatusChangedListener listener) {
+        if (listener == null) throw new IllegalArgumentException("Listener cannot be null");
+        if (executor == null) throw new IllegalArgumentException("Executor cannot be null");
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "addWifiVerboseLoggingStatusChangedListener listener=" + listener
+                    + ", executor=" + executor);
+        }
+        try {
+            synchronized (sWifiVerboseLoggingStatusChangedListenerMap) {
+                IWifiVerboseLoggingStatusChangedListener.Stub binderCallback =
+                        new IWifiVerboseLoggingStatusChangedListener.Stub() {
+                            @Override
+                            public void onStatusChanged(boolean enabled) {
+                                if (mVerboseLoggingEnabled) {
+                                    Log.v(TAG, "WifiVerboseLoggingStatusListener: "
+                                            + "onVerboseLoggingStatusChanged: enabled=" + enabled);
+                                }
+                                Binder.clearCallingIdentity();
+                                executor.execute(() -> listener.onWifiVerboseLoggingStatusChanged(
+                                        enabled));
+                            }
+                        };
+                sWifiVerboseLoggingStatusChangedListenerMap.put(System.identityHashCode(listener),
+                        binderCallback);
+                mService.addWifiVerboseLoggingStatusChangedListener(binderCallback);
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Allow callers to remove a previously registered listener.
+     * <p>
+     * Applications should have the
+     * {@link android.Manifest.permission#ACCESS_WIFI_STATE}.
+     * Callers without the permission will trigger a {@link java.lang.SecurityException}.
+     * <p>
+     * @param listener listener to remove.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(ACCESS_WIFI_STATE)
+    public void removeWifiVerboseLoggingStatusChangedListener(
+            @NonNull WifiVerboseLoggingStatusChangedListener listener) {
+        if (listener == null) throw new IllegalArgumentException("Listener cannot be null");
+        Log.v(TAG, "removeWifiVerboseLoggingStatusChangedListener: listener=" + listener);
+        try {
+            synchronized (sWifiVerboseLoggingStatusChangedListenerMap) {
+                int listenerIdentifier = System.identityHashCode(listener);
+                if (!sWifiVerboseLoggingStatusChangedListenerMap.contains(listenerIdentifier)) {
+                    Log.w(TAG, "Unknown external callback " + listenerIdentifier);
+                    return;
+                }
+                mService.removeWifiVerboseLoggingStatusChangedListener(
+                        sWifiVerboseLoggingStatusChangedListenerMap.get(listenerIdentifier));
+                sWifiVerboseLoggingStatusChangedListenerMap.remove(listenerIdentifier);
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Add a listener for suggestion networks. See {@link SuggestionConnectionStatusListener}.
      * Caller will receive the event when suggested network have connection failure.
      * Caller can remove a previously registered listener using
@@ -5897,9 +7383,14 @@
         Log.v(TAG, "addSuggestionConnectionStatusListener listener=" + listener
                 + ", executor=" + executor);
         try {
-            mService.registerSuggestionConnectionStatusListener(new Binder(),
-                    new SuggestionConnectionStatusListenerProxy(executor, listener),
-                    listener.hashCode(), mContext.getOpPackageName(), mContext.getAttributionTag());
+            synchronized (sSuggestionConnectionStatusListenerMap) {
+                ISuggestionConnectionStatusListener.Stub binderCallback =
+                        new SuggestionConnectionStatusListenerProxy(executor, listener);
+                sSuggestionConnectionStatusListenerMap.put(System.identityHashCode(listener),
+                        binderCallback);
+                mService.registerSuggestionConnectionStatusListener(binderCallback,
+                        mContext.getOpPackageName(), mContext.getAttributionTag());
+            }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -5918,8 +7409,17 @@
         if (listener == null) throw new IllegalArgumentException("Listener cannot be null");
         Log.v(TAG, "removeSuggestionConnectionStatusListener: listener=" + listener);
         try {
-            mService.unregisterSuggestionConnectionStatusListener(listener.hashCode(),
-                    mContext.getOpPackageName());
+            synchronized (sSuggestionConnectionStatusListenerMap) {
+                int listenerIdentifier = System.identityHashCode(listener);
+                if (!sSuggestionConnectionStatusListenerMap.contains(listenerIdentifier)) {
+                    Log.w(TAG, "Unknown external callback " + listenerIdentifier);
+                    return;
+                }
+                mService.unregisterSuggestionConnectionStatusListener(
+                        sSuggestionConnectionStatusListenerMap.get(listenerIdentifier),
+                        mContext.getOpPackageName());
+                sSuggestionConnectionStatusListenerMap.remove(listenerIdentifier);
+            }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -6007,7 +7507,13 @@
     @SystemApi
     public interface ScoreUpdateObserver {
         /**
-         * Called by applications to indicate network status.
+         * Called by applications to indicate network status. For applications targeting
+         * {@link android.os.Build.VERSION_CODES#S} or above: The score is not used to take action
+         * on network selection but for the purpose of Wifi metric collection only; Network
+         * selection is influenced by inputs from
+         * {@link ScoreUpdateObserver#notifyStatusUpdate(int, boolean)},
+         * {@link ScoreUpdateObserver#requestNudOperation(int, boolean)}, and
+         * {@link ScoreUpdateObserver#blocklistCurrentBssid(int)}.
          *
          * @param sessionId The ID to indicate current Wi-Fi network connection obtained from
          *                  {@link WifiConnectedNetworkScorer#onStart(int)}.
@@ -6025,6 +7531,40 @@
          *                  {@link WifiConnectedNetworkScorer#onStart(int)}.
          */
         void triggerUpdateOfWifiUsabilityStats(int sessionId);
+
+        /**
+         * Called by applications to indicate whether current Wi-Fi network is usable or not.
+         *
+         * @param sessionId The ID to indicate current Wi-Fi network connection obtained from
+         *                  {@link WifiConnectedNetworkScorer#onStart(int)}.
+         * @param isUsable The boolean representing whether current Wi-Fi network is usable, and it
+         *                 may be sent to ConnectivityService and used for setting default network.
+         *                 Populated by connected network scorer in applications.
+         */
+        @RequiresApi(Build.VERSION_CODES.S)
+        default void notifyStatusUpdate(int sessionId, boolean isUsable) {}
+
+        /**
+         * Called by applications to start a NUD (Neighbor Unreachability Detection) operation. The
+         * framework throttles NUD operations to no more frequently than every five seconds
+         * (see {@link WifiScoreReport#NUD_THROTTLE_MILLIS}). The framework keeps track of requests
+         * and executes them as soon as possible based on the throttling criteria.
+         *
+         * @param sessionId The ID to indicate current Wi-Fi network connection obtained from
+         *                  {@link WifiConnectedNetworkScorer#onStart(int)}.
+         */
+        @RequiresApi(Build.VERSION_CODES.S)
+        default void requestNudOperation(int sessionId) {}
+
+        /**
+         * Called by applications to blocklist currently connected BSSID. No blocklisting operation
+         * if called after disconnection.
+         *
+         * @param sessionId The ID to indicate current Wi-Fi network connection obtained from
+         *                  {@link WifiConnectedNetworkScorer#onStart(int)}.
+         */
+        @RequiresApi(Build.VERSION_CODES.S)
+        default void blocklistCurrentBssid(int sessionId) {}
     }
 
     /**
@@ -6056,6 +7596,42 @@
                 throw e.rethrowFromSystemServer();
             }
         }
+
+        @Override
+        public void notifyStatusUpdate(int sessionId, boolean isUsable) {
+            if (!SdkLevel.isAtLeastS()) {
+                throw new UnsupportedOperationException();
+            }
+            try {
+                mScoreUpdateObserver.notifyStatusUpdate(sessionId, isUsable);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        @Override
+        public void requestNudOperation(int sessionId) {
+            if (!SdkLevel.isAtLeastS()) {
+                throw new UnsupportedOperationException();
+            }
+            try {
+                mScoreUpdateObserver.requestNudOperation(sessionId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        @Override
+        public void blocklistCurrentBssid(int sessionId) {
+            if (!SdkLevel.isAtLeastS()) {
+                throw new UnsupportedOperationException();
+            }
+            try {
+                mScoreUpdateObserver.blocklistCurrentBssid(sessionId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
     }
 
     /**
@@ -6070,8 +7646,20 @@
         /**
          * Called by framework to indicate the start of a network connection.
          * @param sessionId The ID to indicate current Wi-Fi network connection.
+         * @deprecated This API is deprecated.  Please use
+         *             {@link WifiConnectedNetworkScorer#onStart(WifiConnectedSessionInfo)}.
          */
-        void onStart(int sessionId);
+        @Deprecated
+        default void onStart(int sessionId) {}
+
+        /**
+         * Called by framework to indicate the start of a network connection.
+         * @param sessionInfo The session information to indicate current Wi-Fi network connection.
+         *                    See {@link WifiConnectedSessionInfo}.
+         */
+        default void onStart(@NonNull WifiConnectedSessionInfo sessionInfo) {
+            onStart(sessionInfo.getSessionId());
+        }
 
         /**
          * Called by framework to indicate the end of a network connection.
@@ -6103,12 +7691,12 @@
         }
 
         @Override
-        public void onStart(int sessionId) {
+        public void onStart(@NonNull WifiConnectedSessionInfo sessionInfo) {
             if (mVerboseLoggingEnabled) {
-                Log.v(TAG, "WifiConnectedNetworkScorer: " + "onStart: sessionId=" + sessionId);
+                Log.v(TAG, "WifiConnectedNetworkScorer: " + "onStart: sessionInfo=" + sessionInfo);
             }
             Binder.clearCallingIdentity();
-            mExecutor.execute(() -> mScorer.onStart(sessionId));
+            mExecutor.execute(() -> mScorer.onStart(sessionInfo));
         }
 
         @Override
@@ -6269,4 +7857,304 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Sets the state of carrier offload on merged or unmerged networks for specified subscription.
+     *
+     * <p>
+     * When a subscription's carrier network offload is disabled, all network suggestions related to
+     * this subscription will not be considered for auto join.
+     * <p>
+     * If calling app want disable all carrier network offload from a specified subscription, should
+     * call this API twice to disable both merged and unmerged carrier network suggestions.
+     *
+     * @param subscriptionId See {@link SubscriptionInfo#getSubscriptionId()}.
+     * @param merged True for carrier merged network, false otherwise.
+     *               See {@link WifiNetworkSuggestion.Builder#setCarrierMerged(boolean)}
+     * @param enabled True for enable carrier network offload, false otherwise.
+     * @see #isCarrierNetworkOffloadEnabled(int, boolean)
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_SETTINGS,
+            android.Manifest.permission.NETWORK_SETUP_WIZARD})
+    public void setCarrierNetworkOffloadEnabled(int subscriptionId, boolean merged,
+            boolean enabled) {
+        try {
+            mService.setCarrierNetworkOffloadEnabled(subscriptionId, merged, enabled);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get the carrier network offload state for merged or unmerged networks for specified
+     * subscription.
+     * @param subscriptionId subscription ID see {@link SubscriptionInfo#getSubscriptionId()}
+     * @param merged True for carrier merged network, false otherwise.
+     *               See {@link WifiNetworkSuggestion.Builder#setCarrierMerged(boolean)}
+     * @return True to indicate that carrier network offload is enabled, false otherwise.
+     */
+    @RequiresPermission(ACCESS_WIFI_STATE)
+    public boolean isCarrierNetworkOffloadEnabled(int subscriptionId, boolean merged) {
+        try {
+            return mService.isCarrierNetworkOffloadEnabled(subscriptionId, merged);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Interface for network suggestion user approval status change listener.
+     * Should be implemented by applications and registered using
+     * {@link #addSuggestionUserApprovalStatusListener(Executor,
+     * SuggestionUserApprovalStatusListener)} (
+     */
+    public interface SuggestionUserApprovalStatusListener {
+
+        /**
+         * Called when the user approval status of the App has changed.
+         * @param status The current status code for the user approval. One of the
+         *               {@code STATUS_SUGGESTION_APPROVAL_} values.
+         */
+        void onUserApprovalStatusChange(@SuggestionUserApprovalStatus int status);
+    }
+
+    private class SuggestionUserApprovalStatusListenerProxy extends
+            ISuggestionUserApprovalStatusListener.Stub {
+        private final Executor mExecutor;
+        private final SuggestionUserApprovalStatusListener mListener;
+
+        SuggestionUserApprovalStatusListenerProxy(@NonNull Executor executor,
+                @NonNull SuggestionUserApprovalStatusListener listener) {
+            mExecutor = executor;
+            mListener = listener;
+        }
+
+        @Override
+        public void onUserApprovalStatusChange(int status) {
+            mExecutor.execute(() -> mListener.onUserApprovalStatusChange(status));
+        }
+
+    }
+
+    /**
+     * Add a listener for Wi-Fi network suggestion user approval status.
+     * See {@link SuggestionUserApprovalStatusListener}.
+     * Caller will receive a callback immediately after adding a listener and when the user approval
+     * status of the caller has changed.
+     * Caller can remove a previously registered listener using
+     * {@link WifiManager#removeSuggestionUserApprovalStatusListener(
+     * SuggestionUserApprovalStatusListener)}
+     * A caller can add multiple listeners to monitor the event.
+     * @param executor The executor to execute the listener of the {@code listener} object.
+     * @param listener listener for suggestion user approval status changes.
+     */
+    @RequiresPermission(ACCESS_WIFI_STATE)
+    public void addSuggestionUserApprovalStatusListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull SuggestionUserApprovalStatusListener listener) {
+        if (listener == null) throw new NullPointerException("Listener cannot be null");
+        if (executor == null) throw new NullPointerException("Executor cannot be null");
+        Log.v(TAG, "addSuggestionUserApprovalStatusListener listener=" + listener
+                + ", executor=" + executor);
+        try {
+            synchronized (sSuggestionUserApprovalStatusListenerMap) {
+                ISuggestionUserApprovalStatusListener.Stub binderCallback =
+                        new SuggestionUserApprovalStatusListenerProxy(executor, listener);
+                sSuggestionUserApprovalStatusListenerMap.put(System.identityHashCode(listener),
+                        binderCallback);
+                mService.addSuggestionUserApprovalStatusListener(binderCallback,
+                        mContext.getOpPackageName());
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+    }
+
+    /**
+     * Allow callers to remove a previously registered listener using
+     * {@link #addSuggestionUserApprovalStatusListener(Executor,
+     * SuggestionUserApprovalStatusListener)}. After calling this method,
+     * applications will no longer receive network suggestion user approval status change through
+     * that listener.
+     */
+    @RequiresPermission(ACCESS_WIFI_STATE)
+    public void removeSuggestionUserApprovalStatusListener(
+            @NonNull SuggestionUserApprovalStatusListener listener) {
+        if (listener == null) throw new IllegalArgumentException("Listener cannot be null");
+        Log.v(TAG, "removeSuggestionUserApprovalStatusListener: listener=" + listener);
+        try {
+            synchronized (sSuggestionUserApprovalStatusListenerMap) {
+                int listenerIdentifier = System.identityHashCode(listener);
+                if (!sSuggestionUserApprovalStatusListenerMap.contains(listenerIdentifier)) {
+                    Log.w(TAG, "Unknown external callback " + listenerIdentifier);
+                    return;
+                }
+                mService.removeSuggestionUserApprovalStatusListener(
+                        sSuggestionUserApprovalStatusListenerMap.get(listenerIdentifier),
+                        mContext.getOpPackageName());
+                sSuggestionUserApprovalStatusListenerMap.remove(listenerIdentifier);
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Indicates the start/end of an emergency scan request being processed by {@link WifiScanner}.
+     * The wifi stack should ensure that the wifi chip remains on for the duration of the scan.
+     * WifiScanner detects emergency scan requests via
+     * {@link WifiScanner.ScanSettings#ignoreLocationSettings} flag.
+     *
+     * If the wifi stack is off (because location & wifi toggles are off) when this indication is
+     * received, the wifi stack will temporarily move to a scan only mode. Since location toggle
+     * is off, only scan with
+     * {@link WifiScanner.ScanSettings#ignoreLocationSettings} flag set will be
+     * allowed to be processed for this duration.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
+    public void setEmergencyScanRequestInProgress(boolean inProgress) {
+        try {
+            mService.setEmergencyScanRequestInProgress(inProgress);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Enable or disable Wi-Fi scoring.  Wi-Fi network status is evaluated by Wi-Fi scoring
+     * {@link WifiScoreReport}. This API enables/disables Wi-Fi scoring to take action on network
+     * selection.
+     *
+     * @param enabled {@code true} to enable, {@code false} to disable.
+     * @return true The status of Wifi scoring is set successfully.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+    public boolean setWifiScoringEnabled(boolean enabled) {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "setWifiScoringEnabled: " + enabled);
+        }
+        try {
+            return mService.setWifiScoringEnabled(enabled);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Flush Passpoint ANQP cache, and clear pending ANQP requests. Allows the caller to reset the
+     * Passpoint ANQP state, if required.
+     *
+     * Notes:
+     * 1. Flushing the ANQP cache may cause delays in discovering and connecting to Passpoint
+     * networks.
+     * 2. Intended to be used by Device Owner (DO), Profile Owner (PO), Settings and provisioning
+     * apps.
+     */
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_SETTINGS,
+            android.Manifest.permission.NETWORK_MANAGED_PROVISIONING,
+            android.Manifest.permission.NETWORK_CARRIER_PROVISIONING
+            })
+    public void flushPasspointAnqpCache() {
+        try {
+            mService.flushPasspointAnqpCache(mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns a list of {@link WifiAvailableChannel} for the specified band and operational
+     * mode(s), that is allowed for the current regulatory domain. An empty list implies that there
+     * are no available channels for use.
+     *
+     * @param band one of the following band constants defined in {@code WifiScanner#WIFI_BAND_*}
+     *             constants.
+     *             1. {@code WifiScanner#WIFI_BAND_UNSPECIFIED} - no band specified; Looks for the
+     *                channels in all the available bands - 2.4 GHz, 5 GHz, 6 GHz and 60 GHz
+     *             2. {@code WifiScanner#WIFI_BAND_24_GHZ}
+     *             3. {@code WifiScanner#WIFI_BAND_5_GHZ_WITH_DFS}
+     *             4. {@code WifiScanner#WIFI_BAND_BOTH_WITH_DFS}
+     *             5. {@code WifiScanner#WIFI_BAND_6_GHZ}
+     *             6. {@code WifiScanner#WIFI_BAND_24_5_WITH_DFS_6_GHZ}
+     *             7. {@code WifiScanner#WIFI_BAND_60_GHZ}
+     *             8. {@code WifiScanner#WIFI_BAND_24_5_WITH_DFS_6_60_GHZ}
+     * @param mode Bitwise OR of {@code WifiAvailableChannel#OP_MODE_*} constants
+     *        e.g. {@link WifiAvailableChannel#OP_MODE_WIFI_AWARE}
+     * @return a list of {@link WifiAvailableChannel}
+     *
+     * @throws UnsupportedOperationException - if this API is not supported on this device
+     *         or IllegalArgumentException - if the band specified is not one among the list
+     *         of bands mentioned above.
+     * @hide
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    @SystemApi
+    @NonNull
+    @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+    public List<WifiAvailableChannel> getAllowedChannels(
+            @WifiScanner.WifiBand int band,
+            @WifiAvailableChannel.OpMode int mode) {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return mService.getUsableChannels(band, mode,
+                    WifiAvailableChannel.FILTER_REGULATORY);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns a list of {@link WifiAvailableChannel} for the specified band and operational
+     * mode(s) per the current regulatory domain and device-specific constraints such as concurrency
+     * state and interference due to other radios. An empty list implies that there are no available
+     * channels for use.
+     *
+     * @param band one of the following band constants defined in {@code WifiScanner#WIFI_BAND_*}
+     *             constants.
+     *             1. {@code WifiScanner#WIFI_BAND_UNSPECIFIED} - no band specified; Looks for the
+     *                channels in all the available bands - 2.4 GHz, 5 GHz, 6 GHz and 60 GHz
+     *             2. {@code WifiScanner#WIFI_BAND_24_GHZ}
+     *             3. {@code WifiScanner#WIFI_BAND_5_GHZ_WITH_DFS}
+     *             4. {@code WifiScanner#WIFI_BAND_BOTH_WITH_DFS}
+     *             5. {@code WifiScanner#WIFI_BAND_6_GHZ}
+     *             6. {@code WifiScanner#WIFI_BAND_24_5_WITH_DFS_6_GHZ}
+     *             7. {@code WifiScanner#WIFI_BAND_60_GHZ}
+     *             8. {@code WifiScanner#WIFI_BAND_24_5_WITH_DFS_6_60_GHZ}
+     * @param mode Bitwise OR of {@code WifiAvailableChannel#OP_MODE_*} constants
+     *        e.g. {@link WifiAvailableChannel#OP_MODE_WIFI_AWARE}
+     * @return a list of {@link WifiAvailableChannel}
+     *
+     * @throws UnsupportedOperationException - if this API is not supported on this device
+     *         or IllegalArgumentException - if the band specified is not one among the list
+     *         of bands mentioned above.
+     * @hide
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    @SystemApi
+    @NonNull
+    @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+    public List<WifiAvailableChannel> getUsableChannels(
+            @WifiScanner.WifiBand int band,
+            @WifiAvailableChannel.OpMode int mode) {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return mService.getUsableChannels(band, mode,
+                    WifiAvailableChannel.getUsableFilter());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/framework/java/android/net/wifi/WifiNetworkAgentSpecifier.java b/framework/java/android/net/wifi/WifiNetworkAgentSpecifier.java
index 0d13805..a39a83b 100644
--- a/framework/java/android/net/wifi/WifiNetworkAgentSpecifier.java
+++ b/framework/java/android/net/wifi/WifiNetworkAgentSpecifier.java
@@ -25,6 +25,7 @@
 import android.net.MatchAllNetworkSpecifier;
 import android.net.NetworkRequest;
 import android.net.NetworkSpecifier;
+import android.net.wifi.ScanResult.WifiBand;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -40,10 +41,31 @@
      */
     private final WifiConfiguration mWifiConfiguration;
 
-    public WifiNetworkAgentSpecifier(@NonNull WifiConfiguration wifiConfiguration) {
+    /**
+     * Band, as one of the ScanResult.WIFI_BAND_* constants.
+     */
+    @WifiBand private final int mBand;
+
+    /**
+     * Whether to match P2P requests.
+     *
+     * When matching against an instance of WifiNetworkSpecifier, simply matching SSID or
+     * BSSID patterns would let apps know if a given WiFi network's (B)SSID matches a pattern
+     * by simply filing a NetworkCallback with that pattern. Also, apps asking for a match on
+     * (B)SSID are apps requesting a P2P network, which involves protection with UI shown by
+     * the system, and the WifiNetworkSpecifiers for these P2P networks should never match
+     * an Internet-providing network to avoid calling back these apps on a network that happens
+     * to match their requested pattern but has not been brought up for them.
+     */
+    private final boolean mMatchLocalOnlySpecifiers;
+
+    public WifiNetworkAgentSpecifier(@NonNull WifiConfiguration wifiConfiguration,
+            @WifiBand int band, boolean matchLocalOnlySpecifiers) {
         checkNotNull(wifiConfiguration);
 
         mWifiConfiguration = wifiConfiguration;
+        mBand = band;
+        mMatchLocalOnlySpecifiers = matchLocalOnlySpecifiers;
     }
 
     /**
@@ -54,7 +76,10 @@
                 @Override
                 public WifiNetworkAgentSpecifier createFromParcel(@NonNull Parcel in) {
                     WifiConfiguration wifiConfiguration = in.readParcelable(null);
-                    return new WifiNetworkAgentSpecifier(wifiConfiguration);
+                    int band = in.readInt();
+                    boolean matchLocalOnlySpecifiers = in.readBoolean();
+                    return new WifiNetworkAgentSpecifier(wifiConfiguration, band,
+                            matchLocalOnlySpecifiers);
                 }
 
                 @Override
@@ -71,6 +96,8 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeParcelable(mWifiConfiguration, flags);
+        dest.writeInt(mBand);
+        dest.writeBoolean(mMatchLocalOnlySpecifiers);
     }
 
     @Override
@@ -104,6 +131,13 @@
         checkNotNull(this.mWifiConfiguration.BSSID);
         checkNotNull(this.mWifiConfiguration.allowedKeyManagement);
 
+        if (!mMatchLocalOnlySpecifiers) {
+            // Specifiers that match non-local-only networks only match against the band.
+            return ns.getBand() == mBand;
+        }
+        if (ns.getBand() != ScanResult.UNSPECIFIED && ns.getBand() != mBand) {
+            return false;
+        }
         final String ssidWithQuotes = this.mWifiConfiguration.SSID;
         checkState(ssidWithQuotes.startsWith("\"") && ssidWithQuotes.endsWith("\""));
         final String ssidWithoutQuotes = ssidWithQuotes.substring(1, ssidWithQuotes.length() - 1);
@@ -128,7 +162,9 @@
         return Objects.hash(
                 mWifiConfiguration.SSID,
                 mWifiConfiguration.BSSID,
-                mWifiConfiguration.allowedKeyManagement);
+                mWifiConfiguration.allowedKeyManagement,
+                mBand,
+                mMatchLocalOnlySpecifiers);
     }
 
     @Override
@@ -143,7 +179,9 @@
         return Objects.equals(this.mWifiConfiguration.SSID, lhs.mWifiConfiguration.SSID)
                 && Objects.equals(this.mWifiConfiguration.BSSID, lhs.mWifiConfiguration.BSSID)
                 && Objects.equals(this.mWifiConfiguration.allowedKeyManagement,
-                    lhs.mWifiConfiguration.allowedKeyManagement);
+                    lhs.mWifiConfiguration.allowedKeyManagement)
+                && mBand == lhs.mBand
+                && mMatchLocalOnlySpecifiers == lhs.mMatchLocalOnlySpecifiers;
     }
 
     @Override
@@ -152,6 +190,8 @@
         sb.append("WifiConfiguration=")
                 .append(", SSID=").append(mWifiConfiguration.SSID)
                 .append(", BSSID=").append(mWifiConfiguration.BSSID)
+                .append(", band=").append(mBand)
+                .append(", mMatchLocalOnlySpecifiers=").append(mMatchLocalOnlySpecifiers)
                 .append("]");
         return sb.toString();
     }
diff --git a/framework/java/android/net/wifi/WifiNetworkConnectionStatistics.java b/framework/java/android/net/wifi/WifiNetworkConnectionStatistics.java
index 95b2e77..65f7e68 100644
--- a/framework/java/android/net/wifi/WifiNetworkConnectionStatistics.java
+++ b/framework/java/android/net/wifi/WifiNetworkConnectionStatistics.java
@@ -24,7 +24,9 @@
 /**
  * Connection Statistics For a WiFi Network.
  * @hide
+ * @deprecated This is no longer supported.
  */
+@Deprecated
 @SystemApi
 public class WifiNetworkConnectionStatistics implements Parcelable {
     private static final String TAG = "WifiNetworkConnnectionStatistics";
diff --git a/framework/java/android/net/wifi/WifiNetworkSpecifier.java b/framework/java/android/net/wifi/WifiNetworkSpecifier.java
index e12bb91..045a9a4 100644
--- a/framework/java/android/net/wifi/WifiNetworkSpecifier.java
+++ b/framework/java/android/net/wifi/WifiNetworkSpecifier.java
@@ -20,27 +20,85 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
 import android.net.MacAddress;
+import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.NetworkSpecifier;
+import android.net.wifi.ScanResult.WifiBand;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.PatternMatcher;
 import android.text.TextUtils;
 import android.util.Pair;
 
+import com.android.modules.utils.build.SdkLevel;
+
 import java.nio.charset.CharsetEncoder;
 import java.nio.charset.StandardCharsets;
 import java.util.Objects;
 
 /**
- * Network specifier object used to request a local Wi-Fi network. Apps should use the
+ * Network specifier object used to request a Wi-Fi network. Apps should use the
  * {@link WifiNetworkSpecifier.Builder} class to create an instance.
+ * <p>
+ * This specifier can be used to request a local-only connection on devices that support concurrent
+ * connections (indicated via
+ * {@link WifiManager#isStaConcurrencyForLocalOnlyConnectionsSupported()} and if the initiating app
+ * targets SDK &ge; {@link android.os.Build.VERSION_CODES#S} or is a system app. These local-only
+ * connections may be brought up as a secondary concurrent connection (primary connection will be
+ * used for networks with internet connectivity available to the user and all apps).
+ * </p>
+ * <p>
+ * This specifier can also be used to listen for connected Wi-Fi networks on a particular band.
+ * Additionally, some devices may support requesting a connection to a particular band. If the
+ * device does not support such a request, it will send {@link NetworkCallback#onUnavailable()}
+ * upon request to the callback passed to
+ * {@link ConnectivityManager#requestNetwork(NetworkRequest, NetworkCallback)} or equivalent.
+ * See {@link Builder#build()} for details.
+ * </p>
  */
 public final class WifiNetworkSpecifier extends NetworkSpecifier implements Parcelable {
     private static final String TAG = "WifiNetworkSpecifier";
 
     /**
+     * Returns the band for a given frequency in MHz.
+     * @hide
+     */
+    @WifiBand public static int getBand(final int freqMHz) {
+        if (ScanResult.is24GHz(freqMHz)) {
+            return ScanResult.WIFI_BAND_24_GHZ;
+        } else if (ScanResult.is5GHz(freqMHz)) {
+            return ScanResult.WIFI_BAND_5_GHZ;
+        } else if (ScanResult.is6GHz(freqMHz)) {
+            return ScanResult.WIFI_BAND_6_GHZ;
+        } else if (ScanResult.is60GHz(freqMHz)) {
+            return ScanResult.WIFI_BAND_60_GHZ;
+        }
+        return ScanResult.UNSPECIFIED;
+    }
+
+    /**
+     * Validates that the passed band is a valid band
+     * @param band the band to check
+     * @return true if the band is valid, false otherwise
+     * @hide
+     */
+    public static boolean validateBand(@WifiBand int band) {
+        switch (band) {
+            case ScanResult.UNSPECIFIED:
+            case ScanResult.WIFI_BAND_24_GHZ:
+            case ScanResult.WIFI_BAND_5_GHZ:
+            case ScanResult.WIFI_BAND_6_GHZ:
+            case ScanResult.WIFI_BAND_60_GHZ:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /**
      * Builder used to create {@link WifiNetworkSpecifier} objects.
      */
     public static final class Builder {
@@ -56,6 +114,16 @@
                 MacAddress.BROADCAST_ADDRESS;
 
         /**
+         * Set WPA Enterprise type according to certificate security level.
+         * This is for backward compatibility in R.
+         */
+        private static final int WPA3_ENTERPRISE_AUTO = 0;
+        /** Set WPA Enterprise type to standard mode only. */
+        private static final int WPA3_ENTERPRISE_STANDARD = 1;
+        /** Set WPA Enterprise type to 192 bit mode only. */
+        private static final int WPA3_ENTERPRISE_192_BIT = 2;
+
+        /**
          * SSID pattern match specified by the app.
          */
         private @Nullable PatternMatcher mSsidPatternMatcher;
@@ -87,10 +155,18 @@
          */
         private @Nullable WifiEnterpriseConfig mWpa3EnterpriseConfig;
         /**
+         * Indicate what type this WPA3-Enterprise network is.
+         */
+        private int mWpa3EnterpriseType = WPA3_ENTERPRISE_AUTO;
+        /**
          * This is a network that does not broadcast its SSID, so an
          * SSID-specific probe request must be used for scans.
          */
         private boolean mIsHiddenSSID;
+        /**
+         * The requested band for this connection, or BAND_UNSPECIFIED.
+         */
+        @WifiBand private int mBand;
 
         public Builder() {
             mSsidPatternMatcher = null;
@@ -101,6 +177,7 @@
             mWpa2EnterpriseConfig = null;
             mWpa3EnterpriseConfig = null;
             mIsHiddenSSID = false;
+            mBand = ScanResult.UNSPECIFIED;
         }
 
         /**
@@ -249,9 +326,14 @@
          * sha384WithRSAEncryption (OID 1.2.840.113549.1.1.12) or ecdsa-with-SHA384
          * (OID 1.2.840.10045.4.3.3).
          *
+         * @deprecated use {@link #setWpa3EnterpriseStandardModeConfig(WifiEnterpriseConfig)} or
+         * {@link #setWpa3Enterprise192BitModeConfig(WifiEnterpriseConfig)} to specify
+         * WPA3-Enterprise type explicitly.
+         *
          * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}.
          * @return Instance of {@link Builder} to enable chaining of the builder method.
          */
+        @Deprecated
         public @NonNull Builder setWpa3EnterpriseConfig(
                 @NonNull WifiEnterpriseConfig enterpriseConfig) {
             checkNotNull(enterpriseConfig);
@@ -260,6 +342,58 @@
         }
 
         /**
+         * Set the associated enterprise configuration for this network. Needed for authenticating
+         * to standard WPA3-Enterprise networks. See {@link WifiEnterpriseConfig} for description.
+         * For WPA3-Enterprise in 192-bit security mode networks,
+         * see {@link #setWpa3Enterprise192BitModeConfig(WifiEnterpriseConfig)} for description.
+         *
+         * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}.
+         * @return Instance of {@link Builder} to enable chaining of the builder method.
+         */
+        public @NonNull Builder setWpa3EnterpriseStandardModeConfig(
+                @NonNull WifiEnterpriseConfig enterpriseConfig) {
+            checkNotNull(enterpriseConfig);
+            mWpa3EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig);
+            mWpa3EnterpriseType = WPA3_ENTERPRISE_STANDARD;
+            return this;
+        }
+
+        /**
+         * Set the associated enterprise configuration for this network. Needed for authenticating
+         * to WPA3-Enterprise in 192-bit security mode networks. See {@link WifiEnterpriseConfig}
+         * for description. Both the client and CA certificates must be provided,
+         * and must be of type of either sha384WithRSAEncryption with key length of 3072bit or
+         * more (OID 1.2.840.113549.1.1.12), or ecdsa-with-SHA384 with key length of 384bit or
+         * more (OID 1.2.840.10045.4.3.3).
+         *
+         * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}.
+         * @return Instance of {@link Builder} to enable chaining of the builder method.
+         * @throws IllegalArgumentException if the EAP type or certificates do not
+         *                                  meet 192-bit mode requirements.
+         */
+        public @NonNull Builder setWpa3Enterprise192BitModeConfig(
+                @NonNull WifiEnterpriseConfig enterpriseConfig) {
+            checkNotNull(enterpriseConfig);
+            if (enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.TLS) {
+                throw new IllegalArgumentException("The 192-bit mode network type must be TLS");
+            }
+            if (!WifiEnterpriseConfig.isSuiteBCipherCert(
+                    enterpriseConfig.getClientCertificate())) {
+                throw new IllegalArgumentException(
+                    "The client certificate does not meet 192-bit mode requirements.");
+            }
+            if (!WifiEnterpriseConfig.isSuiteBCipherCert(
+                    enterpriseConfig.getCaCertificate())) {
+                throw new IllegalArgumentException(
+                    "The CA certificate does not meet 192-bit mode requirements.");
+            }
+
+            mWpa3EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig);
+            mWpa3EnterpriseType = WPA3_ENTERPRISE_192_BIT;
+            return this;
+        }
+
+        /**
          * Specifies whether this represents a hidden network.
          * <p>
          * <li>Setting this disallows the usage of {@link #setSsidPattern(PatternMatcher)} since
@@ -275,6 +409,23 @@
             return this;
         }
 
+        /**
+         * Specifies the band requested for this network.
+         *
+         * Only a single band can be requested. An app can file multiple callbacks concurrently
+         * if they need to know about multiple bands.
+         *
+         * @param band The requested band.
+         * @return Instance of {@link Builder} to enable chaining of the builder method.
+         */
+        public @NonNull Builder setBand(@WifiBand int band) {
+            if (!validateBand(band)) {
+                throw new IllegalArgumentException("Unexpected band in setBand : " + band);
+            }
+            mBand = band;
+            return this;
+        }
+
         private void setSecurityParamsInWifiConfiguration(
                 @NonNull WifiConfiguration configuration) {
             if (!TextUtils.isEmpty(mWpa2PskPassphrase)) { // WPA-PSK network.
@@ -289,23 +440,23 @@
                 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
                 configuration.enterpriseConfig = mWpa2EnterpriseConfig;
             } else if (mWpa3EnterpriseConfig != null) { // WPA3-Enterprise
-                if (mWpa3EnterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.TLS
+                if (mWpa3EnterpriseType == WPA3_ENTERPRISE_AUTO
+                        && mWpa3EnterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.TLS
                         && WifiEnterpriseConfig.isSuiteBCipherCert(
                         mWpa3EnterpriseConfig.getClientCertificate())
                         && WifiEnterpriseConfig.isSuiteBCipherCert(
                         mWpa3EnterpriseConfig.getCaCertificate())) {
-                    // WPA3-Enterprise in 192-bit security mode (Suite-B)
-                    configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B);
+                    // WPA3-Enterprise in 192-bit security mode
+                    configuration.setSecurityParams(
+                            WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT);
+                } else if (mWpa3EnterpriseType == WPA3_ENTERPRISE_192_BIT) {
+                    // WPA3-Enterprise in 192-bit security mode
+                    configuration.setSecurityParams(
+                            WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT);
                 } else {
                     // WPA3-Enterprise
-                    configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
-                    configuration.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
-                    configuration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
-                    configuration.allowedPairwiseCiphers.set(
-                            WifiConfiguration.PairwiseCipher.GCMP_256);
-                    configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
-                    configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.GCMP_256);
-                    configuration.requirePmf = true;
+                    configuration.setSecurityParams(
+                            WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
                 }
                 configuration.enterpriseConfig = mWpa3EnterpriseConfig;
             } else if (mIsEnhancedOpen) { // OWE network
@@ -384,14 +535,27 @@
         }
 
         /**
-         * Create a specifier object used to request a local Wi-Fi network. The generated
+         * Create a specifier object used to request a Wi-Fi network. The generated
          * {@link NetworkSpecifier} should be used in
          * {@link NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} when building
-         * the {@link NetworkRequest}. These specifiers can only be used to request a local wifi
-         * network (i.e no internet capability). So, the device will not switch it's default route
-         * to wifi if there are other transports (cellular for example) available.
+         * the {@link NetworkRequest}.
+         *
          *<p>
-         * Note: Apps can set a combination of network match params:
+         * When using with {@link ConnectivityManager#requestNetwork(NetworkRequest,
+         * NetworkCallback)} or variants, note that some devices may not support requesting a
+         * network with all combinations of specifier members. For example, some devices may only
+         * support requesting local-only networks (networks without the
+         * {@link NetworkCapabilities#NET_CAPABILITY_INTERNET} capability), or not support
+         * requesting a particular band. However, there are no restrictions when using
+         * {@link ConnectivityManager#registerNetworkCallback(NetworkRequest, NetworkCallback)}
+         * or other similar methods which monitor but do not request networks.
+         *
+         * If the device can't support a request, the app will receive a call to
+         * {@link NetworkCallback#onUnavailable()}.
+         *</p>
+         *
+         *<p>
+         * When requesting a local-only network, apps can set a combination of network match params:
          * <li> SSID Pattern using {@link #setSsidPattern(PatternMatcher)} OR Specific SSID using
          * {@link #setSsid(String)}. </li>
          * AND/OR
@@ -401,6 +565,12 @@
          * The system will find the set of networks matching the request and present the user
          * with a system dialog which will allow the user to select a specific Wi-Fi network to
          * connect to or to deny the request.
+         *
+         * To protect user privacy, some limitations to the ability of matching patterns apply.
+         * In particular, when the system brings up a network to satisfy a {@link NetworkRequest}
+         * from some app, the system reserves the right to decline matching the SSID pattern to
+         * the real SSID of the network for other apps than the app that requested the network, and
+         * not send those callbacks even if the SSID matches the requested pattern.
          *</p>
          *
          * For example:
@@ -409,7 +579,7 @@
          * <pre>{@code
          * final NetworkSpecifier specifier =
          *      new Builder()
-         *      .setSsidPattern(new PatternMatcher("test", PatterMatcher.PATTERN_PREFIX))
+         *      .setSsidPattern(new PatternMatcher("test", PatternMatcher.PATTERN_PREFIX))
          *      .setBssidPattern(MacAddress.fromString("10:03:23:00:00:00"),
          *                       MacAddress.fromString("ff:ff:ff:00:00:00"))
          *      .build()
@@ -434,15 +604,15 @@
          * @throws IllegalStateException on invalid params set.
          */
         public @NonNull WifiNetworkSpecifier build() {
-            if (!hasSetAnyPattern()) {
+            if (!hasSetAnyPattern() && mBand == ScanResult.UNSPECIFIED) {
                 throw new IllegalStateException("one of setSsidPattern/setSsid/setBssidPattern/"
-                        + "setBssid should be invoked for specifier");
+                        + "setBssid/setBand should be invoked for specifier");
             }
             setMatchAnyPatternIfUnset();
             if (hasSetMatchNonePattern()) {
                 throw new IllegalStateException("cannot set match-none pattern for specifier");
             }
-            if (hasSetMatchAllPattern()) {
+            if (hasSetMatchAllPattern() && mBand == ScanResult.UNSPECIFIED) {
                 throw new IllegalStateException("cannot set match-all pattern for specifier");
             }
             if (mIsHiddenSSID && mSsidPatternMatcher.getType() != PatternMatcher.PATTERN_LITERAL) {
@@ -454,6 +624,7 @@
             return new WifiNetworkSpecifier(
                     mSsidPatternMatcher,
                     mBssidPatternMatcher,
+                    mBand,
                     buildWifiConfiguration());
         }
     }
@@ -472,6 +643,11 @@
     public final Pair<MacAddress, MacAddress> bssidPatternMatcher;
 
     /**
+     * The band for this Wi-Fi network.
+     */
+    @WifiBand private final int mBand;
+
+    /**
      * Security credentials for the network.
      * <p>
      * Note: {@link WifiConfiguration#SSID} & {@link WifiConfiguration#BSSID} fields from
@@ -490,6 +666,7 @@
     /** @hide */
     public WifiNetworkSpecifier(@NonNull PatternMatcher ssidPatternMatcher,
                                 @NonNull Pair<MacAddress, MacAddress> bssidPatternMatcher,
+                                @WifiBand int band,
                                 @NonNull WifiConfiguration wifiConfiguration) {
         checkNotNull(ssidPatternMatcher);
         checkNotNull(bssidPatternMatcher);
@@ -497,9 +674,17 @@
 
         this.ssidPatternMatcher = ssidPatternMatcher;
         this.bssidPatternMatcher = bssidPatternMatcher;
+        this.mBand = band;
         this.wifiConfiguration = wifiConfiguration;
     }
 
+    /**
+     * The band for this Wi-Fi network specifier.
+     */
+    @WifiBand public int getBand() {
+        return mBand;
+    }
+
     public static final @NonNull Creator<WifiNetworkSpecifier> CREATOR =
             new Creator<WifiNetworkSpecifier>() {
                 @Override
@@ -509,8 +694,9 @@
                     MacAddress mask = in.readParcelable(null);
                     Pair<MacAddress, MacAddress> bssidPatternMatcher =
                             Pair.create(baseAddress, mask);
+                    int band = in.readInt();
                     WifiConfiguration wifiConfiguration = in.readParcelable(null);
-                    return new WifiNetworkSpecifier(ssidPatternMatcher, bssidPatternMatcher,
+                    return new WifiNetworkSpecifier(ssidPatternMatcher, bssidPatternMatcher, band,
                             wifiConfiguration);
                 }
 
@@ -530,6 +716,7 @@
         dest.writeParcelable(ssidPatternMatcher, flags);
         dest.writeParcelable(bssidPatternMatcher.first, flags);
         dest.writeParcelable(bssidPatternMatcher.second, flags);
+        dest.writeInt(mBand);
         dest.writeParcelable(wifiConfiguration, flags);
     }
 
@@ -537,7 +724,7 @@
     public int hashCode() {
         return Objects.hash(
                 ssidPatternMatcher.getPath(), ssidPatternMatcher.getType(), bssidPatternMatcher,
-                wifiConfiguration.allowedKeyManagement);
+                mBand, wifiConfiguration.allowedKeyManagement);
     }
 
     @Override
@@ -555,6 +742,7 @@
                     lhs.ssidPatternMatcher.getType())
                 && Objects.equals(this.bssidPatternMatcher,
                     lhs.bssidPatternMatcher)
+                && this.mBand == lhs.mBand
                 && Objects.equals(this.wifiConfiguration.allowedKeyManagement,
                     lhs.wifiConfiguration.allowedKeyManagement);
     }
@@ -567,6 +755,7 @@
                 .append(", BSSID Match pattern=").append(bssidPatternMatcher)
                 .append(", SSID=").append(wifiConfiguration.SSID)
                 .append(", BSSID=").append(wifiConfiguration.BSSID)
+                .append(", band=").append(mBand)
                 .append("]")
                 .toString();
     }
@@ -581,4 +770,13 @@
         // not make much sense!
         return equals(other);
     }
+
+    /** @hide */
+    @Override
+    @Nullable
+    public NetworkSpecifier redact() {
+        if (!SdkLevel.isAtLeastS()) return this;
+
+        return new Builder().setBand(mBand).build();
+    }
 }
diff --git a/framework/java/android/net/wifi/WifiNetworkSuggestion.java b/framework/java/android/net/wifi/WifiNetworkSuggestion.java
index d8be1d2..fa288a03 100644
--- a/framework/java/android/net/wifi/WifiNetworkSuggestion.java
+++ b/framework/java/android/net/wifi/WifiNetworkSuggestion.java
@@ -18,18 +18,30 @@
 
 import static com.android.internal.util.Preconditions.checkNotNull;
 
+import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.net.MacAddress;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
 import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 
+import androidx.annotation.RequiresApi;
+
+import com.android.modules.utils.build.SdkLevel;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.nio.charset.CharsetEncoder;
 import java.nio.charset.StandardCharsets;
 import java.util.List;
@@ -44,6 +56,24 @@
  * {@link WifiManager#addNetworkSuggestions(List)}.
  */
 public final class WifiNetworkSuggestion implements Parcelable {
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"RANDOMIZATION_"}, value = {
+            RANDOMIZATION_PERSISTENT,
+            RANDOMIZATION_NON_PERSISTENT})
+    public @interface MacRandomizationSetting {}
+    /**
+     * Generate a randomized MAC from a secret seed and information from the Wi-Fi configuration
+     * (SSID or Passpoint profile) and reuse it for all connections to this network. The
+     * randomized MAC address for this network will stay the same for each subsequent association
+     * until the device undergoes factory reset.
+     */
+    public static final int RANDOMIZATION_PERSISTENT = 0;
+    /**
+     * With this option, the randomized MAC address will periodically get re-randomized, and
+     * the randomized MAC address will change if the suggestion is removed and then added back.
+     */
+    public static final int RANDOMIZATION_NON_PERSISTENT = 1;
     /**
      * Builder used to create {@link WifiNetworkSuggestion} objects.
      */
@@ -51,6 +81,16 @@
         private static final int UNASSIGNED_PRIORITY = -1;
 
         /**
+         * Set WPA Enterprise type according to certificate security level.
+         * This is for backward compatibility in R.
+         */
+        private static final int WPA3_ENTERPRISE_AUTO = 0;
+        /** Set WPA Enterprise type to standard mode only. */
+        private static final int WPA3_ENTERPRISE_STANDARD = 1;
+        /** Set WPA Enterprise type to 192 bit mode only. */
+        private static final int WPA3_ENTERPRISE_192_BIT = 2;
+
+        /**
          * SSID of the network.
          */
         private String mSsid;
@@ -81,6 +121,10 @@
          */
         private @Nullable WifiEnterpriseConfig mWpa3EnterpriseConfig;
         /**
+         * Indicate what type this WPA3-Enterprise network is.
+         */
+        private int mWpa3EnterpriseType = WPA3_ENTERPRISE_AUTO;
+        /**
          * The passpoint config for use with Hotspot 2.0 network
          */
         private @Nullable PasspointConfiguration mPasspointConfiguration;
@@ -102,10 +146,15 @@
          */
         private int mMeteredOverride;
         /**
-         * Priority of this network among other network suggestions provided by the app.
-         * The lower the number, the higher the priority (i.e value of 0 = highest priority).
+         * Priority of this network among other network suggestions from same priority group
+         * provided by the app.
+         * The higher the number, the higher the priority (i.e value of 0 = lowest priority).
          */
         private int mPriority;
+        /**
+         * Priority group ID, while suggestion priority will only effect inside the priority group.
+         */
+        private int mPriorityGroup;
 
         /**
          * The carrier ID identifies the operator who provides this network configuration.
@@ -114,6 +163,12 @@
         private int mCarrierId;
 
         /**
+         * The Subscription ID identifies the SIM card for which this network configuration is
+         * valid.
+         */
+        private int mSubscriptionId;
+
+        /**
          * Whether this network is shared credential with user to allow user manually connect.
          */
         private boolean mIsSharedWithUser;
@@ -144,6 +199,33 @@
          */
         private boolean mIsNetworkUntrusted;
 
+        /**
+         * Whether this network will be brought up as OEM paid (OEM_PAID capability bit added).
+         */
+        private boolean mIsNetworkOemPaid;
+
+        /**
+         * Whether this network will be brought up as OEM private (OEM_PRIVATE capability bit
+         * added).
+         */
+        private boolean mIsNetworkOemPrivate;
+
+        /**
+         * Whether this network is a carrier merged network.
+         */
+        private boolean mIsCarrierMerged;
+
+        /**
+         * The MAC randomization strategy.
+         */
+        @MacRandomizationSetting
+        private int mMacRandomizationSetting;
+
+        /**
+         * The SAE Hash-to-Element only mode.
+         */
+        private boolean mSaeH2eOnlyMode;
+
         public Builder() {
             mSsid = null;
             mBssid =  null;
@@ -165,6 +247,13 @@
             mWapiPskPassphrase = null;
             mWapiEnterpriseConfig = null;
             mIsNetworkUntrusted = false;
+            mIsNetworkOemPaid = false;
+            mIsNetworkOemPrivate = false;
+            mIsCarrierMerged = false;
+            mPriorityGroup = 0;
+            mMacRandomizationSetting = RANDOMIZATION_PERSISTENT;
+            mSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+            mSaeH2eOnlyMode = false;
         }
 
         /**
@@ -261,14 +350,16 @@
          *
          * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}.
          * @return Instance of {@link Builder} to enable chaining of the builder method.
-         * @throws IllegalArgumentException if configuration CA certificate or
-         *                                  AltSubjectMatch/DomainSuffixMatch is not set.
+         * @throws IllegalArgumentException If configuration uses server certificate but validation
+         *                                  is not enabled. See {@link WifiEnterpriseConfig#isServerCertValidationEnabled()}
          */
         public @NonNull Builder setWpa2EnterpriseConfig(
                 @NonNull WifiEnterpriseConfig enterpriseConfig) {
             checkNotNull(enterpriseConfig);
-            if (enterpriseConfig.isInsecure()) {
-                throw new IllegalArgumentException("Enterprise configuration is insecure");
+            if (enterpriseConfig.isEapMethodServerCertUsed()
+                    && !enterpriseConfig.isMandatoryParameterSetForServerCertValidation()) {
+                throw new IllegalArgumentException("Enterprise configuration mandates server "
+                        + "certificate but validation is not enabled.");
             }
             mWpa2EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig);
             return this;
@@ -282,22 +373,88 @@
          * sha384WithRSAEncryption (OID 1.2.840.113549.1.1.12) or ecdsa-with-SHA384
          * (OID 1.2.840.10045.4.3.3).
          *
+         * @deprecated use {@link #setWpa3EnterpriseStandardModeConfig(WifiEnterpriseConfig)} or
+         * {@link #setWpa3Enterprise192BitModeConfig(WifiEnterpriseConfig)} to specify
+         * WPA3-Enterprise type explicitly.
+         *
          * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}.
          * @return Instance of {@link Builder} to enable chaining of the builder method.
-         * @throws IllegalArgumentException if configuration CA certificate or
-         *                                  AltSubjectMatch/DomainSuffixMatch is not set.
+         * @throws IllegalArgumentException If configuration uses server certificate but validation
+         *                                  is not enabled. See {@link WifiEnterpriseConfig#isServerCertValidationEnabled()}
          */
+        @Deprecated
         public @NonNull Builder setWpa3EnterpriseConfig(
                 @NonNull WifiEnterpriseConfig enterpriseConfig) {
             checkNotNull(enterpriseConfig);
-            if (enterpriseConfig.isInsecure()) {
-                throw new IllegalArgumentException("Enterprise configuration is insecure");
+            if (enterpriseConfig.isEapMethodServerCertUsed()
+                    && !enterpriseConfig.isMandatoryParameterSetForServerCertValidation()) {
+                throw new IllegalArgumentException("Enterprise configuration mandates server "
+                        + "certificate but validation is not enabled.");
             }
             mWpa3EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig);
             return this;
         }
 
         /**
+         * Set the associated enterprise configuration for this network. Needed for authenticating
+         * to WPA3-Enterprise standard networks. See {@link WifiEnterpriseConfig} for description.
+         * For WPA3-Enterprise in 192-bit security mode networks,
+         * see {@link #setWpa3Enterprise192BitModeConfig(WifiEnterpriseConfig)} for description.
+         *
+         * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}.
+         * @return Instance of {@link Builder} to enable chaining of the builder method.
+         * @throws IllegalArgumentException If configuration uses server certificate but validation
+         *                                  is not enabled. See {@link WifiEnterpriseConfig#isServerCertValidationEnabled()}
+         */
+        public @NonNull Builder setWpa3EnterpriseStandardModeConfig(
+                @NonNull WifiEnterpriseConfig enterpriseConfig) {
+            checkNotNull(enterpriseConfig);
+            if (enterpriseConfig.isEapMethodServerCertUsed()
+                    && !enterpriseConfig.isMandatoryParameterSetForServerCertValidation()) {
+                throw new IllegalArgumentException("Enterprise configuration mandates server "
+                        + "certificate but validation is not enabled.");
+            }
+            mWpa3EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig);
+            mWpa3EnterpriseType = WPA3_ENTERPRISE_STANDARD;
+            return this;
+        }
+
+        /**
+         * Set the associated enterprise configuration for this network. Needed for authenticating
+         * to WPA3-Enterprise in 192-bit security mode networks. See {@link WifiEnterpriseConfig}
+         * for description. Both the client and CA certificates must be provided,
+         * and must be of type of either sha384WithRSAEncryption with key length of 3072bit or
+         * more (OID 1.2.840.113549.1.1.12), or ecdsa-with-SHA384 with key length of 384bit or
+         * more (OID 1.2.840.10045.4.3.3).
+         *
+         * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}.
+         * @return Instance of {@link Builder} to enable chaining of the builder method.
+         * @throws IllegalArgumentException if the EAP type or certificates do not
+         *                                  meet 192-bit mode requirements.
+         */
+        public @NonNull Builder setWpa3Enterprise192BitModeConfig(
+                @NonNull WifiEnterpriseConfig enterpriseConfig) {
+            checkNotNull(enterpriseConfig);
+            if (enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.TLS) {
+                throw new IllegalArgumentException("The 192-bit mode network type must be TLS");
+            }
+            if (!WifiEnterpriseConfig.isSuiteBCipherCert(
+                    enterpriseConfig.getClientCertificate())) {
+                throw new IllegalArgumentException(
+                    "The client certificate does not meet 192-bit mode requirements.");
+            }
+            if (!WifiEnterpriseConfig.isSuiteBCipherCert(
+                    enterpriseConfig.getCaCertificate())) {
+                throw new IllegalArgumentException(
+                    "The CA certificate does not meet 192-bit mode requirements.");
+            }
+
+            mWpa3EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig);
+            mWpa3EnterpriseType = WPA3_ENTERPRISE_192_BIT;
+            return this;
+        }
+
+        /**
          * Set the associated Passpoint configuration for this network. Needed for authenticating
          * to Hotspot 2.0 networks. See {@link PasspointConfiguration} for description.
          *
@@ -311,7 +468,7 @@
             if (!passpointConfig.validate()) {
                 throw new IllegalArgumentException("Passpoint configuration is invalid");
             }
-            mPasspointConfiguration = passpointConfig;
+            mPasspointConfiguration = new PasspointConfiguration(passpointConfig);
             return this;
         }
 
@@ -333,6 +490,67 @@
         }
 
         /**
+         * Configure the suggestion to only be used with the SIM identified by the subscription
+         * ID specified in this method. The suggested network will only be used by that SIM and
+         * no other SIM - even from the same carrier.
+         * <p>
+         * The caller is restricted to be either of:
+         * <li>A carrier provisioning app (which holds the
+         * {@code android.Manifest.permission#NETWORK_CARRIER_PROVISIONING} permission).
+         * <li>A carrier-privileged app - which is restricted to only specify a subscription ID
+         * which belong to the same carrier which signed the app, see
+         * {@link TelephonyManager#hasCarrierPrivileges()}.
+         * <p>
+         * Specifying a subscription ID which doesn't match these restriction will cause the
+         * suggestion to be rejected with the error code
+         * {@link WifiManager#STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_NOT_ALLOWED}.
+         *
+         * @param subscriptionId subscription ID see {@link SubscriptionInfo#getSubscriptionId()}
+         * @return Instance of {@link Builder} to enable chaining of the builder method.
+         * @throws IllegalArgumentException if subscriptionId equals to {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}
+         */
+        @RequiresApi(Build.VERSION_CODES.S)
+        public @NonNull Builder setSubscriptionId(int subscriptionId) {
+            if (!SdkLevel.isAtLeastS()) {
+                throw new UnsupportedOperationException();
+            }
+            if (subscriptionId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                throw new IllegalArgumentException("Subscription Id is invalid");
+            }
+            mSubscriptionId = subscriptionId;
+            return this;
+        }
+
+        /**
+         * Suggested networks are considered as part of a pool of all suggested networks and other
+         * networks (e.g. saved networks) - one of which will be selected.
+         * <ul>
+         * <li> Any app can suggest any number of networks. </li>
+         * <li> Priority: only the highest priority (0 being the lowest) currently visible suggested
+         * network or networks (multiple suggested networks may have the same priority) are added to
+         * the network selection pool.</li>
+         * </ul>
+         * <p>
+         * However, this restricts a suggesting app to have a single group of networks which can be
+         * prioritized. In some circumstances multiple groups are useful: for instance, suggesting
+         * networks for 2 different SIMs - each of which may have its own priority order.
+         * <p>
+         * Priority group: creates a separate priority group. Only the highest priority, currently
+         * visible suggested network or networks, within each priority group are included in the
+         * network selection pool.
+         * <p>
+         * Specify an arbitrary integer only used as the priority group. Use with
+         * {@link #setPriority(int)}.
+         *
+         * @param priorityGroup priority group id, if not set default is 0.
+         * @return Instance of {@link Builder} to enable chaining of the builder method.
+         */
+        public @NonNull Builder setPriorityGroup(@IntRange(from = 0) int priorityGroup) {
+            mPriorityGroup = priorityGroup;
+            return this;
+        }
+
+        /**
          * Set the ASCII WAPI passphrase for this network. Needed for authenticating to
          * WAPI-PSK networks.
          *
@@ -380,6 +598,30 @@
         }
 
         /**
+         * Specifies the MAC randomization method.
+         * <p>
+         * Suggested networks will never use the device (factory) MAC address to associate to the
+         * network - instead they use a locally generated random MAC address. This method controls
+         * the strategy for generating the random MAC address. If not set, defaults to
+         * {@link #RANDOMIZATION_PERSISTENT}.
+         *
+         * @param macRandomizationSetting - one of {@code RANDOMIZATION_*} values
+         * @return Instance of {@link Builder} to enable chaining of the builder method.
+         */
+        public @NonNull Builder setMacRandomizationSetting(
+                @MacRandomizationSetting int macRandomizationSetting) {
+            switch (macRandomizationSetting) {
+                case RANDOMIZATION_PERSISTENT:
+                case RANDOMIZATION_NON_PERSISTENT:
+                    mMacRandomizationSetting = macRandomizationSetting;
+                    break;
+                default:
+                    throw new IllegalArgumentException();
+            }
+            return this;
+        }
+
+        /**
          * Specifies whether the app needs to log in to a captive portal to obtain Internet access.
          * <p>
          * This will dictate if the directed broadcast
@@ -415,10 +657,11 @@
 
         /**
          * Specify the priority of this network among other network suggestions provided by the same
-         * app (priorities have no impact on suggestions by different apps). The higher the number,
-         * the higher the priority (i.e value of 0 = lowest priority).
-         * <p>
-         * <li>If not set, defaults a lower priority than any assigned priority.</li>
+         * app and within the same priority group, see {@link #setPriorityGroup(int)}. Priorities
+         * have no impact on suggestions by other apps or suggestions from the same app using a
+         * different priority group. The higher the number, the higher the priority
+         * (i.e value of 0 = lowest priority). If not set, defaults to a lower priority than any
+         * assigned priority.
          *
          * @param priority Integer number representing the priority among suggestions by the app.
          * @return Instance of {@link Builder} to enable chaining of the builder method.
@@ -494,12 +737,15 @@
 
         /**
          * Specifies whether the system will bring up the network (if selected) as untrusted. An
-         * untrusted network has its {@link android.net.NetworkCapabilities#NET_CAPABILITY_TRUSTED}
+         * untrusted network has its {@link NetworkCapabilities#NET_CAPABILITY_TRUSTED}
          * capability removed. The Wi-Fi network selection process may use this information to
          * influence priority of the suggested network for Wi-Fi network selection (most likely to
          * reduce it). The connectivity service may use this information to influence the overall
          * network configuration of the device.
          * <p>
+         * <li> These suggestions are only considered for network selection if a
+         * {@link NetworkRequest} without {@link NetworkCapabilities#NET_CAPABILITY_TRUSTED}
+         * capability is filed.
          * <li> An untrusted network's credentials may not be shared with the user using
          * {@link #setCredentialSharedWithUser(boolean)}.</li>
          * <li> If not set, defaults to false (i.e. network is trusted).</li>
@@ -513,6 +759,148 @@
             return this;
         }
 
+        /**
+         * Specifies whether the system will bring up the network (if selected) as OEM paid. An
+         * OEM paid network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PAID} capability
+         * added.
+         * Note:
+         * <li>The connectivity service may use this information to influence the overall
+         * network configuration of the device. This network is typically only available to system
+         * apps.
+         * <li>On devices which do not support concurrent connection (indicated via
+         * {@link WifiManager#isStaConcurrencyForRestrictedConnectionsSupported()}), Wi-Fi
+         * network selection process may use this information to influence priority of the
+         * suggested network for Wi-Fi network selection (most likely to reduce it).
+         * <li>On devices which support concurrent connections (indicated via
+         * {@link WifiManager#isStaConcurrencyForRestrictedConnectionsSupported()}), these
+         * OEM paid networks may be brought up as a secondary concurrent connection (primary
+         * connection will be used for networks available to the user and all apps.
+         * <p>
+         * <li> An OEM paid network's credentials may not be shared with the user using
+         * {@link #setCredentialSharedWithUser(boolean)}.</li>
+         * <li> These suggestions are only considered for network selection if a
+         * {@link NetworkRequest} with {@link NetworkCapabilities#NET_CAPABILITY_OEM_PAID}
+         * capability is filed.
+         * <li> Each suggestion can have both {@link #setOemPaid(boolean)} and
+         * {@link #setOemPrivate(boolean)} set if the app wants these suggestions considered
+         * for creating either an OEM paid network or OEM private network determined based on
+         * the {@link NetworkRequest} that is active.
+         * <li> If not set, defaults to false (i.e. network is not OEM paid).</li>
+         *
+         * @param isOemPaid Boolean indicating whether the network should be brought up as OEM paid
+         *                  (if true) or not OEM paid (if false).
+         * @return Instance of {@link Builder} to enable chaining of the builder method.
+         * @hide
+         */
+        @SystemApi
+        @RequiresApi(Build.VERSION_CODES.S)
+        public @NonNull Builder setOemPaid(boolean isOemPaid) {
+            if (!SdkLevel.isAtLeastS()) {
+                throw new UnsupportedOperationException();
+            }
+            mIsNetworkOemPaid = isOemPaid;
+            return this;
+        }
+
+        /**
+         * Specifies whether the system will bring up the network (if selected) as OEM private. An
+         * OEM private network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PRIVATE} capability
+         * added.
+         * Note:
+         * <li>The connectivity service may use this information to influence the overall
+         * network configuration of the device. This network is typically only available to system
+         * apps.
+         * <li>On devices which do not support concurrent connection (indicated via
+         * {@link WifiManager#isStaConcurrencyForRestrictedConnectionsSupported()}), Wi-Fi
+         * network selection process may use this information to influence priority of the suggested
+         * network for Wi-Fi network selection (most likely to reduce it).
+         * <li>On devices which support concurrent connections (indicated via
+         * {@link WifiManager#isStaConcurrencyForRestrictedConnectionsSupported()}), these OEM
+         * private networks may be brought up as a secondary concurrent connection (primary
+         * connection will be used for networks available to the user and all apps.
+         * <p>
+         * <li> An OEM private network's credentials may not be shared with the user using
+         * {@link #setCredentialSharedWithUser(boolean)}.</li>
+         * <li> These suggestions are only considered for network selection if a
+         * {@link NetworkRequest} with {@link NetworkCapabilities#NET_CAPABILITY_OEM_PRIVATE}
+         * capability is filed.
+         * <li> Each suggestion can have both {@link #setOemPaid(boolean)} and
+         * {@link #setOemPrivate(boolean)} set if the app wants these suggestions considered
+         * for creating either an OEM paid network or OEM private network determined based on
+         * the {@link NetworkRequest} that is active.
+         * <li> If not set, defaults to false (i.e. network is not OEM private).</li>
+         *
+         * @param isOemPrivate Boolean indicating whether the network should be brought up as OEM
+         *                     private (if true) or not OEM private (if false).
+         * @return Instance of {@link Builder} to enable chaining of the builder method.
+         * @hide
+         */
+        @SystemApi
+        @RequiresApi(Build.VERSION_CODES.S)
+        public @NonNull Builder setOemPrivate(boolean isOemPrivate) {
+            if (!SdkLevel.isAtLeastS()) {
+                throw new UnsupportedOperationException();
+            }
+            mIsNetworkOemPrivate = isOemPrivate;
+            return this;
+        }
+
+        /**
+         * Specifies whether the suggestion represents a carrier merged network. A carrier merged
+         * Wi-Fi network is treated as part of the mobile carrier network. Such configuration may
+         * impact the user interface and data usage accounting.
+         * <p>
+         * Only carriers with the
+         * {@link android.telephony.CarrierConfigManager#KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL}
+         * flag set to {@code true} may use this API.
+         * <p>
+         * <li>A suggestion marked as carrier merged must be metered enterprise network with a valid
+         * subscription Id set.
+         * @see #setIsMetered(boolean)
+         * @see #setSubscriptionId(int)
+         * @see #setWpa2EnterpriseConfig(WifiEnterpriseConfig)
+         * @see #setWpa3Enterprise192BitModeConfig(WifiEnterpriseConfig)
+         * @see #setWpa3EnterpriseStandardModeConfig(WifiEnterpriseConfig)
+         * @see #setPasspointConfig(PasspointConfiguration)
+         * </li>
+         * <li>If not set, defaults to false (i.e. not a carrier merged network.)</li>
+         * </p>
+         * @param isCarrierMerged Boolean indicating whether the network is treated a carrier
+         *                               merged network (if true) or non-merged network (if false);
+         * @return Instance of {@link Builder} to enable chaining of the builder method.
+         */
+        @RequiresApi(Build.VERSION_CODES.S)
+        public @NonNull Builder setCarrierMerged(boolean isCarrierMerged) {
+            if (!SdkLevel.isAtLeastS()) {
+                throw new UnsupportedOperationException();
+            }
+            mIsCarrierMerged = isCarrierMerged;
+            return this;
+        }
+
+        /**
+         * Specifies whether the suggestion represents an SAE network which only
+         * accepts Hash-to-Element mode.
+         * If this is enabled, Hunting & Pecking mode is disabled and only Hash-to-Element
+         * mode is used for this network.
+         * This is only valid for an SAE network which is configured using the
+         * {@link #setWpa3Passphrase}.
+         * Before calling this API, the application should check Hash-to-Element support using
+         * {@link WifiManager#isWpa3SaeH2eSupported()}.
+         *
+         * @param enable Boolean indicating whether the network only accepts Hash-to-Element mode,
+         *        default is false.
+         * @return Instance of {@link Builder} to enable chaining of the builder method.
+         */
+        @RequiresApi(Build.VERSION_CODES.S)
+        public @NonNull Builder setIsWpa3SaeH2eOnlyModeEnabled(boolean enable) {
+            if (!SdkLevel.isAtLeastS()) {
+                throw new UnsupportedOperationException();
+            }
+            mSaeH2eOnlyMode = enable;
+            return this;
+        }
+
         private void setSecurityParamsInWifiConfiguration(
                 @NonNull WifiConfiguration configuration) {
             if (!TextUtils.isEmpty(mWpa2PskPassphrase)) { // WPA-PSK network.
@@ -523,27 +911,28 @@
                 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
                 // WifiConfiguration.preSharedKey needs quotes around ASCII password.
                 configuration.preSharedKey = "\"" + mWpa3SaePassphrase + "\"";
+                if (mSaeH2eOnlyMode) configuration.enableSaeH2eOnlyMode(mSaeH2eOnlyMode);
             } else if (mWpa2EnterpriseConfig != null) { // WPA-EAP network
                 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
                 configuration.enterpriseConfig = mWpa2EnterpriseConfig;
             } else if (mWpa3EnterpriseConfig != null) { // WPA3-Enterprise
-                if (mWpa3EnterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.TLS
+                if (mWpa3EnterpriseType == WPA3_ENTERPRISE_AUTO
+                        && mWpa3EnterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.TLS
                         && WifiEnterpriseConfig.isSuiteBCipherCert(
                         mWpa3EnterpriseConfig.getClientCertificate())
                         && WifiEnterpriseConfig.isSuiteBCipherCert(
                         mWpa3EnterpriseConfig.getCaCertificate())) {
-                    // WPA3-Enterprise in 192-bit security mode (Suite-B)
-                    configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B);
+                    // WPA3-Enterprise in 192-bit security mode
+                    configuration.setSecurityParams(
+                            WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT);
+                } else if (mWpa3EnterpriseType == WPA3_ENTERPRISE_192_BIT) {
+                    // WPA3-Enterprise in 192-bit security mode
+                    configuration.setSecurityParams(
+                            WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT);
                 } else {
                     // WPA3-Enterprise
-                    configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
-                    configuration.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
-                    configuration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
-                    configuration.allowedPairwiseCiphers.set(
-                            WifiConfiguration.PairwiseCipher.GCMP_256);
-                    configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
-                    configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.GCMP_256);
-                    configuration.requirePmf = true;
+                    configuration.setSecurityParams(
+                            WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
                 }
                 configuration.enterpriseConfig = mWpa3EnterpriseConfig;
             } else if (mIsEnhancedOpen) { // OWE network
@@ -579,6 +968,14 @@
             wifiConfiguration.meteredOverride = mMeteredOverride;
             wifiConfiguration.carrierId = mCarrierId;
             wifiConfiguration.trusted = !mIsNetworkUntrusted;
+            wifiConfiguration.oemPaid = mIsNetworkOemPaid;
+            wifiConfiguration.oemPrivate = mIsNetworkOemPrivate;
+            wifiConfiguration.carrierMerged = mIsCarrierMerged;
+            wifiConfiguration.macRandomizationSetting =
+                    mMacRandomizationSetting == RANDOMIZATION_NON_PERSISTENT
+                    ? WifiConfiguration.RANDOMIZATION_NON_PERSISTENT
+                    : WifiConfiguration.RANDOMIZATION_PERSISTENT;
+            wifiConfiguration.subscriptionId = mSubscriptionId;
             return wifiConfiguration;
         }
 
@@ -607,8 +1004,20 @@
             wifiConfiguration.priority = mPriority;
             wifiConfiguration.meteredOverride = mMeteredOverride;
             wifiConfiguration.trusted = !mIsNetworkUntrusted;
+            wifiConfiguration.oemPaid = mIsNetworkOemPaid;
+            wifiConfiguration.oemPrivate = mIsNetworkOemPrivate;
+            wifiConfiguration.carrierMerged = mIsCarrierMerged;
+            wifiConfiguration.subscriptionId = mSubscriptionId;
             mPasspointConfiguration.setCarrierId(mCarrierId);
+            mPasspointConfiguration.setSubscriptionId(mSubscriptionId);
             mPasspointConfiguration.setMeteredOverride(wifiConfiguration.meteredOverride);
+            mPasspointConfiguration.setOemPrivate(mIsNetworkOemPrivate);
+            mPasspointConfiguration.setOemPaid(mIsNetworkOemPaid);
+            mPasspointConfiguration.setCarrierMerged(mIsCarrierMerged);
+            wifiConfiguration.macRandomizationSetting =
+                    mMacRandomizationSetting == RANDOMIZATION_NON_PERSISTENT
+                    ? WifiConfiguration.RANDOMIZATION_NON_PERSISTENT
+                    : WifiConfiguration.RANDOMIZATION_PERSISTENT;
             return wifiConfiguration;
         }
 
@@ -677,6 +1086,8 @@
                             + "suggestion with Passpoint configuration");
                 }
                 wifiConfiguration = buildWifiConfigurationForPasspoint();
+                mPasspointConfiguration.setEnhancedMacRandomizationEnabled(
+                        mMacRandomizationSetting == RANDOMIZATION_NON_PERSISTENT);
             } else {
                 if (mSsid == null) {
                     throw new IllegalStateException("setSsid should be invoked for suggestion");
@@ -689,6 +1100,11 @@
                         || mBssid.equals(WifiManager.ALL_ZEROS_MAC_ADDRESS))) {
                     throw new IllegalStateException("invalid bssid for suggestion");
                 }
+                if (TextUtils.isEmpty(mWpa3SaePassphrase) && mSaeH2eOnlyMode) {
+                    throw new IllegalStateException(
+                            "Hash-to-Element only mode is only allowed for the SAE network");
+                }
+
                 wifiConfiguration = buildWifiConfiguration();
                 if (wifiConfiguration.isOpenNetwork()) {
                     if (mIsSharedWithUserSet && mIsSharedWithUser) {
@@ -707,20 +1123,52 @@
                 if (mIsSharedWithUserSet && mIsSharedWithUser) {
                     throw new IllegalStateException("Should not be both"
                             + "setCredentialSharedWithUser and +"
-                            + "setIsNetworkAsUntrusted to true");
+                            + "setUntrusted to true");
                 }
                 mIsSharedWithUser = false;
             }
+            if (mIsNetworkOemPaid) {
+                if (mIsSharedWithUserSet && mIsSharedWithUser) {
+                    throw new IllegalStateException("Should not be both"
+                            + "setCredentialSharedWithUser and +"
+                            + "setOemPaid to true");
+                }
+                mIsSharedWithUser = false;
+            }
+            if (mIsNetworkOemPrivate) {
+                if (mIsSharedWithUserSet && mIsSharedWithUser) {
+                    throw new IllegalStateException("Should not be both"
+                            + "setCredentialSharedWithUser and +"
+                            + "setOemPrivate to true");
+                }
+                mIsSharedWithUser = false;
+            }
+            if (mIsCarrierMerged) {
+                if (mSubscriptionId == SubscriptionManager.INVALID_SUBSCRIPTION_ID
+                        || mMeteredOverride != WifiConfiguration.METERED_OVERRIDE_METERED
+                        || !isEnterpriseSuggestion()) {
+                    throw new IllegalStateException("A carrier merged network must be a metered, "
+                            + "enterprise network with valid subscription Id");
+                }
+            }
             return new WifiNetworkSuggestion(
                     wifiConfiguration,
                     mPasspointConfiguration,
                     mIsAppInteractionRequired,
                     mIsUserInteractionRequired,
                     mIsSharedWithUser,
-                    mIsInitialAutojoinEnabled);
+                    mIsInitialAutojoinEnabled,
+                    mPriorityGroup);
+        }
+
+        private boolean isEnterpriseSuggestion() {
+            return !(mWpa2EnterpriseConfig == null && mWpa3EnterpriseConfig == null
+                    && mWapiEnterpriseConfig == null && mPasspointConfiguration == null);
         }
     }
 
+
+
     /**
      * Network configuration for the provided network.
      * @hide
@@ -760,6 +1208,12 @@
      */
     public final boolean isInitialAutoJoinEnabled;
 
+    /**
+     * Priority group ID.
+     * @hide
+     */
+    public final int priorityGroup;
+
     /** @hide */
     public WifiNetworkSuggestion() {
         this.wifiConfiguration = new WifiConfiguration();
@@ -768,6 +1222,7 @@
         this.isUserInteractionRequired = false;
         this.isUserAllowedToManuallyConnect = true;
         this.isInitialAutoJoinEnabled = true;
+        this.priorityGroup = 0;
     }
 
     /** @hide */
@@ -776,7 +1231,7 @@
                                  boolean isAppInteractionRequired,
                                  boolean isUserInteractionRequired,
                                  boolean isUserAllowedToManuallyConnect,
-                                 boolean isInitialAutoJoinEnabled) {
+                                 boolean isInitialAutoJoinEnabled, int priorityGroup) {
         checkNotNull(networkConfiguration);
         this.wifiConfiguration = networkConfiguration;
         this.passpointConfiguration = passpointConfiguration;
@@ -785,6 +1240,7 @@
         this.isUserInteractionRequired = isUserInteractionRequired;
         this.isUserAllowedToManuallyConnect = isUserAllowedToManuallyConnect;
         this.isInitialAutoJoinEnabled = isInitialAutoJoinEnabled;
+        this.priorityGroup = priorityGroup;
     }
 
     public static final @NonNull Creator<WifiNetworkSuggestion> CREATOR =
@@ -797,7 +1253,8 @@
                             in.readBoolean(), // isAppInteractionRequired
                             in.readBoolean(), // isUserInteractionRequired
                             in.readBoolean(), // isSharedCredentialWithUser
-                            in.readBoolean()  // isAutojoinEnabled
+                            in.readBoolean(),  // isAutojoinEnabled
+                            in.readInt() // priorityGroup
                     );
                 }
 
@@ -820,17 +1277,17 @@
         dest.writeBoolean(isUserInteractionRequired);
         dest.writeBoolean(isUserAllowedToManuallyConnect);
         dest.writeBoolean(isInitialAutoJoinEnabled);
+        dest.writeInt(priorityGroup);
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(wifiConfiguration.SSID, wifiConfiguration.BSSID,
-                wifiConfiguration.allowedKeyManagement, wifiConfiguration.getKey());
+                wifiConfiguration.getDefaultSecurityType(),
+                wifiConfiguration.getPasspointUniqueId(),
+                wifiConfiguration.subscriptionId, wifiConfiguration.carrierId);
     }
 
-    /**
-     * Equals for network suggestions.
-     */
     @Override
     public boolean equals(Object obj) {
         if (this == obj) {
@@ -846,10 +1303,12 @@
 
         return TextUtils.equals(this.wifiConfiguration.SSID, lhs.wifiConfiguration.SSID)
                 && TextUtils.equals(this.wifiConfiguration.BSSID, lhs.wifiConfiguration.BSSID)
-                && Objects.equals(this.wifiConfiguration.allowedKeyManagement,
-                lhs.wifiConfiguration.allowedKeyManagement)
-                && TextUtils.equals(this.wifiConfiguration.getKey(),
-                lhs.wifiConfiguration.getKey());
+                && TextUtils.equals(this.wifiConfiguration.getDefaultSecurityType(),
+                lhs.wifiConfiguration.getDefaultSecurityType())
+                && TextUtils.equals(this.wifiConfiguration.getPasspointUniqueId(),
+                lhs.wifiConfiguration.getPasspointUniqueId())
+                && this.wifiConfiguration.carrierId == lhs.wifiConfiguration.carrierId
+                && this.wifiConfiguration.subscriptionId == lhs.wifiConfiguration.subscriptionId;
     }
 
     @Override
@@ -858,11 +1317,27 @@
                 .append("SSID=").append(wifiConfiguration.SSID)
                 .append(", BSSID=").append(wifiConfiguration.BSSID)
                 .append(", FQDN=").append(wifiConfiguration.FQDN)
-                .append(", isAppInteractionRequired=").append(isAppInteractionRequired)
+                .append(", SecurityParams=");
+        wifiConfiguration.getSecurityParamsList().stream()
+                .forEach(param -> {
+                    sb.append(" ");
+                    sb.append(WifiConfiguration.getSecurityTypeName(param.getSecurityType()));
+                    if (param.isAddedByAutoUpgrade()) sb.append("^");
+                });
+        sb.append(", isAppInteractionRequired=").append(isAppInteractionRequired)
                 .append(", isUserInteractionRequired=").append(isUserInteractionRequired)
                 .append(", isCredentialSharedWithUser=").append(isUserAllowedToManuallyConnect)
                 .append(", isInitialAutoJoinEnabled=").append(isInitialAutoJoinEnabled)
                 .append(", isUnTrusted=").append(!wifiConfiguration.trusted)
+                .append(", isOemPaid=").append(wifiConfiguration.oemPaid)
+                .append(", isOemPrivate=").append(wifiConfiguration.oemPrivate)
+                .append(", isCarrierMerged=").append(wifiConfiguration.carrierMerged)
+                .append(", isHiddenSsid=").append(wifiConfiguration.hiddenSSID)
+                .append(", priorityGroup=").append(priorityGroup)
+                .append(", subscriptionId=").append(wifiConfiguration.subscriptionId)
+                .append(", carrierId=").append(wifiConfiguration.carrierId)
+                .append(", priority=").append(wifiConfiguration.priority)
+                .append(", meteredness=").append(wifiConfiguration.meteredOverride)
                 .append(" ]");
         return sb.toString();
     }
@@ -957,6 +1432,43 @@
     }
 
     /**
+     * @see Builder#setOemPaid(boolean)
+     * @hide
+     */
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.S)
+    public boolean isOemPaid() {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        return wifiConfiguration.oemPaid;
+    }
+
+    /**
+     * @see Builder#setOemPrivate(boolean)
+     * @hide
+     */
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.S)
+    public boolean isOemPrivate() {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        return wifiConfiguration.oemPrivate;
+    }
+
+    /**
+     * @see Builder#setCarrierMerged(boolean)
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    public boolean isCarrierMerged() {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        return wifiConfiguration.carrierMerged;
+    }
+
+    /**
      * Get the WifiEnterpriseConfig, or null if unset.
      * @see Builder#setWapiEnterpriseConfig(WifiEnterpriseConfig)
      * @see Builder#setWpa2EnterpriseConfig(WifiEnterpriseConfig)
@@ -983,4 +1495,32 @@
         }
         return WifiInfo.removeDoubleQuotes(wifiConfiguration.preSharedKey);
     }
+
+    /**
+     * @see Builder#setPriorityGroup(int)
+     */
+    @IntRange(from = 0)
+    public int getPriorityGroup() {
+        return priorityGroup;
+    }
+
+    /**
+     * @see Builder#setSubscriptionId(int)
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    public int getSubscriptionId() {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        return wifiConfiguration.subscriptionId;
+    }
+
+    /**
+     * @see Builder#setCarrierId(int)
+     * @hide
+     */
+    @SystemApi
+    public int getCarrierId() {
+        return wifiConfiguration.carrierId;
+    }
 }
diff --git a/framework/java/android/net/wifi/WifiScanner.java b/framework/java/android/net/wifi/WifiScanner.java
index 4163c88..22d12c0 100644
--- a/framework/java/android/net/wifi/WifiScanner.java
+++ b/framework/java/android/net/wifi/WifiScanner.java
@@ -27,6 +27,7 @@
 import android.annotation.SystemService;
 import android.content.Context;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
@@ -34,14 +35,18 @@
 import android.os.Messenger;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.WorkSource;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseArray;
 
+import androidx.annotation.RequiresApi;
+
 import com.android.internal.util.AsyncChannel;
 import com.android.internal.util.Protocol;
+import com.android.modules.utils.build.SdkLevel;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -68,7 +73,9 @@
     /** @hide */
     public static final int WIFI_BAND_INDEX_6_GHZ = 3;
     /** @hide */
-    public static final int WIFI_BAND_COUNT = 4;
+    public static final int WIFI_BAND_INDEX_60_GHZ = 4;
+    /** @hide */
+    public static final int WIFI_BAND_COUNT = 5;
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -76,7 +83,8 @@
             WIFI_BAND_INDEX_24_GHZ,
             WIFI_BAND_INDEX_5_GHZ,
             WIFI_BAND_INDEX_5_GHZ_DFS_ONLY,
-            WIFI_BAND_INDEX_6_GHZ})
+            WIFI_BAND_INDEX_6_GHZ,
+            WIFI_BAND_INDEX_60_GHZ})
     public @interface WifiBandIndex {}
 
     /** no band specified; use channel list instead */
@@ -89,6 +97,8 @@
     public static final int WIFI_BAND_5_GHZ_DFS_ONLY  = 1 << WIFI_BAND_INDEX_5_GHZ_DFS_ONLY;
     /** 6 GHz band */
     public static final int WIFI_BAND_6_GHZ = 1 << WIFI_BAND_INDEX_6_GHZ;
+    /** 60 GHz band */
+    public static final int WIFI_BAND_60_GHZ = 1 << WIFI_BAND_INDEX_60_GHZ;
 
     /**
      * Combination of bands
@@ -113,6 +123,12 @@
     /** 2.4 GHz band and 5 GHz band; with DFS channels and 6 GHz */
     public static final int WIFI_BAND_24_5_WITH_DFS_6_GHZ =
             WIFI_BAND_BOTH_WITH_DFS | WIFI_BAND_6_GHZ;
+    /** @hide */
+    public static final int WIFI_BAND_24_5_6_60_GHZ =
+            WIFI_BAND_24_5_6_GHZ | WIFI_BAND_60_GHZ;
+    /** @hide */
+    public static final int WIFI_BAND_24_5_WITH_DFS_6_60_GHZ =
+            WIFI_BAND_24_5_6_60_GHZ | WIFI_BAND_5_GHZ_DFS_ONLY;
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -127,7 +143,10 @@
             WIFI_BAND_BOTH_WITH_DFS,
             WIFI_BAND_6_GHZ,
             WIFI_BAND_24_5_6_GHZ,
-            WIFI_BAND_24_5_WITH_DFS_6_GHZ})
+            WIFI_BAND_24_5_WITH_DFS_6_GHZ,
+            WIFI_BAND_60_GHZ,
+            WIFI_BAND_24_5_6_60_GHZ,
+            WIFI_BAND_24_5_WITH_DFS_6_60_GHZ})
     public @interface WifiBand {}
 
     /**
@@ -158,6 +177,42 @@
     public static final String GET_AVAILABLE_CHANNELS_EXTRA = "Channels";
 
     /**
+     * This constant is used for {@link ScanSettings#setRnrSetting(int)}.
+     * <p>
+     * Scan 6Ghz APs co-located with 2.4/5Ghz APs using Reduced Neighbor Report (RNR) if the 6Ghz
+     * band is explicitly requested to be scanned. The 6Ghz band is explicitly requested if the
+     * ScanSetting.band parameter is set to one of:
+     * <li> {@link #WIFI_BAND_6_GHZ} </li>
+     * <li> {@link #WIFI_BAND_24_5_6_GHZ} </li>
+     * <li> {@link #WIFI_BAND_24_5_WITH_DFS_6_GHZ} </li>
+     * <li> {@link #WIFI_BAND_24_5_6_60_GHZ} </li>
+     * <li> {@link #WIFI_BAND_24_5_WITH_DFS_6_60_GHZ} </li>
+     * <li> {@link #WIFI_BAND_ALL} </li>
+     **/
+    public static final int WIFI_RNR_ENABLED_IF_WIFI_BAND_6_GHZ_SCANNED = 0;
+    /**
+     * This constant is used for {@link ScanSettings#setRnrSetting(int)}.
+     * <p>
+     * Request to scan 6Ghz APs co-located with 2.4/5Ghz APs using Reduced Neighbor Report (RNR).
+     **/
+    public static final int WIFI_RNR_ENABLED = 1;
+    /**
+     * This constant is used for {@link ScanSettings#setRnrSetting(int)}.
+     * <p>
+     * Do not request to scan 6Ghz APs co-located with 2.4/5Ghz APs using
+     * Reduced Neighbor Report (RNR)
+     **/
+    public static final int WIFI_RNR_NOT_NEEDED = 2;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"RNR_"}, value = {
+            WIFI_RNR_ENABLED_IF_WIFI_BAND_6_GHZ_SCANNED,
+            WIFI_RNR_ENABLED,
+            WIFI_RNR_NOT_NEEDED})
+    public @interface RnrSetting {}
+
+    /**
      * Generic action callback invocation interface
      *  @hide
      */
@@ -169,17 +224,18 @@
 
     /**
      * Test if scan is a full scan. i.e. scanning all available bands.
-     * For backward compatibility, since some apps don't include 6GHz in their requests yet,
-     * lacking 6GHz band does not cause the result to be false.
+     * For backward compatibility, since some apps don't include 6GHz or 60Ghz in their requests
+     * yet, lacking 6GHz or 60Ghz band does not cause the result to be false.
      *
-     * @param bandScanned bands that are fully scanned
+     * @param bandsScanned bands that are fully scanned
      * @param excludeDfs when true, DFS band is excluded from the check
      * @return true if all bands are scanned, false otherwise
      *
      * @hide
      */
-    public static boolean isFullBandScan(@WifiBand int bandScanned, boolean excludeDfs) {
-        return (bandScanned | WIFI_BAND_6_GHZ | (excludeDfs ? WIFI_BAND_5_GHZ_DFS_ONLY : 0))
+    public static boolean isFullBandScan(@WifiBand int bandsScanned, boolean excludeDfs) {
+        return (bandsScanned | WIFI_BAND_6_GHZ | WIFI_BAND_60_GHZ
+                | (excludeDfs ? WIFI_BAND_5_GHZ_DFS_ONLY : 0))
                 == WIFI_BAND_ALL;
     }
 
@@ -299,6 +355,16 @@
 
         /** one of the WIFI_BAND values */
         public int band;
+        /**
+         * one of the {@code WIFI_RNR_*} values.
+         */
+        private int mRnrSetting = WIFI_RNR_ENABLED_IF_WIFI_BAND_6_GHZ_SCANNED;
+
+        /**
+         * See {@link #set6GhzPscOnlyEnabled}
+         */
+        private boolean mEnable6GhzPsc = false;
+
         /** list of channels; used when band is set to WIFI_BAND_UNSPECIFIED */
         public ChannelSpec[] channels;
         /**
@@ -404,6 +470,69 @@
         @SystemApi
         public boolean hideFromAppOps;
 
+        /**
+         * Configure whether it is needed to scan 6Ghz non Preferred Scanning Channels when scanning
+         * {@link #WIFI_BAND_6_GHZ}. If set to true and a band that contains
+         * {@link #WIFI_BAND_6_GHZ} is configured for scanning, then only scan 6Ghz PSC channels in
+         * addition to any other bands configured for scanning. Note, 6Ghz non-PSC channels that
+         * are co-located with 2.4/5Ghz APs could still be scanned via the
+         * {@link #setRnrSetting(int)} API.
+         *
+         * <p>
+         * For example, given a ScanSettings with band set to {@link #WIFI_BAND_24_5_WITH_DFS_6_GHZ}
+         * If this API is set to "true" then the ScanSettings is configured to scan all of 2.4Ghz
+         * + all of 5Ghz(DFS and non-DFS) + 6Ghz PSC channels. If this API is set to "false", then
+         * the ScanSetting is configured to scan all of 2.4Ghz + all of 5Ghz(DFS and non_DFS)
+         * + all of 6Ghz channels.
+         * @param enable true to only scan 6Ghz PSC channels, false to scan all 6Ghz channels.
+         */
+        @RequiresApi(Build.VERSION_CODES.S)
+        public void set6GhzPscOnlyEnabled(boolean enable) {
+            if (!SdkLevel.isAtLeastS()) {
+                throw new UnsupportedOperationException();
+            }
+            mEnable6GhzPsc = enable;
+        }
+
+        /**
+         * See {@link #set6GhzPscOnlyEnabled}
+         */
+        @RequiresApi(Build.VERSION_CODES.S)
+        public boolean is6GhzPscOnlyEnabled() {
+            if (!SdkLevel.isAtLeastS()) {
+                throw new UnsupportedOperationException();
+            }
+            return mEnable6GhzPsc;
+        }
+
+        /**
+         * Configure when to scan 6Ghz APs co-located with 2.4/5Ghz APs using Reduced
+         * Neighbor Report (RNR).
+         * @param rnrSetting one of the {@code WIFI_RNR_*} values
+         */
+        @RequiresApi(Build.VERSION_CODES.S)
+        public void setRnrSetting(@RnrSetting int rnrSetting) {
+            if (!SdkLevel.isAtLeastS()) {
+                throw new UnsupportedOperationException();
+            }
+            if (rnrSetting < WIFI_RNR_ENABLED_IF_WIFI_BAND_6_GHZ_SCANNED
+                    || rnrSetting > WIFI_RNR_NOT_NEEDED) {
+                throw new IllegalArgumentException("Invalid rnrSetting");
+            }
+            mRnrSetting = rnrSetting;
+        }
+
+        /**
+         * See {@link #setRnrSetting}
+         */
+        @RequiresApi(Build.VERSION_CODES.S)
+        public @RnrSetting int getRnrSetting() {
+            if (!SdkLevel.isAtLeastS()) {
+                throw new UnsupportedOperationException();
+            }
+            return mRnrSetting;
+        }
+
         /** Implement the Parcelable interface {@hide} */
         public int describeContents() {
             return 0;
@@ -422,6 +551,8 @@
             dest.writeInt(type);
             dest.writeInt(ignoreLocationSettings ? 1 : 0);
             dest.writeInt(hideFromAppOps ? 1 : 0);
+            dest.writeInt(mRnrSetting);
+            dest.writeBoolean(mEnable6GhzPsc);
             if (channels != null) {
                 dest.writeInt(channels.length);
                 for (int i = 0; i < channels.length; i++) {
@@ -454,6 +585,8 @@
                         settings.type = in.readInt();
                         settings.ignoreLocationSettings = in.readInt() == 1;
                         settings.hideFromAppOps = in.readInt() == 1;
+                        settings.mRnrSetting = in.readInt();
+                        settings.mEnable6GhzPsc = in.readBoolean();
                         int num_channels = in.readInt();
                         settings.channels = new ChannelSpec[num_channels];
                         for (int i = 0; i < num_channels; i++) {
@@ -479,7 +612,7 @@
     }
 
     /**
-     * all the information garnered from a single scan
+     * All the information garnered from a single scan
      */
     public static class ScanData implements Parcelable {
         /** scan identifier */
@@ -501,7 +634,7 @@
          * any of the bands.
          * {@hide}
          */
-        private int mBandScanned;
+        private int mScannedBands;
         /** all scan results discovered in this scan, sorted by timestamp in ascending order */
         private final List<ScanResult> mResults;
 
@@ -516,18 +649,18 @@
         }
 
         /** {@hide} */
-        public ScanData(int id, int flags, int bucketsScanned, int bandScanned,
+        public ScanData(int id, int flags, int bucketsScanned, int bandsScanned,
                         ScanResult[] results) {
-            this(id, flags, bucketsScanned, bandScanned, new ArrayList<>(Arrays.asList(results)));
+            this(id, flags, bucketsScanned, bandsScanned, new ArrayList<>(Arrays.asList(results)));
         }
 
         /** {@hide} */
-        public ScanData(int id, int flags, int bucketsScanned, int bandScanned,
+        public ScanData(int id, int flags, int bucketsScanned, int bandsScanned,
                         List<ScanResult> results) {
             mId = id;
             mFlags = flags;
             mBucketsScanned = bucketsScanned;
-            mBandScanned = bandScanned;
+            mScannedBands = bandsScanned;
             mResults = results;
         }
 
@@ -535,7 +668,7 @@
             mId = s.mId;
             mFlags = s.mFlags;
             mBucketsScanned = s.mBucketsScanned;
-            mBandScanned = s.mBandScanned;
+            mScannedBands = s.mScannedBands;
             mResults = new ArrayList<>();
             for (ScanResult scanResult : s.mResults) {
                 mResults.add(new ScanResult(scanResult));
@@ -555,9 +688,37 @@
             return mBucketsScanned;
         }
 
-        /** {@hide} */
-        public int getBandScanned() {
-            return mBandScanned;
+        /**
+         * Retrieve the bands that were fully scanned for this ScanData instance. "fully" here
+         * refers to all the channels available in the band based on the current regulatory
+         * domain.
+         *
+         * @return Bitmask of {@link #WIFI_BAND_24_GHZ}, {@link #WIFI_BAND_5_GHZ},
+         * {@link #WIFI_BAND_5_GHZ_DFS_ONLY}, {@link #WIFI_BAND_6_GHZ} & {@link #WIFI_BAND_60_GHZ}
+         * values. Each bit is set only if all the channels in the corresponding band is scanned.
+         * Will be {@link #WIFI_BAND_UNSPECIFIED} if the list of channels do not fully cover
+         * any of the bands.
+         * <p>
+         * For ex:
+         * <li> Scenario 1:  Fully scanned 2.4Ghz band, partially scanned 5Ghz band
+         *      - Returns {@link #WIFI_BAND_24_GHZ}
+         * </li>
+         * <li> Scenario 2:  Partially scanned 2.4Ghz band and 5Ghz band
+         *      - Returns {@link #WIFI_BAND_UNSPECIFIED}
+         * </li>
+         * </p>
+         */
+        public @WifiBand int getScannedBands() {
+            return getScannedBandsInternal();
+        }
+
+        /**
+         * Same as {@link #getScannedBands()}. For use in the wifi stack without version check.
+         *
+         * {@hide}
+         */
+        public @WifiBand int getScannedBandsInternal() {
+            return mScannedBands;
         }
 
         public ScanResult[] getResults() {
@@ -571,6 +732,19 @@
             }
         }
 
+        /** {@hide} */
+        public void addResults(@NonNull ScanData s) {
+            mScannedBands |= s.mScannedBands;
+            mFlags |= s.mFlags;
+            addResults(s.getResults());
+        }
+
+        /** {@hide} */
+        public boolean isFullBandScanResults() {
+            return (mScannedBands & WifiScanner.WIFI_BAND_24_GHZ) != 0
+                && (mScannedBands & WifiScanner.WIFI_BAND_5_GHZ) != 0;
+        }
+
         /** Implement the Parcelable interface {@hide} */
         public int describeContents() {
             return 0;
@@ -581,7 +755,7 @@
             dest.writeInt(mId);
             dest.writeInt(mFlags);
             dest.writeInt(mBucketsScanned);
-            dest.writeInt(mBandScanned);
+            dest.writeInt(mScannedBands);
             dest.writeParcelableList(mResults, 0);
         }
 
@@ -592,10 +766,10 @@
                         int id = in.readInt();
                         int flags = in.readInt();
                         int bucketsScanned = in.readInt();
-                        int bandScanned = in.readInt();
+                        int bandsScanned = in.readInt();
                         List<ScanResult> results = new ArrayList<>();
                         in.readParcelableList(results, ScanResult.class.getClassLoader());
-                        return new ScanData(id, flags, bucketsScanned, bandScanned, results);
+                        return new ScanData(id, flags, bucketsScanned, bandsScanned, results);
                     }
 
                     public ScanData[] newArray(int size) {
@@ -894,7 +1068,8 @@
     @RequiresPermission(Manifest.permission.NETWORK_STACK)
     public void setScanningEnabled(boolean enable) {
         validateChannel();
-        mAsyncChannel.sendMessage(enable ? CMD_ENABLE : CMD_DISABLE);
+        mAsyncChannel.sendMessage(enable ? CMD_ENABLE : CMD_DISABLE, Process.myTid(),
+                Binder.getCallingPid(), mContext.getOpPackageName());
     }
 
     /**
diff --git a/framework/java/android/net/wifi/WifiUsabilityStatsEntry.java b/framework/java/android/net/wifi/WifiUsabilityStatsEntry.java
index 8f3635f..c33faa2 100644
--- a/framework/java/android/net/wifi/WifiUsabilityStatsEntry.java
+++ b/framework/java/android/net/wifi/WifiUsabilityStatsEntry.java
@@ -17,13 +17,20 @@
 package android.net.wifi;
 
 import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
-
 import android.telephony.Annotation.NetworkType;
+import android.util.Log;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.NoSuchElementException;
 
 /**
  * This class makes a subset of
@@ -33,6 +40,8 @@
  */
 @SystemApi
 public final class WifiUsabilityStatsEntry implements Parcelable {
+    private static final String TAG = "WifiUsabilityStatsEntry";
+
     /** {@hide} */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"PROBE_STATUS_"}, value = {
@@ -99,6 +108,497 @@
     private final int mProbeMcsRateSinceLastUpdate;
     /** Rx link speed at the sample time in Mbps */
     private final int mRxLinkSpeedMbps;
+    /** @see #getTimeSliceDutyCycleInPercent() */
+    private final int mTimeSliceDutyCycleInPercent;
+
+    /** {@hide} */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"WME_ACCESS_CATEGORY_"}, value = {
+        WME_ACCESS_CATEGORY_BE,
+        WME_ACCESS_CATEGORY_BK,
+        WME_ACCESS_CATEGORY_VI,
+        WME_ACCESS_CATEGORY_VO})
+    public @interface WmeAccessCategory {}
+
+    /**
+     * Wireless Multimedia Extensions (WME) Best Effort Access Category, IEEE Std 802.11-2020,
+     * Section 9.4.2.28, Table 9-155
+     */
+    public static final int WME_ACCESS_CATEGORY_BE = 0;
+    /**
+     * Wireless Multimedia Extensions (WME) Background Access Category, IEEE Std 802.11-2020,
+     * Section 9.4.2.28, Table 9-155
+     */
+    public static final int WME_ACCESS_CATEGORY_BK = 1;
+    /**
+     * Wireless Multimedia Extensions (WME) Video Access Category, IEEE Std 802.11-2020,
+     * Section 9.4.2.28, Table 9-155
+     */
+    public static final int WME_ACCESS_CATEGORY_VI = 2;
+    /**
+     * Wireless Multimedia Extensions (WME) Voice Access Category, IEEE Std 802.11-2020,
+     * Section 9.4.2.28, Table 9-155
+     */
+    public static final int WME_ACCESS_CATEGORY_VO = 3;
+    /** Number of WME Access Categories */
+    public static final int NUM_WME_ACCESS_CATEGORIES = 4;
+
+    /**
+     * Data packet contention time statistics.
+     */
+    public static final class ContentionTimeStats implements Parcelable {
+        private long mContentionTimeMinMicros;
+        private long mContentionTimeMaxMicros;
+        private long mContentionTimeAvgMicros;
+        private long mContentionNumSamples;
+
+        /** @hide */
+        public ContentionTimeStats() {
+        }
+
+        /**
+         * Constructor function
+         * @param timeMin The minimum data packet contention time
+         * @param timeMax The maximum data packet contention time
+         * @param timeAvg The average data packet contention time
+         * @param numSamples The number of samples used to get the reported contention time
+         */
+        public ContentionTimeStats(long timeMin, long timeMax, long timeAvg, long numSamples) {
+            this.mContentionTimeMinMicros = timeMin;
+            this.mContentionTimeMaxMicros = timeMax;
+            this.mContentionTimeAvgMicros = timeAvg;
+            this.mContentionNumSamples = numSamples;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeLong(mContentionTimeMinMicros);
+            dest.writeLong(mContentionTimeMaxMicros);
+            dest.writeLong(mContentionTimeAvgMicros);
+            dest.writeLong(mContentionNumSamples);
+        }
+
+        /** Implement the Parcelable interface */
+        public static final @NonNull Creator<ContentionTimeStats> CREATOR =
+                new Creator<ContentionTimeStats>() {
+            public ContentionTimeStats createFromParcel(Parcel in) {
+                ContentionTimeStats stats = new ContentionTimeStats();
+                stats.mContentionTimeMinMicros = in.readLong();
+                stats.mContentionTimeMaxMicros = in.readLong();
+                stats.mContentionTimeAvgMicros = in.readLong();
+                stats.mContentionNumSamples = in.readLong();
+                return stats;
+            }
+            public ContentionTimeStats[] newArray(int size) {
+                return new ContentionTimeStats[size];
+            }
+        };
+
+        /** Data packet min contention time in microseconds */
+        public long getContentionTimeMinMicros() {
+            return mContentionTimeMinMicros;
+        }
+
+        /** Data packet max contention time in microseconds */
+        public long getContentionTimeMaxMicros() {
+            return mContentionTimeMaxMicros;
+        }
+
+        /** Data packet average contention time in microseconds */
+        public long getContentionTimeAvgMicros() {
+            return mContentionTimeAvgMicros;
+        }
+
+        /**
+         * Number of data packets used for deriving the min, the max, and the average
+         * contention time
+         */
+        public long getContentionNumSamples() {
+            return mContentionNumSamples;
+        }
+    }
+    private final ContentionTimeStats[] mContentionTimeStats;
+
+    /** {@hide} */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"WIFI_PREAMBLE_"}, value = {
+        WIFI_PREAMBLE_OFDM,
+        WIFI_PREAMBLE_CCK,
+        WIFI_PREAMBLE_HT,
+        WIFI_PREAMBLE_VHT,
+        WIFI_PREAMBLE_HE,
+        WIFI_PREAMBLE_INVALID})
+    public @interface WifiPreambleType {}
+
+    /** Preamble type for 802.11a/g, IEEE Std 802.11-2020, Section 17 */
+    public static final int WIFI_PREAMBLE_OFDM = 0;
+    /** Preamble type for 802.11b, IEEE Std 802.11-2020, Section 16 */
+    public static final int WIFI_PREAMBLE_CCK = 1;
+    /** Preamble type for 802.11n, IEEE Std 802.11-2020, Section 19 */
+    public static final int WIFI_PREAMBLE_HT = 2;
+    /** Preamble type for 802.11ac, IEEE Std 802.11-2020, Section 21 */
+    public static final int WIFI_PREAMBLE_VHT = 3;
+    /** Preamble type for 802.11ax, IEEE Std 802.11-2020, Section 27 */
+    public static final int WIFI_PREAMBLE_HE = 5;
+    /** Invalid */
+    public static final int WIFI_PREAMBLE_INVALID = -1;
+
+    /** {@hide} */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"WIFI_SPATIAL_STREAMS_"}, value = {
+        WIFI_SPATIAL_STREAMS_ONE,
+        WIFI_SPATIAL_STREAMS_TWO,
+        WIFI_SPATIAL_STREAMS_THREE,
+        WIFI_SPATIAL_STREAMS_FOUR,
+        WIFI_SPATIAL_STREAMS_INVALID})
+    public @interface WifiSpatialStreams {}
+
+    /** Single stream, 1x1 */
+    public static final int WIFI_SPATIAL_STREAMS_ONE = 1;
+    /** Dual streams, 2x2 */
+    public static final int WIFI_SPATIAL_STREAMS_TWO = 2;
+    /** Three streams, 3x3 */
+    public static final int WIFI_SPATIAL_STREAMS_THREE = 3;
+    /** Four streams, 4x4 */
+    public static final int WIFI_SPATIAL_STREAMS_FOUR = 4;
+    /** Invalid */
+    public static final int WIFI_SPATIAL_STREAMS_INVALID = -1;
+
+    /** {@hide} */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"WIFI_BANDWIDTH_"}, value = {
+        WIFI_BANDWIDTH_20_MHZ,
+        WIFI_BANDWIDTH_40_MHZ,
+        WIFI_BANDWIDTH_80_MHZ,
+        WIFI_BANDWIDTH_160_MHZ,
+        WIFI_BANDWIDTH_80P80_MHZ,
+        WIFI_BANDWIDTH_5_MHZ,
+        WIFI_BANDWIDTH_10_MHZ,
+        WIFI_BANDWIDTH_INVALID})
+    public @interface WifiChannelBandwidth {}
+
+    /** Channel bandwidth: 20MHz */
+    public static final int WIFI_BANDWIDTH_20_MHZ = 0;
+    /** Channel bandwidth: 40MHz */
+    public static final int WIFI_BANDWIDTH_40_MHZ = 1;
+    /** Channel bandwidth: 80MHz */
+    public static final int WIFI_BANDWIDTH_80_MHZ = 2;
+    /** Channel bandwidth: 160MHz */
+    public static final int WIFI_BANDWIDTH_160_MHZ = 3;
+    /** Channel bandwidth: 80MHz + 80MHz */
+    public static final int WIFI_BANDWIDTH_80P80_MHZ = 4;
+    /** Channel bandwidth: 5MHz */
+    public static final int WIFI_BANDWIDTH_5_MHZ = 5;
+    /** Channel bandwidth: 10MHz */
+    public static final int WIFI_BANDWIDTH_10_MHZ = 6;
+    /** Invalid */
+    public static final int WIFI_BANDWIDTH_INVALID = -1;
+
+    /**
+     * Rate information and statistics: packet counters (tx/rx successful packets, retries, lost)
+     * indexed by preamble, bandwidth, number of spatial streams, MCS, and bit rate.
+     */
+    public static final class RateStats implements Parcelable {
+        @WifiPreambleType private int mPreamble;
+        @WifiSpatialStreams private int mNss;
+        @WifiChannelBandwidth private int mBw;
+        private int mRateMcsIdx;
+        private int mBitRateInKbps;
+        private int mTxMpdu;
+        private int mRxMpdu;
+        private int mMpduLost;
+        private int mRetries;
+
+        /** @hide */
+        public RateStats() {
+        }
+
+        /**
+         * Constructor function.
+         * @param preamble Preamble information.
+         * @param nss Number of spatial streams.
+         * @param bw Bandwidth information.
+         * @param rateMcsIdx MCS index. OFDM/CCK rate code would be as per IEEE std in the units of
+         *                   0.5Mbps. HT/VHT/HE: it would be MCS index.
+         * @param bitRateInKbps Bitrate in units of 100 Kbps.
+         * @param txMpdu Number of successfully transmitted data packets (ACK received).
+         * @param rxMpdu Number of received data packets.
+         * @param mpduLost Number of data packet losses (no ACK).
+         * @param retries Number of data packet retries.
+         */
+        public RateStats(@WifiPreambleType int preamble, @WifiSpatialStreams int nss,
+                @WifiChannelBandwidth int bw, int rateMcsIdx, int bitRateInKbps, int txMpdu,
+                int rxMpdu, int mpduLost, int retries) {
+            this.mPreamble = preamble;
+            this.mNss = nss;
+            this.mBw = bw;
+            this.mRateMcsIdx = rateMcsIdx;
+            this.mBitRateInKbps = bitRateInKbps;
+            this.mTxMpdu = txMpdu;
+            this.mRxMpdu = rxMpdu;
+            this.mMpduLost = mpduLost;
+            this.mRetries = retries;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeInt(mPreamble);
+            dest.writeInt(mNss);
+            dest.writeInt(mBw);
+            dest.writeInt(mRateMcsIdx);
+            dest.writeInt(mBitRateInKbps);
+            dest.writeInt(mTxMpdu);
+            dest.writeInt(mRxMpdu);
+            dest.writeInt(mMpduLost);
+            dest.writeInt(mRetries);
+        }
+
+        /** Implement the Parcelable interface */
+        public static final @NonNull Creator<RateStats> CREATOR = new Creator<RateStats>() {
+            public RateStats createFromParcel(Parcel in) {
+                RateStats stats = new RateStats();
+                stats.mPreamble = in.readInt();
+                stats.mNss = in.readInt();
+                stats.mBw = in.readInt();
+                stats.mRateMcsIdx = in.readInt();
+                stats.mBitRateInKbps = in.readInt();
+                stats.mTxMpdu = in.readInt();
+                stats.mRxMpdu = in.readInt();
+                stats.mMpduLost = in.readInt();
+                stats.mRetries = in.readInt();
+                return stats;
+            }
+            public RateStats[] newArray(int size) {
+                return new RateStats[size];
+            }
+        };
+
+        /** Preamble information, see {@link WifiPreambleType} */
+        @WifiPreambleType public int getPreamble() {
+            return mPreamble;
+        }
+
+        /** Number of spatial streams, see {@link WifiSpatialStreams} */
+        @WifiSpatialStreams public int getNumberOfSpatialStreams() {
+            return mNss;
+        }
+
+        /** Bandwidth information, see {@link WifiChannelBandwidth} */
+        @WifiChannelBandwidth public int getBandwidthInMhz() {
+            return mBw;
+        }
+
+        /**
+         * MCS index. OFDM/CCK rate code would be as per IEEE std in the units of 0.5Mbps.
+         * HT/VHT/HE: it would be MCS index
+         */
+        public int getRateMcsIdx() {
+            return mRateMcsIdx;
+        }
+
+        /** Bitrate in units of a hundred Kbps */
+        public int getBitRateInKbps() {
+            return mBitRateInKbps;
+        }
+
+        /** Number of successfully transmitted data packets (ACK received) */
+        public int getTxMpdu() {
+            return mTxMpdu;
+        }
+
+        /** Number of received data packets */
+        public int getRxMpdu() {
+            return mRxMpdu;
+        }
+
+        /** Number of data packet losses (no ACK) */
+        public int getMpduLost() {
+            return mMpduLost;
+        }
+
+        /** Number of data packet retries */
+        public int getRetries() {
+            return mRetries;
+        }
+    }
+    private final RateStats[] mRateStats;
+
+    /**
+     * Wifi link layer radio stats.
+     */
+    public static final class RadioStats implements Parcelable {
+        private int mRadioId;
+        private long mTotalRadioOnTimeMillis;
+        private long mTotalRadioTxTimeMillis;
+        private long mTotalRadioRxTimeMillis;
+        private long mTotalScanTimeMillis;
+        private long mTotalNanScanTimeMillis;
+        private long mTotalBackgroundScanTimeMillis;
+        private long mTotalRoamScanTimeMillis;
+        private long mTotalPnoScanTimeMillis;
+        private long mTotalHotspot2ScanTimeMillis;
+
+        /** @hide */
+        public RadioStats() {
+        }
+
+        /**
+         * Constructor function
+         * @param radioId Firmware/Hardware implementation specific persistent value for this
+         *                device, identifying the radio interface for which the stats are produced.
+         * @param onTime The total time the wifi radio is on in ms counted from the last radio
+         *               chip reset
+         * @param txTime The total time the wifi radio is transmitting in ms counted from the last
+         *               radio chip reset
+         * @param rxTime The total time the wifi radio is receiving in ms counted from the last
+         *               radio chip reset
+         * @param onTimeScan The total time spent on all types of scans in ms counted from the
+         *                   last radio chip reset
+         * @param onTimeNanScan The total time spent on nan scans in ms counted from the last radio
+         *                      chip reset
+         * @param onTimeBackgroundScan The total time spent on background scans in ms counted from
+         *                             the last radio chip reset
+         * @param onTimeRoamScan The total time spent on roam scans in ms counted from the last
+         *                       radio chip reset
+         * @param onTimePnoScan The total time spent on pno scans in ms counted from the last radio
+         *                      chip reset
+         * @param onTimeHs20Scan The total time spent on hotspot2.0 scans and GAS exchange in ms
+         *                       counted from the last radio chip reset
+         */
+        public RadioStats(int radioId, long onTime, long txTime, long rxTime, long onTimeScan,
+                long onTimeNanScan, long onTimeBackgroundScan, long onTimeRoamScan,
+                long onTimePnoScan, long onTimeHs20Scan) {
+            this.mRadioId = radioId;
+            this.mTotalRadioOnTimeMillis = onTime;
+            this.mTotalRadioTxTimeMillis = txTime;
+            this.mTotalRadioRxTimeMillis = rxTime;
+            this.mTotalScanTimeMillis = onTimeScan;
+            this.mTotalNanScanTimeMillis = onTimeNanScan;
+            this.mTotalBackgroundScanTimeMillis = onTimeBackgroundScan;
+            this.mTotalRoamScanTimeMillis = onTimeRoamScan;
+            this.mTotalPnoScanTimeMillis = onTimePnoScan;
+            this.mTotalHotspot2ScanTimeMillis = onTimeHs20Scan;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeInt(mRadioId);
+            dest.writeLong(mTotalRadioOnTimeMillis);
+            dest.writeLong(mTotalRadioTxTimeMillis);
+            dest.writeLong(mTotalRadioRxTimeMillis);
+            dest.writeLong(mTotalScanTimeMillis);
+            dest.writeLong(mTotalNanScanTimeMillis);
+            dest.writeLong(mTotalBackgroundScanTimeMillis);
+            dest.writeLong(mTotalRoamScanTimeMillis);
+            dest.writeLong(mTotalPnoScanTimeMillis);
+            dest.writeLong(mTotalHotspot2ScanTimeMillis);
+        }
+
+        /** Implement the Parcelable interface */
+        public static final @NonNull Creator<RadioStats> CREATOR =
+                new Creator<RadioStats>() {
+                    public RadioStats createFromParcel(Parcel in) {
+                        RadioStats stats = new RadioStats();
+                        stats.mRadioId = in.readInt();
+                        stats.mTotalRadioOnTimeMillis = in.readLong();
+                        stats.mTotalRadioTxTimeMillis = in.readLong();
+                        stats.mTotalRadioRxTimeMillis = in.readLong();
+                        stats.mTotalScanTimeMillis = in.readLong();
+                        stats.mTotalNanScanTimeMillis = in.readLong();
+                        stats.mTotalBackgroundScanTimeMillis = in.readLong();
+                        stats.mTotalRoamScanTimeMillis = in.readLong();
+                        stats.mTotalPnoScanTimeMillis = in.readLong();
+                        stats.mTotalHotspot2ScanTimeMillis = in.readLong();
+                        return stats;
+                    }
+                    public RadioStats[] newArray(int size) {
+                        return new RadioStats[size];
+                    }
+                };
+
+        /**
+         * Firmware/Hardware implementation specific persistent value for this
+         * device, identifying the radio interface for which the stats are produced.
+         */
+        public long getRadioId() {
+            return mRadioId;
+        }
+
+        /** The total time the wifi radio is on in ms counted from the last radio chip reset */
+        public long getTotalRadioOnTimeMillis() {
+            return mTotalRadioOnTimeMillis;
+        }
+
+        /**
+         * The total time the wifi radio is transmitting in ms counted from the last radio chip
+         * reset
+         */
+        public long getTotalRadioTxTimeMillis() {
+            return mTotalRadioTxTimeMillis;
+        }
+
+        /**
+         * The total time the wifi radio is receiving in ms counted from the last radio chip reset
+         */
+        public long getTotalRadioRxTimeMillis() {
+            return mTotalRadioRxTimeMillis;
+        }
+
+        /**
+         * The total time spent on all types of scans in ms counted from the last radio chip reset
+         */
+        public long getTotalScanTimeMillis() {
+            return mTotalScanTimeMillis;
+        }
+
+        /** The total time spent on nan scans in ms counted from the last radio chip reset */
+        public long getTotalNanScanTimeMillis() {
+            return mTotalNanScanTimeMillis;
+        }
+
+        /** The total time spent on background scans in ms counted from the last radio chip reset */
+        public long getTotalBackgroundScanTimeMillis() {
+            return mTotalBackgroundScanTimeMillis;
+        }
+
+        /** The total time spent on roam scans in ms counted from the last radio chip reset */
+        public long getTotalRoamScanTimeMillis() {
+            return mTotalRoamScanTimeMillis;
+        }
+
+        /** The total time spent on pno scans in ms counted from the last radio chip reset */
+        public long getTotalPnoScanTimeMillis() {
+            return mTotalPnoScanTimeMillis;
+        }
+
+        /**
+         * The total time spent on hotspot2.0 scans and GAS exchange in ms counted from the
+         * last radio chip reset
+         */
+        public long getTotalHotspot2ScanTimeMillis() {
+            return mTotalHotspot2ScanTimeMillis;
+        }
+    }
+    private final RadioStats[] mRadioStats;
+    private final int mChannelUtilizationRatio;
+    private final boolean mIsThroughputSufficient;
+    private final boolean mIsWifiScoringEnabled;
+    private final boolean mIsCellularDataAvailable;
     private final @NetworkType int mCellularDataNetworkType;
     private final int mCellularSignalStrengthDbm;
     private final int mCellularSignalStrengthDb;
@@ -115,7 +615,10 @@
             long totalCcaBusyFreqTimeMillis, long totalRadioOnFreqTimeMillis, long totalBeaconRx,
             @ProbeStatus int probeStatusSinceLastUpdate, int probeElapsedTimeSinceLastUpdateMillis,
             int probeMcsRateSinceLastUpdate, int rxLinkSpeedMbps,
-            @NetworkType int cellularDataNetworkType,
+            int timeSliceDutyCycleInPercent, ContentionTimeStats[] contentionTimeStats,
+            RateStats[] rateStats, RadioStats[] radiostats, int channelUtilizationRatio,
+            boolean isThroughputSufficient, boolean isWifiScoringEnabled,
+            boolean isCellularDataAvailable, @NetworkType int cellularDataNetworkType,
             int cellularSignalStrengthDbm, int cellularSignalStrengthDb,
             boolean isSameRegisteredCell) {
         mTimeStampMillis = timeStampMillis;
@@ -141,6 +644,14 @@
         mProbeElapsedTimeSinceLastUpdateMillis = probeElapsedTimeSinceLastUpdateMillis;
         mProbeMcsRateSinceLastUpdate = probeMcsRateSinceLastUpdate;
         mRxLinkSpeedMbps = rxLinkSpeedMbps;
+        mTimeSliceDutyCycleInPercent = timeSliceDutyCycleInPercent;
+        mContentionTimeStats = contentionTimeStats;
+        mRateStats = rateStats;
+        mRadioStats = radiostats;
+        mChannelUtilizationRatio = channelUtilizationRatio;
+        mIsThroughputSufficient = isThroughputSufficient;
+        mIsWifiScoringEnabled = isWifiScoringEnabled;
+        mIsCellularDataAvailable = isCellularDataAvailable;
         mCellularDataNetworkType = cellularDataNetworkType;
         mCellularSignalStrengthDbm = cellularSignalStrengthDbm;
         mCellularSignalStrengthDb = cellularSignalStrengthDb;
@@ -177,6 +688,14 @@
         dest.writeInt(mProbeElapsedTimeSinceLastUpdateMillis);
         dest.writeInt(mProbeMcsRateSinceLastUpdate);
         dest.writeInt(mRxLinkSpeedMbps);
+        dest.writeInt(mTimeSliceDutyCycleInPercent);
+        dest.writeTypedArray(mContentionTimeStats, flags);
+        dest.writeTypedArray(mRateStats, flags);
+        dest.writeTypedArray(mRadioStats, flags);
+        dest.writeInt(mChannelUtilizationRatio);
+        dest.writeBoolean(mIsThroughputSufficient);
+        dest.writeBoolean(mIsWifiScoringEnabled);
+        dest.writeBoolean(mIsCellularDataAvailable);
         dest.writeInt(mCellularDataNetworkType);
         dest.writeInt(mCellularSignalStrengthDbm);
         dest.writeInt(mCellularSignalStrengthDb);
@@ -196,8 +715,12 @@
                     in.readLong(), in.readLong(), in.readLong(),
                     in.readLong(), in.readLong(), in.readInt(),
                     in.readInt(), in.readInt(), in.readInt(),
-                    in.readInt(), in.readInt(), in.readInt(),
-                    in.readBoolean()
+                    in.readInt(), in.createTypedArray(ContentionTimeStats.CREATOR),
+                    in.createTypedArray(RateStats.CREATOR),
+                    in.createTypedArray(RadioStats.CREATOR),
+                    in.readInt(), in.readBoolean(), in.readBoolean(),
+                    in.readBoolean(), in.readInt(), in.readInt(),
+                    in.readInt(), in.readBoolean()
             );
         }
 
@@ -323,6 +846,111 @@
         return mRxLinkSpeedMbps;
     }
 
+    /**
+     * Duty cycle of the connection.
+     * if this connection is being served using time slicing on a radio with one or more interfaces
+     * (i.e MCC), then this method returns the duty cycle assigned to this interface in percent.
+     * If no concurrency or not using time slicing during concurrency (i.e SCC or DBS), set to 100.
+     *
+     * @return duty cycle in percent if known.
+     * @throws NoSuchElementException if the duty cylce is unknown (not provided by the HAL).
+     */
+    public @IntRange(from = 0, to = 100) int getTimeSliceDutyCycleInPercent() {
+        if (mTimeSliceDutyCycleInPercent == -1) {
+            throw new NoSuchElementException("Unknown value");
+        }
+        return mTimeSliceDutyCycleInPercent;
+    }
+
+    /**
+     * Data packet contention time statistics for Access Category.
+     * @param ac The access category, see {@link WmeAccessCategory}.
+     * @return The contention time statistics, see {@link ContentionTimeStats}
+     */
+    @NonNull
+    public ContentionTimeStats getContentionTimeStats(@WmeAccessCategory int ac) {
+        if (mContentionTimeStats != null
+                && mContentionTimeStats.length == NUM_WME_ACCESS_CATEGORIES) {
+            return mContentionTimeStats[ac];
+        }
+        Log.e(TAG, "The ContentionTimeStats is not filled out correctly: " + mContentionTimeStats);
+        return new ContentionTimeStats();
+    }
+
+    /**
+     * Rate information and statistics, which are ordered by preamble, modulation and coding scheme
+     * (MCS), and number of spatial streams (NSS).
+     * @return A list of rate statistics in the form of a list of {@link RateStats} objects.
+     *         Depending on the link type, the list is created following the order of:
+     *         - HT (IEEE Std 802.11-2020, Section 19): LEGACY rates (1Mbps, ..., 54Mbps),
+     *           HT MCS0, ..., MCS15;
+     *         - VHT (IEEE Std 802.11-2020, Section 21): LEGACY rates (1Mbps, ..., 54Mbps),
+     *           VHT MCS0/NSS1, ..., VHT MCS11/NSS1, VHT MCSO/NSS2, ..., VHT MCS11/NSS2;
+     *         - HE (IEEE Std 802.11-2020, Section 27): LEGACY rates (1Mbps, ..., 54Mbps),
+     *           HE MCS0/NSS1, ..., HE MCS11/NSS1, HE MCSO/NSS2, ..., HE MCS11/NSS2.
+     */
+    @NonNull
+    public List<RateStats> getRateStats() {
+        if (mRateStats != null) {
+            return Arrays.asList(mRateStats);
+        }
+        return Collections.emptyList();
+    }
+
+    /**
+     * Radio stats from all the radios, see {@link RadioStats#getRadioId()}
+     * @return A list of Wifi link layer radio stats, see {@link RadioStats}
+     */
+    @NonNull
+    public List<RadioStats> getWifiLinkLayerRadioStats() {
+        if (mRadioStats != null) {
+            return Arrays.asList(mRadioStats);
+        }
+        return Collections.emptyList();
+    }
+
+    /**
+     * Channel utilization ratio on the current channel.
+     *
+     * @return The channel utilization ratio (value) in the range of [0, 255], where
+     *         x corresponds to (x * 100 / 255)%.
+     */
+    public @IntRange(from = 0, to = 255) int getChannelUtilizationRatio() {
+        return mChannelUtilizationRatio;
+    }
+
+    /**
+     * Indicate whether current link layer (L2) throughput is sufficient.  L2 throughput is
+     * sufficient when one of the following conditions is met: 1) L3 throughput is low and L2
+     * throughput is above its low threshold; 2) L3 throughput is not low and L2 throughput over L3
+     * throughput ratio is above a threshold; 3) L3 throughput is not low and L2 throughput is
+     * above its high threshold.
+     *
+     * @return true if it is sufficient or false if it is insufficient.
+     */
+    public boolean isThroughputSufficient() {
+        return mIsThroughputSufficient;
+    }
+
+    /**
+     * Indicate whether Wi-Fi scoring is enabled by the user,
+     * see {@link WifiManager#setWifiScoringEnabled(boolean)}.
+     *
+     * @return true if it is enabled.
+     */
+    public boolean isWifiScoringEnabled() {
+        return mIsWifiScoringEnabled;
+    }
+
+    /**
+     * Indicate whether Cellular data is available.
+     *
+     * @return true if it is available and false otherwise.
+     */
+    public boolean isCellularDataAvailable() {
+        return mIsCellularDataAvailable;
+    }
+
     /** Cellular data network type currently in use on the device for data transmission */
     @NetworkType public int getCellularDataNetworkType() {
         return mCellularDataNetworkType;
diff --git a/framework/java/android/net/wifi/aware/AwareResources.java b/framework/java/android/net/wifi/aware/AwareResources.java
new file mode 100644
index 0000000..f609c4a
--- /dev/null
+++ b/framework/java/android/net/wifi/aware/AwareResources.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.aware;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * The resources of the Aware service.
+ */
+public final class AwareResources implements Parcelable {
+    /**
+     * Number of the NDPs are available.
+     */
+    private int mNumOfAvailableNdps;
+
+    /**
+     * Number of the publish sessions are available.
+     */
+    private int mNumOfAvailablePublishSessions;
+
+    /**
+     * Number of the subscribe sessions are available.
+     */
+    private int mNumOfAvailableSubscribeSessions;
+
+    /**
+     * Construct a {@link AwareResources} object, which represents the currently available Aware
+     * resources.
+     *
+     * @param availableDataPathsCount Number of available Aware data-path.
+     * @param availablePublishSessionsCount Number of available Aware publish sessions.
+     * @param availableSubscribeSessionsCount Number of available Aware subscribe sessions.
+     */
+    public AwareResources(@IntRange(from = 0) int availableDataPathsCount,
+            @IntRange(from = 0) int availablePublishSessionsCount,
+            @IntRange(from = 0) int availableSubscribeSessionsCount) {
+        mNumOfAvailableNdps = availableDataPathsCount;
+        mNumOfAvailablePublishSessions = availablePublishSessionsCount;
+        mNumOfAvailableSubscribeSessions = availableSubscribeSessionsCount;
+    }
+
+    /**
+     * Return the number of Aware data-paths (also known as NDPs - NAN Data Paths) which an app
+     * could create. Please refer to the {@link WifiAwareNetworkSpecifier} to create
+     * a Network Specifier and request a data-path.
+     * <p>
+     * Note that these resources aren't reserved - other apps could use them by the time you
+     * attempt to create a data-path.
+     * </p>
+     * @return A Non-negative integer, number of data-paths that could be created.
+     */
+    @IntRange(from = 0)
+    public int getAvailableDataPathsCount() {
+        return mNumOfAvailableNdps;
+    }
+
+    /**
+     * Return the number of Aware publish sessions which an app could create. Please refer to the
+     * {@link WifiAwareSession#publish(PublishConfig, DiscoverySessionCallback, Handler)}
+     * to create a publish session.
+     * <p>
+     * Note that these resources aren't reserved - other apps could use them by the time you
+     * attempt to create a publish session.
+     * </p>
+     * @return A Non-negative integer, number of publish sessions that could be created.
+     */
+    @IntRange(from = 0)
+    public int getAvailablePublishSessionsCount() {
+        return mNumOfAvailablePublishSessions;
+    }
+
+    /**
+     * Return the number of Aware subscribe sessions which an app could create. Please refer to the
+     * {@link WifiAwareSession#subscribe(SubscribeConfig, DiscoverySessionCallback, Handler)}
+     * to create a subscribe session.
+     * <p>
+     * Note that these resources aren't reserved - other apps could use them by the time you
+     * attempt to create a subscribe session.
+     * </p>
+     * @return A Non-negative integer, number of subscribe sessions that could be created.
+     */
+    @IntRange(from = 0)
+    public int getAvailableSubscribeSessionsCount() {
+        return mNumOfAvailableSubscribeSessions;
+    }
+
+    /**
+     * Set the number of the available NDPs.
+     * @hide
+     * @param numOfAvailableNdps Number of available NDPs.
+     */
+    public void setNumOfAvailableDataPaths(int numOfAvailableNdps) {
+        mNumOfAvailableNdps = numOfAvailableNdps;
+    }
+
+    /**
+     * Set the number of the available publish sessions.
+     * @hide
+     * @param numOfAvailablePublishSessions Number of available publish sessions.
+     */
+    public void setNumOfAvailablePublishSessions(int numOfAvailablePublishSessions) {
+        mNumOfAvailablePublishSessions = numOfAvailablePublishSessions;
+    }
+
+    /**
+     * Set the number of the available subscribe sessions.
+     * @hide
+     * @param numOfAvailableSubscribeSessions Number of available subscribe sessions.
+     */
+    public void setNumOfAvailableSubscribeSessions(int numOfAvailableSubscribeSessions) {
+        mNumOfAvailableSubscribeSessions = numOfAvailableSubscribeSessions;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mNumOfAvailableNdps);
+        dest.writeInt(mNumOfAvailablePublishSessions);
+        dest.writeInt(mNumOfAvailableSubscribeSessions);
+    }
+
+    public static final @android.annotation.NonNull Creator<AwareResources> CREATOR =
+            new Creator<AwareResources>() {
+                @Override
+                public AwareResources createFromParcel(Parcel in) {
+                    return new AwareResources(in.readInt(), in.readInt(), in.readInt());
+                }
+
+                @Override
+                public AwareResources[] newArray(int size) {
+                    return new AwareResources[size];
+                }
+            };
+}
diff --git a/framework/java/android/net/wifi/aware/Characteristics.java b/framework/java/android/net/wifi/aware/Characteristics.java
index d5fd48e..42aac20 100644
--- a/framework/java/android/net/wifi/aware/Characteristics.java
+++ b/framework/java/android/net/wifi/aware/Characteristics.java
@@ -17,10 +17,15 @@
 package android.net.wifi.aware;
 
 import android.annotation.IntDef;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import androidx.annotation.RequiresApi;
+
+import com.android.modules.utils.build.SdkLevel;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -37,6 +42,9 @@
     public static final String KEY_MAX_MATCH_FILTER_LENGTH = "key_max_match_filter_length";
     /** @hide */
     public static final String KEY_SUPPORTED_CIPHER_SUITES = "key_supported_cipher_suites";
+    /** @hide */
+    public static final String KEY_IS_INSTANT_COMMUNICATION_MODE_SUPPORTED =
+            "key_is_instant_communication_mode_supported";
 
     private Bundle mCharacteristics = new Bundle();
 
@@ -83,6 +91,19 @@
         return mCharacteristics.getInt(KEY_MAX_MATCH_FILTER_LENGTH);
     }
 
+    /**
+     * Check if instant communication mode is supported by device. The instant communication mode is
+     * defined as per Wi-Fi Alliance (WFA) Wi-Fi Aware specifications version 3.1 Section 12.3.
+     * @return True if supported, false otherwise.
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    public boolean isInstantCommunicationModeSupported() {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        return mCharacteristics.getBoolean(KEY_IS_INSTANT_COMMUNICATION_MODE_SUPPORTED);
+    }
+
     /** @hide */
     @IntDef(flag = true, prefix = { "WIFI_AWARE_CIPHER_SUITE_" }, value = {
             WIFI_AWARE_CIPHER_SUITE_NCS_SK_128,
diff --git a/framework/java/android/net/wifi/aware/DiscoverySessionCallback.java b/framework/java/android/net/wifi/aware/DiscoverySessionCallback.java
index bfb0462..da8e17e 100644
--- a/framework/java/android/net/wifi/aware/DiscoverySessionCallback.java
+++ b/framework/java/android/net/wifi/aware/DiscoverySessionCallback.java
@@ -189,4 +189,20 @@
     public void onMessageReceived(PeerHandle peerHandle, byte[] message) {
         /* empty */
     }
+
+    /**
+     * Called when the discovered service is not available. All further operations on this
+     * discovery session will fail. If the service is available again,
+     * {@link #onServiceDiscovered(PeerHandle, byte[], List)} or
+     * {@link #onServiceDiscoveredWithinRange(PeerHandle, byte[], List, int)} will be called.
+     *
+     * @param peerHandle An opaque handle to the peer matching our discovery operation.
+     * @param reason Discovered service lost reason code. One of
+     *               {@link WifiAwareManager#WIFI_AWARE_DISCOVERY_LOST_REASON_PEER_NOT_VISIBLE},
+     *               {@link WifiAwareManager#WIFI_AWARE_DISCOVERY_LOST_REASON_UNKNOWN
+     */
+    public void onServiceLost(@NonNull PeerHandle peerHandle,
+            @WifiAwareManager.DiscoveryLostReasonCode int reason) {
+        /* empty */
+    }
 }
diff --git a/framework/java/android/net/wifi/aware/IWifiAwareDiscoverySessionCallback.aidl b/framework/java/android/net/wifi/aware/IWifiAwareDiscoverySessionCallback.aidl
index 421a8af..e3e7c8e 100644
--- a/framework/java/android/net/wifi/aware/IWifiAwareDiscoverySessionCallback.aidl
+++ b/framework/java/android/net/wifi/aware/IWifiAwareDiscoverySessionCallback.aidl
@@ -35,4 +35,5 @@
     void onMessageSendSuccess(int messageId);
     void onMessageSendFail(int messageId, int reason);
     void onMessageReceived(int peerId, in byte[] message);
+    void onMatchExpired(int peerId);
 }
diff --git a/framework/java/android/net/wifi/aware/IWifiAwareManager.aidl b/framework/java/android/net/wifi/aware/IWifiAwareManager.aidl
index 88f95ad..c90c4d8 100644
--- a/framework/java/android/net/wifi/aware/IWifiAwareManager.aidl
+++ b/framework/java/android/net/wifi/aware/IWifiAwareManager.aidl
@@ -25,6 +25,7 @@
 import android.net.wifi.aware.PublishConfig;
 import android.net.wifi.aware.SubscribeConfig;
 import android.net.wifi.aware.Characteristics;
+import android.net.wifi.aware.AwareResources;
 
 /**
  * Interface that WifiAwareService implements
@@ -36,6 +37,10 @@
     // Aware API
     boolean isUsageEnabled();
     Characteristics getCharacteristics();
+    AwareResources getAvailableAwareResources();
+    boolean isDeviceAttached();
+    void enableInstantCommunicationMode(in String callingPackage, boolean enable);
+    boolean isInstantCommunicationModeEnabled();
 
     // client API
     void connect(in IBinder binder, in String callingPackage, in String callingFeatureId,
diff --git a/framework/java/android/net/wifi/aware/PublishConfig.java b/framework/java/android/net/wifi/aware/PublishConfig.java
index a8844c1..c795a65 100644
--- a/framework/java/android/net/wifi/aware/PublishConfig.java
+++ b/framework/java/android/net/wifi/aware/PublishConfig.java
@@ -240,8 +240,11 @@
          * <p>
          * The Service Name is a UTF-8 encoded string from 1 to 255 bytes in length.
          * The only acceptable single-byte UTF-8 symbols for a Service Name are alphanumeric
-         * values (A-Z, a-z, 0-9), the hyphen ('-'), and the period ('.'). All valid multi-byte
-         * UTF-8 characters are acceptable in a Service Name.
+         * values (A-Z, a-z, 0-9), the hyphen ('-'), the period ('.') and the underscore ('_'). All
+         * valid multi-byte UTF-8 characters are acceptable in a Service Name.
+         * <p>
+         * Note: for compatibility with devices running Android 11 or older, avoid using
+         * underscore ('_') symbol as a single-byte UTF-8 service name.
          * <p>
          * Must be called - an empty ServiceName is not valid.
          *
diff --git a/framework/java/android/net/wifi/aware/SubscribeConfig.java b/framework/java/android/net/wifi/aware/SubscribeConfig.java
index 76780f4..51c6e25 100644
--- a/framework/java/android/net/wifi/aware/SubscribeConfig.java
+++ b/framework/java/android/net/wifi/aware/SubscribeConfig.java
@@ -297,8 +297,8 @@
          * <p>
          * The Service Name is a UTF-8 encoded string from 1 to 255 bytes in length.
          * The only acceptable single-byte UTF-8 symbols for a Service Name are alphanumeric
-         * values (A-Z, a-z, 0-9), the hyphen ('-'), and the period ('.'). All valid multi-byte
-         * UTF-8 characters are acceptable in a Service Name.
+         * values (A-Z, a-z, 0-9), the hyphen ('-'), the period ('.') and the underscore ('_'). All
+         * valid multi-byte UTF-8 characters are acceptable in a Service Name.
          * <p>
          * Must be called - an empty ServiceName is not valid.
          *
diff --git a/framework/java/android/net/wifi/aware/TlvBufferUtils.java b/framework/java/android/net/wifi/aware/TlvBufferUtils.java
index 2d3cc1e..5e8cc6a 100644
--- a/framework/java/android/net/wifi/aware/TlvBufferUtils.java
+++ b/framework/java/android/net/wifi/aware/TlvBufferUtils.java
@@ -114,7 +114,7 @@
         }
 
         /**
-         * Allocates a new byte array to be used ot construct a TLV.
+         * Allocates a new byte array to be used to construct a TLV.
          *
          * @param capacity The size of the byte array to be allocated.
          * @return The constructor to facilitate chaining
diff --git a/framework/java/android/net/wifi/aware/WifiAwareManager.java b/framework/java/android/net/wifi/aware/WifiAwareManager.java
index c2ae17c..d25036d 100644
--- a/framework/java/android/net/wifi/aware/WifiAwareManager.java
+++ b/framework/java/android/net/wifi/aware/WifiAwareManager.java
@@ -22,6 +22,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.content.Context;
 import android.net.ConnectivityManager;
@@ -37,6 +38,10 @@
 import android.os.RemoteException;
 import android.util.Log;
 
+import androidx.annotation.RequiresApi;
+
+import com.android.modules.utils.build.SdkLevel;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
@@ -115,8 +120,9 @@
     private static final boolean VDBG = false; // STOPSHIP if true
 
     /**
-     * Broadcast intent action to indicate that the state of Wi-Fi Aware availability has changed.
-     * Use the {@link #isAvailable()} to query the current status.
+     * Broadcast intent action to indicate that the state of Wi-Fi Aware availability has changed
+     * and all active Aware sessions are no longer usable. Use the {@link #isAvailable()} to query
+     * the current status.
      * This broadcast is <b>not</b> sticky, use the {@link #isAvailable()} API after registering
      * the broadcast to check the current state of Wi-Fi Aware.
      * <p>Note: The broadcast is only delivered to registered receivers - no manifest registered
@@ -151,6 +157,27 @@
      */
     public static final int WIFI_AWARE_DATA_PATH_ROLE_RESPONDER = 1;
 
+    /** @hide */
+    @IntDef({
+            WIFI_AWARE_DISCOVERY_LOST_REASON_UNKNOWN,
+            WIFI_AWARE_DISCOVERY_LOST_REASON_PEER_NOT_VISIBLE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DiscoveryLostReasonCode {
+    }
+
+    /**
+     * Reason code provided in {@link DiscoverySessionCallback#onServiceLost(PeerHandle, int)}
+     * indicating that the service was lost for unknown reason.
+     */
+    public static final int WIFI_AWARE_DISCOVERY_LOST_REASON_UNKNOWN = 0;
+
+    /**
+     * Reason code provided in {@link DiscoverySessionCallback#onServiceLost(PeerHandle, int)}
+     * indicating that the service advertised by the peer is no longer visible. This may be because
+     * the peer is out of range or because the peer stopped advertising this service.
+     */
+    public static final int WIFI_AWARE_DISCOVERY_LOST_REASON_PEER_NOT_VISIBLE = 1;
+
     private final Context mContext;
     private final IWifiAwareManager mService;
 
@@ -179,12 +206,71 @@
     }
 
     /**
+     * Return the current status of the Aware service: whether or not the device is already attached
+     * to an Aware cluster. To attach to an Aware cluster, please use
+     * {@link #attach(AttachCallback, Handler)} or
+     * {@link #attach(AttachCallback, IdentityChangedListener, Handler)}.
+     * @return A boolean indicating whether the device is attached to a cluster at this time (true)
+     *         or not (false).
+     */
+    public boolean isDeviceAttached() {
+        try {
+            return mService.isDeviceAttached();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Enable the Wifi Aware Instant communication mode. If the device doesn't support this feature
+     * calling this API will result no action.
+     * @see Characteristics#isInstantCommunicationModeSupported()
+     * @param enable true for enable, false otherwise.
+     * @hide
+     */
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.S)
+    public void enableInstantCommunicationMode(boolean enable) {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            mService.enableInstantCommunicationMode(mContext.getOpPackageName(), enable);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Return the current status of the Wifi Aware instant communication mode.
+     * If the device doesn't support this feature, return will always be false.
+     * @see Characteristics#isInstantCommunicationModeSupported()
+     * @return true if it is enabled, false otherwise.
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    public boolean isInstantCommunicationModeEnabled() {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return mService.isInstantCommunicationModeEnabled();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns the characteristics of the Wi-Fi Aware interface: a set of parameters which specify
      * limitations on configurations, e.g. the maximum service name length.
+     * <p>
+     * May return {@code null} if the Wi-Fi Aware service is not initialized. Use
+     * {@link #attach(AttachCallback, Handler)} or
+     * {@link #attach(AttachCallback, IdentityChangedListener, Handler)} to initialize the Wi-Fi
+     * Aware service.
      *
      * @return An object specifying configuration limitations of Aware.
      */
-    public Characteristics getCharacteristics() {
+    public @Nullable Characteristics getCharacteristics() {
         try {
             return mService.getCharacteristics();
         } catch (RemoteException e) {
@@ -193,6 +279,25 @@
     }
 
     /**
+     * Return the available resources of the Wi-Fi aware service: a set of parameters which specify
+     * limitations on service usage, e.g the number of data-paths which could be created.
+     * <p>
+     * May return {@code null} if the Wi-Fi Aware service is not initialized. Use
+     * {@link #attach(AttachCallback, Handler)} or
+     * {@link #attach(AttachCallback, IdentityChangedListener, Handler)} to initialize the Wi-Fi
+     * Aware service.
+     *
+     * @return An object specifying the currently available resource of the Wi-Fi Aware service.
+     */
+    public @Nullable AwareResources getAvailableAwareResources() {
+        try {
+            return mService.getAvailableAwareResources();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Attach to the Wi-Fi Aware service - enabling the application to create discovery sessions or
      * create connections to peers. The device will attach to an existing cluster if it can find
      * one or create a new cluster (if it is the first to enable Aware in its vicinity). Results
@@ -587,6 +692,7 @@
         private static final int CALLBACK_MESSAGE_SEND_FAIL = 6;
         private static final int CALLBACK_MESSAGE_RECEIVED = 7;
         private static final int CALLBACK_MATCH_WITH_DISTANCE = 8;
+        private static final int CALLBACK_MATCH_EXPIRED = 9;
 
         private static final String MESSAGE_BUNDLE_KEY_MESSAGE = "message";
         private static final String MESSAGE_BUNDLE_KEY_MESSAGE2 = "message2";
@@ -676,6 +782,11 @@
                             mOriginalCallback.onMessageReceived(new PeerHandle(msg.arg1),
                                     (byte[]) msg.obj);
                             break;
+                        case CALLBACK_MATCH_EXPIRED:
+                            mOriginalCallback
+                                    .onServiceLost(new PeerHandle(msg.arg1),
+                                            WIFI_AWARE_DISCOVERY_LOST_REASON_PEER_NOT_VISIBLE);
+                            break;
                     }
                 }
             };
@@ -746,6 +857,15 @@
             onMatchCommon(CALLBACK_MATCH_WITH_DISTANCE, peerId, serviceSpecificInfo, matchFilter,
                     distanceMm);
         }
+        @Override
+        public void onMatchExpired(int peerId) {
+            if (VDBG) {
+                Log.v(TAG, "onMatchExpired: peerId=" + peerId);
+            }
+            Message msg = mHandler.obtainMessage(CALLBACK_MATCH_EXPIRED);
+            msg.arg1 = peerId;
+            mHandler.sendMessage(msg);
+        }
 
         @Override
         public void onMessageSendSuccess(int messageId) {
diff --git a/framework/java/android/net/wifi/aware/WifiAwareNetworkInfo.java b/framework/java/android/net/wifi/aware/WifiAwareNetworkInfo.java
index 1e593f6..c9a1114 100644
--- a/framework/java/android/net/wifi/aware/WifiAwareNetworkInfo.java
+++ b/framework/java/android/net/wifi/aware/WifiAwareNetworkInfo.java
@@ -23,8 +23,6 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import com.android.modules.utils.build.SdkLevel;
-
 import java.net.Inet6Address;
 import java.net.NetworkInterface;
 import java.net.SocketException;
@@ -134,7 +132,7 @@
         dest.writeInt(mTransportProtocol);
     }
 
-    public static final @android.annotation.NonNull Creator<WifiAwareNetworkInfo> CREATOR =
+    public static final @NonNull Creator<WifiAwareNetworkInfo> CREATOR =
             new Creator<WifiAwareNetworkInfo>() {
                 @Override
                 public WifiAwareNetworkInfo createFromParcel(Parcel in) {
@@ -197,33 +195,4 @@
     public int hashCode() {
         return Objects.hash(mIpv6Addr, mPort, mTransportProtocol);
     }
-
-    /**
-     * Make a copy of WifiAwareNetworkInfo instance.
-     *
-     * @param parcelSensitiveFields Whether to parcel location sensitive fields or not.
-     * @return instance of {@link WifiAwareNetworkInfo}.
-     *
-     * @hide
-     */
-    @NonNull
-    public WifiAwareNetworkInfo makeCopy(boolean parcelSensitiveFields) {
-        if (!SdkLevel.isAtLeastS()) {
-            throw new UnsupportedOperationException();
-        }
-        // No location sensitive data, ignore parcelSensitiveFields.
-        return new WifiAwareNetworkInfo(this);
-    }
-
-    /**
-     * Whether it has location sensitive data or not.
-     *
-     * @hide
-     */
-    public boolean hasLocationSensitiveFields() {
-        if (!SdkLevel.isAtLeastS()) {
-            throw new UnsupportedOperationException();
-        }
-        return false;
-    }
 }
diff --git a/framework/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java b/framework/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java
index 3547750..423af72 100644
--- a/framework/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java
+++ b/framework/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java
@@ -20,11 +20,19 @@
 
 import android.annotation.IntRange;
 import android.annotation.NonNull;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkRequest;
 import android.net.NetworkSpecifier;
+import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import androidx.annotation.RequiresApi;
+
+import com.android.modules.utils.build.SdkLevel;
+
 import java.util.Arrays;
 import java.util.Objects;
 
@@ -287,7 +295,10 @@
 
         /**
          * Create a builder for {@link WifiAwareNetworkSpecifier} used in requests to set up a
-         * Wi-Fi Aware connection with a peer.
+         * Wi-Fi Aware connection with a specific peer.
+         * <p>
+         * To set up a connection to any peer or to multiple peers use
+         * {@link #Builder(PublishDiscoverySession)}.
          *
          * @param discoverySession A Wi-Fi Aware discovery session in whose context the connection
          *                         is created.
@@ -310,6 +321,32 @@
         }
 
         /**
+         * Create a builder for {@link WifiAwareNetworkSpecifier} used in requests to set up a
+         * Wi-Fi Aware connection. This configuration allows connections to any peers or to
+         * multiple peers (as opposed to only a specific peer with
+         * {@link #Builder(DiscoverySession, PeerHandle)}).
+         * <p>
+         * Multiple connections can be triggered by this configuration and using a single request
+         * via {@link ConnectivityManager#requestNetwork(NetworkRequest, ConnectivityManager.NetworkCallback)}
+         * and similar methods. Each successful connection will be signaled via the standard
+         * Connectivity Manager mechanisms -
+         * {@link ConnectivityManager.NetworkCallback#onAvailable(Network)}.
+         * Calling {@link ConnectivityManager#unregisterNetworkCallback(ConnectivityManager.NetworkCallback)}
+         * will terminate all connections.
+         */
+        @RequiresApi(Build.VERSION_CODES.S)
+        public Builder(@NonNull PublishDiscoverySession publishDiscoverySession) {
+            if (!SdkLevel.isAtLeastS()) {
+                throw new UnsupportedOperationException();
+            }
+            if (publishDiscoverySession == null) {
+                throw new IllegalArgumentException("Non-null publishDiscoverySession required");
+            }
+            mDiscoverySession = publishDiscoverySession;
+            mPeerHandle = null;
+        }
+
+        /**
          * Configure the PSK Passphrase for the Wi-Fi Aware connection being requested. This method
          * is optional - if not called, then an Open (unencrypted) connection will be created.
          *
@@ -413,9 +450,6 @@
             if (mDiscoverySession == null) {
                 throw new IllegalStateException("Null discovery session!?");
             }
-            if (mPeerHandle == null) {
-                throw new IllegalStateException("Null peerHandle!?");
-            }
             if (mPskPassphrase != null & mPmk != null) {
                 throw new IllegalStateException(
                         "Can only specify a Passphrase or a PMK - not both!");
@@ -436,10 +470,12 @@
                             + "only be specified on a secure link");
                 }
             }
+            int type = mPeerHandle == null
+                    ? WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB_ANY_PEER :
+                    WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB;
 
-            return new WifiAwareNetworkSpecifier(
-                    WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB, role,
-                    mDiscoverySession.mClientId, mDiscoverySession.mSessionId, mPeerHandle.peerId,
+            return new WifiAwareNetworkSpecifier(type, role, mDiscoverySession.mClientId,
+                    mDiscoverySession.mSessionId, mPeerHandle != null ? mPeerHandle.peerId : 0,
                     null, mPmk, mPskPassphrase, mPort, mTransportProtocol);
         }
     }
diff --git a/framework/java/android/net/wifi/aware/WifiAwareSession.java b/framework/java/android/net/wifi/aware/WifiAwareSession.java
index fe0872c..d31baa9 100644
--- a/framework/java/android/net/wifi/aware/WifiAwareSession.java
+++ b/framework/java/android/net/wifi/aware/WifiAwareSession.java
@@ -130,6 +130,8 @@
      * Other results of the publish session operations will also be routed to callbacks
      * on the {@code callback} object. The resulting publish session can be modified using
      * {@link PublishDiscoverySession#updatePublish(PublishConfig)}.
+     * <p> The total count of currently available Wi-Fi Aware publish sessions is limited and is
+     * available via the {@link AwareResources#getAvailablePublishSessionsCount()} method.
      * <p>
      *      An application must use the {@link DiscoverySession#close()} to
      *      terminate the publish discovery session once it isn't needed. This will free
@@ -176,6 +178,8 @@
      * Other results of the subscribe session operations will also be routed to callbacks
      * on the {@code callback} object. The resulting subscribe session can be modified using
      * {@link SubscribeDiscoverySession#updateSubscribe(SubscribeConfig)}.
+     * <p> The total count of currently available Wi-Fi Aware subscribe sessions is limited and is
+     * available via the {@link AwareResources#getAvailableSubscribeSessionsCount()} method.
      * <p>
      *      An application must use the {@link DiscoverySession#close()} to
      *      terminate the subscribe discovery session once it isn't needed. This will free
@@ -219,6 +223,11 @@
      * To set up an encrypted link use the
      * {@link #createNetworkSpecifierPassphrase(int, byte[], String)} API.
      *
+     * @deprecated Please use in-band data-path setup, refer to
+     * {@link WifiAwareNetworkSpecifier.Builder},
+     * {@link #publish(PublishConfig, DiscoverySessionCallback, Handler)} and
+     * {@link #subscribe(SubscribeConfig, DiscoverySessionCallback, Handler)}
+     *
      * @param role  The role of this device:
      *              {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_INITIATOR} or
      *              {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_RESPONDER}
@@ -232,6 +241,7 @@
      * android.net.ConnectivityManager.NetworkCallback)}
      * [or other varieties of that API].
      */
+    @Deprecated
     public NetworkSpecifier createNetworkSpecifierOpen(
             @WifiAwareManager.DataPathRole int role, @NonNull byte[] peer) {
         WifiAwareManager mgr = mMgr.get();
@@ -257,6 +267,11 @@
      *     when using Aware discovery use the alternative network specifier method -
      *     {@link android.net.wifi.aware.WifiAwareNetworkSpecifier.Builder}.
      *
+     * @deprecated Please use in-band data-path setup, refer to
+     * {@link WifiAwareNetworkSpecifier.Builder},
+     * {@link #publish(PublishConfig, DiscoverySessionCallback, Handler)} and
+     * {@link #subscribe(SubscribeConfig, DiscoverySessionCallback, Handler)}
+     *
      * @param role  The role of this device:
      *              {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_INITIATOR} or
      *              {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_RESPONDER}
@@ -273,6 +288,7 @@
      * android.net.ConnectivityManager.NetworkCallback)}
      * [or other varieties of that API].
      */
+    @Deprecated
     public NetworkSpecifier createNetworkSpecifierPassphrase(
             @WifiAwareManager.DataPathRole int role, @NonNull byte[] peer,
             @NonNull String passphrase) {
@@ -303,6 +319,11 @@
      *     when using Aware discovery use the alternative network specifier method -
      *     {@link android.net.wifi.aware.WifiAwareNetworkSpecifier.Builder}.
      *
+     * @deprecated Please use in-band data-path setup, refer to
+     * {@link WifiAwareNetworkSpecifier.Builder},
+     * {@link #publish(PublishConfig, DiscoverySessionCallback, Handler)} and
+     * {@link #subscribe(SubscribeConfig, DiscoverySessionCallback, Handler)}
+     *
      * @param role  The role of this device:
      *              {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_INITIATOR} or
      *              {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_RESPONDER}
@@ -323,6 +344,7 @@
      *
      * @hide
      */
+    @Deprecated
     @SystemApi
     public NetworkSpecifier createNetworkSpecifierPmk(
             @WifiAwareManager.DataPathRole int role, @NonNull byte[] peer, @NonNull byte[] pmk) {
diff --git a/framework/java/android/net/wifi/aware/WifiAwareUtils.java b/framework/java/android/net/wifi/aware/WifiAwareUtils.java
index 3ece93d..f1db6ed 100644
--- a/framework/java/android/net/wifi/aware/WifiAwareUtils.java
+++ b/framework/java/android/net/wifi/aware/WifiAwareUtils.java
@@ -29,8 +29,8 @@
     /**
      * Per spec: The Service Name is a UTF-8 encoded string from 1 to 255 bytes in length. The
      * only acceptable single-byte UTF-8 symbols for a Service Name are alphanumeric values (A-Z,
-     * a-z, 0-9), the hyphen ('-'), and the period ('.'). All valid multi-byte UTF-8 characters
-     * are acceptable in a Service Name.
+     * a-z, 0-9), the hyphen ('-'), the underscore ('_') and the period ('.'). All valid multi-byte
+     * UTF-8 characters are acceptable in a Service Name.
      */
     public static void validateServiceName(byte[] serviceNameData) throws IllegalArgumentException {
         if (serviceNameData == null) {
@@ -47,9 +47,9 @@
             byte b = serviceNameData[index];
             if ((b & 0x80) == 0x00) {
                 if (!((b >= '0' && b <= '9') || (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z')
-                        || b == '-' || b == '.')) {
+                        || b == '-' || b == '.' || b == '_')) {
                     throw new IllegalArgumentException("Invalid service name - illegal characters,"
-                            + " allowed = (0-9, a-z,A-Z, -, .)");
+                            + " allowed = (0-9, a-z,A-Z, -, _, .)");
                 }
             }
             ++index;
diff --git a/framework/java/android/net/wifi/hotspot2/ConfigParser.java b/framework/java/android/net/wifi/hotspot2/ConfigParser.java
index bb01365..7b7f943 100644
--- a/framework/java/android/net/wifi/hotspot2/ConfigParser.java
+++ b/framework/java/android/net/wifi/hotspot2/ConfigParser.java
@@ -207,7 +207,7 @@
                 config.getCredential().setClientCertificateChain(
                         clientKey.second.toArray(new X509Certificate[clientKey.second.size()]));
             } catch(GeneralSecurityException | IOException e) {
-                throw new IOException("Failed to parse PCKS12 string");
+                throw new IOException("Failed to parse PKCS12 string: " + e.getMessage());
             }
         }
         return config;
diff --git a/framework/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/framework/java/android/net/wifi/hotspot2/PasspointConfiguration.java
index d1d1780..bafa049 100644
--- a/framework/java/android/net/wifi/hotspot2/PasspointConfiguration.java
+++ b/framework/java/android/net/wifi/hotspot2/PasspointConfiguration.java
@@ -23,17 +23,25 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.net.wifi.WifiManager;
 import android.net.wifi.hotspot2.pps.Credential;
 import android.net.wifi.hotspot2.pps.HomeSp;
 import android.net.wifi.hotspot2.pps.Policy;
 import android.net.wifi.hotspot2.pps.UpdateParameter;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.RequiresApi;
+
+import com.android.modules.utils.build.SdkLevel;
+
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.Collections;
@@ -412,6 +420,12 @@
     private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
 
     /**
+     * The subscription ID identifies the SIM card who provides this network configuration.
+     * See {@link SubscriptionInfo#getSubscriptionId()}
+     */
+    private int mSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+    /**
      * Set the carrier ID associated with current configuration.
      * @param carrierId {@code mCarrierId}
      * @hide
@@ -430,6 +444,24 @@
     }
 
     /**
+     * Set the subscription ID associated with current configuration.
+     * @param subscriptionId {@code mSubscriptionId}
+     * @hide
+     */
+    public void setSubscriptionId(int subscriptionId) {
+        this.mSubscriptionId = subscriptionId;
+    }
+
+    /**
+     * Get the carrier ID associated with current configuration.
+     * @return {@code mSubscriptionId}
+     * @hide
+     */
+    public int getSubscriptionId() {
+        return mSubscriptionId;
+    }
+
+    /**
      * The auto-join configuration specifies whether or not the Passpoint Configuration is
      * considered for auto-connection. If true then yes, if false then it isn't considered as part
      * of auto-connection - but can still be manually connected to.
@@ -444,6 +476,32 @@
     private boolean mIsMacRandomizationEnabled = true;
 
     /**
+     * Whether this passpoint configuration should use enhanced MAC randomization.
+     */
+    private boolean mIsEnhancedMacRandomizationEnabled = false;
+
+
+    /**
+     * Indicate whether the network is oem paid or not. Networks are considered oem paid
+     * if the corresponding connection is only available to system apps.
+     * @hide
+     */
+    private boolean mIsOemPaid;
+
+    /**
+     * Indicate whether the network is oem private or not. Networks are considered oem private
+     * if the corresponding connection is only available to system apps.
+     * @hide
+     */
+    private boolean mIsOemPrivate;
+
+    /**
+     * Indicate whether or not the network is a carrier merged network.
+     * @hide
+     */
+    private boolean mIsCarrierMerged;
+
+    /**
      * Indicates if the end user has expressed an explicit opinion about the
      * meteredness of this network, such as through the Settings app.
      * This value is one of {@link #METERED_OVERRIDE_NONE}, {@link #METERED_OVERRIDE_METERED},
@@ -455,6 +513,8 @@
      */
     private int mMeteredOverride = METERED_OVERRIDE_NONE;
 
+    private String mDecoratedIdentityPrefix;
+
     /**
      * Configures the auto-association status of this Passpoint configuration. A value of true
      * indicates that the configuration will be considered for auto-connection, a value of false
@@ -481,6 +541,20 @@
     }
 
     /**
+     * This setting is only applicable if MAC randomization is enabled.
+     * If set to true, the framework will periodically generate new MAC addresses for new
+     * connections.
+     * If set to false (the default), the framework will use the same locally generated MAC address
+     * for connections to this passpoint configuration.
+     * @param enabled true to use enhanced MAC randomization, false to use persistent MAC
+     *                randomization.
+     * @hide
+     */
+    public void setEnhancedMacRandomizationEnabled(boolean enabled) {
+        mIsEnhancedMacRandomizationEnabled = enabled;
+    }
+
+    /**
      * Sets the metered override setting for this Passpoint configuration.
      *
      * @param meteredOverride One of the values in {@link MeteredOverride}
@@ -531,6 +605,67 @@
     }
 
     /**
+     * When MAC randomization is enabled, this indicates whether enhanced MAC randomization or
+     * persistent MAC randomization will be used for connections to this Passpoint network.
+     * If true, the MAC address used for connections will periodically change. Otherwise, the same
+     * locally generated MAC will be used for all connections to this passpoint configuration.
+     *
+     * @return true for enhanced MAC randomization enabled. False for disabled.
+     * @hide
+     */
+    public boolean isEnhancedMacRandomizationEnabled() {
+        return mIsEnhancedMacRandomizationEnabled;
+    }
+
+    /**
+     * Set whether the network is oem paid or not.
+     * @hide
+     */
+    public void setOemPaid(boolean isOemPaid) {
+        mIsOemPaid = isOemPaid;
+    }
+
+    /**
+     * Get whether the network is oem paid or not.
+     * @hide
+     */
+    public boolean isOemPaid() {
+        return mIsOemPaid;
+    }
+
+    /**
+     * Set whether the network is oem private or not.
+     * @hide
+     */
+    public void setOemPrivate(boolean isOemPrivate) {
+        mIsOemPrivate = isOemPrivate;
+    }
+
+    /**
+     * Get whether the network is oem private or not.
+     * @hide
+     */
+    public boolean isOemPrivate() {
+        return mIsOemPrivate;
+    }
+
+    /**
+     * Set whether the network is carrier merged or not.
+     * @hide
+     */
+    public void setCarrierMerged(boolean isCarrierMerged) {
+        mIsCarrierMerged = isCarrierMerged;
+    }
+
+    /**
+     * Get whether the network is carrier merged or not.
+     * @hide
+     */
+    public boolean isCarrierMerged() {
+        return mIsCarrierMerged;
+    }
+
+    /**
      * Constructor for creating PasspointConfiguration with default values.
      */
     public PasspointConfiguration() {}
@@ -572,9 +707,15 @@
         mServiceFriendlyNames = source.mServiceFriendlyNames;
         mAaaServerTrustedNames = source.mAaaServerTrustedNames;
         mCarrierId = source.mCarrierId;
+        mSubscriptionId = source.mSubscriptionId;
         mIsAutojoinEnabled = source.mIsAutojoinEnabled;
         mIsMacRandomizationEnabled = source.mIsMacRandomizationEnabled;
+        mIsEnhancedMacRandomizationEnabled = source.mIsEnhancedMacRandomizationEnabled;
         mMeteredOverride = source.mMeteredOverride;
+        mIsCarrierMerged = source.mIsCarrierMerged;
+        mIsOemPaid = source.mIsOemPaid;
+        mIsOemPrivate = source.mIsOemPrivate;
+        mDecoratedIdentityPrefix = source.mDecoratedIdentityPrefix;
     }
 
     @Override
@@ -606,7 +747,13 @@
         dest.writeInt(mCarrierId);
         dest.writeBoolean(mIsAutojoinEnabled);
         dest.writeBoolean(mIsMacRandomizationEnabled);
+        dest.writeBoolean(mIsEnhancedMacRandomizationEnabled);
         dest.writeInt(mMeteredOverride);
+        dest.writeInt(mSubscriptionId);
+        dest.writeBoolean(mIsCarrierMerged);
+        dest.writeBoolean(mIsOemPaid);
+        dest.writeBoolean(mIsOemPrivate);
+        dest.writeString(mDecoratedIdentityPrefix);
     }
 
     @Override
@@ -637,11 +784,17 @@
                 && mUsageLimitDataLimit == that.mUsageLimitDataLimit
                 && mUsageLimitTimeLimitInMinutes == that.mUsageLimitTimeLimitInMinutes
                 && mCarrierId == that.mCarrierId
+                && mSubscriptionId == that.mSubscriptionId
+                && mIsOemPrivate == that.mIsOemPrivate
+                && mIsOemPaid == that.mIsOemPaid
+                && mIsCarrierMerged == that.mIsCarrierMerged
                 && mIsAutojoinEnabled == that.mIsAutojoinEnabled
                 && mIsMacRandomizationEnabled == that.mIsMacRandomizationEnabled
+                && mIsEnhancedMacRandomizationEnabled == that.mIsEnhancedMacRandomizationEnabled
                 && mMeteredOverride == that.mMeteredOverride
                 && (mServiceFriendlyNames == null ? that.mServiceFriendlyNames == null
-                : mServiceFriendlyNames.equals(that.mServiceFriendlyNames));
+                : mServiceFriendlyNames.equals(that.mServiceFriendlyNames))
+                && Objects.equals(mDecoratedIdentityPrefix, that.mDecoratedIdentityPrefix);
     }
 
     @Override
@@ -651,7 +804,8 @@
                 mSubscriptionExpirationTimeMillis, mUsageLimitUsageTimePeriodInMinutes,
                 mUsageLimitStartTimeInMillis, mUsageLimitDataLimit, mUsageLimitTimeLimitInMinutes,
                 mServiceFriendlyNames, mCarrierId, mIsAutojoinEnabled, mIsMacRandomizationEnabled,
-                mMeteredOverride);
+                mIsEnhancedMacRandomizationEnabled, mMeteredOverride, mSubscriptionId,
+                mIsCarrierMerged, mIsOemPaid, mIsOemPrivate, mDecoratedIdentityPrefix);
     }
 
     @Override
@@ -705,9 +859,15 @@
             builder.append("ServiceFriendlyNames: ").append(mServiceFriendlyNames);
         }
         builder.append("CarrierId:" + mCarrierId);
+        builder.append("SubscriptionId:" + mSubscriptionId);
         builder.append("IsAutojoinEnabled:" + mIsAutojoinEnabled);
         builder.append("mIsMacRandomizationEnabled:" + mIsMacRandomizationEnabled);
+        builder.append("mIsEnhancedMacRandomizationEnabled:" + mIsEnhancedMacRandomizationEnabled);
         builder.append("mMeteredOverride:" + mMeteredOverride);
+        builder.append("mIsCarrierMerged:" + mIsCarrierMerged);
+        builder.append("mIsOemPaid:" + mIsOemPaid);
+        builder.append("mIsOemPrivate:" + mIsOemPrivate);
+        builder.append("mDecoratedUsernamePrefix:" + mDecoratedIdentityPrefix);
         return builder.toString();
     }
 
@@ -815,7 +975,14 @@
                 config.mCarrierId = in.readInt();
                 config.mIsAutojoinEnabled = in.readBoolean();
                 config.mIsMacRandomizationEnabled = in.readBoolean();
+                config.mIsEnhancedMacRandomizationEnabled = in.readBoolean();
                 config.mMeteredOverride = in.readInt();
+                config.mSubscriptionId = in.readInt();
+                config.mIsCarrierMerged = in.readBoolean();
+                config.mIsOemPaid = in.readBoolean();
+                config.mIsOemPrivate = in.readBoolean();
+                config.mDecoratedIdentityPrefix = in.readString();
+
                 return config;
             }
 
@@ -918,4 +1085,41 @@
                 mCredential.getUniqueId()));
         return sb.toString();
     }
+
+    /**
+     * Set a prefix for a decorated identity as per RFC 7542.
+     * This prefix must contain a list of realms (could be a list of 1) delimited by a '!'
+     * character. e.g. homerealm.example.org! or proxyrealm.example.net!homerealm.example.org!
+     * A prefix of "homerealm.example.org!" will generate a decorated identity that
+     * looks like: homerealm.example.org!user@otherrealm.example.net
+     * Calling with a null parameter will clear the decorated prefix.
+     * Note: Caller must verify that the device supports this feature by calling
+     * {@link WifiManager#isDecoratedIdentitySupported()}
+     *
+     * @param decoratedIdentityPrefix The prefix to add to the outer/anonymous identity
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    public void setDecoratedIdentityPrefix(@Nullable String decoratedIdentityPrefix) {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        if (!TextUtils.isEmpty(decoratedIdentityPrefix) && !decoratedIdentityPrefix.endsWith("!")) {
+            throw new IllegalArgumentException(
+                    "Decorated identity prefix must be delimited by '!'");
+        }
+        mDecoratedIdentityPrefix = decoratedIdentityPrefix;
+    }
+
+    /**
+     * Get the decorated identity prefix.
+     *
+     * @return The decorated identity prefix
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    public @Nullable String getDecoratedIdentityPrefix() {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        return mDecoratedIdentityPrefix;
+    }
 }
diff --git a/framework/java/android/net/wifi/hotspot2/ProvisioningCallback.java b/framework/java/android/net/wifi/hotspot2/ProvisioningCallback.java
index 1d499b6..5bd855b 100644
--- a/framework/java/android/net/wifi/hotspot2/ProvisioningCallback.java
+++ b/framework/java/android/net/wifi/hotspot2/ProvisioningCallback.java
@@ -16,10 +16,14 @@
 
 package android.net.wifi.hotspot2;
 
+import android.annotation.IntDef;
 import android.annotation.SystemApi;
 import android.net.wifi.WifiManager;
 import android.os.Handler;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Base class for provisioning callbacks. Should be extended by applications and set when calling
  * {@link WifiManager#startSubscriptionProvisioning(OsuProvider, ProvisioningCallback, Handler)}.
@@ -30,6 +34,37 @@
 public abstract class ProvisioningCallback {
 
     /**
+     * OSU Failure error codes
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "OSU_FAILURE_" }, value = {
+            OSU_FAILURE_AP_CONNECTION,
+            OSU_FAILURE_SERVER_URL_INVALID,
+            OSU_FAILURE_SERVER_CONNECTION,
+            OSU_FAILURE_SERVER_VALIDATION,
+            OSU_FAILURE_SERVICE_PROVIDER_VERIFICATION,
+            OSU_FAILURE_PROVISIONING_ABORTED,
+            OSU_FAILURE_PROVISIONING_NOT_AVAILABLE,
+            OSU_FAILURE_INVALID_URL_FORMAT_FOR_OSU,
+            OSU_FAILURE_UNEXPECTED_COMMAND_TYPE,
+            OSU_FAILURE_UNEXPECTED_SOAP_MESSAGE_TYPE,
+            OSU_FAILURE_SOAP_MESSAGE_EXCHANGE,
+            OSU_FAILURE_START_REDIRECT_LISTENER,
+            OSU_FAILURE_TIMED_OUT_REDIRECT_LISTENER,
+            OSU_FAILURE_NO_OSU_ACTIVITY_FOUND,
+            OSU_FAILURE_UNEXPECTED_SOAP_MESSAGE_STATUS,
+            OSU_FAILURE_NO_PPS_MO,
+            OSU_FAILURE_NO_AAA_SERVER_TRUST_ROOT_NODE,
+            OSU_FAILURE_NO_REMEDIATION_SERVER_TRUST_ROOT_NODE,
+            OSU_FAILURE_NO_POLICY_SERVER_TRUST_ROOT_NODE,
+            OSU_FAILURE_RETRIEVE_TRUST_ROOT_CERTIFICATES,
+            OSU_FAILURE_NO_AAA_TRUST_ROOT_CERTIFICATE,
+            OSU_FAILURE_ADD_PASSPOINT_CONFIGURATION,
+            OSU_FAILURE_OSU_PROVIDER_NOT_FOUND})
+    public @interface OsuFailure {}
+
+    /**
      * The reason code for Provisioning Failure due to connection failure to OSU AP.
      */
     public static final int OSU_FAILURE_AP_CONNECTION = 1;
@@ -158,6 +193,25 @@
     public static final int OSU_FAILURE_OSU_PROVIDER_NOT_FOUND = 23;
 
     /**
+     * OSU Status error codes
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "OSU_FAILURE_" }, value = {
+            OSU_STATUS_AP_CONNECTING,
+            OSU_STATUS_AP_CONNECTED,
+            OSU_STATUS_SERVER_CONNECTING,
+            OSU_STATUS_SERVER_VALIDATED,
+            OSU_STATUS_SERVER_CONNECTED,
+            OSU_STATUS_INIT_SOAP_EXCHANGE,
+            OSU_STATUS_WAITING_FOR_REDIRECT_RESPONSE,
+            OSU_STATUS_REDIRECT_RESPONSE_RECEIVED,
+            OSU_STATUS_SECOND_SOAP_EXCHANGE,
+            OSU_STATUS_THIRD_SOAP_EXCHANGE,
+            OSU_STATUS_RETRIEVING_TRUST_ROOT_CERTS})
+    public @interface OsuStatus {}
+
+    /**
      * The status code for provisioning flow to indicate connecting to OSU AP
      */
     public static final int OSU_STATUS_AP_CONNECTING = 1;
@@ -218,18 +272,17 @@
      *
      * @param status indicates error condition
      */
-    public abstract void onProvisioningFailure(int status);
+    public abstract void onProvisioningFailure(@OsuFailure int status);
 
     /**
      * Provisioning status when OSU is in progress
      *
      * @param status indicates status of OSU flow
      */
-    public abstract void onProvisioningStatus(int status);
+    public abstract void onProvisioningStatus(@OsuStatus int status);
 
     /**
      * Provisioning complete when provisioning/remediation flow completes
      */
     public abstract void onProvisioningComplete();
 }
-
diff --git a/framework/java/android/net/wifi/hotspot2/omadm/PpsMoParser.java b/framework/java/android/net/wifi/hotspot2/omadm/PpsMoParser.java
index ae60ed4..7ad74d0 100644
--- a/framework/java/android/net/wifi/hotspot2/omadm/PpsMoParser.java
+++ b/framework/java/android/net/wifi/hotspot2/omadm/PpsMoParser.java
@@ -25,6 +25,8 @@
 import android.util.Log;
 import android.util.Pair;
 
+import com.android.modules.utils.build.SdkLevel;
+
 import org.xml.sax.SAXException;
 
 import java.io.IOException;
@@ -187,6 +189,10 @@
      */
     private static final String NODE_AAA_SERVER_TRUSTED_NAMES = "AAAServerTrustedNames";
 
+    private static final String NODE_VENDOR_WBA = "WBA";
+    private static final String NODE_EXTENSION_NAI = "NAI";
+    private static final String NODE_DECORATED_IDENTITY_PREFIX = "DecoratedPrefix";
+
     /**
      * Fields under HomeSP subtree.
      */
@@ -385,9 +391,11 @@
         try {
             root = xmlParser.parse(xmlString);
         } catch(IOException | SAXException e) {
+            Log.e(TAG, "Failed to parse XML input");
             return null;
         }
         if (root == null) {
+            Log.e(TAG, "Root is not available");
             return null;
         }
 
@@ -1648,6 +1656,57 @@
     }
 
     /**
+     * Parse configurations under PerProviderSubscription/Extension/Android/NAI
+     * subtree.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Extension/Android/NAI subtree
+     * @throws ParsingException
+     */
+    private static void parseVendorWbaExtensionNai(PPSNode node,
+            PasspointConfiguration config) throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for NAI instance");
+        }
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_DECORATED_IDENTITY_PREFIX:
+                    if (SdkLevel.isAtLeastS()) {
+                        config.setDecoratedIdentityPrefix(parseDecoratedIdentityPrefix(child));
+                    }
+                    break;
+                default:
+                    Log.w(TAG, "Unknown node under NAI instance: " + child.getName());
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Extension/Android/NAI/DecoratedPrefix
+     * leaf. This leaf node must contain a list of realms (could be a list of 1) delimited by a '!'
+     * character. e.g. homerealm.example.org! or proxyrealm.example.net!homerealm.example.org!
+     * as per RFC 7542.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Extension/Android/NAI/DecoratedPrefix leaf
+     * @return Decorated identity prefix
+     * @throws ParsingException
+     */
+    private static String parseDecoratedIdentityPrefix(PPSNode node) throws ParsingException {
+        if (!node.isLeaf()) {
+            throw new ParsingException("Leaf node expected for " + NODE_DECORATED_IDENTITY_PREFIX);
+        }
+        String decoratedIdentityPrefix = getPpsNodeValue(node);
+
+        if (TextUtils.isEmpty(decoratedIdentityPrefix) || !decoratedIdentityPrefix.endsWith("!")) {
+            throw new ParsingException("Invalid value for node " + NODE_DECORATED_IDENTITY_PREFIX);
+        }
+
+        return decoratedIdentityPrefix;
+    }
+
+    /**
      * Parse configurations under PerProviderSubscription/Extension/Android subtree.
      *
      * @param node PPSNode representing the root of PerProviderSubscription/Extension
@@ -1673,6 +1732,31 @@
     }
 
     /**
+     * Parse configurations under PerProviderSubscription/Extension/WBA subtree.
+     *
+     * @param node PPSNode representing the root of PerProviderSubscription/Extension
+     *             subtree
+     * @param config Instance of {@link PasspointConfiguration}
+     * @throws ParsingException
+     */
+    private static void parseVendorWbaExtension(PPSNode node, PasspointConfiguration config)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for WBA Extension");
+        }
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_EXTENSION_NAI:
+                    parseVendorWbaExtensionNai(child, config);
+                    break;
+                default:
+                    // Don't raise an exception for unknown nodes
+                    Log.w(TAG, "Unknown node under WBA Extension: " + child.getName());
+            }
+        }
+    }
+
+    /**
      * Parse configurations under PerProviderSubscription/Extension subtree.
      *
      * @param node PPSNode representing the root of PerProviderSubscription/Extension
@@ -1690,6 +1774,9 @@
                 case NODE_VENDOR_ANDROID:
                     parseVendorAndroidExtension(child, config);
                     break;
+                case NODE_VENDOR_WBA:
+                    parseVendorWbaExtension(child, config);
+                    break;
                 default:
                     // Unknown nodes under Extension won't raise exception.
                     // This allows adding new nodes in the future and
diff --git a/framework/java/android/net/wifi/hotspot2/pps/HomeSp.java b/framework/java/android/net/wifi/hotspot2/pps/HomeSp.java
index 8f34579..64aad61 100644
--- a/framework/java/android/net/wifi/hotspot2/pps/HomeSp.java
+++ b/framework/java/android/net/wifi/hotspot2/pps/HomeSp.java
@@ -16,6 +16,8 @@
 
 package android.net.wifi.hotspot2.pps;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -23,6 +25,7 @@
 
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
@@ -139,16 +142,26 @@
      * (MO) tree for more detail.
      */
     private long[] mMatchAllOis = null;
+
     /**
-     * @hide
+     * Set a list of HomeOIs such that all OIs in the list must match an OI in the Roaming
+     * Consortium advertised by a hotspot operator. The list set by this API will have precedence
+     * over {@link #setMatchAnyOis(long[])}, meaning the list set in {@link #setMatchAnyOis(long[])}
+     * will only be used for matching if the list set by this API is null or empty.
+     *
+     * @param matchAllOis An array of longs containing the HomeOIs
      */
-    public void setMatchAllOis(long[] matchAllOis) {
+    public void setMatchAllOis(@Nullable long[] matchAllOis) {
         mMatchAllOis = matchAllOis;
     }
+
     /**
-     * @hide
+     * Get the list of HomeOIs such that all OIs in the list must match an OI in the Roaming
+     * Consortium advertised by a hotspot operator.
+     *
+     * @return An array of longs containing the HomeOIs
      */
-    public long[] getMatchAllOis() {
+    public @Nullable long[] getMatchAllOis() {
         return mMatchAllOis;
     }
 
@@ -159,23 +172,34 @@
      * of that Hotspot provider (e.g. successful authentication with such Hotspot
      * is possible).
      *
-     * {@link #mMatchAllOIs} will have precedence over this one, meaning this list will
-     * only be used for matching if {@link #mMatchAllOIs} is null or empty.
+     * The list set by {@link #setMatchAllOis(long[])} will have precedence over this one, meaning
+     * this list will only be used for matching if the list set by {@link #setMatchAllOis(long[])}
+     * is null or empty.
      *
      * Refer to HomeSP/HomeOIList subtree in PerProviderSubscription (PPS) Management Object
      * (MO) tree for more detail.
      */
     private long[] mMatchAnyOis = null;
+
     /**
-     * @hide
+     * Set a list of HomeOIs such that any OI in the list matches an OI in the Roaming Consortium
+     * advertised by a hotspot operator. The list set by {@link #setMatchAllOis(long[])}
+     * will have precedence over this API, meaning this list will only be used for matching if the
+     * list set by {@link #setMatchAllOis(long[])} is null or empty.
+     *
+     * @param matchAnyOis An array of longs containing the HomeOIs
      */
-    public void setMatchAnyOis(long[] matchAnyOis) {
+    public void setMatchAnyOis(@Nullable long[] matchAnyOis) {
         mMatchAnyOis = matchAnyOis;
     }
+
     /**
-     * @hide
+     * Get a list of HomeOIs such that any OI in the list matches an OI in the Roaming Consortium
+     * advertised by a hotspot operator.
+     *
+     * @return An array of longs containing the HomeOIs
      */
-    public long[] getMatchAnyOis() {
+    public @Nullable long[] getMatchAnyOis() {
         return mMatchAnyOis;
     }
 
@@ -186,20 +210,58 @@
      * operator merges between the providers.
      */
     private String[] mOtherHomePartners = null;
+
     /**
+     * Set the list of FQDN (Fully Qualified Domain Name) of other Home partner providers.
+     *
+     * @param otherHomePartners Array of Strings containing the FQDNs of other Home partner
+     *                         providers
      * @hide
      */
-    public void setOtherHomePartners(String[] otherHomePartners) {
+    public void setOtherHomePartners(@Nullable String[] otherHomePartners) {
         mOtherHomePartners = otherHomePartners;
     }
+
     /**
+     * Set the list of FQDN (Fully Qualified Domain Name) of other Home partner providers.
+     *
+     * @param otherHomePartners Collection of Strings containing the FQDNs of other Home partner
+     *                         providers
+     */
+    public void setOtherHomePartnersList(@NonNull Collection<String> otherHomePartners) {
+        if (otherHomePartners == null) {
+            return;
+        }
+        mOtherHomePartners = otherHomePartners.toArray(new String[otherHomePartners.size()]);
+    }
+
+    /**
+     * Get the list of FQDN (Fully Qualified Domain Name) of other Home partner providers set in
+     * the profile.
+     *
+     * @return Array of Strings containing the FQDNs of other Home partner providers set in the
+     * profile
      * @hide
      */
-    public String[] getOtherHomePartners() {
+    public @Nullable String[] getOtherHomePartners() {
         return mOtherHomePartners;
     }
 
     /**
+     * Get the list of FQDN (Fully Qualified Domain Name) of other Home partner providers set in
+     * the profile.
+     *
+     * @return Collection of Strings containing the FQDNs of other Home partner providers set in the
+     * profile
+     */
+    public @NonNull Collection<String> getOtherHomePartnersList() {
+        if (mOtherHomePartners == null) {
+            return Collections.emptyList();
+        }
+        return Arrays.asList(mOtherHomePartners);
+    }
+
+    /**
      * List of Organization Identifiers (OIs) identifying a roaming consortium of
      * which this provider is a member.
      */
diff --git a/framework/java/android/net/wifi/p2p/IWifiP2pManager.aidl b/framework/java/android/net/wifi/p2p/IWifiP2pManager.aidl
index bfdd45d..fd89d3b 100644
--- a/framework/java/android/net/wifi/p2p/IWifiP2pManager.aidl
+++ b/framework/java/android/net/wifi/p2p/IWifiP2pManager.aidl
@@ -25,7 +25,7 @@
  */
 interface IWifiP2pManager
 {
-    Messenger getMessenger(in IBinder binder);
+    Messenger getMessenger(in IBinder binder, in String packageName);
     Messenger getP2pStateMachineMessenger();
     oneway void close(in IBinder binder);
     void setMiracastMode(int mode);
diff --git a/framework/java/android/net/wifi/p2p/WifiP2pDevice.java b/framework/java/android/net/wifi/p2p/WifiP2pDevice.java
index 567637a..ee5b7ff 100644
--- a/framework/java/android/net/wifi/p2p/WifiP2pDevice.java
+++ b/framework/java/android/net/wifi/p2p/WifiP2pDevice.java
@@ -24,6 +24,8 @@
 import android.os.Parcelable;
 import android.util.Log;
 
+import com.android.modules.utils.build.SdkLevel;
+
 import java.util.Objects;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -136,15 +138,16 @@
      *  dev_capab=0x21 group_capab=0x9
      */
     private static final Pattern detailedDevicePattern = Pattern.compile(
-        "((?:[0-9a-f]{2}:){5}[0-9a-f]{2}) " +
-        "(\\d+ )?" +
-        "p2p_dev_addr=((?:[0-9a-f]{2}:){5}[0-9a-f]{2}) " +
-        "pri_dev_type=(\\d+-[0-9a-fA-F]+-\\d+) " +
-        "name='(.*)' " +
-        "config_methods=(0x[0-9a-fA-F]+) " +
-        "dev_capab=(0x[0-9a-fA-F]+) " +
-        "group_capab=(0x[0-9a-fA-F]+)" +
-        "( wfd_dev_info=0x([0-9a-fA-F]{12}))?"
+            "((?:[0-9a-f]{2}:){5}[0-9a-f]{2}) "
+            + "(\\d+ )?"
+            + "p2p_dev_addr=((?:[0-9a-f]{2}:){5}[0-9a-f]{2}) "
+            + "pri_dev_type=(\\d+-[0-9a-fA-F]+-\\d+) "
+            + "name='(.*)' "
+            + "config_methods=(0x[0-9a-fA-F]+) "
+            + "dev_capab=(0x[0-9a-fA-F]+) "
+            + "group_capab=(0x[0-9a-fA-F]+)"
+            + "( wfd_dev_info=0x([0-9a-fA-F]{12}))?"
+            + "( wfd_r2_dev_info=0x([0-9a-fA-F]{4}))?"
     );
 
     /** 2 token device address pattern
@@ -228,9 +231,15 @@
                 groupCapability = parseHex(match.group(8));
                 if (match.group(9) != null) {
                     String str = match.group(10);
+                    if (null == str) break;
                     wfdInfo = new WifiP2pWfdInfo(parseHex(str.substring(0,4)),
                             parseHex(str.substring(4,8)),
                             parseHex(str.substring(8,12)));
+                    if (match.group(11) != null && SdkLevel.isAtLeastS()) {
+                        String r2str = match.group(12);
+                        if (null == r2str) break;
+                        wfdInfo.setR2DeviceType(parseHex(r2str.substring(0, 4)));
+                    }
                 }
                 break;
         }
diff --git a/framework/java/android/net/wifi/p2p/WifiP2pManager.java b/framework/java/android/net/wifi/p2p/WifiP2pManager.java
index 5a27087..cca280d 100644
--- a/framework/java/android/net/wifi/p2p/WifiP2pManager.java
+++ b/framework/java/android/net/wifi/p2p/WifiP2pManager.java
@@ -1156,8 +1156,8 @@
      */
     public Channel initialize(Context srcContext, Looper srcLooper, ChannelListener listener) {
         Binder binder = new Binder();
-        Channel channel = initalizeChannel(srcContext, srcLooper, listener, getMessenger(binder),
-                binder);
+        Channel channel = initializeChannel(srcContext, srcLooper, listener,
+                getMessenger(binder, srcContext.getOpPackageName()), binder);
         return channel;
     }
 
@@ -1167,12 +1167,12 @@
      */
     public Channel initializeInternal(Context srcContext, Looper srcLooper,
                                       ChannelListener listener) {
-        return initalizeChannel(srcContext, srcLooper, listener, getP2pStateMachineMessenger(),
+        return initializeChannel(srcContext, srcLooper, listener, getP2pStateMachineMessenger(),
                 null);
     }
 
-    private Channel initalizeChannel(Context srcContext, Looper srcLooper, ChannelListener listener,
-                                     Messenger messenger, Binder binder) {
+    private Channel initializeChannel(Context srcContext, Looper srcLooper,
+            ChannelListener listener, Messenger messenger, Binder binder) {
         if (messenger == null) return null;
 
         Channel c = new Channel(srcContext, srcLooper, listener, binder, this);
@@ -1417,7 +1417,7 @@
      * {@link ActionListener#onSuccess} or {@link ActionListener#onFailure}.
      * @hide
      */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @UnsupportedAppUsage(trackingBug = 185141982)
     public void startWps(Channel c, WpsInfo wps, ActionListener listener) {
         checkChannel(c);
         c.mAsyncChannel.sendMessage(START_WPS, 0, c.putListener(listener), wps);
@@ -1814,6 +1814,14 @@
         }
     }
 
+    private Messenger getMessenger(@NonNull Binder binder, @Nullable String packageName) {
+        try {
+            return mService.getMessenger(binder, packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /**
      * Get a reference to WifiP2pService handler. This is used to establish
      * an AsyncChannel communication with WifiService
@@ -1824,11 +1832,8 @@
      * @hide
      */
     public Messenger getMessenger(Binder binder) {
-        try {
-            return mService.getMessenger(binder);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        // No way to determine package name in this case.
+        return getMessenger(binder, null);
     }
 
     /**
diff --git a/framework/java/android/net/wifi/p2p/WifiP2pWfdInfo.java b/framework/java/android/net/wifi/p2p/WifiP2pWfdInfo.java
index e399b5b..b7411e8 100644
--- a/framework/java/android/net/wifi/p2p/WifiP2pWfdInfo.java
+++ b/framework/java/android/net/wifi/p2p/WifiP2pWfdInfo.java
@@ -21,9 +21,14 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import androidx.annotation.RequiresApi;
+
+import com.android.modules.utils.build.SdkLevel;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Locale;
@@ -32,6 +37,7 @@
  * A class representing Wifi Display information for a device.
  *
  * See Wifi Display technical specification v1.0.0, section 5.1.2.
+ * See Wifi Display technical specification v2.0.0, section 5.1.12 for Wifi Display R2.
  */
 public final class WifiP2pWfdInfo implements Parcelable {
 
@@ -40,6 +46,9 @@
     /** Device information bitmap */
     private int mDeviceInfo;
 
+    /** R2 Device information bitmap */
+    private int mR2DeviceInfo = -1;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = { "DEVICE_TYPE_" }, value = {
@@ -53,28 +62,137 @@
     public static final int DEVICE_TYPE_WFD_SOURCE = 0;
     /** The device is a primary sink. */
     public static final int DEVICE_TYPE_PRIMARY_SINK = 1;
-    /** The device is a secondary sink. */
+    /** The device is a secondary sink. This type is only supported by R1. */
     public static final int DEVICE_TYPE_SECONDARY_SINK = 2;
     /** The device is dual-role capable i.e. either a WFD source or a primary sink. */
     public static final int DEVICE_TYPE_SOURCE_OR_PRIMARY_SINK = 3;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "PREFERRED_CONNECTIVITY_" }, value = {
+            PREFERRED_CONNECTIVITY_P2P,
+            PREFERRED_CONNECTIVITY_TDLS})
+    public @interface PreferredConnectivity {}
+
+    /** Wifi Display (WFD) preferred connectivity is Wifi Direct (P2P). */
+    public static final int PREFERRED_CONNECTIVITY_P2P = 0;
+    /** Wifi Display (WFD) preferred connectivity is TDLS. */
+    public static final int PREFERRED_CONNECTIVITY_TDLS = 1;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = {"DEVICE_INFO_"}, value = {
+            DEVICE_INFO_DEVICE_TYPE_MASK,
+            DEVICE_INFO_COUPLED_SINK_SUPPORT_AT_SOURCE,
+            DEVICE_INFO_COUPLED_SINK_SUPPORT_AT_SINK,
+            DEVICE_INFO_SESSION_AVAILABLE_MASK,
+            DEVICE_INFO_WFD_SERVICE_DISCOVERY_SUPPORT,
+            DEVICE_INFO_PREFERRED_CONNECTIVITY_MASK,
+            DEVICE_INFO_CONTENT_PROTECTION_SUPPORT,
+            DEVICE_INFO_TIME_SYNCHRONIZATION_SUPPORT,
+            DEVICE_INFO_AUDIO_UNSUPPORTED_AT_PRIMARY_SINK,
+            DEVICE_INFO_AUDIO_ONLY_SUPPORT_AT_SOURCE,
+            DEVICE_INFO_TDLS_PERSISTENT_GROUP,
+            DEVICE_INFO_TDLS_PERSISTENT_GROUP_REINVOKE})
+    public @interface DeviceInfoMask {}
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = {"DEVICE_INFO_"}, value = {DEVICE_INFO_DEVICE_TYPE_MASK})
+    public @interface R2DeviceInfoMask {}
+
     /**
-     * {@link #mDeviceInfo} & {@link #DEVICE_TYPE} is one of {@link #DEVICE_TYPE_WFD_SOURCE},
-     * {@link #DEVICE_TYPE_PRIMARY_SINK}, {@link #DEVICE_TYPE_SECONDARY_SINK} or
-     * {@link #DEVICE_TYPE_SOURCE_OR_PRIMARY_SINK}.
+     * {@link #getDeviceInfo()} & {@link #DEVICE_INFO_DEVICE_TYPE_MASK} is one of
+     * {@link #DEVICE_TYPE_WFD_SOURCE}, {@link #DEVICE_TYPE_PRIMARY_SINK},
+     * {@link #DEVICE_TYPE_SECONDARY_SINK} or {@link #DEVICE_TYPE_SOURCE_OR_PRIMARY_SINK}.
+     *
+     * The bit definition is listed in 5.1.2 WFD Device Information Subelement and
+     * 5.1.12 WFD R2 Device Information Subelement in Wifi Display Technical Specification.
+     */
+    public static final int DEVICE_INFO_DEVICE_TYPE_MASK = 1 << 1 | 1 << 0;
+    /**
+     * Bit field for {@link #getDeviceInfo()}, indicates that coupled sink is supported at source.
      *
      * The bit definition is listed in 5.1.2 WFD Device Information Subelement in
-     * Wi-Fi Display Technical Specification.
+     * Wifi Display Technical Specification.
      */
-    private static final int DEVICE_TYPE                            = 1 << 1 | 1 << 0;
-    private static final int COUPLED_SINK_SUPPORT_AT_SOURCE         = 1 << 2;
-    private static final int COUPLED_SINK_SUPPORT_AT_SINK           = 1 << 3;
-    private static final int SESSION_AVAILABLE_BIT1                 = 1 << 4;
-    private static final int SESSION_AVAILABLE_BIT2                 = 1 << 5;
-    private static final int SESSION_AVAILABLE                      =
+    public static final int DEVICE_INFO_COUPLED_SINK_SUPPORT_AT_SOURCE = 1 << 2;
+    /**
+     * Bit field for {@link #getDeviceInfo()}, indicates that coupled sink is supporeted at sink.
+     *
+     * The bit definition is listed in 5.1.2 WFD Device Information Subelement in
+     * Wifi Display Technical Specification.
+     */
+    public static final int DEVICE_INFO_COUPLED_SINK_SUPPORT_AT_SINK = 1 << 3;
+    private static final int SESSION_AVAILABLE_BIT1 = 1 << 4;
+    private static final int SESSION_AVAILABLE_BIT2 = 1 << 5;
+    /**
+     * Bit field for {@link #getDeviceInfo()}, indicates that Wifi Display session is available.
+     *
+     * The bit definition is listed in 5.1.2 WFD Device Information Subelement in
+     * Wifi Display Technical Specification.
+     */
+    public static final int DEVICE_INFO_SESSION_AVAILABLE_MASK =
             SESSION_AVAILABLE_BIT2 | SESSION_AVAILABLE_BIT1;
-    /* The support of Content Protection using the HDCP system 2.0/2.1. */
-    private static final int CONTENT_PROTECTION_SUPPORT             = 1 << 8;
+    /**
+     * Bit field for {@link #getDeviceInfo()}, indicates that Wifi Display discovery is supported.
+     *
+     * The bit definition is listed in 5.1.2 WFD Device Information Subelement in
+     * Wifi Display Technical Specification.
+     */
+    public static final int DEVICE_INFO_WFD_SERVICE_DISCOVERY_SUPPORT = 1 << 6;
+    /**
+     * Bit field for {@link #getDeviceInfo()}, indicate the preferred connectifity for Wifi Display.
+     *
+     * The bit definition is listed in 5.1.2 WFD Device Information Subelement in
+     * Wifi Display Technical Specification.
+     * The value is one of {@link #PREFERRED_CONNECTIVITY_P2P} or
+     * {@link #PREFERRED_CONNECTIVITY_TDLS}.
+     */
+    public static final int DEVICE_INFO_PREFERRED_CONNECTIVITY_MASK = 1 << 7;
+    /**
+     * Bit field for {@link #getDeviceInfo()}, indicate the support of Content Protection
+     * using the HDCP system 2.0/2.1.
+     *
+     * The bit definition is listed in 5.1.2 WFD Device Information Subelement in
+     * Wifi Display Technical Specification.
+     */
+    public static final int DEVICE_INFO_CONTENT_PROTECTION_SUPPORT = 1 << 8;
+    /**
+     * Bit field for {@link #getDeviceInfo()}, indicate time synchronization
+     * using 802.1AS is supported.
+     *
+     * The bit definition is listed in 5.1.2 WFD Device Information Subelement in
+     * Wifi Display Technical Specification.
+     */
+    public static final int DEVICE_INFO_TIME_SYNCHRONIZATION_SUPPORT = 1 << 9;
+    /**
+     * Bit field for {@link #getDeviceInfo()}, indicate audio is not supported at primary sink.
+     *
+     * The bit definition is listed in 5.1.2 WFD Device Information Subelement in
+     * Wifi Display Technical Specification.
+     */
+    public static final int DEVICE_INFO_AUDIO_UNSUPPORTED_AT_PRIMARY_SINK = 1 << 10;
+    /**
+     * Bit field for {@link #getDeviceInfo()}, indicate audo is only supported at source.
+     *
+     * The bit definition is listed in 5.1.2 WFD Device Information Subelement in
+     * Wifi Display Technical Specification.
+     */
+    public static final int DEVICE_INFO_AUDIO_ONLY_SUPPORT_AT_SOURCE = 1 << 11;
+    /** Bit field for {@link #getDeviceInfo()}, indicate that TDLS persistent group is intended.
+     *
+     * The bit definition is listed in 5.1.2 WFD Device Information Subelement in
+     * Wifi Display Technical Specification.
+     */
+    public static final int DEVICE_INFO_TDLS_PERSISTENT_GROUP = 1 << 12;
+    /** Bit field for {@link #getDeviceInfo()}, indicate that the request is for
+     * re-invocation of TDLS persistent group.
+     *
+     * The bit definition is listed in 5.1.2 WFD Device Information Subelement in
+     * Wifi Display Technical Specification.
+     */
+    public static final int DEVICE_INFO_TDLS_PERSISTENT_GROUP_REINVOKE = 1 << 13;
 
     private int mCtrlPort;
 
@@ -90,6 +208,39 @@
         mDeviceInfo = devInfo;
         mCtrlPort = ctrlPort;
         mMaxThroughput = maxTput;
+        mR2DeviceInfo = -1;
+    }
+
+    /**
+     * Return R1 raw device info, See
+     * Wifi Display technical specification v1.0.0, section 5.1.2.
+     * Access bit fields by DEVICE_INFO_* constants.
+     */
+    @DeviceInfoMask
+    public int getDeviceInfo() {
+        return mDeviceInfo;
+    }
+
+    /**
+     * Set Wifi Display R2 raw device info, see
+     * Wifi Display technical specification v2.0.0, section 5.1.12.
+     * Access bit fields by {@link #DEVICE_INFO_DEVICE_TYPE_MASK}.
+     *
+     * @param r2DeviceInfo the raw data of R2 device info.
+     * @hide
+     */
+    public void setR2DeviceInfo(int r2DeviceInfo) {
+        mR2DeviceInfo = r2DeviceInfo;
+    }
+
+    /**
+     * Return R2 raw device info, See
+     * Wifi Display technical specification v2.0.0, section 5.1.12.
+     * Access bit fields by {@link #DEVICE_INFO_DEVICE_TYPE_MASK}.
+     */
+    @R2DeviceInfoMask
+    public int getR2DeviceInfo() {
+        return mR2DeviceInfo;
     }
 
     /** Returns true is Wifi Display is enabled, false otherwise. */
@@ -97,6 +248,11 @@
         return mEnabled;
     }
 
+    /** Returns true is Wifi Display R2 is enabled, false otherwise. */
+    public boolean isR2Supported() {
+        return mR2DeviceInfo >= 0;
+    }
+
     /**
      * Sets whether Wifi Display should be enabled.
      *
@@ -107,13 +263,52 @@
     }
 
     /**
+     * Sets the type of the Wifi Display R2 device.
+     * See Wifi Display technical specification v2.0.0, section 5.1.12 for Wifi Display R2.
+     * Before calling this API, call {@link WifiManager#isWifiDisplayR2Supported()
+     * to know whether Wifi Display R2 is supported or not.
+     * If R2 info was filled without Wifi Display R2 support,
+     * {@link WifiP2pManager#setWfdInfo(Channel, WifiP2pWfdInfo, ActionListener)
+     * would fail.
+     *
+     * @param deviceType One of {@link #DEVICE_TYPE_WFD_SOURCE}, {@link #DEVICE_TYPE_PRIMARY_SINK},
+     * {@link #DEVICE_TYPE_SOURCE_OR_PRIMARY_SINK}
+     * @return true if the device type was successfully set, false otherwise
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    public boolean setR2DeviceType(@DeviceType int deviceType) {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        if (DEVICE_TYPE_WFD_SOURCE != deviceType
+                && DEVICE_TYPE_PRIMARY_SINK != deviceType
+                && DEVICE_TYPE_SOURCE_OR_PRIMARY_SINK != deviceType) {
+            return false;
+        }
+        if (!isR2Supported()) mR2DeviceInfo = 0;
+        mR2DeviceInfo &= ~DEVICE_INFO_DEVICE_TYPE_MASK;
+        mR2DeviceInfo |= deviceType;
+        return true;
+    }
+
+    /**
      * Get the type of the device.
      * One of {@link #DEVICE_TYPE_WFD_SOURCE}, {@link #DEVICE_TYPE_PRIMARY_SINK},
      * {@link #DEVICE_TYPE_SECONDARY_SINK}, {@link #DEVICE_TYPE_SOURCE_OR_PRIMARY_SINK}
      */
     @DeviceType
     public int getDeviceType() {
-        return mDeviceInfo & DEVICE_TYPE;
+        return mDeviceInfo & DEVICE_INFO_DEVICE_TYPE_MASK;
+    }
+
+    /**
+     * Get the type of the R2 device.
+     * One of {@link #DEVICE_TYPE_WFD_SOURCE}, {@link #DEVICE_TYPE_PRIMARY_SINK},
+     * or {@link #DEVICE_TYPE_SOURCE_OR_PRIMARY_SINK}
+     */
+    @DeviceType
+    public int getR2DeviceType() {
+        return mR2DeviceInfo & DEVICE_INFO_DEVICE_TYPE_MASK;
     }
 
     /**
@@ -126,7 +321,7 @@
     public boolean setDeviceType(@DeviceType int deviceType) {
         if (DEVICE_TYPE_WFD_SOURCE <= deviceType
                 && deviceType <= DEVICE_TYPE_SOURCE_OR_PRIMARY_SINK) {
-            mDeviceInfo &= ~DEVICE_TYPE;
+            mDeviceInfo &= ~DEVICE_INFO_DEVICE_TYPE_MASK;
             mDeviceInfo |= deviceType;
             return true;
         }
@@ -135,7 +330,7 @@
 
     /** Returns true if a session is available, false otherwise. */
     public boolean isSessionAvailable() {
-        return (mDeviceInfo & SESSION_AVAILABLE) != 0;
+        return (mDeviceInfo & DEVICE_INFO_SESSION_AVAILABLE_MASK) != 0;
     }
 
     /**
@@ -148,7 +343,7 @@
             mDeviceInfo |= SESSION_AVAILABLE_BIT1;
             mDeviceInfo &= ~SESSION_AVAILABLE_BIT2;
         } else {
-            mDeviceInfo &= ~SESSION_AVAILABLE;
+            mDeviceInfo &= ~DEVICE_INFO_SESSION_AVAILABLE_MASK;
         }
     }
 
@@ -156,7 +351,7 @@
      * @return true if Content Protection using the HDCP system 2.0/2.1 is supported.
      */
     public boolean isContentProtectionSupported() {
-        return (mDeviceInfo & CONTENT_PROTECTION_SUPPORT) != 0;
+        return (mDeviceInfo & DEVICE_INFO_CONTENT_PROTECTION_SUPPORT) != 0;
     }
 
     /**
@@ -166,9 +361,53 @@
      */
     public void setContentProtectionSupported(boolean enabled) {
         if (enabled) {
-            mDeviceInfo |= CONTENT_PROTECTION_SUPPORT;
+            mDeviceInfo |= DEVICE_INFO_CONTENT_PROTECTION_SUPPORT;
         } else {
-            mDeviceInfo &= ~CONTENT_PROTECTION_SUPPORT;
+            mDeviceInfo &= ~DEVICE_INFO_CONTENT_PROTECTION_SUPPORT;
+        }
+    }
+
+    /**
+     * Returns true if Coupled Sink is supported by WFD Source.
+     * See Wifi Display technical specification v1.0.0, section 4.9.
+     */
+    public boolean isCoupledSinkSupportedAtSource() {
+        return (mDeviceInfo & DEVICE_INFO_COUPLED_SINK_SUPPORT_AT_SOURCE) != 0;
+    }
+
+    /**
+     * Sets whether Coupled Sink feature is supported by WFD Source.
+     * See Wifi Display technical specification v1.0.0, section 4.9.
+     *
+     * @param enabled true to indicate support for coupled sink, false otherwise.
+     */
+    public void setCoupledSinkSupportAtSource(boolean enabled) {
+        if (enabled) {
+            mDeviceInfo |= DEVICE_INFO_COUPLED_SINK_SUPPORT_AT_SOURCE;
+        } else {
+            mDeviceInfo &= ~DEVICE_INFO_COUPLED_SINK_SUPPORT_AT_SOURCE;
+        }
+    }
+
+    /**
+     * Returns true if Coupled Sink is supported by WFD Sink.
+     * See Wifi Display technical specification v1.0.0, section 4.9.
+     */
+    public boolean isCoupledSinkSupportedAtSink() {
+        return (mDeviceInfo & DEVICE_INFO_COUPLED_SINK_SUPPORT_AT_SINK) != 0;
+    }
+
+    /**
+     * Sets whether Coupled Sink feature is supported by WFD Sink.
+     * See Wifi Display technical specification v1.0.0, section 4.9.
+     *
+     * @param enabled true to indicate support for coupled sink, false otherwise.
+     */
+    public void setCoupledSinkSupportAtSink(boolean enabled) {
+        if (enabled) {
+            mDeviceInfo |= DEVICE_INFO_COUPLED_SINK_SUPPORT_AT_SINK;
+        } else {
+            mDeviceInfo &= ~DEVICE_INFO_COUPLED_SINK_SUPPORT_AT_SINK;
         }
     }
 
@@ -198,6 +437,11 @@
                 Locale.US, "%04x%04x%04x", mDeviceInfo, mCtrlPort, mMaxThroughput);
     }
 
+    /** @hide */
+    public String getR2DeviceInfoHex() {
+        return String.format(Locale.US, "%04x%04x", 2, mR2DeviceInfo);
+    }
+
     @Override
     public String toString() {
         StringBuffer sbuf = new StringBuffer();
@@ -205,6 +449,7 @@
         sbuf.append("WFD DeviceInfo: ").append(mDeviceInfo);
         sbuf.append("\n WFD CtrlPort: ").append(mCtrlPort);
         sbuf.append("\n WFD MaxThroughput: ").append(mMaxThroughput);
+        sbuf.append("\n WFD R2 DeviceInfo: ").append(mR2DeviceInfo);
         return sbuf.toString();
     }
 
@@ -220,6 +465,7 @@
             mDeviceInfo = source.mDeviceInfo;
             mCtrlPort = source.mCtrlPort;
             mMaxThroughput = source.mMaxThroughput;
+            mR2DeviceInfo = source.mR2DeviceInfo;
         }
     }
 
@@ -230,6 +476,7 @@
         dest.writeInt(mDeviceInfo);
         dest.writeInt(mCtrlPort);
         dest.writeInt(mMaxThroughput);
+        dest.writeInt(mR2DeviceInfo);
     }
 
     private void readFromParcel(Parcel in) {
@@ -237,6 +484,7 @@
         mDeviceInfo = in.readInt();
         mCtrlPort = in.readInt();
         mMaxThroughput = in.readInt();
+        mR2DeviceInfo = in.readInt();
     }
 
     /** Implement the Parcelable interface */
diff --git a/framework/java/android/net/wifi/rtt/RangingRequest.java b/framework/java/android/net/wifi/rtt/RangingRequest.java
index 318efa6..8d45dca 100644
--- a/framework/java/android/net/wifi/rtt/RangingRequest.java
+++ b/framework/java/android/net/wifi/rtt/RangingRequest.java
@@ -32,6 +32,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.StringJoiner;
 
 /**
@@ -46,6 +47,9 @@
  */
 public final class RangingRequest implements Parcelable {
     private static final int MAX_PEERS = 10;
+    private static final int DEFAULT_RTT_BURST_SIZE = 8;
+    private static final int MIN_RTT_BURST_SIZE = 2;
+    private static final int MAX_RTT_BURST_SIZE = 31;
 
     /**
      * Returns the maximum number of peers to range which can be specified in a single {@code
@@ -59,12 +63,65 @@
         return MAX_PEERS;
     }
 
+    /**
+     * Returns the default RTT burst size used to determine the average range.
+     *
+     * @return the RTT burst size used by default
+     */
+    public static int getDefaultRttBurstSize() {
+        return DEFAULT_RTT_BURST_SIZE;
+    }
+
+    /**
+     * Returns the minimum RTT burst size that can be used to determine a average range.
+     *
+     * @return the minimum RTT burst size that can be used
+     */
+    public static int getMinRttBurstSize() {
+        return MIN_RTT_BURST_SIZE;
+    }
+
+    /**
+     * Returns the minimum RTT burst size that can be used to determine a average range.
+     *
+     * @return the maximum RTT burst size that can be used
+     */
+    public static int getMaxRttBurstSize() {
+        return MAX_RTT_BURST_SIZE;
+    }
+
     /** @hide */
     public final List<ResponderConfig> mRttPeers;
 
     /** @hide */
-    private RangingRequest(List<ResponderConfig> rttPeers) {
+    public final int mRttBurstSize;
+
+    /** @hide */
+    private RangingRequest(List<ResponderConfig> rttPeers, int rttBurstSize) {
         mRttPeers = rttPeers;
+        mRttBurstSize = rttBurstSize;
+    }
+
+    /**
+     * Returns the list of RTT capable responding peers.
+     *
+     * @return the list of RTT capable responding peers in a common system representation
+     *
+     * @hide
+     */
+    @SystemApi
+    @NonNull
+    public List<ResponderConfig> getRttResponders() {
+        return mRttPeers;
+    }
+
+    /**
+     * Returns the RTT burst size used to determine the average range.
+     *
+     * @return the RTT burst size used
+     */
+    public int getRttBurstSize() {
+        return mRttBurstSize;
     }
 
     @Override
@@ -75,6 +132,7 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeList(mRttPeers);
+        dest.writeInt(mRttBurstSize);
     }
 
     public static final @android.annotation.NonNull Creator<RangingRequest> CREATOR = new Creator<RangingRequest>() {
@@ -85,7 +143,7 @@
 
         @Override
         public RangingRequest createFromParcel(Parcel in) {
-            return new RangingRequest(in.readArrayList(null));
+            return new RangingRequest(in.readArrayList(null), in.readInt());
         }
     };
 
@@ -105,12 +163,14 @@
             throw new IllegalArgumentException(
                     "Ranging to too many peers requested. Use getMaxPeers() API to get limit.");
         }
-
         for (ResponderConfig peer: mRttPeers) {
             if (!peer.isValid(awareSupported)) {
                 throw new IllegalArgumentException("Invalid Responder specification");
             }
         }
+        if (mRttBurstSize < getMinRttBurstSize() || mRttBurstSize > getMaxRttBurstSize()) {
+            throw new IllegalArgumentException("RTT burst size is out of range");
+        }
     }
 
     /**
@@ -118,15 +178,38 @@
      */
     public static final class Builder {
         private List<ResponderConfig> mRttPeers = new ArrayList<>();
+        private int mRttBurstSize = DEFAULT_RTT_BURST_SIZE;
+
+        /**
+         * Set the RTT Burst size for the ranging request.
+         * <p>
+         * If not set, the default RTT burst size given by
+         * {@link #getDefaultRttBurstSize()} is used to determine the default value.
+         * If set, the value must be in the range {@link #getMinRttBurstSize()} and
+         * {@link #getMaxRttBurstSize()} inclusively, or a
+         * {@link java.lang.IllegalArgumentException} will be thrown.
+         *
+         * @param rttBurstSize The number of FTM packets used to estimate a range.
+         * @return The builder to facilitate chaining
+         * {@code builder.setXXX(..).setXXX(..)}.
+         */
+        @NonNull
+        public Builder setRttBurstSize(int rttBurstSize) {
+            if (rttBurstSize < MIN_RTT_BURST_SIZE || rttBurstSize > MAX_RTT_BURST_SIZE) {
+                throw new IllegalArgumentException("RTT burst size out of range.");
+            }
+            mRttBurstSize = rttBurstSize;
+            return this;
+        }
 
         /**
          * Add the device specified by the {@link ScanResult} to the list of devices with
          * which to measure range. The total number of peers added to a request cannot exceed the
          * limit specified by {@link #getMaxPeers()}.
          * <p>
-         * Ranging may not be supported if the Access Point does not support IEEE 802.11mc. Use
-         * {@link ScanResult#is80211mcResponder()} to verify the Access Point's capabilities. If
-         * not supported the result status will be
+         * Ranging will only be supported if the Access Point supports IEEE 802.11mc, also known as
+         * two-sided RTT. Use {@link ScanResult#is80211mcResponder()} to verify the Access Point's
+         * capabilities. If not supported the result status will be
          * {@link RangingResult#STATUS_RESPONDER_DOES_NOT_SUPPORT_IEEE80211MC}.
          *
          * @param apInfo Information of an Access Point (AP) obtained in a Scan Result.
@@ -145,9 +228,9 @@
          * which to measure range. The total number of peers added to a request cannot exceed the
          * limit specified by {@link #getMaxPeers()}.
          * <p>
-         * Ranging may not be supported if the Access Point does not support IEEE 802.11mc. Use
-         * {@link ScanResult#is80211mcResponder()} to verify the Access Point's capabilities. If
-         * not supported the result status will be
+         * Ranging will only be supported if the Access Point supports IEEE 802.11mc, also known as
+         * two-sided RTT. Use {@link ScanResult#is80211mcResponder()} to verify the Access Point's
+         * capabilities. If not supported, the result status will be
          * {@link RangingResult#STATUS_RESPONDER_DOES_NOT_SUPPORT_IEEE80211MC}.
          *
          * @param apInfos Information of an Access Points (APs) obtained in a Scan Result.
@@ -165,6 +248,80 @@
         }
 
         /**
+         * Add the non-802.11mc capable device specified by the {@link ScanResult} to the list of
+         * devices with which to measure range. The total number of peers added to a request cannot
+         * exceed the limit specified by {@link #getMaxPeers()}.
+         * <p>
+         * Accurate ranging cannot be supported if the Access Point does not support IEEE 802.11mc,
+         * and instead an alternate protocol called one-sided RTT will be used with lower
+         * accuracy. Use {@link ScanResult#is80211mcResponder()} to verify the Access Point)s) are
+         * not 802.11mc capable.
+         * <p>
+         * One-sided RTT does not subtract the RTT turnaround time at the Access Point, which can
+         * add hundreds of meters to the estimate. With experimentation it is possible to use this
+         * information to make a statistical estimate of the range by taking multiple measurements
+         * to several Access Points and normalizing the result. For some applications this can be
+         * used to improve range estimates based on Receive Signal Strength Indication (RSSI), but
+         * will not be as accurate as IEEE 802.11mc (two-sided RTT).
+         * <p>
+         * Note: one-sided RTT should only be used if you are very familiar with statistical
+         * estimation techniques.
+         *
+         * @param apInfo Information of an Access Point (AP) obtained in a Scan Result.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        @NonNull
+        public Builder addNon80211mcCapableAccessPoint(@NonNull ScanResult apInfo) {
+            if (apInfo == null) {
+                throw new IllegalArgumentException("Null ScanResult!");
+            }
+            if (apInfo.is80211mcResponder()) {
+                throw new IllegalArgumentException("AP supports the 802.11mc protocol.");
+            }
+            return addResponder(ResponderConfig.fromScanResult(apInfo));
+        }
+
+        /**
+         * Add the non-802.11mc capable devices specified by the {@link ScanResult} to the list of
+         * devices with which to measure range. The total number of peers added to a request cannot
+         * exceed the limit specified by {@link #getMaxPeers()}.
+         * <p>
+         * Accurate ranging cannot be supported if the Access Point does not support IEEE 802.11mc,
+         * and instead an alternate protocol called one-sided RTT will be used with lower
+         * accuracy. Use {@link ScanResult#is80211mcResponder()} to verify the Access Point)s) are
+         * not 802.11mc capable.
+         * <p>
+         * One-sided RTT does not subtract the RTT turnaround time at the Access Point, which can
+         * add hundreds of meters to the estimate. With experimentation it is possible to use this
+         * information to make a statistical estimate of the range by taking multiple measurements
+         * to several Access Points and normalizing the result. For some applications this can be
+         * used to improve range estimates based on Receive Signal Strength Indication (RSSI), but
+         * will not be as accurate as IEEE 802.11mc (two-sided RTT).
+         * <p>
+         * Note: one-sided RTT should only be used if you are very familiar with statistical
+         * estimation techniques.
+         *
+         * @param apInfos Information of an Access Points (APs) obtained in a Scan Result.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        @NonNull
+        public Builder addNon80211mcCapableAccessPoints(@NonNull List<ScanResult> apInfos) {
+            if (apInfos == null) {
+                throw new IllegalArgumentException("Null list of ScanResults!");
+            }
+            for (ScanResult scanResult : apInfos) {
+                if (scanResult.is80211mcResponder()) {
+                    throw new IllegalArgumentException(
+                            "At least one AP supports the 802.11mc protocol.");
+                }
+                addAccessPoint(scanResult);
+            }
+            return this;
+        }
+
+        /**
          * Add the device specified by the {@code peerMacAddress} to the list of devices with
          * which to measure range.
          * <p>
@@ -199,11 +356,14 @@
          * using {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], List)}.
          * <p>
          * Note: in order to use this API the device must support Wi-Fi Aware
-         * {@link android.net.wifi.aware}. The peer device which is being ranged to must be
-         * configured to publish a service (with any name) with:
-         * <li>Type {@link android.net.wifi.aware.PublishConfig#PUBLISH_TYPE_UNSOLICITED}.
-         * <li>Ranging enabled
-         * {@link android.net.wifi.aware.PublishConfig.Builder#setRangingEnabled(boolean)}.
+         * {@link android.net.wifi.aware}. The requesting device can be either publisher or
+         * subscriber in a discovery session. For both requesting device and peer device ranging
+         * must be enabled on the discovery session:
+         * <li>{@link android.net.wifi.aware.PublishConfig.Builder#setRangingEnabled(boolean)} for
+         * publisher.</li>
+         * <li>Either {@link android.net.wifi.aware.SubscribeConfig.Builder#setMinDistanceMm(int)}
+         * or {@link android.net.wifi.aware.SubscribeConfig.Builder#setMaxDistanceMm(int)} must be
+         * set to enable ranging on subscriber </li>
          *
          * @param peerHandle The peer handler of the peer Wi-Fi Aware device.
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
@@ -241,7 +401,7 @@
          * builder.
          */
         public RangingRequest build() {
-            return new RangingRequest(mRttPeers);
+            return new RangingRequest(mRttPeers, mRttBurstSize);
         }
     }
 
@@ -257,11 +417,13 @@
 
         RangingRequest lhs = (RangingRequest) o;
 
-        return mRttPeers.size() == lhs.mRttPeers.size() && mRttPeers.containsAll(lhs.mRttPeers);
+        return mRttPeers.size() == lhs.mRttPeers.size()
+                && mRttPeers.containsAll(lhs.mRttPeers)
+                && mRttBurstSize == lhs.mRttBurstSize;
     }
 
     @Override
     public int hashCode() {
-        return mRttPeers.hashCode();
+        return Objects.hash(mRttPeers, mRttBurstSize);
     }
 }
diff --git a/framework/java/android/net/wifi/rtt/RangingResult.java b/framework/java/android/net/wifi/rtt/RangingResult.java
index a065bbc..d9d0717 100644
--- a/framework/java/android/net/wifi/rtt/RangingResult.java
+++ b/framework/java/android/net/wifi/rtt/RangingResult.java
@@ -72,24 +72,50 @@
      */
     public static final int STATUS_RESPONDER_DOES_NOT_SUPPORT_IEEE80211MC = 2;
 
-    private final int mStatus;
-    private final MacAddress mMac;
-    private final PeerHandle mPeerHandle;
-    private final int mDistanceMm;
-    private final int mDistanceStdDevMm;
-    private final int mRssi;
-    private final int mNumAttemptedMeasurements;
-    private final int mNumSuccessfulMeasurements;
-    private final byte[] mLci;
-    private final byte[] mLcr;
-    private final ResponderLocation mResponderLocation;
-    private final long mTimestamp;
+    /** @hide */
+    public final int mStatus;
+
+    /** @hide */
+    public final MacAddress mMac;
+
+    /** @hide */
+    public final PeerHandle mPeerHandle;
+
+    /** @hide */
+    public final int mDistanceMm;
+
+    /** @hide */
+    public final int mDistanceStdDevMm;
+
+    /** @hide */
+    public final int mRssi;
+
+    /** @hide */
+    public final int mNumAttemptedMeasurements;
+
+    /** @hide */
+    public final int mNumSuccessfulMeasurements;
+
+    /** @hide */
+    public final byte[] mLci;
+
+    /** @hide */
+    public final byte[] mLcr;
+
+    /** @hide */
+    public final ResponderLocation mResponderLocation;
+
+    /** @hide */
+    public final long mTimestamp;
+
+    /** @hide */
+    public final boolean mIs80211mcMeasurement;
 
     /** @hide */
     public RangingResult(@RangeResultStatus int status, @NonNull MacAddress mac, int distanceMm,
             int distanceStdDevMm, int rssi, int numAttemptedMeasurements,
             int numSuccessfulMeasurements, byte[] lci, byte[] lcr,
-            ResponderLocation responderLocation, long timestamp) {
+            ResponderLocation responderLocation, long timestamp, boolean is80211McMeasurement) {
         mStatus = status;
         mMac = mac;
         mPeerHandle = null;
@@ -102,6 +128,7 @@
         mLcr = lcr == null ? EMPTY_BYTE_ARRAY : lcr;
         mResponderLocation = responderLocation;
         mTimestamp = timestamp;
+        mIs80211mcMeasurement = is80211McMeasurement;
     }
 
     /** @hide */
@@ -121,6 +148,7 @@
         mLcr = lcr == null ? EMPTY_BYTE_ARRAY : lcr;
         mResponderLocation = responderLocation;
         mTimestamp = timestamp;
+        mIs80211mcMeasurement = true;
     }
 
     /**
@@ -207,11 +235,13 @@
     /**
      * @return The number of attempted measurements used in the RTT exchange resulting in this set
      * of results. The number of successful measurements is returned by
-     * {@link #getNumSuccessfulMeasurements()} which at most, if there are no errors, will be 1 less
-     * that the number of attempted measurements.
+     * {@link #getNumSuccessfulMeasurements()} which at most, if there are no errors, will be 1
+     * less than the number of attempted measurements.
      * <p>
      * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
-     * exception.
+     * exception. If the value is 0, it should be interpreted as no information available, which may
+     * occur for one-sided RTT measurements. Instead {@link RangingRequest#getRttBurstSize()}
+     * should be used instead.
      */
     public int getNumAttemptedMeasurements() {
         if (mStatus != STATUS_SUCCESS) {
@@ -321,6 +351,23 @@
         return mTimestamp;
     }
 
+    /**
+     * @return The result is true if the IEEE 802.11mc protocol was used (also known as
+     * two-sided RTT). If the result is false, a one-side RTT result is provided which does not
+     * subtract the turnaround time at the responder.
+     * <p>
+     * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
+     * exception.
+     */
+    public boolean is80211mcMeasurement() {
+        if (mStatus != STATUS_SUCCESS) {
+            throw new IllegalStateException(
+                    "is80211mcMeasurementResult(): invoked on an invalid result: getStatus()="
+                            + mStatus);
+        }
+        return mIs80211mcMeasurement;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -350,6 +397,7 @@
         dest.writeByteArray(mLcr);
         dest.writeParcelable(mResponderLocation, flags);
         dest.writeLong(mTimestamp);
+        dest.writeBoolean(mIs80211mcMeasurement);
     }
 
     public static final @android.annotation.NonNull Creator<RangingResult> CREATOR = new Creator<RangingResult>() {
@@ -381,6 +429,7 @@
             ResponderLocation responderLocation =
                     in.readParcelable(this.getClass().getClassLoader());
             long timestamp = in.readLong();
+            boolean isllmcMeasurement = in.readBoolean();
             if (peerHandlePresent) {
                 return new RangingResult(status, peerHandle, distanceMm, distanceStdDevMm, rssi,
                         numAttemptedMeasurements, numSuccessfulMeasurements, lci, lcr,
@@ -388,7 +437,7 @@
             } else {
                 return new RangingResult(status, mac, distanceMm, distanceStdDevMm, rssi,
                         numAttemptedMeasurements, numSuccessfulMeasurements, lci, lcr,
-                        responderLocation, timestamp);
+                        responderLocation, timestamp, isllmcMeasurement);
             }
         }
     };
@@ -404,7 +453,8 @@
                 mNumAttemptedMeasurements).append(", numSuccessfulMeasurements=").append(
                 mNumSuccessfulMeasurements).append(", lci=").append(mLci).append(", lcr=").append(
                 mLcr).append(", responderLocation=").append(mResponderLocation)
-                .append(", timestamp=").append(mTimestamp).append("]").toString();
+                .append(", timestamp=").append(mTimestamp).append(", is80211mcMeasurement=")
+                .append(mIs80211mcMeasurement).append("]").toString();
     }
 
     @Override
@@ -426,6 +476,7 @@
                 && mNumSuccessfulMeasurements == lhs.mNumSuccessfulMeasurements
                 && Arrays.equals(mLci, lhs.mLci) && Arrays.equals(mLcr, lhs.mLcr)
                 && mTimestamp == lhs.mTimestamp
+                && mIs80211mcMeasurement == lhs.mIs80211mcMeasurement
                 && Objects.equals(mResponderLocation, lhs.mResponderLocation);
     }
 
@@ -433,6 +484,6 @@
     public int hashCode() {
         return Objects.hash(mStatus, mMac, mPeerHandle, mDistanceMm, mDistanceStdDevMm, mRssi,
                 mNumAttemptedMeasurements, mNumSuccessfulMeasurements, mLci, mLcr,
-                mResponderLocation, mTimestamp);
+                mResponderLocation, mTimestamp, mIs80211mcMeasurement);
     }
 }
diff --git a/framework/java/android/net/wifi/rtt/ResponderConfig.java b/framework/java/android/net/wifi/rtt/ResponderConfig.java
index be4eecc..58dcd65 100644
--- a/framework/java/android/net/wifi/rtt/ResponderConfig.java
+++ b/framework/java/android/net/wifi/rtt/ResponderConfig.java
@@ -325,7 +325,7 @@
                 }
             }
 
-            if (heCapabilitiesPresent) {
+            if (heCapabilitiesPresent && ScanResult.is6GHz(frequency)) {
                 preamble = PREAMBLE_HE;
             } else if (vhtCapabilitiesPresent) {
                 preamble = PREAMBLE_VHT;
diff --git a/framework/tests/Android.bp b/framework/tests/Android.bp
index 289564c..8d37006 100644
--- a/framework/tests/Android.bp
+++ b/framework/tests/Android.bp
@@ -24,6 +24,9 @@
 
     defaults: ["framework-wifi-test-defaults"],
 
+    min_sdk_version: "30",
+    target_sdk_version: "30",
+
     srcs: ["**/*.java"],
 
     jacoco: {
@@ -50,8 +53,8 @@
     ],
 
     test_suites: [
-        "device-tests",
-        "mts",
+        "general-tests",
+        "mts-wifi",
     ],
 
     // static libs used by both framework-wifi & FrameworksWifiApiTests. Need to rename test usage
diff --git a/framework/tests/AndroidManifest.xml b/framework/tests/AndroidManifest.xml
index b6c38bc..8fcf78f 100644
--- a/framework/tests/AndroidManifest.xml
+++ b/framework/tests/AndroidManifest.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!--
   ~ Copyright (C) 2016 The Android Open Source Project
   ~
@@ -17,22 +16,23 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.net.wifi.test">
+     package="android.net.wifi.test">
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
         <activity android:label="WifiTestDummyLabel"
-                  android:name="WifiTestDummyName">
+             android:name="WifiTestDummyName"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.net.wifi.test"
-        android:label="Frameworks Wifi API Tests">
+         android:targetPackage="android.net.wifi.test"
+         android:label="Frameworks Wifi API Tests">
     </instrumentation>
 
 </manifest>
diff --git a/framework/tests/AndroidTest.xml b/framework/tests/AndroidTest.xml
index 34e2e3a..2cdaddb 100644
--- a/framework/tests/AndroidTest.xml
+++ b/framework/tests/AndroidTest.xml
@@ -20,6 +20,8 @@
 
     <option name="test-suite-tag" value="apct" />
     <option name="test-tag" value="FrameworksWifiApiTests" />
+    <option name="config-descriptor:metadata" key="mainline-param"
+            value="com.google.android.wifi.apex" />
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.net.wifi.test" />
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
diff --git a/framework/tests/assets/pps/PerProviderSubscription.xml b/framework/tests/assets/pps/PerProviderSubscription.xml
index e9afb35..a4c78ac 100644
--- a/framework/tests/assets/pps/PerProviderSubscription.xml
+++ b/framework/tests/assets/pps/PerProviderSubscription.xml
@@ -43,6 +43,16 @@
             </Node>
           </Node>
         </Node>
+        <Node>
+          <NodeName>WBA</NodeName>
+          <Node>
+            <NodeName>NAI</NodeName>
+            <Node>
+              <NodeName>DecoratedPrefix</NodeName>
+              <Value>androidwifi.dev!</Value>
+            </Node>
+          </Node>
+        </Node>
       </Node>
       <Node>
         <NodeName>HomeSP</NodeName>
diff --git a/framework/tests/src/android/net/wifi/CoexUnsafeChannelTest.java b/framework/tests/src/android/net/wifi/CoexUnsafeChannelTest.java
new file mode 100644
index 0000000..d983c1b
--- /dev/null
+++ b/framework/tests/src/android/net/wifi/CoexUnsafeChannelTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import static android.net.wifi.CoexUnsafeChannel.POWER_CAP_NONE;
+import static android.net.wifi.WifiScanner.WIFI_BAND_24_GHZ;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.modules.utils.build.SdkLevel;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link android.net.wifi.CoexUnsafeChannel}.
+ */
+@SmallTest
+public class CoexUnsafeChannelTest {
+    @Before
+    public void setUp() {
+        assumeTrue(SdkLevel.isAtLeastS());
+    }
+
+    /**
+     * Verifies {@link CoexUnsafeChannel#getPowerCapDbm()} returns POWER_CAP_NONE if no cap is set.
+     */
+    @Test
+    public void testGetPowerCapDbm_noPowerCap_returnsPowerCapNone() {
+        CoexUnsafeChannel unsafeChannel = new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6);
+
+        assertThat(unsafeChannel.getPowerCapDbm()).isEqualTo(POWER_CAP_NONE);
+    }
+
+    /**
+     * Verifies {@link CoexUnsafeChannel#getPowerCapDbm()} returns a power cap if a cap is set.
+     */
+    @Test
+    public void testGetPowerCapDbm_powerCapSet_returnsSetPowerCap() {
+        CoexUnsafeChannel unsafeChannel = new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6, -50);
+
+        assertThat(unsafeChannel.getPowerCapDbm()).isEqualTo(-50);
+    }
+
+    /**
+     * Verify parcel read/write for CoexUnsafeChannel with or without power cap.
+     */
+    @Test
+    public void testParcelReadWrite_withOrWithoutCap_readEqualsWritten() throws Exception {
+        CoexUnsafeChannel writeUnsafeChannelNoCap =
+                new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6);
+        CoexUnsafeChannel writeUnsafeChannelCapped =
+                new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6, -50);
+
+        CoexUnsafeChannel readUnsafeChannelNoCap = parcelReadWrite(writeUnsafeChannelNoCap);
+        CoexUnsafeChannel readUnsafeChannelCapped = parcelReadWrite(writeUnsafeChannelCapped);
+
+        assertThat(writeUnsafeChannelNoCap).isEqualTo(readUnsafeChannelNoCap);
+        assertThat(writeUnsafeChannelCapped).isEqualTo(readUnsafeChannelCapped);
+    }
+
+    /**
+     * Write the provided {@link CoexUnsafeChannel} to a parcel and deserialize it.
+     */
+    private static CoexUnsafeChannel parcelReadWrite(CoexUnsafeChannel writeResult)
+            throws Exception {
+        Parcel parcel = Parcel.obtain();
+        writeResult.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);    // Rewind data position back to the beginning for read.
+        return CoexUnsafeChannel.CREATOR.createFromParcel(parcel);
+    }
+}
diff --git a/framework/tests/src/android/net/wifi/EasyConnectStatusCallbackTest.java b/framework/tests/src/android/net/wifi/EasyConnectStatusCallbackTest.java
index b101414..3180aa8 100644
--- a/framework/tests/src/android/net/wifi/EasyConnectStatusCallbackTest.java
+++ b/framework/tests/src/android/net/wifi/EasyConnectStatusCallbackTest.java
@@ -19,6 +19,8 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import android.annotation.NonNull;
+import android.net.Uri;
 import android.util.SparseArray;
 
 import androidx.test.filters.SmallTest;
@@ -52,6 +54,11 @@
             mOnFailureR1EventReceived = true;
             mLastCode = code;
         }
+
+        @Override
+        public void onBootstrapUriGenerated(@NonNull Uri dppUri) {
+
+        }
     };
     private boolean mOnFailureR1EventReceived;
     private int mLastCode;
diff --git a/framework/tests/src/android/net/wifi/ScanResultTest.java b/framework/tests/src/android/net/wifi/ScanResultTest.java
index f1ec5e8..c26e535 100644
--- a/framework/tests/src/android/net/wifi/ScanResultTest.java
+++ b/framework/tests/src/android/net/wifi/ScanResultTest.java
@@ -18,7 +18,9 @@
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.validateMockitoUsage;
 
 import android.net.wifi.ScanResult.InformationElement;
@@ -44,6 +46,7 @@
     public static final long TEST_TSF = 04660l;
     public static final @WifiAnnotations.WifiStandard int TEST_WIFI_STANDARD =
             ScanResult.WIFI_STANDARD_11AC;
+    public static final String TEST_IFACE_NAME = "test_ifname";
 
     /**
      * Frequency to channel map. This include some frequencies used outside the US.
@@ -125,6 +128,21 @@
     }
 
     /**
+     * Verify the logic that determines whether a frequency is PSC.
+     */
+    @Test
+    public void testIs6GHzPsc() {
+        int test2G = 2412;
+        int test6GNonPsc = ScanResult.BAND_6_GHZ_PSC_START_MHZ
+                + ScanResult.BAND_6_GHZ_PSC_STEP_SIZE_MHZ - 20;
+        int test6GPsc = ScanResult.BAND_6_GHZ_PSC_START_MHZ
+                + ScanResult.BAND_6_GHZ_PSC_STEP_SIZE_MHZ;
+        assertFalse(ScanResult.is6GHzPsc(test2G));
+        assertFalse(ScanResult.is6GHzPsc(test6GNonPsc));
+        assertTrue(ScanResult.is6GHzPsc(test6GPsc));
+    }
+
+    /**
      * Verify parcel read/write for ScanResult.
      */
     @Test
@@ -220,7 +238,7 @@
                 + "passpoint: no, ChannelBandwidth: 0, centerFreq0: 0, centerFreq1: 0, "
                 + "standard: 11ac, "
                 + "80211mcResponder: is not supported, "
-                + "Radio Chain Infos: null", scanResult.toString());
+                + "Radio Chain Infos: null, interface name: test_ifname", scanResult.toString());
     }
 
     /**
@@ -243,7 +261,8 @@
                 + "standard: 11ac, "
                 + "80211mcResponder: is not supported, "
                 + "Radio Chain Infos: [RadioChainInfo: id=0, level=-45, "
-                + "RadioChainInfo: id=1, level=-54]", scanResult.toString());
+                + "RadioChainInfo: id=1, level=-54], interface name: test_ifname",
+                scanResult.toString());
     }
 
     /**
@@ -253,7 +272,8 @@
     public void convertFrequencyToChannel() throws Exception {
         for (int i = 0; i < FREQUENCY_TO_CHANNEL_MAP.length; i += 3) {
             assertEquals(FREQUENCY_TO_CHANNEL_MAP[i + 2],
-                    ScanResult.convertFrequencyMhzToChannel(FREQUENCY_TO_CHANNEL_MAP[i]));
+                    ScanResult.convertFrequencyMhzToChannelIfSupported(
+                    FREQUENCY_TO_CHANNEL_MAP[i]));
         }
     }
 
@@ -262,7 +282,7 @@
      */
     @Test
     public void convertFrequencyToChannelWithInvalidFreq() throws Exception {
-        assertEquals(-1, ScanResult.convertFrequencyMhzToChannel(8000));
+        assertEquals(-1, ScanResult.convertFrequencyMhzToChannelIfSupported(8000));
     }
 
     /**
@@ -284,6 +304,8 @@
         result.frequency = TEST_FREQUENCY;
         result.timestamp = TEST_TSF;
         result.setWifiStandard(TEST_WIFI_STANDARD);
+        result.ifaceName = TEST_IFACE_NAME;
+
         return result;
     }
 
diff --git a/framework/tests/src/android/net/wifi/SecurityParamsTest.java b/framework/tests/src/android/net/wifi/SecurityParamsTest.java
new file mode 100644
index 0000000..c586ded
--- /dev/null
+++ b/framework/tests/src/android/net/wifi/SecurityParamsTest.java
@@ -0,0 +1,556 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.net.wifi.WifiConfiguration.AuthAlgorithm;
+import android.net.wifi.WifiConfiguration.GroupCipher;
+import android.net.wifi.WifiConfiguration.GroupMgmtCipher;
+import android.net.wifi.WifiConfiguration.KeyMgmt;
+import android.net.wifi.WifiConfiguration.PairwiseCipher;
+import android.net.wifi.WifiConfiguration.Protocol;
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+import java.util.BitSet;
+
+/**
+ * Unit tests for {@link android.net.wifi.WifiInfo}.
+ */
+@SmallTest
+public class SecurityParamsTest {
+
+    private void verifySecurityParams(SecurityParams params,
+            int expectedSecurityType,
+            int[] expectedAllowedKeyManagement,
+            int[] expectedAllowedProtocols,
+            int[] expectedAllowedAuthAlgorithms,
+            int[] expectedAllowedPairwiseCiphers,
+            int[] expectedAllowedGroupCiphers,
+            boolean expectedRequirePmf) {
+        assertTrue(params.isSecurityType(expectedSecurityType));
+        assertEquals(expectedSecurityType, params.getSecurityType());
+        for (int b: expectedAllowedKeyManagement) {
+            assertTrue(params.getAllowedKeyManagement().get(b));
+        }
+        for (int b: expectedAllowedProtocols) {
+            assertTrue(params.getAllowedProtocols().get(b));
+        }
+        for (int b: expectedAllowedAuthAlgorithms) {
+            assertTrue(params.getAllowedAuthAlgorithms().get(b));
+        }
+        for (int b: expectedAllowedPairwiseCiphers) {
+            assertTrue(params.getAllowedPairwiseCiphers().get(b));
+        }
+        for (int b: expectedAllowedGroupCiphers) {
+            assertTrue(params.getAllowedGroupCiphers().get(b));
+        }
+        assertEquals(expectedRequirePmf, params.isRequirePmf());
+    }
+
+    /** Verify the security params created by security type. */
+    @Test
+    public void testSecurityTypeCreator() throws Exception {
+        int[] securityTypes = new int[] {
+                WifiConfiguration.SECURITY_TYPE_WAPI_CERT,
+                WifiConfiguration.SECURITY_TYPE_WAPI_PSK,
+                WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT,
+                WifiConfiguration.SECURITY_TYPE_OWE,
+                WifiConfiguration.SECURITY_TYPE_SAE,
+                WifiConfiguration.SECURITY_TYPE_OSEN,
+                WifiConfiguration.SECURITY_TYPE_EAP,
+                WifiConfiguration.SECURITY_TYPE_PSK,
+                WifiConfiguration.SECURITY_TYPE_OPEN,
+                WifiConfiguration.SECURITY_TYPE_PASSPOINT_R1_R2,
+                WifiConfiguration.SECURITY_TYPE_PASSPOINT_R3,
+        };
+
+        for (int type: securityTypes) {
+            assertEquals(type,
+                    SecurityParams.createSecurityParamsBySecurityType(type).getSecurityType());
+        }
+    }
+
+    /** Verify EAP params creator. */
+    @Test
+    public void testEapCreator() throws Exception {
+        int expectedSecurityType = WifiConfiguration.SECURITY_TYPE_EAP;
+        int[] expectedAllowedKeyManagement = new int[] {KeyMgmt.WPA_EAP, KeyMgmt.IEEE8021X};
+        int[] expectedAllowedProtocols = new int[] {};
+        int[] expectedAllowedAuthAlgorithms = new int[] {};
+        int[] expectedAllowedPairwiseCiphers = new int[] {};
+        int[] expectedAllowedGroupCiphers = new int[] {};
+        boolean expectedRequirePmf = false;
+        SecurityParams p = SecurityParams.createSecurityParamsBySecurityType(
+                expectedSecurityType);
+        verifySecurityParams(p, expectedSecurityType,
+                expectedAllowedKeyManagement, expectedAllowedProtocols,
+                expectedAllowedAuthAlgorithms, expectedAllowedPairwiseCiphers,
+                expectedAllowedGroupCiphers, expectedRequirePmf);
+    }
+
+    /** Verify Passpoint R1/R2 params creator. */
+    @Test
+    public void testEapPasspointR1R2Creator() throws Exception {
+        int expectedSecurityType = WifiConfiguration.SECURITY_TYPE_PASSPOINT_R1_R2;
+        int[] expectedAllowedKeyManagement = new int[] {KeyMgmt.WPA_EAP, KeyMgmt.IEEE8021X};
+        int[] expectedAllowedProtocols = new int[] {};
+        int[] expectedAllowedAuthAlgorithms = new int[] {};
+        int[] expectedAllowedPairwiseCiphers = new int[] {};
+        int[] expectedAllowedGroupCiphers = new int[] {};
+        boolean expectedRequirePmf = false;
+        SecurityParams p = SecurityParams.createSecurityParamsBySecurityType(
+                expectedSecurityType);
+        verifySecurityParams(p, expectedSecurityType,
+                expectedAllowedKeyManagement, expectedAllowedProtocols,
+                expectedAllowedAuthAlgorithms, expectedAllowedPairwiseCiphers,
+                expectedAllowedGroupCiphers, expectedRequirePmf);
+    }
+
+    /** Verify Passpoint R3 params creator. */
+    @Test
+    public void testEapPasspointR3Creator() throws Exception {
+        int expectedSecurityType = WifiConfiguration.SECURITY_TYPE_PASSPOINT_R3;
+        int[] expectedAllowedKeyManagement = new int[] {KeyMgmt.WPA_EAP, KeyMgmt.IEEE8021X};
+        int[] expectedAllowedProtocols = new int[] {};
+        int[] expectedAllowedAuthAlgorithms = new int[] {};
+        int[] expectedAllowedPairwiseCiphers = new int[] {};
+        int[] expectedAllowedGroupCiphers = new int[] {};
+        boolean expectedRequirePmf = true;
+        SecurityParams p = SecurityParams.createSecurityParamsBySecurityType(
+                expectedSecurityType);
+        verifySecurityParams(p, expectedSecurityType,
+                expectedAllowedKeyManagement, expectedAllowedProtocols,
+                expectedAllowedAuthAlgorithms, expectedAllowedPairwiseCiphers,
+                expectedAllowedGroupCiphers, expectedRequirePmf);
+    }
+
+    /** Verify Enhanced Open params creator. */
+    @Test
+    public void testEnhancedOpenCreator() throws Exception {
+        int expectedSecurityType = WifiConfiguration.SECURITY_TYPE_OWE;
+        int[] expectedAllowedKeyManagement = new int[] {KeyMgmt.OWE};
+        int[] expectedAllowedProtocols = new int[] {Protocol.RSN};
+        int[] expectedAllowedAuthAlgorithms = new int[] {};
+        int[] expectedAllowedPairwiseCiphers = new int[] {
+                PairwiseCipher.CCMP, PairwiseCipher.GCMP_128, PairwiseCipher.GCMP_256};
+        int[] expectedAllowedGroupCiphers = new int[] {
+                GroupCipher.CCMP, GroupCipher.GCMP_128, GroupCipher.GCMP_256};
+        boolean expectedRequirePmf = true;
+        SecurityParams p = SecurityParams.createSecurityParamsBySecurityType(
+                expectedSecurityType);
+        verifySecurityParams(p, expectedSecurityType,
+                expectedAllowedKeyManagement, expectedAllowedProtocols,
+                expectedAllowedAuthAlgorithms, expectedAllowedPairwiseCiphers,
+                expectedAllowedGroupCiphers, expectedRequirePmf);
+    }
+
+    /** Verify Open params creator. */
+    @Test
+    public void testOpenCreator() throws Exception {
+        int expectedSecurityType = WifiConfiguration.SECURITY_TYPE_OPEN;
+        int[] expectedAllowedKeyManagement = new int[] {KeyMgmt.NONE};
+        int[] expectedAllowedProtocols = new int[] {};
+        int[] expectedAllowedAuthAlgorithms = new int[] {};
+        int[] expectedAllowedPairwiseCiphers = new int[] {};
+        int[] expectedAllowedGroupCiphers = new int[] {};
+        boolean expectedRequirePmf = false;
+        SecurityParams p = SecurityParams.createSecurityParamsBySecurityType(
+                                expectedSecurityType);
+        verifySecurityParams(p, expectedSecurityType,
+                expectedAllowedKeyManagement, expectedAllowedProtocols,
+                expectedAllowedAuthAlgorithms, expectedAllowedPairwiseCiphers,
+                expectedAllowedGroupCiphers, expectedRequirePmf);
+    }
+
+    /** Verify OSEN params creator. */
+    @Test
+    public void testOsenCreator() throws Exception {
+        int expectedSecurityType = WifiConfiguration.SECURITY_TYPE_OSEN;
+        int[] expectedAllowedKeyManagement = new int[] {KeyMgmt.OSEN};
+        int[] expectedAllowedProtocols = new int[] {Protocol.OSEN};
+        int[] expectedAllowedAuthAlgorithms = new int[] {};
+        int[] expectedAllowedPairwiseCiphers = new int[] {};
+        int[] expectedAllowedGroupCiphers = new int[] {};
+        boolean expectedRequirePmf = false;
+        SecurityParams p = SecurityParams.createSecurityParamsBySecurityType(
+                                expectedSecurityType);
+        verifySecurityParams(p, expectedSecurityType,
+                expectedAllowedKeyManagement, expectedAllowedProtocols,
+                expectedAllowedAuthAlgorithms, expectedAllowedPairwiseCiphers,
+                expectedAllowedGroupCiphers, expectedRequirePmf);
+    }
+
+    /** Verify WAPI CERT params creator. */
+    @Test
+    public void testWapiCertCreator() throws Exception {
+        int expectedSecurityType = WifiConfiguration.SECURITY_TYPE_WAPI_CERT;
+        int[] expectedAllowedKeyManagement = new int[] {KeyMgmt.WAPI_CERT};
+        int[] expectedAllowedProtocols = new int[] {Protocol.WAPI};
+        int[] expectedAllowedAuthAlgorithms = new int[] {};
+        int[] expectedAllowedPairwiseCiphers = new int[] {PairwiseCipher.SMS4};
+        int[] expectedAllowedGroupCiphers = new int[] {GroupCipher.SMS4};
+        boolean expectedRequirePmf = false;
+        SecurityParams p = SecurityParams.createSecurityParamsBySecurityType(
+                                expectedSecurityType);
+        verifySecurityParams(p, expectedSecurityType,
+                expectedAllowedKeyManagement, expectedAllowedProtocols,
+                expectedAllowedAuthAlgorithms, expectedAllowedPairwiseCiphers,
+                expectedAllowedGroupCiphers, expectedRequirePmf);
+    }
+
+    /** Verify WAPI PSK params creator. */
+    @Test
+    public void testWapiPskCreator() throws Exception {
+        int expectedSecurityType = WifiConfiguration.SECURITY_TYPE_WAPI_PSK;
+        int[] expectedAllowedKeyManagement = new int[] {KeyMgmt.WAPI_PSK};
+        int[] expectedAllowedProtocols = new int[] {Protocol.WAPI};
+        int[] expectedAllowedAuthAlgorithms = new int[] {};
+        int[] expectedAllowedPairwiseCiphers = new int[] {PairwiseCipher.SMS4};
+        int[] expectedAllowedGroupCiphers = new int[] {GroupCipher.SMS4};
+        boolean expectedRequirePmf = false;
+        SecurityParams p = SecurityParams.createSecurityParamsBySecurityType(
+                                expectedSecurityType);
+        verifySecurityParams(p, expectedSecurityType,
+                expectedAllowedKeyManagement, expectedAllowedProtocols,
+                expectedAllowedAuthAlgorithms, expectedAllowedPairwiseCiphers,
+                expectedAllowedGroupCiphers, expectedRequirePmf);
+    }
+
+    /** Verify WEP params creator. */
+    @Test
+    public void testWepCreator() throws Exception {
+        int expectedSecurityType = WifiConfiguration.SECURITY_TYPE_WEP;
+        int[] expectedAllowedKeyManagement = new int[] {KeyMgmt.NONE};
+        int[] expectedAllowedProtocols = new int[] {};
+        int[] expectedAllowedAuthAlgorithms = new int[] {AuthAlgorithm.OPEN, AuthAlgorithm.SHARED};
+        int[] expectedAllowedPairwiseCiphers = new int[] {};
+        int[] expectedAllowedGroupCiphers = new int[] {};
+        boolean expectedRequirePmf = false;
+        SecurityParams p = SecurityParams.createSecurityParamsBySecurityType(
+                                expectedSecurityType);
+        verifySecurityParams(p, expectedSecurityType,
+                expectedAllowedKeyManagement, expectedAllowedProtocols,
+                expectedAllowedAuthAlgorithms, expectedAllowedPairwiseCiphers,
+                expectedAllowedGroupCiphers, expectedRequirePmf);
+    }
+
+    /** Verify WPA3 Enterprise 192-bit params creator. */
+    @Test
+    public void testWpa3Enterprise192BitCreator() throws Exception {
+        int expectedSecurityType = WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT;
+        int[] expectedAllowedKeyManagement = new int[] {
+                KeyMgmt.WPA_EAP, KeyMgmt.IEEE8021X, KeyMgmt.SUITE_B_192};
+        int[] expectedAllowedProtocols = new int[] {Protocol.RSN};
+        int[] expectedAllowedAuthAlgorithms = new int[] {};
+        int[] expectedAllowedPairwiseCiphers = new int[] {
+                PairwiseCipher.GCMP_128, PairwiseCipher.GCMP_256};
+        int[] expectedAllowedGroupCiphers = new int[] {GroupCipher.GCMP_128, GroupCipher.GCMP_256};
+        boolean expectedRequirePmf = true;
+        SecurityParams p = SecurityParams.createSecurityParamsBySecurityType(
+                                expectedSecurityType);
+        verifySecurityParams(p, expectedSecurityType,
+                expectedAllowedKeyManagement, expectedAllowedProtocols,
+                expectedAllowedAuthAlgorithms, expectedAllowedPairwiseCiphers,
+                expectedAllowedGroupCiphers, expectedRequirePmf);
+
+        assertTrue(p.getAllowedGroupManagementCiphers().get(GroupMgmtCipher.BIP_GMAC_256));
+    }
+
+    /** Verify WPA3 Enterprise params creator. */
+    @Test
+    public void testWpa3EnterpriseCreator() throws Exception {
+        int expectedSecurityType = WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE;
+        int[] expectedAllowedKeyManagement = new int[] {KeyMgmt.WPA_EAP, KeyMgmt.IEEE8021X};
+        int[] expectedAllowedProtocols = new int[] {Protocol.RSN};
+        int[] expectedAllowedAuthAlgorithms = new int[] {};
+        int[] expectedAllowedPairwiseCiphers = new int[] {
+                PairwiseCipher.CCMP, PairwiseCipher.GCMP_256};
+        int[] expectedAllowedGroupCiphers = new int[] {GroupCipher.CCMP, GroupCipher.GCMP_256};
+        boolean expectedRequirePmf = true;
+        SecurityParams p = SecurityParams.createSecurityParamsBySecurityType(
+                                expectedSecurityType);
+        verifySecurityParams(p, expectedSecurityType,
+                expectedAllowedKeyManagement, expectedAllowedProtocols,
+                expectedAllowedAuthAlgorithms, expectedAllowedPairwiseCiphers,
+                expectedAllowedGroupCiphers, expectedRequirePmf);
+    }
+
+    /** Verify WPA3 Personal params creator. */
+    @Test
+    public void testWpa3PersonalCreator() throws Exception {
+        int expectedSecurityType = WifiConfiguration.SECURITY_TYPE_SAE;
+        int[] expectedAllowedKeyManagement = new int[] {KeyMgmt.SAE};
+        int[] expectedAllowedProtocols = new int[] {Protocol.RSN};
+        int[] expectedAllowedAuthAlgorithms = new int[] {};
+        int[] expectedAllowedPairwiseCiphers = new int[] {
+                PairwiseCipher.CCMP, PairwiseCipher.GCMP_128, PairwiseCipher.GCMP_256};
+        int[] expectedAllowedGroupCiphers = new int[] {
+                GroupCipher.CCMP, GroupCipher.GCMP_128, GroupCipher.GCMP_256};
+        boolean expectedRequirePmf = true;
+        SecurityParams p = SecurityParams.createSecurityParamsBySecurityType(
+                                expectedSecurityType);
+        verifySecurityParams(p, expectedSecurityType,
+                expectedAllowedKeyManagement, expectedAllowedProtocols,
+                expectedAllowedAuthAlgorithms, expectedAllowedPairwiseCiphers,
+                expectedAllowedGroupCiphers, expectedRequirePmf);
+    }
+
+    /** Verify WPA2 Personal EAP params creator. */
+    @Test
+    public void testWpaWpa2PersonalCreator() throws Exception {
+        int expectedSecurityType = WifiConfiguration.SECURITY_TYPE_PSK;
+        int[] expectedAllowedKeyManagement = new int[] {KeyMgmt.WPA_PSK};
+        int[] expectedAllowedProtocols = new int[] {};
+        int[] expectedAllowedAuthAlgorithms = new int[] {};
+        int[] expectedAllowedPairwiseCiphers = new int[] {};
+        int[] expectedAllowedGroupCiphers = new int[] {};
+        boolean expectedRequirePmf = false;
+        SecurityParams p = SecurityParams.createSecurityParamsBySecurityType(
+                                expectedSecurityType);
+        verifySecurityParams(p, expectedSecurityType,
+                expectedAllowedKeyManagement, expectedAllowedProtocols,
+                expectedAllowedAuthAlgorithms, expectedAllowedPairwiseCiphers,
+                expectedAllowedGroupCiphers, expectedRequirePmf);
+    }
+
+    /** Verify setter/getter methods */
+    @Test
+    public void testCommonSetterGetter() throws Exception {
+        SecurityParams params = SecurityParams.createSecurityParamsBySecurityType(
+                WifiConfiguration.SECURITY_TYPE_PSK);
+
+        // PSK setting
+        BitSet allowedKeyManagement = new BitSet();
+        allowedKeyManagement.set(KeyMgmt.WPA_PSK);
+
+        BitSet allowedProtocols = new BitSet();
+        allowedProtocols.set(Protocol.RSN);
+        allowedProtocols.set(Protocol.WPA);
+
+        BitSet allowedPairwiseCiphers = new BitSet();
+        allowedPairwiseCiphers.set(PairwiseCipher.CCMP);
+        allowedPairwiseCiphers.set(PairwiseCipher.TKIP);
+
+        BitSet allowedGroupCiphers = new BitSet();
+        allowedGroupCiphers.set(GroupCipher.CCMP);
+        allowedGroupCiphers.set(GroupCipher.TKIP);
+        allowedGroupCiphers.set(GroupCipher.WEP40);
+        allowedGroupCiphers.set(GroupCipher.WEP104);
+
+        assertEquals(allowedKeyManagement, params.getAllowedKeyManagement());
+        assertTrue(params.getAllowedKeyManagement().get(KeyMgmt.WPA_PSK));
+
+        assertEquals(allowedProtocols, params.getAllowedProtocols());
+        assertTrue(params.getAllowedProtocols().get(Protocol.RSN));
+        assertTrue(params.getAllowedProtocols().get(Protocol.WPA));
+
+        assertEquals(allowedPairwiseCiphers, params.getAllowedPairwiseCiphers());
+        assertTrue(params.getAllowedPairwiseCiphers().get(PairwiseCipher.CCMP));
+        assertTrue(params.getAllowedPairwiseCiphers().get(PairwiseCipher.TKIP));
+
+        assertEquals(allowedGroupCiphers, params.getAllowedGroupCiphers());
+        assertTrue(params.getAllowedGroupCiphers().get(GroupCipher.CCMP));
+        assertTrue(params.getAllowedGroupCiphers().get(GroupCipher.TKIP));
+        assertTrue(params.getAllowedGroupCiphers().get(GroupCipher.WEP40));
+        assertTrue(params.getAllowedGroupCiphers().get(GroupCipher.WEP104));
+
+        params.setEnabled(false);
+        assertFalse(params.isEnabled());
+    }
+
+    /** Verify SAE-specific methods */
+    @Test
+    public void testSaeMethods() throws Exception {
+        SecurityParams p = SecurityParams.createSecurityParamsBySecurityType(
+                WifiConfiguration.SECURITY_TYPE_SAE);
+
+        assertFalse(p.isAddedByAutoUpgrade());
+        p.setIsAddedByAutoUpgrade(true);
+        assertTrue(p.isAddedByAutoUpgrade());
+
+        assertFalse(p.isSaeH2eOnlyMode());
+        p.enableSaeH2eOnlyMode(true);
+        assertTrue(p.isSaeH2eOnlyMode());
+
+        assertFalse(p.isSaePkOnlyMode());
+        p.enableSaePkOnlyMode(true);
+        assertTrue(p.isSaePkOnlyMode());
+    }
+
+    /** Verify copy constructor. */
+    @Test
+    public void testCopyConstructor() throws Exception {
+        SecurityParams params = SecurityParams.createSecurityParamsBySecurityType(
+                WifiConfiguration.SECURITY_TYPE_PSK);
+        params.setEnabled(false);
+        params.setIsAddedByAutoUpgrade(true);
+
+        SecurityParams copiedParams = new SecurityParams(params);
+
+        assertTrue(params.isSameSecurityType(copiedParams));
+        assertEquals(params.getAllowedKeyManagement(), copiedParams.getAllowedKeyManagement());
+        assertEquals(params.getAllowedProtocols(), copiedParams.getAllowedProtocols());
+        assertEquals(params.getAllowedAuthAlgorithms(), copiedParams.getAllowedAuthAlgorithms());
+        assertEquals(params.getAllowedPairwiseCiphers(), copiedParams.getAllowedPairwiseCiphers());
+        assertEquals(params.getAllowedGroupCiphers(), copiedParams.getAllowedGroupCiphers());
+        assertEquals(params.getAllowedGroupManagementCiphers(),
+                copiedParams.getAllowedGroupManagementCiphers());
+        assertEquals(params.getAllowedSuiteBCiphers(), copiedParams.getAllowedSuiteBCiphers());
+        assertEquals(params.isRequirePmf(), copiedParams.isRequirePmf());
+        assertEquals(params.isEnabled(), copiedParams.isEnabled());
+        assertEquals(params.isSaeH2eOnlyMode(), copiedParams.isSaeH2eOnlyMode());
+        assertEquals(params.isSaePkOnlyMode(), copiedParams.isSaePkOnlyMode());
+        assertEquals(params.isAddedByAutoUpgrade(), copiedParams.isAddedByAutoUpgrade());
+    }
+
+    /** Check that two params are equal if and only if their types are the same. */
+    @Test
+    public void testEquals() {
+        SecurityParams saeParams1 = SecurityParams.createSecurityParamsBySecurityType(
+                WifiConfiguration.SECURITY_TYPE_SAE);
+        SecurityParams saeParams2 = SecurityParams.createSecurityParamsBySecurityType(
+                WifiConfiguration.SECURITY_TYPE_SAE);
+        SecurityParams pskParams = SecurityParams.createSecurityParamsBySecurityType(
+                WifiConfiguration.SECURITY_TYPE_PSK);
+        assertEquals(saeParams1, saeParams2);
+        assertNotEquals(saeParams1, pskParams);
+    }
+
+    /** Check that hash values are the same if and only if their types are the same. */
+    @Test
+    public void testHashCode() {
+        SecurityParams saeParams1 = SecurityParams.createSecurityParamsBySecurityType(
+                WifiConfiguration.SECURITY_TYPE_SAE);
+        SecurityParams saeParams2 = SecurityParams.createSecurityParamsBySecurityType(
+                WifiConfiguration.SECURITY_TYPE_SAE);
+        SecurityParams pskParams = SecurityParams.createSecurityParamsBySecurityType(
+                WifiConfiguration.SECURITY_TYPE_PSK);
+        assertEquals(saeParams1.hashCode(), saeParams2.hashCode());
+        assertNotEquals(saeParams1.hashCode(), pskParams.hashCode());
+    }
+
+    /** Verify open network check */
+    @Test
+    public void testIsOpenNetwork() {
+        SecurityParams[] openSecurityParams = new SecurityParams[] {
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_OWE),
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_OPEN),
+        };
+        for (SecurityParams p: openSecurityParams) {
+            assertTrue(p.isOpenSecurityType());
+        }
+
+        SecurityParams[] nonOpenSecurityParams = new SecurityParams[] {
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_EAP),
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_PASSPOINT_R1_R2),
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_PASSPOINT_R3),
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_OSEN),
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_WAPI_PSK),
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_WAPI_CERT),
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_WEP),
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT),
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE),
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_SAE),
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_PSK),
+        };
+        for (SecurityParams p: nonOpenSecurityParams) {
+            assertFalse(p.isOpenSecurityType());
+        }
+    }
+
+    /** Verify enterprise network check */
+    @Test
+    public void testIsEnterpriseNetwork() {
+        SecurityParams[] enterpriseSecurityParams = new SecurityParams[] {
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_EAP),
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_PASSPOINT_R1_R2),
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_PASSPOINT_R3),
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_WAPI_CERT),
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT),
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE),
+        };
+        for (SecurityParams p: enterpriseSecurityParams) {
+            assertTrue(p.isEnterpriseSecurityType());
+        }
+
+        SecurityParams[] nonEnterpriseSecurityParams = new SecurityParams[] {
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_OWE),
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_OPEN),
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_OSEN),
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_WAPI_PSK),
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_WEP),
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_SAE),
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_PSK),
+        };
+        for (SecurityParams p: nonEnterpriseSecurityParams) {
+            assertFalse(p.isEnterpriseSecurityType());
+        }
+    }
+
+    /** Check that parcel marshalling/unmarshalling works */
+    @Test
+    public void testParcelMethods() {
+        SecurityParams params = SecurityParams.createSecurityParamsBySecurityType(
+                WifiConfiguration.SECURITY_TYPE_SAE);
+
+        Parcel parcelW = Parcel.obtain();
+        params.writeToParcel(parcelW, 0);
+        byte[] bytes = parcelW.marshall();
+        parcelW.recycle();
+
+        Parcel parcelR = Parcel.obtain();
+        parcelR.unmarshall(bytes, 0, bytes.length);
+        parcelR.setDataPosition(0);
+
+        SecurityParams reParams = SecurityParams.createFromParcel(parcelR);
+        assertEquals(params, reParams);
+    }
+}
diff --git a/framework/tests/src/android/net/wifi/SoftApCapabilityTest.java b/framework/tests/src/android/net/wifi/SoftApCapabilityTest.java
index 73b501a..d59605c 100644
--- a/framework/tests/src/android/net/wifi/SoftApCapabilityTest.java
+++ b/framework/tests/src/android/net/wifi/SoftApCapabilityTest.java
@@ -16,10 +16,10 @@
 
 package android.net.wifi;
 
-import android.os.Parcel;
-
 import static org.junit.Assert.assertEquals;
 
+import android.os.Parcel;
+
 import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
@@ -37,8 +37,14 @@
     public void testCopyOperator() throws Exception {
         long testSoftApFeature = SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT
                 | SoftApCapability.SOFTAP_FEATURE_ACS_OFFLOAD;
+        int[] testSupported2Glist = {1, 2, 3, 4};
+        int[] testSupported5Glist = {36, 149};
+        int[] testSupported60Glist = {1, 2};
         SoftApCapability capability = new SoftApCapability(testSoftApFeature);
         capability.setMaxSupportedClients(10);
+        capability.setSupportedChannelList(SoftApConfiguration.BAND_2GHZ, testSupported2Glist);
+        capability.setSupportedChannelList(SoftApConfiguration.BAND_5GHZ, testSupported5Glist);
+        capability.setSupportedChannelList(SoftApConfiguration.BAND_60GHZ, testSupported60Glist);
 
         SoftApCapability copiedCapability = new SoftApCapability(capability);
 
@@ -55,6 +61,13 @@
                 | SoftApCapability.SOFTAP_FEATURE_ACS_OFFLOAD;
         SoftApCapability capability = new SoftApCapability(testSoftApFeature);
         capability.setMaxSupportedClients(10);
+        int[] testSupported2Glist = {1, 2, 3, 4};
+        int[] testSupported5Glist = {36, 149};
+        int[] testSupported60Glist = {1, 2};
+
+        capability.setSupportedChannelList(SoftApConfiguration.BAND_2GHZ, testSupported2Glist);
+        capability.setSupportedChannelList(SoftApConfiguration.BAND_5GHZ, testSupported5Glist);
+        capability.setSupportedChannelList(SoftApConfiguration.BAND_60GHZ, testSupported60Glist);
 
         Parcel parcelW = Parcel.obtain();
         capability.writeToParcel(parcelW, 0);
@@ -70,4 +83,22 @@
         assertEquals(capability.hashCode(), fromParcel.hashCode());
     }
 
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetSupportedChannelListWithInvalidBand() {
+        long testSoftApFeature = SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT
+                | SoftApCapability.SOFTAP_FEATURE_ACS_OFFLOAD;
+        SoftApCapability capability = new SoftApCapability(testSoftApFeature);
+        capability.setSupportedChannelList(
+                SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ, new int[0]);
+
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetSupportedChannelListWithInvalidBand() {
+        long testSoftApFeature = SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT
+                | SoftApCapability.SOFTAP_FEATURE_ACS_OFFLOAD;
+        SoftApCapability capability = new SoftApCapability(testSoftApFeature);
+        capability.getSupportedChannelList(
+                SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ);
+    }
 }
diff --git a/framework/tests/src/android/net/wifi/SoftApConfigurationTest.java b/framework/tests/src/android/net/wifi/SoftApConfigurationTest.java
index 2d7e535..eb9dbb3 100644
--- a/framework/tests/src/android/net/wifi/SoftApConfigurationTest.java
+++ b/framework/tests/src/android/net/wifi/SoftApConfigurationTest.java
@@ -19,21 +19,28 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import android.net.MacAddress;
 import android.os.Parcel;
+import android.util.SparseIntArray;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.modules.utils.build.SdkLevel;
+
 import org.junit.Test;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Random;
 
 @SmallTest
 public class SoftApConfigurationTest {
     private static final String TEST_CHAR_SET_AS_STRING = "abcdefghijklmnopqrstuvwxyz0123456789";
+    private static final String TEST_BSSID = "aa:22:33:aa:bb:cc";
 
     private SoftApConfiguration parcelUnparcel(SoftApConfiguration configIn) {
         Parcel parcel = Parcel.obtain();
@@ -66,18 +73,29 @@
 
     @Test
     public void testBasicSettings() {
+        MacAddress testBssid = MacAddress.fromString(TEST_BSSID);
         SoftApConfiguration original = new SoftApConfiguration.Builder()
                 .setSsid("ssid")
-                .setBssid(MacAddress.fromString("11:22:33:44:55:66"))
+                .setBssid(testBssid)
                 .build();
         assertThat(original.getSsid()).isEqualTo("ssid");
-        assertThat(original.getBssid()).isEqualTo(MacAddress.fromString("11:22:33:44:55:66"));
+        assertThat(original.getBssid()).isEqualTo(testBssid);
         assertThat(original.getPassphrase()).isNull();
         assertThat(original.getSecurityType()).isEqualTo(SoftApConfiguration.SECURITY_TYPE_OPEN);
         assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_2GHZ);
         assertThat(original.getChannel()).isEqualTo(0);
         assertThat(original.isHiddenSsid()).isEqualTo(false);
         assertThat(original.getMaxNumberOfClients()).isEqualTo(0);
+        if (SdkLevel.isAtLeastS()) {
+            assertThat(original.getMacRandomizationSetting())
+                    .isEqualTo(SoftApConfiguration.RANDOMIZATION_PERSISTENT);
+            assertThat(original.isBridgedModeOpportunisticShutdownEnabled())
+                    .isEqualTo(true);
+            assertThat(original.isIeee80211axEnabled())
+                    .isEqualTo(true);
+            assertThat(original.isUserConfiguration())
+                    .isEqualTo(true);
+        }
 
         SoftApConfiguration unparceled = parcelUnparcel(original);
         assertThat(unparceled).isNotSameInstanceAs(original);
@@ -120,7 +138,7 @@
         List<MacAddress> testAllowedClientList = new ArrayList<>();
         testBlockedClientList.add(MacAddress.fromString("11:22:33:44:55:66"));
         testAllowedClientList.add(MacAddress.fromString("aa:bb:cc:dd:ee:ff"));
-        SoftApConfiguration original = new SoftApConfiguration.Builder()
+        SoftApConfiguration.Builder originalBuilder = new SoftApConfiguration.Builder()
                 .setPassphrase("secretsecret", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
                 .setChannel(149, SoftApConfiguration.BAND_5GHZ)
                 .setHiddenSsid(true)
@@ -129,8 +147,15 @@
                 .setShutdownTimeoutMillis(500000)
                 .setClientControlByUserEnabled(true)
                 .setBlockedClientList(testBlockedClientList)
-                .setAllowedClientList(testAllowedClientList)
-                .build();
+                .setAllowedClientList(testAllowedClientList);
+        if (SdkLevel.isAtLeastS()) {
+            originalBuilder.setMacRandomizationSetting(SoftApConfiguration.RANDOMIZATION_NONE);
+            originalBuilder.setBridgedModeOpportunisticShutdownEnabled(false);
+            originalBuilder.setIeee80211axEnabled(false);
+            originalBuilder.setUserConfiguration(false);
+        }
+
+        SoftApConfiguration original = originalBuilder.build();
         assertThat(original.getPassphrase()).isEqualTo("secretsecret");
         assertThat(original.getSecurityType()).isEqualTo(
                 SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
@@ -143,6 +168,16 @@
         assertThat(original.isClientControlByUserEnabled()).isEqualTo(true);
         assertThat(original.getBlockedClientList()).isEqualTo(testBlockedClientList);
         assertThat(original.getAllowedClientList()).isEqualTo(testAllowedClientList);
+        if (SdkLevel.isAtLeastS()) {
+            assertThat(original.getMacRandomizationSetting())
+                    .isEqualTo(SoftApConfiguration.RANDOMIZATION_NONE);
+            assertThat(original.isBridgedModeOpportunisticShutdownEnabled())
+                    .isEqualTo(false);
+            assertThat(original.isIeee80211axEnabled())
+                    .isEqualTo(false);
+            assertThat(original.isUserConfiguration())
+                    .isEqualTo(false);
+        }
 
         SoftApConfiguration unparceled = parcelUnparcel(original);
         assertThat(unparceled).isNotSameInstanceAs(original);
@@ -209,6 +244,20 @@
     }
 
     @Test(expected = IllegalArgumentException.class)
+    public void testInvalidBroadcastBssid() {
+        SoftApConfiguration original = new SoftApConfiguration.Builder()
+                .setBssid(MacAddress.BROADCAST_ADDRESS)
+                .build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidMulticastBssid() {
+        SoftApConfiguration original = new SoftApConfiguration.Builder()
+                .setBssid(MacAddress.fromString("01:aa:bb:cc:dd:ee"))
+                .build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
     public void testInvalidShortPasswordLengthForWpa2() {
         SoftApConfiguration original = new SoftApConfiguration.Builder()
                 .setPassphrase(generateRandomString(SoftApConfiguration.PSK_MIN_LEN - 1),
@@ -336,4 +385,165 @@
                 .isEqualTo(WifiConfiguration.KeyMgmt.WPA2_PSK);
         assertThat(wifiConfig_sae_transition.preSharedKey).isEqualTo("secretsecret");
     }
+
+    @Test
+    public void testDualBands() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        int[] dual_bands = new int[2];
+        dual_bands[0] = SoftApConfiguration.BAND_2GHZ;
+        dual_bands[1] = SoftApConfiguration.BAND_5GHZ;
+        SoftApConfiguration dual_bands_config = new SoftApConfiguration.Builder()
+                .setSsid("ssid")
+                .setBands(dual_bands)
+                .build();
+        assertTrue(Arrays.equals(dual_bands, dual_bands_config.getBands()));
+        assertThat(dual_bands_config.getBand()).isEqualTo(SoftApConfiguration.BAND_2GHZ);
+    }
+
+    @Test
+    public void testDualChannels() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        int[] expected_dual_bands = new int[2];
+        expected_dual_bands[0] = SoftApConfiguration.BAND_2GHZ;
+        expected_dual_bands[1] = SoftApConfiguration.BAND_5GHZ;
+        SparseIntArray dual_channels = new SparseIntArray(2);
+        dual_channels.put(SoftApConfiguration.BAND_5GHZ, 149);
+        dual_channels.put(SoftApConfiguration.BAND_2GHZ, 2);
+        SoftApConfiguration dual_channels_config = new SoftApConfiguration.Builder()
+                .setSsid("ssid")
+                .setChannels(dual_channels)
+                .build();
+        assertTrue(Arrays.equals(expected_dual_bands, dual_channels_config.getBands()));
+        assertThat(dual_channels_config.getBand()).isEqualTo(SoftApConfiguration.BAND_2GHZ);
+        assertTrue(dual_channels.toString().equals(dual_channels_config.getChannels().toString()));
+        assertThat(dual_channels_config.getChannel()).isEqualTo(2);
+
+        // Test different parameters.
+        dual_channels.clear();
+        dual_channels.put(SoftApConfiguration.BAND_5GHZ, 149);
+        dual_channels.put(SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ, 0);
+        expected_dual_bands[0] = SoftApConfiguration.BAND_5GHZ;
+        expected_dual_bands[1] = SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ;
+        dual_channels_config = new SoftApConfiguration.Builder()
+                .setSsid("ssid")
+                .setChannels(dual_channels)
+                .build();
+        assertTrue(Arrays.equals(expected_dual_bands, dual_channels_config.getBands()));
+        assertThat(dual_channels_config.getBand()).isEqualTo(SoftApConfiguration.BAND_5GHZ);
+        assertTrue(dual_channels.toString().equals(dual_channels_config.getChannels().toString()));
+        assertThat(dual_channels_config.getChannel()).isEqualTo(149);
+    }
+
+    @Test
+    public void testInvalidBandWhenSetBands() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        boolean isIllegalArgumentExceptionHappened = false;
+        int[] dual_bands = new int[2];
+        dual_bands[0] = SoftApConfiguration.BAND_2GHZ;
+        dual_bands[1] = -1;
+        try {
+            SoftApConfiguration dual_channels_config = new SoftApConfiguration.Builder()
+                    .setSsid("ssid")
+                    .setBands(dual_bands)
+                    .build();
+            isIllegalArgumentExceptionHappened = false;
+        } catch (IllegalArgumentException iae) {
+            isIllegalArgumentExceptionHappened = true;
+        }
+        assertTrue(isIllegalArgumentExceptionHappened);
+
+        try {
+            SoftApConfiguration dual_channels_config = new SoftApConfiguration.Builder()
+                    .setSsid("ssid")
+                    .setBands(new int[0])
+                    .build();
+            isIllegalArgumentExceptionHappened = false;
+        } catch (IllegalArgumentException iae) {
+            isIllegalArgumentExceptionHappened = true;
+        }
+        assertTrue(isIllegalArgumentExceptionHappened);
+
+        try {
+            SoftApConfiguration dual_channels_config = new SoftApConfiguration.Builder()
+                    .setSsid("ssid")
+                    .setBands(new int[3])
+                    .build();
+            isIllegalArgumentExceptionHappened = false;
+        } catch (IllegalArgumentException iae) {
+            isIllegalArgumentExceptionHappened = true;
+        }
+        assertTrue(isIllegalArgumentExceptionHappened);
+    }
+
+    @Test
+    public void testInvalidConfigWhenSetChannels() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        boolean isIllegalArgumentExceptionHappened = false;
+        SparseIntArray invalid_channels = new SparseIntArray();
+        try {
+            SoftApConfiguration zero_channels_config = new SoftApConfiguration.Builder()
+                    .setSsid("ssid")
+                    .setChannels(invalid_channels)
+                    .build();
+            isIllegalArgumentExceptionHappened = false;
+        } catch (IllegalArgumentException iae) {
+            isIllegalArgumentExceptionHappened = true;
+        }
+        assertTrue(isIllegalArgumentExceptionHappened);
+
+        try {
+            invalid_channels.clear();
+            invalid_channels.put(SoftApConfiguration.BAND_2GHZ, 2);
+            invalid_channels.put(SoftApConfiguration.BAND_5GHZ, 11);
+            SoftApConfiguration invalid_band_channels_config = new SoftApConfiguration.Builder()
+                    .setSsid("ssid")
+                    .setChannels(invalid_channels)
+                    .build();
+            isIllegalArgumentExceptionHappened = false;
+        } catch (IllegalArgumentException iae) {
+            isIllegalArgumentExceptionHappened = true;
+        }
+        assertTrue(isIllegalArgumentExceptionHappened);
+
+        try {
+            invalid_channels.clear();
+            invalid_channels.put(SoftApConfiguration.BAND_2GHZ, 2);
+            invalid_channels.put(SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ,
+                    149);
+            SoftApConfiguration invalid_dual_channels_config = new SoftApConfiguration.Builder()
+                    .setSsid("ssid")
+                    .setChannels(invalid_channels)
+                    .build();
+            isIllegalArgumentExceptionHappened = false;
+        } catch (IllegalArgumentException iae) {
+            isIllegalArgumentExceptionHappened = true;
+        }
+        assertTrue(isIllegalArgumentExceptionHappened);
+
+        try {
+            invalid_channels.clear();
+            invalid_channels.put(SoftApConfiguration.BAND_2GHZ, 2);
+            invalid_channels.put(SoftApConfiguration.BAND_5GHZ, 149);
+            invalid_channels.put(SoftApConfiguration.BAND_6GHZ, 2);
+            SoftApConfiguration three_channels_config = new SoftApConfiguration.Builder()
+                    .setSsid("ssid")
+                    .setChannels(invalid_channels)
+                    .build();
+            isIllegalArgumentExceptionHappened = false;
+        } catch (IllegalArgumentException iae) {
+            isIllegalArgumentExceptionHappened = true;
+        }
+        assertTrue(isIllegalArgumentExceptionHappened);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidConfigWhenSet60GhzChannels() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        SparseIntArray invalid_channels = new SparseIntArray();
+        invalid_channels.put(SoftApConfiguration.BAND_60GHZ, 99);
+        SoftApConfiguration config = new SoftApConfiguration.Builder()
+                .setSsid("ssid")
+                .setChannels(invalid_channels)
+                .build();
+    }
 }
diff --git a/framework/tests/src/android/net/wifi/SoftApInfoTest.java b/framework/tests/src/android/net/wifi/SoftApInfoTest.java
index 929f3ab..f415447 100644
--- a/framework/tests/src/android/net/wifi/SoftApInfoTest.java
+++ b/framework/tests/src/android/net/wifi/SoftApInfoTest.java
@@ -16,12 +16,15 @@
 
 package android.net.wifi;
 
-import android.os.Parcel;
-
 import static org.junit.Assert.assertEquals;
 
+import android.net.MacAddress;
+import android.os.Parcel;
+
 import androidx.test.filters.SmallTest;
 
+import com.android.modules.utils.build.SdkLevel;
+
 import org.junit.Test;
 
 /**
@@ -29,15 +32,24 @@
  */
 @SmallTest
 public class SoftApInfoTest {
-
+    private static final String TEST_AP_INSTANCE = "wlan1";
+    private static final int TEST_FREQUENCY = 2412;
+    private static final int TEST_BANDWIDTH = SoftApInfo.CHANNEL_WIDTH_20MHZ;
+    private static final int TEST_WIFI_STANDARD = ScanResult.WIFI_STANDARD_LEGACY;
+    private static final MacAddress TEST_AP_MAC = MacAddress.fromString("aa:bb:cc:dd:ee:ff");
+    private static final long TEST_SHUTDOWN_TIMEOUT_MILLIS = 100_000;
     /**
      * Verifies copy constructor.
      */
     @Test
     public void testCopyOperator() throws Exception {
         SoftApInfo info = new SoftApInfo();
-        info.setFrequency(2412);
-        info.setBandwidth(SoftApInfo.CHANNEL_WIDTH_20MHZ);
+        info.setFrequency(TEST_FREQUENCY);
+        info.setBandwidth(TEST_BANDWIDTH);
+        info.setBssid(TEST_AP_MAC);
+        info.setWifiStandard(TEST_WIFI_STANDARD);
+        info.setApInstanceIdentifier(TEST_AP_INSTANCE);
+
 
         SoftApInfo copiedInfo = new SoftApInfo(info);
 
@@ -51,8 +63,11 @@
     @Test
     public void testParcelOperation() throws Exception {
         SoftApInfo info = new SoftApInfo();
-        info.setFrequency(2412);
-        info.setBandwidth(SoftApInfo.CHANNEL_WIDTH_20MHZ);
+        info.setFrequency(TEST_FREQUENCY);
+        info.setBandwidth(TEST_BANDWIDTH);
+        info.setBssid(TEST_AP_MAC);
+        info.setWifiStandard(TEST_WIFI_STANDARD);
+        info.setApInstanceIdentifier(TEST_AP_INSTANCE);
 
         Parcel parcelW = Parcel.obtain();
         info.writeToParcel(parcelW, 0);
@@ -68,4 +83,42 @@
         assertEquals(info.hashCode(), fromParcel.hashCode());
     }
 
+
+    /**
+     * Verifies the initial value same as expected.
+     */
+    @Test
+    public void testInitialValue() throws Exception {
+        SoftApInfo info = new SoftApInfo();
+        assertEquals(info.getFrequency(), 0);
+        assertEquals(info.getBandwidth(), SoftApInfo.CHANNEL_WIDTH_INVALID);
+        if (SdkLevel.isAtLeastS()) {
+            assertEquals(info.getBssid(), null);
+            assertEquals(info.getWifiStandard(), ScanResult.WIFI_STANDARD_UNKNOWN);
+            assertEquals(info.getApInstanceIdentifier(), null);
+        }
+    }
+
+    /**
+     * Verifies the set/get method same as expected.
+     */
+    @Test
+    public void testGetXXXAlignedWithSetXXX() throws Exception {
+        SoftApInfo info = new SoftApInfo();
+        info.setFrequency(TEST_FREQUENCY);
+        info.setBandwidth(TEST_BANDWIDTH);
+        info.setBssid(TEST_AP_MAC);
+        info.setWifiStandard(TEST_WIFI_STANDARD);
+        info.setApInstanceIdentifier(TEST_AP_INSTANCE);
+        info.setAutoShutdownTimeoutMillis(TEST_SHUTDOWN_TIMEOUT_MILLIS);
+        assertEquals(info.getFrequency(), TEST_FREQUENCY);
+        assertEquals(info.getBandwidth(), TEST_BANDWIDTH);
+        if (SdkLevel.isAtLeastS()) {
+            assertEquals(info.getBssid(), TEST_AP_MAC);
+            assertEquals(info.getWifiStandard(), TEST_WIFI_STANDARD);
+            assertEquals(info.getApInstanceIdentifier(), TEST_AP_INSTANCE);
+        }
+        assertEquals(info.getAutoShutdownTimeoutMillis(), TEST_SHUTDOWN_TIMEOUT_MILLIS);
+    }
+
 }
diff --git a/framework/tests/src/android/net/wifi/WifiClientTest.java b/framework/tests/src/android/net/wifi/WifiClientTest.java
index 7a3baf9..7046563 100644
--- a/framework/tests/src/android/net/wifi/WifiClientTest.java
+++ b/framework/tests/src/android/net/wifi/WifiClientTest.java
@@ -42,9 +42,9 @@
      */
     @Test
     public void testWifiClientParcelWriteRead() throws Exception {
-        WifiClient writeWifiClient = new WifiClient(MAC_ADDRESS);
+        WifiClient writeWifiClient = new WifiClient(MAC_ADDRESS, INTERFACE_NAME);
 
-        assertParcelSane(writeWifiClient, 1);
+        assertParcelSane(writeWifiClient, 2);
     }
 
     /**
@@ -52,12 +52,12 @@
      */
     @Test
     public void testWifiClientEquals() throws Exception {
-        WifiClient writeWifiClient = new WifiClient(MAC_ADDRESS);
-        WifiClient writeWifiClientEquals = new WifiClient(MAC_ADDRESS);
+        WifiClient writeWifiClient = new WifiClient(MAC_ADDRESS, INTERFACE_NAME);
+        WifiClient writeWifiClientEquals = new WifiClient(MAC_ADDRESS, INTERFACE_NAME);
 
         assertEquals(writeWifiClient, writeWifiClientEquals);
         assertEquals(writeWifiClient.hashCode(), writeWifiClientEquals.hashCode());
-        assertFieldCountEquals(1, WifiClient.class);
+        assertFieldCountEquals(2, WifiClient.class);
     }
 
     /**
@@ -66,8 +66,8 @@
     @Test
     public void testWifiClientNotEquals() throws Exception {
         final MacAddress macAddressNotEquals = MacAddress.fromString("00:00:00:00:00:00");
-        WifiClient writeWifiClient = new WifiClient(MAC_ADDRESS);
-        WifiClient writeWifiClientNotEquals = new WifiClient(macAddressNotEquals);
+        WifiClient writeWifiClient = new WifiClient(MAC_ADDRESS, INTERFACE_NAME);
+        WifiClient writeWifiClientNotEquals = new WifiClient(macAddressNotEquals, INTERFACE_NAME);
 
         assertNotEquals(writeWifiClient, writeWifiClientNotEquals);
         assertNotEquals(writeWifiClient.hashCode(), writeWifiClientNotEquals.hashCode());
diff --git a/framework/tests/src/android/net/wifi/WifiConfigurationTest.java b/framework/tests/src/android/net/wifi/WifiConfigurationTest.java
index 890d3d3..276d85f 100644
--- a/framework/tests/src/android/net/wifi/WifiConfigurationTest.java
+++ b/framework/tests/src/android/net/wifi/WifiConfigurationTest.java
@@ -17,12 +17,18 @@
 package android.net.wifi;
 
 import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_EAP;
-import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT;
 import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_OPEN;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_OSEN;
 import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_OWE;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_PASSPOINT_R1_R2;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_PASSPOINT_R3;
 import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_PSK;
 import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_SAE;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_WAPI_CERT;
 import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_WAPI_PSK;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_WEP;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -30,24 +36,39 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
 import android.net.MacAddress;
+import android.net.wifi.WifiConfiguration.GroupCipher;
 import android.net.wifi.WifiConfiguration.KeyMgmt;
 import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
+import android.net.wifi.WifiConfiguration.PairwiseCipher;
+import android.net.wifi.WifiConfiguration.Protocol;
 import android.os.Parcel;
+import android.util.Pair;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.MacAddressUtils;
 
 import org.junit.Before;
 import org.junit.Test;
 
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.List;
+
 /**
  * Unit tests for {@link android.net.wifi.WifiConfiguration}.
  */
 @SmallTest
 public class WifiConfigurationTest {
+    private static final String TEST_PASSPOINT_UNIQUE_ID = "uniqueId";
+    private static final int TEST_CARRIER_ID = 1234;
+    private static final int TEST_SUB_ID = 3;
+    private static final String TEST_PACKAGE_NAME = "google.com";
 
     @Before
     public void setUp() {
@@ -67,11 +88,16 @@
         WifiConfiguration config = new WifiConfiguration();
         config.setPasspointManagementObjectTree(cookie);
         config.trusted = false;
+        config.oemPaid = true;
+        config.oemPrivate = true;
+        config.carrierMerged = true;
         config.updateIdentifier = "1234";
         config.fromWifiNetworkSpecifier = true;
         config.fromWifiNetworkSuggestion = true;
         config.setRandomizedMacAddress(MacAddressUtils.createRandomUnicastAddress());
         MacAddress macBeforeParcel = config.getRandomizedMacAddress();
+        config.subscriptionId = 1;
+        config.carrierId = 1189;
         Parcel parcelW = Parcel.obtain();
         config.writeToParcel(parcelW, 0);
         byte[] bytes = parcelW.marshall();
@@ -87,8 +113,11 @@
         assertEquals(macBeforeParcel, reconfig.getRandomizedMacAddress());
         assertEquals(config.updateIdentifier, reconfig.updateIdentifier);
         assertFalse(reconfig.trusted);
-        assertTrue(config.fromWifiNetworkSpecifier);
-        assertTrue(config.fromWifiNetworkSuggestion);
+        assertTrue(reconfig.fromWifiNetworkSpecifier);
+        assertTrue(reconfig.fromWifiNetworkSuggestion);
+        assertTrue(reconfig.oemPaid);
+        assertTrue(reconfig.oemPrivate);
+        assertTrue(reconfig.carrierMerged);
 
         Parcel parcelWW = Parcel.obtain();
         reconfig.writeToParcel(parcelWW, 0);
@@ -99,6 +128,34 @@
     }
 
     @Test
+    public void testWifiConfigurationCopyConstructor() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.trusted = false;
+        config.oemPaid = true;
+        config.oemPrivate = true;
+        config.carrierMerged = true;
+        config.updateIdentifier = "1234";
+        config.fromWifiNetworkSpecifier = true;
+        config.fromWifiNetworkSuggestion = true;
+        config.setRandomizedMacAddress(MacAddressUtils.createRandomUnicastAddress());
+        MacAddress macBeforeParcel = config.getRandomizedMacAddress();
+        config.subscriptionId = 1;
+        config.carrierId = 1189;
+
+        WifiConfiguration reconfig = new WifiConfiguration(config);
+
+        // lacking a useful config.equals, check two fields near the end.
+        assertEquals(macBeforeParcel, reconfig.getRandomizedMacAddress());
+        assertEquals(config.updateIdentifier, reconfig.updateIdentifier);
+        assertFalse(reconfig.trusted);
+        assertTrue(reconfig.fromWifiNetworkSpecifier);
+        assertTrue(reconfig.fromWifiNetworkSuggestion);
+        assertTrue(reconfig.oemPaid);
+        assertTrue(reconfig.oemPrivate);
+        assertTrue(reconfig.carrierMerged);
+    }
+
+    @Test
     public void testIsOpenNetwork_IsOpen_NullWepKeys() {
         WifiConfiguration config = new WifiConfiguration();
         config.allowedKeyManagement.clear();
@@ -145,18 +202,24 @@
 
     @Test
     public void testIsOpenNetwork_NotOpen_HasAuthType() {
-        for (int keyMgmt = 0; keyMgmt < WifiConfiguration.KeyMgmt.strings.length; keyMgmt++) {
-            if (keyMgmt == WifiConfiguration.KeyMgmt.NONE
-                    || keyMgmt == WifiConfiguration.KeyMgmt.OWE) {
-                continue;
-            }
+        int[] securityTypes = new int [] {
+                SECURITY_TYPE_WEP,
+                SECURITY_TYPE_PSK,
+                SECURITY_TYPE_EAP,
+                SECURITY_TYPE_SAE,
+                SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT,
+                SECURITY_TYPE_WAPI_PSK,
+                SECURITY_TYPE_WAPI_CERT,
+                SECURITY_TYPE_EAP_WPA3_ENTERPRISE,
+                SECURITY_TYPE_OSEN,
+        };
+        for (int type: securityTypes) {
             WifiConfiguration config = new WifiConfiguration();
-            config.allowedKeyManagement.clear();
-            config.allowedKeyManagement.set(keyMgmt);
+            config.setSecurityParams(type);
             config.wepKeys = null;
 
-            assertFalse("Open network reported when key mgmt was set to "
-                            + WifiConfiguration.KeyMgmt.strings[keyMgmt], config.isOpenNetwork());
+            assertFalse("Open network reported when security type was set to "
+                            + type, config.isOpenNetwork());
         }
     }
 
@@ -166,6 +229,7 @@
         config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
         config.wepKeys = null;
+        config.convertLegacyFieldsToSecurityParamsIfNeeded();
 
         assertFalse(config.isOpenNetwork());
     }
@@ -388,6 +452,79 @@
     }
 
     /**
+     * Verifies that getNetworkKey returns the correct String for networks of
+     * various different security types, the result should be stable.
+     */
+    @Test
+    public void testGetNetworkKeyString() {
+        WifiConfiguration config = new WifiConfiguration();
+        final String mSsid = "TestAP";
+        config.SSID = mSsid;
+
+        // Test various combinations
+        config.allowedKeyManagement.set(KeyMgmt.WPA_PSK);
+        assertEquals(mSsid + KeyMgmt.strings[KeyMgmt.WPA_PSK],
+                config.getNetworkKey());
+
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(KeyMgmt.WPA_EAP);
+        assertEquals(mSsid + KeyMgmt.strings[KeyMgmt.WPA_EAP],
+                config.getNetworkKey());
+
+        config.wepKeys[0] = "TestWep";
+        config.allowedKeyManagement.clear();
+        assertEquals(mSsid + "WEP", config.getNetworkKey());
+
+        // set WEP key and give a valid index.
+        config.wepKeys[0] = null;
+        config.wepKeys[2] = "TestWep";
+        config.wepTxKeyIndex = 2;
+        config.allowedKeyManagement.clear();
+        assertEquals(mSsid + "WEP", config.getNetworkKey());
+
+        // set WEP key but does not give a valid index.
+        config.wepKeys[0] = null;
+        config.wepKeys[2] = "TestWep";
+        config.wepTxKeyIndex = 0;
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(KeyMgmt.OWE);
+        assertEquals(mSsid + KeyMgmt.strings[KeyMgmt.OWE], config.getNetworkKey());
+
+        config.wepKeys[0] = null;
+        config.wepTxKeyIndex = 0;
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(KeyMgmt.OWE);
+        assertEquals(mSsid + KeyMgmt.strings[KeyMgmt.OWE], config.getNetworkKey());
+
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(KeyMgmt.SAE);
+        assertEquals(mSsid + KeyMgmt.strings[KeyMgmt.SAE], config.getNetworkKey());
+
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(KeyMgmt.SUITE_B_192);
+        assertEquals(mSsid + KeyMgmt.strings[KeyMgmt.SUITE_B_192],
+                config.getNetworkKey());
+
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(KeyMgmt.NONE);
+        assertEquals(mSsid + KeyMgmt.strings[KeyMgmt.NONE], config.getNetworkKey());
+
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(KeyMgmt.WAPI_PSK);
+        assertEquals(mSsid + KeyMgmt.strings[KeyMgmt.WAPI_PSK],
+                config.getNetworkKey());
+
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(KeyMgmt.WAPI_CERT);
+        assertEquals(mSsid + KeyMgmt.strings[KeyMgmt.WAPI_CERT],
+                config.getNetworkKey());
+
+        config.allowedKeyManagement.clear();
+        config.setPasspointUniqueId(TEST_PASSPOINT_UNIQUE_ID);
+        assertEquals(TEST_PASSPOINT_UNIQUE_ID, config.getNetworkKey());
+    }
+
+    /**
      * Ensure that the {@link NetworkSelectionStatus.DisableReasonInfo}s are populated in
      * {@link NetworkSelectionStatus#DISABLE_REASON_INFOS} for reason codes from 0 to
      * {@link NetworkSelectionStatus#NETWORK_SELECTION_DISABLED_MAX} - 1.
@@ -462,7 +599,7 @@
     public void testSetSecurityParamsForSuiteB() throws Exception {
         WifiConfiguration config = new WifiConfiguration();
 
-        config.setSecurityParams(SECURITY_TYPE_EAP_SUITE_B);
+        config.setSecurityParams(SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT);
 
         assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SUITE_B_192));
         assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP));
@@ -475,6 +612,26 @@
     }
 
     /**
+     * Ensure that {@link WifiConfiguration#setSecurityParams(int)} sets up the
+     * {@link WifiConfiguration} object correctly for WPA3 Enterprise security type.
+     * @throws Exception
+     */
+    @Test
+    public void testSetSecurityParamsForWpa3Enterprise() throws Exception {
+        WifiConfiguration config = new WifiConfiguration();
+
+        config.setSecurityParams(SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
+
+        assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP));
+        assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X));
+        assertTrue(config.allowedPairwiseCiphers.get(WifiConfiguration.PairwiseCipher.CCMP));
+        assertTrue(config.allowedPairwiseCiphers.get(WifiConfiguration.PairwiseCipher.GCMP_256));
+        assertTrue(config.allowedGroupCiphers.get(WifiConfiguration.GroupCipher.CCMP));
+        assertTrue(config.allowedGroupCiphers.get(WifiConfiguration.GroupCipher.GCMP_256));
+        assertTrue(config.requirePmf);
+    }
+
+    /**
      * Test that the NetworkSelectionStatus Builder returns the same values that was set, and that
      * calling build multiple times returns different instances.
      */
@@ -535,7 +692,526 @@
         configuration.setSecurityParams(SECURITY_TYPE_EAP);
         assertFalse(configuration.needsPreSharedKey());
 
-        configuration.setSecurityParams(SECURITY_TYPE_EAP_SUITE_B);
+        configuration.setSecurityParams(SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
         assertFalse(configuration.needsPreSharedKey());
+
+        configuration.setSecurityParams(SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT);
+        assertFalse(configuration.needsPreSharedKey());
+    }
+
+    @Test
+    public void testGetAuthType() throws Exception {
+        WifiConfiguration configuration = new WifiConfiguration();
+
+        configuration.setSecurityParams(SECURITY_TYPE_PSK);
+        assertEquals(KeyMgmt.WPA_PSK, configuration.getAuthType());
+
+        configuration.setSecurityParams(SECURITY_TYPE_SAE);
+        assertEquals(KeyMgmt.SAE, configuration.getAuthType());
+
+        configuration.setSecurityParams(SECURITY_TYPE_WAPI_PSK);
+        assertEquals(KeyMgmt.WAPI_PSK, configuration.getAuthType());
+
+        configuration.setSecurityParams(SECURITY_TYPE_OPEN);
+        assertEquals(KeyMgmt.NONE, configuration.getAuthType());
+
+        configuration.setSecurityParams(SECURITY_TYPE_OWE);
+        assertEquals(KeyMgmt.OWE, configuration.getAuthType());
+
+        configuration.setSecurityParams(SECURITY_TYPE_EAP);
+        assertEquals(KeyMgmt.WPA_EAP, configuration.getAuthType());
+
+        configuration.setSecurityParams(SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
+        assertEquals(KeyMgmt.WPA_EAP, configuration.getAuthType());
+
+        configuration.setSecurityParams(SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT);
+        assertEquals(KeyMgmt.SUITE_B_192, configuration.getAuthType());
+
+        configuration.setSecurityParams(SECURITY_TYPE_WAPI_CERT);
+        assertEquals(KeyMgmt.WAPI_CERT, configuration.getAuthType());
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void testGetAuthTypeFailure1() throws Exception {
+        WifiConfiguration configuration = new WifiConfiguration();
+
+        configuration.setSecurityParams(SECURITY_TYPE_PSK);
+        configuration.allowedKeyManagement.set(KeyMgmt.IEEE8021X);
+        configuration.getAuthType();
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void testGetAuthTypeFailure2() throws Exception {
+        WifiConfiguration configuration = new WifiConfiguration();
+
+        configuration.allowedKeyManagement.set(KeyMgmt.IEEE8021X);
+        configuration.allowedKeyManagement.set(KeyMgmt.WPA_EAP);
+        configuration.allowedKeyManagement.set(KeyMgmt.SAE);
+        configuration.getAuthType();
+    }
+
+    /**
+     * Verifies that getProfileKey returns the correct String for networks of
+     * various different security types, the result should be stable.
+     */
+    @Test
+    public void testGetProfileKeyString() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        WifiConfiguration config = new WifiConfiguration();
+        final String mSsid = "TestAP";
+        config.SSID = mSsid;
+        config.carrierId = TEST_CARRIER_ID;
+        config.subscriptionId = TEST_SUB_ID;
+        config.creatorName = TEST_PACKAGE_NAME;
+
+
+        // Test various combinations
+        config.allowedKeyManagement.set(KeyMgmt.WPA_PSK);
+        config.fromWifiNetworkSuggestion = false;
+        assertEquals(createProfileKey(mSsid, KeyMgmt.strings[KeyMgmt.WPA_PSK], TEST_PACKAGE_NAME,
+                TEST_CARRIER_ID, TEST_SUB_ID, false), config.getProfileKey());
+        config.fromWifiNetworkSuggestion = true;
+        assertEquals(createProfileKey(mSsid, KeyMgmt.strings[KeyMgmt.WPA_PSK], TEST_PACKAGE_NAME,
+                TEST_CARRIER_ID, TEST_SUB_ID, true), config.getProfileKey());
+
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(KeyMgmt.WPA_EAP);
+        config.fromWifiNetworkSuggestion = false;
+        assertEquals(createProfileKey(mSsid, KeyMgmt.strings[KeyMgmt.WPA_EAP], TEST_PACKAGE_NAME,
+                TEST_CARRIER_ID, TEST_SUB_ID, false), config.getProfileKey());
+        config.fromWifiNetworkSuggestion = true;
+        assertEquals(createProfileKey(mSsid, KeyMgmt.strings[KeyMgmt.WPA_EAP], TEST_PACKAGE_NAME,
+                TEST_CARRIER_ID, TEST_SUB_ID, true), config.getProfileKey());
+
+        config.wepKeys[0] = "TestWep";
+        config.allowedKeyManagement.clear();
+        config.fromWifiNetworkSuggestion = false;
+        assertEquals(createProfileKey(mSsid, "WEP", TEST_PACKAGE_NAME,
+                TEST_CARRIER_ID, TEST_SUB_ID, false), config.getProfileKey());
+        config.fromWifiNetworkSuggestion = true;
+        assertEquals(createProfileKey(mSsid, "WEP", TEST_PACKAGE_NAME,
+                TEST_CARRIER_ID, TEST_SUB_ID, true), config.getProfileKey());
+
+        // set WEP key and give a valid index.
+        config.wepKeys[0] = null;
+        config.wepKeys[2] = "TestWep";
+        config.wepTxKeyIndex = 2;
+        config.allowedKeyManagement.clear();
+        config.fromWifiNetworkSuggestion = false;
+        assertEquals(createProfileKey(mSsid, "WEP", TEST_PACKAGE_NAME,
+                TEST_CARRIER_ID, TEST_SUB_ID, false), config.getProfileKey());
+        config.fromWifiNetworkSuggestion = true;
+        assertEquals(createProfileKey(mSsid, "WEP", TEST_PACKAGE_NAME,
+                TEST_CARRIER_ID, TEST_SUB_ID, true), config.getProfileKey());
+
+        // set WEP key but does not give a valid index.
+        config.wepKeys[0] = null;
+        config.wepKeys[2] = "TestWep";
+        config.wepTxKeyIndex = 0;
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(KeyMgmt.OWE);
+        config.fromWifiNetworkSuggestion = false;
+        assertEquals(createProfileKey(mSsid, KeyMgmt.strings[KeyMgmt.OWE], TEST_PACKAGE_NAME,
+                TEST_CARRIER_ID, TEST_SUB_ID, false), config.getProfileKey());
+        config.fromWifiNetworkSuggestion = true;
+        assertEquals(createProfileKey(mSsid, KeyMgmt.strings[KeyMgmt.OWE], TEST_PACKAGE_NAME,
+                TEST_CARRIER_ID, TEST_SUB_ID, true), config.getProfileKey());
+
+        config.wepKeys[0] = null;
+        config.wepTxKeyIndex = 0;
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(KeyMgmt.OWE);
+        config.fromWifiNetworkSuggestion = false;
+        assertEquals(createProfileKey(mSsid, KeyMgmt.strings[KeyMgmt.OWE], TEST_PACKAGE_NAME,
+                TEST_CARRIER_ID, TEST_SUB_ID, false), config.getProfileKey());
+        config.fromWifiNetworkSuggestion = true;
+        assertEquals(createProfileKey(mSsid, KeyMgmt.strings[KeyMgmt.OWE], TEST_PACKAGE_NAME,
+                TEST_CARRIER_ID, TEST_SUB_ID, true), config.getProfileKey());
+
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(KeyMgmt.SAE);
+        config.fromWifiNetworkSuggestion = false;
+        assertEquals(createProfileKey(mSsid, KeyMgmt.strings[KeyMgmt.SAE], TEST_PACKAGE_NAME,
+                TEST_CARRIER_ID, TEST_SUB_ID, false), config.getProfileKey());
+        config.fromWifiNetworkSuggestion = true;
+        assertEquals(createProfileKey(mSsid, KeyMgmt.strings[KeyMgmt.SAE], TEST_PACKAGE_NAME,
+                TEST_CARRIER_ID, TEST_SUB_ID, true), config.getProfileKey());
+
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(KeyMgmt.SUITE_B_192);
+        config.fromWifiNetworkSuggestion = false;
+        assertEquals(createProfileKey(mSsid, KeyMgmt.strings[KeyMgmt.SUITE_B_192],
+                TEST_PACKAGE_NAME, TEST_CARRIER_ID, TEST_SUB_ID, false), config.getProfileKey());
+        config.fromWifiNetworkSuggestion = true;
+        assertEquals(createProfileKey(mSsid, KeyMgmt.strings[KeyMgmt.SUITE_B_192],
+                TEST_PACKAGE_NAME, TEST_CARRIER_ID, TEST_SUB_ID, true), config.getProfileKey());
+
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(KeyMgmt.NONE);
+        config.fromWifiNetworkSuggestion = false;
+        assertEquals(createProfileKey(mSsid, KeyMgmt.strings[KeyMgmt.NONE], TEST_PACKAGE_NAME,
+                TEST_CARRIER_ID, TEST_SUB_ID, false), config.getProfileKey());
+        config.fromWifiNetworkSuggestion = true;
+        assertEquals(createProfileKey(mSsid, KeyMgmt.strings[KeyMgmt.NONE], TEST_PACKAGE_NAME,
+                TEST_CARRIER_ID, TEST_SUB_ID, true), config.getProfileKey());
+
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(KeyMgmt.WAPI_PSK);
+        config.fromWifiNetworkSuggestion = false;
+        assertEquals(createProfileKey(mSsid, KeyMgmt.strings[KeyMgmt.WAPI_PSK], TEST_PACKAGE_NAME,
+                TEST_CARRIER_ID, TEST_SUB_ID, false), config.getProfileKey());
+        config.fromWifiNetworkSuggestion = true;
+        assertEquals(createProfileKey(mSsid, KeyMgmt.strings[KeyMgmt.WAPI_PSK], TEST_PACKAGE_NAME,
+                TEST_CARRIER_ID, TEST_SUB_ID, true), config.getProfileKey());
+
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(KeyMgmt.WAPI_CERT);
+        config.fromWifiNetworkSuggestion = false;
+        assertEquals(createProfileKey(mSsid, KeyMgmt.strings[KeyMgmt.WAPI_CERT], TEST_PACKAGE_NAME,
+                TEST_CARRIER_ID, TEST_SUB_ID, false), config.getProfileKey());
+        config.fromWifiNetworkSuggestion = true;
+        assertEquals(createProfileKey(mSsid, KeyMgmt.strings[KeyMgmt.WAPI_CERT], TEST_PACKAGE_NAME,
+                TEST_CARRIER_ID, TEST_SUB_ID, true), config.getProfileKey());
+
+        config.allowedKeyManagement.clear();
+        config.setPasspointUniqueId(TEST_PASSPOINT_UNIQUE_ID);
+        assertEquals(TEST_PASSPOINT_UNIQUE_ID, config.getProfileKey());
+    }
+
+    @Test
+    public void testGetProfileKeyOnR() {
+        assumeFalse(SdkLevel.isAtLeastS());
+        WifiConfiguration config = new WifiConfiguration();
+        final String mSsid = "TestAP";
+        config.SSID = mSsid;
+        config.carrierId = TEST_CARRIER_ID;
+        config.subscriptionId = TEST_SUB_ID;
+        config.creatorName = TEST_PACKAGE_NAME;
+
+        assertEquals(mSsid + KeyMgmt.strings[KeyMgmt.NONE] , config.getProfileKey());
+    }
+
+    private String createProfileKey(String ssid, String keyMgmt, String providerName,
+            int carrierId, int subId, boolean isFromSuggestion) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(ssid).append(keyMgmt);
+        if (isFromSuggestion) {
+            sb.append("_").append(providerName).append('-')
+                    .append(carrierId).append('-').append(subId);
+        }
+        return sb.toString();
+    }
+
+    private void verifyAllowedKeyManagement(WifiConfiguration config, int[] akms) {
+        for (int akm: akms) {
+            assertTrue(config.getSecurityParamsList().stream()
+                    .anyMatch(params -> params.getAllowedKeyManagement().get(akm)));
+        }
+    }
+
+    private void verifyAllowedProtocols(WifiConfiguration config, int[] aps) {
+        for (int ap: aps) {
+            assertTrue(config.getSecurityParamsList().stream()
+                    .anyMatch(params -> params.getAllowedProtocols().get(ap)));
+        }
+    }
+
+    private void verifyAllowedPairwiseCiphers(WifiConfiguration config, int[] apcs) {
+        for (int apc: apcs) {
+            assertTrue(config.getSecurityParamsList().stream()
+                    .anyMatch(params -> params.getAllowedPairwiseCiphers().get(apc)));
+        }
+    }
+
+    private void verifyAllowedGroupCiphers(WifiConfiguration config, int[] agcs) {
+        for (int agc: agcs) {
+            assertTrue(config.getSecurityParamsList().stream()
+                    .anyMatch(params -> params.getAllowedGroupCiphers().get(agc)));
+        }
+    }
+
+    /** Verify that adding security types works as expected. */
+    @Test
+    public void testAddSecurityTypes() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
+        config.addSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
+        config.addSecurityParams(WifiConfiguration.SECURITY_TYPE_WAPI_PSK);
+        List<SecurityParams> paramsList = config.getSecurityParamsList();
+        assertEquals(3, paramsList.size());
+
+        verifyAllowedKeyManagement(config, new int[] {
+                KeyMgmt.WPA_PSK, KeyMgmt.SAE, KeyMgmt.WAPI_PSK});
+        verifyAllowedProtocols(config, new int[] {Protocol.WPA, Protocol.RSN, Protocol.WAPI});
+        verifyAllowedPairwiseCiphers(config, new int[] {
+                PairwiseCipher.CCMP, PairwiseCipher.TKIP,
+                PairwiseCipher.GCMP_128, PairwiseCipher.GCMP_256,
+                PairwiseCipher.SMS4});
+        verifyAllowedGroupCiphers(config, new int[] {
+                GroupCipher.CCMP, GroupCipher.TKIP,
+                GroupCipher.GCMP_128, GroupCipher.GCMP_256,
+                GroupCipher.SMS4});
+    }
+
+    /** Check that a personal security type can be added to a personal configuration. */
+    @Test
+    public void testAddPersonalTypeToPersonalConfiguration() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.addSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
+        config.addSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
+    }
+
+    /** Check that an enterprise security type can be added to an enterprise configuration. */
+    @Test
+    public void testAddEnterpriseTypeToEnterpriseConfiguration() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.addSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
+        config.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.SIM);
+        config.enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.NONE);
+        config.addSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
+    }
+
+    /** Verify that adding an enterprise type to a personal configuration. */
+    @Test (expected = IllegalArgumentException.class)
+    public void testAddEnterpriseTypeToPersonalConfig() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
+        config.addSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
+    }
+
+    /** Verify that adding a personal type to an enterprise configuration. */
+    @Test (expected = IllegalArgumentException.class)
+    public void testAddPersonalTypeToEnterpriseConfig() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
+        config.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.SIM);
+        config.enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.NONE);
+        config.addSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
+    }
+
+    /** Check that an open security cannot be added to a non-open configuration. */
+    @Test(expected = IllegalArgumentException.class)
+    public void testAddOpenTypeToNonOpenConfiguration() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.addSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
+        config.addSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN);
+    }
+
+    /** Check that a non-open security cannot be added to an open configuration. */
+    @Test(expected = IllegalArgumentException.class)
+    public void testAddNonOpenTypeToOpenConfiguration() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.addSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN);
+        config.addSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
+    }
+
+    /** Check that a OSEN security cannot be added as additional type. */
+    @Test(expected = IllegalArgumentException.class)
+    public void testAddOsenTypeToConfiguration() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.addSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
+        config.addSecurityParams(WifiConfiguration.SECURITY_TYPE_OSEN);
+    }
+
+    /** Verify that adding duplicate security types raises the exception. */
+    @Test (expected = IllegalArgumentException.class)
+    public void testAddDuplicateSecurityTypes() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
+        config.addSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
+    }
+
+    /** Verify that adding duplicate security params raises the exception. */
+    @Test (expected = IllegalArgumentException.class)
+    public void testAddDuplicateSecurityParams() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.addSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
+        config.addSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
+    }
+
+    /** Verify that Suite-B type works as expected. */
+    @Test
+    public void testAddSuiteBSecurityType() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.addSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
+        config.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.SIM);
+        config.enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.NONE);
+        config.addSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT);
+
+        assertFalse(config.isSuiteBCipherEcdheRsaEnabled());
+        config.enableSuiteBCiphers(false, true);
+        assertTrue(config.isSuiteBCipherEcdheRsaEnabled());
+    }
+
+    /** Verify that FILS bit can be set correctly. */
+    @Test
+    public void testFilsKeyMgmt() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
+        config.addSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
+
+        config.enableFils(false, true);
+        assertFalse(config.isFilsSha256Enabled());
+        assertTrue(config.isFilsSha384Enabled());
+    }
+
+    /** Verify that SAE mode can be configured correctly. */
+    @Test
+    public void testSaeTypeMethods() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
+        config.addSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
+
+        SecurityParams saeParams = config.getSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
+        assertNotNull(saeParams);
+        assertFalse(saeParams.isSaeH2eOnlyMode());
+        assertFalse(saeParams.isSaePkOnlyMode());
+
+        config.enableSaeH2eOnlyMode(true);
+        config.enableSaePkOnlyMode(true);
+
+        saeParams = config.getSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
+        assertNotNull(saeParams);
+        assertTrue(saeParams.isSaeH2eOnlyMode());
+        assertTrue(saeParams.isSaePkOnlyMode());
+    }
+
+    /** Verify the legacy configuration conversion */
+    @Test
+    public void testLegacyConfigurationConversion() {
+        Pair[] keyMgmtSecurityTypePairs = new Pair[] {
+                new Pair<>(KeyMgmt.WAPI_CERT, SECURITY_TYPE_WAPI_CERT),
+                new Pair<>(KeyMgmt.WAPI_PSK, SECURITY_TYPE_WAPI_PSK),
+                new Pair<>(KeyMgmt.SUITE_B_192, SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT),
+                new Pair<>(KeyMgmt.OWE, SECURITY_TYPE_OWE),
+                new Pair<>(KeyMgmt.SAE, SECURITY_TYPE_SAE),
+                new Pair<>(KeyMgmt.OSEN, SECURITY_TYPE_OSEN),
+                new Pair<>(KeyMgmt.WPA2_PSK, SECURITY_TYPE_PSK),
+                new Pair<>(KeyMgmt.WPA_EAP, SECURITY_TYPE_EAP),
+                new Pair<>(KeyMgmt.WPA_PSK, SECURITY_TYPE_PSK),
+                new Pair<>(KeyMgmt.NONE, SECURITY_TYPE_OPEN),
+        };
+
+        for (Pair pair: keyMgmtSecurityTypePairs) {
+            WifiConfiguration config = new WifiConfiguration();
+            config.allowedKeyManagement.set((int) pair.first);
+            config.convertLegacyFieldsToSecurityParamsIfNeeded();
+            assertNotNull(config.getSecurityParams((int) pair.second));
+        }
+
+        // If none of key management is set, it should be open.
+        WifiConfiguration emptyConfig = new WifiConfiguration();
+        emptyConfig.convertLegacyFieldsToSecurityParamsIfNeeded();
+        assertNotNull(emptyConfig.getSecurityParams(SECURITY_TYPE_OPEN));
+
+        // If EAP key management is set and requirePmf is true, it is WPA3 Enterprise.
+        WifiConfiguration wpa3EnterpriseConfig = new WifiConfiguration();
+        wpa3EnterpriseConfig.allowedKeyManagement.set(KeyMgmt.WPA_EAP);
+        wpa3EnterpriseConfig.requirePmf = true;
+        wpa3EnterpriseConfig.convertLegacyFieldsToSecurityParamsIfNeeded();
+        assertNotNull(wpa3EnterpriseConfig.getSecurityParams(SECURITY_TYPE_EAP_WPA3_ENTERPRISE));
+
+        // If key management is NONE and wep key is set, it is WEP type.
+        WifiConfiguration wepConfig = new WifiConfiguration();
+        wepConfig.allowedKeyManagement.set(KeyMgmt.NONE);
+        wepConfig.wepKeys = new String[] {"\"abcdef\""};
+        wepConfig.convertLegacyFieldsToSecurityParamsIfNeeded();
+        assertNotNull(wepConfig.getSecurityParams(SECURITY_TYPE_WEP));
+    }
+
+    /** Verify the set security params by SecurityParams objects. */
+    @Test
+    public void testSetBySecurityParamsObject() {
+        int[] securityTypes = new int[] {
+                SECURITY_TYPE_WAPI_CERT,
+                SECURITY_TYPE_WAPI_PSK,
+                SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT,
+                SECURITY_TYPE_OWE,
+                SECURITY_TYPE_SAE,
+                SECURITY_TYPE_OSEN,
+                SECURITY_TYPE_EAP,
+                SECURITY_TYPE_PSK,
+                SECURITY_TYPE_OPEN,
+                SECURITY_TYPE_PASSPOINT_R1_R2,
+                SECURITY_TYPE_PASSPOINT_R3,
+        };
+        for (int type: securityTypes) {
+            WifiConfiguration config = new WifiConfiguration();
+            config.setSecurityParams(type);
+            assertTrue(config.isSecurityType(type));
+            assertNotNull(config.getSecurityParams(type));
+        }
+    }
+
+    /** Verify the set security params by an allowed key management mask. */
+    @Test
+    public void testSetSecurityParamsByAllowedKeyManagement() {
+        Pair[] keyMgmtSecurityTypePairs = new Pair[] {
+                new Pair<>(KeyMgmt.WAPI_CERT, SECURITY_TYPE_WAPI_CERT),
+                new Pair<>(KeyMgmt.WAPI_PSK, SECURITY_TYPE_WAPI_PSK),
+                new Pair<>(KeyMgmt.SUITE_B_192, SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT),
+                new Pair<>(KeyMgmt.OWE, SECURITY_TYPE_OWE),
+                new Pair<>(KeyMgmt.SAE, SECURITY_TYPE_SAE),
+                new Pair<>(KeyMgmt.OSEN, SECURITY_TYPE_OSEN),
+                new Pair<>(KeyMgmt.WPA2_PSK, SECURITY_TYPE_PSK),
+                new Pair<>(KeyMgmt.WPA_EAP, SECURITY_TYPE_EAP),
+                new Pair<>(KeyMgmt.WPA_PSK, SECURITY_TYPE_PSK),
+                new Pair<>(KeyMgmt.NONE, SECURITY_TYPE_OPEN),
+        };
+
+        for (Pair pair: keyMgmtSecurityTypePairs) {
+            BitSet akm = new BitSet();
+            akm.set((int) pair.first);
+            WifiConfiguration config = new WifiConfiguration();
+            config.setSecurityParams(akm);
+            assertNotNull(config.getSecurityParams((int) pair.second));
+        }
+    }
+
+    /** Verify the set security params by an invalid allowed key management mask. */
+    @Test (expected = IllegalArgumentException.class)
+    public void testSetSecurityParamsByInvalidAllowedKeyManagement() {
+        WifiConfiguration config = new WifiConfiguration();
+        BitSet akm = null;
+        config.setSecurityParams(akm);
+    }
+
+    /** Verify the set security params by a security params list. */
+    @Test
+    public void testSetSecurityParamsBySecurityParamsList() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.SIM);
+        config.enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.NONE);
+        config.addSecurityParams(SECURITY_TYPE_EAP);
+        config.addSecurityParams(SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
+        assertTrue(config.isSecurityType(SECURITY_TYPE_EAP));
+        assertTrue(config.isSecurityType(SECURITY_TYPE_EAP_WPA3_ENTERPRISE));
+        assertFalse(config.isSecurityType(SECURITY_TYPE_PSK));
+        assertFalse(config.isSecurityType(SECURITY_TYPE_SAE));
+
+        List<SecurityParams> list = new ArrayList<>();
+        list.add(SecurityParams.createSecurityParamsBySecurityType(SECURITY_TYPE_PSK));
+        list.add(SecurityParams.createSecurityParamsBySecurityType(SECURITY_TYPE_SAE));
+        config.setSecurityParams(list);
+        assertFalse(config.isSecurityType(SECURITY_TYPE_EAP));
+        assertFalse(config.isSecurityType(SECURITY_TYPE_EAP_WPA3_ENTERPRISE));
+        assertTrue(config.isSecurityType(SECURITY_TYPE_PSK));
+        assertTrue(config.isSecurityType(SECURITY_TYPE_SAE));
+    }
+
+    /** Verify the set security params by an empty security params list. */
+    @Test (expected = IllegalArgumentException.class)
+    public void testSetSecurityParamsByEmptySecurityParamsList() {
+        WifiConfiguration config = new WifiConfiguration();
+        List<SecurityParams> list = new ArrayList<>();
+        config.setSecurityParams(list);
+    }
+
+    /** Verify the set security params by a null security params list. */
+    @Test (expected = IllegalArgumentException.class)
+    public void testSetSecurityParamsByNullSecurityParamsList() {
+        WifiConfiguration config = new WifiConfiguration();
+        List<SecurityParams> list = null;
+        config.setSecurityParams(list);
     }
 }
diff --git a/framework/tests/src/android/net/wifi/WifiConnectedSessionInfoTest.java b/framework/tests/src/android/net/wifi/WifiConnectedSessionInfoTest.java
new file mode 100644
index 0000000..cdc8b2f
--- /dev/null
+++ b/framework/tests/src/android/net/wifi/WifiConnectedSessionInfoTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.validateMockitoUsage;
+
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockitoAnnotations;
+
+
+/**
+ * Unit tests for {@link android.net.wifi.WifiConnectedSessionInfo}.
+ */
+@SmallTest
+public class WifiConnectedSessionInfoTest {
+    /**
+     * Setup before tests.
+     */
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    /**
+     * Clean up after tests.
+     */
+    @After
+    public void cleanup() {
+        validateMockitoUsage();
+    }
+
+    /**
+     * Verify parcel read/write for Wifi connected session information.
+     */
+    @Test
+    public void verifySessionInfoWriteAndThenRead() throws Exception {
+        WifiConnectedSessionInfo writeResult = createResult();
+        WifiConnectedSessionInfo readResult = parcelWriteRead(writeResult);
+        assertWifiConnectedSessionInfoEquals(writeResult, readResult);
+    }
+
+    /**
+     * Write the provided {@link WifiConnectedSessionInfo} to a parcel and deserialize it.
+     */
+    private static WifiConnectedSessionInfo parcelWriteRead(
+            WifiConnectedSessionInfo writeResult) throws Exception {
+        Parcel parcel = Parcel.obtain();
+        writeResult.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);    // Rewind data position back to the beginning for read.
+        return WifiConnectedSessionInfo.CREATOR.createFromParcel(parcel);
+    }
+
+    private static WifiConnectedSessionInfo createResult() {
+        return new WifiConnectedSessionInfo.Builder(101)
+                .setUserSelected(true)
+                .build();
+    }
+
+    private static void assertWifiConnectedSessionInfoEquals(
+            WifiConnectedSessionInfo expected,
+            WifiConnectedSessionInfo actual) {
+        assertEquals(expected.getSessionId(), actual.getSessionId());
+        assertEquals(expected.isUserSelected(), actual.isUserSelected());
+    }
+}
diff --git a/framework/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java b/framework/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
index 62485ec..6b1ffb9 100644
--- a/framework/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
+++ b/framework/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
@@ -24,14 +24,18 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
 import android.net.wifi.WifiEnterpriseConfig.Eap;
 import android.net.wifi.WifiEnterpriseConfig.Phase2;
+import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.os.Parcel;
 import android.security.Credentials;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.modules.utils.build.SdkLevel;
+
 import org.junit.Before;
 import org.junit.Test;
 
@@ -48,6 +52,8 @@
     public static final String CA_CERT_PREFIX = KEYSTORE_URI + Credentials.CA_CERTIFICATE;
     public static final String KEYSTORES_URI = "keystores://";
     private static final String TEST_DOMAIN_SUFFIX_MATCH = "domainSuffixMatch";
+    private static final String TEST_ALT_SUBJECT_MATCH = "DNS:server.test.com";
+    private static final String TEST_DECORATED_IDENTITY_PREFIX = "androidwifi.dev!";
 
     private WifiEnterpriseConfig mEnterpriseConfig;
 
@@ -130,6 +136,16 @@
         assertTrue(mEnterpriseConfig.getClientCertificate() == cert0);
     }
 
+    @Test
+    public void testSetGetClientKeyPairAlias() {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        final String alias = "alias";
+        mEnterpriseConfig.setClientKeyPairAlias(alias);
+        assertEquals(alias, mEnterpriseConfig.getClientKeyPairAlias());
+        assertEquals(alias, mEnterpriseConfig.getClientKeyPairAliasInternal());
+    }
+
     private boolean isClientCertificateChainInvalid(X509Certificate[] clientChain) {
         boolean exceptionThrown = false;
         try {
@@ -543,35 +559,101 @@
     }
 
     @Test
-    public void testIsEnterpriseConfigSecure() {
+    public void testIsEnterpriseConfigServerCertNotEnabled() {
         WifiEnterpriseConfig baseConfig = new WifiEnterpriseConfig();
         baseConfig.setEapMethod(Eap.PEAP);
         baseConfig.setPhase2Method(Phase2.MSCHAPV2);
-        assertTrue(baseConfig.isInsecure());
+        assertTrue(baseConfig.isEapMethodServerCertUsed());
+        assertFalse(baseConfig.isServerCertValidationEnabled());
 
         WifiEnterpriseConfig noMatchConfig = new WifiEnterpriseConfig(baseConfig);
         noMatchConfig.setCaCertificate(FakeKeys.CA_CERT0);
-        // Missing match is insecure.
-        assertTrue(noMatchConfig.isInsecure());
+        // Missing match disables validation.
+        assertTrue(baseConfig.isEapMethodServerCertUsed());
+        assertFalse(baseConfig.isServerCertValidationEnabled());
 
         WifiEnterpriseConfig noCaConfig = new WifiEnterpriseConfig(baseConfig);
         noCaConfig.setDomainSuffixMatch(TEST_DOMAIN_SUFFIX_MATCH);
-        // Missing CA certificate is insecure.
-        assertTrue(noCaConfig.isInsecure());
+        // Missing CA certificate disables validation.
+        assertTrue(baseConfig.isEapMethodServerCertUsed());
+        assertFalse(baseConfig.isServerCertValidationEnabled());
 
-        WifiEnterpriseConfig secureConfig = new WifiEnterpriseConfig();
-        secureConfig.setEapMethod(Eap.PEAP);
-        secureConfig.setPhase2Method(Phase2.MSCHAPV2);
-        secureConfig.setCaCertificate(FakeKeys.CA_CERT0);
-        secureConfig.setDomainSuffixMatch(TEST_DOMAIN_SUFFIX_MATCH);
-        assertFalse(secureConfig.isInsecure());
-
-        WifiEnterpriseConfig secureConfigWithCaAlias = new WifiEnterpriseConfig();
-        secureConfigWithCaAlias.setEapMethod(Eap.PEAP);
-        secureConfigWithCaAlias.setPhase2Method(Phase2.MSCHAPV2);
-        secureConfigWithCaAlias.setCaCertificateAliases(new String[]{"alias1", "alisa2"});
-        secureConfigWithCaAlias.setDomainSuffixMatch(TEST_DOMAIN_SUFFIX_MATCH);
-        assertFalse(secureConfigWithCaAlias.isInsecure());
+        WifiEnterpriseConfig noValidationConfig = new WifiEnterpriseConfig();
+        noValidationConfig.setEapMethod(Eap.AKA);
+        assertFalse(noValidationConfig.isEapMethodServerCertUsed());
     }
 
+    @Test
+    public void testIsEnterpriseConfigServerCertEnabledWithPeap() {
+        testIsEnterpriseConfigServerCertEnabled(Eap.PEAP);
+    }
+
+    @Test
+    public void testIsEnterpriseConfigServerCertEnabledWithTls() {
+        testIsEnterpriseConfigServerCertEnabled(Eap.TLS);
+    }
+
+    @Test
+    public void testIsEnterpriseConfigServerCertEnabledWithTTLS() {
+        testIsEnterpriseConfigServerCertEnabled(Eap.TTLS);
+    }
+
+    @Test
+    public void testSetGetDecoratedIdentityPrefix() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        WifiEnterpriseConfig config = new WifiEnterpriseConfig();
+
+        assertNull(config.getDecoratedIdentityPrefix());
+        config.setDecoratedIdentityPrefix(TEST_DECORATED_IDENTITY_PREFIX);
+        assertEquals(TEST_DECORATED_IDENTITY_PREFIX, config.getDecoratedIdentityPrefix());
+    }
+
+    /**
+     * Verify that the set decorated identity prefix doesn't accept a malformed input.
+     *
+     * @throws Exception
+     */
+    @Test (expected = IllegalArgumentException.class)
+    public void testSetDecoratedIdentityPrefixWithInvalidValue() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        PasspointConfiguration config = new PasspointConfiguration();
+
+        config.setDecoratedIdentityPrefix(TEST_DECORATED_IDENTITY_PREFIX.replace('!', 'a'));
+    }
+
+    private void testIsEnterpriseConfigServerCertEnabled(int eapMethod) {
+        WifiEnterpriseConfig configWithCertAndDomainSuffixMatch = createEnterpriseConfig(eapMethod,
+                Phase2.NONE, FakeKeys.CA_CERT0, null, TEST_DOMAIN_SUFFIX_MATCH, null);
+        assertTrue(configWithCertAndDomainSuffixMatch.isEapMethodServerCertUsed());
+        assertTrue(configWithCertAndDomainSuffixMatch.isServerCertValidationEnabled());
+
+        WifiEnterpriseConfig configWithCertAndAltSubjectMatch = createEnterpriseConfig(eapMethod,
+                Phase2.NONE, FakeKeys.CA_CERT0, null, null, TEST_ALT_SUBJECT_MATCH);
+        assertTrue(configWithCertAndAltSubjectMatch.isEapMethodServerCertUsed());
+        assertTrue(configWithCertAndAltSubjectMatch.isServerCertValidationEnabled());
+
+        WifiEnterpriseConfig configWithAliasAndDomainSuffixMatch = createEnterpriseConfig(eapMethod,
+                Phase2.NONE, null, new String[]{"alias1", "alisa2"}, TEST_DOMAIN_SUFFIX_MATCH,
+                null);
+        assertTrue(configWithAliasAndDomainSuffixMatch.isEapMethodServerCertUsed());
+        assertTrue(configWithAliasAndDomainSuffixMatch.isServerCertValidationEnabled());
+
+        WifiEnterpriseConfig configWithAliasAndAltSubjectMatch = createEnterpriseConfig(eapMethod,
+                Phase2.NONE, null, new String[]{"alias1", "alisa2"}, null, TEST_ALT_SUBJECT_MATCH);
+        assertTrue(configWithAliasAndAltSubjectMatch.isEapMethodServerCertUsed());
+        assertTrue(configWithAliasAndAltSubjectMatch.isServerCertValidationEnabled());
+    }
+
+    private WifiEnterpriseConfig createEnterpriseConfig(int eapMethod, int phase2Method,
+            X509Certificate caCertificate, String[] aliases, String domainSuffixMatch,
+            String altSubjectMatch) {
+        WifiEnterpriseConfig config = new WifiEnterpriseConfig();
+        config.setEapMethod(eapMethod);
+        config.setPhase2Method(phase2Method);
+        config.setCaCertificate(caCertificate);
+        config.setCaCertificateAliases(aliases);
+        config.setDomainSuffixMatch(domainSuffixMatch);
+        config.setAltSubjectMatch(altSubjectMatch);
+        return config;
+    }
 }
diff --git a/framework/tests/src/android/net/wifi/WifiInfoTest.java b/framework/tests/src/android/net/wifi/WifiInfoTest.java
index 286088c..7b72fc7 100644
--- a/framework/tests/src/android/net/wifi/WifiInfoTest.java
+++ b/framework/tests/src/android/net/wifi/WifiInfoTest.java
@@ -16,6 +16,7 @@
 
 package android.net.wifi;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -23,10 +24,12 @@
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import android.net.NetworkCapabilities;
 import android.os.Parcel;
+import android.telephony.SubscriptionManager;
 
 import androidx.test.filters.SmallTest;
 
@@ -35,6 +38,8 @@
 import org.junit.Test;
 
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Unit tests for {@link android.net.wifi.WifiInfo}.
@@ -56,134 +61,250 @@
     private static final int TEST_RSSI = -60;
     private static final int TEST_NETWORK_ID = 5;
     private static final int TEST_NETWORK_ID2 = 6;
+    private static final int TEST_SUB_ID = 1;
+
+    private WifiInfo makeWifiInfoForNoRedactions(
+            List<ScanResult.InformationElement> informationElements) {
+        WifiInfo info = new WifiInfo();
+        info.txSuccess = TEST_TX_SUCCESS;
+        info.txRetries = TEST_TX_RETRIES;
+        info.txBad = TEST_TX_BAD;
+        info.rxSuccess = TEST_RX_SUCCESS;
+        info.setSSID(WifiSsid.createFromAsciiEncoded(TEST_SSID));
+        info.setBSSID(TEST_BSSID);
+        info.setNetworkId(TEST_NETWORK_ID);
+        info.setTrusted(true);
+        info.setOemPaid(true);
+        info.setOemPrivate(true);
+        info.setCarrierMerged(true);
+        info.setOsuAp(true);
+        info.setFQDN(TEST_FQDN);
+        info.setProviderFriendlyName(TEST_PROVIDER_NAME);
+        info.setRequestingPackageName(TEST_PACKAGE_NAME);
+        info.setWifiStandard(TEST_WIFI_STANDARD);
+        info.setMaxSupportedTxLinkSpeedMbps(TEST_MAX_SUPPORTED_TX_LINK_SPEED_MBPS);
+        info.setMaxSupportedRxLinkSpeedMbps(TEST_MAX_SUPPORTED_RX_LINK_SPEED_MBPS);
+        info.setSubscriptionId(TEST_SUB_ID);
+        info.setInformationElements(informationElements);
+        info.setIsPrimary(true);
+        info.setMacAddress(TEST_BSSID);
+        return info;
+    }
+
+    private void assertNoRedaction(WifiInfo info,
+            List<ScanResult.InformationElement> informationElements) {
+        assertEquals(TEST_TX_SUCCESS, info.txSuccess);
+        assertEquals(TEST_TX_RETRIES, info.txRetries);
+        assertEquals(TEST_TX_BAD, info.txBad);
+        assertEquals(TEST_RX_SUCCESS, info.rxSuccess);
+        assertEquals("\"" + TEST_SSID + "\"", info.getSSID());
+        assertEquals(TEST_BSSID, info.getBSSID());
+        assertEquals(TEST_NETWORK_ID, info.getNetworkId());
+        assertTrue(info.isTrusted());
+        assertTrue(info.isOsuAp());
+        assertTrue(info.isPasspointAp());
+        assertEquals(TEST_PACKAGE_NAME, info.getRequestingPackageName());
+        assertEquals(TEST_FQDN, info.getPasspointFqdn());
+        assertEquals(TEST_PROVIDER_NAME, info.getPasspointProviderFriendlyName());
+        assertEquals(TEST_WIFI_STANDARD, info.getWifiStandard());
+        assertEquals(TEST_MAX_SUPPORTED_TX_LINK_SPEED_MBPS, info.getMaxSupportedTxLinkSpeedMbps());
+        assertEquals(TEST_MAX_SUPPORTED_RX_LINK_SPEED_MBPS, info.getMaxSupportedRxLinkSpeedMbps());
+        assertEquals(TEST_BSSID, info.getMacAddress());
+        assertEquals(2, info.getInformationElements().size());
+        assertEquals(informationElements.get(0).id,
+                info.getInformationElements().get(0).id);
+        assertEquals(informationElements.get(0).idExt,
+                info.getInformationElements().get(0).idExt);
+        assertArrayEquals(informationElements.get(0).bytes,
+                info.getInformationElements().get(0).bytes);
+        assertEquals(informationElements.get(1).id,
+                info.getInformationElements().get(1).id);
+        assertEquals(informationElements.get(1).idExt,
+                info.getInformationElements().get(1).idExt);
+        assertArrayEquals(informationElements.get(1).bytes,
+                info.getInformationElements().get(1).bytes);
+        if (SdkLevel.isAtLeastS()) {
+            assertTrue(info.isOemPaid());
+            assertTrue(info.isOemPrivate());
+            assertTrue(info.isCarrierMerged());
+            assertEquals(TEST_SUB_ID, info.getSubscriptionId());
+            assertTrue(info.isPrimary());
+        }
+    }
 
     /**
-     *  Verify parcel write/read with WifiInfo.
+     *  Verify redaction of WifiInfo with REDACT_NONE.
      */
     @Test
-    public void testWifiInfoParcelWriteReadWithNoRedactions() throws Exception {
-        WifiInfo writeWifiInfo = new WifiInfo();
-        writeWifiInfo.txSuccess = TEST_TX_SUCCESS;
-        writeWifiInfo.txRetries = TEST_TX_RETRIES;
-        writeWifiInfo.txBad = TEST_TX_BAD;
-        writeWifiInfo.rxSuccess = TEST_RX_SUCCESS;
-        writeWifiInfo.setSSID(WifiSsid.createFromAsciiEncoded(TEST_SSID));
-        writeWifiInfo.setBSSID(TEST_BSSID);
-        writeWifiInfo.setNetworkId(TEST_NETWORK_ID);
-        writeWifiInfo.setTrusted(true);
-        writeWifiInfo.setOsuAp(true);
-        writeWifiInfo.setFQDN(TEST_FQDN);
-        writeWifiInfo.setProviderFriendlyName(TEST_PROVIDER_NAME);
-        writeWifiInfo.setRequestingPackageName(TEST_PACKAGE_NAME);
-        writeWifiInfo.setWifiStandard(TEST_WIFI_STANDARD);
-        writeWifiInfo.setMaxSupportedTxLinkSpeedMbps(TEST_MAX_SUPPORTED_TX_LINK_SPEED_MBPS);
-        writeWifiInfo.setMaxSupportedRxLinkSpeedMbps(TEST_MAX_SUPPORTED_RX_LINK_SPEED_MBPS);
-        writeWifiInfo.setMacAddress(TEST_BSSID);
+    public void testWifiInfoRedactNoRedactions() throws Exception {
+        List<ScanResult.InformationElement> informationElements = generateIes();
+        WifiInfo writeWifiInfo = makeWifiInfoForNoRedactions(informationElements);
 
         // Make a copy which allows parcelling of location sensitive data.
-        WifiInfo writeWifiInfoCopy = writeWifiInfo.makeCopy(NetworkCapabilities.REDACT_NONE);
+        WifiInfo redactedWifiInfo = writeWifiInfo.makeCopy(NetworkCapabilities.REDACT_NONE);
 
         Parcel parcel = Parcel.obtain();
-        writeWifiInfoCopy.writeToParcel(parcel, 0);
+        redactedWifiInfo.writeToParcel(parcel, 0);
         // Rewind the pointer to the head of the parcel.
         parcel.setDataPosition(0);
         WifiInfo readWifiInfo = WifiInfo.CREATOR.createFromParcel(parcel);
 
-        assertNotNull(readWifiInfo);
-        assertEquals(TEST_TX_SUCCESS, readWifiInfo.txSuccess);
-        assertEquals(TEST_TX_RETRIES, readWifiInfo.txRetries);
-        assertEquals(TEST_TX_BAD, readWifiInfo.txBad);
-        assertEquals(TEST_RX_SUCCESS, readWifiInfo.rxSuccess);
-        assertEquals("\"" + TEST_SSID + "\"", readWifiInfo.getSSID());
-        assertEquals(TEST_BSSID, readWifiInfo.getBSSID());
-        assertEquals(TEST_NETWORK_ID, readWifiInfo.getNetworkId());
-        assertTrue(readWifiInfo.isTrusted());
-        assertTrue(readWifiInfo.isOsuAp());
-        assertTrue(readWifiInfo.isPasspointAp());
-        assertEquals(TEST_PACKAGE_NAME, readWifiInfo.getRequestingPackageName());
-        assertEquals(TEST_FQDN, readWifiInfo.getPasspointFqdn());
-        assertEquals(TEST_PROVIDER_NAME, readWifiInfo.getPasspointProviderFriendlyName());
-        assertEquals(TEST_WIFI_STANDARD, readWifiInfo.getWifiStandard());
-        assertEquals(TEST_MAX_SUPPORTED_TX_LINK_SPEED_MBPS,
-                readWifiInfo.getMaxSupportedTxLinkSpeedMbps());
-        assertEquals(TEST_MAX_SUPPORTED_RX_LINK_SPEED_MBPS,
-                readWifiInfo.getMaxSupportedRxLinkSpeedMbps());
-        assertEquals(TEST_BSSID, readWifiInfo.getMacAddress());
+        assertNoRedaction(redactedWifiInfo, informationElements);
+        assertNoRedaction(readWifiInfo, informationElements);
+
+        if (SdkLevel.isAtLeastS()) {
+            // equals() was only introduced in S.
+            assertEquals(readWifiInfo, redactedWifiInfo);
+        }
+    }
+
+    private WifiInfo makeWifiInfoForLocationSensitiveRedaction() {
+        WifiInfo info = new WifiInfo();
+        info.txSuccess = TEST_TX_SUCCESS;
+        info.txRetries = TEST_TX_RETRIES;
+        info.txBad = TEST_TX_BAD;
+        info.rxSuccess = TEST_RX_SUCCESS;
+        info.setSSID(WifiSsid.createFromAsciiEncoded(TEST_SSID));
+        info.setBSSID(TEST_BSSID);
+        info.setNetworkId(TEST_NETWORK_ID);
+        info.setTrusted(true);
+        info.setOemPaid(true);
+        info.setOemPrivate(true);
+        info.setCarrierMerged(true);
+        info.setOsuAp(true);
+        info.setFQDN(TEST_FQDN);
+        info.setProviderFriendlyName(TEST_PROVIDER_NAME);
+        info.setRequestingPackageName(TEST_PACKAGE_NAME);
+        info.setWifiStandard(TEST_WIFI_STANDARD);
+        info.setMaxSupportedTxLinkSpeedMbps(TEST_MAX_SUPPORTED_TX_LINK_SPEED_MBPS);
+        info.setMaxSupportedRxLinkSpeedMbps(TEST_MAX_SUPPORTED_RX_LINK_SPEED_MBPS);
+        info.setSubscriptionId(TEST_SUB_ID);
+        info.setInformationElements(generateIes());
+        info.setIsPrimary(true);
+        info.setMacAddress(TEST_BSSID);
+        return info;
+    }
+
+    private void assertLocationSensitiveRedaction(WifiInfo info) {
+        assertNotNull(info);
+        assertEquals(TEST_TX_SUCCESS, info.txSuccess);
+        assertEquals(TEST_TX_RETRIES, info.txRetries);
+        assertEquals(TEST_TX_BAD, info.txBad);
+        assertEquals(TEST_RX_SUCCESS, info.rxSuccess);
+        assertEquals(WifiManager.UNKNOWN_SSID, info.getSSID());
+        assertEquals(WifiInfo.DEFAULT_MAC_ADDRESS, info.getBSSID());
+        assertEquals(WifiConfiguration.INVALID_NETWORK_ID, info.getNetworkId());
+        assertTrue(info.isTrusted());
+        assertTrue(info.isOsuAp());
+        assertFalse(info.isPasspointAp()); // fqdn & friendly name is masked.
+        assertEquals(TEST_PACKAGE_NAME, info.getRequestingPackageName());
+        assertNull(info.getPasspointFqdn());
+        assertNull(info.getPasspointProviderFriendlyName());
+        assertEquals(TEST_WIFI_STANDARD, info.getWifiStandard());
+        assertEquals(TEST_MAX_SUPPORTED_TX_LINK_SPEED_MBPS, info.getMaxSupportedTxLinkSpeedMbps());
+        assertEquals(TEST_MAX_SUPPORTED_RX_LINK_SPEED_MBPS, info.getMaxSupportedRxLinkSpeedMbps());
+        assertEquals(WifiInfo.DEFAULT_MAC_ADDRESS, info.getMacAddress());
+        assertNull(info.getInformationElements());
+        if (SdkLevel.isAtLeastS()) {
+            assertTrue(info.isOemPaid());
+            assertTrue(info.isOemPrivate());
+            assertTrue(info.isCarrierMerged());
+            assertEquals(TEST_SUB_ID, info.getSubscriptionId());
+            assertTrue(info.isPrimary());
+        }
     }
 
     /**
-     *  Verify parcel write/read with WifiInfo.
+     *  Verify redaction of WifiInfo with REDACT_FOR_ACCESS_FINE_LOCATION.
      */
     @Test
-    public void testWifiInfoParcelWriteReadWithoutLocationSensitiveInfo() throws Exception {
-        WifiInfo writeWifiInfo = new WifiInfo();
-        writeWifiInfo.txSuccess = TEST_TX_SUCCESS;
-        writeWifiInfo.txRetries = TEST_TX_RETRIES;
-        writeWifiInfo.txBad = TEST_TX_BAD;
-        writeWifiInfo.rxSuccess = TEST_RX_SUCCESS;
-        writeWifiInfo.setSSID(WifiSsid.createFromAsciiEncoded(TEST_SSID));
-        writeWifiInfo.setBSSID(TEST_BSSID);
-        writeWifiInfo.setNetworkId(TEST_NETWORK_ID);
-        writeWifiInfo.setTrusted(true);
-        writeWifiInfo.setOsuAp(true);
-        writeWifiInfo.setFQDN(TEST_FQDN);
-        writeWifiInfo.setProviderFriendlyName(TEST_PROVIDER_NAME);
-        writeWifiInfo.setRequestingPackageName(TEST_PACKAGE_NAME);
-        writeWifiInfo.setWifiStandard(TEST_WIFI_STANDARD);
-        writeWifiInfo.setMaxSupportedTxLinkSpeedMbps(TEST_MAX_SUPPORTED_TX_LINK_SPEED_MBPS);
-        writeWifiInfo.setMaxSupportedRxLinkSpeedMbps(TEST_MAX_SUPPORTED_RX_LINK_SPEED_MBPS);
-        writeWifiInfo.setMacAddress(TEST_BSSID);
+    public void testWifiInfoRedactLocationSensitiveInfo() throws Exception {
+        WifiInfo writeWifiInfo = makeWifiInfoForLocationSensitiveRedaction();
 
-        WifiInfo writeWifiInfoCopy =
+        WifiInfo redactedWifiInfo =
                 writeWifiInfo.makeCopy(NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION);
 
         Parcel parcel = Parcel.obtain();
-        writeWifiInfoCopy.writeToParcel(parcel, 0);
+        redactedWifiInfo.writeToParcel(parcel, 0);
         // Rewind the pointer to the head of the parcel.
         parcel.setDataPosition(0);
         WifiInfo readWifiInfo = WifiInfo.CREATOR.createFromParcel(parcel);
 
-        assertNotNull(readWifiInfo);
-        assertEquals(TEST_TX_SUCCESS, readWifiInfo.txSuccess);
-        assertEquals(TEST_TX_RETRIES, readWifiInfo.txRetries);
-        assertEquals(TEST_TX_BAD, readWifiInfo.txBad);
-        assertEquals(TEST_RX_SUCCESS, readWifiInfo.rxSuccess);
-        assertEquals(WifiManager.UNKNOWN_SSID, readWifiInfo.getSSID());
-        assertEquals(WifiInfo.DEFAULT_MAC_ADDRESS, readWifiInfo.getBSSID());
-        assertEquals(WifiConfiguration.INVALID_NETWORK_ID, readWifiInfo.getNetworkId());
-        assertTrue(readWifiInfo.isTrusted());
-        assertTrue(readWifiInfo.isOsuAp());
-        assertFalse(readWifiInfo.isPasspointAp()); // fqdn & friendly name is masked.
-        assertEquals(TEST_PACKAGE_NAME, readWifiInfo.getRequestingPackageName());
-        assertNull(readWifiInfo.getPasspointFqdn());
-        assertNull(readWifiInfo.getPasspointProviderFriendlyName());
-        assertEquals(TEST_WIFI_STANDARD, readWifiInfo.getWifiStandard());
-        assertEquals(TEST_MAX_SUPPORTED_TX_LINK_SPEED_MBPS,
-                readWifiInfo.getMaxSupportedTxLinkSpeedMbps());
-        assertEquals(TEST_MAX_SUPPORTED_RX_LINK_SPEED_MBPS,
-                readWifiInfo.getMaxSupportedRxLinkSpeedMbps());
-        assertEquals(WifiInfo.DEFAULT_MAC_ADDRESS, readWifiInfo.getMacAddress());
+        assertLocationSensitiveRedaction(redactedWifiInfo);
+        assertLocationSensitiveRedaction(readWifiInfo);
+
+        if (SdkLevel.isAtLeastS()) {
+            // equals() was only introduced in S.
+            assertEquals(redactedWifiInfo, readWifiInfo);
+        }
     }
 
     /**
-     *  Verify parcel write/read with WifiInfo.
+     *  Verify redaction of WifiInfo with REDACT_FOR_LOCAL_MAC_ADDRESS.
      */
     @Test
-    public void testWifiInfoParcelWriteReadWithoutLocalMacAddressInfo() throws Exception {
+    public void testWifiInfoRedactLocalMacAddressInfo() throws Exception {
         WifiInfo writeWifiInfo = new WifiInfo();
         writeWifiInfo.setMacAddress(TEST_BSSID);
 
-        WifiInfo writeWifiInfoCopy =
+        WifiInfo redactedWifiInfo =
                 writeWifiInfo.makeCopy(NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS);
 
         Parcel parcel = Parcel.obtain();
-        writeWifiInfoCopy.writeToParcel(parcel, 0);
+        redactedWifiInfo.writeToParcel(parcel, 0);
         // Rewind the pointer to the head of the parcel.
         parcel.setDataPosition(0);
         WifiInfo readWifiInfo = WifiInfo.CREATOR.createFromParcel(parcel);
 
+        assertNotNull(redactedWifiInfo);
+        assertEquals(WifiInfo.DEFAULT_MAC_ADDRESS, redactedWifiInfo.getMacAddress());
+
         assertNotNull(readWifiInfo);
         assertEquals(WifiInfo.DEFAULT_MAC_ADDRESS, readWifiInfo.getMacAddress());
+
+        if (SdkLevel.isAtLeastS()) {
+            // equals() was only introduced in S.
+            assertEquals(redactedWifiInfo, readWifiInfo);
+        }
+    }
+
+    private void assertIsPrimaryThrowsSecurityException(WifiInfo info) {
+        try {
+            // Should generate a security exception if caller does not have network settings
+            // permission.
+            info.isPrimary();
+            fail();
+        } catch (SecurityException e) { /* pass */ }
+    }
+
+    /**
+     *  Verify redaction of WifiInfo with REDACT_FOR_NETWORK_SETTINGS.
+     */
+    @Test
+    public void testWifiInfoRedactNetworkSettingsInfo() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        WifiInfo writeWifiInfo = new WifiInfo();
+        writeWifiInfo.setIsPrimary(true);
+
+        WifiInfo redactedWifiInfo =
+                writeWifiInfo.makeCopy(NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS);
+
+        Parcel parcel = Parcel.obtain();
+        redactedWifiInfo.writeToParcel(parcel, 0);
+        // Rewind the pointer to the head of the parcel.
+        parcel.setDataPosition(0);
+        WifiInfo readWifiInfo = WifiInfo.CREATOR.createFromParcel(parcel);
+
+        assertNotNull(redactedWifiInfo);
+        assertIsPrimaryThrowsSecurityException(redactedWifiInfo);
+        assertNotNull(readWifiInfo);
+        assertIsPrimaryThrowsSecurityException(readWifiInfo);
+
+        if (SdkLevel.isAtLeastS()) {
+            // equals() was only introduced in S.
+            assertEquals(redactedWifiInfo, readWifiInfo);
+        }
     }
 
     @Test
@@ -194,36 +315,95 @@
                 | NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS, redactions);
     }
 
+    private WifiInfo makeWifiInfoForLocationSensitiveAndLocalMacAddressRedaction() {
+        WifiInfo info = new WifiInfo();
+        info.setSSID(WifiSsid.createFromAsciiEncoded(TEST_SSID));
+        info.setBSSID(TEST_BSSID);
+        info.setNetworkId(TEST_NETWORK_ID);
+        info.setFQDN(TEST_FQDN);
+        info.setProviderFriendlyName(TEST_PROVIDER_NAME);
+        info.setInformationElements(generateIes());
+        info.setMacAddress(TEST_BSSID);
+        return info;
+    }
+
+    private void assertLocationSensitiveAndLocalMacAddressRedaction(WifiInfo info) {
+        assertNotNull(info);
+        assertEquals(WifiManager.UNKNOWN_SSID, info.getSSID());
+        assertEquals(WifiInfo.DEFAULT_MAC_ADDRESS, info.getBSSID());
+        assertEquals(WifiConfiguration.INVALID_NETWORK_ID, info.getNetworkId());
+        assertNull(info.getPasspointFqdn());
+        assertNull(info.getPasspointProviderFriendlyName());
+        assertEquals(WifiInfo.DEFAULT_MAC_ADDRESS, info.getMacAddress());
+        assertNull(info.getInformationElements());
+    }
+
     @Test
-    public void testWifiInfoParcelWriteReadWithoutLocationAndLocalMacAddressSensitiveInfo()
+    public void testWifiInfoRedactLocationAndLocalMacAddressSensitiveInfo()
             throws Exception {
         assumeTrue(SdkLevel.isAtLeastS());
 
-        WifiInfo writeWifiInfo = new WifiInfo();
-        writeWifiInfo.setSSID(WifiSsid.createFromAsciiEncoded(TEST_SSID));
-        writeWifiInfo.setBSSID(TEST_BSSID);
-        writeWifiInfo.setNetworkId(TEST_NETWORK_ID);
-        writeWifiInfo.setFQDN(TEST_FQDN);
-        writeWifiInfo.setProviderFriendlyName(TEST_PROVIDER_NAME);
-        writeWifiInfo.setMacAddress(TEST_BSSID);
+        WifiInfo writeWifiInfo = makeWifiInfoForLocationSensitiveAndLocalMacAddressRedaction();
 
-        WifiInfo writeWifiInfoCopy =
+        WifiInfo redactedWifiInfo =
                 writeWifiInfo.makeCopy(NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION
                         | NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS);
 
         Parcel parcel = Parcel.obtain();
+        redactedWifiInfo.writeToParcel(parcel, 0);
+        // Rewind the pointer to the head of the parcel.
+        parcel.setDataPosition(0);
+        WifiInfo readWifiInfo = WifiInfo.CREATOR.createFromParcel(parcel);
+
+        assertLocationSensitiveAndLocalMacAddressRedaction(redactedWifiInfo);
+        assertLocationSensitiveAndLocalMacAddressRedaction(readWifiInfo);
+
+        if (SdkLevel.isAtLeastS()) {
+            // equals() was only introduced in S.
+            assertEquals(redactedWifiInfo, readWifiInfo);
+        }
+    }
+
+    /**
+     *  Verify parcel write/read with null information elements.
+     */
+    @Test
+    public void testWifiInfoParcelWriteReadWithNullInfoElements() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        WifiInfo writeWifiInfo = new WifiInfo();
+        writeWifiInfo.setInformationElements(null);
+
+        // Make a copy which allows parcelling of location sensitive data.
+        WifiInfo writeWifiInfoCopy = writeWifiInfo.makeCopy(NetworkCapabilities.REDACT_NONE);
+
+        Parcel parcel = Parcel.obtain();
         writeWifiInfoCopy.writeToParcel(parcel, 0);
         // Rewind the pointer to the head of the parcel.
         parcel.setDataPosition(0);
         WifiInfo readWifiInfo = WifiInfo.CREATOR.createFromParcel(parcel);
+        assertNull(readWifiInfo.getInformationElements());
+    }
 
-        assertNotNull(readWifiInfo);
-        assertEquals(WifiManager.UNKNOWN_SSID, readWifiInfo.getSSID());
-        assertEquals(WifiInfo.DEFAULT_MAC_ADDRESS, readWifiInfo.getBSSID());
-        assertEquals(WifiConfiguration.INVALID_NETWORK_ID, readWifiInfo.getNetworkId());
-        assertNull(readWifiInfo.getPasspointFqdn());
-        assertNull(readWifiInfo.getPasspointProviderFriendlyName());
-        assertEquals(WifiInfo.DEFAULT_MAC_ADDRESS, readWifiInfo.getMacAddress());
+    /**
+     *  Verify parcel write/read with empty information elements.
+     */
+    @Test
+    public void testWifiInfoParcelWriteReadWithEmptyInfoElements() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        WifiInfo writeWifiInfo = new WifiInfo();
+        writeWifiInfo.setInformationElements(new ArrayList<>());
+
+        // Make a copy which allows parcelling of location sensitive data.
+        WifiInfo writeWifiInfoCopy = writeWifiInfo.makeCopy(NetworkCapabilities.REDACT_NONE);
+
+        Parcel parcel = Parcel.obtain();
+        writeWifiInfoCopy.writeToParcel(parcel, 0);
+        // Rewind the pointer to the head of the parcel.
+        parcel.setDataPosition(0);
+        WifiInfo readWifiInfo = WifiInfo.CREATOR.createFromParcel(parcel);
+        assertTrue(readWifiInfo.getInformationElements().isEmpty());
     }
 
     @Test
@@ -234,6 +414,9 @@
         writeWifiInfo.txBad = TEST_TX_BAD;
         writeWifiInfo.rxSuccess = TEST_RX_SUCCESS;
         writeWifiInfo.setTrusted(true);
+        writeWifiInfo.setOemPaid(true);
+        writeWifiInfo.setOemPrivate(true);
+        writeWifiInfo.setCarrierMerged(true);
         writeWifiInfo.setOsuAp(true);
         writeWifiInfo.setFQDN(TEST_FQDN);
         writeWifiInfo.setProviderFriendlyName(TEST_PROVIDER_NAME);
@@ -241,6 +424,8 @@
         writeWifiInfo.setWifiStandard(TEST_WIFI_STANDARD);
         writeWifiInfo.setMaxSupportedTxLinkSpeedMbps(TEST_MAX_SUPPORTED_TX_LINK_SPEED_MBPS);
         writeWifiInfo.setMaxSupportedRxLinkSpeedMbps(TEST_MAX_SUPPORTED_RX_LINK_SPEED_MBPS);
+        writeWifiInfo.setSubscriptionId(TEST_SUB_ID);
+        writeWifiInfo.setIsPrimary(true);
 
         WifiInfo readWifiInfo = new WifiInfo(writeWifiInfo);
 
@@ -259,6 +444,13 @@
                 readWifiInfo.getMaxSupportedTxLinkSpeedMbps());
         assertEquals(TEST_MAX_SUPPORTED_RX_LINK_SPEED_MBPS,
                 readWifiInfo.getMaxSupportedRxLinkSpeedMbps());
+        if (SdkLevel.isAtLeastS()) {
+            assertTrue(readWifiInfo.isOemPaid());
+            assertTrue(readWifiInfo.isOemPrivate());
+            assertTrue(readWifiInfo.isCarrierMerged());
+            assertEquals(TEST_SUB_ID, readWifiInfo.getSubscriptionId());
+            assertTrue(readWifiInfo.isPrimary());
+        }
     }
 
     /**
@@ -276,6 +468,13 @@
         assertEquals(WifiManager.UNKNOWN_SSID, wifiInfo.getSSID());
         assertEquals(null, wifiInfo.getBSSID());
         assertEquals(-1, wifiInfo.getNetworkId());
+        if (SdkLevel.isAtLeastS()) {
+            assertFalse(wifiInfo.isOemPaid());
+            assertFalse(wifiInfo.isOemPrivate());
+            assertFalse(wifiInfo.isCarrierMerged());
+            assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID, wifiInfo.getSubscriptionId());
+            assertFalse(wifiInfo.isPrimary());
+        }
     }
 
     /**
@@ -330,28 +529,57 @@
         if (SdkLevel.isAtLeastS()) {
             assertEquals(info1, info2);
         } else {
+            // On R devices, reference equality.
             assertNotEquals(info1, info2);
         }
 
-        info1.setTrusted(true);
-        // Same behavior pre-S & post-S.
+        info1.setSubscriptionId(TEST_SUB_ID);
         assertNotEquals(info1, info2);
 
-        info2.setTrusted(true);
+        info2.setSubscriptionId(TEST_SUB_ID);
         if (SdkLevel.isAtLeastS()) {
             assertEquals(info1, info2);
         } else {
+            // On R devices, reference equality.
             assertNotEquals(info1, info2);
         }
 
         info1.setSSID(WifiSsid.createFromHex(null));
-        // Same behavior pre-S & post-S.
         assertNotEquals(info1, info2);
 
         info2.setSSID(WifiSsid.createFromHex(null));
         if (SdkLevel.isAtLeastS()) {
             assertEquals(info1, info2);
         } else {
+            // On R devices, reference equality.
+            assertNotEquals(info1, info2);
+        }
+    }
+
+    @Test
+    public void testWifiInfoEqualsWithInfoElements() throws Exception {
+        WifiInfo.Builder builder = new WifiInfo.Builder()
+                .setSsid(TEST_SSID.getBytes(StandardCharsets.UTF_8))
+                .setBssid(TEST_BSSID)
+                .setRssi(TEST_RSSI)
+                .setNetworkId(TEST_NETWORK_ID);
+
+        WifiInfo info1 = builder.build();
+        WifiInfo info2 = builder.build();
+        if (SdkLevel.isAtLeastS()) {
+            assertEquals(info1, info2);
+        } else {
+            // On R devices, reference equality.
+            assertNotEquals(info1, info2);
+        }
+
+        info1.setInformationElements(generateIes());
+        info2.setInformationElements(generateIes());
+
+        if (SdkLevel.isAtLeastS()) {
+            assertEquals(info1, info2);
+        } else {
+            // On R devices, reference equality.
             assertNotEquals(info1, info2);
         }
     }
@@ -369,29 +597,103 @@
         if (SdkLevel.isAtLeastS()) {
             assertEquals(info1.hashCode(), info2.hashCode());
         } else {
+            // On R devices, system generated hashcode.
             assertNotEquals(info1.hashCode(), info2.hashCode());
         }
 
-        info1.setTrusted(true);
-        // Same behavior pre-S & post-S.
+        info1.setSubscriptionId(TEST_SUB_ID);
         assertNotEquals(info1.hashCode(), info2.hashCode());
 
-        info2.setTrusted(true);
+        info2.setSubscriptionId(TEST_SUB_ID);
         if (SdkLevel.isAtLeastS()) {
             assertEquals(info1.hashCode(), info2.hashCode());
         } else {
+            // On R devices, system generated hashcode.
             assertNotEquals(info1.hashCode(), info2.hashCode());
         }
 
         info1.setSSID(WifiSsid.createFromHex(null));
-        // Same behavior pre-S & post-S.
         assertNotEquals(info1.hashCode(), info2.hashCode());
 
         info2.setSSID(WifiSsid.createFromHex(null));
         if (SdkLevel.isAtLeastS()) {
             assertEquals(info1.hashCode(), info2.hashCode());
         } else {
+            // On R devices, system generated hashcode.
             assertNotEquals(info1.hashCode(), info2.hashCode());
         }
     }
+
+    @Test
+    public void testWifiInfoCurrentSecurityType() throws Exception {
+        WifiInfo.Builder builder = new WifiInfo.Builder()
+                .setSsid(TEST_SSID.getBytes(StandardCharsets.UTF_8))
+                .setBssid(TEST_BSSID)
+                .setRssi(TEST_RSSI)
+                .setNetworkId(TEST_NETWORK_ID)
+                .setCurrentSecurityType(WifiConfiguration.SECURITY_TYPE_SAE);
+
+        WifiInfo info = new WifiInfo();
+        assertEquals(WifiInfo.SECURITY_TYPE_UNKNOWN, info.getCurrentSecurityType());
+
+        info = builder.build();
+        assertEquals(WifiInfo.SECURITY_TYPE_SAE, info.getCurrentSecurityType());
+
+        info = builder.setCurrentSecurityType(WifiConfiguration.SECURITY_TYPE_OPEN).build();
+        assertEquals(WifiInfo.SECURITY_TYPE_OPEN, info.getCurrentSecurityType());
+
+        info = builder.setCurrentSecurityType(WifiConfiguration.SECURITY_TYPE_WEP).build();
+        assertEquals(WifiInfo.SECURITY_TYPE_WEP, info.getCurrentSecurityType());
+
+        info = builder.setCurrentSecurityType(WifiConfiguration.SECURITY_TYPE_PSK).build();
+        assertEquals(WifiInfo.SECURITY_TYPE_PSK, info.getCurrentSecurityType());
+
+        info = builder.setCurrentSecurityType(WifiConfiguration.SECURITY_TYPE_EAP).build();
+        assertEquals(WifiInfo.SECURITY_TYPE_EAP, info.getCurrentSecurityType());
+
+        info = builder.setCurrentSecurityType(WifiConfiguration.SECURITY_TYPE_OWE).build();
+        assertEquals(WifiInfo.SECURITY_TYPE_OWE, info.getCurrentSecurityType());
+
+        info = builder.setCurrentSecurityType(WifiConfiguration.SECURITY_TYPE_WAPI_PSK).build();
+        assertEquals(WifiInfo.SECURITY_TYPE_WAPI_PSK, info.getCurrentSecurityType());
+
+        info = builder.setCurrentSecurityType(WifiConfiguration.SECURITY_TYPE_WAPI_CERT).build();
+        assertEquals(WifiInfo.SECURITY_TYPE_WAPI_CERT, info.getCurrentSecurityType());
+
+        info = builder.setCurrentSecurityType(
+                WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE).build();
+        assertEquals(WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE, info.getCurrentSecurityType());
+
+        info = builder.setCurrentSecurityType(
+                WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT).build();
+        assertEquals(WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT,
+                info.getCurrentSecurityType());
+
+        info = builder.setCurrentSecurityType(
+                WifiConfiguration.SECURITY_TYPE_PASSPOINT_R1_R2).build();
+        assertEquals(WifiInfo.SECURITY_TYPE_PASSPOINT_R1_R2, info.getCurrentSecurityType());
+
+        info = builder.setCurrentSecurityType(WifiConfiguration.SECURITY_TYPE_PASSPOINT_R3).build();
+        assertEquals(WifiInfo.SECURITY_TYPE_PASSPOINT_R3, info.getCurrentSecurityType());
+
+        info.clearCurrentSecurityType();
+        assertEquals(WifiInfo.SECURITY_TYPE_UNKNOWN, info.getCurrentSecurityType());
+    }
+
+    private static List<ScanResult.InformationElement> generateIes() {
+        List<ScanResult.InformationElement> informationElements = new ArrayList<>();
+        ScanResult.InformationElement informationElement = new ScanResult.InformationElement();
+        informationElement.id = ScanResult.InformationElement.EID_HT_OPERATION;
+        informationElement.idExt = 0;
+        informationElement.bytes = new byte[]{0x11, 0x22, 0x33};
+        informationElements.add(informationElement);
+
+        informationElement = new ScanResult.InformationElement();
+        informationElement.id = ScanResult.InformationElement.EID_EXTENSION_PRESENT;
+        informationElement.idExt = ScanResult.InformationElement.EID_EXT_HE_OPERATION;
+        informationElement.bytes = new byte[]{0x44, 0x55, 0x66};
+        informationElements.add(informationElement);
+
+        return informationElements;
+    }
 }
diff --git a/framework/tests/src/android/net/wifi/WifiManagerTest.java b/framework/tests/src/android/net/wifi/WifiManagerTest.java
index f5cc43e..29a2fbe 100644
--- a/framework/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/framework/tests/src/android/net/wifi/WifiManagerTest.java
@@ -19,6 +19,9 @@
 import static android.net.wifi.WifiConfiguration.METERED_OVERRIDE_METERED;
 import static android.net.wifi.WifiManager.ActionListener;
 import static android.net.wifi.WifiManager.BUSY;
+import static android.net.wifi.WifiManager.COEX_RESTRICTION_SOFTAP;
+import static android.net.wifi.WifiManager.COEX_RESTRICTION_WIFI_AWARE;
+import static android.net.wifi.WifiManager.COEX_RESTRICTION_WIFI_DIRECT;
 import static android.net.wifi.WifiManager.ERROR;
 import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.ERROR_GENERIC;
 import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.ERROR_INCOMPATIBLE_MODE;
@@ -33,10 +36,17 @@
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING;
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED;
+import static android.net.wifi.WifiManager.WIFI_FEATURE_ADDITIONAL_STA_LOCAL_ONLY;
+import static android.net.wifi.WifiManager.WIFI_FEATURE_ADDITIONAL_STA_MBB;
+import static android.net.wifi.WifiManager.WIFI_FEATURE_ADDITIONAL_STA_RESTRICTED;
+import static android.net.wifi.WifiManager.WIFI_FEATURE_AP_STA;
+import static android.net.wifi.WifiManager.WIFI_FEATURE_DECORATED_IDENTITY;
 import static android.net.wifi.WifiManager.WIFI_FEATURE_DPP;
+import static android.net.wifi.WifiManager.WIFI_FEATURE_DPP_ENROLLEE_RESPONDER;
 import static android.net.wifi.WifiManager.WIFI_FEATURE_OWE;
 import static android.net.wifi.WifiManager.WIFI_FEATURE_P2P;
 import static android.net.wifi.WifiManager.WIFI_FEATURE_PASSPOINT;
+import static android.net.wifi.WifiManager.WIFI_FEATURE_PASSPOINT_TERMS_AND_CONDITIONS;
 import static android.net.wifi.WifiManager.WIFI_FEATURE_SCANNER;
 import static android.net.wifi.WifiManager.WIFI_FEATURE_WPA3_SAE;
 import static android.net.wifi.WifiManager.WIFI_FEATURE_WPA3_SUITE_B;
@@ -49,28 +59,34 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyList;
 import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.argThat;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.net.DhcpInfo;
 import android.net.MacAddress;
+import android.net.wifi.WifiManager.CoexCallback;
 import android.net.wifi.WifiManager.LocalOnlyHotspotCallback;
 import android.net.wifi.WifiManager.LocalOnlyHotspotObserver;
 import android.net.wifi.WifiManager.LocalOnlyHotspotReservation;
@@ -80,10 +96,14 @@
 import android.net.wifi.WifiManager.OnWifiUsabilityStatsListener;
 import android.net.wifi.WifiManager.ScanResultsCallback;
 import android.net.wifi.WifiManager.SoftApCallback;
+import android.net.wifi.WifiManager.SubsystemRestartTrackingCallback;
 import android.net.wifi.WifiManager.SuggestionConnectionStatusListener;
+import android.net.wifi.WifiManager.SuggestionUserApprovalStatusListener;
 import android.net.wifi.WifiManager.TrafficStateCallback;
 import android.net.wifi.WifiManager.WifiConnectedNetworkScorer;
-import android.os.Binder;
+import android.net.wifi.WifiUsabilityStatsEntry.ContentionTimeStats;
+import android.net.wifi.WifiUsabilityStatsEntry.RadioStats;
+import android.net.wifi.WifiUsabilityStatsEntry.RateStats;
 import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
@@ -95,15 +115,18 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.modules.utils.HandlerExecutor;
+import com.android.modules.utils.build.SdkLevel;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -124,8 +147,18 @@
     private static final String TEST_FEATURE_ID = "TestFeature";
     private static final String TEST_COUNTRY_CODE = "US";
     private static final String[] TEST_MAC_ADDRESSES = {"da:a1:19:0:0:0"};
-    private static final int TEST_AP_FREQUENCY = 2412;
-    private static final int TEST_AP_BANDWIDTH = SoftApInfo.CHANNEL_WIDTH_20MHZ;
+    private static final int TEST_SUB_ID = 3;
+    private static final String[] TEST_AP_INSTANCES = new String[] {"wlan1", "wlan2"};
+    private static final int[] TEST_AP_FREQS = new int[] {2412, 5220};
+    private static final int[] TEST_AP_BWS = new int[] {SoftApInfo.CHANNEL_WIDTH_20MHZ_NOHT,
+            SoftApInfo.CHANNEL_WIDTH_80MHZ};
+    private static final MacAddress[] TEST_AP_BSSIDS = new MacAddress[] {
+            MacAddress.fromString("22:33:44:55:66:77"),
+            MacAddress.fromString("aa:bb:cc:dd:ee:ff")};
+    private static final MacAddress[] TEST_AP_CLIENTS = new MacAddress[] {
+            MacAddress.fromString("22:33:44:aa:aa:77"),
+            MacAddress.fromString("aa:bb:cc:11:11:ff"),
+            MacAddress.fromString("22:bb:cc:11:aa:ff")};
 
     @Mock Context mContext;
     @Mock android.net.wifi.IWifiManager mWifiService;
@@ -136,20 +169,29 @@
     @Mock NetworkRequestMatchCallback mNetworkRequestMatchCallback;
     @Mock OnWifiUsabilityStatsListener mOnWifiUsabilityStatsListener;
     @Mock OnWifiActivityEnergyInfoListener mOnWifiActivityEnergyInfoListener;
-    @Mock SuggestionConnectionStatusListener mListener;
+    @Mock SuggestionConnectionStatusListener mSuggestionConnectionListener;
     @Mock Runnable mRunnable;
     @Mock Executor mExecutor;
     @Mock Executor mAnotherExecutor;
     @Mock ActivityManager mActivityManager;
     @Mock WifiConnectedNetworkScorer mWifiConnectedNetworkScorer;
+    @Mock SuggestionUserApprovalStatusListener mSuggestionUserApprovalStatusListener;
 
     private Handler mHandler;
     private TestLooper mLooper;
     private WifiManager mWifiManager;
     private WifiNetworkSuggestion mWifiNetworkSuggestion;
     private ScanResultsCallback mScanResultsCallback;
+    private CoexCallback mCoexCallback;
+    private SubsystemRestartTrackingCallback mRestartCallback;
+    private int mRestartCallbackMethodRun = 0; // 1: restarting, 2: restarted
     private WifiActivityEnergyInfo mWifiActivityEnergyInfo;
 
+    private HashMap<String, SoftApInfo> mTestSoftApInfoMap = new HashMap<>();
+    private HashMap<String, List<WifiClient>> mTestWifiClientsMap = new HashMap<>();
+    private SoftApInfo mTestApInfo1 = new SoftApInfo();
+    private SoftApInfo mTestApInfo2 = new SoftApInfo();
+
     /**
      * Util function to check public field which used for softap  in WifiConfiguration
      * same as the value in SoftApConfiguration.
@@ -194,6 +236,31 @@
                 .build();
     }
 
+    private void initTestInfoAndAddToTestMap(int numberOfInfos) {
+        if (numberOfInfos > 2) return;
+        for (int i = 0; i < numberOfInfos; i++) {
+            SoftApInfo info = mTestApInfo1;
+            if (i == 1) info = mTestApInfo2;
+            info.setFrequency(TEST_AP_FREQS[i]);
+            info.setBandwidth(TEST_AP_BWS[i]);
+            info.setBssid(TEST_AP_BSSIDS[i]);
+            info.setApInstanceIdentifier(TEST_AP_INSTANCES[i]);
+            mTestSoftApInfoMap.put(TEST_AP_INSTANCES[i], info);
+        }
+    }
+
+    private List<WifiClient> initWifiClientAndAddToTestMap(String targetInstance,
+            int numberOfClients, int startIdx) {
+        if (numberOfClients > 3) return null;
+        List<WifiClient> clients = new ArrayList<>();
+        for (int i = startIdx; i < startIdx + numberOfClients; i++) {
+            WifiClient client = new WifiClient(TEST_AP_CLIENTS[i], targetInstance);
+            clients.add(client);
+        }
+        mTestWifiClientsMap.put(targetInstance, clients);
+        return clients;
+    }
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -211,7 +278,181 @@
                 mRunnable.run();
             }
         };
+        if (SdkLevel.isAtLeastS()) {
+            mCoexCallback = new CoexCallback() {
+                @Override
+                public void onCoexUnsafeChannelsChanged(
+                        @NonNull List<CoexUnsafeChannel> unsafeChannels, int restrictions) {
+                    mRunnable.run();
+                }
+            };
+        }
+        mRestartCallback = new SubsystemRestartTrackingCallback() {
+            @Override
+            public void onSubsystemRestarting() {
+                mRestartCallbackMethodRun = 1;
+                mRunnable.run();
+            }
+
+            @Override
+            public void onSubsystemRestarted() {
+                mRestartCallbackMethodRun = 2;
+                mRunnable.run();
+            }
+        };
         mWifiActivityEnergyInfo = new WifiActivityEnergyInfo(0, 0, 0, 0, 0, 0);
+        mTestSoftApInfoMap.clear();
+        mTestWifiClientsMap.clear();
+    }
+
+    /**
+     * Check the call to setCoexUnsafeChannels calls WifiServiceImpl to setCoexUnsafeChannels with
+     * the provided CoexUnsafeChannels and restrictions bitmask.
+     */
+    @Test
+    public void testSetCoexUnsafeChannelsGoesToWifiServiceImpl() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        List<CoexUnsafeChannel> unsafeChannels = new ArrayList<>();
+        int restrictions = COEX_RESTRICTION_WIFI_DIRECT | COEX_RESTRICTION_SOFTAP
+                | COEX_RESTRICTION_WIFI_AWARE;
+
+        mWifiManager.setCoexUnsafeChannels(unsafeChannels, restrictions);
+
+        verify(mWifiService).setCoexUnsafeChannels(unsafeChannels, restrictions);
+    }
+
+    /**
+     * Verify an IllegalArgumentException if passed a null value for unsafeChannels.
+     */
+    @Test
+    public void testSetCoexUnsafeChannelsThrowsIllegalArgumentExceptionOnNullUnsafeChannels() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        try {
+            mWifiManager.setCoexUnsafeChannels(null, 0);
+            fail("expected IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    /**
+     * Verify an IllegalArgumentException is thrown if callback is not provided.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testRegisterCoexCallbackWithNullCallback() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        mWifiManager.registerCoexCallback(mExecutor, null);
+    }
+
+    /**
+     * Verify an IllegalArgumentException is thrown if executor is not provided.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testRegisterCoexCallbackWithNullExecutor() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        mWifiManager.registerCoexCallback(null, mCoexCallback);
+    }
+
+    /**
+     * Verify client provided callback is being called to the right callback.
+     */
+    @Test
+    public void testAddCoexCallbackAndReceiveEvent() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        ArgumentCaptor<ICoexCallback.Stub> callbackCaptor =
+                ArgumentCaptor.forClass(ICoexCallback.Stub.class);
+        mWifiManager.registerCoexCallback(new SynchronousExecutor(), mCoexCallback);
+        verify(mWifiService).registerCoexCallback(callbackCaptor.capture());
+        callbackCaptor.getValue().onCoexUnsafeChannelsChanged(Collections.emptyList(), 0);
+        verify(mRunnable).run();
+    }
+
+    /**
+     * Verify client provided callback is being called to the right executor.
+     */
+    @Test
+    public void testRegisterCoexCallbackWithTheTargetExecutor() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        ArgumentCaptor<ICoexCallback.Stub> callbackCaptor =
+                ArgumentCaptor.forClass(ICoexCallback.Stub.class);
+        mWifiManager.registerCoexCallback(mExecutor, mCoexCallback);
+        verify(mWifiService).registerCoexCallback(callbackCaptor.capture());
+        mWifiManager.registerCoexCallback(mAnotherExecutor, mCoexCallback);
+        callbackCaptor.getValue().onCoexUnsafeChannelsChanged(Collections.emptyList(), 0);
+        verify(mExecutor, never()).execute(any(Runnable.class));
+        verify(mAnotherExecutor).execute(any(Runnable.class));
+    }
+
+    /**
+     * Verify client register unregister then register again, to ensure callback still works.
+     */
+    @Test
+    public void testRegisterUnregisterThenRegisterAgainWithCoexCallback() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        ArgumentCaptor<ICoexCallback.Stub> callbackCaptor =
+                ArgumentCaptor.forClass(ICoexCallback.Stub.class);
+        mWifiManager.registerCoexCallback(new SynchronousExecutor(), mCoexCallback);
+        verify(mWifiService).registerCoexCallback(callbackCaptor.capture());
+        mWifiManager.unregisterCoexCallback(mCoexCallback);
+        callbackCaptor.getValue().onCoexUnsafeChannelsChanged(Collections.emptyList(), 0);
+        verify(mRunnable, never()).run();
+        mWifiManager.registerCoexCallback(new SynchronousExecutor(), mCoexCallback);
+        callbackCaptor.getValue().onCoexUnsafeChannelsChanged(Collections.emptyList(), 0);
+        verify(mRunnable).run();
+    }
+
+    /**
+     * Verify client unregisterCoexCallback.
+     */
+    @Test
+    public void testUnregisterCoexCallback() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        mWifiManager.unregisterCoexCallback(mCoexCallback);
+        verify(mWifiService).unregisterCoexCallback(any());
+    }
+
+    /**
+     * Verify client unregisterCoexCallback with null callback will cause an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testUnregisterCoexCallbackWithNullCallback() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        mWifiManager.unregisterCoexCallback(null);
+    }
+
+    /**
+     * Verify that call is passed to binder.
+     */
+    @Test
+    public void testRestartWifiSubsystem() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        mWifiManager.restartWifiSubsystem();
+        verify(mWifiService).restartWifiSubsystem();
+    }
+
+    /**
+     * Verify that can register a subsystem restart tracking callback and that calls are passed
+     * through when registered and blocked once unregistered.
+     */
+    @Test
+    public void testRegisterSubsystemRestartTrackingCallback() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        mRestartCallbackMethodRun = 0; // none
+        ArgumentCaptor<ISubsystemRestartCallback.Stub> callbackCaptor =
+                ArgumentCaptor.forClass(ISubsystemRestartCallback.Stub.class);
+        mWifiManager.registerSubsystemRestartTrackingCallback(new SynchronousExecutor(),
+                mRestartCallback);
+        verify(mWifiService).registerSubsystemRestartCallback(callbackCaptor.capture());
+        mWifiManager.unregisterSubsystemRestartTrackingCallback(mRestartCallback);
+        verify(mWifiService).unregisterSubsystemRestartCallback(callbackCaptor.capture());
+        callbackCaptor.getValue().onSubsystemRestarting();
+        verify(mRunnable, never()).run();
+        mWifiManager.registerSubsystemRestartTrackingCallback(new SynchronousExecutor(),
+                mRestartCallback);
+        callbackCaptor.getValue().onSubsystemRestarting();
+        assertEquals(mRestartCallbackMethodRun, 1); // restarting
+        callbackCaptor.getValue().onSubsystemRestarted();
+        verify(mRunnable, times(2)).run();
+        assertEquals(mRestartCallbackMethodRun, 2); // restarted
     }
 
     /**
@@ -220,10 +461,10 @@
      */
     @Test
     public void testStartSoftApCallsServiceWithWifiConfig() throws Exception {
-        when(mWifiService.startSoftAp(eq(mApConfig))).thenReturn(true);
+        when(mWifiService.startSoftAp(mApConfig, TEST_PACKAGE_NAME)).thenReturn(true);
         assertTrue(mWifiManager.startSoftAp(mApConfig));
 
-        when(mWifiService.startSoftAp(eq(mApConfig))).thenReturn(false);
+        when(mWifiService.startSoftAp(mApConfig, TEST_PACKAGE_NAME)).thenReturn(false);
         assertFalse(mWifiManager.startSoftAp(mApConfig));
     }
 
@@ -233,10 +474,10 @@
      */
     @Test
     public void testStartSoftApCallsServiceWithNullConfig() throws Exception {
-        when(mWifiService.startSoftAp(eq(null))).thenReturn(true);
+        when(mWifiService.startSoftAp(null, TEST_PACKAGE_NAME)).thenReturn(true);
         assertTrue(mWifiManager.startSoftAp(null));
 
-        when(mWifiService.startSoftAp(eq(null))).thenReturn(false);
+        when(mWifiService.startSoftAp(null, TEST_PACKAGE_NAME)).thenReturn(false);
         assertFalse(mWifiManager.startSoftAp(null));
     }
 
@@ -259,10 +500,12 @@
     @Test
     public void testStartTetheredHotspotCallsServiceWithSoftApConfig() throws Exception {
         SoftApConfiguration softApConfig = generatorTestSoftApConfig();
-        when(mWifiService.startTetheredHotspot(eq(softApConfig))).thenReturn(true);
+        when(mWifiService.startTetheredHotspot(softApConfig, TEST_PACKAGE_NAME))
+                .thenReturn(true);
         assertTrue(mWifiManager.startTetheredHotspot(softApConfig));
 
-        when(mWifiService.startTetheredHotspot(eq(softApConfig))).thenReturn(false);
+        when(mWifiService.startTetheredHotspot(softApConfig, TEST_PACKAGE_NAME))
+                .thenReturn(false);
         assertFalse(mWifiManager.startTetheredHotspot(softApConfig));
     }
 
@@ -272,10 +515,10 @@
      */
     @Test
     public void testStartTetheredHotspotCallsServiceWithNullConfig() throws Exception {
-        when(mWifiService.startTetheredHotspot(eq(null))).thenReturn(true);
+        when(mWifiService.startTetheredHotspot(null, TEST_PACKAGE_NAME)).thenReturn(true);
         assertTrue(mWifiManager.startTetheredHotspot(null));
 
-        when(mWifiService.startTetheredHotspot(eq(null))).thenReturn(false);
+        when(mWifiService.startTetheredHotspot(null, TEST_PACKAGE_NAME)).thenReturn(false);
         assertFalse(mWifiManager.startTetheredHotspot(null));
     }
 
@@ -846,6 +1089,67 @@
     }
 
     /**
+     * Verify an IllegalArgumentException is thrown if a callback or executor is not provided.
+     */
+    @Test
+    public void testAddWifiVerboseLoggingStatusChangedListenerIllegalArguments() throws Exception {
+        try {
+            mWifiManager.addWifiVerboseLoggingStatusChangedListener(
+                    new HandlerExecutor(mHandler), null);
+            fail("expected IllegalArgumentException - null callback");
+        } catch (IllegalArgumentException expected) {
+        }
+        try {
+            WifiManager.WifiVerboseLoggingStatusChangedListener listener =
+                    new WifiManager.WifiVerboseLoggingStatusChangedListener() {
+                        @Override
+                        public void onWifiVerboseLoggingStatusChanged(boolean enabled) {
+
+                        }
+                    };
+            mWifiManager.addWifiVerboseLoggingStatusChangedListener(null, listener);
+            fail("expected IllegalArgumentException - null executor");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    /**
+     * Verify the call to addWifiVerboseLoggingStatusChangedListener and
+     * removeWifiVerboseLoggingStatusChangedListener goes to WifiServiceImpl.
+     */
+    @Test
+    public void testWifiVerboseLoggingStatusChangedListenerGoesToWifiServiceImpl()
+            throws Exception {
+        WifiManager.WifiVerboseLoggingStatusChangedListener listener =
+                new WifiManager.WifiVerboseLoggingStatusChangedListener() {
+                    @Override
+                    public void onWifiVerboseLoggingStatusChanged(boolean enabled) {
+
+                    }
+                };
+        mWifiManager.addWifiVerboseLoggingStatusChangedListener(new HandlerExecutor(mHandler),
+                listener);
+        verify(mWifiService).addWifiVerboseLoggingStatusChangedListener(
+                any(IWifiVerboseLoggingStatusChangedListener.Stub.class));
+        mWifiManager.removeWifiVerboseLoggingStatusChangedListener(listener);
+        verify(mWifiService).removeWifiVerboseLoggingStatusChangedListener(
+                any(IWifiVerboseLoggingStatusChangedListener.Stub.class));
+    }
+
+    /**
+     * Verify an IllegalArgumentException is thrown if a callback is not provided.
+     */
+    @Test
+    public void testRemoveWifiVerboseLoggingStatusChangedListenerIllegalArguments()
+            throws Exception {
+        try {
+            mWifiManager.removeWifiVerboseLoggingStatusChangedListener(null);
+            fail("expected IllegalArgumentException - null callback");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    /**
      * Verify an IllegalArgumentException is thrown if callback is not provided.
      */
     @Test
@@ -887,8 +1191,7 @@
     @Test
     public void registerSoftApCallbackCallGoesToWifiServiceImpl() throws Exception {
         mWifiManager.registerSoftApCallback(new HandlerExecutor(mHandler), mSoftApCallback);
-        verify(mWifiService).registerSoftApCallback(any(IBinder.class),
-                any(ISoftApCallback.Stub.class), anyInt());
+        verify(mWifiService).registerSoftApCallback(any(ISoftApCallback.Stub.class));
     }
 
     /**
@@ -896,13 +1199,13 @@
      */
     @Test
     public void unregisterSoftApCallbackCallGoesToWifiServiceImpl() throws Exception {
-        ArgumentCaptor<Integer> callbackIdentifier = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<ISoftApCallback.Stub> callbackCaptor =
+                ArgumentCaptor.forClass(ISoftApCallback.Stub.class);
         mWifiManager.registerSoftApCallback(new HandlerExecutor(mHandler), mSoftApCallback);
-        verify(mWifiService).registerSoftApCallback(any(IBinder.class),
-                any(ISoftApCallback.Stub.class), callbackIdentifier.capture());
+        verify(mWifiService).registerSoftApCallback(callbackCaptor.capture());
 
         mWifiManager.unregisterSoftApCallback(mSoftApCallback);
-        verify(mWifiService).unregisterSoftApCallback(eq((int) callbackIdentifier.getValue()));
+        verify(mWifiService).unregisterSoftApCallback(callbackCaptor.getValue());
     }
 
     /*
@@ -913,8 +1216,7 @@
         ArgumentCaptor<ISoftApCallback.Stub> callbackCaptor =
                 ArgumentCaptor.forClass(ISoftApCallback.Stub.class);
         mWifiManager.registerSoftApCallback(new HandlerExecutor(mHandler), mSoftApCallback);
-        verify(mWifiService).registerSoftApCallback(any(IBinder.class), callbackCaptor.capture(),
-                anyInt());
+        verify(mWifiService).registerSoftApCallback(callbackCaptor.capture());
 
         callbackCaptor.getValue().onStateChanged(WIFI_AP_STATE_ENABLED, 0);
         mLooper.dispatchAll();
@@ -922,6 +1224,104 @@
     }
 
     /*
+     * Verify client-provided callback is being called through callback proxy when registration.
+     */
+    @Test
+    public void softApCallbackProxyCallsOnRegistrationAndApStartedWithClientsConnected()
+            throws Exception {
+        ArgumentCaptor<ISoftApCallback.Stub> callbackCaptor =
+                ArgumentCaptor.forClass(ISoftApCallback.Stub.class);
+        mWifiManager.registerSoftApCallback(new HandlerExecutor(mHandler), mSoftApCallback);
+        verify(mWifiService).registerSoftApCallback(callbackCaptor.capture());
+        // Prepare test info and clients
+        initTestInfoAndAddToTestMap(1);
+        List<WifiClient> clientList = initWifiClientAndAddToTestMap(TEST_AP_INSTANCES[0], 1, 0);
+        // Trigger callback with registration in AP started and clients connected.
+        callbackCaptor.getValue().onConnectedClientsOrInfoChanged(
+                (Map<String, SoftApInfo>) mTestSoftApInfoMap.clone(),
+                (Map<String, List<WifiClient>>) mTestWifiClientsMap.clone(), false, true);
+
+        mLooper.dispatchAll();
+        verify(mSoftApCallback).onConnectedClientsChanged(clientList);
+        verify(mSoftApCallback).onConnectedClientsChanged(mTestApInfo1, clientList);
+        verify(mSoftApCallback).onInfoChanged(mTestApInfo1);
+        verify(mSoftApCallback).onInfoChanged(Mockito.argThat((List<SoftApInfo> infos) ->
+                        infos.contains(mTestApInfo1)));
+    }
+
+
+    /*
+     * Verify client-provided callback is being called through callback proxy
+     */
+    @Test
+    public void softApCallbackProxyCallsOnConnectedClientsChangedEvenIfNoInfoChanged()
+            throws Exception {
+        ArgumentCaptor<ISoftApCallback.Stub> callbackCaptor =
+                ArgumentCaptor.forClass(ISoftApCallback.Stub.class);
+        mWifiManager.registerSoftApCallback(new HandlerExecutor(mHandler), mSoftApCallback);
+        verify(mWifiService).registerSoftApCallback(callbackCaptor.capture());
+        List<WifiClient> clientList;
+        // Verify the register callback in disable state.
+        callbackCaptor.getValue().onConnectedClientsOrInfoChanged(
+                (Map<String, SoftApInfo>) mTestSoftApInfoMap.clone(),
+                (Map<String, List<WifiClient>>) mTestWifiClientsMap.clone(), false, true);
+        mLooper.dispatchAll();
+        verify(mSoftApCallback).onConnectedClientsChanged(new ArrayList<WifiClient>());
+        verify(mSoftApCallback, never()).onConnectedClientsChanged(any(), any());
+        verify(mSoftApCallback).onInfoChanged(new SoftApInfo());
+        verify(mSoftApCallback).onInfoChanged(new ArrayList<SoftApInfo>());
+        // After verify, reset mSoftApCallback for nex test
+        reset(mSoftApCallback);
+
+        // Test first client connected
+        clientList = initWifiClientAndAddToTestMap(TEST_AP_INSTANCES[0], 1, 0);
+        callbackCaptor.getValue().onConnectedClientsOrInfoChanged(
+                (Map<String, SoftApInfo>) mTestSoftApInfoMap.clone(),
+                (Map<String, List<WifiClient>>) mTestWifiClientsMap.clone(), false, false);
+        mLooper.dispatchAll();
+        // checked NO any infoChanged, includes InfoChanged(SoftApInfo)
+        // and InfoChanged(List<SoftApInfo>)
+        verify(mSoftApCallback, never()).onInfoChanged(any(SoftApInfo.class));
+        verify(mSoftApCallback, never()).onInfoChanged(any(List.class));
+        verify(mSoftApCallback, never()).onConnectedClientsChanged(mTestApInfo1, clientList);
+        verify(mSoftApCallback).onConnectedClientsChanged(clientList);
+        // After verify, reset mSoftApCallback for nex test
+        reset(mSoftApCallback);
+
+        // Test second client connected
+        mTestWifiClientsMap.clear();
+        clientList = initWifiClientAndAddToTestMap(TEST_AP_INSTANCES[0], 2, 0);
+        callbackCaptor.getValue().onConnectedClientsOrInfoChanged(
+                (Map<String, SoftApInfo>) mTestSoftApInfoMap.clone(),
+                (Map<String, List<WifiClient>>) mTestWifiClientsMap.clone(), false, false);
+        mLooper.dispatchAll();
+        // checked NO any infoChanged, includes InfoChanged(SoftApInfo)
+        // and InfoChanged(List<SoftApInfo>)
+        verify(mSoftApCallback, never()).onInfoChanged(any(SoftApInfo.class));
+        verify(mSoftApCallback, never()).onInfoChanged(any(List.class));
+        verify(mSoftApCallback, never()).onConnectedClientsChanged(mTestApInfo1, clientList);
+        verify(mSoftApCallback).onConnectedClientsChanged(clientList);
+        // After verify, reset mSoftApCallback for nex test
+        reset(mSoftApCallback);
+
+        // Test second client disconnect
+        mTestWifiClientsMap.clear();
+        clientList = initWifiClientAndAddToTestMap(TEST_AP_INSTANCES[0], 1, 0);
+        callbackCaptor.getValue().onConnectedClientsOrInfoChanged(
+                (Map<String, SoftApInfo>) mTestSoftApInfoMap.clone(),
+                (Map<String, List<WifiClient>>) mTestWifiClientsMap.clone(), false, false);
+        mLooper.dispatchAll();
+        // checked NO any infoChanged, includes InfoChanged(SoftApInfo)
+        // and InfoChanged(List<SoftApInfo>)
+        verify(mSoftApCallback, never()).onInfoChanged(any(SoftApInfo.class));
+        verify(mSoftApCallback, never()).onInfoChanged(any(List.class));
+        verify(mSoftApCallback, never()).onConnectedClientsChanged(mTestApInfo1, clientList);
+        verify(mSoftApCallback).onConnectedClientsChanged(clientList);
+        // After verify, reset mSoftApCallback for nex test
+        reset(mSoftApCallback);
+    }
+
+    /*
      * Verify client-provided callback is being called through callback proxy
      */
     @Test
@@ -929,13 +1329,150 @@
         ArgumentCaptor<ISoftApCallback.Stub> callbackCaptor =
                 ArgumentCaptor.forClass(ISoftApCallback.Stub.class);
         mWifiManager.registerSoftApCallback(new HandlerExecutor(mHandler), mSoftApCallback);
-        verify(mWifiService).registerSoftApCallback(any(IBinder.class), callbackCaptor.capture(),
-                anyInt());
-
-        final List<WifiClient> testClients = new ArrayList();
-        callbackCaptor.getValue().onConnectedClientsChanged(testClients);
+        verify(mWifiService).registerSoftApCallback(callbackCaptor.capture());
+        List<WifiClient> clientList;
+        // Verify the register callback in disable state.
+        callbackCaptor.getValue().onConnectedClientsOrInfoChanged(
+                (Map<String, SoftApInfo>) mTestSoftApInfoMap.clone(),
+                (Map<String, List<WifiClient>>) mTestWifiClientsMap.clone(), false, true);
         mLooper.dispatchAll();
-        verify(mSoftApCallback).onConnectedClientsChanged(testClients);
+        verify(mSoftApCallback).onConnectedClientsChanged(new ArrayList<WifiClient>());
+        verify(mSoftApCallback, never()).onConnectedClientsChanged(any(), any());
+        verify(mSoftApCallback).onInfoChanged(new SoftApInfo());
+        verify(mSoftApCallback).onInfoChanged(new ArrayList<SoftApInfo>());
+        // After verify, reset mSoftApCallback for nex test
+        reset(mSoftApCallback);
+
+        // Single AP mode Test
+        // Test info update
+        initTestInfoAndAddToTestMap(1);
+        callbackCaptor.getValue().onConnectedClientsOrInfoChanged(
+                (Map<String, SoftApInfo>) mTestSoftApInfoMap.clone(),
+                (Map<String, List<WifiClient>>) mTestWifiClientsMap.clone(), false, false);
+        mLooper.dispatchAll();
+        verify(mSoftApCallback).onInfoChanged(mTestApInfo1);
+        verify(mSoftApCallback).onInfoChanged(Mockito.argThat((List<SoftApInfo> infos) ->
+                        infos.contains(mTestApInfo1)));
+        verify(mSoftApCallback, never()).onConnectedClientsChanged(any());
+        verify(mSoftApCallback, never()).onConnectedClientsChanged(any(), any());
+        // After verify, reset mSoftApCallback for nex test
+        reset(mSoftApCallback);
+
+        // Test first client connected
+        clientList = initWifiClientAndAddToTestMap(TEST_AP_INSTANCES[0], 1, 0);
+        callbackCaptor.getValue().onConnectedClientsOrInfoChanged(
+                (Map<String, SoftApInfo>) mTestSoftApInfoMap.clone(),
+                (Map<String, List<WifiClient>>) mTestWifiClientsMap.clone(), false, false);
+        mLooper.dispatchAll();
+        // checked NO any infoChanged, includes InfoChanged(SoftApInfo)
+        // and InfoChanged(List<SoftApInfo>)
+        verify(mSoftApCallback, never()).onInfoChanged(any(SoftApInfo.class));
+        verify(mSoftApCallback, never()).onInfoChanged(any(List.class));
+        verify(mSoftApCallback).onConnectedClientsChanged(mTestApInfo1, clientList);
+        verify(mSoftApCallback).onConnectedClientsChanged(clientList);
+        // After verify, reset mSoftApCallback for nex test
+        reset(mSoftApCallback);
+
+        // Test second client connected
+        mTestWifiClientsMap.clear();
+        clientList = initWifiClientAndAddToTestMap(TEST_AP_INSTANCES[0], 2, 0);
+        callbackCaptor.getValue().onConnectedClientsOrInfoChanged(
+                (Map<String, SoftApInfo>) mTestSoftApInfoMap.clone(),
+                (Map<String, List<WifiClient>>) mTestWifiClientsMap.clone(), false, false);
+        mLooper.dispatchAll();
+        // checked NO any infoChanged, includes InfoChanged(SoftApInfo)
+        // and InfoChanged(List<SoftApInfo>)
+        verify(mSoftApCallback, never()).onInfoChanged(any(SoftApInfo.class));
+        verify(mSoftApCallback, never()).onInfoChanged(any(List.class));
+        verify(mSoftApCallback).onConnectedClientsChanged(mTestApInfo1, clientList);
+        verify(mSoftApCallback).onConnectedClientsChanged(clientList);
+        // After verify, reset mSoftApCallback for nex test
+        reset(mSoftApCallback);
+
+        // Test second client disconnect
+        mTestWifiClientsMap.clear();
+        clientList = initWifiClientAndAddToTestMap(TEST_AP_INSTANCES[0], 1, 0);
+        callbackCaptor.getValue().onConnectedClientsOrInfoChanged(
+                (Map<String, SoftApInfo>) mTestSoftApInfoMap.clone(),
+                (Map<String, List<WifiClient>>) mTestWifiClientsMap.clone(), false, false);
+        mLooper.dispatchAll();
+        // checked NO any infoChanged, includes InfoChanged(SoftApInfo)
+        // and InfoChanged(List<SoftApInfo>)
+        verify(mSoftApCallback, never()).onInfoChanged(any(SoftApInfo.class));
+        verify(mSoftApCallback, never()).onInfoChanged(any(List.class));
+        verify(mSoftApCallback).onConnectedClientsChanged(mTestApInfo1, clientList);
+        verify(mSoftApCallback).onConnectedClientsChanged(clientList);
+        // After verify, reset mSoftApCallback for nex test
+        reset(mSoftApCallback);
+
+        // Test bridged mode case
+        mTestSoftApInfoMap.clear();
+        initTestInfoAndAddToTestMap(2);
+        callbackCaptor.getValue().onConnectedClientsOrInfoChanged(
+                (Map<String, SoftApInfo>) mTestSoftApInfoMap.clone(),
+                (Map<String, List<WifiClient>>) mTestWifiClientsMap.clone(), true, false);
+        mLooper.dispatchAll();
+        verify(mSoftApCallback, never()).onInfoChanged(any(SoftApInfo.class));
+        verify(mSoftApCallback).onInfoChanged(Mockito.argThat((List<SoftApInfo> infos) ->
+                  infos.contains(mTestApInfo1) && infos.contains(mTestApInfo2)
+                  ));
+        verify(mSoftApCallback, never()).onConnectedClientsChanged(any());
+        verify(mSoftApCallback, never()).onConnectedClientsChanged(any(), any());
+        // After verify, reset mSoftApCallback for nex test
+        reset(mSoftApCallback);
+
+        // Test client connect to second instance
+        List<WifiClient> clientListOnSecond =
+                initWifiClientAndAddToTestMap(TEST_AP_INSTANCES[1], 1, 2); // client3 to wlan2
+        List<WifiClient> totalList = new ArrayList<>();
+        totalList.addAll(clientList);
+        totalList.addAll(clientListOnSecond);
+        callbackCaptor.getValue().onConnectedClientsOrInfoChanged(
+                (Map<String, SoftApInfo>) mTestSoftApInfoMap.clone(),
+                (Map<String, List<WifiClient>>) mTestWifiClientsMap.clone(), true, false);
+        mLooper.dispatchAll();
+        // checked NO any infoChanged, includes InfoChanged(SoftApInfo)
+        // and InfoChanged(List<SoftApInfo>)
+        verify(mSoftApCallback, never()).onInfoChanged(any(SoftApInfo.class));
+        verify(mSoftApCallback, never()).onInfoChanged(any(List.class));
+        verify(mSoftApCallback).onConnectedClientsChanged(mTestApInfo2, clientListOnSecond);
+        verify(mSoftApCallback).onConnectedClientsChanged(totalList);
+        // After verify, reset mSoftApCallback for nex test
+        reset(mSoftApCallback);
+
+        // Test shutdown on second instance
+        mTestSoftApInfoMap.clear();
+        mTestWifiClientsMap.clear();
+        initTestInfoAndAddToTestMap(1);
+        clientList = initWifiClientAndAddToTestMap(TEST_AP_INSTANCES[0], 1, 0);
+        callbackCaptor.getValue().onConnectedClientsOrInfoChanged(
+                (Map<String, SoftApInfo>) mTestSoftApInfoMap.clone(),
+                (Map<String, List<WifiClient>>) mTestWifiClientsMap.clone(), true, false);
+        mLooper.dispatchAll();
+        verify(mSoftApCallback, never()).onInfoChanged(any(SoftApInfo.class));
+        verify(mSoftApCallback).onInfoChanged(Mockito.argThat((List<SoftApInfo> infos) ->
+                        infos.contains(mTestApInfo1)));
+        // second instance have client connected before, thus it should send empty list
+        verify(mSoftApCallback).onConnectedClientsChanged(
+                mTestApInfo2, new ArrayList<WifiClient>());
+        verify(mSoftApCallback).onConnectedClientsChanged(clientList);
+        // After verify, reset mSoftApCallback for nex test
+        reset(mSoftApCallback);
+
+        // Test bridged mode disable when client connected
+        mTestSoftApInfoMap.clear();
+        mTestWifiClientsMap.clear();
+        callbackCaptor.getValue().onConnectedClientsOrInfoChanged(
+                (Map<String, SoftApInfo>) mTestSoftApInfoMap.clone(),
+                (Map<String, List<WifiClient>>) mTestWifiClientsMap.clone(), true, false);
+        mLooper.dispatchAll();
+        verify(mSoftApCallback, never()).onInfoChanged(any(SoftApInfo.class));
+        verify(mSoftApCallback).onInfoChanged(new ArrayList<SoftApInfo>());
+        verify(mSoftApCallback).onConnectedClientsChanged(new ArrayList<WifiClient>());
+        verify(mSoftApCallback).onConnectedClientsChanged(
+                mTestApInfo1, new ArrayList<WifiClient>());
+        // After verify, reset mSoftApCallback for nex test
+        reset(mSoftApCallback);
     }
 
 
@@ -944,20 +1481,137 @@
      */
     @Test
     public void softApCallbackProxyCallsOnSoftApInfoChanged() throws Exception {
-        SoftApInfo testSoftApInfo = new SoftApInfo();
-        testSoftApInfo.setFrequency(TEST_AP_FREQUENCY);
-        testSoftApInfo.setBandwidth(TEST_AP_BANDWIDTH);
         ArgumentCaptor<ISoftApCallback.Stub> callbackCaptor =
                 ArgumentCaptor.forClass(ISoftApCallback.Stub.class);
         mWifiManager.registerSoftApCallback(new HandlerExecutor(mHandler), mSoftApCallback);
-        verify(mWifiService).registerSoftApCallback(any(IBinder.class), callbackCaptor.capture(),
-                anyInt());
-
-        callbackCaptor.getValue().onInfoChanged(testSoftApInfo);
+        verify(mWifiService).registerSoftApCallback(callbackCaptor.capture());
+        // Verify the register callback in disable state.
+        callbackCaptor.getValue().onConnectedClientsOrInfoChanged(
+                (Map<String, SoftApInfo>) mTestSoftApInfoMap.clone(),
+                (Map<String, List<WifiClient>>) mTestWifiClientsMap.clone(), false, true);
         mLooper.dispatchAll();
-        verify(mSoftApCallback).onInfoChanged(testSoftApInfo);
+        verify(mSoftApCallback).onConnectedClientsChanged(new ArrayList<WifiClient>());
+        verify(mSoftApCallback, never()).onConnectedClientsChanged(any(), any());
+        verify(mSoftApCallback).onInfoChanged(new SoftApInfo());
+        verify(mSoftApCallback).onInfoChanged(new ArrayList<SoftApInfo>());
+        // After verify, reset mSoftApCallback for nex test
+        reset(mSoftApCallback);
+
+        // Single AP mode Test
+        // Test info update
+        initTestInfoAndAddToTestMap(1);
+        callbackCaptor.getValue().onConnectedClientsOrInfoChanged(
+                (Map<String, SoftApInfo>) mTestSoftApInfoMap.clone(),
+                (Map<String, List<WifiClient>>) mTestWifiClientsMap.clone(), false, false);
+        mLooper.dispatchAll();
+        verify(mSoftApCallback).onInfoChanged(mTestApInfo1);
+        verify(mSoftApCallback).onInfoChanged(Mockito.argThat((List<SoftApInfo> infos) ->
+                        infos.contains(mTestApInfo1)));
+        verify(mSoftApCallback, never()).onConnectedClientsChanged(any());
+        verify(mSoftApCallback, never()).onConnectedClientsChanged(any(), any());
+        // After verify, reset mSoftApCallback for nex test
+        reset(mSoftApCallback);
+
+        // Test info changed
+        SoftApInfo changedInfo = new SoftApInfo(mTestSoftApInfoMap.get(TEST_AP_INSTANCES[0]));
+        changedInfo.setFrequency(2422);
+        mTestSoftApInfoMap.put(TEST_AP_INSTANCES[0], changedInfo);
+        callbackCaptor.getValue().onConnectedClientsOrInfoChanged(
+                (Map<String, SoftApInfo>) mTestSoftApInfoMap.clone(),
+                (Map<String, List<WifiClient>>) mTestWifiClientsMap.clone(), false, false);
+        mLooper.dispatchAll();
+        verify(mSoftApCallback).onInfoChanged(changedInfo);
+        verify(mSoftApCallback).onInfoChanged(Mockito.argThat((List<SoftApInfo> infos) ->
+                        infos.contains(changedInfo)));
+        verify(mSoftApCallback, never()).onConnectedClientsChanged(any());
+        verify(mSoftApCallback, never()).onConnectedClientsChanged(any(), any());
+
+        // Test Stop, all of infos is empty
+        mTestSoftApInfoMap.clear();
+        callbackCaptor.getValue().onConnectedClientsOrInfoChanged(
+                (Map<String, SoftApInfo>) mTestSoftApInfoMap.clone(),
+                (Map<String, List<WifiClient>>) mTestWifiClientsMap.clone(), false, false);
+        mLooper.dispatchAll();
+        verify(mSoftApCallback).onInfoChanged(new SoftApInfo());
+        verify(mSoftApCallback).onInfoChanged(new ArrayList<SoftApInfo>());
+        verify(mSoftApCallback, never()).onConnectedClientsChanged(any());
+        verify(mSoftApCallback, never()).onConnectedClientsChanged(any(), any());
+        // After verify, reset mSoftApCallback for nex test
+        reset(mSoftApCallback);
     }
 
+    /*
+     * Verify client-provided callback is being called through callback proxy
+     */
+    @Test
+    public void softApCallbackProxyCallsOnSoftApInfoChangedInBridgedMode() throws Exception {
+        ArgumentCaptor<ISoftApCallback.Stub> callbackCaptor =
+                ArgumentCaptor.forClass(ISoftApCallback.Stub.class);
+        mWifiManager.registerSoftApCallback(new HandlerExecutor(mHandler), mSoftApCallback);
+        verify(mWifiService).registerSoftApCallback(callbackCaptor.capture());
+
+        // Test bridged mode case
+        initTestInfoAndAddToTestMap(2);
+        callbackCaptor.getValue().onConnectedClientsOrInfoChanged(
+                (Map<String, SoftApInfo>) mTestSoftApInfoMap.clone(),
+                (Map<String, List<WifiClient>>) mTestWifiClientsMap.clone(), true, false);
+        mLooper.dispatchAll();
+        verify(mSoftApCallback, never()).onInfoChanged(any(SoftApInfo.class));
+        verify(mSoftApCallback).onInfoChanged(Mockito.argThat((List<SoftApInfo> infos) ->
+                  infos.contains(mTestApInfo1) && infos.contains(mTestApInfo2)
+                  ));
+        verify(mSoftApCallback, never()).onConnectedClientsChanged(any());
+        verify(mSoftApCallback, never()).onConnectedClientsChanged(any(), any());
+        // After verify, reset mSoftApCallback for nex test
+        reset(mSoftApCallback);
+
+        // Test bridged mode case but an info changed
+        SoftApInfo changedInfoBridgedMode = new SoftApInfo(mTestSoftApInfoMap.get(
+                TEST_AP_INSTANCES[0]));
+        changedInfoBridgedMode.setFrequency(2422);
+        mTestSoftApInfoMap.put(TEST_AP_INSTANCES[0], changedInfoBridgedMode);
+        callbackCaptor.getValue().onConnectedClientsOrInfoChanged(
+                (Map<String, SoftApInfo>) mTestSoftApInfoMap.clone(),
+                (Map<String, List<WifiClient>>) mTestWifiClientsMap.clone(), true, false);
+        mLooper.dispatchAll();
+        verify(mSoftApCallback, never()).onInfoChanged(any(SoftApInfo.class));
+        verify(mSoftApCallback).onInfoChanged(Mockito.argThat((List<SoftApInfo> infos) ->
+                  infos.contains(changedInfoBridgedMode) && infos.contains(mTestApInfo2)
+                  ));
+        verify(mSoftApCallback, never()).onConnectedClientsChanged(any());
+        verify(mSoftApCallback, never()).onConnectedClientsChanged(any(), any());
+        // After verify, reset mSoftApCallback for nex test
+        reset(mSoftApCallback);
+
+        // Test bridged mode case but an instance shutdown
+        mTestSoftApInfoMap.clear();
+        initTestInfoAndAddToTestMap(1);
+        callbackCaptor.getValue().onConnectedClientsOrInfoChanged(
+                (Map<String, SoftApInfo>) mTestSoftApInfoMap.clone(),
+                (Map<String, List<WifiClient>>) mTestWifiClientsMap.clone(), true, false);
+        mLooper.dispatchAll();
+        verify(mSoftApCallback, never()).onInfoChanged(any(SoftApInfo.class));
+        verify(mSoftApCallback).onInfoChanged(Mockito.argThat((List<SoftApInfo> infos) ->
+                  infos.contains(mTestApInfo1)
+                  ));
+        verify(mSoftApCallback, never()).onConnectedClientsChanged(any());
+        verify(mSoftApCallback, never()).onConnectedClientsChanged(any(), any());
+        // After verify, reset mSoftApCallback for nex test
+        reset(mSoftApCallback);
+
+        // Test bridged mode disable case
+        mTestSoftApInfoMap.clear();
+        callbackCaptor.getValue().onConnectedClientsOrInfoChanged(
+                (Map<String, SoftApInfo>) mTestSoftApInfoMap.clone(),
+                (Map<String, List<WifiClient>>) mTestWifiClientsMap.clone(), true, false);
+        mLooper.dispatchAll();
+        verify(mSoftApCallback, never()).onInfoChanged(any(SoftApInfo.class));
+        verify(mSoftApCallback).onInfoChanged(new ArrayList<SoftApInfo>());
+        verify(mSoftApCallback, never()).onConnectedClientsChanged(any());
+        verify(mSoftApCallback, never()).onConnectedClientsChanged(any(), any());
+        // After verify, reset mSoftApCallback for nex test
+        reset(mSoftApCallback);
+    }
 
     /*
      * Verify client-provided callback is being called through callback proxy
@@ -969,8 +1623,7 @@
         ArgumentCaptor<ISoftApCallback.Stub> callbackCaptor =
                 ArgumentCaptor.forClass(ISoftApCallback.Stub.class);
         mWifiManager.registerSoftApCallback(new HandlerExecutor(mHandler), mSoftApCallback);
-        verify(mWifiService).registerSoftApCallback(any(IBinder.class), callbackCaptor.capture(),
-                anyInt());
+        verify(mWifiService).registerSoftApCallback(callbackCaptor.capture());
 
         callbackCaptor.getValue().onCapabilityChanged(testSoftApCapability);
         mLooper.dispatchAll();
@@ -982,12 +1635,12 @@
      */
     @Test
     public void softApCallbackProxyCallsOnBlockedClientConnecting() throws Exception {
-        WifiClient testWifiClient = new WifiClient(MacAddress.fromString("22:33:44:55:66:77"));
+        WifiClient testWifiClient = new WifiClient(MacAddress.fromString("22:33:44:55:66:77"),
+                TEST_AP_INSTANCES[0]);
         ArgumentCaptor<ISoftApCallback.Stub> callbackCaptor =
                 ArgumentCaptor.forClass(ISoftApCallback.Stub.class);
         mWifiManager.registerSoftApCallback(new HandlerExecutor(mHandler), mSoftApCallback);
-        verify(mWifiService).registerSoftApCallback(any(IBinder.class), callbackCaptor.capture(),
-                anyInt());
+        verify(mWifiService).registerSoftApCallback(callbackCaptor.capture());
 
         callbackCaptor.getValue().onBlockedClientConnecting(testWifiClient,
                 WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS);
@@ -1001,29 +1654,21 @@
      */
     @Test
     public void softApCallbackProxyCallsOnMultipleUpdates() throws Exception {
-        SoftApInfo testSoftApInfo = new SoftApInfo();
-        testSoftApInfo.setFrequency(TEST_AP_FREQUENCY);
-        testSoftApInfo.setBandwidth(TEST_AP_BANDWIDTH);
         SoftApCapability testSoftApCapability = new SoftApCapability(0);
         testSoftApCapability.setMaxSupportedClients(10);
         ArgumentCaptor<ISoftApCallback.Stub> callbackCaptor =
                 ArgumentCaptor.forClass(ISoftApCallback.Stub.class);
         mWifiManager.registerSoftApCallback(new HandlerExecutor(mHandler), mSoftApCallback);
-        verify(mWifiService).registerSoftApCallback(any(IBinder.class), callbackCaptor.capture(),
-                anyInt());
+        verify(mWifiService).registerSoftApCallback(callbackCaptor.capture());
 
         final List<WifiClient> testClients = new ArrayList();
         callbackCaptor.getValue().onStateChanged(WIFI_AP_STATE_ENABLING, 0);
-        callbackCaptor.getValue().onConnectedClientsChanged(testClients);
-        callbackCaptor.getValue().onInfoChanged(testSoftApInfo);
         callbackCaptor.getValue().onStateChanged(WIFI_AP_STATE_FAILED, SAP_START_FAILURE_GENERAL);
         callbackCaptor.getValue().onCapabilityChanged(testSoftApCapability);
 
 
         mLooper.dispatchAll();
         verify(mSoftApCallback).onStateChanged(WIFI_AP_STATE_ENABLING, 0);
-        verify(mSoftApCallback).onConnectedClientsChanged(testClients);
-        verify(mSoftApCallback).onInfoChanged(testSoftApInfo);
         verify(mSoftApCallback).onStateChanged(WIFI_AP_STATE_FAILED, SAP_START_FAILURE_GENERAL);
         verify(mSoftApCallback).onCapabilityChanged(testSoftApCapability);
     }
@@ -1038,8 +1683,7 @@
         TestLooper altLooper = new TestLooper();
         Handler altHandler = new Handler(altLooper.getLooper());
         mWifiManager.registerSoftApCallback(new HandlerExecutor(altHandler), mSoftApCallback);
-        verify(mWifiService).registerSoftApCallback(any(IBinder.class), callbackCaptor.capture(),
-                anyInt());
+        verify(mWifiService).registerSoftApCallback(callbackCaptor.capture());
 
         callbackCaptor.getValue().onStateChanged(WIFI_AP_STATE_ENABLED, 0);
         altLooper.dispatchAll();
@@ -1053,8 +1697,7 @@
     public void testCorrectLooperIsUsedForSoftApCallbackHandler() throws Exception {
         mWifiManager.registerSoftApCallback(new HandlerExecutor(mHandler), mSoftApCallback);
         mLooper.dispatchAll();
-        verify(mWifiService).registerSoftApCallback(any(IBinder.class),
-                any(ISoftApCallback.Stub.class), anyInt());
+        verify(mWifiService).registerSoftApCallback(any(ISoftApCallback.Stub.class));
         verify(mContext, never()).getMainLooper();
         verify(mContext, never()).getMainExecutor();
     }
@@ -1383,8 +2026,7 @@
                 ArgumentCaptor.forClass(ITrafficStateCallback.Stub.class);
         mWifiManager.registerTrafficStateCallback(
                 new HandlerExecutor(new Handler(mLooper.getLooper())), mTrafficStateCallback);
-        verify(mWifiService).registerTrafficStateCallback(
-                any(IBinder.class), callbackCaptor.capture(), anyInt());
+        verify(mWifiService).registerTrafficStateCallback(callbackCaptor.capture());
 
         assertEquals(0, mLooper.dispatchAll());
         callbackCaptor.getValue().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_INOUT);
@@ -1397,15 +2039,14 @@
      */
     @Test
     public void unregisterTrafficStateCallbackCallGoesToWifiServiceImpl() throws Exception {
-        ArgumentCaptor<Integer> callbackIdentifier = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<ITrafficStateCallback.Stub> callbackCaptor =
+                ArgumentCaptor.forClass(ITrafficStateCallback.Stub.class);
         mWifiManager.registerTrafficStateCallback(new HandlerExecutor(mHandler),
                 mTrafficStateCallback);
-        verify(mWifiService).registerTrafficStateCallback(any(IBinder.class),
-                any(ITrafficStateCallback.Stub.class), callbackIdentifier.capture());
+        verify(mWifiService).registerTrafficStateCallback(callbackCaptor.capture());
 
         mWifiManager.unregisterTrafficStateCallback(mTrafficStateCallback);
-        verify(mWifiService).unregisterTrafficStateCallback(
-                eq((int) callbackIdentifier.getValue()));
+        verify(mWifiService).unregisterTrafficStateCallback(callbackCaptor.getValue());
     }
 
     /*
@@ -1417,8 +2058,7 @@
                 ArgumentCaptor.forClass(ITrafficStateCallback.Stub.class);
         mWifiManager.registerTrafficStateCallback(new HandlerExecutor(mHandler),
                 mTrafficStateCallback);
-        verify(mWifiService).registerTrafficStateCallback(
-                any(IBinder.class), callbackCaptor.capture(), anyInt());
+        verify(mWifiService).registerTrafficStateCallback(callbackCaptor.capture());
 
         InOrder inOrder = inOrder(mTrafficStateCallback);
 
@@ -1448,8 +2088,7 @@
                 mTrafficStateCallback);
         verify(mContext, never()).getMainLooper();
         verify(mContext, never()).getMainExecutor();
-        verify(mWifiService).registerTrafficStateCallback(
-                any(IBinder.class), callbackCaptor.capture(), anyInt());
+        verify(mWifiService).registerTrafficStateCallback(callbackCaptor.capture());
 
         assertEquals(0, altLooper.dispatchAll());
         callbackCaptor.getValue().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_INOUT);
@@ -1468,8 +2107,7 @@
         mWifiManager.registerNetworkRequestMatchCallback(
                 new HandlerExecutor(new Handler(mLooper.getLooper())),
                 mNetworkRequestMatchCallback);
-        verify(mWifiService).registerNetworkRequestMatchCallback(
-                any(IBinder.class), callbackCaptor.capture(), anyInt());
+        verify(mWifiService).registerNetworkRequestMatchCallback(callbackCaptor.capture());
 
         INetworkRequestUserSelectionCallback iUserSelectionCallback =
                 mock(INetworkRequestUserSelectionCallback.class);
@@ -1500,16 +2138,14 @@
      */
     @Test
     public void unregisterNetworkRequestMatchCallbackCallGoesToWifiServiceImpl() throws Exception {
-        ArgumentCaptor<Integer> callbackIdentifier = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<INetworkRequestMatchCallback.Stub> callbackCaptor =
+                ArgumentCaptor.forClass(INetworkRequestMatchCallback.Stub.class);
         mWifiManager.registerNetworkRequestMatchCallback(new HandlerExecutor(mHandler),
                 mNetworkRequestMatchCallback);
-        verify(mWifiService).registerNetworkRequestMatchCallback(
-                any(IBinder.class), any(INetworkRequestMatchCallback.class),
-                callbackIdentifier.capture());
+        verify(mWifiService).registerNetworkRequestMatchCallback(callbackCaptor.capture());
 
         mWifiManager.unregisterNetworkRequestMatchCallback(mNetworkRequestMatchCallback);
-        verify(mWifiService).unregisterNetworkRequestMatchCallback(
-                eq((int) callbackIdentifier.getValue()));
+        verify(mWifiService).unregisterNetworkRequestMatchCallback(callbackCaptor.getValue());
     }
 
     /**
@@ -1524,8 +2160,7 @@
         mWifiManager.registerNetworkRequestMatchCallback(
                 new HandlerExecutor(new Handler(mLooper.getLooper())),
                 mNetworkRequestMatchCallback);
-        verify(mWifiService).registerNetworkRequestMatchCallback(
-                any(IBinder.class), callbackCaptor.capture(), anyInt());
+        verify(mWifiService).registerNetworkRequestMatchCallback(callbackCaptor.capture());
 
         INetworkRequestUserSelectionCallback iUserSelectionCallback =
                 mock(INetworkRequestUserSelectionCallback.class);
@@ -1629,14 +2264,62 @@
     }
 
     /**
+     * Verify the call to getCallerConfiguredNetworks goes to WifiServiceImpl.
+     */
+    @Test
+    public void testGetCallerConfiguredNetworks() throws Exception {
+        mWifiManager.getCallerConfiguredNetworks();
+        verify(mWifiService).getConfiguredNetworks(any(), any(), eq(true));
+    }
+
+    /**
+     * Verify the call to startRestrictingAutoJoinToSubscriptionId goes to WifiServiceImpl.
+     */
+    @Test
+    public void testStartRestrictAutoJoinToSubscriptionId() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        mWifiManager.startRestrictingAutoJoinToSubscriptionId(1);
+        verify(mWifiService).startRestrictingAutoJoinToSubscriptionId(1);
+    }
+
+    /**
+     * Verify the call to stopRestrictingAutoJoinToSubscriptionId goes to WifiServiceImpl.
+     */
+    @Test
+    public void testStopTemporarilyDisablingAllNonCarrierMergedWifi() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        mWifiManager.stopRestrictingAutoJoinToSubscriptionId();
+        verify(mWifiService).stopRestrictingAutoJoinToSubscriptionId();
+    }
+
+    /**
      * Verify the call to addOnWifiUsabilityStatsListener goes to WifiServiceImpl.
      */
     @Test
-    public void addOnWifiUsabilityStatsListeneroesToWifiServiceImpl() throws Exception {
+    public void addOnWifiUsabilityStatsListenerGoesToWifiServiceImpl() throws Exception {
         mExecutor = new SynchronousExecutor();
+        ArgumentCaptor<IOnWifiUsabilityStatsListener.Stub> callbackCaptor =
+                ArgumentCaptor.forClass(IOnWifiUsabilityStatsListener.Stub.class);
         mWifiManager.addOnWifiUsabilityStatsListener(mExecutor, mOnWifiUsabilityStatsListener);
-        verify(mWifiService).addOnWifiUsabilityStatsListener(any(IBinder.class),
-                any(IOnWifiUsabilityStatsListener.Stub.class), anyInt());
+        verify(mWifiService).addOnWifiUsabilityStatsListener(callbackCaptor.capture());
+        ContentionTimeStats[] contentionTimeStats = new ContentionTimeStats[4];
+        contentionTimeStats[0] = new ContentionTimeStats(1, 2, 3, 4);
+        contentionTimeStats[1] = new ContentionTimeStats(5, 6, 7, 8);
+        contentionTimeStats[2] = new ContentionTimeStats(9, 10, 11, 12);
+        contentionTimeStats[3] = new ContentionTimeStats(13, 14, 15, 16);
+        RateStats[] rateStats = new RateStats[2];
+        rateStats[0] = new RateStats(1, 3, 5, 7, 9, 11, 13, 15, 17);
+        rateStats[1] = new RateStats(2, 4, 6, 8, 10, 12, 14, 16, 18);
+        RadioStats[] radioStats = new RadioStats[2];
+        radioStats[0] = new RadioStats(0, 10, 11, 12, 13, 14, 15, 16, 17, 18);
+        radioStats[1] = new RadioStats(1, 20, 21, 22, 23, 24, 25, 26, 27, 28);
+        callbackCaptor.getValue().onWifiUsabilityStats(1, true,
+                new WifiUsabilityStatsEntry(System.currentTimeMillis(), -50, 100, 10, 0, 5, 5,
+                        100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 1, 100, 10,
+                        100, 27, contentionTimeStats, rateStats, radioStats, 101, true, true, true,
+                        0, 10, 10, true));
+        verify(mOnWifiUsabilityStatsListener).onWifiUsabilityStats(anyInt(), anyBoolean(),
+                any(WifiUsabilityStatsEntry.class));
     }
 
     /**
@@ -1644,15 +2327,14 @@
      */
     @Test
     public void removeOnWifiUsabilityListenerGoesToWifiServiceImpl() throws Exception {
-        ArgumentCaptor<Integer> listenerIdentifier = ArgumentCaptor.forClass(Integer.class);
         mExecutor = new SynchronousExecutor();
+        ArgumentCaptor<IOnWifiUsabilityStatsListener.Stub> callbackCaptor =
+                ArgumentCaptor.forClass(IOnWifiUsabilityStatsListener.Stub.class);
         mWifiManager.addOnWifiUsabilityStatsListener(mExecutor, mOnWifiUsabilityStatsListener);
-        verify(mWifiService).addOnWifiUsabilityStatsListener(any(IBinder.class),
-                any(IOnWifiUsabilityStatsListener.Stub.class), listenerIdentifier.capture());
+        verify(mWifiService).addOnWifiUsabilityStatsListener(callbackCaptor.capture());
 
         mWifiManager.removeOnWifiUsabilityStatsListener(mOnWifiUsabilityStatsListener);
-        verify(mWifiService).removeOnWifiUsabilityStatsListener(
-                eq((int) listenerIdentifier.getValue()));
+        verify(mWifiService).removeOnWifiUsabilityStatsListener(callbackCaptor.getValue());
     }
 
     /**
@@ -1708,6 +2390,58 @@
     }
 
     /**
+     * Test behavior of isEasyConnectEnrolleeResponderModeSupported
+     */
+    @Test
+    public void testIsEasyConnectEnrolleeResponderModeSupported() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        when(mWifiService.getSupportedFeatures())
+                .thenReturn(new Long(WIFI_FEATURE_DPP_ENROLLEE_RESPONDER));
+        assertTrue(mWifiManager.isEasyConnectEnrolleeResponderModeSupported());
+        when(mWifiService.getSupportedFeatures())
+                .thenReturn(new Long(~WIFI_FEATURE_DPP_ENROLLEE_RESPONDER));
+        assertFalse(mWifiManager.isEasyConnectEnrolleeResponderModeSupported());
+    }
+
+    /**
+     * Test behavior of isStaApConcurrencySupported
+     */
+    @Test
+    public void testIsStaApConcurrencyOpenSupported() throws Exception {
+        when(mWifiService.getSupportedFeatures())
+                .thenReturn(new Long(WIFI_FEATURE_AP_STA));
+        assertTrue(mWifiManager.isStaApConcurrencySupported());
+        when(mWifiService.getSupportedFeatures())
+                .thenReturn(new Long(~WIFI_FEATURE_AP_STA));
+        assertFalse(mWifiManager.isStaApConcurrencySupported());
+    }
+
+    /**
+     * Test behavior of isStaConcurrencySupported
+     */
+    @Test
+    public void testIsStaConcurrencySupported() throws Exception {
+        when(mWifiService.getSupportedFeatures()).thenReturn(0L);
+        assertFalse(mWifiManager.isStaConcurrencyForLocalOnlyConnectionsSupported());
+        assertFalse(mWifiManager.isMakeBeforeBreakWifiSwitchingSupported());
+        assertFalse(mWifiManager.isStaConcurrencyForRestrictedConnectionsSupported());
+
+        when(mWifiService.getSupportedFeatures())
+                .thenReturn(new Long(WIFI_FEATURE_ADDITIONAL_STA_LOCAL_ONLY));
+        assertTrue(mWifiManager.isStaConcurrencyForLocalOnlyConnectionsSupported());
+        assertFalse(mWifiManager.isMakeBeforeBreakWifiSwitchingSupported());
+        assertFalse(mWifiManager.isStaConcurrencyForRestrictedConnectionsSupported());
+
+        when(mWifiService.getSupportedFeatures())
+                .thenReturn(new Long(WIFI_FEATURE_ADDITIONAL_STA_MBB
+                        | WIFI_FEATURE_ADDITIONAL_STA_RESTRICTED));
+        assertFalse(mWifiManager.isStaConcurrencyForLocalOnlyConnectionsSupported());
+        assertTrue(mWifiManager.isMakeBeforeBreakWifiSwitchingSupported());
+        assertTrue(mWifiManager.isStaConcurrencyForRestrictedConnectionsSupported());
+    }
+
+    /**
      * Test behavior of {@link WifiManager#addNetwork(WifiConfiguration)}
      */
     @Test
@@ -1724,6 +2458,25 @@
     }
 
     /**
+     * Test {@link WifiManager#addNetworkPrivileged(WifiConfiguration)} goes to WifiService.
+     * Also verify that an IllegalArgumentException is thrown if the input is null.
+     */
+    @Test
+    public void testAddNetworkPrivileged() throws Exception {
+        WifiConfiguration configuration = new WifiConfiguration();
+        mWifiManager.addNetworkPrivileged(configuration);
+        verify(mWifiService).addOrUpdateNetworkPrivileged(configuration,
+                mContext.getOpPackageName());
+
+        // send a null config and verify an exception is thrown
+        try {
+            mWifiManager.addNetworkPrivileged(null);
+            fail("configuration is null - IllegalArgumentException is expected.");
+        } catch (IllegalArgumentException e) {
+        }
+    }
+
+    /**
      * Test behavior of {@link WifiManager#addNetwork(WifiConfiguration)}
      */
     @Test
@@ -1858,7 +2611,6 @@
         assertFalse(mWifiManager.isDeviceToDeviceRttSupported());
         assertFalse(mWifiManager.isDeviceToApRttSupported());
         assertFalse(mWifiManager.isPreferredNetworkOffloadSupported());
-        assertFalse(mWifiManager.isAdditionalStaSupported());
         assertFalse(mWifiManager.isTdlsSupported());
         assertFalse(mWifiManager.isOffChannelTdlsSupported());
         assertFalse(mWifiManager.isEnhancedPowerReportingSupported());
@@ -1929,6 +2681,16 @@
     }
 
     /**
+     * Test behavior of {@link WifiManager#is24GHzBandSupported()}
+     */
+    @Test
+    public void testIs24GHzBandSupported() throws Exception {
+        when(mWifiService.is24GHzBandSupported()).thenReturn(true);
+        assertTrue(mWifiManager.is24GHzBandSupported());
+        verify(mWifiService).is24GHzBandSupported();
+    }
+
+    /**
      * Test behavior of {@link WifiManager#is5GHzBandSupported()}
      */
     @Test
@@ -1949,6 +2711,18 @@
     }
 
     /**
+     * Test behavior of {@link WifiManager#is60GHzBandSupported()}
+     */
+    @Test
+    public void testIs60GHzBandSupported() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        when(mWifiService.is60GHzBandSupported()).thenReturn(true);
+        assertTrue(mWifiManager.is60GHzBandSupported());
+        verify(mWifiService).is60GHzBandSupported();
+    }
+
+    /**
      * Test behavior of {@link WifiManager#isWifiStandardSupported()}
      */
     @Test
@@ -1966,9 +2740,9 @@
     public void testGetDhcpInfo() throws Exception {
         DhcpInfo dhcpInfo = new DhcpInfo();
 
-        when(mWifiService.getDhcpInfo()).thenReturn(dhcpInfo);
+        when(mWifiService.getDhcpInfo(TEST_PACKAGE_NAME)).thenReturn(dhcpInfo);
         assertEquals(dhcpInfo, mWifiManager.getDhcpInfo());
-        verify(mWifiService).getDhcpInfo();
+        verify(mWifiService).getDhcpInfo(TEST_PACKAGE_NAME);
     }
 
     /**
@@ -1993,8 +2767,7 @@
 
         ArgumentCaptor<IActionListener> binderListenerCaptor =
                 ArgumentCaptor.forClass(IActionListener.class);
-        verify(mWifiService).connect(eq(null), eq(TEST_NETWORK_ID), any(Binder.class),
-                binderListenerCaptor.capture(), anyInt());
+        verify(mWifiService).connect(eq(null), eq(TEST_NETWORK_ID), binderListenerCaptor.capture());
         assertNotNull(binderListenerCaptor.getValue());
 
         // Trigger on success.
@@ -2014,8 +2787,7 @@
     @Test
     public void testConnectWithListenerHandleSecurityException() throws Exception {
         doThrow(new SecurityException()).when(mWifiService)
-                .connect(eq(null), anyInt(), any(IBinder.class),
-                        any(IActionListener.class), anyInt());
+                .connect(eq(null), anyInt(), any(IActionListener.class));
         ActionListener externalListener = mock(ActionListener.class);
         mWifiManager.connect(TEST_NETWORK_ID, externalListener);
 
@@ -2029,8 +2801,7 @@
     @Test
     public void testConnectWithListenerHandleRemoteException() throws Exception {
         doThrow(new RemoteException()).when(mWifiService)
-                .connect(eq(null), anyInt(), any(IBinder.class),
-                        any(IActionListener.class), anyInt());
+                .connect(eq(null), anyInt(), any(IActionListener.class));
         ActionListener externalListener = mock(ActionListener.class);
         mWifiManager.connect(TEST_NETWORK_ID, externalListener);
 
@@ -2046,8 +2817,7 @@
         WifiConfiguration configuration = new WifiConfiguration();
         mWifiManager.connect(configuration, null);
 
-        verify(mWifiService).connect(configuration, WifiConfiguration.INVALID_NETWORK_ID, null,
-                null, 0);
+        verify(mWifiService).connect(configuration, WifiConfiguration.INVALID_NETWORK_ID, null);
     }
 
     /**
@@ -2133,7 +2903,7 @@
      */
     @Test(expected = IllegalArgumentException.class)
     public void testAddSuggestionConnectionStatusListenerWithNullExecutor() {
-        mWifiManager.addSuggestionConnectionStatusListener(null, mListener);
+        mWifiManager.addSuggestionConnectionStatusListener(null, mSuggestionConnectionListener);
     }
 
     /**
@@ -2153,11 +2923,12 @@
         ArgumentCaptor<ISuggestionConnectionStatusListener.Stub> callbackCaptor =
                 ArgumentCaptor.forClass(ISuggestionConnectionStatusListener.Stub.class);
         Executor executor = new SynchronousExecutor();
-        mWifiManager.addSuggestionConnectionStatusListener(executor, mListener);
-        verify(mWifiService).registerSuggestionConnectionStatusListener(any(IBinder.class),
-                callbackCaptor.capture(), anyInt(), anyString(), nullable(String.class));
+        mWifiManager.addSuggestionConnectionStatusListener(executor, mSuggestionConnectionListener);
+        verify(mWifiService).registerSuggestionConnectionStatusListener(callbackCaptor.capture(),
+                anyString(), nullable(String.class));
         callbackCaptor.getValue().onConnectionStatus(mWifiNetworkSuggestion, errorCode);
-        verify(mListener).onConnectionStatus(any(WifiNetworkSuggestion.class), eq(errorCode));
+        verify(mSuggestionConnectionListener).onConnectionStatus(any(WifiNetworkSuggestion.class),
+                eq(errorCode));
     }
 
     /**
@@ -2168,9 +2939,10 @@
         int errorCode = STATUS_SUGGESTION_CONNECTION_FAILURE_AUTHENTICATION;
         ArgumentCaptor<ISuggestionConnectionStatusListener.Stub> callbackCaptor =
                 ArgumentCaptor.forClass(ISuggestionConnectionStatusListener.Stub.class);
-        mWifiManager.addSuggestionConnectionStatusListener(mExecutor, mListener);
-        verify(mWifiService).registerSuggestionConnectionStatusListener(any(IBinder.class),
-                callbackCaptor.capture(), anyInt(), anyString(), nullable(String.class));
+        mWifiManager.addSuggestionConnectionStatusListener(mExecutor,
+                mSuggestionConnectionListener);
+        verify(mWifiService).registerSuggestionConnectionStatusListener(callbackCaptor.capture(),
+                anyString(), nullable(String.class));
         callbackCaptor.getValue().onConnectionStatus(any(WifiNetworkSuggestion.class), errorCode);
         verify(mExecutor).execute(any(Runnable.class));
     }
@@ -2188,8 +2960,16 @@
      */
     @Test
     public void testRemoveSuggestionConnectionListener() throws Exception {
-        mWifiManager.removeSuggestionConnectionStatusListener(mListener);
-        verify(mWifiService).unregisterSuggestionConnectionStatusListener(anyInt(), anyString());
+        ArgumentCaptor<ISuggestionConnectionStatusListener.Stub> callbackCaptor =
+                ArgumentCaptor.forClass(ISuggestionConnectionStatusListener.Stub.class);
+        mWifiManager.addSuggestionConnectionStatusListener(mExecutor,
+                mSuggestionConnectionListener);
+        verify(mWifiService).registerSuggestionConnectionStatusListener(callbackCaptor.capture(),
+                anyString(), nullable(String.class));
+
+        mWifiManager.removeSuggestionConnectionStatusListener(mSuggestionConnectionListener);
+        verify(mWifiService).unregisterSuggestionConnectionStatusListener(
+                eq(callbackCaptor.getValue()), anyString());
     }
 
     /** Test {@link WifiManager#calculateSignalLevel(int)} */
@@ -2350,13 +3130,26 @@
                 ArgumentCaptor.forClass(IWifiConnectedNetworkScorer.Stub.class);
         verify(mWifiService).setWifiConnectedNetworkScorer(any(IBinder.class),
                 callbackCaptor.capture());
-        callbackCaptor.getValue().onStart(0);
+        callbackCaptor.getValue().onStart(new WifiConnectedSessionInfo.Builder(10)
+                .setUserSelected(true)
+                .build());
         callbackCaptor.getValue().onStop(10);
         mLooper.dispatchAll();
-        verify(mWifiConnectedNetworkScorer).onStart(0);
+        verify(mWifiConnectedNetworkScorer).onStart(
+                argThat(sessionInfo -> sessionInfo.getSessionId() == 10
+                        && sessionInfo.isUserSelected()));
         verify(mWifiConnectedNetworkScorer).onStop(10);
     }
 
+    /**
+     * Verify the call to setWifiScoringEnabled goes to WifiServiceImpl.
+     */
+    @Test
+    public void setWifiScoringEnabledGoesToWifiServiceImpl() throws Exception {
+        mWifiManager.setWifiScoringEnabled(true);
+        verify(mWifiService).setWifiScoringEnabled(true);
+    }
+
     @Test
     public void testScanThrottle() throws Exception {
         mWifiManager.setScanThrottleEnabled(true);
@@ -2381,10 +3174,214 @@
     @Test
     public void testScanAvailable() throws Exception {
         mWifiManager.setScanAlwaysAvailable(true);
-        verify(mWifiService).setScanAlwaysAvailable(true);
+        verify(mWifiService).setScanAlwaysAvailable(true, TEST_PACKAGE_NAME);
 
         when(mWifiService.isScanAlwaysAvailable()).thenReturn(false);
         assertFalse(mWifiManager.isScanAlwaysAvailable());
         verify(mWifiService).isScanAlwaysAvailable();
     }
+
+    @Test
+    public void testSetCarrierNetworkOffload() throws Exception {
+        mWifiManager.setCarrierNetworkOffloadEnabled(TEST_SUB_ID, true, false);
+        verify(mWifiService).setCarrierNetworkOffloadEnabled(TEST_SUB_ID,
+                true, false);
+    }
+
+    @Test
+    public void testGetCarrierNetworkOffload() throws Exception {
+        when(mWifiService.isCarrierNetworkOffloadEnabled(TEST_SUB_ID, false)).thenReturn(true);
+        assertTrue(mWifiManager.isCarrierNetworkOffloadEnabled(TEST_SUB_ID, false));
+        verify(mWifiService).isCarrierNetworkOffloadEnabled(TEST_SUB_ID, false);
+    }
+
+
+    /**
+     * Verify an IllegalArgumentException is thrown if listener is not provided.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testRemoveSuggestionUserApprovalStatusListenerWithNullListener() {
+        mWifiManager.removeSuggestionUserApprovalStatusListener(null);
+    }
+
+
+    /**
+     * Verify removeSuggestionUserApprovalStatusListener.
+     */
+    @Test
+    public void testRemoveSuggestionUserApprovalStatusListener() throws Exception {
+        ArgumentCaptor<ISuggestionUserApprovalStatusListener.Stub> callbackCaptor =
+                ArgumentCaptor.forClass(ISuggestionUserApprovalStatusListener.Stub.class);
+        mWifiManager.addSuggestionUserApprovalStatusListener(mExecutor,
+                mSuggestionUserApprovalStatusListener);
+        verify(mWifiService).addSuggestionUserApprovalStatusListener(callbackCaptor.capture(),
+                anyString());
+
+        mWifiManager.removeSuggestionUserApprovalStatusListener(
+                mSuggestionUserApprovalStatusListener);
+        verify(mWifiService).removeSuggestionUserApprovalStatusListener(
+                eq(callbackCaptor.getValue()), anyString());
+    }
+
+    /**
+     * Verify an IllegalArgumentException is thrown if executor not provided.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testAddSuggestionUserApprovalStatusListenerWithNullExecutor() {
+        mWifiManager.addSuggestionUserApprovalStatusListener(null,
+                mSuggestionUserApprovalStatusListener);
+    }
+
+    /**
+     * Verify an IllegalArgumentException is thrown if listener is not provided.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testAddSuggestionUserApprovalStatusListenerWithNullListener() {
+        mWifiManager.addSuggestionUserApprovalStatusListener(mExecutor, null);
+    }
+
+    /**
+     * Verify client provided listener is being called to the right listener.
+     */
+    @Test
+    public void testAddSuggestionUserApprovalStatusListenerAndReceiveEvent() throws Exception {
+        ArgumentCaptor<ISuggestionUserApprovalStatusListener.Stub> callbackCaptor =
+                ArgumentCaptor.forClass(ISuggestionUserApprovalStatusListener.Stub.class);
+        Executor executor = new SynchronousExecutor();
+        mWifiManager.addSuggestionUserApprovalStatusListener(executor,
+                mSuggestionUserApprovalStatusListener);
+        verify(mWifiService).addSuggestionUserApprovalStatusListener(callbackCaptor.capture(),
+                anyString());
+        callbackCaptor.getValue().onUserApprovalStatusChange(
+                WifiManager.STATUS_SUGGESTION_APPROVAL_APPROVED_BY_USER);
+        verify(mSuggestionUserApprovalStatusListener).onUserApprovalStatusChange(
+                WifiManager.STATUS_SUGGESTION_APPROVAL_APPROVED_BY_USER);
+    }
+
+    /**
+     * Verify client provided listener is being called to the right executor.
+     */
+    @Test
+    public void testAddSuggestionUserApprovalStatusListenerWithTheTargetExecutor()
+            throws Exception {
+        ArgumentCaptor<ISuggestionUserApprovalStatusListener.Stub> callbackCaptor =
+                ArgumentCaptor.forClass(ISuggestionUserApprovalStatusListener.Stub.class);
+        mWifiManager.addSuggestionUserApprovalStatusListener(mExecutor,
+                mSuggestionUserApprovalStatusListener);
+        verify(mWifiService).addSuggestionUserApprovalStatusListener(callbackCaptor.capture(),
+                anyString());
+        callbackCaptor.getValue().onUserApprovalStatusChange(
+                WifiManager.STATUS_SUGGESTION_APPROVAL_APPROVED_BY_USER);
+        verify(mExecutor).execute(any(Runnable.class));
+    }
+
+    @Test
+    public void testSetEmergencyScanRequestInProgress() throws Exception {
+        mWifiManager.setEmergencyScanRequestInProgress(true);
+        verify(mWifiService).setEmergencyScanRequestInProgress(true);
+
+        mWifiManager.setEmergencyScanRequestInProgress(false);
+        verify(mWifiService).setEmergencyScanRequestInProgress(false);
+    }
+
+    @Test
+    public void testRemoveAppState() throws Exception {
+        mWifiManager.removeAppState(TEST_UID, TEST_PACKAGE_NAME);
+        verify(mWifiService).removeAppState(TEST_UID, TEST_PACKAGE_NAME);
+    }
+
+    /**
+     * Test behavior of isPasspointTermsAndConditionsSupported
+     */
+    @Test
+    public void testIsPasspointTermsAndConditionsSupported() throws Exception {
+        when(mWifiService.getSupportedFeatures())
+                .thenReturn(new Long(WIFI_FEATURE_PASSPOINT_TERMS_AND_CONDITIONS));
+        assertTrue(mWifiManager.isPasspointTermsAndConditionsSupported());
+        when(mWifiService.getSupportedFeatures())
+                .thenReturn(new Long(~WIFI_FEATURE_PASSPOINT_TERMS_AND_CONDITIONS));
+        assertFalse(mWifiManager.isPasspointTermsAndConditionsSupported());
+    }
+
+    /**
+     * Verify the call to setOverrideCountryCode goes to WifiServiceImpl.
+     */
+    @Test
+    public void testSetOverrideCountryCode() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        mWifiManager.setOverrideCountryCode(TEST_COUNTRY_CODE);
+        verify(mWifiService).setOverrideCountryCode(eq(TEST_COUNTRY_CODE));
+    }
+
+    /**
+     * Verify the call to clearOverrideCountryCode goes to WifiServiceImpl.
+     */
+    @Test
+    public void testClearOverrideCountryCode() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        mWifiManager.clearOverrideCountryCode();
+        verify(mWifiService).clearOverrideCountryCode();
+    }
+
+    /**
+     * Verify the call to setDefaultCountryCode goes to WifiServiceImpl.
+     */
+    @Test
+    public void testSetDefaultCountryCode() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        mWifiManager.setDefaultCountryCode(TEST_COUNTRY_CODE);
+        verify(mWifiService).setDefaultCountryCode(eq(TEST_COUNTRY_CODE));
+    }
+
+    /**
+     * Test behavior of flushPasspointAnqpCache
+     */
+    @Test
+    public void testFlushPasspointAnqpCache() throws Exception {
+        mWifiManager.flushPasspointAnqpCache();
+        verify(mWifiService).flushPasspointAnqpCache(anyString());
+    }
+
+    /**
+     * Test behavior of isDecoratedIdentitySupported
+     */
+    @Test
+    public void testIsDecoratedIdentitySupported() throws Exception {
+        when(mWifiService.getSupportedFeatures())
+                .thenReturn(new Long(WIFI_FEATURE_DECORATED_IDENTITY));
+        assertTrue(mWifiManager.isDecoratedIdentitySupported());
+        when(mWifiService.getSupportedFeatures())
+                .thenReturn(new Long(~WIFI_FEATURE_DECORATED_IDENTITY));
+        assertFalse(mWifiManager.isDecoratedIdentitySupported());
+    }
+
+    /**
+     * Verify call to getAllowedChannels goes to WifiServiceImpl
+     */
+    @Test
+    public void testGetAllowedChannels() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        int band = WifiScanner.WIFI_BAND_24_5_6_GHZ;
+        int mode = WifiAvailableChannel.OP_MODE_WIFI_AWARE
+                | WifiAvailableChannel.OP_MODE_WIFI_DIRECT_GO
+                | WifiAvailableChannel.OP_MODE_WIFI_DIRECT_CLI;
+        mWifiManager.getAllowedChannels(band, mode);
+        verify(mWifiService).getUsableChannels(eq(band), eq(mode),
+                eq(WifiAvailableChannel.FILTER_REGULATORY));
+    }
+
+    /**
+     * Verify call to getUsableChannels goes to WifiServiceImpl
+     */
+    @Test
+    public void testGetUsableChannels() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        int band = WifiScanner.WIFI_BAND_BOTH_WITH_DFS;
+        int mode = WifiAvailableChannel.OP_MODE_WIFI_AWARE
+                | WifiAvailableChannel.OP_MODE_WIFI_DIRECT_CLI;
+        mWifiManager.getUsableChannels(band, mode);
+        verify(mWifiService).getUsableChannels(eq(band), eq(mode),
+                eq(WifiAvailableChannel.FILTER_CONCURRENCY
+                    | WifiAvailableChannel.FILTER_CELLULAR_COEXISTENCE));
+    }
 }
diff --git a/framework/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java b/framework/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java
index d479aac..9d1d55d 100644
--- a/framework/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java
+++ b/framework/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java
@@ -90,13 +90,13 @@
         WifiConfiguration wifiConfiguration1 = createDefaultWifiConfiguration();
         WifiNetworkAgentSpecifier specifier1 =
                 new WifiNetworkAgentSpecifier(
-                        wifiConfiguration1);
+                        wifiConfiguration1, ScanResult.WIFI_BAND_24_GHZ, true);
 
         WifiConfiguration wifiConfiguration2 = new WifiConfiguration(wifiConfiguration1);
         wifiConfiguration2.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkAgentSpecifier specifier2 =
                 new WifiNetworkAgentSpecifier(
-                        wifiConfiguration2);
+                        wifiConfiguration2, ScanResult.WIFI_BAND_24_GHZ, true);
 
         assertFalse(specifier2.equals(specifier1));
     }
@@ -112,13 +112,13 @@
         WifiConfiguration wifiConfiguration1 = createDefaultWifiConfiguration();
         WifiNetworkAgentSpecifier specifier1 =
                 new WifiNetworkAgentSpecifier(
-                        wifiConfiguration1);
+                        wifiConfiguration1, ScanResult.WIFI_BAND_5_GHZ, true);
 
         WifiConfiguration wifiConfiguration2 = new WifiConfiguration(wifiConfiguration1);
         wifiConfiguration2.SSID = TEST_SSID_1;
         WifiNetworkAgentSpecifier specifier2 =
                 new WifiNetworkAgentSpecifier(
-                        wifiConfiguration2);
+                        wifiConfiguration2, ScanResult.WIFI_BAND_5_GHZ, true);
 
         assertFalse(specifier2.equals(specifier1));
     }
@@ -134,13 +134,13 @@
         WifiConfiguration wifiConfiguration1 = createDefaultWifiConfiguration();
         WifiNetworkAgentSpecifier specifier1 =
                 new WifiNetworkAgentSpecifier(
-                        wifiConfiguration1);
+                        wifiConfiguration1, ScanResult.WIFI_BAND_24_GHZ, false);
 
         WifiConfiguration wifiConfiguration2 = new WifiConfiguration(wifiConfiguration1);
         wifiConfiguration2.BSSID = TEST_BSSID_1;
         WifiNetworkAgentSpecifier specifier2 =
                 new WifiNetworkAgentSpecifier(
-                        wifiConfiguration2);
+                        wifiConfiguration2, ScanResult.WIFI_BAND_24_GHZ, false);
 
         assertFalse(specifier2.equals(specifier1));
     }
@@ -194,6 +194,7 @@
         WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier(
                 ssidPattern,
                 bssidPattern,
+                ScanResult.WIFI_BAND_5_GHZ,
                 wificonfigurationNetworkSpecifier);
 
         assertTrue(wifiNetworkSpecifier.canBeSatisfiedBy(wifiNetworkAgentSpecifier));
@@ -222,6 +223,7 @@
         WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier(
                 ssidPattern,
                 bssidPattern,
+                ScanResult.WIFI_BAND_5_GHZ,
                 wificonfigurationNetworkSpecifier);
 
         assertTrue(wifiNetworkSpecifier.canBeSatisfiedBy(wifiNetworkAgentSpecifier));
@@ -250,6 +252,7 @@
         WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier(
                 ssidPattern,
                 bssidPattern,
+                ScanResult.WIFI_BAND_5_GHZ,
                 wificonfigurationNetworkSpecifier);
 
         assertTrue(wifiNetworkSpecifier.canBeSatisfiedBy(wifiNetworkAgentSpecifier));
@@ -269,7 +272,7 @@
         wifiConfigurationNetworkAgent.SSID = "\"" + TEST_SSID_1 + "\"";
         WifiNetworkAgentSpecifier wifiNetworkAgentSpecifier =
                 new WifiNetworkAgentSpecifier(
-                        wifiConfigurationNetworkAgent);
+                        wifiConfigurationNetworkAgent, ScanResult.WIFI_BAND_24_GHZ, true);
 
         PatternMatcher ssidPattern =
                 new PatternMatcher(TEST_SSID_PATTERN, PatternMatcher.PATTERN_PREFIX);
@@ -281,6 +284,7 @@
         WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier(
                 ssidPattern,
                 bssidPattern,
+                ScanResult.WIFI_BAND_24_GHZ,
                 wificonfigurationNetworkSpecifier);
 
         assertFalse(wifiNetworkSpecifier.canBeSatisfiedBy(wifiNetworkAgentSpecifier));
@@ -288,6 +292,113 @@
     }
 
     /**
+     * Validate {@link WifiNetworkAgentSpecifier} with {@link WifiNetworkSpecifier} band matching.
+     */
+    @Test
+    public void testWifiNetworkAgentSpecifierBandMatching() {
+        WifiConfiguration wifiConfigurationNetworkAgent = createDefaultWifiConfiguration();
+        wifiConfigurationNetworkAgent.SSID = "\"" + TEST_SSID + "\"";
+        WifiNetworkAgentSpecifier wifiNetworkAgentSpecifier =
+                new WifiNetworkAgentSpecifier(
+                        wifiConfigurationNetworkAgent, ScanResult.WIFI_BAND_5_GHZ, true);
+
+        PatternMatcher ssidPattern =
+                new PatternMatcher(TEST_SSID_PATTERN, PatternMatcher.PATTERN_PREFIX);
+        Pair<MacAddress, MacAddress> bssidPattern =
+                Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
+                        MacAddress.fromString(TEST_BSSID_OUI_MASK));
+        WifiConfiguration wificonfigurationNetworkSpecifier = new WifiConfiguration();
+        wificonfigurationNetworkSpecifier.allowedKeyManagement
+                .set(WifiConfiguration.KeyMgmt.WPA_PSK);
+
+        // Same band matches.
+        WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier(
+                ssidPattern,
+                bssidPattern,
+                ScanResult.WIFI_BAND_5_GHZ,
+                wificonfigurationNetworkSpecifier);
+        assertTrue(wifiNetworkSpecifier.canBeSatisfiedBy(wifiNetworkAgentSpecifier));
+        assertTrue(wifiNetworkAgentSpecifier.canBeSatisfiedBy(wifiNetworkSpecifier));
+
+        // Different band does not match.
+        wifiNetworkSpecifier = new WifiNetworkSpecifier(
+                ssidPattern,
+                bssidPattern,
+                ScanResult.WIFI_BAND_24_GHZ,
+                wificonfigurationNetworkSpecifier);
+        assertFalse(wifiNetworkSpecifier.canBeSatisfiedBy(wifiNetworkAgentSpecifier));
+        assertFalse(wifiNetworkAgentSpecifier.canBeSatisfiedBy(wifiNetworkSpecifier));
+
+        // UNSPECIFIED matches a specified band.
+        wifiNetworkSpecifier = new WifiNetworkSpecifier(
+                ssidPattern,
+                bssidPattern,
+                ScanResult.UNSPECIFIED,
+                wificonfigurationNetworkSpecifier);
+        assertTrue(wifiNetworkSpecifier.canBeSatisfiedBy(wifiNetworkAgentSpecifier));
+        assertTrue(wifiNetworkAgentSpecifier.canBeSatisfiedBy(wifiNetworkSpecifier));
+    }
+
+    /**
+     * Validate {@link WifiNetworkAgentSpecifier} local-only matching.
+     */
+    @Test
+    public void testWifiNetworkAgentSpecifierLocalOnlyMatching() {
+        WifiConfiguration wifiConfigurationNetworkAgent = createDefaultWifiConfiguration();
+        wifiConfigurationNetworkAgent.SSID = "\"" + TEST_SSID_1 + "\"";
+
+        PatternMatcher ssidPattern =
+                new PatternMatcher(TEST_SSID_PATTERN, PatternMatcher.PATTERN_PREFIX);
+        Pair<MacAddress, MacAddress> bssidPattern =
+                Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
+                        MacAddress.fromString(TEST_BSSID_OUI_MASK));
+        WifiConfiguration wificonfigurationNetworkSpecifier = new WifiConfiguration();
+        wificonfigurationNetworkSpecifier.allowedKeyManagement
+                .set(WifiConfiguration.KeyMgmt.WPA_PSK);
+
+        // There is no match because the SSID pattern does not match.
+        WifiNetworkAgentSpecifier wifiNetworkAgentSpecifier =
+                new WifiNetworkAgentSpecifier(
+                        wifiConfigurationNetworkAgent, ScanResult.WIFI_BAND_5_GHZ, true);
+        WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier(
+                ssidPattern,
+                bssidPattern,
+                ScanResult.WIFI_BAND_5_GHZ,
+                wificonfigurationNetworkSpecifier);
+        assertFalse(wifiNetworkSpecifier.canBeSatisfiedBy(wifiNetworkAgentSpecifier));
+        assertFalse(wifiNetworkAgentSpecifier.canBeSatisfiedBy(wifiNetworkSpecifier));
+
+        // If the WifiNetworkAgentSpecifier is not a local-only specifier, then it matches, because
+        // the SSID pattern is ignored.
+        // TODO: this should also fail to match. An app setting a specifier with a SSID or BSSID
+        // pattern should not match any peer-to-peer network.
+        wifiNetworkAgentSpecifier =
+                new WifiNetworkAgentSpecifier(
+                        wifiConfigurationNetworkAgent, ScanResult.WIFI_BAND_5_GHZ, false);
+        assertTrue(wifiNetworkSpecifier.canBeSatisfiedBy(wifiNetworkAgentSpecifier));
+        assertTrue(wifiNetworkAgentSpecifier.canBeSatisfiedBy(wifiNetworkSpecifier));
+
+        // If the band doesn't match, then there is no match.
+        wifiNetworkSpecifier = new WifiNetworkSpecifier(
+                ssidPattern,
+                bssidPattern,
+                ScanResult.WIFI_BAND_24_GHZ,
+                wificonfigurationNetworkSpecifier);
+        assertFalse(wifiNetworkSpecifier.canBeSatisfiedBy(wifiNetworkAgentSpecifier));
+        assertFalse(wifiNetworkAgentSpecifier.canBeSatisfiedBy(wifiNetworkSpecifier));
+
+        // An UNSPECIFIED band also doesn't match anything.
+        // TODO: this is also likely incorrect.
+        wifiNetworkSpecifier = new WifiNetworkSpecifier(
+                ssidPattern,
+                bssidPattern,
+                ScanResult.UNSPECIFIED,
+                wificonfigurationNetworkSpecifier);
+        assertFalse(wifiNetworkSpecifier.canBeSatisfiedBy(wifiNetworkAgentSpecifier));
+        assertFalse(wifiNetworkAgentSpecifier.canBeSatisfiedBy(wifiNetworkSpecifier));
+    }
+
+    /**
      * Validate {@link WifiNetworkAgentSpecifier} with {@link WifiNetworkSpecifier} matching.
      * a) Create network agent specifier for WPA_PSK network
      * b) Create network specifier with non-matching BSSID pattern.
@@ -300,7 +411,7 @@
         wifiConfigurationNetworkAgent.BSSID = TEST_BSSID_1;
         WifiNetworkAgentSpecifier wifiNetworkAgentSpecifier =
                 new WifiNetworkAgentSpecifier(
-                        wifiConfigurationNetworkAgent);
+                        wifiConfigurationNetworkAgent, ScanResult.WIFI_BAND_5_GHZ, true);
 
         PatternMatcher ssidPattern =
                 new PatternMatcher(".*", PatternMatcher.PATTERN_SIMPLE_GLOB);
@@ -313,6 +424,7 @@
         WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier(
                 ssidPattern,
                 bssidPattern,
+                ScanResult.WIFI_BAND_5_GHZ,
                 wificonfigurationNetworkSpecifier);
 
         assertFalse(wifiNetworkSpecifier.canBeSatisfiedBy(wifiNetworkAgentSpecifier));
@@ -332,7 +444,7 @@
         wifiConfigurationNetworkAgent.BSSID = TEST_BSSID_1;
         WifiNetworkAgentSpecifier wifiNetworkAgentSpecifier =
                 new WifiNetworkAgentSpecifier(
-                        wifiConfigurationNetworkAgent);
+                        wifiConfigurationNetworkAgent, ScanResult.WIFI_BAND_24_GHZ, true);
 
         PatternMatcher ssidPattern =
                 new PatternMatcher(TEST_SSID_PATTERN, PatternMatcher.PATTERN_PREFIX);
@@ -345,6 +457,7 @@
         WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier(
                 ssidPattern,
                 bssidPattern,
+                ScanResult.WIFI_BAND_24_GHZ,
                 wificonfigurationNetworkSpecifier);
 
         assertFalse(wifiNetworkSpecifier.canBeSatisfiedBy(wifiNetworkAgentSpecifier));
@@ -372,13 +485,13 @@
         WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier(
                 ssidPattern,
                 bssidPattern,
+                ScanResult.UNSPECIFIED,
                 wificonfigurationNetworkSpecifier);
 
         assertFalse(wifiNetworkSpecifier.canBeSatisfiedBy(wifiNetworkAgentSpecifier));
         assertFalse(wifiNetworkAgentSpecifier.canBeSatisfiedBy(wifiNetworkSpecifier));
     }
 
-
     private WifiConfiguration createDefaultWifiConfiguration() {
         WifiConfiguration wifiConfiguration = new WifiConfiguration();
         wifiConfiguration.SSID = "\"" + TEST_SSID + "\"";
@@ -389,7 +502,8 @@
     }
 
     private WifiNetworkAgentSpecifier createDefaultNetworkAgentSpecifier() {
-        return new WifiNetworkAgentSpecifier(createDefaultWifiConfiguration());
+        return new WifiNetworkAgentSpecifier(createDefaultWifiConfiguration(),
+                ScanResult.WIFI_BAND_5_GHZ, true);
     }
 
 }
diff --git a/framework/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java b/framework/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java
index 6f47f3d..0a24523 100644
--- a/framework/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java
+++ b/framework/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java
@@ -25,6 +25,7 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import android.net.MacAddress;
 import android.net.MatchAllNetworkSpecifier;
@@ -35,6 +36,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.modules.utils.build.SdkLevel;
+
 import org.junit.Test;
 
 import java.security.cert.X509Certificate;
@@ -141,11 +144,11 @@
     }
 
     /**
-     * Validate correctness of WifiNetworkSuggestion object created by
-     * {@link WifiNetworkSuggestion.Builder#build()} for WPA3-Enterprise network.
+     * Validate correctness of WifiNetworkSpecifier object created by
+     * {@link WifiNetworkSpecifier.Builder#build()} for WPA3-Enterprise network.
      */
     @Test
-    public void testWifiNetworkSuggestionBuilderForWpa3EapNetwork() {
+    public void testWifiNetworkSpecifierBuilderForWpa3EapNetwork() {
         WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
         enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
         enterpriseConfig.setCaCertificate(FakeKeys.CA_CERT0);
@@ -174,11 +177,121 @@
     }
 
     /**
-     * Validate correctness of WifiNetworkSuggestion object created by
-     * {@link WifiNetworkSuggestion.Builder#build()} for WPA3-Enterprise 192-bit RSA SuiteB network.
+     * Validate correctness of WifiNetworkSpecifier object created by
+     * {@link WifiNetworkSpecifier.Builder#build()} for WPA3-Enterprise network.
      */
     @Test
-    public void testWifiNetworkSuggestionBuilderForWpa3SuiteBRsaEapNetwork() {
+    public void testWifiNetworkSpecifierBuilderForWpa3EapNetworkWithStandardApi() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        enterpriseConfig.setCaCertificate(FakeKeys.CA_CERT0);
+        enterpriseConfig.setDomainSuffixMatch(TEST_DOMAIN_SUFFIX_MATCH);
+
+        NetworkSpecifier specifier = new WifiNetworkSpecifier.Builder()
+                .setSsid(TEST_SSID)
+                .setWpa3EnterpriseStandardModeConfig(enterpriseConfig)
+                .build();
+
+        assertTrue(specifier instanceof WifiNetworkSpecifier);
+        WifiNetworkSpecifier wifiNetworkSpecifier = (WifiNetworkSpecifier) specifier;
+
+        assertEquals("\"" + TEST_SSID + "\"", wifiNetworkSpecifier.wifiConfiguration.SSID);
+        assertTrue(wifiNetworkSpecifier.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.IEEE8021X));
+        assertTrue(wifiNetworkSpecifier.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.WPA_EAP));
+        assertFalse(wifiNetworkSpecifier.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.SUITE_B_192));
+        assertTrue(wifiNetworkSpecifier.wifiConfiguration.allowedGroupCiphers
+                .get(WifiConfiguration.GroupCipher.CCMP));
+        assertTrue(wifiNetworkSpecifier.wifiConfiguration.requirePmf);
+        assertNull(wifiNetworkSpecifier.wifiConfiguration.preSharedKey);
+        assertNotNull(wifiNetworkSpecifier.wifiConfiguration.enterpriseConfig);
+    }
+
+    /**
+     * Validate correctness of WifiNetworkSpecifier object created by
+     * {@link WifiNetworkSpecifier.Builder#build()} for WPA3-Enterprise network
+     * with 192-bit RSA certificates.
+     */
+    @Test
+    public void testWifiNetworkSpecifierBuilderForWpa3EapNetworkWithSuiteBRsaCerts() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        enterpriseConfig.setCaCertificate(FakeKeys.CA_SUITE_B_RSA3072_CERT);
+        enterpriseConfig.setClientKeyEntryWithCertificateChain(FakeKeys.CLIENT_SUITE_B_RSA3072_KEY,
+                new X509Certificate[] {FakeKeys.CLIENT_SUITE_B_RSA3072_CERT});
+
+        enterpriseConfig.setDomainSuffixMatch(TEST_DOMAIN_SUFFIX_MATCH);
+
+        NetworkSpecifier specifier = new WifiNetworkSpecifier.Builder()
+                .setSsid(TEST_SSID)
+                .setWpa3EnterpriseStandardModeConfig(enterpriseConfig)
+                .build();
+
+        assertTrue(specifier instanceof WifiNetworkSpecifier);
+        WifiNetworkSpecifier wifiNetworkSpecifier = (WifiNetworkSpecifier) specifier;
+
+        assertEquals("\"" + TEST_SSID + "\"", wifiNetworkSpecifier.wifiConfiguration.SSID);
+        assertTrue(wifiNetworkSpecifier.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.IEEE8021X));
+        assertTrue(wifiNetworkSpecifier.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.WPA_EAP));
+        assertFalse(wifiNetworkSpecifier.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.SUITE_B_192));
+        assertTrue(wifiNetworkSpecifier.wifiConfiguration.allowedGroupCiphers
+                .get(WifiConfiguration.GroupCipher.CCMP));
+        assertTrue(wifiNetworkSpecifier.wifiConfiguration.requirePmf);
+        assertNull(wifiNetworkSpecifier.wifiConfiguration.preSharedKey);
+        assertNotNull(wifiNetworkSpecifier.wifiConfiguration.enterpriseConfig);
+    }
+
+    /**
+     * Validate correctness of WifiNetworkSpecifier object created by
+     * {@link WifiNetworkSpecifier.Builder#build()} for WPA3-Enterprise network
+     * with 192-bit ECC certificates.
+     */
+    @Test
+    public void testWifiNetworkSpecifierBuilderForWpa3EapNetworkWithSuiteBEccCerts() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        enterpriseConfig.setCaCertificate(FakeKeys.CA_SUITE_B_ECDSA_CERT);
+        enterpriseConfig.setClientKeyEntryWithCertificateChain(FakeKeys.CLIENT_SUITE_B_ECC_KEY,
+                new X509Certificate[] {FakeKeys.CLIENT_SUITE_B_ECDSA_CERT});
+
+        enterpriseConfig.setDomainSuffixMatch(TEST_DOMAIN_SUFFIX_MATCH);
+
+        NetworkSpecifier specifier = new WifiNetworkSpecifier.Builder()
+                .setSsid(TEST_SSID)
+                .setWpa3EnterpriseStandardModeConfig(enterpriseConfig)
+                .build();
+
+        assertTrue(specifier instanceof WifiNetworkSpecifier);
+        WifiNetworkSpecifier wifiNetworkSpecifier = (WifiNetworkSpecifier) specifier;
+
+        assertEquals("\"" + TEST_SSID + "\"", wifiNetworkSpecifier.wifiConfiguration.SSID);
+        assertTrue(wifiNetworkSpecifier.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.IEEE8021X));
+        assertTrue(wifiNetworkSpecifier.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.WPA_EAP));
+        assertFalse(wifiNetworkSpecifier.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.SUITE_B_192));
+        assertTrue(wifiNetworkSpecifier.wifiConfiguration.allowedGroupCiphers
+                .get(WifiConfiguration.GroupCipher.CCMP));
+        assertTrue(wifiNetworkSpecifier.wifiConfiguration.requirePmf);
+        assertNull(wifiNetworkSpecifier.wifiConfiguration.preSharedKey);
+        assertNotNull(wifiNetworkSpecifier.wifiConfiguration.enterpriseConfig);
+    }
+
+    /**
+     * Validate correctness of WifiNetworkSpecifier object created by
+     * {@link WifiNetworkSpecifier.Builder#build()} for WPA3-Enterprise 192-bit RSA SuiteB network.
+     */
+    @Test
+    public void testWifiNetworkSpecifierBuilderForWpa3SuiteBRsaEapNetwork() {
         WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
         enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
         enterpriseConfig.setCaCertificate(FakeKeys.CA_SUITE_B_RSA3072_CERT);
@@ -208,11 +321,11 @@
     }
 
     /**
-     * Validate correctness of WifiNetworkSuggestion object created by
-     * {@link WifiNetworkSuggestion.Builder#build()} for WPA3-Enterprise 192-bit ECC SuiteB network.
+     * Validate correctness of WifiNetworkSpecifier object created by
+     * {@link WifiNetworkSpecifier.Builder#build()} for WPA3-Enterprise 192-bit ECC SuiteB network.
      */
     @Test
-    public void testWifiNetworkSuggestionBuilderForWpa3SuiteBEccEapNetwork() {
+    public void testWifiNetworkSpecifierBuilderForWpa3SuiteBEccEapNetwork() {
         WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
         enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
         enterpriseConfig.setCaCertificate(FakeKeys.CA_SUITE_B_ECDSA_CERT);
@@ -242,6 +355,76 @@
     }
 
     /**
+     * Validate correctness of WifiNetworkSpecifier object created by
+     * {@link WifiNetworkSpecifier.Builder#build()} for WPA3-Enterprise 192-bit RSA SuiteB network.
+     */
+    @Test
+    public void testWifiNetworkSpecifierBuilderForWpa3SuiteBRsaEapNetworkWith192BitApi() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        enterpriseConfig.setCaCertificate(FakeKeys.CA_SUITE_B_RSA3072_CERT);
+        enterpriseConfig.setClientKeyEntryWithCertificateChain(FakeKeys.CLIENT_SUITE_B_RSA3072_KEY,
+                new X509Certificate[] {FakeKeys.CLIENT_SUITE_B_RSA3072_CERT});
+
+        enterpriseConfig.setDomainSuffixMatch(TEST_DOMAIN_SUFFIX_MATCH);
+
+        NetworkSpecifier specifier = new WifiNetworkSpecifier.Builder()
+                .setSsid(TEST_SSID)
+                .setWpa3Enterprise192BitModeConfig(enterpriseConfig)
+                .build();
+
+        assertTrue(specifier instanceof WifiNetworkSpecifier);
+        WifiNetworkSpecifier wifiNetworkSpecifier = (WifiNetworkSpecifier) specifier;
+
+        assertEquals("\"" + TEST_SSID + "\"", wifiNetworkSpecifier.wifiConfiguration.SSID);
+        assertTrue(wifiNetworkSpecifier.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.SUITE_B_192));
+        assertTrue(wifiNetworkSpecifier.wifiConfiguration.allowedGroupCiphers
+                .get(WifiConfiguration.GroupCipher.GCMP_256));
+        assertTrue(wifiNetworkSpecifier.wifiConfiguration.allowedGroupManagementCiphers
+                .get(WifiConfiguration.GroupMgmtCipher.BIP_GMAC_256));
+        assertTrue(wifiNetworkSpecifier.wifiConfiguration.requirePmf);
+        assertNull(wifiNetworkSpecifier.wifiConfiguration.preSharedKey);
+        assertNotNull(wifiNetworkSpecifier.wifiConfiguration.enterpriseConfig);
+    }
+
+    /**
+     * Validate correctness of WifiNetworkSpecifier object created by
+     * {@link WifiNetworkSpecifier.Builder#build()} for WPA3-Enterprise 192-bit ECC SuiteB network.
+     */
+    @Test
+    public void testWifiNetworkSpecifierBuilderForWpa3SuiteBEccEapNetworkWith192BitApi() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        enterpriseConfig.setCaCertificate(FakeKeys.CA_SUITE_B_ECDSA_CERT);
+        enterpriseConfig.setClientKeyEntryWithCertificateChain(FakeKeys.CLIENT_SUITE_B_ECC_KEY,
+                new X509Certificate[] {FakeKeys.CLIENT_SUITE_B_ECDSA_CERT});
+
+        enterpriseConfig.setDomainSuffixMatch(TEST_DOMAIN_SUFFIX_MATCH);
+
+        NetworkSpecifier specifier = new WifiNetworkSpecifier.Builder()
+                .setSsid(TEST_SSID)
+                .setWpa3Enterprise192BitModeConfig(enterpriseConfig)
+                .build();
+
+        assertTrue(specifier instanceof WifiNetworkSpecifier);
+        WifiNetworkSpecifier wifiNetworkSpecifier = (WifiNetworkSpecifier) specifier;
+
+        assertEquals("\"" + TEST_SSID + "\"", wifiNetworkSpecifier.wifiConfiguration.SSID);
+        assertTrue(wifiNetworkSpecifier.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.SUITE_B_192));
+        assertTrue(wifiNetworkSpecifier.wifiConfiguration.allowedGroupCiphers
+                .get(WifiConfiguration.GroupCipher.GCMP_256));
+        assertTrue(wifiNetworkSpecifier.wifiConfiguration.allowedGroupManagementCiphers
+                .get(WifiConfiguration.GroupMgmtCipher.BIP_GMAC_256));
+        assertTrue(wifiNetworkSpecifier.wifiConfiguration.requirePmf);
+        assertNull(wifiNetworkSpecifier.wifiConfiguration.preSharedKey);
+        assertNotNull(wifiNetworkSpecifier.wifiConfiguration.enterpriseConfig);
+    }
+
+    /**
      * Ensure {@link WifiNetworkSpecifier.Builder#setSsid(String)} throws an exception
      * when the string is not Unicode.
      */
@@ -430,15 +613,17 @@
     /**
      * Ensure {@link WifiNetworkSpecifier.Builder#build()} throws an exception
      * when both {@link WifiNetworkSpecifier.Builder#setWpa3Passphrase(String)} and
-     * {@link WifiNetworkSpecifier.Builder#setWpa3EnterpriseConfig(WifiEnterpriseConfig)} are
-     * invoked.
+     * {@link WifiNetworkSpecifier.Builder
+     * #setWpa3EnterpriseStandardModeConfig(WifiEnterpriseConfig)}
+     * are invoked.
      */
     @Test(expected = IllegalStateException.class)
     public void testWifiNetworkSpecifierBuilderWithBothWpa3PasphraseAndEnterprise() {
+        assumeTrue(SdkLevel.isAtLeastS());
         new WifiNetworkSpecifier.Builder()
                 .setSsidPattern(new PatternMatcher(TEST_SSID, PATTERN_LITERAL))
                 .setWpa3Passphrase(TEST_PRESHARED_KEY)
-                .setWpa3EnterpriseConfig(new WifiEnterpriseConfig())
+                .setWpa3EnterpriseStandardModeConfig(new WifiEnterpriseConfig())
                 .build();
     }
 
@@ -468,6 +653,7 @@
                 new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
                         Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
                                 MacAddress.fromString(TEST_BSSID_OUI_MASK)),
+                        ScanResult.WIFI_BAND_5_GHZ,
                         wifiConfiguration);
 
         Parcel parcelW = Parcel.obtain();
@@ -499,6 +685,7 @@
                 new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
                         Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
                                 MacAddress.fromString(TEST_BSSID_OUI_MASK)),
+                        ScanResult.UNSPECIFIED,  /* band */
                         wifiConfiguration);
 
         assertFalse(specifier.canBeSatisfiedBy(null));
@@ -521,12 +708,14 @@
                 new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
                         Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
                                 MacAddress.fromString(TEST_BSSID_OUI_MASK)),
+                        ScanResult.WIFI_BAND_5_GHZ,
                         wifiConfiguration);
 
         WifiNetworkSpecifier specifier2 =
                 new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
                         Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
                                 MacAddress.fromString(TEST_BSSID_OUI_MASK)),
+                        ScanResult.WIFI_BAND_5_GHZ,
                         wifiConfiguration);
 
         assertTrue(specifier2.canBeSatisfiedBy(specifier1));
@@ -548,6 +737,7 @@
                 new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
                         Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
                                 MacAddress.fromString(TEST_BSSID_OUI_MASK)),
+                        ScanResult.WIFI_BAND_24_GHZ,
                         wifiConfiguration1);
 
         WifiConfiguration wifiConfiguration2 = new WifiConfiguration();
@@ -556,6 +746,7 @@
                 new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
                         Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
                                 MacAddress.fromString(TEST_BSSID_OUI_MASK)),
+                        ScanResult.WIFI_BAND_24_GHZ,
                         wifiConfiguration2);
 
         assertFalse(specifier2.canBeSatisfiedBy(specifier1));
@@ -577,12 +768,14 @@
                 new WifiNetworkSpecifier(new PatternMatcher("", PATTERN_LITERAL),
                         Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
                                 MacAddress.fromString(TEST_BSSID_OUI_MASK)),
+                        ScanResult.WIFI_BAND_5_GHZ,
                         wifiConfiguration);
 
         WifiNetworkSpecifier specifier2 =
                 new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
                         Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
                                 MacAddress.fromString(TEST_BSSID_OUI_MASK)),
+                        ScanResult.WIFI_BAND_5_GHZ,
                         wifiConfiguration);
 
         assertFalse(specifier2.canBeSatisfiedBy(specifier1));
@@ -604,14 +797,93 @@
                 new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
                         Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
                                 MacAddress.fromString(TEST_BSSID_OUI_MASK)),
+                        ScanResult.WIFI_BAND_24_GHZ,
                         wifiConfiguration);
 
         WifiNetworkSpecifier specifier2 =
                 new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
                         Pair.create(WifiManager.ALL_ZEROS_MAC_ADDRESS,
                                 WifiManager.ALL_ZEROS_MAC_ADDRESS),
+                        ScanResult.WIFI_BAND_24_GHZ,
                         wifiConfiguration);
 
         assertFalse(specifier2.canBeSatisfiedBy(specifier1));
     }
+
+    /**
+     * Validate NetworkSpecifier band matching.
+     */
+    @Test
+    public void testWifiNetworkSpecifierBandMatching() {
+        WifiConfiguration wifiConfiguration = new WifiConfiguration();
+        wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+        wifiConfiguration.preSharedKey = TEST_PRESHARED_KEY;
+
+        WifiNetworkSpecifier specifier1 =
+                new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
+                        Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
+                                MacAddress.fromString(TEST_BSSID_OUI_MASK)),
+                        ScanResult.WIFI_BAND_24_GHZ,
+                        wifiConfiguration);
+
+        WifiNetworkSpecifier specifier2 =
+                new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
+                        Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
+                                MacAddress.fromString(TEST_BSSID_OUI_MASK)),
+                        ScanResult.WIFI_BAND_24_GHZ,
+                        wifiConfiguration);
+
+        // Same band matches.
+        assertTrue(specifier2.canBeSatisfiedBy(specifier1));
+        assertTrue(specifier1.canBeSatisfiedBy(specifier2));
+
+        specifier2 =
+                new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
+                        Pair.create(WifiManager.ALL_ZEROS_MAC_ADDRESS,
+                                WifiManager.ALL_ZEROS_MAC_ADDRESS),
+                        ScanResult.WIFI_BAND_5_GHZ,
+                        wifiConfiguration);
+
+        // Different band does not match.
+        assertFalse(specifier2.canBeSatisfiedBy(specifier1));
+        assertFalse(specifier1.canBeSatisfiedBy(specifier2));
+
+        specifier1 =
+                new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
+                        Pair.create(WifiManager.ALL_ZEROS_MAC_ADDRESS,
+                                WifiManager.ALL_ZEROS_MAC_ADDRESS),
+                        ScanResult.UNSPECIFIED,
+                        wifiConfiguration);
+
+        // An UNSPECIFIED band does not match a specified band, because a WifiNetworkSpecifier
+        // satisfies another only if they are equal.
+        assertFalse(specifier2.canBeSatisfiedBy(specifier1));
+        assertFalse(specifier1.canBeSatisfiedBy(specifier2));
+    }
+
+    /**
+     * Test WifiNetworkSpecifier redaction.
+     */
+    @Test
+    public void testRedact() {
+        WifiConfiguration wifiConfiguration = new WifiConfiguration();
+        wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+        wifiConfiguration.preSharedKey = TEST_PRESHARED_KEY;
+
+        WifiNetworkSpecifier specifier =
+                new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
+                        Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
+                                MacAddress.fromString(TEST_BSSID_OUI_MASK)),
+                        ScanResult.WIFI_BAND_5_GHZ,
+                        wifiConfiguration);
+
+        final NetworkSpecifier redacted = specifier.redact();
+        if (SdkLevel.isAtLeastS()) {
+            assertEquals(
+                    new WifiNetworkSpecifier.Builder().setBand(ScanResult.WIFI_BAND_5_GHZ).build(),
+                    redacted);
+        } else {
+            assertTrue(redacted == specifier);
+        }
+    }
 }
diff --git a/framework/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java b/framework/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
index 00a0442..5ffb08a 100644
--- a/framework/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
+++ b/framework/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
@@ -16,15 +16,24 @@
 
 package android.net.wifi;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import android.net.MacAddress;
 import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.net.wifi.hotspot2.PasspointTestUtils;
 import android.os.Parcel;
+import android.telephony.SubscriptionManager;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.modules.utils.build.SdkLevel;
+
 import org.junit.Test;
 
 import java.security.cert.X509Certificate;
@@ -41,6 +50,9 @@
     private static final String TEST_FQDN = "fqdn";
     private static final String TEST_WAPI_CERT_SUITE = "suite";
     private static final String TEST_DOMAIN_SUFFIX_MATCH = "domainSuffixMatch";
+    private static final int DEFAULT_PRIORITY_GROUP = 0;
+    private static final int TEST_PRIORITY_GROUP = 1;
+    private static final int TEST_CARRIER_ID = 1998;
 
     /**
      * Validate correctness of WifiNetworkSuggestion object created by
@@ -64,6 +76,7 @@
         assertEquals(-1, suggestion.wifiConfiguration.priority);
         assertFalse(suggestion.isUserAllowedToManuallyConnect);
         assertTrue(suggestion.isInitialAutoJoinEnabled);
+        assertNull(suggestion.getEnterpriseConfig());
     }
 
     /**
@@ -94,6 +107,7 @@
         assertEquals(0, suggestion.wifiConfiguration.priority);
         assertFalse(suggestion.isUserAllowedToManuallyConnect);
         assertTrue(suggestion.isInitialAutoJoinEnabled);
+        assertNull(suggestion.getEnterpriseConfig());
     }
 
     /**
@@ -124,6 +138,7 @@
         assertEquals(-1, suggestion.wifiConfiguration.priority);
         assertTrue(suggestion.isUserAllowedToManuallyConnect);
         assertFalse(suggestion.isInitialAutoJoinEnabled);
+        assertNull(suggestion.getEnterpriseConfig());
     }
 
     /**
@@ -154,6 +169,7 @@
         assertEquals(-1, suggestion.wifiConfiguration.priority);
         assertTrue(suggestion.isUserAllowedToManuallyConnect);
         assertFalse(suggestion.isInitialAutoJoinEnabled);
+        assertNull(suggestion.getEnterpriseConfig());
     }
 
     /**
@@ -176,6 +192,63 @@
         assertTrue(suggestion.wifiConfiguration.requirePmf);
         assertFalse(suggestion.isUserAllowedToManuallyConnect);
         assertTrue(suggestion.isInitialAutoJoinEnabled);
+        assertNull(suggestion.getEnterpriseConfig());
+    }
+
+    /**
+     * Validate correctness of WifiNetworkSuggestion object created by
+     * {@link WifiNetworkSuggestion.Builder#build()} for OWE network.
+     */
+    @Test
+    public void testWifiNetworkSuggestionBuilderForOemPaidEnhancedOpenNetworkWithBssid() {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setBssid(MacAddress.fromString(TEST_BSSID))
+                .setOemPaid(true)
+                .setIsEnhancedOpen(true)
+                .build();
+
+        assertEquals("\"" + TEST_SSID + "\"", suggestion.wifiConfiguration.SSID);
+        assertEquals(TEST_BSSID, suggestion.wifiConfiguration.BSSID);
+        assertTrue(suggestion.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.OWE));
+        assertNull(suggestion.wifiConfiguration.preSharedKey);
+        assertTrue(suggestion.wifiConfiguration.requirePmf);
+        assertTrue(suggestion.wifiConfiguration.oemPaid);
+        assertTrue(suggestion.isOemPaid());
+        assertFalse(suggestion.isUserAllowedToManuallyConnect);
+        assertTrue(suggestion.isInitialAutoJoinEnabled);
+        assertNull(suggestion.getEnterpriseConfig());
+    }
+
+    /**
+     * Validate correctness of WifiNetworkSuggestion object created by
+     * {@link WifiNetworkSuggestion.Builder#build()} for OWE network.
+     */
+    @Test
+    public void testWifiNetworkSuggestionBuilderForOemPrivateEnhancedOpenNetworkWithBssid() {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setBssid(MacAddress.fromString(TEST_BSSID))
+                .setOemPrivate(true)
+                .setIsEnhancedOpen(true)
+                .build();
+
+        assertEquals("\"" + TEST_SSID + "\"", suggestion.wifiConfiguration.SSID);
+        assertEquals(TEST_BSSID, suggestion.wifiConfiguration.BSSID);
+        assertTrue(suggestion.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.OWE));
+        assertNull(suggestion.wifiConfiguration.preSharedKey);
+        assertTrue(suggestion.wifiConfiguration.requirePmf);
+        assertTrue(suggestion.wifiConfiguration.oemPrivate);
+        assertTrue(suggestion.isOemPrivate());
+        assertFalse(suggestion.isUserAllowedToManuallyConnect);
+        assertTrue(suggestion.isInitialAutoJoinEnabled);
+        assertNull(suggestion.getEnterpriseConfig());
     }
 
     /**
@@ -199,6 +272,7 @@
         assertTrue(suggestion.wifiConfiguration.requirePmf);
         assertTrue(suggestion.isUserAllowedToManuallyConnect);
         assertFalse(suggestion.isInitialAutoJoinEnabled);
+        assertNull(suggestion.getEnterpriseConfig());
     }
 
     /**
@@ -237,6 +311,119 @@
 
     /**
      * Validate correctness of WifiNetworkSuggestion object created by
+     * {@link WifiNetworkSuggestion.Builder#build()} for WPA3-Enterprise standard network.
+     */
+    @Test
+    public void testWifiNetworkSuggestionBuilderForWpa3EapNetworkWithStandardApi() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        enterpriseConfig.setCaCertificate(FakeKeys.CA_CERT0);
+        enterpriseConfig.setDomainSuffixMatch(TEST_DOMAIN_SUFFIX_MATCH);
+
+        WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setWpa3EnterpriseStandardModeConfig(enterpriseConfig)
+                .build();
+
+        assertEquals("\"" + TEST_SSID + "\"", suggestion.wifiConfiguration.SSID);
+        assertTrue(suggestion.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.IEEE8021X));
+        assertTrue(suggestion.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.WPA_EAP));
+        assertFalse(suggestion.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.SUITE_B_192));
+        assertTrue(suggestion.wifiConfiguration.allowedGroupCiphers
+                .get(WifiConfiguration.GroupCipher.CCMP));
+        assertTrue(suggestion.wifiConfiguration.requirePmf);
+        assertNull(suggestion.wifiConfiguration.preSharedKey);
+        // allowedSuiteBCiphers are set according to the loaded certificate and cannot be tested
+        // here.
+        assertTrue(suggestion.isUserAllowedToManuallyConnect);
+        assertTrue(suggestion.isInitialAutoJoinEnabled);
+        assertNotNull(suggestion.getEnterpriseConfig());
+    }
+
+    /**
+     * Validate correctness of WifiNetworkSuggestion object created by
+     * {@link WifiNetworkSuggestion.Builder#build()} for WPA3-Enterprise network
+     * with 192-bit RSA SuiteB certificates.
+     */
+    @Test
+    public void testWifiNetworkSuggestionBuilderForWpa3EapNetworkWithSuiteBRsaCerts() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        enterpriseConfig.setCaCertificate(FakeKeys.CA_SUITE_B_RSA3072_CERT);
+        enterpriseConfig.setClientKeyEntryWithCertificateChain(FakeKeys.CLIENT_SUITE_B_RSA3072_KEY,
+                new X509Certificate[] {FakeKeys.CLIENT_SUITE_B_RSA3072_CERT});
+
+        enterpriseConfig.setDomainSuffixMatch(TEST_DOMAIN_SUFFIX_MATCH);
+
+        WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setWpa3EnterpriseStandardModeConfig(enterpriseConfig)
+                .build();
+
+        assertEquals("\"" + TEST_SSID + "\"", suggestion.wifiConfiguration.SSID);
+        assertTrue(suggestion.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.IEEE8021X));
+        assertTrue(suggestion.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.WPA_EAP));
+        assertFalse(suggestion.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.SUITE_B_192));
+        assertTrue(suggestion.wifiConfiguration.allowedGroupCiphers
+                .get(WifiConfiguration.GroupCipher.CCMP));
+        assertTrue(suggestion.wifiConfiguration.requirePmf);
+        assertNull(suggestion.wifiConfiguration.preSharedKey);
+        // allowedSuiteBCiphers are set according to the loaded certificate and cannot be tested
+        // here.
+        assertTrue(suggestion.isUserAllowedToManuallyConnect);
+        assertTrue(suggestion.isInitialAutoJoinEnabled);
+        assertNotNull(suggestion.getEnterpriseConfig());
+    }
+
+    /**
+     * Validate correctness of WifiNetworkSuggestion object created by
+     * {@link WifiNetworkSuggestion.Builder#build()} for WPA3-Enterprise network
+     * with 192-bit ECC certificates.
+     */
+    @Test
+    public void testWifiNetworkSuggestionBuilderForWpa3EapNetworkWithSuiteBEccCerts() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        enterpriseConfig.setCaCertificate(FakeKeys.CA_SUITE_B_ECDSA_CERT);
+        enterpriseConfig.setClientKeyEntryWithCertificateChain(FakeKeys.CLIENT_SUITE_B_ECC_KEY,
+                new X509Certificate[] {FakeKeys.CLIENT_SUITE_B_ECDSA_CERT});
+
+        enterpriseConfig.setDomainSuffixMatch(TEST_DOMAIN_SUFFIX_MATCH);
+
+        WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setWpa3EnterpriseStandardModeConfig(enterpriseConfig)
+                .build();
+
+        assertEquals("\"" + TEST_SSID + "\"", suggestion.wifiConfiguration.SSID);
+        assertTrue(suggestion.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.IEEE8021X));
+        assertTrue(suggestion.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.WPA_EAP));
+        assertFalse(suggestion.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.SUITE_B_192));
+        assertTrue(suggestion.wifiConfiguration.allowedGroupCiphers
+                .get(WifiConfiguration.GroupCipher.CCMP));
+        assertTrue(suggestion.wifiConfiguration.requirePmf);
+        assertNull(suggestion.wifiConfiguration.preSharedKey);
+        // allowedSuiteBCiphers are set according to the loaded certificate and cannot be tested
+        // here.
+        assertTrue(suggestion.isUserAllowedToManuallyConnect);
+        assertTrue(suggestion.isInitialAutoJoinEnabled);
+        assertNotNull(suggestion.getEnterpriseConfig());
+    }
+
+    /**
+     * Validate correctness of WifiNetworkSuggestion object created by
      * {@link WifiNetworkSuggestion.Builder#build()} for WPA3-Enterprise 192-bit RSA SuiteB network.
      */
     @Test
@@ -272,6 +459,42 @@
 
     /**
      * Validate correctness of WifiNetworkSuggestion object created by
+     * {@link WifiNetworkSuggestion.Builder#build()} for WPA3-Enterprise 192-bit RSA SuiteB network.
+     */
+    @Test
+    public void testWifiNetworkSuggestionBuilderForWpa3SuiteBRsaEapNetworWith192BitApi() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        enterpriseConfig.setCaCertificate(FakeKeys.CA_SUITE_B_RSA3072_CERT);
+        enterpriseConfig.setClientKeyEntryWithCertificateChain(FakeKeys.CLIENT_SUITE_B_RSA3072_KEY,
+                new X509Certificate[] {FakeKeys.CLIENT_SUITE_B_RSA3072_CERT});
+
+        enterpriseConfig.setDomainSuffixMatch(TEST_DOMAIN_SUFFIX_MATCH);
+
+        WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setWpa3Enterprise192BitModeConfig(enterpriseConfig)
+                .build();
+
+        assertEquals("\"" + TEST_SSID + "\"", suggestion.wifiConfiguration.SSID);
+        assertTrue(suggestion.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.SUITE_B_192));
+        assertTrue(suggestion.wifiConfiguration.allowedGroupCiphers
+                .get(WifiConfiguration.GroupCipher.GCMP_256));
+        assertTrue(suggestion.wifiConfiguration.allowedGroupManagementCiphers
+                .get(WifiConfiguration.GroupMgmtCipher.BIP_GMAC_256));
+        assertTrue(suggestion.wifiConfiguration.requirePmf);
+        assertNull(suggestion.wifiConfiguration.preSharedKey);
+        // allowedSuiteBCiphers are set according to the loaded certificate and cannot be tested
+        // here.
+        assertTrue(suggestion.isUserAllowedToManuallyConnect);
+        assertTrue(suggestion.isInitialAutoJoinEnabled);
+        assertNotNull(suggestion.getEnterpriseConfig());
+    }
+
+    /**
+     * Validate correctness of WifiNetworkSuggestion object created by
      * {@link WifiNetworkSuggestion.Builder#build()} for WPA3-Enterprise 192-bit ECC SuiteB network.
      */
     @Test
@@ -302,6 +525,43 @@
         // here.
         assertTrue(suggestion.isUserAllowedToManuallyConnect);
         assertTrue(suggestion.isInitialAutoJoinEnabled);
+        assertNotNull(suggestion.getEnterpriseConfig());
+    }
+
+    /**
+     * Validate correctness of WifiNetworkSuggestion object created by
+     * {@link WifiNetworkSuggestion.Builder#build()} for WPA3-Enterprise 192-bit ECC SuiteB network.
+     */
+    @Test
+    public void testWifiNetworkSuggestionBuilderForWpa3SuiteBEccEapNetworkWith192BitApi() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        enterpriseConfig.setCaCertificate(FakeKeys.CA_SUITE_B_ECDSA_CERT);
+        enterpriseConfig.setClientKeyEntryWithCertificateChain(FakeKeys.CLIENT_SUITE_B_ECC_KEY,
+                new X509Certificate[] {FakeKeys.CLIENT_SUITE_B_ECDSA_CERT});
+
+        enterpriseConfig.setDomainSuffixMatch(TEST_DOMAIN_SUFFIX_MATCH);
+
+        WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setWpa3Enterprise192BitModeConfig(enterpriseConfig)
+                .build();
+
+        assertEquals("\"" + TEST_SSID + "\"", suggestion.wifiConfiguration.SSID);
+        assertTrue(suggestion.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.SUITE_B_192));
+        assertTrue(suggestion.wifiConfiguration.allowedGroupCiphers
+                .get(WifiConfiguration.GroupCipher.GCMP_256));
+        assertTrue(suggestion.wifiConfiguration.allowedGroupManagementCiphers
+                .get(WifiConfiguration.GroupMgmtCipher.BIP_GMAC_256));
+        assertTrue(suggestion.wifiConfiguration.requirePmf);
+        assertNull(suggestion.wifiConfiguration.preSharedKey);
+        // allowedSuiteBCiphers are set according to the loaded certificate and cannot be tested
+        // here.
+        assertTrue(suggestion.isUserAllowedToManuallyConnect);
+        assertTrue(suggestion.isInitialAutoJoinEnabled);
+        assertNotNull(suggestion.getEnterpriseConfig());
     }
 
     /**
@@ -327,6 +587,7 @@
      */
     @Test (expected = IllegalArgumentException.class)
     public void testWifiNetworkSuggestionBuilderForEapNetworkWithoutMatch() {
+        assumeTrue(SdkLevel.isAtLeastS());
         WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
         enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
         enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.GTC);
@@ -334,7 +595,7 @@
 
         WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
                 .setSsid(TEST_SSID)
-                .setWpa3EnterpriseConfig(enterpriseConfig)
+                .setWpa3EnterpriseStandardModeConfig(enterpriseConfig)
                 .build();
     }
 
@@ -358,6 +619,7 @@
                 .get(WifiConfiguration.GroupCipher.SMS4));
         assertEquals("\"" + TEST_PRESHARED_KEY + "\"",
                 suggestion.wifiConfiguration.preSharedKey);
+        assertNull(suggestion.getEnterpriseConfig());
     }
 
 
@@ -388,6 +650,7 @@
                 suggestion.wifiConfiguration.enterpriseConfig.getEapMethod());
         assertEquals(TEST_WAPI_CERT_SUITE,
                 suggestion.wifiConfiguration.enterpriseConfig.getWapiCertSuite());
+        assertNotNull(suggestion.getEnterpriseConfig());
     }
 
     /**
@@ -417,6 +680,7 @@
                 suggestion.wifiConfiguration.enterpriseConfig.getEapMethod());
         assertEquals("",
                 suggestion.wifiConfiguration.enterpriseConfig.getWapiCertSuite());
+        assertNotNull(suggestion.getEnterpriseConfig());
     }
 
     /**
@@ -439,6 +703,7 @@
         assertEquals(suggestion.getPasspointConfig().getMeteredOverride(),
                 WifiConfiguration.METERED_OVERRIDE_METERED);
         assertTrue(suggestion.isUserAllowedToManuallyConnect);
+        assertNull(suggestion.getEnterpriseConfig());
     }
 
     /**
@@ -552,15 +817,17 @@
     /**
      * Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception
      * when both {@link WifiNetworkSuggestion.Builder#setWpa3Passphrase(String)} and
-     * {@link WifiNetworkSuggestion.Builder#setWpa3EnterpriseConfig(WifiEnterpriseConfig)} are
-     * invoked.
+     * {@link WifiNetworkSuggestion.Builder
+     * #setWpa3EnterpriseStandardModeConfig(WifiEnterpriseConfig)}
+     * are invoked.
      */
     @Test(expected = IllegalStateException.class)
     public void testWifiNetworkSuggestionBuilderWithBothWpa3PasphraseAndEnterprise() {
+        assumeTrue(SdkLevel.isAtLeastS());
         new WifiNetworkSuggestion.Builder()
                 .setSsid(TEST_SSID)
                 .setWpa3Passphrase(TEST_PRESHARED_KEY)
-                .setWpa3EnterpriseConfig(new WifiEnterpriseConfig())
+                .setWpa3EnterpriseStandardModeConfig(new WifiEnterpriseConfig())
                 .build();
     }
 
@@ -622,15 +889,18 @@
 
     /**
      * Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception
-     * when both {@link WifiNetworkSuggestion.Builder#setWpa3EnterpriseConfig(WifiEnterpriseConfig)}
+     * when both
+     * {@link WifiNetworkSuggestion.Builder
+     * #setWpa3EnterpriseStandardModeConfig(WifiEnterpriseConfig)}
      * and {@link WifiNetworkSuggestion.Builder#setPasspointConfig(PasspointConfiguration)} are
      * invoked.
      */
     @Test(expected = IllegalStateException.class)
     public void testWifiNetworkSuggestionBuilderWithBothEnterpriseAndPasspointConfig() {
+        assumeTrue(SdkLevel.isAtLeastS());
         PasspointConfiguration passpointConfiguration = PasspointTestUtils.createConfig();
         new WifiNetworkSuggestion.Builder()
-                .setWpa3EnterpriseConfig(new WifiEnterpriseConfig())
+                .setWpa3EnterpriseStandardModeConfig(new WifiEnterpriseConfig())
                 .setPasspointConfig(passpointConfiguration)
                 .build();
     }
@@ -664,6 +934,98 @@
     }
 
     /**
+     * Verify that the macRandomizationSetting defaults to RANDOMIZATION_PERSISTENT and could be set
+     * to RANDOMIZATION_NON_PERSISTENT.
+     */
+    @Test
+    public void testWifiNetworkSuggestionBuilderSetMacRandomization() {
+        WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .build();
+        assertEquals(WifiConfiguration.RANDOMIZATION_PERSISTENT,
+                suggestion.wifiConfiguration.macRandomizationSetting);
+
+        suggestion = new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setMacRandomizationSetting(WifiNetworkSuggestion.RANDOMIZATION_PERSISTENT)
+                .build();
+        assertEquals(WifiConfiguration.RANDOMIZATION_PERSISTENT,
+                suggestion.wifiConfiguration.macRandomizationSetting);
+
+        suggestion = new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setMacRandomizationSetting(
+                        WifiNetworkSuggestion.RANDOMIZATION_NON_PERSISTENT)
+                .build();
+        assertEquals(WifiConfiguration.RANDOMIZATION_NON_PERSISTENT,
+                suggestion.wifiConfiguration.macRandomizationSetting);
+    }
+
+    /**
+     * Verify that the builder creates the appropriate PasspointConfiguration according to the
+     * enhanced MAC randomization setting.
+     */
+    @Test
+    public void testWifiNetworkSuggestionBuilderSetMacRandomizationPasspoint() {
+        PasspointConfiguration passpointConfiguration = PasspointTestUtils.createConfig();
+        WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+                .setPasspointConfig(passpointConfiguration)
+                .build();
+        assertEquals(false, suggestion.passpointConfiguration.isEnhancedMacRandomizationEnabled());
+
+        suggestion = new WifiNetworkSuggestion.Builder()
+                .setPasspointConfig(passpointConfiguration)
+                .setMacRandomizationSetting(
+                        WifiNetworkSuggestion.RANDOMIZATION_PERSISTENT)
+                .build();
+        assertEquals(false, suggestion.passpointConfiguration.isEnhancedMacRandomizationEnabled());
+
+        suggestion = new WifiNetworkSuggestion.Builder()
+                .setPasspointConfig(passpointConfiguration)
+                .setMacRandomizationSetting(
+                        WifiNetworkSuggestion.RANDOMIZATION_NON_PERSISTENT)
+                .build();
+        assertEquals(true, suggestion.passpointConfiguration.isEnhancedMacRandomizationEnabled());
+    }
+
+    /**
+     * Verify calling setMacRandomizationSetting with an invalid argument throws an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testWifiNetworkSuggestionBuilderSetMacRandomizationInvalidParam() {
+        WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setMacRandomizationSetting(-1234)
+                .build();
+    }
+
+    /**
+     * Verify that the builder creates the appropriate SIM credential suggestion with SubId, also
+     * verify {@link WifiNetworkSuggestion#equals(Object)} consider suggestion with different SubId
+     * as different suggestions.
+     */
+    @Test
+    public void testSimCredentialNetworkWithSubId() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.SIM);
+        enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.NONE);
+        WifiNetworkSuggestion suggestion1 = new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setWpa2EnterpriseConfig(enterpriseConfig)
+                .setSubscriptionId(1)
+                .build();
+        assertEquals(1, suggestion1.getSubscriptionId());
+        WifiNetworkSuggestion suggestion2 = new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setWpa2EnterpriseConfig(enterpriseConfig)
+                .setSubscriptionId(2)
+                .build();
+        assertEquals(2, suggestion2.getSubscriptionId());
+        assertNotEquals(suggestion1, suggestion2);
+    }
+
+    /**
      * Check that parcel marshalling/unmarshalling works
      */
     @Test
@@ -673,7 +1035,7 @@
         configuration.BSSID = TEST_BSSID;
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion(
-                configuration, null, false, true, true, true);
+                configuration, null, false, true, true, true, TEST_PRIORITY_GROUP);
 
         Parcel parcelW = Parcel.obtain();
         suggestion.writeToParcel(parcelW, 0);
@@ -744,14 +1106,16 @@
         configuration.BSSID = TEST_BSSID;
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
         WifiNetworkSuggestion suggestion =
-                new WifiNetworkSuggestion(configuration, null, true, false, true, true);
+                new WifiNetworkSuggestion(configuration, null, true, false, true, true,
+                        TEST_PRIORITY_GROUP);
 
         WifiConfiguration configuration1 = new WifiConfiguration();
         configuration1.SSID = TEST_SSID;
         configuration1.BSSID = TEST_BSSID;
         configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
         WifiNetworkSuggestion suggestion1 =
-                new WifiNetworkSuggestion(configuration1, null, false, true, true, true);
+                new WifiNetworkSuggestion(configuration1, null, false, true, true, true,
+                        DEFAULT_PRIORITY_GROUP);
 
         assertEquals(suggestion, suggestion1);
         assertEquals(suggestion.hashCode(), suggestion1.hashCode());
@@ -767,13 +1131,15 @@
         configuration.SSID = TEST_SSID;
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkSuggestion suggestion =
-                new WifiNetworkSuggestion(configuration, null, false, false, true, true);
+                new WifiNetworkSuggestion(configuration, null, false, false, true, true,
+                        DEFAULT_PRIORITY_GROUP);
 
         WifiConfiguration configuration1 = new WifiConfiguration();
         configuration1.SSID = TEST_SSID_1;
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkSuggestion suggestion1 =
-                new WifiNetworkSuggestion(configuration1, null, false, false, true, true);
+                new WifiNetworkSuggestion(configuration1, null, false, false, true, true,
+                        DEFAULT_PRIORITY_GROUP);
 
         assertNotEquals(suggestion, suggestion1);
     }
@@ -789,13 +1155,15 @@
         configuration.BSSID = TEST_BSSID;
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkSuggestion suggestion =
-                new WifiNetworkSuggestion(configuration, null,  false, false, true, true);
+                new WifiNetworkSuggestion(configuration, null, false, false, true, true,
+                        DEFAULT_PRIORITY_GROUP);
 
         WifiConfiguration configuration1 = new WifiConfiguration();
         configuration1.SSID = TEST_SSID;
         configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkSuggestion suggestion1 =
-                new WifiNetworkSuggestion(configuration1, null, false, false, true, true);
+                new WifiNetworkSuggestion(configuration1, null, false, false, true, true,
+                        DEFAULT_PRIORITY_GROUP);
 
         assertNotEquals(suggestion, suggestion1);
     }
@@ -810,13 +1178,15 @@
         configuration.SSID = TEST_SSID;
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkSuggestion suggestion =
-                new WifiNetworkSuggestion(configuration, null, false, false, true, true);
+                new WifiNetworkSuggestion(configuration, null, false, false, true, true,
+                        DEFAULT_PRIORITY_GROUP);
 
         WifiConfiguration configuration1 = new WifiConfiguration();
         configuration1.SSID = TEST_SSID;
         configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
         WifiNetworkSuggestion suggestion1 =
-                new WifiNetworkSuggestion(configuration1, null, false, false, true, true);
+                new WifiNetworkSuggestion(configuration1, null, false, false, true, true,
+                        DEFAULT_PRIORITY_GROUP);
 
         assertNotEquals(suggestion, suggestion1);
     }
@@ -932,6 +1302,78 @@
     }
 
     /**
+     * Validate {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} set the
+     * correct value to the WifiConfiguration.
+     */
+    @Test
+    public void testSetIsNetworkAsOemPaid() {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setWpa2Passphrase(TEST_PRESHARED_KEY)
+                .setOemPaid(true)
+                .build();
+        assertTrue(suggestion.isOemPaid());
+        assertFalse(suggestion.isUserAllowedToManuallyConnect);
+    }
+
+    /**
+     * Validate {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} set the
+     * correct value to the WifiConfiguration.
+     * Also the {@link WifiNetworkSuggestion#isUserAllowedToManuallyConnect} should be false;
+     */
+    @Test
+    public void testSetIsNetworkAsOemPaidOnPasspointNetwork() {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        PasspointConfiguration passpointConfiguration = PasspointTestUtils.createConfig();
+        WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+                .setPasspointConfig(passpointConfiguration)
+                .setOemPaid(true)
+                .build();
+        assertTrue(suggestion.isOemPaid());
+        assertFalse(suggestion.isUserAllowedToManuallyConnect);
+        assertTrue(suggestion.getPasspointConfig().isOemPaid());
+    }
+
+    /**
+     * Validate {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} set the
+     * correct value to the WifiConfiguration.
+     */
+    @Test
+    public void testSetIsNetworkAsOemPrivate() {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setWpa2Passphrase(TEST_PRESHARED_KEY)
+                .setOemPrivate(true)
+                .build();
+        assertTrue(suggestion.isOemPrivate());
+        assertFalse(suggestion.isUserAllowedToManuallyConnect);
+    }
+
+    /**
+     * Validate {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} set the
+     * correct value to the WifiConfiguration.
+     * Also the {@link WifiNetworkSuggestion#isUserAllowedToManuallyConnect} should be false;
+     */
+    @Test
+    public void testSetIsNetworkAsOemPrivateOnPasspointNetwork() {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        PasspointConfiguration passpointConfiguration = PasspointTestUtils.createConfig();
+        WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+                .setPasspointConfig(passpointConfiguration)
+                .setOemPrivate(true)
+                .build();
+        assertTrue(suggestion.isOemPrivate());
+        assertFalse(suggestion.isUserAllowedToManuallyConnect);
+        assertTrue(suggestion.getPasspointConfig().isOemPrivate());
+    }
+
+    /**
      * Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception
      * when set {@link WifiNetworkSuggestion.Builder#setUntrusted(boolean)} to true and
      * set {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} to true
@@ -949,6 +1391,42 @@
 
     /**
      * Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception
+     * when set {@link WifiNetworkSuggestion.Builder#setOemPaid(boolean)} to true and
+     * set {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} to true
+     * together.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testSetCredentialSharedWithUserWithSetIsNetworkAsOemPaid() {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setWpa2Passphrase(TEST_PRESHARED_KEY)
+                .setCredentialSharedWithUser(true)
+                .setOemPaid(true)
+                .build();
+    }
+
+    /**
+     * Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception
+     * when set {@link WifiNetworkSuggestion.Builder#setOemPrivate(boolean)} to true and
+     * set {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} to true
+     * together.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testSetCredentialSharedWithUserWithSetIsNetworkAsOemPrivate() {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setWpa2Passphrase(TEST_PRESHARED_KEY)
+                .setCredentialSharedWithUser(true)
+                .setOemPrivate(true)
+                .build();
+    }
+
+    /**
+     * Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception
      * when set both {@link WifiNetworkSuggestion.Builder#setIsInitialAutojoinEnabled(boolean)}
      * and {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} (boolean)}
      * to false on a passpoint suggestion.
@@ -962,4 +1440,172 @@
                 .setIsInitialAutojoinEnabled(false)
                 .build();
     }
+
+    /**
+     * Validate {@link WifiNetworkSuggestion.Builder#setCarrierMerged(boolean)} (boolean)} set the
+     * correct value to the WifiConfiguration.
+     */
+    @Test
+    public void testSetCarrierMergedNetwork() {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        enterpriseConfig.setCaCertificate(FakeKeys.CA_CERT0);
+        enterpriseConfig.setDomainSuffixMatch(TEST_DOMAIN_SUFFIX_MATCH);
+        WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setSubscriptionId(1)
+                .setWpa2EnterpriseConfig(enterpriseConfig)
+                .setCarrierMerged(true)
+                .setIsMetered(true)
+                .build();
+        assertTrue(suggestion.isCarrierMerged());
+        assertTrue(suggestion.wifiConfiguration.carrierMerged);
+    }
+
+    /**
+     * Validate {@link WifiNetworkSuggestion.Builder#setCarrierMerged(boolean)} (boolean)} set the
+     * correct value to the passpoint network.
+     */
+    @Test
+    public void testSetCarrierMergedNetworkOnPasspointNetwork() {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        PasspointConfiguration passpointConfiguration = PasspointTestUtils.createConfig();
+        WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+                .setPasspointConfig(passpointConfiguration)
+                .setSubscriptionId(1)
+                .setCarrierMerged(true)
+                .setIsMetered(true)
+                .build();
+        assertTrue(suggestion.isCarrierMerged());
+        assertTrue(suggestion.getPasspointConfig().isCarrierMerged());
+        assertEquals(1, suggestion.getPasspointConfig().getSubscriptionId());
+
+        passpointConfiguration.setSubscriptionId(4);
+        assertEquals(1, suggestion.getPasspointConfig().getSubscriptionId());
+    }
+
+    /**
+     * Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception
+     * when set both {@link WifiNetworkSuggestion.Builder#setCarrierMerged(boolean)} (boolean)}
+     * to true on a network is not metered.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testSetCarrierMergedNetworkOnUnmeteredNetworkFail() {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        enterpriseConfig.setCaCertificate(FakeKeys.CA_CERT0);
+        enterpriseConfig.setDomainSuffixMatch(TEST_DOMAIN_SUFFIX_MATCH);
+        new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setSubscriptionId(1)
+                .setWpa2EnterpriseConfig(enterpriseConfig)
+                .setCarrierMerged(true)
+                .build();
+    }
+
+    /**
+     * Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception
+     * when set both {@link WifiNetworkSuggestion.Builder#setCarrierMerged(boolean)} (boolean)}
+     * to true on a network which {@link WifiNetworkSuggestion.Builder#setSubscriptionId(int)}
+     * is not set with a valid sub id.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testSetCarrierMergedNetworkWithoutValidSubscriptionIdFail() {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        enterpriseConfig.setCaCertificate(FakeKeys.CA_CERT0);
+        enterpriseConfig.setDomainSuffixMatch(TEST_DOMAIN_SUFFIX_MATCH);
+        WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setWpa2EnterpriseConfig(enterpriseConfig)
+                .setCarrierMerged(true)
+                .setIsMetered(true)
+                .build();
+        assertTrue(suggestion.isCarrierMerged());
+    }
+
+    /**
+     * Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception
+     * when set both {@link WifiNetworkSuggestion.Builder#setCarrierMerged(boolean)} (boolean)}
+     * to true on a non enterprise network.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testSetCarrierMergedNetworkWithNonEnterpriseNetworkFail() {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setSubscriptionId(1)
+                .setWpa2Passphrase(TEST_PRESHARED_KEY)
+                .setCarrierMerged(true)
+                .setIsMetered(true)
+                .build();
+        assertTrue(suggestion.isCarrierMerged());
+    }
+
+    /**
+     * Ensure {@link WifiNetworkSuggestion.Builder#setSubscriptionId(int)} throws an exception when
+     * Subscription ID is invalid.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetInvalidSubscriptionId() {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+                .setSubscriptionId(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+                .build();
+    }
+
+    /**
+     * Test set and get carrier Id
+     */
+    @Test
+    public void testSetCarrierId() {
+        WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setWpa2Passphrase(TEST_PRESHARED_KEY)
+                .setCarrierId(TEST_CARRIER_ID)
+                .build();
+
+        assertEquals(TEST_CARRIER_ID, suggestion.getCarrierId());
+    }
+
+    /**
+     * Test set and get SAE Hash-to-Element only mode for WPA3 SAE network.
+     */
+    @Test
+    public void testSetSaeH2eOnlyModeForWpa3Sae() {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setWpa3Passphrase(TEST_PRESHARED_KEY)
+                .setIsWpa3SaeH2eOnlyModeEnabled(true)
+                .build();
+        assertTrue(suggestion.getWifiConfiguration().getSecurityParamsList()
+                .stream().anyMatch(param -> param.isSaeH2eOnlyMode()));
+    }
+
+    /**
+     * Test set and get SAE Hash-to-Element only mode for WPA2 PSK network.
+     * For non-WPA3 SAE network, enabling H2E only mode should raise
+     * IllegalStateException.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testSetSaeH2eOnlyModeForWpa2Psk() {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setWpa2Passphrase(TEST_PRESHARED_KEY)
+                .setIsWpa3SaeH2eOnlyModeEnabled(true)
+                .build();
+    }
 }
diff --git a/framework/tests/src/android/net/wifi/WifiScannerTest.java b/framework/tests/src/android/net/wifi/WifiScannerTest.java
index b68616f..5e7e459 100644
--- a/framework/tests/src/android/net/wifi/WifiScannerTest.java
+++ b/framework/tests/src/android/net/wifi/WifiScannerTest.java
@@ -22,6 +22,8 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -47,6 +49,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.util.test.BidirectionalAsyncChannelServer;
+import com.android.modules.utils.build.SdkLevel;
 
 import org.junit.After;
 import org.junit.Before;
@@ -227,7 +230,8 @@
         assertEquals(writeScanData.getId(), readScanData.getId());
         assertEquals(writeScanData.getFlags(), readScanData.getFlags());
         assertEquals(writeScanData.getBucketsScanned(), readScanData.getBucketsScanned());
-        assertEquals(writeScanData.getBandScanned(), readScanData.getBandScanned());
+        assertEquals(writeScanData.getScannedBandsInternal(),
+                readScanData.getScannedBandsInternal());
         assertArrayEquals(writeScanData.getResults(), readScanData.getResults());
     }
 
@@ -241,6 +245,25 @@
         return ScanData.CREATOR.createFromParcel(parcel);
     }
 
+    /**
+     * Verify #setRnrSetting with valid and invalid inputs.
+     */
+    @Test
+    public void testSetRnrSetting() throws Exception {
+        // First verify IllegalArgumentException if an invalid input is passed in.
+        assumeTrue(SdkLevel.isAtLeastS());
+        try {
+            WifiScanner.ScanSettings scanSettings = new WifiScanner.ScanSettings();
+            scanSettings.setRnrSetting(-1);
+            fail("Excepted IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+        }
+
+        // Then verify calling the API with a valid input.
+        WifiScanner.ScanSettings scanSettings = new WifiScanner.ScanSettings();
+        scanSettings.setRnrSetting(WifiScanner.WIFI_RNR_NOT_NEEDED);
+        assertEquals(WifiScanner.WIFI_RNR_NOT_NEEDED, scanSettings.getRnrSetting());
+    }
 
     /**
      * Test behavior of {@link WifiScanner#startScan(ScanSettings, ScanListener)}
diff --git a/framework/tests/src/android/net/wifi/WifiUsabilityStatsEntryTest.java b/framework/tests/src/android/net/wifi/WifiUsabilityStatsEntryTest.java
index 5184152..4cdd617 100644
--- a/framework/tests/src/android/net/wifi/WifiUsabilityStatsEntryTest.java
+++ b/framework/tests/src/android/net/wifi/WifiUsabilityStatsEntryTest.java
@@ -17,8 +17,12 @@
 package android.net.wifi;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 import static org.mockito.Mockito.validateMockitoUsage;
 
+import android.net.wifi.WifiUsabilityStatsEntry.ContentionTimeStats;
+import android.net.wifi.WifiUsabilityStatsEntry.RadioStats;
+import android.net.wifi.WifiUsabilityStatsEntry.RateStats;
 import android.os.Parcel;
 
 import androidx.test.filters.SmallTest;
@@ -28,6 +32,8 @@
 import org.junit.Test;
 import org.mockito.MockitoAnnotations;
 
+import java.util.NoSuchElementException;
+
 
 /**
  * Unit tests for {@link android.net.wifi.WifiUsabilityStatsEntry}.
@@ -62,6 +68,43 @@
     }
 
     /**
+     * Verify parcel read/write for Wifi usability stats result.
+     */
+    @Test
+    public void getTimeSliceDutyCycleInPercent() throws Exception {
+        ContentionTimeStats[] contentionTimeStats = new ContentionTimeStats[4];
+        contentionTimeStats[0] = new ContentionTimeStats(1, 2, 3, 4);
+        contentionTimeStats[1] = new ContentionTimeStats(5, 6, 7, 8);
+        contentionTimeStats[2] = new ContentionTimeStats(9, 10, 11, 12);
+        contentionTimeStats[3] = new ContentionTimeStats(13, 14, 15, 16);
+        RateStats[] rateStats = new RateStats[2];
+        rateStats[0] = new RateStats(1, 3, 4, 7, 9, 11, 13, 15, 17);
+        rateStats[1] = new RateStats(2, 2, 3, 8, 10, 12, 14, 16, 18);
+
+        RadioStats[] radioStats = new RadioStats[2];
+        radioStats[0] = new RadioStats(0, 10, 11, 12, 13, 14, 15, 16, 17, 18);
+        radioStats[1] = new RadioStats(1, 20, 21, 22, 23, 24, 25, 26, 27, 28);
+
+        WifiUsabilityStatsEntry usabilityStatsEntry = new WifiUsabilityStatsEntry(
+                0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
+                32, contentionTimeStats, rateStats, radioStats, 100, true,
+                true, true, 23, 24, 25, true);
+        assertEquals(32, usabilityStatsEntry.getTimeSliceDutyCycleInPercent());
+
+        WifiUsabilityStatsEntry usabilityStatsEntryWithInvalidDutyCycleValue =
+                new WifiUsabilityStatsEntry(
+                        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
+                        21, 22, -1, contentionTimeStats, rateStats, radioStats, 101, true, true,
+                        true, 23, 24, 25, true);
+        try {
+            usabilityStatsEntryWithInvalidDutyCycleValue.getTimeSliceDutyCycleInPercent();
+            fail();
+        } catch (NoSuchElementException e) {
+            // pass
+        }
+    }
+
+    /**
      * Write the provided {@link WifiUsabilityStatsEntry} to a parcel and deserialize it.
      */
     private static WifiUsabilityStatsEntry parcelWriteRead(
@@ -73,9 +116,23 @@
     }
 
     private static WifiUsabilityStatsEntry createResult() {
+        ContentionTimeStats[] contentionTimeStats = new ContentionTimeStats[4];
+        contentionTimeStats[0] = new ContentionTimeStats(1, 2, 3, 4);
+        contentionTimeStats[1] = new ContentionTimeStats(5, 6, 7, 8);
+        contentionTimeStats[2] = new ContentionTimeStats(9, 10, 11, 12);
+        contentionTimeStats[3] = new ContentionTimeStats(13, 14, 15, 16);
+        RateStats[] rateStats = new RateStats[2];
+        rateStats[0] = new RateStats(1, 3, 4, 7, 9, 11, 13, 15, 17);
+        rateStats[1] = new RateStats(2, 2, 3, 8, 10, 12, 14, 16, 18);
+
+        RadioStats[] radioStats = new RadioStats[2];
+        radioStats[0] = new RadioStats(0, 10, 11, 12, 13, 14, 15, 16, 17, 18);
+        radioStats[1] = new RadioStats(1, 20, 21, 22, 23, 24, 25, 26, 27, 28);
+
         return new WifiUsabilityStatsEntry(
-                0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
-                14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, true
+                0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
+                50, contentionTimeStats, rateStats, radioStats, 102, true,
+                true, true, 23, 24, 25, true
         );
     }
 
@@ -89,6 +146,32 @@
         assertEquals(expected.getTotalTxRetries(), actual.getTotalTxRetries());
         assertEquals(expected.getTotalTxBad(), actual.getTotalTxBad());
         assertEquals(expected.getTotalRxSuccess(), actual.getTotalRxSuccess());
+        assertEquals(expected.getWifiLinkLayerRadioStats().size(),
+                actual.getWifiLinkLayerRadioStats().size());
+        for (int i = 0; i < expected.getWifiLinkLayerRadioStats().size(); i++) {
+            RadioStats expectedRadioStats = expected.getWifiLinkLayerRadioStats().get(i);
+            RadioStats actualRadioStats = actual.getWifiLinkLayerRadioStats().get(i);
+            assertEquals(expectedRadioStats.getRadioId(),
+                    actualRadioStats.getRadioId());
+            assertEquals(expectedRadioStats.getTotalRadioOnTimeMillis(),
+                    actualRadioStats.getTotalRadioOnTimeMillis());
+            assertEquals(expectedRadioStats.getTotalRadioTxTimeMillis(),
+                    actualRadioStats.getTotalRadioTxTimeMillis());
+            assertEquals(expectedRadioStats.getTotalRadioRxTimeMillis(),
+                    actualRadioStats.getTotalRadioRxTimeMillis());
+            assertEquals(expectedRadioStats.getTotalScanTimeMillis(),
+                    actualRadioStats.getTotalScanTimeMillis());
+            assertEquals(expectedRadioStats.getTotalNanScanTimeMillis(),
+                    actualRadioStats.getTotalNanScanTimeMillis());
+            assertEquals(expectedRadioStats.getTotalBackgroundScanTimeMillis(),
+                    actualRadioStats.getTotalBackgroundScanTimeMillis());
+            assertEquals(expectedRadioStats.getTotalRoamScanTimeMillis(),
+                    actualRadioStats.getTotalRoamScanTimeMillis());
+            assertEquals(expectedRadioStats.getTotalPnoScanTimeMillis(),
+                    actualRadioStats.getTotalPnoScanTimeMillis());
+            assertEquals(expectedRadioStats.getTotalHotspot2ScanTimeMillis(),
+                    actualRadioStats.getTotalHotspot2ScanTimeMillis());
+        }
         assertEquals(expected.getTotalRadioOnTimeMillis(), actual.getTotalRadioOnTimeMillis());
         assertEquals(expected.getTotalRadioTxTimeMillis(), actual.getTotalRadioTxTimeMillis());
         assertEquals(expected.getTotalRadioRxTimeMillis(), actual.getTotalRadioRxTimeMillis());
@@ -112,6 +195,106 @@
         assertEquals(expected.getProbeMcsRateSinceLastUpdate(),
                 actual.getProbeMcsRateSinceLastUpdate());
         assertEquals(expected.getRxLinkSpeedMbps(), actual.getRxLinkSpeedMbps());
+        assertEquals(expected.getTimeSliceDutyCycleInPercent(),
+                actual.getTimeSliceDutyCycleInPercent());
+        assertEquals(
+                expected.getContentionTimeStats(WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_BE)
+                        .getContentionTimeMinMicros(),
+                actual.getContentionTimeStats(WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_BE)
+                        .getContentionTimeMinMicros());
+        assertEquals(
+                expected.getContentionTimeStats(WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_BE)
+                        .getContentionTimeMaxMicros(),
+                actual.getContentionTimeStats(WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_BE)
+                        .getContentionTimeMaxMicros());
+        assertEquals(
+                expected.getContentionTimeStats(WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_BE)
+                        .getContentionTimeAvgMicros(),
+                actual.getContentionTimeStats(WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_BE)
+                        .getContentionTimeAvgMicros());
+        assertEquals(
+                expected.getContentionTimeStats(WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_BE)
+                        .getContentionNumSamples(),
+                actual.getContentionTimeStats(WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_BE)
+                        .getContentionNumSamples());
+        assertEquals(
+                expected.getContentionTimeStats(WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_BK)
+                        .getContentionTimeMinMicros(),
+                actual.getContentionTimeStats(WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_BK)
+                        .getContentionTimeMinMicros());
+        assertEquals(
+                expected.getContentionTimeStats(WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_BK)
+                        .getContentionTimeMaxMicros(),
+                actual.getContentionTimeStats(WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_BK)
+                        .getContentionTimeMaxMicros());
+        assertEquals(
+                expected.getContentionTimeStats(WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_BK)
+                        .getContentionTimeAvgMicros(),
+                actual.getContentionTimeStats(WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_BK)
+                        .getContentionTimeAvgMicros());
+        assertEquals(
+                expected.getContentionTimeStats(WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_BK)
+                        .getContentionNumSamples(),
+                actual.getContentionTimeStats(WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_BK)
+                        .getContentionNumSamples());
+        assertEquals(
+                expected.getContentionTimeStats(WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_VI)
+                        .getContentionTimeMinMicros(),
+                actual.getContentionTimeStats(WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_VI)
+                        .getContentionTimeMinMicros());
+        assertEquals(
+                expected.getContentionTimeStats(WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_VI)
+                        .getContentionTimeMaxMicros(),
+                actual.getContentionTimeStats(WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_VI)
+                        .getContentionTimeMaxMicros());
+        assertEquals(
+                expected.getContentionTimeStats(WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_VI)
+                        .getContentionTimeAvgMicros(),
+                actual.getContentionTimeStats(WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_VI)
+                        .getContentionTimeAvgMicros());
+        assertEquals(
+                expected.getContentionTimeStats(WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_VI)
+                        .getContentionNumSamples(),
+                actual.getContentionTimeStats(WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_VI)
+                        .getContentionNumSamples());
+        assertEquals(
+                expected.getContentionTimeStats(WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_VO)
+                        .getContentionTimeMinMicros(),
+                actual.getContentionTimeStats(WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_VO)
+                        .getContentionTimeMinMicros());
+        assertEquals(
+                expected.getContentionTimeStats(WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_VO)
+                        .getContentionTimeMaxMicros(),
+                actual.getContentionTimeStats(WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_VO)
+                        .getContentionTimeMaxMicros());
+        assertEquals(
+                expected.getContentionTimeStats(WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_VO)
+                        .getContentionTimeAvgMicros(),
+                actual.getContentionTimeStats(WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_VO)
+                        .getContentionTimeAvgMicros());
+        assertEquals(
+                expected.getContentionTimeStats(WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_VO)
+                        .getContentionNumSamples(),
+                actual.getContentionTimeStats(WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_VO)
+                        .getContentionNumSamples());
+        for (int i = 0; i < expected.getRateStats().size(); i++) {
+            RateStats expectedStats = expected.getRateStats().get(i);
+            RateStats actualStats = actual.getRateStats().get(i);
+            assertEquals(expectedStats.getPreamble(), actualStats.getPreamble());
+            assertEquals(expectedStats.getNumberOfSpatialStreams(),
+                    actualStats.getNumberOfSpatialStreams());
+            assertEquals(expectedStats.getBandwidthInMhz(), actualStats.getBandwidthInMhz());
+            assertEquals(expectedStats.getRateMcsIdx(), actualStats.getRateMcsIdx());
+            assertEquals(expectedStats.getBitRateInKbps(), actualStats.getBitRateInKbps());
+            assertEquals(expectedStats.getTxMpdu(), actualStats.getTxMpdu());
+            assertEquals(expectedStats.getRxMpdu(), actualStats.getRxMpdu());
+            assertEquals(expectedStats.getMpduLost(), actualStats.getMpduLost());
+            assertEquals(expectedStats.getRetries(), actualStats.getRetries());
+        }
+        assertEquals(expected.getChannelUtilizationRatio(), actual.getChannelUtilizationRatio());
+        assertEquals(expected.isThroughputSufficient(), actual.isThroughputSufficient());
+        assertEquals(expected.isWifiScoringEnabled(), actual.isWifiScoringEnabled());
+        assertEquals(expected.isCellularDataAvailable(), actual.isCellularDataAvailable());
         assertEquals(expected.getCellularDataNetworkType(), actual.getCellularDataNetworkType());
         assertEquals(expected.getCellularSignalStrengthDbm(),
                 actual.getCellularSignalStrengthDbm());
diff --git a/framework/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java b/framework/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
index 43d728b..f2bdb3c 100644
--- a/framework/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
+++ b/framework/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
@@ -16,11 +16,13 @@
 
 package android.net.wifi.aware;
 
+import static android.net.wifi.aware.WifiAwareManager.WIFI_AWARE_DISCOVERY_LOST_REASON_PEER_NOT_VISIBLE;
 import static android.net.wifi.aware.WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB;
 
 import static org.hamcrest.core.IsEqual.equalTo;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -45,6 +47,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.modules.utils.build.SdkLevel;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -146,6 +150,38 @@
         verify(mockAwareService).getCharacteristics();
     }
 
+    /**
+     * Validate pass-through of isDeviceAttached() API.
+     */
+    @Test
+    public void testIsAttached() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        mDut.isDeviceAttached();
+        verify(mockAwareService).isDeviceAttached();
+    }
+
+    /**
+     * Validate pass-through of isInstantCommunicationModeEnabled() and
+     * enableInstantCommunicationMode() API
+     */
+    @Test
+    public void testEnableInstantCommunicationMode() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        mDut.isInstantCommunicationModeEnabled();
+        verify(mockAwareService).isInstantCommunicationModeEnabled();
+        mDut.enableInstantCommunicationMode(true);
+        verify(mockAwareService).enableInstantCommunicationMode(anyString(), eq(true));
+    }
+
+    /**
+     * Validate pass-through of getAvailableAwareResources() API.
+     */
+    @Test
+    public void testGetAvailableAwareResource() throws Exception {
+        mDut.getAvailableAwareResources();
+        verify(mockAwareService).getAvailableAwareResources();
+    }
+
     /*
      * WifiAwareEventCallbackProxy Tests
      */
@@ -360,12 +396,19 @@
                 eq(publishConfig));
         inOrder.verify(mockSessionCallback).onSessionConfigFailed();
 
-        // (5) terminate
+        // (5) discovery session is no longer visible
+        sessionProxyCallback.getValue().onMatchExpired(peerHandle.peerId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onServiceLost(peerIdCaptor.capture(),
+                eq(WIFI_AWARE_DISCOVERY_LOST_REASON_PEER_NOT_VISIBLE));
+        assertEquals(peerHandle.peerId, peerIdCaptor.getValue().peerId);
+
+        // (6) terminate
         publishSession.getValue().close();
         mMockLooper.dispatchAll();
         inOrder.verify(mockAwareService).terminateSession(clientId, sessionId);
 
-        // (6) try an update (nothing)
+        // (7) try an update (nothing)
         publishSession.getValue().updatePublish(publishConfig);
         mMockLooper.dispatchAll();
 
@@ -502,12 +545,19 @@
                 eq(subscribeConfig));
         inOrder.verify(mockSessionCallback).onSessionConfigFailed();
 
-        // (5) terminate
+        // (5) discovery session is no longer visible
+        sessionProxyCallback.getValue().onMatchExpired(peerHandle.peerId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onServiceLost(peerIdCaptor.capture(),
+                eq(WIFI_AWARE_DISCOVERY_LOST_REASON_PEER_NOT_VISIBLE));
+        assertEquals(peerHandle.peerId, peerIdCaptor.getValue().peerId);
+
+        // (6) terminate
         subscribeSession.getValue().close();
         mMockLooper.dispatchAll();
         inOrder.verify(mockAwareService).terminateSession(clientId, sessionId);
 
-        // (6) try an update (nothing)
+        // (7) try an update (nothing)
         subscribeSession.getValue().updateSubscribe(subscribeConfig);
         mMockLooper.dispatchAll();
 
diff --git a/framework/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java b/framework/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
index 8270d64..e25f882 100644
--- a/framework/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
+++ b/framework/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
@@ -18,10 +18,13 @@
 
 import static android.net.wifi.WifiConfiguration.METERED_OVERRIDE_NONE;
 
+import static junit.framework.Assert.assertNull;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import android.net.wifi.EAPConstants;
 import android.net.wifi.FakeKeys;
@@ -31,6 +34,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.modules.utils.build.SdkLevel;
+
 import org.junit.Test;
 
 import java.nio.charset.StandardCharsets;
@@ -50,6 +55,7 @@
 public class PasspointConfigurationTest {
     private static final int MAX_URL_BYTES = 1023;
     private static final int CERTIFICATE_FINGERPRINT_BYTES = 32;
+    private static final String TEST_DECORATED_IDENTITY_PREFIX = "androidwifi.dev!";
 
     /**
      * Verify parcel write and read consistency for the given configuration.
@@ -186,6 +192,7 @@
         assertFalse(config.validateForR2());
         assertTrue(config.isAutojoinEnabled());
         assertTrue(config.isMacRandomizationEnabled());
+        assertFalse(config.isEnhancedMacRandomizationEnabled());
         assertTrue(config.getMeteredOverride() == METERED_OVERRIDE_NONE);
     }
 
@@ -623,4 +630,32 @@
         config.setCredential(null);
         String uniqueId = config.getUniqueId();
     }
+
+    /**
+     * Verify that the set and get decorated identity prefix methods work as expected.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testSetGetDecoratedIdentityPrefix() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        PasspointConfiguration config = new PasspointConfiguration();
+
+        assertNull(config.getDecoratedIdentityPrefix());
+        config.setDecoratedIdentityPrefix(TEST_DECORATED_IDENTITY_PREFIX);
+        assertEquals(TEST_DECORATED_IDENTITY_PREFIX, config.getDecoratedIdentityPrefix());
+    }
+
+    /**
+     * Verify that the set decorated identity prefix doesn't accept a malformed input.
+     *
+     * @throws Exception
+     */
+    @Test (expected = IllegalArgumentException.class)
+    public void testSetDecoratedIdentityPrefixWithInvalidValue() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        PasspointConfiguration config = new PasspointConfiguration();
+
+        config.setDecoratedIdentityPrefix(TEST_DECORATED_IDENTITY_PREFIX.replace('!', 'a'));
+    }
 }
diff --git a/framework/tests/src/android/net/wifi/hotspot2/PasspointTestUtils.java b/framework/tests/src/android/net/wifi/hotspot2/PasspointTestUtils.java
index 8d55acb..5830a1e 100644
--- a/framework/tests/src/android/net/wifi/hotspot2/PasspointTestUtils.java
+++ b/framework/tests/src/android/net/wifi/hotspot2/PasspointTestUtils.java
@@ -30,6 +30,7 @@
 
 public class PasspointTestUtils {
     private static final int CERTIFICATE_FINGERPRINT_BYTES = 32;
+    private static final int TEST_SUB_ID = 1;
 
     /**
      * Utility function for creating a {@link android.net.wifi.hotspot2.pps.HomeSP}.
@@ -156,6 +157,7 @@
         friendlyNames.put("en", "ServiceName1");
         friendlyNames.put("kr", "ServiceName2");
         config.setServiceFriendlyNames(friendlyNames);
+        config.setSubscriptionId(TEST_SUB_ID);
         return config;
     }
 
diff --git a/framework/tests/src/android/net/wifi/hotspot2/omadm/PpsMoParserTest.java b/framework/tests/src/android/net/wifi/hotspot2/omadm/PpsMoParserTest.java
index 1ac9cb8..266899c 100644
--- a/framework/tests/src/android/net/wifi/hotspot2/omadm/PpsMoParserTest.java
+++ b/framework/tests/src/android/net/wifi/hotspot2/omadm/PpsMoParserTest.java
@@ -27,6 +27,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.modules.utils.build.SdkLevel;
+
 import org.junit.Test;
 
 import java.io.BufferedReader;
@@ -113,7 +115,8 @@
 
         // Subscription parameters.
         config.setSubscriptionCreationTimeInMillis(format.parse("2016-02-01T10:00:00Z").getTime());
-        config.setSubscriptionExpirationTimeInMillis(format.parse("2016-03-01T10:00:00Z").getTime());
+        config.setSubscriptionExpirationTimeInMillis(
+                format.parse("2016-03-01T10:00:00Z").getTime());
         config.setSubscriptionType("Gold");
         config.setUsageLimitDataLimit(921890);
         config.setUsageLimitStartTimeInMillis(format.parse("2016-12-01T10:00:00Z").getTime());
@@ -124,20 +127,17 @@
         HomeSp homeSp = new HomeSp();
         homeSp.setFriendlyName("Century House");
         homeSp.setFqdn("mi6.co.uk");
-        homeSp.setRoamingConsortiumOis(new long[] {0x112233L, 0x445566L});
+        homeSp.setRoamingConsortiumOis(new long[]{0x112233L, 0x445566L});
         homeSp.setIconUrl("icon.test.com");
         Map<String, Long> homeNetworkIds = new HashMap<>();
         homeNetworkIds.put("TestSSID", 0x12345678L);
         homeNetworkIds.put("NullHESSID", null);
         homeSp.setHomeNetworkIds(homeNetworkIds);
-        homeSp.setMatchAllOis(new long[] {0x11223344});
-        homeSp.setMatchAnyOis(new long[] {0x55667788});
-        homeSp.setOtherHomePartners(new String[] {"other.fqdn.com"});
+        homeSp.setMatchAllOis(new long[]{0x11223344});
+        homeSp.setMatchAnyOis(new long[]{0x55667788});
+        homeSp.setOtherHomePartners(new String[]{"other.fqdn.com"});
         config.setHomeSp(homeSp);
 
-        config.setAaaServerTrustedNames(
-                new String[] {"trusted.fqdn.com", "another-trusted.fqdn.com"});
-
         // Credential configuration.
         Credential credential = new Credential();
         credential.setCreationTimeInMillis(format.parse("2016-01-01T10:00:00Z").getTime());
@@ -183,7 +183,7 @@
         policy.setMinHomeUplinkBandwidth(9823);
         policy.setMinRoamingDownlinkBandwidth(9271);
         policy.setMinRoamingUplinkBandwidth(2315);
-        policy.setExcludedSsidList(new String[] {"excludeSSID"});
+        policy.setExcludedSsidList(new String[]{"excludeSSID"});
         Map<Integer, String> requiredProtoPortMap = new HashMap<>();
         requiredProtoPortMap.put(12, "34,92,234");
         policy.setRequiredProtoPortMap(requiredProtoPortMap);
@@ -199,6 +199,16 @@
         policyUpdate.setTrustRootCertSha256Fingerprint(certFingerprint);
         policy.setPolicyUpdate(policyUpdate);
         config.setPolicy(policy);
+
+        // Extensions
+        // Android
+        config.setAaaServerTrustedNames(
+                new String[]{"trusted.fqdn.com", "another-trusted.fqdn.com"});
+
+        // WBA
+        if (SdkLevel.isAtLeastS()) {
+            config.setDecoratedIdentityPrefix("androidwifi.dev!");
+        }
         return config;
     }
 
diff --git a/framework/tests/src/android/net/wifi/hotspot2/pps/HomeSpTest.java b/framework/tests/src/android/net/wifi/hotspot2/pps/HomeSpTest.java
index 93d471a..fe889fc 100644
--- a/framework/tests/src/android/net/wifi/hotspot2/pps/HomeSpTest.java
+++ b/framework/tests/src/android/net/wifi/hotspot2/pps/HomeSpTest.java
@@ -16,6 +16,7 @@
 
 package android.net.wifi.hotspot2.pps;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
@@ -26,7 +27,9 @@
 import org.junit.Test;
 
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -35,6 +38,7 @@
  */
 @SmallTest
 public class HomeSpTest {
+    private static final String[] OTHER_HOME_PARTNER_LIST = new String[]{"partner1", "partner2"};
 
     /**
      * Helper function for creating a map of home network IDs for testing.
@@ -62,7 +66,7 @@
         homeSp.setHomeNetworkIds(homeNetworkIds);
         homeSp.setMatchAllOis(new long[] {0x11L, 0x22L});
         homeSp.setMatchAnyOis(new long[] {0x33L, 0x44L});
-        homeSp.setOtherHomePartners(new String[] {"partner1", "partner2"});
+        homeSp.setOtherHomePartners(OTHER_HOME_PARTNER_LIST);
         homeSp.setRoamingConsortiumOis(new long[] {0x55, 0x66});
         return homeSp;
     }
@@ -218,4 +222,34 @@
         HomeSp copySp = new HomeSp(sourceSp);
         assertTrue(copySp.equals(sourceSp));
     }
+
+    /**
+     * Verify that the getOtherHomePartnersList gets the list of partners as expected.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateGetOtherHomePartnersList() throws Exception {
+        HomeSp homeSp = createHomeSpWithoutHomeNetworkIds();
+
+        Collection<String> otherHomePartnersList = homeSp.getOtherHomePartnersList();
+        assertEquals(2, otherHomePartnersList.size());
+        assertTrue(Arrays.equals(OTHER_HOME_PARTNER_LIST, otherHomePartnersList.toArray()));
+    }
+
+    /**
+     * Verify that the setOtherHomePartnersList sets the list of partners as expected.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateSetOtherHomePartnersList() throws Exception {
+        HomeSp homeSp = createHomeSpWithoutHomeNetworkIds();
+
+        final Collection<String> homePartners =
+                new ArrayList<>(Arrays.asList(OTHER_HOME_PARTNER_LIST));
+
+        homeSp.setOtherHomePartnersList(homePartners);
+        assertTrue(Arrays.equals(homeSp.getOtherHomePartners(), OTHER_HOME_PARTNER_LIST));
+    }
 }
diff --git a/framework/tests/src/android/net/wifi/p2p/WifiP2pWfdInfoTest.java b/framework/tests/src/android/net/wifi/p2p/WifiP2pWfdInfoTest.java
index 2a9b36b..8a29a1c 100644
--- a/framework/tests/src/android/net/wifi/p2p/WifiP2pWfdInfoTest.java
+++ b/framework/tests/src/android/net/wifi/p2p/WifiP2pWfdInfoTest.java
@@ -17,12 +17,16 @@
 package android.net.wifi.p2p;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import android.os.Parcel;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.modules.utils.build.SdkLevel;
+
 import org.junit.Before;
 import org.junit.Test;
 
@@ -77,6 +81,41 @@
     }
 
     /**
+     * Verify WFD R2 setters/getters.
+     */
+    @Test
+    public void testWfdR2SetterGetter() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        WifiP2pWfdInfo info = new WifiP2pWfdInfo();
+        assertFalse(info.isR2Supported());
+        info.setR2DeviceType(WifiP2pWfdInfo.DEVICE_TYPE_WFD_SOURCE);
+        assertEquals(WifiP2pWfdInfo.DEVICE_TYPE_WFD_SOURCE, info.getR2DeviceType());
+
+        info.setR2DeviceInfo(0x10f);
+        assertEquals(0x10f, info.getR2DeviceInfo());
+
+        assertTrue(info.isR2Supported());
+    }
+
+    /**
+     * Verify coupled-sink usage APIs.
+     */
+    @Test
+    public void testCoupledSinkUsage() throws Exception {
+        WifiP2pWfdInfo info = new WifiP2pWfdInfo();
+
+        info.setCoupledSinkSupportAtSink(true);
+        assertTrue(info.isCoupledSinkSupportedAtSink());
+        info.setCoupledSinkSupportAtSink(false);
+        assertFalse(info.isCoupledSinkSupportedAtSink());
+
+        info.setCoupledSinkSupportAtSource(true);
+        assertTrue(info.isCoupledSinkSupportedAtSource());
+        info.setCoupledSinkSupportAtSource(false);
+        assertFalse(info.isCoupledSinkSupportedAtSource());
+    }
+
+    /**
      * Verifies copy constructor.
      */
     @Test
diff --git a/framework/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java b/framework/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java
index e6eae41..f83a579 100644
--- a/framework/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java
+++ b/framework/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java
@@ -16,6 +16,8 @@
 
 package android.net.wifi.rtt;
 
+import static junit.framework.Assert.fail;
+
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
@@ -83,7 +85,7 @@
         List<RangingResult> results = new ArrayList<>();
         results.add(
                 new RangingResult(RangingResult.STATUS_SUCCESS, MacAddress.BROADCAST_ADDRESS, 15, 5,
-                        10, 8, 5, null, null, null, 666));
+                        10, 8, 5, null, null, null, 666, true));
         RangingResultCallback callbackMock = mock(RangingResultCallback.class);
         ArgumentCaptor<IRttCallback> callbackCaptor = ArgumentCaptor.forClass(IRttCallback.class);
 
@@ -132,10 +134,13 @@
         // Note: not validating parcel code of ScanResult (assumed to work)
         ScanResult scanResult1 = new ScanResult();
         scanResult1.BSSID = "00:01:02:03:04:05";
+        scanResult1.setFlag(ScanResult.FLAG_80211mc_RESPONDER);
         ScanResult scanResult2 = new ScanResult();
         scanResult2.BSSID = "06:07:08:09:0A:0B";
+        scanResult2.setFlag(ScanResult.FLAG_80211mc_RESPONDER);
         ScanResult scanResult3 = new ScanResult();
         scanResult3.BSSID = "AA:BB:CC:DD:EE:FF";
+        scanResult3.setFlag(ScanResult.FLAG_80211mc_RESPONDER);
         List<ScanResult> scanResults2and3 = new ArrayList<>(2);
         scanResults2and3.add(scanResult2);
         scanResults2and3.add(scanResult3);
@@ -147,6 +152,7 @@
         builder.addAccessPoints(scanResults2and3);
         builder.addWifiAwarePeer(mac1);
         builder.addWifiAwarePeer(peerHandle1);
+        builder.setRttBurstSize(4);
         RangingRequest request = builder.build();
 
         Parcel parcelW = Parcel.obtain();
@@ -163,19 +169,110 @@
     }
 
     /**
+     * Validate the rtt burst size is set correctly when in range.
+     */
+    @Test
+    public void test802llmcCapableAccessPointFailsForNon11mcBuilderMethods() {
+        ScanResult scanResult1 = new ScanResult();
+        scanResult1.BSSID = "AA:BB:CC:DD:EE:FF";
+        scanResult1.setFlag(ScanResult.FLAG_80211mc_RESPONDER);
+
+        // create request for one AP
+        try {
+            RangingRequest.Builder builder = new RangingRequest.Builder();
+            builder.addNon80211mcCapableAccessPoint(scanResult1);
+            fail("Single Access Point was 11mc capable.");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        ScanResult scanResult2 = new ScanResult();
+        scanResult2.BSSID = "11:BB:CC:DD:EE:FF";
+        scanResult2.setFlag(ScanResult.FLAG_80211mc_RESPONDER);
+        List<ScanResult> scanResults = new ArrayList<>();
+        scanResults.add(scanResult1);
+        scanResults.add(scanResult2);
+
+        // create request for a list of 2 APs.
+        try {
+            RangingRequest.Builder builder = new RangingRequest.Builder();
+            builder.addNon80211mcCapableAccessPoints(scanResults);
+            fail("One Access Point in the List was 11mc capable.");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    /**
+     * Validate the rtt burst size is set correctly when in range.
+     */
+    @Test
+    public void testRangingRequestSetBurstSize() {
+        ScanResult scanResult = new ScanResult();
+        scanResult.BSSID = "AA:BB:CC:DD:EE:FF";
+        scanResult.setFlag(ScanResult.FLAG_80211mc_RESPONDER);
+
+        // create request
+        RangingRequest.Builder builder = new RangingRequest.Builder();
+        builder.setRttBurstSize(4);
+        builder.addAccessPoint(scanResult);
+        RangingRequest request = builder.build();
+
+        // confirm rtt burst size is set correctly to default value
+        assertEquals(request.getRttBurstSize(), 4);
+    }
+
+    /**
+     * Validate the rtt burst size cannot be smaller than the minimum.
+     */
+    @Test
+    public void testRangingRequestMinBurstSizeIsEnforced() {
+        ScanResult scanResult = new ScanResult();
+        scanResult.BSSID = "AA:BB:CC:DD:EE:FF";
+
+        // create request
+        try {
+            RangingRequest.Builder builder = new RangingRequest.Builder();
+            builder.setRttBurstSize(RangingRequest.getMinRttBurstSize() - 1);
+            fail("RTT burst size was smaller than min value.");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    /**
+     * Validate the rtt burst size cannot exceed the maximum.
+     */
+    @Test
+    public void testRangingRequestMaxBurstSizeIsEnforced() {
+        ScanResult scanResult = new ScanResult();
+        scanResult.BSSID = "AA:BB:CC:DD:EE:FF";
+
+        // create request
+        try {
+            RangingRequest.Builder builder = new RangingRequest.Builder();
+            builder.setRttBurstSize(RangingRequest.getMaxRttBurstSize() + 1);
+            fail("RTT Burst size exceeded max value.");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    /**
      * Validate that can request as many range operation as the upper limit on number of requests.
      */
     @Test
     public void testRangingRequestAtLimit() {
         ScanResult scanResult = new ScanResult();
         scanResult.BSSID = "AA:BB:CC:DD:EE:FF";
+        scanResult.setFlag(ScanResult.FLAG_80211mc_RESPONDER);
         List<ScanResult> scanResultList = new ArrayList<>();
         for (int i = 0; i < RangingRequest.getMaxPeers() - 3; ++i) {
             scanResultList.add(scanResult);
         }
         MacAddress mac1 = MacAddress.fromString("00:01:02:03:04:05");
 
-        // create request
+        // create request using max RTT Peers
         RangingRequest.Builder builder = new RangingRequest.Builder();
         builder.addAccessPoint(scanResult);
         builder.addAccessPoints(scanResultList);
@@ -185,6 +282,18 @@
 
         // verify request
         request.enforceValidity(true);
+        // confirm rtt burst size is set correctly to default value
+        assertEquals(request.getRttBurstSize(), RangingRequest.getDefaultRttBurstSize());
+        // confirm the number of peers in the request is the max number of peers
+        List<ResponderConfig> rttPeers = request.getRttResponders();
+        int numRttPeers = rttPeers.size();
+        assertEquals(RangingRequest.getMaxPeers(), numRttPeers);
+        // confirm each peer has the correct mac address
+        for (int i = 0; i < numRttPeers - 1; ++i) {
+            assertEquals("AA:BB:CC:DD:EE:FF", rttPeers.get(i).macAddress.toString().toUpperCase());
+            assertEquals("00:01:02:03:04:05",
+                    rttPeers.get(numRttPeers - 1).macAddress.toString().toUpperCase());
+        }
     }
 
     /**
@@ -245,7 +354,8 @@
 
         // RangingResults constructed with a MAC address
         RangingResult result = new RangingResult(status, mac, distanceCm, distanceStdDevCm, rssi,
-                numAttemptedMeasurements, numSuccessfulMeasurements, lci, lcr, null, timestamp);
+                numAttemptedMeasurements, numSuccessfulMeasurements, lci, lcr, null, timestamp,
+                true);
 
         Parcel parcelW = Parcel.obtain();
         result.writeToParcel(parcelW, 0);
@@ -294,9 +404,11 @@
         byte[] lcr = { };
 
         RangingResult rr1 = new RangingResult(status, mac, distanceCm, distanceStdDevCm, rssi,
-                numAttemptedMeasurements, numSuccessfulMeasurements, lci, lcr, null, timestamp);
+                numAttemptedMeasurements, numSuccessfulMeasurements, lci, lcr, null, timestamp,
+                true);
         RangingResult rr2 = new RangingResult(status, mac, distanceCm, distanceStdDevCm, rssi,
-                numAttemptedMeasurements, numSuccessfulMeasurements, null, null, null, timestamp);
+                numAttemptedMeasurements, numSuccessfulMeasurements, null, null, null, timestamp,
+                true);
 
         assertEquals(rr1, rr2);
     }
diff --git a/metrics_pdd_hook.py b/metrics_pdd_hook.py
index fa85855..65d467c 100755
--- a/metrics_pdd_hook.py
+++ b/metrics_pdd_hook.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 #
 # Copyright 2020, The Android Open Source Project
@@ -28,8 +28,8 @@
 
     for branch in branches:
         # current branch starts with a '*'
-        if branch.startswith('*'):
-            return '[aosp/' in branch
+        if branch.startswith(b'*'):
+            return b'[aosp/' in branch
 
     # otherwise assume in AOSP
     return True
diff --git a/service/Android.bp b/service/Android.bp
index 14aaa98..4f7a1d8 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -38,6 +38,7 @@
         ":framework-wifi-service-shared-srcs",
         ":net-utils-wifi-service-common-srcs",
         ":statslog-wifi-java-gen",
+        ":coex-table-parser",
     ],
 }
 
@@ -58,7 +59,8 @@
 
     sdk_version: "system_server_current",
     lint: {
-    	baseline_filename: "lint-baseline-pre-jarjar.xml",
+        baseline_filename: "lint-baseline-pre-jarjar.xml",
+        strict_updatability_linting: true,
     },
     libs: [
         "error_prone_annotations",
@@ -81,13 +83,16 @@
         "android.hardware.wifi-V1.2-java",
         "android.hardware.wifi-V1.3-java",
         "android.hardware.wifi-V1.4-java",
+        "android.hardware.wifi-V1.5-java",
         "android.hardware.wifi.hostapd-V1.0-java",
         "android.hardware.wifi.hostapd-V1.1-java",
         "android.hardware.wifi.hostapd-V1.2-java",
+        "android.hardware.wifi.hostapd-V1.3-java",
         "android.hardware.wifi.supplicant-V1.0-java",
         "android.hardware.wifi.supplicant-V1.1-java",
         "android.hardware.wifi.supplicant-V1.2-java",
         "android.hardware.wifi.supplicant-V1.3-java",
+        "android.hardware.wifi.supplicant-V1.4-java",
         "android.hidl.manager-V1.2-java",
         "androidx.annotation_annotation",
         "bouncycastle-unbundled",
@@ -101,7 +106,6 @@
         "services.net-module-wifi",
         "wifi-lite-protos",
         "wifi-nano-protos",
-        "modules-utils-os",
     ],
 }
 
diff --git a/service/ServiceWifiResources/AndroidManifest.xml b/service/ServiceWifiResources/AndroidManifest.xml
index 6d637cb..04ba1d3 100644
--- a/service/ServiceWifiResources/AndroidManifest.xml
+++ b/service/ServiceWifiResources/AndroidManifest.xml
@@ -22,6 +22,7 @@
           coreApp="true"
           android:versionCode="1"
           android:versionName="R-initial">
+    <protected-broadcast android:name="com.android.server.wifi.action.CarrierNetwork.USER_CLICKED" />
     <application
         android:label="@string/wifiResourcesAppLabel"
         android:defaultToDeviceProtectedStorage="true"
diff --git a/service/ServiceWifiResources/res/values-af/strings.xml b/service/ServiceWifiResources/res/values-af/strings.xml
index b109ca9..f332710 100644
--- a/service/ServiceWifiResources/res/values-af/strings.xml
+++ b/service/ServiceWifiResources/res/values-af/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"Netwerke wat deur <xliff:g id="NAME">%s</xliff:g> voorgestel is. Toestel sal dalk outomaties koppel."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Laat toe"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Nee, dankie"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Koppel aan <xliff:g id="CARRIERNAME">%s</xliff:g>-Wi‑Fi?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Hierdie netwerke ontvang \'n SIM-ID wat gebruik kan word om toestelligging na te spoor"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Koppel"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Moenie koppel nie"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Bevestig verbinding?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"As jy koppel, kan <xliff:g id="CARRIERNAME">%s</xliff:g> se Wi‑Fi-netwerke toegang kry tot \'n unieke ID wat met jou SIM geassosieer word, of dit deel. Dit kan dit vir hulle moontlik maak om jou toestel se ligging na te spoor."</string>
diff --git a/service/ServiceWifiResources/res/values-am/strings.xml b/service/ServiceWifiResources/res/values-am/strings.xml
index d6ab7b2..d8c3494 100644
--- a/service/ServiceWifiResources/res/values-am/strings.xml
+++ b/service/ServiceWifiResources/res/values-am/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"በ<xliff:g id="NAME">%s</xliff:g> የተጠቆሙ አውታረ መረቦች። መሣሪያ በራስ-ሰር ሊገናኝ ይችላል።"</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"ፍቀድ"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"አይ፣ አመሰግናለሁ"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"ከ<xliff:g id="CARRIERNAME">%s</xliff:g> Wi-Fi ጋር ይገናኙ?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"እነዚህ አውታረ መረቦች የመሣሪያ አካባቢን ለመከታተል ሥራ ላይ ሊውል የሚችል የሲም መታወቂያ ተቀብለዋል።"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"አገናኝ"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"አታገናኝ"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"ግንኙነት ይረጋገጥ?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"ከተገናኙ የ<xliff:g id="CARRIERNAME">%s</xliff:g> Wi‑Fi አውትረ መረቦች ከእርስዎ ሲም ጋር የተጎዳኘ ልዩ መታወቂያ ሊደርሱ ወይም ሊያጋሩ ይችላሉ። ይህ የመሣሪያዎ አካባቢ ክትትል እንዲደረግበት ሊያስችል ይችላል።"</string>
diff --git a/service/ServiceWifiResources/res/values-ar/strings.xml b/service/ServiceWifiResources/res/values-ar/strings.xml
index f6833c3..9d7c002 100644
--- a/service/ServiceWifiResources/res/values-ar/strings.xml
+++ b/service/ServiceWifiResources/res/values-ar/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"شبكات <xliff:g id="NAME">%s</xliff:g> المقترحة - قد يتم توصيل الجهاز تلقائيًا."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"سماح"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"لا، شكرًا"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"‏هل تريد ربط <xliff:g id="CARRIERNAME">%s</xliff:g> بشبكة Wi-Fi؟"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"‏تتلقّى هذه الشبكات رقم تعريف لشريحة SIM يمكن استخدامه لتتبُّع الموقع الجغرافي للجهاز."</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"ربط"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"عدم الاتصال"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"هل تريد تأكيد الاتصال بالشبكة؟"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"‏في حال اتصال بالشبكة، يمكن لشبكات Wi‑Fi من <xliff:g id="CARRIERNAME">%s</xliff:g> الوصول إلى المعرّف الفريد المرتبط بشريحة SIM أو مشاركته. قد يسمح هذا بتتبُّع الموقع الجغرافي لجهازك."</string>
diff --git a/service/ServiceWifiResources/res/values-as/strings.xml b/service/ServiceWifiResources/res/values-as/strings.xml
index 44ae47a..dc6e1d6 100644
--- a/service/ServiceWifiResources/res/values-as/strings.xml
+++ b/service/ServiceWifiResources/res/values-as/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g>এ পৰামর্শ হিচাপে দিয়া নেটৱর্কবোৰ। ডিভাইচটো স্বয়ংক্ৰিয়ভাৱে সংযোগ হ\'ব পাৰে।"</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"অনুমতি দিয়ক"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"নালাগে, ধন্যবাদ"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"<xliff:g id="CARRIERNAME">%s</xliff:g> ৱাই-ফাইৰ সৈতে সংযোগ কৰিবনে?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"এই নেটৱৰ্কটোৱে এটা ছিম আইডি পায়, যিটো ডিভাইচৰ অৱস্থান ট্ৰেক কৰিবলৈ ব্যৱহাৰ কৰিব পাৰি"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"সংযোগ কৰক"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"সংযোগ নকৰিব"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"সংযোগ নিশ্চিত কৰিবনে?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"আপুনি যদি সংযোগ কৰে, <xliff:g id="CARRIERNAME">%s</xliff:g>ৰ ৱাই-ফাই নেটৱৰ্কসমূহে আপোনাৰ ছিমৰ সৈতে জড়িত এটা সুকীয়া আইডি এক্সেছ কৰিব অথবা সেইটো শ্বেয়াৰ কৰিব পাৰে। এইটোৱে আপোনাৰ ডিভাইচটোৰ অৱস্থান ট্ৰেক কৰাৰ অনুমতি দিব পাৰে।"</string>
diff --git a/service/ServiceWifiResources/res/values-az/strings.xml b/service/ServiceWifiResources/res/values-az/strings.xml
index f122570..af0378a 100644
--- a/service/ServiceWifiResources/res/values-az/strings.xml
+++ b/service/ServiceWifiResources/res/values-az/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> təklif edilən şəbəkə. Cihaz avtomatik qoşula bilər."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"İcazə verin"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Xeyr, təşəkkürlər"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"<xliff:g id="CARRIERNAME">%s</xliff:g> Wi-Fi şəbəkəsinə qoşulsun?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Bu şəbəkələr cihaz məkanını izləmək üçün istifadə edilə biləcək SIM ID qəbul edir"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Qoşun"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Qoşulmayın"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Bağlantı təsdiq edilsin?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Qoşulsanız, <xliff:g id="CARRIERNAME">%s</xliff:g> Wi‑Fi şəbəkələri SIM kartınızla əlaqəli unikal ID\'ə daxil ola və ya onu paylaşa bilər. Bu, cihazınızın məkanının izlənilməsinə icazə verə bilər."</string>
diff --git a/service/ServiceWifiResources/res/values-b+sr+Latn/strings.xml b/service/ServiceWifiResources/res/values-b+sr+Latn/strings.xml
index a2093f4..60ed190 100644
--- a/service/ServiceWifiResources/res/values-b+sr+Latn/strings.xml
+++ b/service/ServiceWifiResources/res/values-b+sr+Latn/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"Mreže koje predlaže <xliff:g id="NAME">%s</xliff:g>. Uređaj će se možda povezati automatski."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Dozvoli"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Ne, hvala"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Želite da se povežete na WiFi mrežu <xliff:g id="CARRIERNAME">%s</xliff:g>?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Ove mreže dobijaju ID SIM kartice koji može da se koristi za praćenje lokacije uređaja"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Poveži"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Ne povezuj"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Želite li da potvrdite povezivanje?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Ako se povežete, Wi‑Fi mreže operatera <xliff:g id="CARRIERNAME">%s</xliff:g> mogu da pristupaju jedinstvenom ID-u povezanom sa SIM karticom ili da ga dele. To može da omogući praćenje lokacije uređaja."</string>
diff --git a/service/ServiceWifiResources/res/values-be/strings.xml b/service/ServiceWifiResources/res/values-be/strings.xml
index a21290a..c124b49 100644
--- a/service/ServiceWifiResources/res/values-be/strings.xml
+++ b/service/ServiceWifiResources/res/values-be/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"Праграма \"<xliff:g id="NAME">%s</xliff:g>\" прапанавала сеткі. Прылада можа падключыцца да ніх аўтаматычна."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Дазволіць"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Не, дзякуй"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Падключыцца да Wi-Fi аператара \"<xliff:g id="CARRIERNAME">%s</xliff:g>\"?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Гэтыя сеткі атрымліваюць ідэнтыфікатар SIM-карты, які можа выкарыстоўвацца для адсочвання месцазнаходжання прылады"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Падключыцца"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Не падключацца"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Пацвердзіць падключэнне?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Калі вы падключыцеся, сеткі Wi‑Fi аператара \"<xliff:g id="CARRIERNAME">%s</xliff:g>\" змогуць атрымаць доступ да ўнікальнага ідэнтыфікатара, звязанага з вашай SIM-картай, ці абагуліць яго. Можа ўзнікнуць магчымасць адсочваць месцазнаходжанне вашай прылады."</string>
diff --git a/service/ServiceWifiResources/res/values-bg/strings.xml b/service/ServiceWifiResources/res/values-bg/strings.xml
index bfe834a..9b46cdc 100644
--- a/service/ServiceWifiResources/res/values-bg/strings.xml
+++ b/service/ServiceWifiResources/res/values-bg/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"Предложени от <xliff:g id="NAME">%s</xliff:g> мрежи. Устройството може да се свърже автоматично."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Разрешаване"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Не, благодаря"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Искате ли да се свържете с Wi-Fi от <xliff:g id="CARRIERNAME">%s</xliff:g>?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Тези мрежи получават идентификатор за SIM карта, който може да се използва за проследяване на местоположението на устройството"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Свързване"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Без свързване"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Потвърждавате ли връзката?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Ако се свържете, Wi-Fi мрежите на <xliff:g id="CARRIERNAME">%s</xliff:g> може да имат достъп или да споделят уникален идентификатор, свързан със SIM картата ви. Това може да позволи проследяването на местоположението на устройството ви."</string>
diff --git a/service/ServiceWifiResources/res/values-bn/strings.xml b/service/ServiceWifiResources/res/values-bn/strings.xml
index ba3c2b9..f6786af 100644
--- a/service/ServiceWifiResources/res/values-bn/strings.xml
+++ b/service/ServiceWifiResources/res/values-bn/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g>-এর সাজেস্ট করা নেটওয়ার্ক। ডিভাইস নিজে থেকে কানেক্ট হতে পারে।"</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"অনুমতি দিন"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"না থাক"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"<xliff:g id="CARRIERNAME">%s</xliff:g> ওয়াই-ফাইতে কানেক্ট করবেন?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"এই নেটওয়ার্কগুলির কাছে একটি সিম আইডি থাকে যা ব্যবহার করে এই নেটওয়ার্কের সাথে যুক্ত থাকা ডিভাইসের লোকেশন ট্র্যাক করা যেত পারে"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"কানেক্ট করুন"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"কানেক্ট করবেন না"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"কানেকশন কনফার্ম করতে চান?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"আপনি কানেক্ট করলে, <xliff:g id="CARRIERNAME">%s</xliff:g> ওয়াই-ফাই নেটওয়ার্ক আপনার সিমের সাথে সম্পর্কযুক্ত একটি অনন্য আইডি অ্যাক্সেস বা শেয়ার করতে পারে। এর ফলে আপনার ডিভাইসের লোকেশন ট্র্যাক করা যেতে পারে।"</string>
diff --git a/service/ServiceWifiResources/res/values-bs/strings.xml b/service/ServiceWifiResources/res/values-bs/strings.xml
index 31dd23b..8e47a11 100644
--- a/service/ServiceWifiResources/res/values-bs/strings.xml
+++ b/service/ServiceWifiResources/res/values-bs/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"Mreže koje predlaže <xliff:g id="NAME">%s</xliff:g>. Uređaj će se možda povezati automatski."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Dozvoli"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Ne, hvala"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Povezati se s WiFi mrežom operatera <xliff:g id="CARRIERNAME">%s</xliff:g>?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Ove mreže primaju ID SIM-a koji se može koristiti za praćenje lokacije uređaja"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Poveži"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Nemoj se povezati"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Potvrditi povezivanje?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Ako se povežete, Wi-Fi mreže mobilnog operatera <xliff:g id="CARRIERNAME">%s</xliff:g> mogu pristupiti jedinstvenom ID-u koji je povezan s vašom SIM karticom ili ga podijeliti. Na taj način možete dozvoliti da se prati lokacija vašeg uređaja."</string>
diff --git a/service/ServiceWifiResources/res/values-ca/strings.xml b/service/ServiceWifiResources/res/values-ca/strings.xml
index 2c71cf2..c62e0ab 100644
--- a/service/ServiceWifiResources/res/values-ca/strings.xml
+++ b/service/ServiceWifiResources/res/values-ca/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"Xarxes suggerides de l\'aplicació <xliff:g id="NAME">%s</xliff:g>. El dispositiu pot connectar-se automàticament."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Permet"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"No, gràcies"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Vols connectar-te a la Wi‑Fi de l\'operador <xliff:g id="CARRIERNAME">%s</xliff:g>?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Aquestes xarxes reben un identificador de SIM que es pot utilitzar per fer el seguiment de la ubicació del dispositiu"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Connecta"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"No et connectis"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Vols confirmar la connexió?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Si et connectes, les xarxes Wi‑Fi de l\'operador <xliff:g id="CARRIERNAME">%s</xliff:g> poden compartir un identificador únic associat a la teva SIM o bé accedir-hi. Això pot permetre que es faci un seguiment de la ubicació del teu dispositiu."</string>
diff --git a/service/ServiceWifiResources/res/values-cs/strings.xml b/service/ServiceWifiResources/res/values-cs/strings.xml
index c2b6cfb..d802261 100644
--- a/service/ServiceWifiResources/res/values-cs/strings.xml
+++ b/service/ServiceWifiResources/res/values-cs/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"Sítě navrhované aplikací <xliff:g id="NAME">%s</xliff:g>. Zařízení se může připojovat automaticky."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Povolit"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Ne, díky"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Připojit k Wi-Fi <xliff:g id="CARRIERNAME">%s</xliff:g>?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Tyto sítě dostávají identifikátor SIM karty, pomocí něhož lze sledovat polohu zařízení"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Připojit"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Nepřipojovat"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Potvrdit připojení?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Pokud se připojíte, sítě Wi-Fi operátora <xliff:g id="CARRIERNAME">%s</xliff:g> mohou získat přístup k jedinečnému ID přidruženému k vaší SIM kartě. Může to umožnit sledování polohy zařízení."</string>
diff --git a/service/ServiceWifiResources/res/values-da/strings.xml b/service/ServiceWifiResources/res/values-da/strings.xml
index 7566aff..c823271 100644
--- a/service/ServiceWifiResources/res/values-da/strings.xml
+++ b/service/ServiceWifiResources/res/values-da/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"Netværk foreslået af <xliff:g id="NAME">%s</xliff:g>. Enheden opretter muligvis forbindelse automatisk."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Tillad"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Nej tak"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Opret forbindelse til <xliff:g id="CARRIERNAME">%s</xliff:g>-Wi-Fi"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Disse netværk kræver et SIM-id, der kan bruges til at spore enhedens placering"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Opret forbindelse"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Opret ikke forbindelse"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Vil du bekræfte forbindelsen?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Hvis du opretter forbindelse, kan Wi-Fi-netværk fra <xliff:g id="CARRIERNAME">%s</xliff:g> få adgang til eller dele et unikt id, der er knyttet til dit SIM-kort. Derved kan din enheds placering muligvis spores."</string>
diff --git a/service/ServiceWifiResources/res/values-de/strings.xml b/service/ServiceWifiResources/res/values-de/strings.xml
index 9a920c0..6811bf0 100644
--- a/service/ServiceWifiResources/res/values-de/strings.xml
+++ b/service/ServiceWifiResources/res/values-de/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"Von <xliff:g id="NAME">%s</xliff:g> vorgeschlagene Netzwerke. Gerät verbindet sich möglicherweise automatisch."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Zulassen"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Nein danke"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Mit <xliff:g id="CARRIERNAME">%s</xliff:g>-WLAN verbinden?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Diese Netzwerke empfangen eine SIM-ID, mit der der Gerätestandort erfasst werden kann"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Verbinden"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Nicht verbinden"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Verbindung bestätigen?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Wenn du eine Verbindung herstellst, können die WLANs von <xliff:g id="CARRIERNAME">%s</xliff:g> möglicherweise eine eindeutige ID abrufen oder teilen, die deiner SIM zugewiesen ist. Damit lässt sich unter Umständen der Standort deines Geräts ermitteln."</string>
diff --git a/service/ServiceWifiResources/res/values-el/strings.xml b/service/ServiceWifiResources/res/values-el/strings.xml
index 8ac607e..a3f9174 100644
--- a/service/ServiceWifiResources/res/values-el/strings.xml
+++ b/service/ServiceWifiResources/res/values-el/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"Προτεινόμενα δίκτυα <xliff:g id="NAME">%s</xliff:g>. Η συσκευή μπορεί να συνδεθεί αυτόματα."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Αποδοχή"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Όχι, ευχαριστώ"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Σύνδεση στο Wi-Fi <xliff:g id="CARRIERNAME">%s</xliff:g>;"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Αυτά τα δίκτυα λαμβάνουν ένα αναγνωριστικό SIM που μπορεί να χρησιμοποιηθεί για την παρακολούθηση της τοποθεσίας της συσκευής."</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Σύνδεση"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Να μην γίνει σύνδεση."</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Επιβεβαίωση της σύνδεσης;"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Εάν συνδεθείτε, τα δίκτυα Wi-Fi <xliff:g id="CARRIERNAME">%s</xliff:g> ενδέχεται να έχουν πρόσβαση ή να μοιράζονται ένα μοναδικό αναγνωριστικό που σχετίζεται με την κάρτα σας SIM. Αυτό μπορεί να επιτρέψει την παρακολούθηση της τοποθεσίας της συσκευής σας."</string>
diff --git a/service/ServiceWifiResources/res/values-en-rAU/strings.xml b/service/ServiceWifiResources/res/values-en-rAU/strings.xml
index 4340ad9..49be15f 100644
--- a/service/ServiceWifiResources/res/values-en-rAU/strings.xml
+++ b/service/ServiceWifiResources/res/values-en-rAU/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> suggested networks. Device may connect automatically."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Allow"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"No, thanks"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Connect to <xliff:g id="CARRIERNAME">%s</xliff:g> Wi‑Fi?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"These networks receive a SIM ID that can be used to track device location"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Connect"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Don\'t connect"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Confirm connection?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"If you connect, <xliff:g id="CARRIERNAME">%s</xliff:g> Wi‑Fi networks may access or share a unique ID associated with your SIM. This may allow your device\'s location to be tracked."</string>
diff --git a/service/ServiceWifiResources/res/values-en-rCA/strings.xml b/service/ServiceWifiResources/res/values-en-rCA/strings.xml
index 4340ad9..49be15f 100644
--- a/service/ServiceWifiResources/res/values-en-rCA/strings.xml
+++ b/service/ServiceWifiResources/res/values-en-rCA/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> suggested networks. Device may connect automatically."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Allow"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"No, thanks"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Connect to <xliff:g id="CARRIERNAME">%s</xliff:g> Wi‑Fi?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"These networks receive a SIM ID that can be used to track device location"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Connect"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Don\'t connect"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Confirm connection?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"If you connect, <xliff:g id="CARRIERNAME">%s</xliff:g> Wi‑Fi networks may access or share a unique ID associated with your SIM. This may allow your device\'s location to be tracked."</string>
diff --git a/service/ServiceWifiResources/res/values-en-rGB/strings.xml b/service/ServiceWifiResources/res/values-en-rGB/strings.xml
index 4340ad9..49be15f 100644
--- a/service/ServiceWifiResources/res/values-en-rGB/strings.xml
+++ b/service/ServiceWifiResources/res/values-en-rGB/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> suggested networks. Device may connect automatically."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Allow"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"No, thanks"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Connect to <xliff:g id="CARRIERNAME">%s</xliff:g> Wi‑Fi?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"These networks receive a SIM ID that can be used to track device location"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Connect"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Don\'t connect"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Confirm connection?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"If you connect, <xliff:g id="CARRIERNAME">%s</xliff:g> Wi‑Fi networks may access or share a unique ID associated with your SIM. This may allow your device\'s location to be tracked."</string>
diff --git a/service/ServiceWifiResources/res/values-en-rIN/strings.xml b/service/ServiceWifiResources/res/values-en-rIN/strings.xml
index 4340ad9..49be15f 100644
--- a/service/ServiceWifiResources/res/values-en-rIN/strings.xml
+++ b/service/ServiceWifiResources/res/values-en-rIN/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> suggested networks. Device may connect automatically."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Allow"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"No, thanks"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Connect to <xliff:g id="CARRIERNAME">%s</xliff:g> Wi‑Fi?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"These networks receive a SIM ID that can be used to track device location"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Connect"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Don\'t connect"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Confirm connection?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"If you connect, <xliff:g id="CARRIERNAME">%s</xliff:g> Wi‑Fi networks may access or share a unique ID associated with your SIM. This may allow your device\'s location to be tracked."</string>
diff --git a/service/ServiceWifiResources/res/values-en-rXC/strings.xml b/service/ServiceWifiResources/res/values-en-rXC/strings.xml
index e58810f..25571d7 100644
--- a/service/ServiceWifiResources/res/values-en-rXC/strings.xml
+++ b/service/ServiceWifiResources/res/values-en-rXC/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‏‎‎‎‎‎‎‏‏‏‏‎‎‏‎‎‎‏‎‎‎‎‎‏‏‏‎‎‏‎‏‏‎‏‎‎‏‏‏‎‎‎‏‏‏‏‎‏‏‏‎‏‏‎‎‏‎‎‏‏‎<xliff:g id="NAME">%s</xliff:g>‎‏‎‎‏‏‏‎ suggested networks. Device may connect automatically.‎‏‎‎‏‎"</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‏‎‏‎‎‏‎‏‏‏‎‏‎‎‏‎‎‎‏‎‏‎‏‎‏‏‎‎‎‎‏‏‏‎‎‏‏‏‏‎‏‎‏‎‏‏‎‏‏‎‎‏‎‎‎Allow‎‏‎‎‏‎"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‎‏‏‏‎‏‎‎‏‎‏‏‏‏‎‏‏‎‎‎‏‏‎‎‎‎‏‎‎‏‎‏‏‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‏‎‏‏‎‎No thanks‎‏‎‎‏‎"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‏‎‎‏‎‎‏‏‏‎‎‎‎‎‎‏‏‎‎‎‏‏‏‏‏‏‎‎‎‎‎‏‏‏‏‎‏‏‎‏‏‎‎‏‎‎‎‎‏‏‏‏‎Connect to ‎‏‎‎‏‏‎<xliff:g id="CARRIERNAME">%s</xliff:g>‎‏‎‎‏‏‏‎ Wi‑Fi?‎‏‎‎‏‎"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‏‎‎‏‏‎‏‏‏‎‎‏‎‏‏‏‏‎‏‏‎‎‎‎‏‏‏‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‏‏‎‏‎‎‎‎‎These networks receive a SIM ID that can be used to track device location‎‏‎‎‏‎"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‏‏‎‏‏‎‏‏‎‏‏‏‎‎‏‏‎‏‎‎‏‎‏‏‎‏‏‎‏‎‎‎‎‎‏‎‏‏‏‎‏‎‏‎‏‏‎‎‎‎‏‏‎‎‎Connect‎‏‎‎‏‎"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‎‏‏‎‎‎‎‏‎‏‏‏‎‏‎‏‎‎‎‎‏‎‏‏‎‎‏‎‏‎‎‎‏‏‏‏‏‎‎‎‏‏‏‎‎‎‏‏‎‎‏‏‏‏‎‎‎‎Don\'t connect‎‏‎‎‏‎"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‎‎‏‎‏‎‏‎‎‏‎‎‏‎‎‎‏‏‎‏‏‏‎‏‎‏‎‏‎‎‎‎‎‏‎‎‎‎‏‏‎‏‎‏‎‏‏‏‎‏‏‏‏‏‎‏‎Confirm connection?‎‏‎‎‏‎"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‏‏‎‎‏‏‏‎‎‎‎‏‏‏‎‏‏‏‎‏‎‏‏‏‎‎‏‏‎‏‎‎‎‎‎‏‏‎‏‏‎‏‎‎‎‎‎‎If you connect, ‎‏‎‎‏‏‎<xliff:g id="CARRIERNAME">%s</xliff:g>‎‏‎‎‏‏‏‎ Wi‑Fi networks may access or share a unique ID associated with your SIM. This may allow your device\'s location to be tracked.‎‏‎‎‏‎"</string>
diff --git a/service/ServiceWifiResources/res/values-es-rUS/strings.xml b/service/ServiceWifiResources/res/values-es-rUS/strings.xml
index d78f344..12a13fb 100644
--- a/service/ServiceWifiResources/res/values-es-rUS/strings.xml
+++ b/service/ServiceWifiResources/res/values-es-rUS/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> sugirió redes. Es posible que el dispositivo se conecte automáticamente."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Permitir"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"No, gracias"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"¿Quieres conectarte a la red Wi-Fi de <xliff:g id="CARRIERNAME">%s</xliff:g>?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Estas redes reciben un ID de SIM que puede usarse para realizar el seguimiento de la ubicación del dispositivo"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Conectar"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"No conectar"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"¿Confirmar conexión?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Si te conectas, las redes de Wi-Fi de <xliff:g id="CARRIERNAME">%s</xliff:g> podrán acceder o compartir un ID único asociado con tu SIM. Esto podría permitir que se realice el seguimiento de la ubicación del dispositivo."</string>
diff --git a/service/ServiceWifiResources/res/values-es/strings.xml b/service/ServiceWifiResources/res/values-es/strings.xml
index 7e74446..a6d6748 100644
--- a/service/ServiceWifiResources/res/values-es/strings.xml
+++ b/service/ServiceWifiResources/res/values-es/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> te ha sugerido alguna red. El dispositivo puede que se conecte automáticamente."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Permitir"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"No, gracias"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"¿Conectarse a la red Wi‑Fi de <xliff:g id="CARRIERNAME">%s</xliff:g>?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Estas redes reciben un ID de SIM con el que se puede rastrear la ubicación del dispositivo"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Conectar"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"No conectar"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"¿Confirmar conexión?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Si te conectas, es posible que la red Wi‑Fi de <xliff:g id="CARRIERNAME">%s</xliff:g> obtenga o comparta un ID único asociado a tu SIM, lo que puede permitir que se rastree la ubicación de tu dispositivo."</string>
@@ -80,7 +77,7 @@
     <string name="wifi_eap_error_message_code_32764" msgid="7349538467012877101">"<xliff:g id="SSID">%1$s</xliff:g>: error de autenticación de EAP 32764"</string>
     <string name="wifi_eap_error_message_code_32765" msgid="2167528358066037980">"<xliff:g id="SSID">%1$s</xliff:g>: error de autenticación de EAP 32765"</string>
     <string name="wifi_eap_error_message_code_32766" msgid="2335996367705677670">"<xliff:g id="SSID">%1$s</xliff:g>: error de autenticación de EAP 32766"</string>
-    <string name="wifi_softap_auto_shutdown_timeout_expired_title" msgid="4896534374569504484">"Punto de acceso desactivado"</string>
+    <string name="wifi_softap_auto_shutdown_timeout_expired_title" msgid="4896534374569504484">"Compartir Internet desactivado"</string>
     <string name="wifi_softap_auto_shutdown_timeout_expired_summary" msgid="7975476698140267728">"No hay dispositivos conectados. Toca para modificar este ajuste."</string>
     <string name="wifi_sim_required_title" msgid="2262227800991155459">"Wi‑Fi desconectado"</string>
     <string name="wifi_sim_required_message" msgid="284812212346125745">"Para conectarte a <xliff:g id="SSID">%1$s</xliff:g>, inserta una tarjeta SIM de <xliff:g id="CARRIER_NAME">%2$s</xliff:g>"</string>
diff --git a/service/ServiceWifiResources/res/values-et/strings.xml b/service/ServiceWifiResources/res/values-et/strings.xml
index 8556524..82134bb 100644
--- a/service/ServiceWifiResources/res/values-et/strings.xml
+++ b/service/ServiceWifiResources/res/values-et/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"Rakenduse <xliff:g id="NAME">%s</xliff:g> soovitatud võrgud. Seade võib automaatselt ühenduse luua."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Luba"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Tänan, ei"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Kas luua ühendus operaatori <xliff:g id="CARRIERNAME">%s</xliff:g> WiFi-võrguga?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Need võrgud saavad SIM-i ID, mida saab kasutada seadme asukoha jälgimiseks."</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Ühenda"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Ära ühenda"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Kas soovite ühenduse kinnitada?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Kui loote ühenduse, võivad operaatori <xliff:g id="CARRIERNAME">%s</xliff:g> WiFi-võrgud teie SIM-kaardiga seotud kordumatule ID-le juurde pääseda või seda jagada. See võib võimaldada seadme asukohta jälgida."</string>
diff --git a/service/ServiceWifiResources/res/values-eu/strings.xml b/service/ServiceWifiResources/res/values-eu/strings.xml
index d45910e..e985031 100644
--- a/service/ServiceWifiResources/res/values-eu/strings.xml
+++ b/service/ServiceWifiResources/res/values-eu/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> aplikazioak sare batzuk iradoki ditu. Baliteke gailua automatikoki konektatzea."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Baimendu"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Ez, eskerrik asko"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"<xliff:g id="CARRIERNAME">%s</xliff:g> operadorearen wifi-sarera konektatu nahi duzu?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Sare hauek SIM ID bat jasotzen dute, gailuaren kokapenaren jarraipena egin ahal izateko"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Konektatu"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Ez konektatu"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Konexioa berretsi nahi duzu?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Konektatzen bazara, baliteke <xliff:g id="CARRIERNAME">%s</xliff:g> operadorearen wifi-sareek zure SIM txartelarekin lotutako ID esklusiboa atzitzea edo partekatzea. Horrela, baliteke zure gailuaren kokapenaren jarraipena egiteko aukera izatea."</string>
@@ -68,7 +65,7 @@
     <string name="wifi_cannot_connect_with_randomized_mac_title" msgid="2344570489693915253">"Ezin da konektatu <xliff:g id="SSID">%1$s</xliff:g> sarera"</string>
     <string name="wifi_cannot_connect_with_randomized_mac_message" msgid="4834133226521813352">"Sakatu hau pribatutasun-ezarpenak aldatzeko, eta saiatu berriro"</string>
     <string name="wifi_disable_mac_randomization_dialog_title" msgid="2054540994993681606">"Pribatutasun-ezarpena aldatu nahi duzu?"</string>
-    <string name="wifi_disable_mac_randomization_dialog_message" msgid="8874064864332248988">"Konektatzeko, <xliff:g id="SSID">%1$s</xliff:g> sareak gailuaren MAC helbidea (identifikatzaile esklusiboa) behar du. Sarearen uneko pribatutasun-ezarpenen arabera, ausazko identifikatzaile bat erabiltzen da. \n\nAldaketa honekin, baliteke inguruko gailuek zure gailuaren kokapenaren jarraipena egin ahal izatea."</string>
+    <string name="wifi_disable_mac_randomization_dialog_message" msgid="8874064864332248988">"Konektatzeko, <xliff:g id="SSID">%1$s</xliff:g> sareak gailuaren MAC helbidea (identifikatzaile esklusiboa) behar du. Sarearen pribatutasun-ezarpenen arabera, ausazko identifikatzaile bat erabiltzen da. \n\nAldaketa honekin, baliteke inguruko gailuek zure gailuaren kokapenaren jarraipena egin ahal izatea."</string>
     <string name="wifi_disable_mac_randomization_dialog_confirm_text" msgid="6954419863076751626">"Aldatu ezarpena"</string>
     <string name="wifi_disable_mac_randomization_dialog_success" msgid="5849155828154391387">"Eguneratu da ezarpena. Saiatu berriro konektatzen."</string>
     <string name="wifi_disable_mac_randomization_dialog_failure" msgid="2894643619143813096">"Ezin da aldatu pribatutasun-ezarpena"</string>
diff --git a/service/ServiceWifiResources/res/values-fa/strings.xml b/service/ServiceWifiResources/res/values-fa/strings.xml
index 9ffaf9f..ed0cff7 100644
--- a/service/ServiceWifiResources/res/values-fa/strings.xml
+++ b/service/ServiceWifiResources/res/values-fa/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"شبکه‌های پیشنهادی <xliff:g id="NAME">%s</xliff:g>. ممکن است دستگاه به‌طور خودکار متصل شود."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"مجاز"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"نه متشکرم"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"‏به Wi-Fi <xliff:g id="CARRIERNAME">%s</xliff:g> متصل می‌شوید؟"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"این شبکه‌ها شناسه سیم‌کارتی دریافت می‌کنند که می‌توان از آن برای ردیابی مکان دستگاه استفاده کرد"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"اتصال"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"متصل نشود"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"اتصال را تأیید می‌کنید؟"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"‏اگر متصل شوید، شبکه‌های Wi‑Fi <xliff:g id="CARRIERNAME">%s</xliff:g> می‌توانند به شناسه یکتای مربوط به سیم‌کارتتان دسترسی پیدا کنند و از آن به‌صورت مشترک استفاده کنند. این کار ممکن است ردیابی مکان دستگاه را مجاز کند."</string>
diff --git a/service/ServiceWifiResources/res/values-fi/strings.xml b/service/ServiceWifiResources/res/values-fi/strings.xml
index b642aed..ad6d6a0 100644
--- a/service/ServiceWifiResources/res/values-fi/strings.xml
+++ b/service/ServiceWifiResources/res/values-fi/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> ehdotti verkkoja. Laite voi muodostaa yhteyden automaattisesti."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Salli"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Ei kiitos"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Yhdistetäänkö Wi-Fi-verkkoon (<xliff:g id="CARRIERNAME">%s</xliff:g>)?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Nämä verkot saavat SIM-tunnuksen, jolla voidaan seurata laitteen sijaintia."</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Yhdistä"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Älä yhdistä"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Vahvistetaanko yhteys?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Jos muodostat yhteyden, Wi-Fi-verkot (<xliff:g id="CARRIERNAME">%s</xliff:g>) voivat nähdä tai jakaa yksilöllisen tunnuksen, joka liittyy SIM-korttiisi. Tämän kautta laitteesi sijaintia voidaan seurata."</string>
diff --git a/service/ServiceWifiResources/res/values-fr-rCA/strings.xml b/service/ServiceWifiResources/res/values-fr-rCA/strings.xml
index 037989c..9e6c5f7 100644
--- a/service/ServiceWifiResources/res/values-fr-rCA/strings.xml
+++ b/service/ServiceWifiResources/res/values-fr-rCA/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"Réseaux suggérés par <xliff:g id="NAME">%s</xliff:g>. L\'appareil peut s\'y connecter automatiquement."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Autoriser"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Non merci"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Connexion au Wi-Fi de <xliff:g id="CARRIERNAME">%s</xliff:g>?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Ces réseaux reçoivent un identifiant SIM qui peut être utilisé pour faire le suivi de la position de l\'appareil"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Connecter"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Ne pas se connecter"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Confirmer la connexion?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Si vous vous connectez, les réseaux Wi-Fi de <xliff:g id="CARRIERNAME">%s</xliff:g> pourront accéder à un identifiant unique associé à votre module SIM ou partager cet identifiant. Cela pourrait permettre à d\'autres appareils de faire le suivi de la position de votre appareil."</string>
diff --git a/service/ServiceWifiResources/res/values-fr/strings.xml b/service/ServiceWifiResources/res/values-fr/strings.xml
index 3407ee1..55c58b5 100644
--- a/service/ServiceWifiResources/res/values-fr/strings.xml
+++ b/service/ServiceWifiResources/res/values-fr/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"Réseaux suggérés par <xliff:g id="NAME">%s</xliff:g>. L\'appareil pourra se connecter automatiquement."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Autoriser"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Non, merci"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Se connecter au Wi-Fi <xliff:g id="CARRIERNAME">%s</xliff:g> ?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Ces réseaux reçoivent un ID de SIM qui peut être utilisé pour suivre la position de l\'appareil"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Se connecter"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Ne pas connecter"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Confirmer la connexion ?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Si vous établissez la connexion, les réseaux Wi-Fi <xliff:g id="CARRIERNAME">%s</xliff:g> peuvent accéder à un identifiant unique associé à votre carte SIM ou le partager. Suite à cette connexion, la position de votre appareil peut également d\'être suivie."</string>
diff --git a/service/ServiceWifiResources/res/values-gl/strings.xml b/service/ServiceWifiResources/res/values-gl/strings.xml
index 052043d..0cfe40e 100644
--- a/service/ServiceWifiResources/res/values-gl/strings.xml
+++ b/service/ServiceWifiResources/res/values-gl/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"Redes suxeridas de <xliff:g id="NAME">%s</xliff:g>. O dispositivo pode conectarse automaticamente."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Permitir"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Non, grazas"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Quéreste conectar á rede wifi de <xliff:g id="CARRIERNAME">%s</xliff:g>?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Estas redes reciben un código SIM que se pode usar para facer un seguimento da localización do dispositivo"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Conectar"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Non conectar"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Queres confirmar a conexión?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Se te conectas, as redes wifi de <xliff:g id="CARRIERNAME">%s</xliff:g> poden acceder a un código exclusivo asociado coa túa SIM ou ben compartilo. Deste xeito, pódese facer un seguimento da localización do teu dispositivo."</string>
diff --git a/service/ServiceWifiResources/res/values-gu/strings.xml b/service/ServiceWifiResources/res/values-gu/strings.xml
index c60edca..b50e8a0 100644
--- a/service/ServiceWifiResources/res/values-gu/strings.xml
+++ b/service/ServiceWifiResources/res/values-gu/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> સૂચવેલા નેટવર્ક. ડિવાઇસ ઑટોમૅટિક રીતે કનેક્ટ થાય તેમ બની શકે છે."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"મંજૂરી આપો"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"ના, આભાર"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"<xliff:g id="CARRIERNAME">%s</xliff:g> વાઇ-ફાઇ સાથે કનેક્ટ કરીએ?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"આ બધા નેટવર્કને સિમ ID મળે છે કે જેનો ઉપયોગ ડિવાઇસના સ્થાનને ટ્રૅક કરવા માટે થઈ શકે છે"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"કનેક્ટ કરો"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"કનેક્ટ કરશો નહીં"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"કનેક્શનને કન્ફર્મ કરીએ?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"જો તમે કનેક્ટ કરો છો, તો <xliff:g id="CARRIERNAME">%s</xliff:g> વાઇ-ફાઇ નેટવર્ક, તમારા SIM સાથે સંકળાયેલા અજોડ IDને ઍક્સેસ અથવા શેર કરી શકે છે. આમ કરવાથી તમારા ડિવાઇસનું સ્થાન ટ્રૅક કરવાની મંજૂરી આપવામાં આવી શકે છે."</string>
diff --git a/service/ServiceWifiResources/res/values-hi/strings.xml b/service/ServiceWifiResources/res/values-hi/strings.xml
index 2487fd2..b0d5521 100644
--- a/service/ServiceWifiResources/res/values-hi/strings.xml
+++ b/service/ServiceWifiResources/res/values-hi/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> के सुझाए गए नेटवर्क. डिवाइस अपने आप कनेक्ट हो सकता है."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"अनुमति दें"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"रहने दें"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"<xliff:g id="CARRIERNAME">%s</xliff:g> वाई-फ़ाई से कनेक्ट करना चाहते हैं?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"इन नेटवर्क को एक सिम आईडी दिया जाता है जिसका इस्तेमाल डिवाइस की जगह की जानकारी का पता लगाने के लिए किया जा सकता है"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"कनेक्ट करें"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"कनेक्ट न करें"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"कनेक्शन की पुष्टि करना चाहते हैं?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"अगर आप कनेक्ट करते हैं, तो <xliff:g id="CARRIERNAME">%s</xliff:g> वाई-फ़ाई नेटवर्क आपकी सिम से जुड़े हुए विशेष आईडी को ऐक्सेस या शेयर कर सकता है. इससे आपके डिवाइस की जगह की जानकारी का पता लगा सकते हैं."</string>
diff --git a/service/ServiceWifiResources/res/values-hr/strings.xml b/service/ServiceWifiResources/res/values-hr/strings.xml
index 5134cfe..53b6a11 100644
--- a/service/ServiceWifiResources/res/values-hr/strings.xml
+++ b/service/ServiceWifiResources/res/values-hr/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"Mreže koje predlaže aplikacija <xliff:g id="NAME">%s</xliff:g>. Uređaji se mogu povezati automatski."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Dopusti"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Ne, hvala"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Želite li se povezati s Wi-Fijem mobilnog operatera <xliff:g id="CARRIERNAME">%s</xliff:g>?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Ove mreže primaju ID SIM-a koji se može upotrijebiti za praćenje lokacije uređaja"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Poveži"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Nemoj povezati"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Želite li potvrditi povezivanje?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Ako se povežete, Wifi mreže operatera <xliff:g id="CARRIERNAME">%s</xliff:g> mogu pristupiti jedinstvenom ID-ju povezanom s vašim SIM-om ili ga podijeliti. To može omogućiti praćenje lokacije vašeg uređaja."</string>
diff --git a/service/ServiceWifiResources/res/values-hu/strings.xml b/service/ServiceWifiResources/res/values-hu/strings.xml
index bcc1762..bd963b8 100644
--- a/service/ServiceWifiResources/res/values-hu/strings.xml
+++ b/service/ServiceWifiResources/res/values-hu/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"A(z) <xliff:g id="NAME">%s</xliff:g> hálózatokat javasolt. Az eszköz automatikusan csatlakozhat hozzájuk."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Engedélyezés"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Nem, köszönöm"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Csatlakozni szeretne a(z) <xliff:g id="CARRIERNAME">%s</xliff:g> Wi-Fi-hálózathoz?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Ezek a hálózatok SIM-azonosítót kapnak, amely felhasználható az eszköz helyadatainak követésére"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Csatlakozás"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Ne csatlakozzon"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Biztosan csatlakozik?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Ha csatlakozik, a(z) <xliff:g id="CARRIERNAME">%s</xliff:g> Wi‑Fi-hálózatai hozzáférhetnek a SIM-hez társított egyedi azonosítóhoz, amelyet meg is oszthatnak. Ez lehetővé teheti eszköze helyadatainak követését."</string>
diff --git a/service/ServiceWifiResources/res/values-hy/strings.xml b/service/ServiceWifiResources/res/values-hy/strings.xml
index e4b912f..0ce91be 100644
--- a/service/ServiceWifiResources/res/values-hy/strings.xml
+++ b/service/ServiceWifiResources/res/values-hy/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> հավելվածի առաջարկվող ցանցեր: Սարքը կարող է ավտոմատ միանալ:"</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Թույլատրել"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Ոչ, շնորհակալություն"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Միանա՞լ <xliff:g id="CARRIERNAME">%s</xliff:g> Wi-Fi ցանցին"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Այս ցանցերը ստանում են SIM քարտի ID, որը կարող է օգտագործվել սարքի տեղադրությունը հետագծելու համար։"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Միանալ"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Չմիանալ"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Հաստատե՞լ միացումը"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Միանալու դեպքում <xliff:g id="CARRIERNAME">%s</xliff:g>-ի Wi‑Fi ցանցերը կարող են ստանալ ձեր SIM քարտի հետ կապված եզակի ID-ն կամ կիսվել դրանով։ Դա հնարավորություն կտա հետագծել ձեր սարքի տեղադրությունը։"</string>
diff --git a/service/ServiceWifiResources/res/values-in/strings.xml b/service/ServiceWifiResources/res/values-in/strings.xml
index fa8f385b..e3c3a83 100644
--- a/service/ServiceWifiResources/res/values-in/strings.xml
+++ b/service/ServiceWifiResources/res/values-in/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"Jaringan yang disarankan <xliff:g id="NAME">%s</xliff:g>. Perangkat dapat terhubung secara otomatis."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Izinkan"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Lain kali"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Hubungkan ke Wi-Fi <xliff:g id="CARRIERNAME">%s</xliff:g>?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Jaringan ini menerima ID SIM yang dapat digunakan untuk melacak lokasi perangkat"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Hubungkan"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Jangan hubungkan"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Konfirmasi koneksi?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Jika Anda menghubungkan, jaringan Wi-Fi <xliff:g id="CARRIERNAME">%s</xliff:g> mungkin mengakses atau membagikan ID unik terkait dengan SIM Anda. Ini memungkinkan lokasi perangkat Anda untuk dilacak."</string>
diff --git a/service/ServiceWifiResources/res/values-is/strings.xml b/service/ServiceWifiResources/res/values-is/strings.xml
index 22ec7a2..ecbdd65 100644
--- a/service/ServiceWifiResources/res/values-is/strings.xml
+++ b/service/ServiceWifiResources/res/values-is/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> kom með tillögur að netkerfum. Tækið gæti tengst sjálfkrafa."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Leyfa"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Nei, takk"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Tengjast við <xliff:g id="CARRIERNAME">%s</xliff:g> Wi‑Fi?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Þessi net fá SIM-auðkenni sem hægt er að nota til að rekja staðsetningu tækisins"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Tengja"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Ekki tengjast"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Staðfesta tengingu?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Ef þú tengist geta Wi-Fi net <xliff:g id="CARRIERNAME">%s</xliff:g> fengið aðgang að eða deilt einkvæmu auðkenni sem er tengt SIM-kortinu þínu. Með þessu er hugsanlega hægt að rekja staðsetningu tækisins þíns."</string>
diff --git a/service/ServiceWifiResources/res/values-it/strings.xml b/service/ServiceWifiResources/res/values-it/strings.xml
index ba94004..14056fa 100644
--- a/service/ServiceWifiResources/res/values-it/strings.xml
+++ b/service/ServiceWifiResources/res/values-it/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> ha suggerito delle reti. Il dispositivo potrebbe collegarsi automaticamente."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Consenti"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"No, grazie"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Vuoi connetterti alla rete Wi-Fi <xliff:g id="CARRIERNAME">%s</xliff:g>?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Queste reti ricevono un ID SIM che può essere usato per monitorare la posizione del dispositivo"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Connetti"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Non connettere"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Confermi la connessione?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Se esegui la connessione, le reti Wi-Fi di <xliff:g id="CARRIERNAME">%s</xliff:g> potranno accedere o condividere un ID univoco associato alla tua SIM. Questa azione potrebbe consentire il monitoraggio della posizione del tuo dispositivo."</string>
diff --git a/service/ServiceWifiResources/res/values-iw/strings.xml b/service/ServiceWifiResources/res/values-iw/strings.xml
index 2919e02..895c5be 100644
--- a/service/ServiceWifiResources/res/values-iw/strings.xml
+++ b/service/ServiceWifiResources/res/values-iw/strings.xml
@@ -19,56 +19,53 @@
     <string name="wifiResourcesAppLabel" product="default" msgid="3120115613525263696">"‏משאבי Wi-Fi של המערכת"</string>
     <string name="wifi_available_title" msgid="3899472737467127635">"‏התחברות לרשת Wi‑Fi פתוחה"</string>
     <string name="wifi_available_title_connecting" msgid="7233590022728579868">"‏התחברות לרשת Wi-Fi"</string>
-    <string name="wifi_available_title_connected" msgid="6329493859989844201">"‏מחובר לרשת Wi-Fi"</string>
+    <string name="wifi_available_title_connected" msgid="6329493859989844201">"‏בוצע חיבור לרשת Wi-Fi"</string>
     <string name="wifi_available_title_failed_to_connect" msgid="4840833680513368639">"‏לא ניתן היה להתחבר לרשת Wi-Fi"</string>
     <string name="wifi_available_content_failed_to_connect" msgid="4330035988269701861">"יש להקיש כדי לראות את כל הרשתות"</string>
-    <string name="wifi_available_action_connect" msgid="5636634933476946222">"התחבר"</string>
+    <string name="wifi_available_action_connect" msgid="5636634933476946222">"התחברות"</string>
     <string name="wifi_available_action_all_networks" msgid="8491109932336522211">"כל הרשתות"</string>
     <string name="notification_channel_network_status" msgid="1631786866932924838">"סטטוס הרשת"</string>
     <string name="notification_channel_network_alerts" msgid="1391603215241200880">"התראות רשת"</string>
     <string name="notification_channel_network_available" msgid="8454366142428864948">"יש רשת זמינה"</string>
     <string name="wifi_suggestion_title" msgid="2564179935989099139">"‏לאפשר הצעות לרשתות Wi-Fi?"</string>
-    <string name="wifi_suggestion_content" msgid="6985149577828091835">"הצעות לרשתות <xliff:g id="NAME">%s</xliff:g>. ייתכן שחיבור המכשיר ייעשה באופן אוטומטי."</string>
+    <string name="wifi_suggestion_content" msgid="6985149577828091835">"הצעות לרשתות מאפליקציית <xliff:g id="NAME">%s</xliff:g>. ייתכן שחיבור המכשיר ייעשה באופן אוטומטי."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"אישור"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"לא תודה"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"‏האם להתחבר ל-Wi-Fi של <xliff:g id="CARRIERNAME">%s</xliff:g>?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"‏הרשתות האלה מקבלות מזהה SIM שיכול לשמש למעקב אחר מיקום של מכשיר"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"חיבור"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"אין להתחבר"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"לאשר את ההתחברות?"</string>
-    <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"‏אם תתבצע התחברות, ייתכן שרשתות Wi‑Fi של <xliff:g id="CARRIERNAME">%s</xliff:g> יקבלו גישה או ישתפו מזהה ייחודי שמשויך לכרטיס ה-SUM שלך. בעקבות זאת, ייתכן שניתן יהיה לעקוב אחר מיקום המכשיר שלך."</string>
+    <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"‏אם תתבצע התחברות, ייתכן שרשתות Wi‑Fi של <xliff:g id="CARRIERNAME">%s</xliff:g> יקבלו גישה או ישתפו מזהה ייחודי שמשויך לכרטיס ה-SIM שלך. בעקבות זאת, ייתכן שניתן יהיה לעקוב אחר מיקום המכשיר שלך."</string>
     <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_confirmation" msgid="2168947026413431603">"התחברות"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_confirmation" msgid="5156881939985876066">"אין להתחבר"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="3868826648004934540">"‏ה-Wi-Fi יופעל אוטומטית"</string>
-    <string name="wifi_wakeup_onboarding_subtext" msgid="5705886295837387430">"כשתימצאו בקרבת רשת באיכות גבוהה ששמרתם"</string>
-    <string name="wifi_wakeup_onboarding_action_disable" msgid="6209706680391785825">"אל תפעיל שוב"</string>
+    <string name="wifi_wakeup_onboarding_subtext" msgid="5705886295837387430">"כשנמצאים בקרבת רשת באיכות גבוהה שנשמרה"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="6209706680391785825">"אין להפעיל שוב"</string>
     <string name="wifi_wakeup_enabled_title" msgid="5043486751612595850">"‏רשת Wi‑Fi הופעלה אוטומטית"</string>
     <string name="wifi_wakeup_enabled_content" msgid="3911262526267025882">"המיקום הנוכחי שלך הוא בקרבת הרשת השמורה: <xliff:g id="NETWORK_SSID">%1$s</xliff:g>"</string>
     <string name="wifi_watchdog_network_disabled" msgid="5769226742956006362">"‏אין אפשרות להתחבר ל-Wi-Fi"</string>
-    <string name="wifi_watchdog_network_disabled_detailed" msgid="1725243835135539125">" בעל חיבור גרוע לאינטרנט."</string>
+    <string name="wifi_watchdog_network_disabled_detailed" msgid="1725243835135539125">" – חיבור גרוע לאינטרנט."</string>
     <string name="wifi_connect_alert_title" msgid="2368200646665663612">"האם להתיר את החיבור?"</string>
     <string name="wifi_connect_alert_message" msgid="7226456300982080746">"‏האפליקציה %1$s מנסה להתחבר אל רשת ה-Wi-Fi ‏%2$s"</string>
     <string name="wifi_connect_default_application" msgid="8917703737222707062">"אפליקציה"</string>
-    <string name="accept" msgid="8346431649376483879">"קבל"</string>
-    <string name="decline" msgid="4172251727603762084">"דחה"</string>
+    <string name="accept" msgid="8346431649376483879">"אישור"</string>
+    <string name="decline" msgid="4172251727603762084">"דחייה"</string>
     <string name="ok" msgid="847575529546290102">"אישור"</string>
     <string name="wifi_p2p_invitation_sent_title" msgid="6552639940428040869">"ההזמנה נשלחה"</string>
     <string name="wifi_p2p_invitation_to_connect_title" msgid="8917157937652519251">"הזמנה להתחבר"</string>
     <string name="wifi_p2p_from_message" msgid="5921308150192756898">"מאת:"</string>
     <string name="wifi_p2p_to_message" msgid="3809923305696994787">"אל:"</string>
-    <string name="wifi_p2p_enter_pin_message" msgid="5200220251738047620">"הקלד את קוד הגישה הנדרש."</string>
-    <string name="wifi_p2p_show_pin_message" msgid="1000091690967930798">"קוד גישה:"</string>
-    <string name="wifi_p2p_frequency_conflict_message" product="tablet" msgid="2875937871590955304">"‏הטאבלט יתנתק מרשת ה-Wi-Fi באופן זמני בשעה שהוא מחובר אל <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
-    <string name="wifi_p2p_frequency_conflict_message" product="tv" msgid="9133053225387001827">"‏מכשיר ה-Android TV יתנתק מרשת ה-Wi-Fi באופן זמני בשעה שהוא מחובר אל <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
-    <string name="wifi_p2p_frequency_conflict_message" product="default" msgid="2226863827636191980">"‏הטלפון יתנתק מרשת ה-Wi-Fi באופן זמני בשעה שהוא מחובר אל <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="wifi_p2p_enter_pin_message" msgid="5200220251738047620">"יש להקליד את קוד האימות הנדרש:"</string>
+    <string name="wifi_p2p_show_pin_message" msgid="1000091690967930798">"קוד אימות:"</string>
+    <string name="wifi_p2p_frequency_conflict_message" product="tablet" msgid="2875937871590955304">"‏הטאבלט יתנתק מרשת ה-Wi-Fi באופן זמני כשהוא מחובר אל <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="wifi_p2p_frequency_conflict_message" product="tv" msgid="9133053225387001827">"‏מכשיר ה-Android TV יתנתק מרשת ה-Wi-Fi באופן זמני כשהוא מחובר אל <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="wifi_p2p_frequency_conflict_message" product="default" msgid="2226863827636191980">"‏הטלפון יתנתק מרשת ה-Wi-Fi באופן זמני כשהוא מחובר אל <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="dlg_ok" msgid="254496739491689405">"אישור"</string>
     <string name="wifi_cannot_connect_with_randomized_mac_title" msgid="2344570489693915253">"לא ניתן להתחבר אל <xliff:g id="SSID">%1$s</xliff:g>"</string>
     <string name="wifi_cannot_connect_with_randomized_mac_message" msgid="4834133226521813352">"יש להקיש כדי לשנות את הגדרות הפרטיות ולנסות שוב"</string>
     <string name="wifi_disable_mac_randomization_dialog_title" msgid="2054540994993681606">"לשנות את הגדרות הפרטיות?"</string>
-    <string name="wifi_disable_mac_randomization_dialog_message" msgid="8874064864332248988">"‏כדי לחבר, עליך לאפשר ל-<xliff:g id="SSID">%1$s</xliff:g> להשתמש בכתובת ה-MAC של המכשיר, מזהה ייחודי. הגדרת הפרטיות לרשת זו משתמשת כרגע במזהה רנדומלי. \n\nשינוי זה עשוי לאפשר למכשירים בקרבת מקום לעקוב אחר מיקום המכשיר שלך."</string>
+    <string name="wifi_disable_mac_randomization_dialog_message" msgid="8874064864332248988">"‏כדי לבצע חיבור, עליך לאפשר ל-<xliff:g id="SSID">%1$s</xliff:g> להשתמש בכתובת ה-MAC של המכשיר, שהיא מזהה ייחודי. הגדרת הפרטיות לרשת הזו משתמשת כרגע במזהה רנדומלי. \n\nשינוי זה עשוי לאפשר למכשירים בקרבת מקום לעקוב אחר מיקום המכשיר שלך."</string>
     <string name="wifi_disable_mac_randomization_dialog_confirm_text" msgid="6954419863076751626">"שינוי ההגדרות"</string>
     <string name="wifi_disable_mac_randomization_dialog_success" msgid="5849155828154391387">"ההגדרה עודכנה. אפשר לנסות שוב להתחבר."</string>
     <string name="wifi_disable_mac_randomization_dialog_failure" msgid="2894643619143813096">"לא ניתן לשנות את הגדרות הפרטיות"</string>
diff --git a/service/ServiceWifiResources/res/values-ja/strings.xml b/service/ServiceWifiResources/res/values-ja/strings.xml
index 7815fb8..b10ab30 100644
--- a/service/ServiceWifiResources/res/values-ja/strings.xml
+++ b/service/ServiceWifiResources/res/values-ja/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> からのネットワーク候補に、デバイスが自動的に接続される可能性があります。"</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"許可"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"許可しない"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"<xliff:g id="CARRIERNAME">%s</xliff:g> のWi-Fi に接続しますか?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"これらのネットワークは SIM ID を受信します。この ID を使ってデバイスの位置情報が追跡される可能性があります"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"接続"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"接続しない"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"接続を確認しますか?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"接続すると、<xliff:g id="CARRIERNAME">%s</xliff:g> の Wi‑Fi ネットワークが、SIM に関連付けられている一意の ID にアクセスしたりその ID を共有したりする可能性があります。これにより、デバイスの位置情報が追跡される可能性もあります。"</string>
diff --git a/service/ServiceWifiResources/res/values-ka/strings.xml b/service/ServiceWifiResources/res/values-ka/strings.xml
index 7577196..1de03b0 100644
--- a/service/ServiceWifiResources/res/values-ka/strings.xml
+++ b/service/ServiceWifiResources/res/values-ka/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> შემოთავაზებული ქსელები. მოწყობილობა შეიძლება ავტომატურად დაუკავშირდეს."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"დაშვება"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"არა, გმადლობთ"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"დაუკავშირდებით <xliff:g id="CARRIERNAME">%s</xliff:g> Wi-Fi-ს?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"ეს ქსელები მიიღებს SIM-ის ID-ს, რომლის მეშვეობითაც შესაძლებელია მოწყობილობის მდებარეობაზე თვალის დევნება."</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"დაკავშირება"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"არ დაუკავშირდეს"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"გსურთ, დაადასტუროთ დაკავშირება?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"დაკავშირების შემთხვევაში, <xliff:g id="CARRIERNAME">%s</xliff:g>-ის Wi‑Fi ქსელებმა შეიძლება წვდომა იქონიოს ან გააზიაროს უნიკალური ID, რომელიც თქვენს SIM-ბარათთან არის ასოცირებული. ამ ცვლილებამ შესაძლოა თქვენი მოწყობილობის მდებარეობა აღნუსხვადი გახადოს."</string>
diff --git a/service/ServiceWifiResources/res/values-kk/strings.xml b/service/ServiceWifiResources/res/values-kk/strings.xml
index 91be4b6..0d28492 100644
--- a/service/ServiceWifiResources/res/values-kk/strings.xml
+++ b/service/ServiceWifiResources/res/values-kk/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> ұсынған желілер. Құрылғы автоматты түрде қосылуы мүмкін."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Рұқсат беру"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Жоқ, рақмет"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"<xliff:g id="CARRIERNAME">%s</xliff:g> қызметі Wi‑Fi желісімен байланыстырылсын ба?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Бұл желілер SIM идентификаторын алады, оны құрылғының орналасқан жерін бақылау үшін қолдануға болады."</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Байланыстыру"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Қосылмау"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Қосылымды растайсыз ба?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Егер қосылсаңыз, <xliff:g id="CARRIERNAME">%s</xliff:g> Wi‑Fi желілері SIM картаңызға байланыстырылған бірегей идентификаторды пайдалана немесе бөлісе алады. Оның көмегімен құрылғыңыздың орнын қадағалауға болады."</string>
diff --git a/service/ServiceWifiResources/res/values-km/strings.xml b/service/ServiceWifiResources/res/values-km/strings.xml
index 41be843..de96aba 100644
--- a/service/ServiceWifiResources/res/values-km/strings.xml
+++ b/service/ServiceWifiResources/res/values-km/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"បណ្ដាញ​ដែលបាន​ណែនាំ​របស់ <xliff:g id="NAME">%s</xliff:g> ។ ឧបករណ៍​អាច​ភ្ជាប់​ដោយស្វ័យប្រវត្តិ។"</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"អនុញ្ញាត"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"ទេ អរគុណ"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"ភ្ជាប់ Wi-Fi របស់ <xliff:g id="CARRIERNAME">%s</xliff:g> ឬ?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"បណ្ដាញ​ទាំងនេះ​ទទួលបាន​លេខសម្គាល់ស៊ីម ដែល​អាចប្រើ​ដើម្បី​តាមដាន​ទីតាំង​ឧបករណ៍"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"ភ្ជាប់"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"កុំ​ភ្ជាប់"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"បញ្ជាក់​ការតភ្ជាប់​ដែរទេ​?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"ប្រសិនបើ​អ្នក​ភ្ជាប់ នោះបណ្ដាញ Wi-Fi របស់ <xliff:g id="CARRIERNAME">%s</xliff:g> អាច​ចូលប្រើ ឬ​ចែករំលែក​លេខសម្គាល់​ពិសេស​ដែលភ្ជាប់​ជាមួយ​ស៊ីម​របស់អ្នក​។ សកម្មភាពនេះ​អាច​អនុញ្ញាតឱ្យមាន​ការតាមដាន​ទីតាំង​ឧបករណ៍​របស់អ្នក​។"</string>
diff --git a/service/ServiceWifiResources/res/values-kn/strings.xml b/service/ServiceWifiResources/res/values-kn/strings.xml
index fe60978..112f07b 100644
--- a/service/ServiceWifiResources/res/values-kn/strings.xml
+++ b/service/ServiceWifiResources/res/values-kn/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> ಸೂಚಿಸಿರುವ ನೆಟ್‌ವರ್ಕ್‌ಗಳು. ಸಾಧನಗಳು ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಸಂಪರ್ಕಗೊಳ್ಳಬಹುದು."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"ಅನುಮತಿಸಿ"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"ಬೇಡ"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"<xliff:g id="CARRIERNAME">%s</xliff:g> ವೈ-ಫೈ ಗೆ ಕನೆಕ್ಟ್ ಮಾಡುವುದೇ?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"ಸಾಧನದ ಸ್ಥಳವನ್ನು ಟ್ರ್ಯಾಕ್ ಮಾಡಲು ಬಳಸಬಹುದಾದ SIM ಐಡಿಯನ್ನು ಈ ನೆಟ್‌ವರ್ಕ್‌ಗಳು ಸ್ವೀಕರಿಸುತ್ತವೆ"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"ಕನೆಕ್ಟ್ ಮಾಡಿ"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"ಕನೆಕ್ಟ್ ಮಾಡಬೇಡಿ"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"ಕನೆಕ್ಷನ್ ಅನ್ನು ಖಚಿತಪಡಿಸಬೇಕೇ?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"ನೀವು ಕನೆಕ್ಟ್ ಮಾಡಿದರೆ, <xliff:g id="CARRIERNAME">%s</xliff:g> ವೈ-ಫೈ ನೆಟ್‌ವರ್ಕ್‌ಗಳು ನಿಮ್ಮ SIM ಗೆ ಸಂಬಂಧಿಸಿದ ಅನನ್ಯ ಐಡಿಗೆ ಪ್ರವೇಶ ಪಡೆಯಬಹುದು ಅಥವಾ ಅದನ್ನು ಹಂಚಿಕೊಳ್ಳಬಹುದು. ನಿಮ್ಮ ಸಾಧನದ ಸ್ಥಳವನ್ನು ಟ್ರ್ಯಾಕ್ ಮಾಡಲು ಇದು ಅವಕಾಶ ನೀಡಬಹುದು."</string>
diff --git a/service/ServiceWifiResources/res/values-ko/strings.xml b/service/ServiceWifiResources/res/values-ko/strings.xml
index ce8c43b..2daf32a 100644
--- a/service/ServiceWifiResources/res/values-ko/strings.xml
+++ b/service/ServiceWifiResources/res/values-ko/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g>에서 네트워크를 제안했습니다. 기기가 자동으로 연결될 수 있습니다."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"허용"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"허용 안함"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"<xliff:g id="CARRIERNAME">%s</xliff:g> Wi-Fi에 연결하시겠습니까?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"이 네트워크는 기기 위치 추적에 사용할 수 있는 SIM ID를 수신합니다."</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"연결"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"연결 안 함"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"연결하시겠습니까?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"연결하면 <xliff:g id="CARRIERNAME">%s</xliff:g> Wi‑Fi 네트워크에서 SIM과 연결된 고유 ID에 액세스하거나 고유 ID를 공유할 수 있습니다. 이 경우 내 기기의 위치를 추적할 수도 있습니다."</string>
diff --git a/service/ServiceWifiResources/res/values-ky/strings.xml b/service/ServiceWifiResources/res/values-ky/strings.xml
index a7a22ec..2c486b7 100644
--- a/service/ServiceWifiResources/res/values-ky/strings.xml
+++ b/service/ServiceWifiResources/res/values-ky/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> сунуштаган тармактар. Түзмөк автоматтык түрдө туташышы мүмкүн."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Уруксат берүү"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Жок, рахмат"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"<xliff:g id="CARRIERNAME">%s</xliff:g> Wi‑Fi тармагына туташтырылсынбы?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Бул тармактарга SIM-картанын идентификатору берилип, анын жардамы менен түзмөктүн жайгашкан жерин аныктоого болот"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Туташуу"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Туташпасын"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Туташууну ырастайсызбы?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Туташсаңыз, <xliff:g id="CARRIERNAME">%s</xliff:g> операторунун Wi‑Fi тармактары SIM картаңызга байланышкан өзгөчө идентификаторго мүмкүнчүлүк алып, аны башкалар менен бөлүшө алат. Ушуну менен, түзмөгүңүздүн жайгашкан жерин аныктай аласыз."</string>
diff --git a/service/ServiceWifiResources/res/values-lo/strings.xml b/service/ServiceWifiResources/res/values-lo/strings.xml
index 6bacb06..15453c4 100644
--- a/service/ServiceWifiResources/res/values-lo/strings.xml
+++ b/service/ServiceWifiResources/res/values-lo/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"ເຄືອຂ່າຍ <xliff:g id="NAME">%s</xliff:g> ທີ່ແນະນຳ. ອຸປະກອນອາດເຊື່ອມຕໍ່ເອງໂດຍອັດຕະໂນມັດ."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"ອະນຸຍາດ"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"ບໍ່, ຂອບໃຈ"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"ເຊື່ອມຕໍ່ກັບ Wi-Fi <xliff:g id="CARRIERNAME">%s</xliff:g> ບໍ?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"ເຄືອຂ່າຍເຫຼົ່ານີ້ຈະໄດ້ຮັບ ID ຊິມທີ່ສາມາດໃຊ້ເພື່ອຕິດຕາມສະຖານທີ່ຂອງອຸປະກອນໄດ້"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"ເຊື່ອມຕໍ່"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"ຢ່າເຊື່ອມຕໍ່"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"ຢືນຢັນການເຊື່ອມຕໍ່ບໍ?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"ຫາກທ່ານເຊື່ອມຕໍ່, ເຄືອຂ່າຍ Wi-Fi ຂອງ <xliff:g id="CARRIERNAME">%s</xliff:g> ຈະສາມາດເຂົ້າເຖິງ ແລະ ແບ່ງປັນ unique ID ທີ່ເຊື່ອມໂຍງກັບຊິມຂອງທ່ານໄດ້. ນີ້ອາດເຮັດໃຫ້ສາມາດຕິດຕາມສະຖານທີ່ຂອງອຸປະກອນທ່ານໄດ້."</string>
diff --git a/service/ServiceWifiResources/res/values-lt/strings.xml b/service/ServiceWifiResources/res/values-lt/strings.xml
index 3d90093..bcb010b 100644
--- a/service/ServiceWifiResources/res/values-lt/strings.xml
+++ b/service/ServiceWifiResources/res/values-lt/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"„<xliff:g id="NAME">%s</xliff:g>“ siūlomi tinklai. Įrenginys gali prisijungti automatiškai."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Leisti"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Ne, ačiū"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Prisijungti prie „<xliff:g id="CARRIERNAME">%s</xliff:g>“ „Wi-Fi“?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Šie tinklai gauna SIM kortelės ID, kurį galima naudoti įrenginio vietovei stebėti"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Prisijungti"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Nesprisijungti"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Patvirtinti prijungimą?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Prisijungus „<xliff:g id="CARRIERNAME">%s</xliff:g>“ teikiami „Wi‑Fi“ tinklai gali pasiekti ar bendrinti su jūsų SIM susietą unikalų ID. Gali būti, kad jūsų įrenginio vieta bus stebima."</string>
diff --git a/service/ServiceWifiResources/res/values-lv/strings.xml b/service/ServiceWifiResources/res/values-lv/strings.xml
index 8a0347a..9ea6d37 100644
--- a/service/ServiceWifiResources/res/values-lv/strings.xml
+++ b/service/ServiceWifiResources/res/values-lv/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"Lietotnes <xliff:g id="NAME">%s</xliff:g> ieteiktie tīkli. Ierīcē var tikt automātiski izveidots savienojums."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Atļaut"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Nē, paldies"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Vai izveidot savienojumu ar <xliff:g id="CARRIERNAME">%s</xliff:g> Wi‑Fi?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Šie tīkli saņem SIM ID, ko var izmantot, lai izsekotu ierīces atrašanās vietu."</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Izveidot savienojumu"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Neizveidot savienojumu"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Vai apstiprināt savienojumu?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Ja izveidosiet savienojumu, <xliff:g id="CARRIERNAME">%s</xliff:g> Wi‑Fi tīkli varēs piekļūt unikālajam ID, kas saistīts ar jūsu SIM, vai kopīgot to. Tādējādi varēs tikt izsekota jūsu ierīces atrašanās vieta."</string>
diff --git a/service/ServiceWifiResources/res/values-mcc310-mnc004-ta/strings.xml b/service/ServiceWifiResources/res/values-mcc310-mnc004-ta/strings.xml
index 0049c9c..da08ad4 100644
--- a/service/ServiceWifiResources/res/values-mcc310-mnc004-ta/strings.xml
+++ b/service/ServiceWifiResources/res/values-mcc310-mnc004-ta/strings.xml
@@ -16,11 +16,11 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="wifi_eap_error_message_code_32760" msgid="5711364720754353427">"<xliff:g id="SSID">%1$s</xliff:g> : Verizon கவரேஜ் பகுதியைத் தாண்டி Verizon Wi-Fi Accessஸுடன் இணைக்க முடியாது."</string>
-    <string name="wifi_eap_error_message_code_32761" msgid="7871615432524623339">"<xliff:g id="SSID">%1$s</xliff:g> : நீங்கள் Verizon Wi-Fi Accessஸிற்கு சந்தா செய்யவில்லை. 800-922-0204 என்ற எண்ணில் எங்களைத் தொடர்புகொள்ளவும்."</string>
-    <string name="wifi_eap_error_message_code_32762" msgid="2994908156286205343">"<xliff:g id="SSID">%1$s</xliff:g> : உங்கள் Verizon Wi-Fi Access கணக்கில் சிக்கல் உள்ளது. 800-922-0204 என்ற எண்ணில் எங்களைத் தொடர்புகொள்ளவும்."</string>
-    <string name="wifi_eap_error_message_code_32763" msgid="8051026304965697200">"<xliff:g id="SSID">%1$s</xliff:g> : Verizon Wi-Fi Accessஸுடன் ஏற்கெனவே இணைப்பில் உள்ளீர்கள்."</string>
-    <string name="wifi_eap_error_message_code_32764" msgid="7311904315070770780">"<xliff:g id="SSID">%1$s</xliff:g> : Verizon Wi-Fi Accessஸுடன் இணைப்பதில் சிக்கல் உள்ளது. 800-922-0204 என்ற எண்ணில் எங்களைத் தொடர்புகொள்ளவும்."</string>
-    <string name="wifi_eap_error_message_code_32765" msgid="576968115890091383">"<xliff:g id="SSID">%1$s</xliff:g> : உங்கள் Verizon Wi-Fi Access கணக்கில் சிக்கல் உள்ளது. 800-922-0204 என்ற எண்ணில் எங்களைத் தொடர்புகொள்ளவும்."</string>
-    <string name="wifi_eap_error_message_code_32766" msgid="895346637290649533">"<xliff:g id="SSID">%1$s</xliff:g> : உங்கள் பகுதிக்கு Verizon Wi-Fi Access சேவை கிடைக்கவில்லை. மீண்டும் முயலவும் அல்லது வேறு இருப்பிடத்தில் இருந்து முயலவும்."</string>
+    <string name="wifi_eap_error_message_code_32760" msgid="5711364720754353427">"<xliff:g id="SSID">%1$s</xliff:g> : Verizon கவரேஜ் பகுதியைத் தாண்டி Verizon வைஃபை Accessஸுடன் இணைக்க முடியாது."</string>
+    <string name="wifi_eap_error_message_code_32761" msgid="7871615432524623339">"<xliff:g id="SSID">%1$s</xliff:g> : நீங்கள் Verizon வைஃபை Accessஸிற்கு சந்தா செய்யவில்லை. 800-922-0204 என்ற எண்ணில் எங்களைத் தொடர்புகொள்ளவும்."</string>
+    <string name="wifi_eap_error_message_code_32762" msgid="2994908156286205343">"<xliff:g id="SSID">%1$s</xliff:g> : உங்கள் Verizon வைஃபை Access கணக்கில் சிக்கல் உள்ளது. 800-922-0204 என்ற எண்ணில் எங்களைத் தொடர்புகொள்ளவும்."</string>
+    <string name="wifi_eap_error_message_code_32763" msgid="8051026304965697200">"<xliff:g id="SSID">%1$s</xliff:g> : Verizon வைஃபை Accessஸுடன் ஏற்கெனவே இணைப்பில் உள்ளீர்கள்."</string>
+    <string name="wifi_eap_error_message_code_32764" msgid="7311904315070770780">"<xliff:g id="SSID">%1$s</xliff:g> : Verizon வைஃபை Accessஸுடன் இணைப்பதில் சிக்கல் உள்ளது. 800-922-0204 என்ற எண்ணில் எங்களைத் தொடர்புகொள்ளவும்."</string>
+    <string name="wifi_eap_error_message_code_32765" msgid="576968115890091383">"<xliff:g id="SSID">%1$s</xliff:g> : உங்கள் Verizon வைஃபை Access கணக்கில் சிக்கல் உள்ளது. 800-922-0204 என்ற எண்ணில் எங்களைத் தொடர்புகொள்ளவும்."</string>
+    <string name="wifi_eap_error_message_code_32766" msgid="895346637290649533">"<xliff:g id="SSID">%1$s</xliff:g> : உங்கள் பகுதிக்கு Verizon வைஃபை Access சேவை கிடைக்கவில்லை. மீண்டும் முயலவும் அல்லது வேறு இருப்பிடத்தில் இருந்து முயலவும்."</string>
 </resources>
diff --git a/service/ServiceWifiResources/res/values-mcc310-mnc030/config.xml b/service/ServiceWifiResources/res/values-mcc310-mnc030/config.xml
new file mode 100644
index 0000000..765ffbe
--- /dev/null
+++ b/service/ServiceWifiResources/res/values-mcc310-mnc030/config.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <integer name="config_wifiFrameworkSecureNetworkBonus">540</integer>
+</resources>
diff --git a/service/ServiceWifiResources/res/values-mcc310-mnc170 b/service/ServiceWifiResources/res/values-mcc310-mnc170
new file mode 120000
index 0000000..179ce02
--- /dev/null
+++ b/service/ServiceWifiResources/res/values-mcc310-mnc170
@@ -0,0 +1 @@
+values-mcc310-mnc030
\ No newline at end of file
diff --git a/service/ServiceWifiResources/res/values-mcc310-mnc280 b/service/ServiceWifiResources/res/values-mcc310-mnc280
new file mode 120000
index 0000000..179ce02
--- /dev/null
+++ b/service/ServiceWifiResources/res/values-mcc310-mnc280
@@ -0,0 +1 @@
+values-mcc310-mnc030
\ No newline at end of file
diff --git a/service/ServiceWifiResources/res/values-mcc310-mnc380 b/service/ServiceWifiResources/res/values-mcc310-mnc380
new file mode 120000
index 0000000..179ce02
--- /dev/null
+++ b/service/ServiceWifiResources/res/values-mcc310-mnc380
@@ -0,0 +1 @@
+values-mcc310-mnc030
\ No newline at end of file
diff --git a/service/ServiceWifiResources/res/values-mcc310-mnc410 b/service/ServiceWifiResources/res/values-mcc310-mnc410
new file mode 120000
index 0000000..179ce02
--- /dev/null
+++ b/service/ServiceWifiResources/res/values-mcc310-mnc410
@@ -0,0 +1 @@
+values-mcc310-mnc030
\ No newline at end of file
diff --git a/service/ServiceWifiResources/res/values-mcc310-mnc560 b/service/ServiceWifiResources/res/values-mcc310-mnc560
new file mode 120000
index 0000000..179ce02
--- /dev/null
+++ b/service/ServiceWifiResources/res/values-mcc310-mnc560
@@ -0,0 +1 @@
+values-mcc310-mnc030
\ No newline at end of file
diff --git a/service/ServiceWifiResources/res/values-mcc310-mnc950 b/service/ServiceWifiResources/res/values-mcc310-mnc950
new file mode 120000
index 0000000..179ce02
--- /dev/null
+++ b/service/ServiceWifiResources/res/values-mcc310-mnc950
@@ -0,0 +1 @@
+values-mcc310-mnc030
\ No newline at end of file
diff --git a/service/ServiceWifiResources/res/values-mcc311-mnc180 b/service/ServiceWifiResources/res/values-mcc311-mnc180
new file mode 120000
index 0000000..179ce02
--- /dev/null
+++ b/service/ServiceWifiResources/res/values-mcc311-mnc180
@@ -0,0 +1 @@
+values-mcc310-mnc030
\ No newline at end of file
diff --git a/service/ServiceWifiResources/res/values-mcc311-mnc480-ta/strings.xml b/service/ServiceWifiResources/res/values-mcc311-mnc480-ta/strings.xml
index 32d01a2..1a1083e 100644
--- a/service/ServiceWifiResources/res/values-mcc311-mnc480-ta/strings.xml
+++ b/service/ServiceWifiResources/res/values-mcc311-mnc480-ta/strings.xml
@@ -16,11 +16,11 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="wifi_eap_error_message_code_32760" msgid="1073938428568671170">"<xliff:g id="SSID">%1$s</xliff:g> : Verizon கவரேஜ் பகுதியைத் தாண்டி Verizon Wi-Fi Accessஸுடன் இணைக்க முடியாது."</string>
-    <string name="wifi_eap_error_message_code_32761" msgid="5240496654119200117">"<xliff:g id="SSID">%1$s</xliff:g> : நீங்கள் Verizon Wi-Fi Accessஸிற்கு சந்தா செய்யவில்லை. 800-922-0204 என்ற எண்ணில் எங்களைத் தொடர்புகொள்ளவும்."</string>
-    <string name="wifi_eap_error_message_code_32762" msgid="1380081181230313771">"<xliff:g id="SSID">%1$s</xliff:g> : உங்கள் Verizon Wi-Fi Access கணக்கில் சிக்கல் உள்ளது. 800-922-0204 என்ற எண்ணில் எங்களைத் தொடர்புகொள்ளவும்."</string>
-    <string name="wifi_eap_error_message_code_32763" msgid="2668371888408710653">"<xliff:g id="SSID">%1$s</xliff:g> : Verizon Wi-Fi Accessஸுடன் ஏற்கெனவே இணைப்பில் உள்ளீர்கள்."</string>
-    <string name="wifi_eap_error_message_code_32764" msgid="4857239018269450670">"<xliff:g id="SSID">%1$s</xliff:g> : Verizon Wi-Fi Accessஸுடன் இணைப்பதில் சிக்கல் உள்ளது. 800-922-0204 என்ற எண்ணில் எங்களைத் தொடர்புகொள்ளவும்."</string>
-    <string name="wifi_eap_error_message_code_32765" msgid="3223606535554861258">"<xliff:g id="SSID">%1$s</xliff:g> : உங்கள் Verizon Wi-Fi Access கணக்கில் சிக்கல் உள்ளது. 800-922-0204 என்ற எண்ணில் எங்களைத் தொடர்புகொள்ளவும்."</string>
-    <string name="wifi_eap_error_message_code_32766" msgid="2703925241673900110">"<xliff:g id="SSID">%1$s</xliff:g> : உங்கள் பகுதிக்கு Verizon Wi-Fi Access சேவை கிடைக்கவில்லை. மீண்டும் முயலவும் அல்லது வேறு இருப்பிடத்தில் இருந்து முயலவும்."</string>
+    <string name="wifi_eap_error_message_code_32760" msgid="1073938428568671170">"<xliff:g id="SSID">%1$s</xliff:g> : Verizon கவரேஜ் பகுதியைத் தாண்டி Verizon வைஃபை Accessஸுடன் இணைக்க முடியாது."</string>
+    <string name="wifi_eap_error_message_code_32761" msgid="5240496654119200117">"<xliff:g id="SSID">%1$s</xliff:g> : நீங்கள் Verizon வைஃபை Accessஸிற்கு சந்தா செய்யவில்லை. 800-922-0204 என்ற எண்ணில் எங்களைத் தொடர்புகொள்ளவும்."</string>
+    <string name="wifi_eap_error_message_code_32762" msgid="1380081181230313771">"<xliff:g id="SSID">%1$s</xliff:g> : உங்கள் Verizon வைஃபை Access கணக்கில் சிக்கல் உள்ளது. 800-922-0204 என்ற எண்ணில் எங்களைத் தொடர்புகொள்ளவும்."</string>
+    <string name="wifi_eap_error_message_code_32763" msgid="2668371888408710653">"<xliff:g id="SSID">%1$s</xliff:g> : Verizon வைஃபை Accessஸுடன் ஏற்கெனவே இணைப்பில் உள்ளீர்கள்."</string>
+    <string name="wifi_eap_error_message_code_32764" msgid="4857239018269450670">"<xliff:g id="SSID">%1$s</xliff:g> : Verizon வைஃபை Accessஸுடன் இணைப்பதில் சிக்கல் உள்ளது. 800-922-0204 என்ற எண்ணில் எங்களைத் தொடர்புகொள்ளவும்."</string>
+    <string name="wifi_eap_error_message_code_32765" msgid="3223606535554861258">"<xliff:g id="SSID">%1$s</xliff:g> : உங்கள் Verizon வைஃபை Access கணக்கில் சிக்கல் உள்ளது. 800-922-0204 என்ற எண்ணில் எங்களைத் தொடர்புகொள்ளவும்."</string>
+    <string name="wifi_eap_error_message_code_32766" msgid="2703925241673900110">"<xliff:g id="SSID">%1$s</xliff:g> : உங்கள் பகுதிக்கு Verizon வைஃபை Access சேவை கிடைக்கவில்லை. மீண்டும் முயலவும் அல்லது வேறு இருப்பிடத்தில் இருந்து முயலவும்."</string>
 </resources>
diff --git a/service/ServiceWifiResources/res/values-mk/strings.xml b/service/ServiceWifiResources/res/values-mk/strings.xml
index 76644b5..5079742 100644
--- a/service/ServiceWifiResources/res/values-mk/strings.xml
+++ b/service/ServiceWifiResources/res/values-mk/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"Предложени мрежи од <xliff:g id="NAME">%s</xliff:g>. Уредот може да се поврзе автоматски."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Дозволи"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Не, фала"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Да се поврзе на Wi-Fi на <xliff:g id="CARRIERNAME">%s</xliff:g>?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Мреживе примаат уникатен ID на SIM што може да се користи за следење на локацијата на уредот"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Поврзи"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Не поврзувај"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Да се потврди врската?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Ако се поврзете, Wi‑Fi мрежите на <xliff:g id="CARRIERNAME">%s</xliff:g> може да имаат пристап до уникатен ID поврзан со вашата SIM-картичка или да го споделуваат. Ова може да овозможи следење на локацијата на вашиот уред."</string>
diff --git a/service/ServiceWifiResources/res/values-ml/strings.xml b/service/ServiceWifiResources/res/values-ml/strings.xml
index 9a675af..632de13 100644
--- a/service/ServiceWifiResources/res/values-ml/strings.xml
+++ b/service/ServiceWifiResources/res/values-ml/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> നിർദ്ദേശിച്ച നെറ്റ്‌വർക്കുകൾ. ഉപകരണം സ്വയമേവ കണക്‌റ്റ് ചെയ്‌തേക്കാം."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"അനുവദിക്കുക"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"വേണ്ട"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"<xliff:g id="CARRIERNAME">%s</xliff:g> വൈഫൈയിൽ കണക്റ്റ് ചെയ്യണോ?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"ഉപകരണ ലൊക്കേഷൻ ട്രാക്ക് ചെയ്യാൻ ഉപയോഗിക്കാവുന്ന സിം ഐഡി ഈ നെറ്റ്‌വർക്കുകൾക്ക് ലഭിക്കും"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"കണക്‌റ്റ് ചെയ്യുക"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"കണക്റ്റ് ചെയ്യരുത്"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"കണക്ഷൻ സ്ഥിരീകരിക്കണോ?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"നിങ്ങൾ കണക്റ്റ് ചെയ്യുകയാണെങ്കിൽ, സിമ്മുമായി ബന്ധപ്പെട്ട തനത് ഐഡി <xliff:g id="CARRIERNAME">%s</xliff:g> വൈഫൈ നെറ്റ്‌വർക്കുകൾ ആക്സസ് ചെയ്യുകയോ പങ്കിടുകയോ ചെയ്തേക്കാം. നിങ്ങളുടെ ഉപകരണ ലൊക്കേഷൻ ട്രാക്ക് ചെയ്യാൻ ഇത് അനുവദിച്ചേക്കാം."</string>
@@ -57,7 +54,7 @@
     <string name="ok" msgid="847575529546290102">"ശരി"</string>
     <string name="wifi_p2p_invitation_sent_title" msgid="6552639940428040869">"ക്ഷണം അയച്ചു"</string>
     <string name="wifi_p2p_invitation_to_connect_title" msgid="8917157937652519251">"കണക്റ്റുചെയ്യാനുള്ള ക്ഷണം"</string>
-    <string name="wifi_p2p_from_message" msgid="5921308150192756898">"അയച്ചത്:"</string>
+    <string name="wifi_p2p_from_message" msgid="5921308150192756898">"അയയ്ക്കുന്നത്:"</string>
     <string name="wifi_p2p_to_message" msgid="3809923305696994787">"ടു:"</string>
     <string name="wifi_p2p_enter_pin_message" msgid="5200220251738047620">"ആവശ്യമായ പിൻ ടൈപ്പുചെയ്യുക:"</string>
     <string name="wifi_p2p_show_pin_message" msgid="1000091690967930798">"പിൻ:"</string>
diff --git a/service/ServiceWifiResources/res/values-mn/strings.xml b/service/ServiceWifiResources/res/values-mn/strings.xml
index b48552d..c168472 100644
--- a/service/ServiceWifiResources/res/values-mn/strings.xml
+++ b/service/ServiceWifiResources/res/values-mn/strings.xml
@@ -31,15 +31,12 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> сүлжээ санал болголоо. Төхөөрөмж автоматаар холбогдож магадгүй."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Зөвшөөрөх"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Үгүй, баярлалаа"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"<xliff:g id="CARRIERNAME">%s</xliff:g>-н Wi‑Fi-д холбоогдох уу?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Эдгээр сүлжээ нь төхөөрөмжийн байршлыг тандахад ашиглах боломжтой SIM ID-г хүлээн авдаг"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Холбогдох"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Битгий холбогд"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Холболтыг баталгаажуулах уу?"</string>
-    <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Хэрэв та холбогдвол <xliff:g id="CARRIERNAME">%s</xliff:g>-н Wi‑Fi сүлжээ нь таны СИМ-тэй холбоотой цор ганц дугаарт хандаж эсвэл түүнийг хуваалцаж болзошгүй. Энэ нь таны төхөөрөмжийн байршлыг тандахыг зөвшөөрч болзошгүй."</string>
+    <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Хэрэв та холбогдвол <xliff:g id="CARRIERNAME">%s</xliff:g>-н Wi‑Fi сүлжээ нь таны SIM-тэй холбоотой цор ганц дугаарт хандаж эсвэл түүнийг хуваалцаж болзошгүй. Энэ нь таны төхөөрөмжийн байршлыг тандахыг зөвшөөрч болзошгүй."</string>
     <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_confirmation" msgid="2168947026413431603">"Холбогдох"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_confirmation" msgid="5156881939985876066">"Битгий холбогд"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="3868826648004934540">"Wi‑Fi автоматаар асна"</string>
@@ -83,5 +80,5 @@
     <string name="wifi_softap_auto_shutdown_timeout_expired_title" msgid="4896534374569504484">"Сүлжээний цэгийг унтраасан"</string>
     <string name="wifi_softap_auto_shutdown_timeout_expired_summary" msgid="7975476698140267728">"Ямар ч төхөөрөмж холбогдоогүй байна. Өөрчлөхийн тулд товшино уу."</string>
     <string name="wifi_sim_required_title" msgid="2262227800991155459">"Wifi-г салгалаа"</string>
-    <string name="wifi_sim_required_message" msgid="284812212346125745">"<xliff:g id="SSID">%1$s</xliff:g>-д холбогдохын тулд <xliff:g id="CARRIER_NAME">%2$s</xliff:g> СИМ-г хийнэ үү"</string>
+    <string name="wifi_sim_required_message" msgid="284812212346125745">"<xliff:g id="SSID">%1$s</xliff:g>-д холбогдохын тулд <xliff:g id="CARRIER_NAME">%2$s</xliff:g> SIM-г хийнэ үү"</string>
 </resources>
diff --git a/service/ServiceWifiResources/res/values-mr/strings.xml b/service/ServiceWifiResources/res/values-mr/strings.xml
index f570589..563b3da 100644
--- a/service/ServiceWifiResources/res/values-mr/strings.xml
+++ b/service/ServiceWifiResources/res/values-mr/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> सुचवलेली नेटवर्क. डिव्हाइस आपोआप कनेक्ट होऊ शकते."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"अनुमती द्या"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"नाही, नको"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"<xliff:g id="CARRIERNAME">%s</xliff:g> वाय-फायशी कनेक्ट करायचे आहे का?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"या नेटवर्कना एक सिम आयडी मिळतो जो डिव्हाइस स्थानाचा माग ठेवण्यासाठी वापरला जाऊ शकतो"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"कनेक्ट करा"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"कनेक्ट करू नका"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"कनेक्शन निश्चित करायचे आहे का?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"तुम्ही कनेक्ट केल्यास, <xliff:g id="CARRIERNAME">%s</xliff:g> वाय-फाय नेटवर्क तुमच्या सिम शी संबंधित एक युनिक आयडी अ‍ॅक्सेस किंवा शेअर करू शकतात. यामुळे तुमच्या डिव्हाइस स्थानाचा मागोवा घेतला जाऊ शकतो."</string>
diff --git a/service/ServiceWifiResources/res/values-ms/strings.xml b/service/ServiceWifiResources/res/values-ms/strings.xml
index c02011e..9aa664c 100644
--- a/service/ServiceWifiResources/res/values-ms/strings.xml
+++ b/service/ServiceWifiResources/res/values-ms/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"Rangkaian yang dicadangkan oleh <xliff:g id="NAME">%s</xliff:g>. Peranti mungkin disambungkan secara automatik."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Benarkan"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Tidak perlu"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Sambung ke Wi‑Fi <xliff:g id="CARRIERNAME">%s</xliff:g>?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Rangkaian ini menerima ID SIM yang boleh digunakan untuk menjejaki lokasi peranti"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Sambung"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Jangan sambung"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Sahkan sambungan?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Jika anda bersambung, rangkaian Wi-Fi <xliff:g id="CARRIERNAME">%s</xliff:g> mungkin mengakses atau berkongsi ID unik yang dikaitkan dengan SIM anda. Hal ini mungkin membenarkan lokasi peranti anda dijejaki."</string>
diff --git a/service/ServiceWifiResources/res/values-my/strings.xml b/service/ServiceWifiResources/res/values-my/strings.xml
index d615ccb..31f9008 100644
--- a/service/ServiceWifiResources/res/values-my/strings.xml
+++ b/service/ServiceWifiResources/res/values-my/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> သည် ကွန်ရက်များကို အကြံပြုထားသည်။ စက်သည် အလိုအလျောက် ချိတ်ဆက်နိုင်သည်။"</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"ခွင့်ပြုရန်"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"မလိုပါ"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"<xliff:g id="CARRIERNAME">%s</xliff:g> Wi-Fi သို့ ချိတ်ဆက်မလား။"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"ဤကွန်ရက်များက စက်တည်နေရာကို ခြေရာခံရန် အသုံးပြုနိုင်သည့် SIM ID တစ်ခုကို ရရှိပါသည်"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"ချိတ်ဆက်ပါ"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"မချိတ်ဆက်ပါနှင့်"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"ချိတ်ဆက်မှု အတည်ပြုမလား။"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"သင်ချိတ်ဆက်ပါက <xliff:g id="CARRIERNAME">%s</xliff:g> Wi‑Fi ကွန်ရက်များက သင့်ဆင်းမ်ကတ်နှင့် ဆက်စပ်နေသည့် သီးသန့် ID ကို သုံးခြင်း သို့မဟုတ် မျှဝေခြင်းတို့ ပြုလုပ်နိုင်သည်။ ၎င်းက သင့်စက်၏တည်နေရာကို ခြေရာခံခွင့် ပြုနိုင်သည်။"</string>
diff --git a/service/ServiceWifiResources/res/values-nb/strings.xml b/service/ServiceWifiResources/res/values-nb/strings.xml
index 3a0208d..b0c0e85 100644
--- a/service/ServiceWifiResources/res/values-nb/strings.xml
+++ b/service/ServiceWifiResources/res/values-nb/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g>-foreslåtte nettverk. Enheten kan koble til automatisk."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Tillat"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Nei takk"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Vil du koble til <xliff:g id="CARRIERNAME">%s</xliff:g>-Wi-Fi?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Disse nettverkene mottar en SIM-ID som kan brukes til å spore enhetsposisjonen"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Koble til"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Ikke koble til"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Vil du bekrefte tilkoblingen?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Hvis du kobler til, kan <xliff:g id="CARRIERNAME">%s</xliff:g>-Wi‑Fi-nettverk få tilgang til eller dele en unik ID som er knyttet til SIM-kortet ditt. Dette gjør at enhetens posisjon kan spores."</string>
diff --git a/service/ServiceWifiResources/res/values-ne/strings.xml b/service/ServiceWifiResources/res/values-ne/strings.xml
index ce42f2e..82f4e2e 100644
--- a/service/ServiceWifiResources/res/values-ne/strings.xml
+++ b/service/ServiceWifiResources/res/values-ne/strings.xml
@@ -31,22 +31,19 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> ले सिफारिस गरेका नेटवर्कहरू। यन्त्र स्वतः जडान हुन सक्छ।"</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"अनुमति दिनुहोस्"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"पर्दैन, धन्यवाद"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"<xliff:g id="CARRIERNAME">%s</xliff:g> Wi-Fi मा कनेक्ट गर्ने हो?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"यी नेटवर्कहरूलाई डिभाइसको स्थान पहिल्याउन प्रयोग गर्न मिल्ने एउटा अद्वितीय SIM कार्ड ID उपलब्ध गराइन्छ"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"कनेक्ट गर्नुहोस्"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"नजोड्नुहोस्"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"जोडिने हो?"</string>
-    <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"तपाईं जोडिनुभयो भने <xliff:g id="CARRIERNAME">%s</xliff:g> Wi‑Fi नेटवर्कहरूले तपाईंको SIM सँग सम्बद्ध अद्वित्तीय ID प्रयोग गर्न वा उक्त ID आदान प्रदान गर्न सक्छ। उक्त ID प्रयोग गरी तपाईंको यन्त्रको स्थान पहिल्याउन सकिन्छ।"</string>
+    <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"तपाईं जोडिनुभयो भने <xliff:g id="CARRIERNAME">%s</xliff:g> Wi‑Fi नेटवर्कहरूले तपाईंको SIM सँग सम्बद्ध अद्वित्तीय ID प्रयोग गर्न वा उक्त ID आदान प्रदान गर्न सक्छ। उक्त ID प्रयोग गरी तपाईंको डिभाइसको स्थान पहिल्याउन सकिन्छ।"</string>
     <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_confirmation" msgid="2168947026413431603">"जोड्नुहोस्"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_confirmation" msgid="5156881939985876066">"नजोड्नुहोस्"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="3868826648004934540">"Wi‑Fi स्वतः सक्रिय हुनेछ"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="5705886295837387430">"तपाईं कुनै सुरक्षित गरिएको उच्च गुणस्तरीय नेटवर्कको नजिक हुनुभएको अवस्थामा"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="6209706680391785825">"फेरि सक्रिय नगर्नुहोला"</string>
     <string name="wifi_wakeup_enabled_title" msgid="5043486751612595850">"Wi‑Fi स्वतः सक्रिय गरियो"</string>
-    <string name="wifi_wakeup_enabled_content" msgid="3911262526267025882">"तपाईं कुनै सुरक्षित गरिएको नेटवर्क नजिकै हुनुहुन्छ: <xliff:g id="NETWORK_SSID">%1$s</xliff:g>"</string>
+    <string name="wifi_wakeup_enabled_content" msgid="3911262526267025882">"तपाईं कुनै सेभ गरिएको नेटवर्क नजिकै हुनुहुन्छ: <xliff:g id="NETWORK_SSID">%1$s</xliff:g>"</string>
     <string name="wifi_watchdog_network_disabled" msgid="5769226742956006362">"वाइ-फाइसँग जडान गर्न सकेन"</string>
     <string name="wifi_watchdog_network_disabled_detailed" msgid="1725243835135539125">" खराब इन्टरनेट जडान रहेको छ।"</string>
     <string name="wifi_connect_alert_title" msgid="2368200646665663612">"जडान अनुमति दिने हो?"</string>
@@ -68,7 +65,7 @@
     <string name="wifi_cannot_connect_with_randomized_mac_title" msgid="2344570489693915253">"<xliff:g id="SSID">%1$s</xliff:g> मा जडान गर्न सकिएन"</string>
     <string name="wifi_cannot_connect_with_randomized_mac_message" msgid="4834133226521813352">"गोपनीयता सेटिङ परिवर्तन गर्न ट्याप गरेर फेरि प्रयास गर्नुहोस्"</string>
     <string name="wifi_disable_mac_randomization_dialog_title" msgid="2054540994993681606">"गोपनीयतासम्बन्धी सेटिङ परिवर्तन गर्ने हो?"</string>
-    <string name="wifi_disable_mac_randomization_dialog_message" msgid="8874064864332248988">"जोड्नका लागि <xliff:g id="SSID">%1$s</xliff:g> ले तपाईंको यन्त्रको MAC ठेगाना अर्थात् एउटा अद्वितीय पहिचानकर्ता प्रयोग गर्नु पर्ने हुन्छ। हाल, यो नेटवर्कमा लागू हुने तपाईंको गोपनीयतासम्बन्धी सेटिङले अनियमित पहिचानकर्ताको प्रयोग गर्छ। \n\nयो परिवर्तन गर्नाले वरपरका यन्त्रहरूलाई तपाईंको यन्त्रको स्थान ट्र्याक गर्न दिने सम्भावना हुन्छ।"</string>
+    <string name="wifi_disable_mac_randomization_dialog_message" msgid="8874064864332248988">"जोड्नका लागि <xliff:g id="SSID">%1$s</xliff:g> ले तपाईंको डिभाइसको MAC एड्रेस अर्थात् एउटा अद्वितीय पहिचानकर्ता प्रयोग गर्नु पर्ने हुन्छ। हाल, यो नेटवर्कमा लागू हुने तपाईंको गोपनीयतासम्बन्धी सेटिङले अनियमित पहिचानकर्ताको प्रयोग गर्छ। \n\nयो परिवर्तन गर्नाले वरपरका यन्त्रहरूलाई तपाईंको डिभाइसको स्थान ट्र्याक गर्न दिने सम्भावना हुन्छ।"</string>
     <string name="wifi_disable_mac_randomization_dialog_confirm_text" msgid="6954419863076751626">"सेटिङ परिवर्तन गर्नुहोस्"</string>
     <string name="wifi_disable_mac_randomization_dialog_success" msgid="5849155828154391387">"सेटिङ अद्यावधिक गरियो। फेरि जोडी हेर्नुहोस्।"</string>
     <string name="wifi_disable_mac_randomization_dialog_failure" msgid="2894643619143813096">"गोपनीयता सेटिङ परिवर्तन गर्न सकिँदैन"</string>
diff --git a/service/ServiceWifiResources/res/values-nl/strings.xml b/service/ServiceWifiResources/res/values-nl/strings.xml
index 659bf2b..db9e233 100644
--- a/service/ServiceWifiResources/res/values-nl/strings.xml
+++ b/service/ServiceWifiResources/res/values-nl/strings.xml
@@ -31,21 +31,18 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> heeft netwerken voorgesteld. Apparaat kan automatisch verbinding maken."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Toestaan"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Nee, bedankt"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Verbinding maken met wifi van <xliff:g id="CARRIERNAME">%s</xliff:g>?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Deze netwerken ontvangen een simkaart-ID die kan worden gebruikt om de apparaatlocatie bij te houden"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Verbinding maken"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Geen verbinding maken"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Verbinding bevestigen?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Als je verbinding maakt, hebben de wifi-netwerken van <xliff:g id="CARRIERNAME">%s</xliff:g> mogelijk toegang tot de unieke ID die aan je simkaart is gekoppeld of kunnen ze deze delen. Op deze manier kan de locatie van je apparaat worden bijgehouden."</string>
     <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_confirmation" msgid="2168947026413431603">"Verbinding maken"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_confirmation" msgid="5156881939985876066">"Geen verbinding maken"</string>
-    <string name="wifi_wakeup_onboarding_title" msgid="3868826648004934540">"Wifi wordt automatisch ingeschakeld"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="3868826648004934540">"Wifi wordt automatisch aangezet"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="5705886295837387430">"Wanneer je in de buurt van een opgeslagen netwerk van hoge kwaliteit bent"</string>
-    <string name="wifi_wakeup_onboarding_action_disable" msgid="6209706680391785825">"Niet weer inschakelen"</string>
-    <string name="wifi_wakeup_enabled_title" msgid="5043486751612595850">"Wifi automatisch ingeschakeld"</string>
+    <string name="wifi_wakeup_onboarding_action_disable" msgid="6209706680391785825">"Niet weer aanzetten"</string>
+    <string name="wifi_wakeup_enabled_title" msgid="5043486751612595850">"Wifi automatisch aangezet"</string>
     <string name="wifi_wakeup_enabled_content" msgid="3911262526267025882">"Je bent in de buurt van een opgeslagen netwerk: <xliff:g id="NETWORK_SSID">%1$s</xliff:g>"</string>
     <string name="wifi_watchdog_network_disabled" msgid="5769226742956006362">"Kan geen verbinding maken met wifi"</string>
     <string name="wifi_watchdog_network_disabled_detailed" msgid="1725243835135539125">" heeft een slechte internetverbinding."</string>
@@ -61,9 +58,9 @@
     <string name="wifi_p2p_to_message" msgid="3809923305696994787">"Naar:"</string>
     <string name="wifi_p2p_enter_pin_message" msgid="5200220251738047620">"Voer de gewenste pincode in:"</string>
     <string name="wifi_p2p_show_pin_message" msgid="1000091690967930798">"Pincode"</string>
-    <string name="wifi_p2p_frequency_conflict_message" product="tablet" msgid="2875937871590955304">"De verbinding met het wifi-netwerk wordt tijdelijk uitgeschakeld terwijl de telefoon is verbonden met <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
-    <string name="wifi_p2p_frequency_conflict_message" product="tv" msgid="9133053225387001827">"De verbinding met het wifi-netwerk wordt tijdelijk uitgeschakeld terwijl het Android TV-apparaat is verbonden met <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
-    <string name="wifi_p2p_frequency_conflict_message" product="default" msgid="2226863827636191980">"De verbinding met het wifi-netwerk wordt tijdelijk uitgeschakeld terwijl de telefoon verbonden is met <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="wifi_p2p_frequency_conflict_message" product="tablet" msgid="2875937871590955304">"De verbinding met het wifi-netwerk wordt tijdelijk uitgezet terwijl de telefoon is verbonden met <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="wifi_p2p_frequency_conflict_message" product="tv" msgid="9133053225387001827">"De verbinding met het wifi-netwerk wordt tijdelijk uitgezet terwijl het Android TV-apparaat is verbonden met <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="wifi_p2p_frequency_conflict_message" product="default" msgid="2226863827636191980">"De verbinding met het wifi-netwerk wordt tijdelijk uitgezet terwijl de telefoon verbonden is met <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="dlg_ok" msgid="254496739491689405">"OK"</string>
     <string name="wifi_cannot_connect_with_randomized_mac_title" msgid="2344570489693915253">"Kan niet verbinden met <xliff:g id="SSID">%1$s</xliff:g>"</string>
     <string name="wifi_cannot_connect_with_randomized_mac_message" msgid="4834133226521813352">"Tik om de privacyinstellingen aan te passen en probeer het opnieuw"</string>
@@ -80,7 +77,7 @@
     <string name="wifi_eap_error_message_code_32764" msgid="7349538467012877101">"<xliff:g id="SSID">%1$s</xliff:g>: EAP-verificatiefout 32764"</string>
     <string name="wifi_eap_error_message_code_32765" msgid="2167528358066037980">"<xliff:g id="SSID">%1$s</xliff:g>: EAP-verificatiefout 32765"</string>
     <string name="wifi_eap_error_message_code_32766" msgid="2335996367705677670">"<xliff:g id="SSID">%1$s</xliff:g>: EAP-verificatiefout 32766"</string>
-    <string name="wifi_softap_auto_shutdown_timeout_expired_title" msgid="4896534374569504484">"Hotspot uitgeschakeld"</string>
+    <string name="wifi_softap_auto_shutdown_timeout_expired_title" msgid="4896534374569504484">"Hotspot staat uit"</string>
     <string name="wifi_softap_auto_shutdown_timeout_expired_summary" msgid="7975476698140267728">"Geen apparaten gekoppeld. Tik om te wijzigen."</string>
     <string name="wifi_sim_required_title" msgid="2262227800991155459">"Wifi-verbinding verbroken"</string>
     <string name="wifi_sim_required_message" msgid="284812212346125745">"Plaats een simkaart van <xliff:g id="CARRIER_NAME">%2$s</xliff:g> om verbinding te maken met <xliff:g id="SSID">%1$s</xliff:g>"</string>
diff --git a/service/ServiceWifiResources/res/values-or/strings.xml b/service/ServiceWifiResources/res/values-or/strings.xml
index a6d18c7..e7f4c54 100644
--- a/service/ServiceWifiResources/res/values-or/strings.xml
+++ b/service/ServiceWifiResources/res/values-or/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> ପ୍ରସ୍ତାବିତ ନେଟ୍‌ୱାର୍କଗୁଡ଼ିକ। ଡିଭାଇସ୍ ହୁଏତ ସ୍ୱଚାଳିତ ଭାବେ ସଂଯୋଗ ହୋଇପାରେ।"</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"ଅନୁମତି ଦିଅନ୍ତୁ"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"ନାହିଁ, ଥାଉ"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"<xliff:g id="CARRIERNAME">%s</xliff:g> ୱାଇ-ଫାଇ ସହ ସଂଯୋଗ କରିବେ?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"ଏହି ନେଟୱାର୍କଗୁଡ଼ିକ ଏକ SIM ID ପ୍ରାପ୍ତ କରେ ଯାହା ଡିଭାଇସର ଲୋକେସନ୍ ଟ୍ରାକ୍ କରିବାରେ ବ୍ୟବହାର କରାଯାଇପାରିବ"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"ସଂଯୋଗ କରନ୍ତୁ"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"ସଂଯୋଗ କରନ୍ତୁ ନାହିଁ"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"ସଂଯୋଗ ସୁନିଶ୍ଚିତ କରିବେ?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"ଯଦି ଆପଣ ସଂଯୋଗ କରିବେ, ତେବେ <xliff:g id="CARRIERNAME">%s</xliff:g> ୱାଇ-ଫାଇ ନେଟୱାର୍କଗୁଡ଼ିକ ଆପଣଙ୍କ SIM ସମ୍ବନ୍ଧିତ ଏକ ସ୍ଵତନ୍ତ୍ର ID ଆକ୍ସେସ୍ କିମ୍ବା ସେୟାର୍ କରିପାରେ। ଏହା ଆପଣଙ୍କ ଡିଭାଇସର ଲୋକେସନ୍ ଟ୍ରାକ୍ କରିବାକୁ ଅନୁମତି ଦେଇପାରେ।"</string>
@@ -52,7 +49,7 @@
     <string name="wifi_connect_alert_title" msgid="2368200646665663612">"ସଂଯୋଗର ଅନୁମତି ଦେବେ?"</string>
     <string name="wifi_connect_alert_message" msgid="7226456300982080746">"ଆପ୍ଲିକେଶନ୍‍ %1$s %2$s ୱାଇ-ଫାଇ ନେଟୱର୍କକୁ ସଂଯୋଗ କରିବାକୁ ଚାହେଁ"</string>
     <string name="wifi_connect_default_application" msgid="8917703737222707062">"ଏକ ଅନୁପ୍ରୟୋଗ"</string>
-    <string name="accept" msgid="8346431649376483879">"ସ୍ୱୀକାର କରନ୍ତୁ"</string>
+    <string name="accept" msgid="8346431649376483879">"ଗ୍ରହଣ କରନ୍ତୁ"</string>
     <string name="decline" msgid="4172251727603762084">"ପ୍ରତ୍ୟାଖ୍ୟାନ"</string>
     <string name="ok" msgid="847575529546290102">"ଠିକ୍‍ ଅଛି"</string>
     <string name="wifi_p2p_invitation_sent_title" msgid="6552639940428040869">"ନିମନ୍ତ୍ରଣ ପଠାଗଲା"</string>
diff --git a/service/ServiceWifiResources/res/values-pa/strings.xml b/service/ServiceWifiResources/res/values-pa/strings.xml
index 2f842fc..e095962 100644
--- a/service/ServiceWifiResources/res/values-pa/strings.xml
+++ b/service/ServiceWifiResources/res/values-pa/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> ਦੇ ਸੁਝਾਏ ਨੈੱਟਵਰਕ। ਸ਼ਾਇਦ ਡੀਵਾਈਸ ਸਵੈਚਲਿਤ ਤੌਰ \'ਤੇ ਕਨੈਕਟ ਹੋਵੇ।"</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"ਵਰਤਣ ਦਿਓ"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"ਨਹੀਂ ਧੰਨਵਾਦ"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"ਕੀ <xliff:g id="CARRIERNAME">%s</xliff:g> ਵਾਈ-ਫਾਈ ਨਾਲ ਕਨੈਕਟ ਕਰਨਾ ਹੈ?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"ਇਹ ਨੈੱਟਵਰਕ ਇੱਕ ਸਿਮ ਆਈਡੀ ਪ੍ਰਾਪਤ ਕਰਦੇ ਹਨ ਜਿਨ੍ਹਾਂ ਨੂੰ ਡੀਵਾਈਸ ਦੇ ਟਿਕਾਣੇ ਨੂੰ ਟਰੈਕ ਕਰਨ ਲਈ ਵਰਤਿਆ ਜਾ ਸਕਦਾ ਹੈ"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"ਕਨੈਕਟ ਕਰੋ"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"ਕਨੈਕਟ ਨਾ ਕਰੋ"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"ਕੀ ਕਨੈਕਸ਼ਨ ਦੀ ਤਸਦੀਕ ਕਰਨੀ ਹੈ?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"ਜੇ ਤੁਸੀਂ ਕਨੈਕਟ ਕਰਦੇ ਹੋ, ਤਾਂ <xliff:g id="CARRIERNAME">%s</xliff:g> ਵਾਈ-ਫਾਈ ਨੈੱਟਵਰਕ ਤੁਹਾਡੇ ਸਿਮ ਨਾਲ ਸੰਬੰਧਿਤ ਵਿਲੱਖਣ ਆਈਡੀ ਤੱਕ ਪਹੁੰਚ ਜਾਂ ਉਸ ਨੂੰ ਸਾਂਝਾ ਕਰ ਸਕਦੇ ਹਨ। ਇਸ ਨਾਲ ਸ਼ਾਇਦ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਦਾ ਟਿਕਾਣਾ ਟਰੈਕ ਕੀਤਾ ਜਾ ਸਕੇ।"</string>
diff --git a/service/ServiceWifiResources/res/values-pl/strings.xml b/service/ServiceWifiResources/res/values-pl/strings.xml
index 825db8b..8b4d8bd 100644
--- a/service/ServiceWifiResources/res/values-pl/strings.xml
+++ b/service/ServiceWifiResources/res/values-pl/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"Sugerowane sieci: <xliff:g id="NAME">%s</xliff:g>. Urządzenie może łączyć się automatycznie."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Zezwól"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Nie, dziękuję"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Połączyć z Wi-Fi operatora <xliff:g id="CARRIERNAME">%s</xliff:g>?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Te sieci otrzymują identyfikator SIM, który można wykorzystać do śledzenia lokalizacji urządzenia"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Połącz"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Nie łącz"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Potwierdzić połączenie?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Jeśli się połączysz, sieci Wi-Fi operatora <xliff:g id="CARRIERNAME">%s</xliff:g> będą mogły korzystać z unikalnego identyfikatora powiązanego z Twoją kartą SIM oraz go udostępniać. To może pozwolić na monitorowanie lokalizacji Twojego urządzenia."</string>
diff --git a/service/ServiceWifiResources/res/values-pt-rBR/strings.xml b/service/ServiceWifiResources/res/values-pt-rBR/strings.xml
index b3745f6..5c8b2e4 100644
--- a/service/ServiceWifiResources/res/values-pt-rBR/strings.xml
+++ b/service/ServiceWifiResources/res/values-pt-rBR/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"Redes sugeridas pelo app <xliff:g id="NAME">%s</xliff:g>. O dispositivo pode se conectar automaticamente."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Permitir"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Não"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Conectar à rede Wi-Fi <xliff:g id="CARRIERNAME">%s</xliff:g>?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Essas redes recebem um ID de chip que pode ser usado para monitorar o local do dispositivo"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Conectar"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Não conectar"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Confirmar a conexão?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Se você fizer a conexão, as redes Wi-Fi da <xliff:g id="CARRIERNAME">%s</xliff:g> poderão acessar ou compartilhar o código exclusivo associado ao seu chip. Isso talvez permita que o local do dispositivo seja monitorado."</string>
diff --git a/service/ServiceWifiResources/res/values-pt-rPT/strings.xml b/service/ServiceWifiResources/res/values-pt-rPT/strings.xml
index 0f24b4c..23b27a0 100644
--- a/service/ServiceWifiResources/res/values-pt-rPT/strings.xml
+++ b/service/ServiceWifiResources/res/values-pt-rPT/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"Redes sugeridas por <xliff:g id="NAME">%s</xliff:g>. O dispositivo pode estabelecer ligação automaticamente."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Permitir"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Não, obrigado"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Pretende estabelecer ligação à rede Wi-Fi do operador <xliff:g id="CARRIERNAME">%s</xliff:g>?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Estas redes recebem um ID do SIM que pode ser utilizado para monitorizar a localização dos dispositivos."</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Ligar"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Não ligar"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Pretende confirmar a ligação?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Caso proceda à ligação, as redes Wi-Fi do operador <xliff:g id="CARRIERNAME">%s</xliff:g> podem partilhar ou aceder a um ID exclusivo associado ao seu SIM. Esta ação pode permitir que a localização do seu dispositivo seja monitorizada."</string>
diff --git a/service/ServiceWifiResources/res/values-pt/strings.xml b/service/ServiceWifiResources/res/values-pt/strings.xml
index b3745f6..5c8b2e4 100644
--- a/service/ServiceWifiResources/res/values-pt/strings.xml
+++ b/service/ServiceWifiResources/res/values-pt/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"Redes sugeridas pelo app <xliff:g id="NAME">%s</xliff:g>. O dispositivo pode se conectar automaticamente."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Permitir"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Não"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Conectar à rede Wi-Fi <xliff:g id="CARRIERNAME">%s</xliff:g>?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Essas redes recebem um ID de chip que pode ser usado para monitorar o local do dispositivo"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Conectar"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Não conectar"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Confirmar a conexão?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Se você fizer a conexão, as redes Wi-Fi da <xliff:g id="CARRIERNAME">%s</xliff:g> poderão acessar ou compartilhar o código exclusivo associado ao seu chip. Isso talvez permita que o local do dispositivo seja monitorado."</string>
diff --git a/service/ServiceWifiResources/res/values-ro/strings.xml b/service/ServiceWifiResources/res/values-ro/strings.xml
index bf488d1..820d77f 100644
--- a/service/ServiceWifiResources/res/values-ro/strings.xml
+++ b/service/ServiceWifiResources/res/values-ro/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> a sugerat rețele. Dispozitivul se poate conecta automat."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Permiteți"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Nu, mulțumesc"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Vă conectați la rețeaua Wi-Fi <xliff:g id="CARRIERNAME">%s</xliff:g>?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Aceste rețele primesc un cod SIM care se poate folosi pentru a urmări locația dispozitivului"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Conectați"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Nu conectați"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Confirmați conexiunea?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Dacă vă conectați, rețelele Wi-Fi <xliff:g id="CARRIERNAME">%s</xliff:g> pot să acceseze sau să trimită un ID unic asociat profilului dvs. SIM. Astfel, locația dispozitivului poate fi urmărită."</string>
diff --git a/service/ServiceWifiResources/res/values-ru/strings.xml b/service/ServiceWifiResources/res/values-ru/strings.xml
index 8256a0f..45c4b1d 100644
--- a/service/ServiceWifiResources/res/values-ru/strings.xml
+++ b/service/ServiceWifiResources/res/values-ru/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"Приложение \"<xliff:g id="NAME">%s</xliff:g>\" рекомендует сети, к которым устройство может подключаться автоматически."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Разрешить"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Нет, спасибо"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Подключиться к сети Wi-Fi оператора \"<xliff:g id="CARRIERNAME">%s</xliff:g>\"?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Этим сетям будет передан идентификатор SIM-карты, с помощью которого можно отслеживать местоположение устройства."</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Подключиться"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Не подключаться"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Подтвердите подключение"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Если вы установите подключение, Wi‑Fi-сети оператора \"<xliff:g id="CARRIERNAME">%s</xliff:g>\" смогут получать доступ к уникальному идентификатору, связанному с вашей SIM-картой, или делиться им. Это позволит отслеживать местоположение устройства."</string>
diff --git a/service/ServiceWifiResources/res/values-si/strings.xml b/service/ServiceWifiResources/res/values-si/strings.xml
index 2de7eee..3e7984a 100644
--- a/service/ServiceWifiResources/res/values-si/strings.xml
+++ b/service/ServiceWifiResources/res/values-si/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> යෝජිත ජාල. උපාංගය ස්වයංක්‍රියව සම්බන්ධ වනු ඇත."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"ඉඩ දෙන්න"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"එපා, ස්තූතියි"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"<xliff:g id="CARRIERNAME">%s</xliff:g> Wi-Fi වෙත සබඳින්නද?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"උපාංග ස්ථානය නිරීක්ෂණය කිරීමට භාවිත කළ හැකි SIM ID එකක් මෙම ජාලවලට ලැබේ"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"සම්බන්ධ කරන්න"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"සම්බන්ධ නොකරන්න"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"නිවැරදි කිරීම තහවුරු කරන්නද?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"ඔබ සම්බන්ධ වුවහොත්, <xliff:g id="CARRIERNAME">%s</xliff:g> Wi‑Fi ජාල ඔබේ SIM එක හා සම්බන්ධිත අනන්‍ය ID එකකට ප්‍රවේශ වීමට හෝ එය බෙදා ගැනීමට හැකිය. මෙය ඔබේ උපාංගයෙහි ස්ථානය නිරීක්ෂණය කිරීමට ඉඩ දිය හැකිය."</string>
diff --git a/service/ServiceWifiResources/res/values-sk/strings.xml b/service/ServiceWifiResources/res/values-sk/strings.xml
index 0f263a6..2928434 100644
--- a/service/ServiceWifiResources/res/values-sk/strings.xml
+++ b/service/ServiceWifiResources/res/values-sk/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"Siete navrhuje aplikácia <xliff:g id="NAME">%s</xliff:g>. Zariadenie sa môže pripájať automaticky."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Povoliť"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Nie, ďakujem"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Chcete sa pripojiť k sieti Wi‑Fi operátora <xliff:g id="CARRIERNAME">%s</xliff:g>?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Tieto siete dostávajú jedinečný identifikátor SIM karty, pomocou ktorého je možné sledovať polohu zariadenia"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Pripojiť"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Nepripojiť"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Chcete potvrdiť pripojenie?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Po pripojení môžu siete Wi‑Fi operátora <xliff:g id="CARRIERNAME">%s</xliff:g> pristupovať k jedinečnému identifikátoru spojenému s vašou SIM kartou alebo ho zdieľať. To umožňuje sledovať polohu vášho zariadenia."</string>
diff --git a/service/ServiceWifiResources/res/values-sl/strings.xml b/service/ServiceWifiResources/res/values-sl/strings.xml
index 5d77e5f..9a1d12b 100644
--- a/service/ServiceWifiResources/res/values-sl/strings.xml
+++ b/service/ServiceWifiResources/res/values-sl/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> – predlagana omrežja. Naprava se lahko poveže samodejno."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Dovoli"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Ne, hvala"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Želite vzpostaviti povezavo z omrežjem Wi-Fi operaterja <xliff:g id="CARRIERNAME">%s</xliff:g>?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Ta omrežja prejmejo ID kartice SIM, s katerim je mogoče spremljati lokacijo naprave."</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Poveži"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Ne poveži"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Želite potrditi povezavo?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Če vzpostavite povezavo, lahko omrežja Wi-Fi operaterja <xliff:g id="CARRIERNAME">%s</xliff:g> dostopajo do enoličnega ID-ja, povezanega s kartico SIM, in ga delijo. To lahko omogoči spremljanje lokacije naprave."</string>
diff --git a/service/ServiceWifiResources/res/values-sq/strings.xml b/service/ServiceWifiResources/res/values-sq/strings.xml
index b315029..e9ce3a9 100644
--- a/service/ServiceWifiResources/res/values-sq/strings.xml
+++ b/service/ServiceWifiResources/res/values-sq/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"Rrjet e sugjeruara të <xliff:g id="NAME">%s</xliff:g>. Pajisja mund të lidhet automatikisht."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Lejo"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Jo, faleminderit"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Do të lidhesh me Wi-Fi nga <xliff:g id="CARRIERNAME">%s</xliff:g>?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Këto rrjete marrin një ID karte SIM që mund të përdoret për të monitoruar vendndodhjen"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Lidh"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Mos u lidh"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Të konfirmohet lidhja?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Nëse lidhesh, rrjetet Wi-Fi të <xliff:g id="CARRIERNAME">%s</xliff:g> mund të kenë qasje ose mund të ndajnë një ID unike që lidhet me kartën tënde SIM. Kjo mund të lejojë monitorimin e vendndodhjes sate."</string>
diff --git a/service/ServiceWifiResources/res/values-sr/strings.xml b/service/ServiceWifiResources/res/values-sr/strings.xml
index 6ebfdf4..36d3821 100644
--- a/service/ServiceWifiResources/res/values-sr/strings.xml
+++ b/service/ServiceWifiResources/res/values-sr/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"Мреже које предлаже <xliff:g id="NAME">%s</xliff:g>. Уређај ће се можда повезати аутоматски."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Дозволи"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Не, хвала"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Желите да се повежете на WiFi мрежу <xliff:g id="CARRIERNAME">%s</xliff:g>?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Ове мреже добијају ИД SIM картице који може да се користи за праћење локације уређаја"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Повежи"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Не повезуј"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Желите ли да потврдите повезивање?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Ако се повежете, Wi‑Fi мреже оператера <xliff:g id="CARRIERNAME">%s</xliff:g> могу да приступају јединственом ИД-у повезаном са SIM картицом или да га деле. То може да омогући праћење локације уређаја."</string>
diff --git a/service/ServiceWifiResources/res/values-sv/strings.xml b/service/ServiceWifiResources/res/values-sv/strings.xml
index 7a41ee3..75ace03 100644
--- a/service/ServiceWifiResources/res/values-sv/strings.xml
+++ b/service/ServiceWifiResources/res/values-sv/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"Nätverk som föreslagits av <xliff:g id="NAME">%s</xliff:g>. Enheten kan anslutas automatiskt."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Tillåt"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Nej tack"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Vill du ansluta till Wi-Fi från <xliff:g id="CARRIERNAME">%s</xliff:g>?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Dessa nätverk får ett SIM-id som kan användas till att spåra enhetens plats"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Anslut"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Anslut inte"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Vill du bekräfta anslutningen?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Om du ansluter kan Wi-Fi-nätverk från <xliff:g id="CARRIERNAME">%s</xliff:g> få åtkomst till eller dela ett unikt id som är kopplat till ditt SIM-kort. Detta kan göra det möjligt att spåra enhetens plats."</string>
diff --git a/service/ServiceWifiResources/res/values-sw/strings.xml b/service/ServiceWifiResources/res/values-sw/strings.xml
index 0853a97..18bbe01 100644
--- a/service/ServiceWifiResources/res/values-sw/strings.xml
+++ b/service/ServiceWifiResources/res/values-sw/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"Mitandao inayopendekezwa kwa ajili ya <xliff:g id="NAME">%s</xliff:g>. Huenda kifaa kikaunganisha kiotomatiki."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Ruhusu"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Hapana"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Ungependa kuunganisha kwenye Wi‑Fi ya <xliff:g id="CARRIERNAME">%s</xliff:g>?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Mitandao hii hupokea kitambulisho cha SIM kinachoweza kutumika ili kufuatilia mahali kifaa kilipo"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Unganisha"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Usiunganishe"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Ungependa kuthibitisha muunganisho?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Ukiunganisha, huenda mitandao ya Wi-Fi ya <xliff:g id="CARRIERNAME">%s</xliff:g> ikafikia au kushiriki kitambulisho cha kipekee kinachohusishwa na SIM yako. Huenda hii ikaruhusu ufuatiliaji wa mahali kifaa chako kilipo."</string>
diff --git a/service/ServiceWifiResources/res/values-ta/strings.xml b/service/ServiceWifiResources/res/values-ta/strings.xml
index 78477d4..cc1ddc4 100644
--- a/service/ServiceWifiResources/res/values-ta/strings.xml
+++ b/service/ServiceWifiResources/res/values-ta/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> பரிந்துரைக்கும் நெட்வொர்க்குகள். சாதனம் தானாக இணைக்கப்படக்கூடும்."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"அனுமதி"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"வேண்டாம்"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"<xliff:g id="CARRIERNAME">%s</xliff:g> வைஃபையுடன் இணைக்கவா?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"சாதன இருப்பிடத்தைக் கண்காணிக்கப் பயன்படுத்தப்படும் சிம் ஐடியை இந்த நெட்வொர்க்குகள் பெறும்"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"இணை"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"இணைக்காதே"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"நிச்சயமாக இணைக்கவா?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"இணைத்தால், உங்கள் SIMமுடன் இணைக்கப்பட்டிருக்கும் தனிப்பட்ட ஐடியை <xliff:g id="CARRIERNAME">%s</xliff:g> வைஃபை நெட்வொர்க்குகள் அணுகக்கூடும் அல்லது பகிரக்கூடும். இதனால் உங்கள் சாதனத்தின் இருப்பிடத்தை டிராக் செய்ய அனுமதிக்கப்படலாம்."</string>
diff --git a/service/ServiceWifiResources/res/values-te/strings.xml b/service/ServiceWifiResources/res/values-te/strings.xml
index 08ef99f..9ce3214 100644
--- a/service/ServiceWifiResources/res/values-te/strings.xml
+++ b/service/ServiceWifiResources/res/values-te/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> సూచించిన నెట్‌వర్క్‌లు. పరికరం ఆటోమేటిక్‌గా కనెక్ట్ అవచ్చు."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"అనుమతించు"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"వద్దు"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"<xliff:g id="CARRIERNAME">%s</xliff:g> Wi-Fiకి కనెక్ట్ చేయాలా?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"పరికర లొకేషన్‌ను ట్రాక్ చేయడానికి ఉపయోగపడే SIM IDని ఈ నెట్‌వర్క్‌లు అందుకుంటాయి"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"కనెక్ట్ చేయి"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"కనెక్ట్ చేయవద్దు"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"ఖచ్చితంగా కనెక్ట్ చేయాలా?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"<xliff:g id="CARRIERNAME">%s</xliff:g> నెట్‌వర్క్‌కు మీరు కనెక్ట్ చేస్తే, ఆ క్యారియర్ Wi‑Fi నెట్‌వర్క్‌లు మీ SIMకు అనుబంధితమైన ప్రత్యేక IDని యాక్సెస్ లేదా షేర్ చేయగలగవచ్చు. దీని వలన మీ పరికరం లొకేషన్ ట్రాక్ చేయబడవచ్చు."</string>
diff --git a/service/ServiceWifiResources/res/values-th/strings.xml b/service/ServiceWifiResources/res/values-th/strings.xml
index 15d1798..c780db6 100644
--- a/service/ServiceWifiResources/res/values-th/strings.xml
+++ b/service/ServiceWifiResources/res/values-th/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"เครือข่ายที่แนะนำโดย <xliff:g id="NAME">%s</xliff:g> และอุปกรณ์อาจเชื่อมต่อโดยอัตโนมัติ"</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"อนุญาต"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"ไม่เป็นไร"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"เชื่อมต่อกับ Wi-Fi <xliff:g id="CARRIERNAME">%s</xliff:g> ไหม"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"เครือข่ายเหล่านี้จะได้รับรหัส SIM ซึ่งใช้ติดตามตำแหน่งอุปกรณ์ได้"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"เชื่อมต่อ"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"ไม่เชื่อมต่อ"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"ยืนยันการเชื่อมต่อใช่ไหม"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"หากเชื่อมต่อ เครือข่าย Wi-Fi ของ <xliff:g id="CARRIERNAME">%s</xliff:g> อาจเข้าถึงหรือแชร์รหัสที่ไม่ซ้ำกันซึ่งเชื่อมโยงกับซิมของคุณ และอาจอนุญาตให้มีการติดตามตำแหน่งอุปกรณ์ของคุณ"</string>
diff --git a/service/ServiceWifiResources/res/values-tl/strings.xml b/service/ServiceWifiResources/res/values-tl/strings.xml
index 188fa2bb..7dfc668 100644
--- a/service/ServiceWifiResources/res/values-tl/strings.xml
+++ b/service/ServiceWifiResources/res/values-tl/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"Mga iminumungkahing network ng <xliff:g id="NAME">%s</xliff:g>. Posibleng awtomatikong kumonekta ang device."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Payagan"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Hindi, salamat na lang"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Kumonekta sa <xliff:g id="CARRIERNAME">%s</xliff:g> Wi‑Fi?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Tumatanggap ang mga network na ito ng SIM ID na magagamit sa pag-track sa lokasyon ng device"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Kumonekta"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Huwag kumonekta"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Kumpirmahin ang koneksyon?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Kung kokonekta ka, posibleng ma-access o maging kapareho ng mga Wi-Fi network ng <xliff:g id="CARRIERNAME">%s</xliff:g> ang natatanging ID na nauugnay sa iyong SIM. Baka mapahintulutan nito ang pagsubaybay sa lokasyon ng iyong device."</string>
diff --git a/service/ServiceWifiResources/res/values-tr/strings.xml b/service/ServiceWifiResources/res/values-tr/strings.xml
index 0d0a7e1..1d36896 100644
--- a/service/ServiceWifiResources/res/values-tr/strings.xml
+++ b/service/ServiceWifiResources/res/values-tr/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> tarafından önerilen ağlar. Cihaz otomatik olarak bağlanabilir."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"İzin ver"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Hayır, teşekkürler"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"<xliff:g id="CARRIERNAME">%s</xliff:g> kablosuz ağına bağlanılsın mı?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Bu ağlar, cihazın konumunu izlemek için kullanılabilecek bir SIM kimliği alır"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Bağlan"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Bağlanma"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Bağlantı onaylansın mı?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Bağlanırsanız <xliff:g id="CARRIERNAME">%s</xliff:g> kablosuz ağları SIM\'inizle ilişkilendirilmiş benzersiz kimliğe erişebilir veya bunları paylaşabilir. Bu, cihazınızın konumunun izlenmesine olanak sağlayabilir."</string>
diff --git a/service/ServiceWifiResources/res/values-uk/strings.xml b/service/ServiceWifiResources/res/values-uk/strings.xml
index 580ccd1..84f2970 100644
--- a/service/ServiceWifiResources/res/values-uk/strings.xml
+++ b/service/ServiceWifiResources/res/values-uk/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"Мережі, пропоновані додатком <xliff:g id="NAME">%s</xliff:g>. Пристрій може підключитися автоматично."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Дозволити"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Ні, дякую"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Підключитися до мережі Wi‑Fi <xliff:g id="CARRIERNAME">%s</xliff:g>?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Ці мережі отримують ідентифікатор SIM-карти, за допомогою якого можна відстежити місцезнаходження пристрою"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Підключитися"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Не підключатися"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Підтвердити підключення?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Якщо ви під\'єднаєтеся, мережі Wi‑Fi оператора \"<xliff:g id="CARRIERNAME">%s</xliff:g>\" зможуть розкрити унікальний ідентифікатор вашої SIM-карти й відстежити місцезнаходження вашого пристрою."</string>
diff --git a/service/ServiceWifiResources/res/values-ur/strings.xml b/service/ServiceWifiResources/res/values-ur/strings.xml
index 90cdd30..83481fa 100644
--- a/service/ServiceWifiResources/res/values-ur/strings.xml
+++ b/service/ServiceWifiResources/res/values-ur/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> تجویز کردہ نیٹ ورکس۔ آلہ خودکار طور پر منسلک ہو سکتا ہے۔"</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"اجازت ہیں"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"نہیں شکریہ"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"‏<xliff:g id="CARRIERNAME">%s</xliff:g> Wi-Fi سے منسلک ہوں؟"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"‏یہ نیٹ ورکس ایک منفرد SIM ID موصول کرتے ہیں جسے آلہ کا مقام ٹریک کرنے کے لیے استعمال کیا جا سکتا ہے"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"منسلک کریں"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"منسلک نہ کریں"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"کنکشن کی تصدیق کریں؟"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"‏اگر آب منسلک ہیں، Wi-Fi <xliff:g id="CARRIERNAME">%s</xliff:g> نیٹ ورکس آپ کے SIM سے وابستہ ایک منفرد ID تک رسائی حاصل یا اشتراک کر سکتے ہیں۔ یہ آپ کے آلہ کے مقام کو ٹریک کیے جانے کی اجازت دے سکتا ہے۔"</string>
diff --git a/service/ServiceWifiResources/res/values-uz/strings.xml b/service/ServiceWifiResources/res/values-uz/strings.xml
index 70da265..4a289db 100644
--- a/service/ServiceWifiResources/res/values-uz/strings.xml
+++ b/service/ServiceWifiResources/res/values-uz/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> tavsiya qilgan tarmoqlar. Qurilma avtomatik ulanishi mumkin."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Ruxsat"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Kerak emas"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"<xliff:g id="CARRIERNAME">%s</xliff:g> Wi-Fi tarmoqqa ulaning"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Bu tarmoqlar SIM identifikator oladi, bu orqali qurilma joylashuvini kuzatish mumkin"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Ulash"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Ulanmasin"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Ulanish tasdiqlansinmi?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Agar ulansangiz, <xliff:g id="CARRIERNAME">%s</xliff:g> Wi‑Fi tarmoqlari SIM kartangizga aloqador unikal identifikatordan foydalanishi yoki ulashishi mumkin. Bu qurilmangiz joylashuvi kuzatilishiga ruxsat berishi mumkin."</string>
diff --git a/service/ServiceWifiResources/res/values-vi/strings.xml b/service/ServiceWifiResources/res/values-vi/strings.xml
index 545b1b8..a90bd21 100644
--- a/service/ServiceWifiResources/res/values-vi/strings.xml
+++ b/service/ServiceWifiResources/res/values-vi/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"Các mạng do <xliff:g id="NAME">%s</xliff:g> đề xuất. Thiết bị có thể kết nối tự động."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Cho phép"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Không, cảm ơn"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Kết nối với Wi-Fi của <xliff:g id="CARRIERNAME">%s</xliff:g>?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Các mạng này nhận được một mã nhận dạng SIM có thể dùng để theo dõi vị trí của thiết bị"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Kết nối"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Không kết nối"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Xác nhận kết nối?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Nếu bạn kết nối, các mạng Wi-Fi của <xliff:g id="CARRIERNAME">%s</xliff:g> có thể truy cập hoặc chia sẻ mã nhận dạng duy nhất liên kết với SIM của bạn. Từ đó, các mạng này có thể theo dõi vị trí thiết bị của bạn."</string>
diff --git a/service/ServiceWifiResources/res/values-zh-rCN/strings.xml b/service/ServiceWifiResources/res/values-zh-rCN/strings.xml
index c972fbb..b0ab302 100644
--- a/service/ServiceWifiResources/res/values-zh-rCN/strings.xml
+++ b/service/ServiceWifiResources/res/values-zh-rCN/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g>建议的网络。设备可能会自动连接到这些网络。"</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"允许"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"不用了"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"要连接到<xliff:g id="CARRIERNAME">%s</xliff:g>的 WLAN 吗?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"这些网络会收到可用于跟踪设备位置的 SIM 卡 ID"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"连接"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"不连接"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"确认连接?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"如果您确认连接,<xliff:g id="CARRIERNAME">%s</xliff:g> WLAN 网络可获取或共享与您的 SIM 卡关联的唯一 ID。他人或许可以借此跟踪您的设备位置。"</string>
diff --git a/service/ServiceWifiResources/res/values-zh-rHK/strings.xml b/service/ServiceWifiResources/res/values-zh-rHK/strings.xml
index 5c81eea..4d81fc2 100644
--- a/service/ServiceWifiResources/res/values-zh-rHK/strings.xml
+++ b/service/ServiceWifiResources/res/values-zh-rHK/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"「<xliff:g id="NAME">%s</xliff:g>」已建議網絡連線,裝置可能會自動連接網絡。"</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"允許"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"不用了,謝謝"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"要連線至「<xliff:g id="CARRIERNAME">%s</xliff:g>」Wi-Fi 嗎?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"這些網絡會收到可追蹤裝置位置的 SIM 卡 ID"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"連接"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"不要連線"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"確定要連線嗎?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"連線後,「<xliff:g id="CARRIERNAME">%s</xliff:g>」的 Wi‑Fi 網絡就能存取或分享與 SIM 卡相關的獨有 ID,有心人或許可藉此追蹤您裝置的位置。"</string>
diff --git a/service/ServiceWifiResources/res/values-zh-rTW/strings.xml b/service/ServiceWifiResources/res/values-zh-rTW/strings.xml
index d0c69c5..5e3c474 100644
--- a/service/ServiceWifiResources/res/values-zh-rTW/strings.xml
+++ b/service/ServiceWifiResources/res/values-zh-rTW/strings.xml
@@ -31,12 +31,9 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"「<xliff:g id="NAME">%s</xliff:g>」建議的網路。裝置可能會自動連線到這些網路。"</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"允許"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"不用了,謝謝"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"要連線至「<xliff:g id="CARRIERNAME">%s</xliff:g>」的 Wi-Fi 網路嗎?"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"這些網路會收到可用於追蹤裝置位置的 SIM 卡 ID"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"連線"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"不要連線"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"確定要連線嗎?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"連線後,「<xliff:g id="CARRIERNAME">%s</xliff:g>」的 Wi‑Fi 網路可能會存取或分享與 SIM 卡相關的唯一 ID,有心人士或許可藉此追蹤你的裝置所在位置。"</string>
diff --git a/service/ServiceWifiResources/res/values-zu/strings.xml b/service/ServiceWifiResources/res/values-zu/strings.xml
index e244186..5e99219 100644
--- a/service/ServiceWifiResources/res/values-zu/strings.xml
+++ b/service/ServiceWifiResources/res/values-zu/strings.xml
@@ -31,18 +31,15 @@
     <string name="wifi_suggestion_content" msgid="6985149577828091835">"<xliff:g id="NAME">%s</xliff:g> amanethiwekhi aphakanyisiwe. Idivayisi ingaxhumeka ngokuzenzakalela."</string>
     <string name="wifi_suggestion_action_allow_app" msgid="7757859972144671588">"Vumela"</string>
     <string name="wifi_suggestion_action_disallow_app" msgid="4565857699629860726">"Cha ngiyabonga"</string>
-    <!-- no translation found for wifi_suggestion_imsi_privacy_title (2218937297875316823) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_imsi_privacy_content (600053954955350404) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_imsi_privacy_exemption_carrier (1953266435620254961) -->
-    <skip />
+    <string name="wifi_suggestion_imsi_privacy_title" msgid="8969261812845304079">"Xhuma ku-Wi-Fi ye-<xliff:g id="CARRIERNAME">%s</xliff:g>"</string>
+    <string name="wifi_suggestion_imsi_privacy_content" msgid="4266931269306079184">"Lama nethiwekhi athola i-SIM ID engasetshenziselwa ukulandelela indawo yedivayisi"</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier" msgid="3888538126440442636">"Xhuma"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier" msgid="3225397664735676024">"Ungaxhumi"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_title" msgid="4407415300707014525">"Qinisekisa ukuxhuma?"</string>
     <string name="wifi_suggestion_imsi_privacy_exemption_confirmation_content" msgid="9211241189147807136">"Uma uxhuma, amanethiwekhi we-Wi-Fi ye-<xliff:g id="CARRIERNAME">%s</xliff:g> angase akwazi ukufinyelela ku-ID yakho ehlukile ehlobene ne-SIM yakho. Lokhu kungase kuvumele ukuthi indawo yedivayisi yakho ilandelelwe."</string>
     <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_confirmation" msgid="2168947026413431603">"Xhuma"</string>
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_confirmation" msgid="5156881939985876066">"Ungaxhumi"</string>
-    <string name="wifi_wakeup_onboarding_title" msgid="3868826648004934540">"I-Wi-Fi izovuleka ngokuzenzakalela"</string>
+    <string name="wifi_wakeup_onboarding_title" msgid="3868826648004934540">"I-Wi-Fi izovuleka ngokuzenzekela"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="5705886295837387430">"Uma useduze kwenethiwekhi yekhwalithi ephezulu elondoloziwe"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="6209706680391785825">"Ungaphindi uvule"</string>
     <string name="wifi_wakeup_enabled_title" msgid="5043486751612595850">"I-Wi‑Fi ivuleke ngokuzenzakalela"</string>
diff --git a/service/ServiceWifiResources/res/values/config.xml b/service/ServiceWifiResources/res/values/config.xml
index 0264c3e..a35036e 100644
--- a/service/ServiceWifiResources/res/values/config.xml
+++ b/service/ServiceWifiResources/res/values/config.xml
@@ -21,6 +21,9 @@
      entries do not follow the convention, but all new entries should. -->
 
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- boolean indicating whether the WiFi chipset has 24GHz band support -->
+    <bool translatable="false" name ="config_wifi24ghzSupport">true</bool>
+
     <!-- boolean indicating whether the WiFi chipset has 5GHz band support.
          Note: This config is replacing the config_wifi_dual_band_support
          since more bands may now be supported (such as 6GHz), the naming dual_band
@@ -30,6 +33,9 @@
     <!-- boolean indicating whether the WiFi chipset has 6GHz band support -->
     <bool translatable="false" name ="config_wifi6ghzSupport">false</bool>
 
+    <!-- boolean indicating whether the WiFi chipset has 60GHz band support -->
+    <bool translatable="false" name ="config_wifi60ghzSupport">false</bool>
+
       <!-- Indicates that 11ax mode is supported on this device
            Note that if this flag is set to true, then 11ax is assumed to be supported.
            However, if it is left to the default value of false, the 11ax support will
@@ -133,6 +139,12 @@
          are no connected devices. -->
     <integer translatable="false" name="config_wifiFrameworkSoftApShutDownTimeoutMilliseconds">600000</integer>
 
+    <!-- Integer delay in milliseconds before shutting down idle soft AP instance.
+         This timer is the inactivity timer for transitioning a Dual AP to Single AP mode
+         by shutting down one of the APs that has been inactive/unused.
+         If both APs in Dual AP mode are idle, it shuts down the AP in higher band. -->
+    <integer translatable="false" name="config_wifiFrameworkSoftApShutDownIdleInstanceInBridgedModeTimeoutMillisecond">300000</integer>
+
     <!-- Integer indicating maximum hardware supported client number of soft ap -->
     <integer translatable="false" name="config_wifiHardwareSoftapMaxClientCount">16</integer>
 
@@ -166,6 +178,11 @@
          range string like '36-48,149'. -->
     <string  translatable="false" name="config_wifiSoftap6gChannelList"></string>
 
+    <!-- List of allowed channels in 60GHz band for softap. If the device doesn't want to restrict
+         channels this should be empty. Values is a comma separated channel string and/or channel
+         range string like '1-2,4'. -->
+    <string  translatable="false" name="config_wifiSoftap60gChannelList"></string>
+
     <!-- Integer indicating associated full scan max num active channels -->
     <integer translatable="false" name="config_wifi_framework_associated_partial_scan_max_num_active_channels">6</integer>
 
@@ -178,6 +195,14 @@
     <!-- Integer duration after connection that a user-selected network is considered sufficient (milliseconds) -->
     <integer translatable="false" name="config_wifiSufficientDurationAfterUserSelectionMilliseconds">60000</integer>
 
+    <!-- Boolean indicating whether the wifi module should always scan the 6Ghz Preferred Scanning
+         Channels when performing full connectivity scans.
+         If set to true, the wifi module will scan 6Ghz PSC channels in addition to the 2.4Ghz,
+         5Ghz, and 6Ghz channels co-located with 2.4/5Ghz APs when doing full connectivity scans.
+         If set to false, the wifi module will only scan the 2.4Ghz, 5Ghz,
+         and 6Ghz channels co-located with 2.4/5Ghz APs when doing full connectivity scans.-->
+    <bool translatable="false" name="config_wifiEnable6ghzPscScanning">true</bool>
+
     <!-- Boolean indicating performing a partial initial scan is enabled -->
     <bool translatable="false" name="config_wifiEnablePartialInitialScan">false</bool>
 
@@ -191,7 +216,13 @@
     <!-- Boolean indicating whether single radio chain scan results are to be used for network selection -->
     <bool translatable="false" name="config_wifi_framework_use_single_radio_chain_scan_results_network_selection">true</bool>
 
-    <!-- Boolean indicating that wifi only link configuratios that have exact same credentials (i.e PSK) -->
+    <!-- Boolean indicating that wifi may link networks whose gateways have not yet been determined -->
+    <bool translatable="false" name="config_wifiAllowLinkingUnknownDefaultGatewayConfigurations">true</bool>
+
+    <!-- Boolean indicating that enable roaming between linked networks -->
+    <bool translatable="false" name="config_wifiEnableLinkedNetworkRoaming">false</bool>
+
+    <!-- Boolean indicating that only configurations that have the same pre-shared key will be linked -->
     <bool translatable="false" name="config_wifi_only_link_same_credential_configurations">true</bool>
 
     <!-- Boolean indicating whether framework needs to set the tx power limit for meeting SAR requirements -->
@@ -201,6 +232,9 @@
          power limit for meeting SAR requirements -->
     <bool translatable="false" name="config_wifi_framework_enable_soft_ap_sar_tx_power_limit">false</bool>
 
+    <!-- Boolean indicating that softap passphrase need to enable ASCII encodable check -->
+    <bool translatable="false" name="config_wifiSoftapPassphraseAsciiEncodableCheck">true</bool>
+
     <!-- Wifi Hal supports force client disconnect for softap -->
     <bool translatable="false" name="config_wifiSofapClientForceDisconnectSupported">true</bool>
 
@@ -210,6 +244,9 @@
     <!-- Wifi driver supports WPA3 Simultaneous Authentication of Equals (WPA3-SAE) for softap -->
     <bool translatable="false" name="config_wifi_softap_sae_supported">false</bool>
 
+    <!-- Wifi driver supports Mac address customization for softap -->
+    <bool translatable="false" name="config_wifiSoftapMacAddressCustomizationSupported">true</bool>
+
     <!-- Wifi driver supports IEEE80211AC for softap -->
     <bool translatable="false" name="config_wifi_softap_ieee80211ac_supported">false</bool>
 
@@ -228,9 +265,18 @@
     <!-- Wifi driver supports IEEE80211AX TWT (Target Wake Time) for softap -->
     <bool translatable="false" name="config_wifiSoftapHeTwtSupported">false</bool>
 
-    <!-- Wifi driver supports 6GHz band for softap -->
+    <!-- Wifi driver supports 2.4GHz band for softap when chip support 24GHz -->
+    <bool translatable="false" name="config_wifiSoftap24ghzSupported">true</bool>
+
+    <!-- Wifi driver supports 5GHz band for softap when chip support 5GHz -->
+    <bool translatable="false" name="config_wifiSoftap5ghzSupported">true</bool>
+
+    <!-- Wifi driver supports 6GHz band for softap when chip support 6GHz -->
     <bool translatable="false" name="config_wifiSoftap6ghzSupported">false</bool>
 
+    <!-- Wifi driver supports 60GHz band for softap when chip support 60GHz -->
+    <bool translatable="false" name="config_wifiSoftap60ghzSupported">false</bool>
+
     <!-- Indicates that local-only hotspot should be brought up at 6GHz if possible.
          This option is for automotive builds only (the one that have
          PackageManager#FEATURE_AUTOMOTIVE) -->
@@ -244,12 +290,25 @@
     <!-- Indicates that connected MAC randomization is supported on this device -->
     <bool translatable="false" name="config_wifi_connected_mac_randomization_supported">false</bool>
 
+    <!-- Indicates that enhanced MAC randomization is allowed on open networks that do not
+     use captive portals -->
+    <bool translatable="false" name="config_wifiAllowEnhancedMacRandomizationOnOpenSsids">false</bool>
+
     <!-- Indicates that p2p MAC randomization is supported on this device -->
     <bool translatable="false" name="config_wifi_p2p_mac_randomization_supported">false</bool>
 
     <!-- Indicates that AP mode MAC randomization is supported on this device -->
     <bool translatable="false" name="config_wifi_ap_mac_randomization_supported">true</bool>
 
+    <!-- Indicates that bridged AP mode is supported on this device -->
+    <bool translatable="false" name="config_wifiBridgedSoftApSupported">false</bool>
+
+    <!-- Indicates that STA + bridged AP concurrency mode is supported on this device -->
+    <bool translatable="false" name="config_wifiStaWithBridgedSoftApConcurrencySupported">false</bool>
+
+    <!-- Indicates that dynamic country code update in AP mode is supported on this device -->
+    <bool translatable="false" name="config_wifiSoftApDynamicCountryCodeUpdateSupported">false</bool>
+
     <!-- list of SSIDs to enable aggressive MAC randomization on -->
     <string-array translatable="false" name="config_wifi_aggressive_randomization_ssid_allowlist">
         <!-- SSIDs are expected in quoted format:
@@ -267,6 +326,22 @@
         -->
     </string-array>
 
+    <!-- Duration in minutes a recent failure should be displayed in the wifi picker UI. -->
+    <integer translatable="false" name="config_wifiRecentFailureReasonExpirationMinutes">30</integer>
+
+    <!-- The minimum duration in minutes that all non-carrier-merged wifi becomes disabled when
+    WifiManager#startRestrictingAutoJoinToSubscriptionId is called. -->
+    <integer translatable="false"
+             name="config_wifiAllNonCarrierMergedWifiMinDisableDurationMinutes">30</integer>
+
+    <!-- Configures 2 things:
+    1. The maximum duration in minutes that all non-carrier-merged wifi becomes disabled when
+    WifiManager#startRestrictingAutoJoinToSubscriptionId is called.
+    2. The maximum duration in minutes that a network is disabled when the user manually
+    triggers the "disconnect" feature.-->
+    <integer translatable="false"
+             name="config_wifiAllNonCarrierMergedWifiMaxDisableDurationMinutes">480</integer>
+
     <!-- Indicates that wifi link probing is supported on this device -->
     <bool translatable="false" name="config_wifi_link_probing_supported">false</bool>
 
@@ -285,6 +360,24 @@
     <!-- Indicates that wifi watchdog is enabled on this device -->
     <bool translatable="false" name="config_wifi_watchdog_enabled">true</bool>
 
+    <!-- list of package names for which WifiManager.startScan() will not be throttled when the app
+    is in foreground. -->
+    <string-array translatable="false" name="config_wifiForegroundScanThrottleExceptionList">
+        <!-- Below is a sample configuration for this list:
+        <item>com.company1.example.test.name1</item>
+        <item>com.company2.example.test.name2</item>
+        -->
+    </string-array>
+
+    <!-- list of package names for which WifiManager.startScan() will not be throttled when the app
+    is in background. -->
+    <string-array translatable="false" name="config_wifiBackgroundScanThrottleExceptionList">
+        <!-- Below is a sample configuration for this list:
+        <item>com.company1.example.test.name1</item>
+        <item>com.company2.example.test.name2</item>
+        -->
+    </string-array>
+
     <!--
     Controls the mapping between RSSI and RSSI levels.
 
@@ -326,6 +419,14 @@
         <item>160</item>
     </integer-array>
 
+    <!-- Integer specifying minimum wait time in seconds for next PNO scan when a network is found
+    by PNO scan but gets rejected by Wifi Network Selector due to its low RSSI value-->
+    <integer translatable="false" name="config_wifiPnoScanLowRssiNetworkRetryStartDelaySec"> 20 </integer>
+
+    <!-- Integer specifying maximum wait time in seconds for next PNO scan when a network is found
+    by PNO scan but gets rejected by Wifi Network Selector due to its low RSSI value-->
+    <integer translatable="false" name="config_wifiPnoScanLowRssiNetworkRetryMaxDelaySec"> 80 </integer>
+
     <!-- Integer for minimum time between the last network selection and next high RSSI scan
          in seconds when device is connected and screen is on -->
     <integer translatable="false" name="config_wifiConnectedHighRssiScanMinimumWindowSizeSec"> 600 </integer>
@@ -337,6 +438,13 @@
     <integer-array translatable="false" name="config_wifiSingleSavedNetworkConnectedScanIntervalScheduleSec">
     </integer-array>
 
+    <!-- List of constants to indicate how many failures are needed to temporarily disable a network
+    from auto-connect -->
+    <integer translatable="false" name="config_wifiDisableReasonAssociationRejectionThreshold"> 3 </integer>
+    <integer translatable="false" name="config_wifiDisableReasonAuthenticationFailureThreshold"> 3 </integer>
+    <integer translatable="false" name="config_wifiDisableReasonDhcpFailureThreshold"> 2 </integer>
+    <integer translatable="false" name="config_wifiDisableReasonNetworkNotFoundThreshold"> 2 </integer>
+
     <!-- List of constants that indicate the number of consecutive failures per type needed to block a BSSID.
     A blocked BSSID will not be considered in network selection and firmware roaming.-->
     <integer translatable="false" name="config_wifiBssidBlocklistMonitorApUnableToHandleNewStaThreshold"> 1 </integer>
@@ -346,8 +454,9 @@
     <integer translatable="false" name="config_wifiBssidBlocklistMonitorAssociationRejectionThreshold"> 3 </integer>
     <integer translatable="false" name="config_wifiBssidBlocklistMonitorAssociationTimeoutThreshold"> 3 </integer>
     <integer translatable="false" name="config_wifiBssidBlocklistMonitorAuthenticationFailureThreshold"> 3 </integer>
-    <integer translatable="false" name="config_wifiBssidBlocklistMonitorDhcpFailureThreshold"> 3 </integer>
+    <integer translatable="false" name="config_wifiBssidBlocklistMonitorDhcpFailureThreshold"> 2 </integer>
     <integer translatable="false" name="config_wifiBssidBlocklistMonitorAbnormalDisconnectThreshold"> 3 </integer>
+    <integer translatable="false" name="config_wifiBssidBlocklistMonitorNonlocalDisconnectConnectingThreshold"> 2 </integer>
 
     <!-- Base duration to block a BSSID after consecutive failures happen. (default = 5 mins)
     The blocklist duration is increased exponentially for a BSSID that consecutively gets added to the blocklist.
@@ -399,12 +508,19 @@
     <!-- Maximum number of SSIDs that can be PNO scanned concurrently-->
     <integer translatable="false" name="config_wifiMaxPnoSsidCount">16</integer>
 
+    <!-- When disconnected and PNO scan is enabled, wake up to do a single scan every
+    config_wifiPnoWatchdogIntervalMs in case PNO scan failed. (default = 20 minutes)-->
+    <integer translatable="false" name="config_wifiPnoWatchdogIntervalMs">1200000</integer>
+
     <!-- Suspend optimization. -->
     <bool translatable="false" name="config_wifiSuspendOptimizationsEnabled">true</bool>
 
     <!-- Network selection optimization at DEVICE_MOBILITY_STATE_HIGH_MVMT -->
     <bool translatable="false" name="config_wifiHighMovementNetworkSelectionOptimizationEnabled">true</bool>
 
+    <!-- Do a single scan when cell data loss is detected. -->
+    <bool translatable="false" name="config_wifiScanOnCellularDataLossEnabled">false</bool>
+
     <!-- Duration for the delayed scan used to verify access points are staying relatively stationary
     to the device at high mobility state. (default = 10 seconds) -->
     <integer translatable="false" name="config_wifiHighMovementNetworkSelectionOptimizationScanDelayMs">10000</integer>
@@ -414,6 +530,10 @@
     filtered out from network selection. (default = 10 dBs) -->
     <integer translatable="false" name="config_wifiHighMovementNetworkSelectionOptimizationRssiDelta">10</integer>
 
+    <!-- The estimate RSSI error margin in dBs to account minor differences in the environment and
+    the device's orientation. -->
+    <integer translatable="false" name="config_wifiEstimateRssiErrorMarginDb">5</integer>
+
     <!-- The interval in milliseconds at which wifi rtt ranging requests will be throttled when
          they are coming from the background apps (default = 30 mins). -->
     <integer translatable="false" name="config_wifiRttBackgroundExecGapMs">1800000</integer>
@@ -436,6 +556,9 @@
     <!-- Enable WPA2 to WPA3 auto-upgrade offload to capable Driver/Firmware -->
     <bool translatable="false" name="config_wifiSaeUpgradeOffloadEnabled">false</bool>
 
+    <!-- Enable Open to OWE auto-upgrade -->
+    <bool translatable="false" name="config_wifiOweUpgradeEnabled">true</bool>
+
     <!-- Number of self recoveries to be attempted per hour. Any fatal errors beyond this will
          cause the wifi stack to turn wifi off and wait for user input.
          Set to 0 to turn off recovery attempts and always turn off wifi on failures -->
@@ -464,4 +587,83 @@
 
     <!-- Enable adding minimum confirmation duration when sending network score to connectivity service. -->
     <bool translatable="false" name="config_wifiMinConfirmationDurationSendNetworkScoreEnabled">false</bool>
+
+    <!-- Enable Make-Before-Break Wifi network switching.
+         Note: this is conditional on the device supporting dual concurrent STAs. -->
+    <bool translatable="false" name="config_wifiMultiStaNetworkSwitchingMakeBeforeBreakEnabled">false</bool>
+    <!-- Enable concurrent peer to peer + internet connectivity
+         Note: this is conditional on the device supporting dual concurrent STAs. -->
+    <bool translatable="false" name="config_wifiMultiStaLocalOnlyConcurrencyEnabled">false</bool>
+    <!-- Enable concurrent restricted connectivity + internet connectivity
+         Note: this is conditional on the device supporting dual concurrent STAs. -->
+    <bool translatable="false" name="config_wifiMultiStaRestrictedConcurrencyEnabled">false</bool>
+    <!-- Enable the default coex channel avoidance algorithm and disable the functionality of
+         WifiManager#setCoexUnsafeChannels. -->
+    <bool translatable="false" name="config_wifiDefaultCoexAlgorithmEnabled">false</bool>
+    <!-- Filepath of the xml table of parameters used by Wifi coex channel avoidance. -->
+    <string translatable="false" name="config_wifiCoexTableFilepath">/vendor/etc/wifi/coex_table.xml</string>
+    <!-- A value to indicate how many failures are needed to temporarily or permanently
+    (depends on config_wifiDisableReasonAuthenticationFailureCarrierSpecificDurationMs)
+    disable a network from auto-connect based on Carrier requirement -->
+    <integer translatable="false" name="config_wifiDisableReasonAuthenticationFailureCarrierSpecificThreshold">1</integer>
+    <!-- A value to indicate the duration (in ms) to disable a network from auto-connect based on
+    Carrier requirement. -1 represents disabling a network permanently -->
+    <integer translatable="false" name="config_wifiDisableReasonAuthenticationFailureCarrierSpecificDurationMs">-1</integer>
+    <!-- Flush ANQP cache on Wi-Fi toggle off event -->
+    <bool translatable="false" name="config_wifiFlushAnqpCacheOnWifiToggleOffEvent">true</bool>
+
+    <!-- Enable Aware NDP interface selection on interface that already has network set up.
+     Note: the default AOSP Android does not support multiple networks on the same Aware NDI.
+     Enabling this configuration will restore legacy behavior but may result in incorrect behavior.
+     -->
+    <bool translatable="false" name="config_wifiAllowMultipleNetworksOnSameAwareNdi">false</bool>
+
+    <!-- Integer threshold for minimum packets required to notify clients of data activity -->
+    <integer translatable="false" name="config_wifiTrafficPollerTxPacketThreshold">0</integer>
+    <integer translatable="false" name="config_wifiTrafficPollerRxPacketThreshold">0</integer>
+
+    <!-- A value to decide when NetworkCapabilities are updated to reflect the latest link
+    bandwidth. If the bandwidth change is above this value, NetworkCapabilities are updated -->
+    <integer translatable="false" name="config_wifiLinkBandwidthUpdateThresholdPercent">15</integer>
+
+    <!-- Whether we should apply APF filters on non primary STA connections when STA + STA is active.
+         Defaults to false since most wifi chips cannot support concurrent APF filter set needed for STA + STA-->
+    <bool translatable="false" name="config_wifiEnableApfOnNonPrimarySta">false</bool>
+
+    <!-- Whether to use the explicit vendor HAL API: IWifiStaIface.setRoamingState for disabling fw roaming (only needed if
+         setting the bssid on the connection alone does not disable fw roaming on this chip) -->
+    <bool translatable="false" name="config_wifiUseHalApiToDisableFwRoaming">false</bool>
+
+    <!-- Indicates that SAE Hash-to-Element is supported on this device -->
+    <bool translatable="false" name="config_wifiSaeH2eSupported">false</bool>
+
+    <!-- Enable aggregation of Wifi link layer radio stats from all radios.
+         Defaults to false will fetch radio stats only from Radio 0. This is to avoid
+         incorrect behavior due to driver/firmware returning bogus radio stats from other radios.
+         Note: Full DBS capable devices interested in radio stats from all the radios can enable this
+         configuration. -->
+    <bool translatable="false" name="config_wifiLinkLayerAllRadiosStatsAggregationEnabled">false</bool>
+
+    <!-- Indicate the prefix of wifi p2p device name, the length should be between 1 ~ 28. -->
+    <string translatable="false" name="config_wifiP2pDeviceNamePrefix">Android_</string>
+    <!-- Indicate how many digits the postfix are. If the value is negative or smaller than 4,
+         the postfix will fallback to the first digit of ANDROID_ID. -->
+    <integer translatable="false" name="config_wifiP2pDeviceNamePostfixNumDigits">-1</integer>
+
+    <!-- Integer threshold for max number of WifiConfigurations that can be saved on the device.
+         A value of -1 indicates no limit. If a max number is specified, then under-used configs will
+         be deleted to make room for new configs.-->
+    <integer translatable="false" name="config_wifiMaxNumWifiConfigurations">-1</integer>
+
+    <!-- Whether to allow Settings or SUW to create insecure Enterprise networks where server
+         certificate is not validated, by not specifying a Root CA certificate and/or server domain
+         name. It is STRONGLY RECOMMENDED to be set to false -->
+    <bool translatable="false" name="config_wifiAllowInsecureEnterpriseConfigurationsForSettingsAndSUW">false</bool>
+
+    <!-- Indicate the max lines for connectivity local log based on the device ram size -->
+    <integer translatable="false" name="config_wifiConnectivityLocalLogMaxLinesLowRam">256</integer>
+    <integer translatable="false" name="config_wifiConnectivityLocalLogMaxLinesHighRam">512</integer>
+
+    <!-- Indicate max number of log records for WifiClientModeImpl -->
+    <integer translatable="false" name="config_wifiClientModeImplNumLogRecs">100</integer>
 </resources>
diff --git a/service/ServiceWifiResources/res/values/overlayable.xml b/service/ServiceWifiResources/res/values/overlayable.xml
index 60764ba..524bbb7 100644
--- a/service/ServiceWifiResources/res/values/overlayable.xml
+++ b/service/ServiceWifiResources/res/values/overlayable.xml
@@ -20,8 +20,10 @@
         <policy type="product|system|vendor">
 
           <!-- Params from config.xml that can be overlayed -->
+          <item type="bool" name="config_wifi24ghzSupport" />
           <item type="bool" name="config_wifi5ghzSupport" />
           <item type="bool" name="config_wifi6ghzSupport" />
+          <item type="bool" name="config_wifi60ghzSupport" />
           <item type="bool" name="config_wifi11axSupportOverride" />
           <item type="bool" name="config_wifiFrameworkMaxNumSpatialStreamDeviceOverrideEnable" />
           <item type="integer" name="config_wifiFrameworkMaxNumSpatialStreamDeviceOverrideValue" />
@@ -57,6 +59,8 @@
           <item type="integer" name="config_wifiFrameworkScoreLowRssiThreshold6ghz" />
           <item type="integer" name="config_wifiFrameworkScoreGoodRssiThreshold6ghz" />
           <item type="integer" name="config_wifiFrameworkSoftApShutDownTimeoutMilliseconds" />
+          <item type="integer" name="config_wifiFrameworkSoftApShutDownIdleInstanceInBridgedModeTimeoutMillisecond" />
+          <item type="bool" name="config_wifiSoftApDynamicCountryCodeUpdateSupported" />
           <item type="bool" name="config_wifiSoftapResetChannelConfig" />
           <item type="bool" name="config_wifiSoftapResetHiddenConfig" />
           <item type="bool" name="config_wifiSoftapResetUserControlConfig" />
@@ -65,23 +69,32 @@
           <item type="string"  name="config_wifiSoftap2gChannelList" />
           <item type="string"  name="config_wifiSoftap5gChannelList" />
           <item type="string"  name="config_wifiSoftap6gChannelList" />
+          <item type="string"  name="config_wifiSoftap60gChannelList" />
           <item type="integer" name="config_wifi_framework_associated_partial_scan_max_num_active_channels" />
           <item type="integer" name="config_wifi_framework_recovery_timeout_delay" />
           <item type="bool" name="config_wifi_framework_enable_associated_network_selection" />
           <item type="integer" name="config_wifiSufficientDurationAfterUserSelectionMilliseconds" />
+          <item type="bool" name="config_wifiEnable6ghzPscScanning" />
           <item type="bool" name="config_wifiEnablePartialInitialScan" />
           <item type="integer" name="config_wifiInitialPartialScanChannelMaxCount" />
           <item type="integer" name="config_wifiInitialPartialScanChannelCacheAgeMins" />
           <item type="bool" name="config_wifi_framework_use_single_radio_chain_scan_results_network_selection" />
+          <item type="bool" name="config_wifiAllowLinkingUnknownDefaultGatewayConfigurations" />
+          <item type="bool" name="config_wifiEnableLinkedNetworkRoaming" />
           <item type="bool" name="config_wifi_only_link_same_credential_configurations" />
           <item type="bool" name="config_wifi_framework_enable_sar_tx_power_limit" />
           <item type="bool" name="config_wifi_framework_enable_soft_ap_sar_tx_power_limit" />
+          <item type="bool" name="config_wifiSoftapPassphraseAsciiEncodableCheck" />
           <item type="bool" name="config_wifiSofapClientForceDisconnectSupported" />
           <item type="bool" name="config_wifi_softap_acs_supported" />
           <item type="bool" name="config_wifi_softap_sae_supported" />
+          <item type="bool" name="config_wifiSoftapMacAddressCustomizationSupported" />
           <item type="bool" name="config_wifi_softap_ieee80211ac_supported" />
           <item type="bool" name="config_wifiSoftapIeee80211axSupported" />
+          <item type="bool" name="config_wifiSoftap24ghzSupported" />
+          <item type="bool" name="config_wifiSoftap5ghzSupported" />
           <item type="bool" name="config_wifiSoftap6ghzSupported" />
+          <item type="bool" name="config_wifiSoftap60ghzSupported" />
           <item type="bool" name="config_wifiSoftapHeSuBeamformerSupported" />
           <item type="bool" name="config_wifiSoftapHeSuBeamformeeSupported" />
           <item type="bool" name="config_wifiSoftapHeMuBeamformerSupported" />
@@ -89,21 +102,35 @@
           <item type="bool" name="config_wifiLocalOnlyHotspot6ghz" />
           <item type="bool" name="config_wifi_local_only_hotspot_5ghz" />
           <item type="bool" name="config_wifi_connected_mac_randomization_supported" />
+          <item type="bool" name="config_wifiAllowEnhancedMacRandomizationOnOpenSsids" />
           <item type="bool" name="config_wifi_p2p_mac_randomization_supported" />
           <item type="bool" name="config_wifi_ap_mac_randomization_supported" />
+          <item type="bool" name="config_wifiBridgedSoftApSupported" />
+          <item type="bool" name="config_wifiStaWithBridgedSoftApConcurrencySupported" />
           <item type="array" name="config_wifi_aggressive_randomization_ssid_allowlist" />
           <item type="array" name="config_wifi_aggressive_randomization_ssid_blocklist" />
+          <item type="integer" name="config_wifiRecentFailureReasonExpirationMinutes" />
+          <item type="integer" name="config_wifiAllNonCarrierMergedWifiMinDisableDurationMinutes" />
+          <item type="integer" name="config_wifiAllNonCarrierMergedWifiMaxDisableDurationMinutes" />
           <item type="bool" name="config_wifi_link_probing_supported" />
           <item type="string" name="config_wifi_tcp_buffers" />
           <item type="string" name="wifi_tether_configure_ssid_default" />
           <item type="string" name="wifi_localhotspot_configure_ssid_default" />
           <item type="bool" name="config_wifi_diagnostics_bugreport_enabled" />
           <item type="bool" name="config_wifi_watchdog_enabled" />
+          <item type="array" name="config_wifiForegroundScanThrottleExceptionList" />
+          <item type="array" name="config_wifiBackgroundScanThrottleExceptionList" />
           <item type="array" name="config_wifiRssiLevelThresholds" />
           <item type="array" name="config_wifiDisconnectedScanIntervalScheduleSec" />
           <item type="array" name="config_wifiConnectedScanIntervalScheduleSec" />
           <item type="array" name="config_wifiSingleSavedNetworkConnectedScanIntervalScheduleSec" />
+          <item type="integer" name="config_wifiPnoScanLowRssiNetworkRetryStartDelaySec" />
+          <item type="integer" name="config_wifiPnoScanLowRssiNetworkRetryMaxDelaySec" />
           <item type="integer" name="config_wifiConnectedHighRssiScanMinimumWindowSizeSec" />
+          <item type="integer" name="config_wifiDisableReasonAssociationRejectionThreshold" />
+          <item type="integer" name="config_wifiDisableReasonAuthenticationFailureThreshold" />
+          <item type="integer" name="config_wifiDisableReasonDhcpFailureThreshold" />
+          <item type="integer" name="config_wifiDisableReasonNetworkNotFoundThreshold" />
           <item type="integer" name="config_wifiBssidBlocklistMonitorApUnableToHandleNewStaThreshold" />
           <item type="integer" name="config_wifiBssidBlocklistMonitorNetworkValidationFailureThreshold" />
           <item type="integer" name="config_wifiBssidBlocklistMonitorWrongPasswordThreshold" />
@@ -113,6 +140,7 @@
           <item type="integer" name="config_wifiBssidBlocklistMonitorAuthenticationFailureThreshold" />
           <item type="integer" name="config_wifiBssidBlocklistMonitorDhcpFailureThreshold" />
           <item type="integer" name="config_wifiBssidBlocklistMonitorAbnormalDisconnectThreshold" />
+          <item type="integer" name="config_wifiBssidBlocklistMonitorNonlocalDisconnectConnectingThreshold" />
           <item type="integer" name="config_wifiBssidBlocklistMonitorBaseBlockDurationMs" />
           <item type="integer" name="config_wifiBssidBlocklistMonitorConnectedScoreBaseBlockDurationMs" />
           <item type="integer" name="config_wifiBssidBlocklistMonitorFailureStreakCap" />
@@ -126,10 +154,13 @@
           <item type="bool" name="config_wifiPnoFrequencyCullingEnabled" />
           <item type="bool" name="config_wifiPnoRecencySortingEnabled" />
           <item type="integer" name="config_wifiMaxPnoSsidCount" />
+          <item type="integer" name="config_wifiPnoWatchdogIntervalMs" />
           <item type="bool" name="config_wifiSuspendOptimizationsEnabled" />
           <item type="bool" name="config_wifiHighMovementNetworkSelectionOptimizationEnabled" />
+          <item type="bool" name="config_wifiScanOnCellularDataLossEnabled" />
           <item type="integer" name="config_wifiHighMovementNetworkSelectionOptimizationScanDelayMs" />
           <item type="integer" name="config_wifiHighMovementNetworkSelectionOptimizationRssiDelta" />
+          <item type="integer" name="config_wifiEstimateRssiErrorMarginDb" />
           <item type="integer" name="config_wifiRttBackgroundExecGapMs" />
           <item type="integer" name="config_wifiPollRssiIntervalMilliseconds" />
           <item type="bool" name="config_wifiChannelUtilizationOverrideEnabled" />
@@ -138,6 +169,7 @@
           <item type="integer" name="config_wifiChannelUtilizationOverride6g" />
           <item type="bool" name="config_wifiSaeUpgradeEnabled" />
           <item type="bool" name="config_wifiSaeUpgradeOffloadEnabled" />
+          <item type="bool" name="config_wifiOweUpgradeEnabled" />
           <item type="integer" name="config_wifiMaxNativeFailureSelfRecoveryPerHour" />
           <item type="bool" name="config_wifiIgnoreOpenSavedNetworkWhenSecureSuggestionAvailable" />
           <item type="bool" name="config_wifiSoftapAcsIncludeDfs" />
@@ -145,6 +177,29 @@
           <item type="integer" name="config_wifiStationaryPnoScanIntervalMillis" />
           <item type="integer" name="config_wifiDelayDisconnectOnImsLostMs" />
           <item type="bool" name="config_wifiMinConfirmationDurationSendNetworkScoreEnabled" />
+          <item type="bool" name="config_wifiMultiStaNetworkSwitchingMakeBeforeBreakEnabled" />
+          <item type="bool" name="config_wifiMultiStaLocalOnlyConcurrencyEnabled" />
+          <item type="bool" name="config_wifiMultiStaRestrictedConcurrencyEnabled" />
+          <item type="bool" name="config_wifiDefaultCoexAlgorithmEnabled"/>
+          <item type="string" name="config_wifiCoexTableFilepath"/>
+          <item type="integer" name="config_wifiDisableReasonAuthenticationFailureCarrierSpecificThreshold" />
+          <item type="integer" name="config_wifiDisableReasonAuthenticationFailureCarrierSpecificDurationMs" />
+          <item type="bool" name="config_wifiAllowMultipleNetworksOnSameAwareNdi"/>
+          <item type="integer" name="config_wifiTrafficPollerTxPacketThreshold"/>
+          <item type="integer" name="config_wifiTrafficPollerRxPacketThreshold"/>
+          <item type="integer" name="config_wifiLinkBandwidthUpdateThresholdPercent" />
+          <item type="bool" name="config_wifiFlushAnqpCacheOnWifiToggleOffEvent" />
+          <item type="bool" name="config_wifiEnableApfOnNonPrimarySta" />
+          <item type="bool" name="config_wifiUseHalApiToDisableFwRoaming" />
+          <item type="bool" name="config_wifiSaeH2eSupported" />
+          <item type="bool" name="config_wifiLinkLayerAllRadiosStatsAggregationEnabled" />
+          <item type="string" name="config_wifiP2pDeviceNamePrefix"/>
+          <item type="integer" name="config_wifiP2pDeviceNamePostfixNumDigits" />
+          <item type="integer" name="config_wifiMaxNumWifiConfigurations" />
+          <item type="bool" name="config_wifiAllowInsecureEnterpriseConfigurationsForSettingsAndSUW" />
+          <item type="integer" name="config_wifiConnectivityLocalLogMaxLinesLowRam" />
+          <item type="integer" name="config_wifiConnectivityLocalLogMaxLinesHighRam" />
+          <item type="integer" name="config_wifiClientModeImplNumLogRecs" />
           <!-- Params from config.xml that can be overlayed -->
 
           <!-- Params from strings.xml that can be overlayed -->
diff --git a/service/ServiceWifiResources/res/values/strings.xml b/service/ServiceWifiResources/res/values/strings.xml
index 441ecca..ae3f197 100644
--- a/service/ServiceWifiResources/res/values/strings.xml
+++ b/service/ServiceWifiResources/res/values/strings.xml
@@ -49,11 +49,11 @@
     <string name="wifi_suggestion_action_disallow_app">No thanks</string>
 
     <!-- Notification title for a connection to a SIM-based carrier network without IMSI privacy protection. -->
-    <string name="wifi_suggestion_imsi_privacy_title"><xliff:g id="carrierName" example="xxxMobile">%s</xliff:g> wants to auto\u2011connect</string>
+    <string name="wifi_suggestion_imsi_privacy_title">Connect to <xliff:g id="carrierName" example="xxxMobile">%s</xliff:g> Wi\u2011Fi?</string>
     <!-- Notification content for a connection to a SIM-based carrier network without IMSI privacy protection.-->
-    <string name="wifi_suggestion_imsi_privacy_content">These networks receive a unique ID that can be used to track device location</string>
+    <string name="wifi_suggestion_imsi_privacy_content">These networks receive a SIM ID that can be used to track device location</string>
     <!-- Notification action for allowing carrier specified in the notification body.-->
-    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier">Connect anyway</string>
+    <string name="wifi_suggestion_action_allow_imsi_privacy_exemption_carrier">Connect</string>
     <!-- Notification action for disallowing carrier specified in the notification body.-->
     <string name="wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier">Don\u0027t connect</string>
     <!-- Confirmation dialog title for a connection to a SIM-based carrier network without IMSI privacy protection. -->
diff --git a/service/TEST_MAPPING b/service/TEST_MAPPING
index fe5ba48..3d27184 100644
--- a/service/TEST_MAPPING
+++ b/service/TEST_MAPPING
@@ -1,8 +1,10 @@
 {
-  "presubmit": [
+  "presubmit-large": [
     {
       "name": "FrameworksWifiTests"
-    },
+    }
+  ],
+  "presubmit": [
     {
       "name": "CtsBackupHostTestCases",
       "options": [
@@ -11,5 +13,10 @@
         }
       ]
     }
+  ],
+  "mainline-presubmit": [
+    {
+      "name": "FrameworksWifiTests[com.google.android.wifi.apex]"
+    }
   ]
 }
diff --git a/service/coex-table-parser/Android.bp b/service/coex-table-parser/Android.bp
new file mode 100644
index 0000000..4b9d24e
--- /dev/null
+++ b/service/coex-table-parser/Android.bp
@@ -0,0 +1,24 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+xsd_config {
+    name: "coex-table-parser",
+    srcs: ["coex_table.xsd"],
+    package_name: "com.android.server.wifi.coex",
+}
diff --git a/service/coex-table-parser/api/current.txt b/service/coex-table-parser/api/current.txt
new file mode 100644
index 0000000..0b0d787
--- /dev/null
+++ b/service/coex-table-parser/api/current.txt
@@ -0,0 +1,128 @@
+// Signature format: 2.0
+package com.android.server.wifi.coex {
+
+  public class DefaultChannels {
+    ctor public DefaultChannels();
+    method public int getDefault2g();
+    method public int getDefault5g();
+    method public void setDefault2g(int);
+    method public void setDefault5g(int);
+  }
+
+  public class Entry {
+    ctor public Entry();
+    method public int getBand();
+    method public com.android.server.wifi.coex.Override getOverride();
+    method public com.android.server.wifi.coex.Params getParams();
+    method public int getPowerCapDbm();
+    method public com.android.server.wifi.coex.RatType getRat();
+    method public void setBand(int);
+    method public void setOverride(com.android.server.wifi.coex.Override);
+    method public void setParams(com.android.server.wifi.coex.Params);
+    method public void setPowerCapDbm(int);
+    method public void setRat(com.android.server.wifi.coex.RatType);
+  }
+
+  public class HarmonicParams {
+    ctor public HarmonicParams();
+    method public int getN();
+    method public int getOverlap();
+    method public void setN(int);
+    method public void setOverlap(int);
+  }
+
+  public class IntermodParams {
+    ctor public IntermodParams();
+    method public int getM();
+    method public int getN();
+    method public int getOverlap();
+    method public void setM(int);
+    method public void setN(int);
+    method public void setOverlap(int);
+  }
+
+  public class NeighborThresholds {
+    ctor public NeighborThresholds();
+    method public int getCellVictimMhz();
+    method public int getWifiVictimMhz();
+    method public void setCellVictimMhz(int);
+    method public void setWifiVictimMhz(int);
+  }
+
+  public class Override {
+    ctor public Override();
+    method public com.android.server.wifi.coex.Override2g getOverride2g();
+    method public com.android.server.wifi.coex.Override5g getOverride5g();
+    method public void setOverride2g(com.android.server.wifi.coex.Override2g);
+    method public void setOverride5g(com.android.server.wifi.coex.Override5g);
+  }
+
+  public class Override2g {
+    ctor public Override2g();
+    method public java.util.List<com.android.server.wifi.coex.OverrideCategory2g> getCategory();
+    method public java.util.List<java.lang.Integer> getChannel();
+  }
+
+  public class Override5g {
+    ctor public Override5g();
+    method public java.util.List<com.android.server.wifi.coex.OverrideCategory5g> getCategory();
+    method public java.util.List<java.lang.Integer> getChannel();
+  }
+
+  public enum OverrideCategory2g {
+    method public String getRawName();
+    enum_constant public static final com.android.server.wifi.coex.OverrideCategory2g all;
+  }
+
+  public enum OverrideCategory5g {
+    method public String getRawName();
+    enum_constant public static final com.android.server.wifi.coex.OverrideCategory5g _160Mhz;
+    enum_constant public static final com.android.server.wifi.coex.OverrideCategory5g _20Mhz;
+    enum_constant public static final com.android.server.wifi.coex.OverrideCategory5g _40Mhz;
+    enum_constant public static final com.android.server.wifi.coex.OverrideCategory5g _80Mhz;
+    enum_constant public static final com.android.server.wifi.coex.OverrideCategory5g all;
+  }
+
+  public class Params {
+    ctor public Params();
+    method public com.android.server.wifi.coex.DefaultChannels getDefaultChannels();
+    method public com.android.server.wifi.coex.HarmonicParams getHarmonicParams2g();
+    method public com.android.server.wifi.coex.HarmonicParams getHarmonicParams5g();
+    method public com.android.server.wifi.coex.IntermodParams getIntermodParams2g();
+    method public com.android.server.wifi.coex.IntermodParams getIntermodParams5g();
+    method public com.android.server.wifi.coex.NeighborThresholds getNeighborThresholds();
+    method public void setDefaultChannels(com.android.server.wifi.coex.DefaultChannels);
+    method public void setHarmonicParams2g(com.android.server.wifi.coex.HarmonicParams);
+    method public void setHarmonicParams5g(com.android.server.wifi.coex.HarmonicParams);
+    method public void setIntermodParams2g(com.android.server.wifi.coex.IntermodParams);
+    method public void setIntermodParams5g(com.android.server.wifi.coex.IntermodParams);
+    method public void setNeighborThresholds(com.android.server.wifi.coex.NeighborThresholds);
+  }
+
+  public enum RatType {
+    method public String getRawName();
+    enum_constant public static final com.android.server.wifi.coex.RatType LTE;
+    enum_constant public static final com.android.server.wifi.coex.RatType NR;
+  }
+
+  public class Table {
+    ctor public Table();
+    method public java.util.List<com.android.server.wifi.coex.Entry> getEntry();
+  }
+
+  public class XmlParser {
+    ctor public XmlParser();
+    method public static com.android.server.wifi.coex.DefaultChannels readDefaultChannels(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+    method public static com.android.server.wifi.coex.Entry readEntry(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+    method public static com.android.server.wifi.coex.NeighborThresholds readNeighborThresholds(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+    method public static com.android.server.wifi.coex.Override readOverride(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+    method public static com.android.server.wifi.coex.Override2g readOverride2g(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+    method public static com.android.server.wifi.coex.Override5g readOverride5g(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+    method public static com.android.server.wifi.coex.Params readParams(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+    method public static com.android.server.wifi.coex.Table readTable(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+    method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+    method public static void skip(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+  }
+
+}
+
diff --git a/service/coex-table-parser/api/last_current.txt b/service/coex-table-parser/api/last_current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/service/coex-table-parser/api/last_current.txt
diff --git a/service/coex-table-parser/api/last_removed.txt b/service/coex-table-parser/api/last_removed.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/service/coex-table-parser/api/last_removed.txt
diff --git a/service/coex-table-parser/api/removed.txt b/service/coex-table-parser/api/removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/service/coex-table-parser/api/removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/service/coex-table-parser/coex_table.xsd b/service/coex-table-parser/coex_table.xsd
new file mode 100644
index 0000000..8c3603d
--- /dev/null
+++ b/service/coex-table-parser/coex_table.xsd
@@ -0,0 +1,141 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+         Licensed under the Apache License, Version 2.0 (the "License");
+         you may not use this file except in compliance with the License.
+         You may obtain a copy of the License at
+
+                    http://www.apache.org/licenses/LICENSE-2.0
+
+         Unless required by applicable law or agreed to in writing, software
+         distributed under the License is distributed on an "AS IS" BASIS,
+         WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+         See the License for the specific language governing permissions and
+         limitations under the License.
+-->
+<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+            version="1.0">
+
+    <xsd:element name="table">
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element ref="entry" minOccurs="1" maxOccurs="unbounded"/>
+            </xsd:sequence>
+        </xsd:complexType>
+    </xsd:element>
+
+    <xsd:element name="entry">
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element name="rat" type="ratType"/>
+                <xsd:element name="band" type="xsd:int"/>
+                <xsd:element name="powerCapDbm" type="xsd:int" minOccurs="0"/>
+                <xsd:choice>
+                    <xsd:element ref="params"/>
+                    <xsd:element ref="override"/>
+                </xsd:choice>
+            </xsd:sequence>
+        </xsd:complexType>
+    </xsd:element>
+
+    <xsd:simpleType name="ratType">
+        <xsd:restriction base="xsd:string">
+            <xsd:enumeration value="LTE"/>
+            <xsd:enumeration value="NR"/>
+        </xsd:restriction>
+    </xsd:simpleType>
+
+    <!-- Define coex algorithm parameters -->
+
+    <xsd:element name="params">
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element ref="neighborThresholds" minOccurs="0"/>
+                <xsd:element name="harmonicParams2g" type="harmonicParams" minOccurs="0"/>
+                <xsd:element name="harmonicParams5g" type="harmonicParams" minOccurs="0"/>
+                <xsd:element name="intermodParams2g" type="intermodParams" minOccurs="0"/>
+                <xsd:element name="intermodParams5g" type="intermodParams" minOccurs="0"/>
+                <xsd:element ref="defaultChannels" minOccurs="0"/>
+            </xsd:sequence>
+        </xsd:complexType>
+    </xsd:element>
+
+    <xsd:element name="neighborThresholds">
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element name="wifiVictimMhz" type="xsd:int" minOccurs="0"/>
+                <xsd:element name="cellVictimMhz" type="xsd:int" minOccurs="0"/>
+            </xsd:sequence>
+        </xsd:complexType>
+    </xsd:element>
+
+    <xsd:complexType name="harmonicParams">
+        <xsd:sequence>
+            <xsd:element name="N" type="xsd:int"/>
+            <xsd:element name="overlap" type="xsd:int"/>
+        </xsd:sequence>
+    </xsd:complexType>
+
+    <xsd:complexType name="intermodParams">
+        <xsd:sequence>
+            <xsd:element name="N" type="xsd:int"/>
+            <xsd:element name="M" type="xsd:int"/>
+            <xsd:element name="overlap" type="xsd:int"/>
+        </xsd:sequence>
+    </xsd:complexType>
+
+    <xsd:element name="defaultChannels">
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element name="default2g" type="xsd:int" minOccurs="0"/>
+                <xsd:element name="default5g" type="xsd:int" minOccurs="0"/>
+            </xsd:sequence>
+        </xsd:complexType>
+    </xsd:element>
+
+    <!-- Define algorithm override lists -->
+
+    <xsd:element name="override">
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element ref="override2g" minOccurs="0"/>
+                <xsd:element ref="override5g" minOccurs="0"/>
+            </xsd:sequence>
+        </xsd:complexType>
+    </xsd:element>
+
+    <xsd:element name="override2g">
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element name="category" type="overrideCategory2g" minOccurs="0" maxOccurs="unbounded"/>
+                <xsd:element name="channel" type="xsd:int" minOccurs="0" maxOccurs="unbounded"/>
+            </xsd:sequence>
+        </xsd:complexType>
+    </xsd:element>
+
+    <xsd:element name="override5g">
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element name="category" type="overrideCategory5g" minOccurs="0" maxOccurs="unbounded"/>
+                <xsd:element name="channel" type="xsd:int" minOccurs="0" maxOccurs="unbounded"/>
+            </xsd:sequence>
+        </xsd:complexType>
+    </xsd:element>
+
+    <xsd:simpleType name="overrideCategory2g">
+        <xsd:restriction base="xsd:string">
+            <xsd:enumeration value="all"/>
+        </xsd:restriction>
+    </xsd:simpleType>
+
+    <xsd:simpleType name="overrideCategory5g">
+        <xsd:restriction base="xsd:string">
+            <xsd:enumeration value="all"/>
+            <xsd:enumeration value="20Mhz"/>
+            <xsd:enumeration value="40Mhz"/>
+            <xsd:enumeration value="80Mhz"/>
+            <xsd:enumeration value="160Mhz"/>
+        </xsd:restriction>
+    </xsd:simpleType>
+
+</xsd:schema>
diff --git a/service/java/com/android/server/wifi/ActiveModeManager.java b/service/java/com/android/server/wifi/ActiveModeManager.java
index 0983e9b..fddfbd4 100644
--- a/service/java/com/android/server/wifi/ActiveModeManager.java
+++ b/service/java/com/android/server/wifi/ActiveModeManager.java
@@ -16,14 +16,12 @@
 
 package com.android.server.wifi;
 
-import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.WorkSource;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
-import java.util.List;
 
 /**
  * Base class for available WiFi operating modes.
@@ -33,95 +31,151 @@
 public interface ActiveModeManager {
     /**
      * Listener for ActiveModeManager state changes.
+     * @param <T> type of ActiveModeManager that is being listened
      */
-    interface Listener {
+    interface Listener<T extends ActiveModeManager> {
         /**
-         * Invoked when mode manager completes start or on mode switch.
+         * Invoked when mode manager completes start.
          */
-        void onStarted();
+        void onStarted(@NonNull T activeModeManager);
         /**
          * Invoked when mode manager completes stop.
          */
-        void onStopped();
+        void onStopped(@NonNull T activeModeManager);
+        /**
+         * Invoked when mode manager completes a role switch.
+         */
+        void onRoleChanged(@NonNull T activeModeManager);
         /**
          * Invoked when mode manager encountered a failure on start or on mode switch.
          */
-        void onStartFailure();
+        void onStartFailure(@NonNull T activeModeManager);
     }
 
     /**
-     * Method used to start the Manager for a given Wifi operational mode.
-     */
-    void start();
-
-    /**
      * Method used to stop the Manager for a given Wifi operational mode.
      */
     void stop();
 
-    /**
-     * Method used to indicate if the mode manager is still stopping.
-     */
-    boolean isStopping();
+    // Hierarchy of roles - note that currently, the roles form a tree: no role has more than 1
+    // parent. However, since interfaces support multiple inheritance, a role could have more than 1
+    // parent if needed.
 
     /** Roles assigned to each mode manager. */
-    int ROLE_UNSPECIFIED = -1;
-    // SoftApManager - Tethering, will respond to public APIs.
-    int ROLE_SOFTAP_TETHERED = 0;
-    // SoftApManager - Local only hotspot.
-    int ROLE_SOFTAP_LOCAL_ONLY = 1;
-    // ClientModeManager, primary STA, will respond to public APIs
-    int ROLE_CLIENT_PRIMARY = 2;
-    // ClientModeManager, secondary STA, can switch to primary later.
-    int ROLE_CLIENT_SECONDARY = 3;
-    // ClientModeManager, secondary STA created for local connection (no internet connectivity).
-    int ROLE_CLIENT_LOCAL_ONLY = 4;
-    // ClientModeManager, STA created for scans only.
-    int ROLE_CLIENT_SCAN_ONLY = 5;
+    interface Role {}
 
-    @IntDef(prefix = { "ROLE_" }, value = {
-            ROLE_SOFTAP_TETHERED,
-            ROLE_SOFTAP_LOCAL_ONLY,
-            ROLE_CLIENT_PRIMARY,
-            ROLE_CLIENT_SECONDARY,
-            ROLE_CLIENT_LOCAL_ONLY,
-            ROLE_CLIENT_SCAN_ONLY
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    @interface Role{}
+    /** SoftAp roles */
+    interface SoftApRole extends Role {}
+    /** SoftApManager - Tethering, will respond to public APIs. */
+    SoftApRole ROLE_SOFTAP_TETHERED = new SoftApRole() {
+        @Override
+        public String toString() {
+            return "ROLE_SOFTAP_TETHERED";
+        }
+    };
+    /** SoftApManager - Local only hotspot. */
+    SoftApRole ROLE_SOFTAP_LOCAL_ONLY = new SoftApRole() {
+        @Override
+        public String toString() {
+            return "ROLE_SOFTAP_LOCAL_ONLY";
+        }
+    };
 
-    /** List of Client roles */
-    List<Integer> CLIENT_ROLES = Arrays.asList(
-            ROLE_CLIENT_PRIMARY,
-            ROLE_CLIENT_SECONDARY,
-            ROLE_CLIENT_LOCAL_ONLY,
-            ROLE_CLIENT_SCAN_ONLY);
-    /** List of Client roles that could initiate a wifi connection */
-    List<Integer> CLIENT_CONNECTIVITY_ROLES = Arrays.asList(
-            ROLE_CLIENT_PRIMARY,
-            ROLE_CLIENT_SECONDARY,
-            ROLE_CLIENT_LOCAL_ONLY);
-    /** List of Client roles that could initiate a wifi connection for internet connectivity */
-    List<Integer> CLIENT_INTERNET_CONNECTIVITY_ROLES = Arrays.asList(
-            ROLE_CLIENT_PRIMARY,
-            ROLE_CLIENT_SECONDARY);
-    /** List of SoftAp roles */
-    List<Integer> SOFTAP_ROLES = Arrays.asList(
-            ROLE_SOFTAP_LOCAL_ONLY,
-            ROLE_SOFTAP_TETHERED);
+    /** Client roles */
+    interface ClientRole extends Role {}
+    /** ClientModeManager, STA created for scans only. */
+    ClientRole ROLE_CLIENT_SCAN_ONLY = new ClientRole() {
+        @Override
+        public String toString() {
+            return "ROLE_CLIENT_SCAN_ONLY";
+        }
+    };
+
+    /** Client roles that could initiate a wifi connection */
+    interface ClientConnectivityRole extends ClientRole {}
+    /**
+     * ClientModeManager, secondary STA used for make before break, can switch to primary later.
+     * Note: ClientModeManagers in this role will call {@link #stop()} upon disconnecting from Wifi.
+     */
+    ClientConnectivityRole ROLE_CLIENT_SECONDARY_TRANSIENT = new ClientConnectivityRole() {
+        @Override
+        public String toString() {
+            return "ROLE_CLIENT_SECONDARY_TRANSIENT";
+        }
+    };
+    /** ClientModeManager, secondary STA created for local connection (no internet connectivity). */
+    ClientConnectivityRole ROLE_CLIENT_LOCAL_ONLY = new ClientConnectivityRole() {
+        @Override
+        public String toString() {
+            return "ROLE_CLIENT_LOCAL_ONLY";
+        }
+    };
+
+    /** Long running Client roles that could initiate a wifi connection for internet connectivity */
+    interface ClientInternetConnectivityRole extends ClientConnectivityRole {}
+    /**
+     * ClientModeManager, primary STA, will respond to public WifiManager APIs
+     * Note: Primary STA can be used to satisfy any of the other client roles whenever it is not
+     * possible to create a concurrent ClientModeManager for the specified role. This is only true
+     * for primary role. ClientModeManager in any of the other roles are dedicated to the
+     * corresponding role.
+     */
+    ClientInternetConnectivityRole ROLE_CLIENT_PRIMARY =
+            new ClientInternetConnectivityRole() {
+                @Override
+                public String toString() {
+                    return "ROLE_CLIENT_PRIMARY";
+                }
+            };
+    /**
+     * ClientModeManager, secondary STA used for duplication/bonding use cases, will not respond to
+     * public WifiManager APIs.
+     *
+     * Note: ClientModeManagers in this role will call {@link #stop()} upon disconnecting from Wifi.
+     */
+    ClientInternetConnectivityRole ROLE_CLIENT_SECONDARY_LONG_LIVED =
+            new ClientInternetConnectivityRole() {
+                @Override
+                public String toString() {
+                    return "ROLE_CLIENT_SECONDARY_LONG_LIVED";
+                }
+            };
 
     /**
      * Method to get the role for a mode manager.
      */
-    @Role int getRole();
+    @Nullable Role getRole();
 
     /**
-     * Method to set the role for a mode manager.
+     * Method to get the previous role a mode manager.
      */
-    void setRole(@Role int role);
+    @Nullable Role getPreviousRole();
+
+    /**
+     * Get the time in ms since boot of the last role change.
+     */
+    long getLastRoleChangeSinceBootMs();
+
+    /**
+     * Method to get the iface name for the mode manager.
+     */
+    String getInterfaceName();
+
+    /**
+     * Method to retrieve the original requestorWs
+     */
+    WorkSource getRequestorWs();
 
     /**
      * Method to dump for logging state.
      */
     void dump(FileDescriptor fd, PrintWriter pw, String[] args);
+
+    /**
+     * Method to enable verbose logging.
+     */
+    void enableVerboseLogging(boolean verbose);
+
+    /** Unique ID for this ActiveModeManager instance, used for debugging. */
+    long getId();
 }
diff --git a/service/java/com/android/server/wifi/ActiveModeWarden.java b/service/java/com/android/server/wifi/ActiveModeWarden.java
index cf54e32..1686d70 100644
--- a/service/java/com/android/server/wifi/ActiveModeWarden.java
+++ b/service/java/com/android/server/wifi/ActiveModeWarden.java
@@ -19,21 +19,41 @@
 import static android.net.wifi.WifiManager.IFACE_IP_MODE_LOCAL_ONLY;
 import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED;
 
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_LOCAL_ONLY;
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_PRIMARY;
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SCAN_ONLY;
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SECONDARY_LONG_LIVED;
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SECONDARY_TRANSIENT;
+import static com.android.server.wifi.ActiveModeManager.ROLE_SOFTAP_TETHERED;
+
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.location.LocationManager;
+import android.net.wifi.ISubsystemRestartCallback;
+import android.net.wifi.IWifiConnectedNetworkScorer;
 import android.net.wifi.SoftApCapability;
 import android.net.wifi.SoftApConfiguration;
+import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
 import android.os.BatteryStatsManager;
+import android.os.Build;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Process;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.WorkSource;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IState;
@@ -41,13 +61,25 @@
 import com.android.internal.util.Protocol;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.android.server.wifi.ActiveModeManager.ClientConnectivityRole;
+import com.android.server.wifi.ActiveModeManager.ClientInternetConnectivityRole;
+import com.android.server.wifi.ActiveModeManager.ClientRole;
+import com.android.server.wifi.ActiveModeManager.SoftApRole;
 import com.android.server.wifi.util.WifiPermissionsUtil;
 import com.android.wifi.resources.R;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * This class provides the implementation for different WiFi operating modes.
@@ -55,17 +87,22 @@
 public class ActiveModeWarden {
     private static final String TAG = "WifiActiveModeWarden";
     private static final String STATE_MACHINE_EXITED_STATE_NAME = "STATE_MACHINE_EXITED";
+    public static final WorkSource INTERNAL_REQUESTOR_WS = new WorkSource(Process.WIFI_UID);
 
     // Holder for active mode managers
-    private final ArraySet<ActiveModeManager> mActiveModeManagers;
-    // DefaultModeManager used to service API calls when there are not active mode managers.
-    private final DefaultModeManager mDefaultModeManager;
+    private final Set<ConcreteClientModeManager> mClientModeManagers = new ArraySet<>();
+    private final Set<SoftApManager> mSoftApManagers = new ArraySet<>();
 
+    private final Set<ModeChangeCallback> mCallbacks = new ArraySet<>();
+    private final Set<PrimaryClientModeManagerChangedCallback> mPrimaryChangedCallbacks =
+            new ArraySet<>();
+    // DefaultModeManager used to service API calls when there are no active client mode managers.
+    private final DefaultClientModeManager mDefaultClientModeManager;
     private final WifiInjector mWifiInjector;
     private final Looper mLooper;
     private final Handler mHandler;
     private final Context mContext;
-    private final ClientModeImpl mClientModeImpl;
+    private final WifiDiagnostics mWifiDiagnostics;
     private final WifiSettingsStore mSettingsStore;
     private final FrameworkFacade mFacade;
     private final WifiPermissionsUtil mWifiPermissionsUtil;
@@ -73,18 +110,34 @@
     private final ScanRequestProxy mScanRequestProxy;
     private final WifiNative mWifiNative;
     private final WifiController mWifiController;
+    private final Graveyard mGraveyard;
+    private final WifiMetrics mWifiMetrics;
+    private final ExternalScoreUpdateObserverProxy mExternalScoreUpdateObserverProxy;
+    private final DppManager mDppManager;
 
-    private WifiManager.SoftApCallback mSoftApCallback;
-    private WifiManager.SoftApCallback mLohsCallback;
+    private WifiServiceImpl.SoftApCallbackInternal mSoftApCallback;
+    private WifiServiceImpl.SoftApCallbackInternal mLohsCallback;
 
-    private boolean mCanRequestMoreClientModeManagers = false;
-    private boolean mCanRequestMoreSoftApManagers = false;
+    private final RemoteCallbackList<ISubsystemRestartCallback> mRestartCallbacks =
+            new RemoteCallbackList<>();
+
     private boolean mIsShuttingdown = false;
+    private boolean mVerboseLoggingEnabled = false;
+    /** Cache to store the external scorer for primary and secondary (MBB) client mode manager. */
+    @Nullable private Pair<IBinder, IWifiConnectedNetworkScorer> mClientModeManagerScorer;
+
+    @Nullable
+    private ConcreteClientModeManager mLastPrimaryClientModeManager = null;
+
+    @Nullable
+    private WorkSource mLastPrimaryClientModeManagerRequestorWs = null;
+    @Nullable
+    private WorkSource mLastScanOnlyClientModeManagerRequestorWs = null;
 
     /**
      * Called from WifiServiceImpl to register a callback for notifications from SoftApManager
      */
-    public void registerSoftApCallback(@NonNull WifiManager.SoftApCallback callback) {
+    public void registerSoftApCallback(@NonNull WifiServiceImpl.SoftApCallbackInternal callback) {
         mSoftApCallback = callback;
     }
 
@@ -92,41 +145,141 @@
      * Called from WifiServiceImpl to register a callback for notifications from SoftApManager
      * for local-only hotspot.
      */
-    public void registerLohsCallback(@NonNull WifiManager.SoftApCallback callback) {
+    public void registerLohsCallback(@NonNull WifiServiceImpl.SoftApCallbackInternal callback) {
         mLohsCallback = callback;
     }
 
+    /**
+     * Callbacks for indicating any mode manager changes to the rest of the system.
+     */
+    public interface ModeChangeCallback {
+        /**
+         * Invoked when new mode manager is added.
+         *
+         * @param activeModeManager Instance of {@link ActiveModeManager}.
+         */
+        void onActiveModeManagerAdded(@NonNull ActiveModeManager activeModeManager);
+
+        /**
+         * Invoked when a mode manager is removed.
+         *
+         * @param activeModeManager Instance of {@link ActiveModeManager}.
+         */
+        void onActiveModeManagerRemoved(@NonNull ActiveModeManager activeModeManager);
+
+        /**
+         * Invoked when an existing mode manager's role is changed.
+         *
+         * @param activeModeManager Instance of {@link ActiveModeManager}.
+         */
+        void onActiveModeManagerRoleChanged(@NonNull ActiveModeManager activeModeManager);
+    }
+
+    /** Called when the primary ClientModeManager changes. */
+    public interface PrimaryClientModeManagerChangedCallback {
+        /**
+         * Note: The current implementation for changing primary CMM is not atomic (due to setRole()
+         * needing to go through StateMachine, which is async). Thus, when the primary CMM changes,
+         * the sequence of calls looks like this:
+         * 1. onChange(prevPrimaryCmm, null)
+         * 2. onChange(null, newPrimaryCmm)
+         * Nevertheless, at run time, these two calls should occur in rapid succession.
+         *
+         * @param prevPrimaryClientModeManager the previous primary ClientModeManager, or null if
+         *                                     there was no previous primary (e.g. Wifi was off).
+         * @param newPrimaryClientModeManager the new primary ClientModeManager, or null if there is
+         *                                    no longer a primary (e.g. Wifi was turned off).
+         */
+        void onChange(
+                @Nullable ConcreteClientModeManager prevPrimaryClientModeManager,
+                @Nullable ConcreteClientModeManager newPrimaryClientModeManager);
+    }
+
+    /**
+     * Keep stopped {@link ActiveModeManager} instances so that they can be dumped to aid debugging.
+     *
+     * TODO(b/160283853): Find a smarter way to evict old ActiveModeManagers
+     */
+    private static class Graveyard {
+        private static final int INSTANCES_TO_KEEP = 3;
+
+        private final ArrayDeque<ConcreteClientModeManager> mClientModeManagers =
+                new ArrayDeque<>();
+        private final ArrayDeque<SoftApManager> mSoftApManagers = new ArrayDeque<>();
+
+        /**
+         * Add this stopped {@link ConcreteClientModeManager} to the graveyard, and evict the oldest
+         * ClientModeManager if the graveyard is full.
+         */
+        void inter(ConcreteClientModeManager clientModeManager) {
+            if (mClientModeManagers.size() == INSTANCES_TO_KEEP) {
+                mClientModeManagers.removeFirst();
+            }
+            mClientModeManagers.addLast(clientModeManager);
+        }
+
+        /**
+         * Add this stopped {@link SoftApManager} to the graveyard, and evict the oldest
+         * SoftApManager if the graveyard is full.
+         */
+        void inter(SoftApManager softApManager) {
+            if (mSoftApManagers.size() == INSTANCES_TO_KEEP) {
+                mSoftApManagers.removeFirst();
+            }
+            mSoftApManagers.addLast(softApManager);
+        }
+
+        /** Dump the contents of the graveyard. */
+        void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            pw.println("Dump of ActiveModeWarden.Graveyard");
+            pw.println("Stopped ClientModeManagers: " + mClientModeManagers.size() + " total");
+            for (ConcreteClientModeManager clientModeManager : mClientModeManagers) {
+                clientModeManager.dump(fd, pw, args);
+            }
+            pw.println("Stopped SoftApManagers: " + mSoftApManagers.size() + " total");
+            for (SoftApManager softApManager : mSoftApManagers) {
+                softApManager.dump(fd, pw, args);
+            }
+            pw.println();
+        }
+    }
+
     ActiveModeWarden(WifiInjector wifiInjector,
-                     Looper looper,
-                     WifiNative wifiNative,
-                     DefaultModeManager defaultModeManager,
-                     BatteryStatsManager batteryStatsManager,
-                     BaseWifiDiagnostics wifiDiagnostics,
-                     Context context,
-                     ClientModeImpl clientModeImpl,
-                     WifiSettingsStore settingsStore,
-                     FrameworkFacade facade,
-                     WifiPermissionsUtil wifiPermissionsUtil) {
+            Looper looper,
+            WifiNative wifiNative,
+            DefaultClientModeManager defaultClientModeManager,
+            BatteryStatsManager batteryStatsManager,
+            WifiDiagnostics wifiDiagnostics,
+            Context context,
+            WifiSettingsStore settingsStore,
+            FrameworkFacade facade,
+            WifiPermissionsUtil wifiPermissionsUtil,
+            WifiMetrics wifiMetrics,
+            ExternalScoreUpdateObserverProxy externalScoreUpdateObserverProxy,
+            DppManager dppManager) {
         mWifiInjector = wifiInjector;
         mLooper = looper;
         mHandler = new Handler(looper);
         mContext = context;
-        mClientModeImpl = clientModeImpl;
+        mWifiDiagnostics = wifiDiagnostics;
         mSettingsStore = settingsStore;
         mFacade = facade;
         mWifiPermissionsUtil = wifiPermissionsUtil;
-        mActiveModeManagers = new ArraySet<>();
-        mDefaultModeManager = defaultModeManager;
+        mDefaultClientModeManager = defaultClientModeManager;
         mBatteryStatsManager = batteryStatsManager;
         mScanRequestProxy = wifiInjector.getScanRequestProxy();
         mWifiNative = wifiNative;
+        mWifiMetrics = wifiMetrics;
         mWifiController = new WifiController();
+        mExternalScoreUpdateObserverProxy = externalScoreUpdateObserverProxy;
+        mDppManager = dppManager;
+        mGraveyard = new Graveyard();
 
         wifiNative.registerStatusListener(isReady -> {
             if (!isReady && !mIsShuttingdown) {
                 mHandler.post(() -> {
                     Log.e(TAG, "One of the native daemons died. Triggering recovery");
-                    wifiDiagnostics.captureBugReportData(
+                    wifiDiagnostics.triggerBugReportDataCapture(
                             WifiDiagnostics.REPORT_REASON_WIFINATIVE_FAILURE);
 
                     // immediately trigger SelfRecovery if we receive a notice about an
@@ -139,14 +292,123 @@
             }
         });
 
-        wifiNative.registerClientInterfaceAvailabilityListener(
-                (isAvailable) -> mHandler.post(() -> {
-                    mCanRequestMoreClientModeManagers = isAvailable;
-                }));
-        wifiNative.registerSoftApInterfaceAvailabilityListener(
-                (isAvailable) -> mHandler.post(() -> {
-                    mCanRequestMoreSoftApManagers = isAvailable;
-                }));
+        registerPrimaryClientModeManagerChangedCallback(
+                (prevPrimaryClientModeManager, newPrimaryClientModeManager) -> {
+                    // TODO (b/181363901): We can always propagate the external scorer to all
+                    // ClientModeImpl instances. WifiScoreReport already handles skipping external
+                    // scorer notification for local only & restricted STA + STA use-cases. For MBB
+                    // use-case, we may want the external scorer to be notified.
+                    if (prevPrimaryClientModeManager != null) {
+                        prevPrimaryClientModeManager.clearWifiConnectedNetworkScorer();
+                    }
+                    if (newPrimaryClientModeManager != null && mClientModeManagerScorer != null) {
+                        newPrimaryClientModeManager.setWifiConnectedNetworkScorer(
+                                mClientModeManagerScorer.first,
+                                mClientModeManagerScorer.second);
+                    }
+                });
+    }
+
+    private void invokeOnAddedCallbacks(@NonNull ActiveModeManager activeModeManager) {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "ModeManager added " + activeModeManager);
+        }
+        for (ModeChangeCallback callback : mCallbacks) {
+            callback.onActiveModeManagerAdded(activeModeManager);
+        }
+    }
+
+    private void invokeOnRemovedCallbacks(@NonNull ActiveModeManager activeModeManager) {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "ModeManager removed " + activeModeManager);
+        }
+        for (ModeChangeCallback callback : mCallbacks) {
+            callback.onActiveModeManagerRemoved(activeModeManager);
+        }
+    }
+
+    private void invokeOnRoleChangedCallbacks(@NonNull ActiveModeManager activeModeManager) {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "ModeManager role changed " + activeModeManager);
+        }
+        for (ModeChangeCallback callback : mCallbacks) {
+            callback.onActiveModeManagerRoleChanged(activeModeManager);
+        }
+    }
+
+    private void invokeOnPrimaryClientModeManagerChangedCallbacks(
+            @Nullable ConcreteClientModeManager prevPrimaryClientModeManager,
+            @Nullable ConcreteClientModeManager newPrimaryClientModeManager) {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "Primary ClientModeManager changed from " + prevPrimaryClientModeManager
+                    + " to " + newPrimaryClientModeManager);
+        }
+        for (PrimaryClientModeManagerChangedCallback callback : mPrimaryChangedCallbacks) {
+            callback.onChange(prevPrimaryClientModeManager, newPrimaryClientModeManager);
+        }
+    }
+
+    /**
+     * Enable verbose logging.
+     */
+    public void enableVerboseLogging(boolean verbose) {
+        mVerboseLoggingEnabled = verbose;
+        for (ActiveModeManager modeManager : getActiveModeManagers()) {
+            modeManager.enableVerboseLogging(verbose);
+        }
+    }
+
+    /**
+     * See {@link android.net.wifi.WifiManager#setWifiConnectedNetworkScorer(Executor,
+     * WifiManager.WifiConnectedNetworkScorer)}
+     */
+    public boolean setWifiConnectedNetworkScorer(IBinder binder,
+            IWifiConnectedNetworkScorer scorer) {
+        try {
+            scorer.onSetScoreUpdateObserver(mExternalScoreUpdateObserverProxy);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Unable to set score update observer " + scorer, e);
+            return false;
+        }
+        mClientModeManagerScorer = Pair.create(binder, scorer);
+        return getPrimaryClientModeManager().setWifiConnectedNetworkScorer(binder, scorer);
+    }
+
+    /**
+     * See {@link WifiManager#clearWifiConnectedNetworkScorer()}
+     */
+    public void clearWifiConnectedNetworkScorer() {
+        mClientModeManagerScorer = null;
+        getPrimaryClientModeManager().clearWifiConnectedNetworkScorer();
+    }
+
+    /**
+     * Register for mode change callbacks.
+     */
+    public void registerModeChangeCallback(@NonNull ModeChangeCallback callback) {
+        mCallbacks.add(Objects.requireNonNull(callback));
+    }
+
+    /**
+     * Unregister mode change callback.
+     */
+    public void unregisterModeChangeCallback(@NonNull ModeChangeCallback callback) {
+        mCallbacks.remove(Objects.requireNonNull(callback));
+    }
+
+    /** Register for primary ClientModeManager changed callbacks. */
+    public void registerPrimaryClientModeManagerChangedCallback(
+            @NonNull PrimaryClientModeManagerChangedCallback callback) {
+        mPrimaryChangedCallbacks.add(Objects.requireNonNull(callback));
+        // If there is already a primary CMM when registering, send a callback with the info.
+        ConcreteClientModeManager cm = getPrimaryClientModeManagerNullable();
+        if (cm != null) callback.onChange(null, cm);
+    }
+
+    /** Unregister for primary ClientModeManager changed callbacks. */
+    public void unregisterPrimaryClientModeManagerChangedCallback(
+            @NonNull PrimaryClientModeManagerChangedCallback callback) {
+        mPrimaryChangedCallbacks.remove(Objects.requireNonNull(callback));
     }
 
     /**
@@ -161,15 +423,41 @@
     /**
      * @return Returns whether we can create more client mode managers or not.
      */
-    public boolean canRequestMoreClientModeManagers() {
-        return mCanRequestMoreClientModeManagers;
+    public boolean canRequestMoreClientModeManagersInRole(@NonNull WorkSource requestorWs,
+            @NonNull ClientRole clientRole) {
+        if (!mWifiNative.isItPossibleToCreateStaIface(requestorWs)) {
+            return false;
+        }
+        if (clientRole == ROLE_CLIENT_LOCAL_ONLY) {
+            if (!mContext.getResources().getBoolean(
+                    R.bool.config_wifiMultiStaLocalOnlyConcurrencyEnabled)) {
+                return false;
+            }
+            final int uid = requestorWs.getUid(0);
+            final String packageName = requestorWs.getPackageName(0);
+            // For peer to peer use-case, only allow secondary STA if the app is targeting S SDK
+            // or is a system app to provide backward compatibility.
+            return mWifiPermissionsUtil.isSystem(packageName, uid)
+                    || !mWifiPermissionsUtil.isTargetSdkLessThan(
+                            packageName, Build.VERSION_CODES.S, uid);
+        }
+        if (clientRole == ROLE_CLIENT_SECONDARY_TRANSIENT) {
+            return mContext.getResources().getBoolean(
+                    R.bool.config_wifiMultiStaNetworkSwitchingMakeBeforeBreakEnabled);
+        }
+        if (clientRole == ROLE_CLIENT_SECONDARY_LONG_LIVED) {
+            return mContext.getResources().getBoolean(
+                    R.bool.config_wifiMultiStaRestrictedConcurrencyEnabled);
+        }
+        Log.e(TAG, "Unrecognized role=" + clientRole);
+        return false;
     }
 
     /**
      * @return Returns whether we can create more SoftAp managers or not.
      */
-    public boolean canRequestMoreSoftApManagers() {
-        return mCanRequestMoreSoftApManagers;
+    public boolean canRequestMoreSoftApManagers(@NonNull WorkSource requestorWs) {
+        return mWifiNative.isItPossibleToCreateApIface(requestorWs);
     }
 
     /**
@@ -180,8 +468,75 @@
         return mWifiNative.isStaApConcurrencySupported();
     }
 
+    /**
+     * @return Returns whether the device can support at least two concurrent client mode managers
+     * and the local only use-case is enabled.
+     */
+    public boolean isStaStaConcurrencySupportedForLocalOnlyConnections() {
+        return mWifiNative.isStaStaConcurrencySupported()
+                && mContext.getResources().getBoolean(
+                        R.bool.config_wifiMultiStaLocalOnlyConcurrencyEnabled);
+    }
+
+    /**
+     * @return Returns whether the device can support at least two concurrent client mode managers
+     * and the mbb wifi switching is enabled.
+     */
+    public boolean isStaStaConcurrencySupportedForMbb() {
+        return mWifiNative.isStaStaConcurrencySupported()
+                && mContext.getResources().getBoolean(
+                        R.bool.config_wifiMultiStaNetworkSwitchingMakeBeforeBreakEnabled);
+    }
+
+    /**
+     * @return Returns whether the device can support at least two concurrent client mode managers
+     * and the restricted use-case is enabled.
+     */
+    public boolean isStaStaConcurrencySupportedForRestrictedConnections() {
+        return mWifiNative.isStaStaConcurrencySupported()
+                && mContext.getResources().getBoolean(
+                        R.bool.config_wifiMultiStaRestrictedConcurrencyEnabled);
+    }
+
     /** Begin listening to broadcasts and start the internal state machine. */
     public void start() {
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                // Location mode has been toggled...  trigger with the scan change
+                // update to make sure we are in the correct mode
+                scanAlwaysModeChanged();
+            }
+        }, new IntentFilter(LocationManager.MODE_CHANGED_ACTION));
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (mSettingsStore.handleAirplaneModeToggled()) {
+                    airplaneModeToggled();
+                }
+            }
+        }, new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                boolean emergencyMode =
+                        intent.getBooleanExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false);
+                emergencyCallbackModeChanged(emergencyMode);
+            }
+        }, new IntentFilter(TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED));
+        boolean trackEmergencyCallState = mContext.getResources().getBoolean(
+                R.bool.config_wifi_turn_off_during_emergency_call);
+        if (trackEmergencyCallState) {
+            mContext.registerReceiver(new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    boolean inCall = intent.getBooleanExtra(
+                            TelephonyManager.EXTRA_PHONE_IN_EMERGENCY_CALL, false);
+                    emergencyCallStateChanged(inCall);
+                }
+            }, new IntentFilter(TelephonyManager.ACTION_EMERGENCY_CALL_STATE_CHANGED));
+        }
+
         mWifiController.start();
     }
 
@@ -194,13 +549,32 @@
      * Restart Wifi for recovery purposes.
      * @param reason One of {@link SelfRecovery.RecoveryReason}
      */
-    public void recoveryRestartWifi(@SelfRecovery.RecoveryReason int reason) {
-        mWifiController.sendMessage(WifiController.CMD_RECOVERY_RESTART_WIFI, reason);
+    public void recoveryRestartWifi(@SelfRecovery.RecoveryReason int reason,
+            @Nullable String reasonDetail, boolean requestBugReport) {
+        mWifiController.sendMessage(WifiController.CMD_RECOVERY_RESTART_WIFI, reason,
+                requestBugReport ? 1 : 0, reasonDetail);
+    }
+
+    /**
+     * register a callback to monitor the progress of Wi-Fi subsystem operation (started/finished)
+     * - started via {@link #recoveryRestartWifi(int, String, boolean)}.
+     */
+    public boolean registerSubsystemRestartCallback(ISubsystemRestartCallback callback) {
+        return mRestartCallbacks.register(callback);
+    }
+
+    /**
+     * unregister a callback to monitor the progress of Wi-Fi subsystem operation (started/finished)
+     * - started via {@link #recoveryRestartWifi(int, String, boolean)}. Callback is registered via
+     * {@link #registerSubsystemRestartCallback(ISubsystemRestartCallback)}.
+     */
+    public boolean unregisterSubsystemRestartCallback(ISubsystemRestartCallback callback) {
+        return mRestartCallbacks.unregister(callback);
     }
 
     /** Wifi has been toggled. */
-    public void wifiToggled() {
-        mWifiController.sendMessage(WifiController.CMD_WIFI_TOGGLED);
+    public void wifiToggled(WorkSource requestorWs) {
+        mWifiController.sendMessage(WifiController.CMD_WIFI_TOGGLED, requestorWs);
     }
 
     /** Airplane Mode has been toggled. */
@@ -209,8 +583,9 @@
     }
 
     /** Starts SoftAp. */
-    public void startSoftAp(SoftApModeConfiguration softApConfig) {
-        mWifiController.sendMessage(WifiController.CMD_SET_AP, 1, 0, softApConfig);
+    public void startSoftAp(SoftApModeConfiguration softApConfig, WorkSource requestorWs) {
+        mWifiController.sendMessage(WifiController.CMD_SET_AP, 1, 0,
+                Pair.create(softApConfig, requestorWs));
     }
 
     /** Stop SoftAp. */
@@ -242,53 +617,236 @@
 
     /** Scan always mode has changed. */
     public void scanAlwaysModeChanged() {
-        mWifiController.sendMessage(WifiController.CMD_SCAN_ALWAYS_MODE_CHANGED);
+        mWifiController.sendMessage(
+                WifiController.CMD_SCAN_ALWAYS_MODE_CHANGED,
+                // Scan only mode change is not considered a direct user interaction since user
+                // is not explicitly turning on wifi scanning (side-effect of location toggle).
+                // So, use the lowest priority internal requestor worksource to ensure that this
+                // is treated with the lowest priority.
+                INTERNAL_REQUESTOR_WS);
+    }
+
+    /** emergency scan progress indication. */
+    public void setEmergencyScanRequestInProgress(boolean inProgress) {
+        mWifiController.sendMessage(
+                WifiController.CMD_EMERGENCY_SCAN_STATE_CHANGED,
+                inProgress ? 1 : 0, 0,
+                // Emergency scans should have the highest priority, so use settings worksource.
+                mFacade.getSettingsWorkSource(mContext));
+    }
+
+    /**
+     * Listener to request a ModeManager instance for a particular operation.
+     */
+    public interface ExternalClientModeManagerRequestListener {
+        /**
+         * Returns an instance of ClientModeManager or null if the request failed (when wifi is
+         * off).
+         */
+        void onAnswer(@Nullable ClientModeManager modeManager);
+    }
+
+    private static class AdditionalClientModeManagerRequestInfo {
+        @NonNull public final ExternalClientModeManagerRequestListener listener;
+        @NonNull public final WorkSource requestorWs;
+        @NonNull public final ClientConnectivityRole clientRole;
+        @NonNull public final String ssid;
+        @Nullable public final String bssid;
+
+        AdditionalClientModeManagerRequestInfo(
+                @NonNull  ExternalClientModeManagerRequestListener listener,
+                @NonNull WorkSource requestorWs,
+                @NonNull ClientConnectivityRole clientRole,
+                @NonNull String ssid,
+                // For some use-cases, bssid is selected by firmware.
+                @Nullable String bssid) {
+            this.listener = listener;
+            this.requestorWs = requestorWs;
+            this.clientRole = clientRole;
+            this.ssid = ssid;
+            this.bssid = bssid;
+        }
+    }
+
+    /**
+     * Request a local only client manager.
+     *
+     * @param listener used to receive the requested ClientModeManager. Will receive:
+     *                 1. null - if Wifi is toggled off
+     *                 2. The primary ClientModeManager - if a new ClientModeManager cannot be
+     *                    created.
+     *                 3. The new ClientModeManager - if it was created successfully.
+     * @param requestorWs the WorkSource for this request
+     */
+    public void requestLocalOnlyClientModeManager(
+            @NonNull ExternalClientModeManagerRequestListener listener,
+            @NonNull WorkSource requestorWs, @NonNull String ssid, @NonNull String bssid) {
+        mWifiController.sendMessage(
+                WifiController.CMD_REQUEST_ADDITIONAL_CLIENT_MODE_MANAGER,
+                new AdditionalClientModeManagerRequestInfo(
+                        Objects.requireNonNull(listener), Objects.requireNonNull(requestorWs),
+                        ROLE_CLIENT_LOCAL_ONLY, ssid, bssid));
+    }
+
+    /**
+     * Request a secondary long lived client manager.
+     *
+     * @param listener used to receive the requested ClientModeManager. Will receive:
+     *                 1. null - if Wifi is toggled off
+     *                 2. The primary ClientModeManager - if a new ClientModeManager cannot be
+     *                    created.
+     *                 3. The new ClientModeManager - if it was created successfully.
+     * @param requestorWs the WorkSource for this request
+     */
+    public void requestSecondaryLongLivedClientModeManager(
+            @NonNull ExternalClientModeManagerRequestListener listener,
+            @NonNull WorkSource requestorWs, @NonNull String ssid, @Nullable String bssid) {
+        mWifiController.sendMessage(
+                WifiController.CMD_REQUEST_ADDITIONAL_CLIENT_MODE_MANAGER,
+                new AdditionalClientModeManagerRequestInfo(
+                        Objects.requireNonNull(listener), Objects.requireNonNull(requestorWs),
+                        ROLE_CLIENT_SECONDARY_LONG_LIVED, ssid, bssid));
+    }
+
+    /**
+     * Request a secondary transient client manager.
+     *
+     * @param listener used to receive the requested ClientModeManager. Will receive:
+     *                 1. null - if Wifi is toggled off.
+     *                 2. An existing secondary transient ClientModeManager - if it already exists.
+     *                 3. A new secondary transient ClientModeManager - if one doesn't exist and one
+     *                    was created successfully.
+     *                 4. The primary ClientModeManager - if a new ClientModeManager cannot be
+     *                    created.
+     * @param requestorWs the WorkSource for this request
+     */
+    public void requestSecondaryTransientClientModeManager(
+            @NonNull ExternalClientModeManagerRequestListener listener,
+            @NonNull WorkSource requestorWs, @NonNull String ssid, @Nullable String bssid) {
+        mWifiController.sendMessage(
+                WifiController.CMD_REQUEST_ADDITIONAL_CLIENT_MODE_MANAGER,
+                new AdditionalClientModeManagerRequestInfo(
+                        Objects.requireNonNull(listener), Objects.requireNonNull(requestorWs),
+                        ROLE_CLIENT_SECONDARY_TRANSIENT, ssid, bssid));
+    }
+
+    /**
+     * Remove the provided client manager.
+     */
+    public void removeClientModeManager(ClientModeManager clientModeManager) {
+        mWifiController.sendMessage(
+                WifiController.CMD_REMOVE_ADDITIONAL_CLIENT_MODE_MANAGER, clientModeManager);
+    }
+
+    /**
+     * Check whether we have a primary client mode manager (indicates wifi toggle on).
+     */
+    public boolean hasPrimaryClientModeManager() {
+        return getClientModeManagerInRole(ROLE_CLIENT_PRIMARY) != null;
+    }
+
+    /**
+     * Returns primary client mode manager if any, else returns null
+     * This mode manager can be the default route on the device & will handle all external API
+     * calls.
+     * @return Instance of {@link ConcreteClientModeManager} or null.
+     */
+    @Nullable
+    public ConcreteClientModeManager getPrimaryClientModeManagerNullable() {
+        return getClientModeManagerInRole(ROLE_CLIENT_PRIMARY);
+    }
+
+    /**
+     * Returns primary client mode manager if any, else returns an instance of
+     * {@link ClientModeManager}.
+     * This mode manager can be the default route on the device & will handle all external API
+     * calls.
+     * @return Instance of {@link ClientModeManager}.
+     */
+    @NonNull
+    public ClientModeManager getPrimaryClientModeManager() {
+        ClientModeManager cm = getPrimaryClientModeManagerNullable();
+        if (cm != null) return cm;
+        // If there is no primary client manager, return the default one.
+        return mDefaultClientModeManager;
+    }
+
+    /**
+     * Returns all instances of ClientModeManager in
+     * {@link ActiveModeManager.ClientInternetConnectivityRole} roles.
+     * @return List of {@link ClientModeManager}.
+     */
+    @NonNull
+    public List<ClientModeManager> getInternetConnectivityClientModeManagers() {
+        List<ClientModeManager> modeManagers = new ArrayList<>();
+        for (ConcreteClientModeManager manager : mClientModeManagers) {
+            if (manager.getRole() instanceof ClientInternetConnectivityRole) {
+                modeManagers.add(manager);
+            }
+        }
+        return modeManagers;
+    }
+
+    /** Stop all secondary transient ClientModeManagers. */
+    public void stopAllClientModeManagersInRole(ClientRole role) {
+        // there should only be at most one Make Before Break CMM, but check all of them to be safe.
+        for (ConcreteClientModeManager manager : mClientModeManagers) {
+            if (manager.getRole() == role) {
+                stopAdditionalClientModeManager(manager);
+            }
+        }
+    }
+
+    @NonNull
+    public List<ClientModeManager> getClientModeManagers() {
+        return new ArrayList<>(mClientModeManagers);
+    }
+
+    /**
+     * Returns scan only client mode manager, if any.
+     * This mode manager will only allow scanning.
+     * @return Instance of {@link ClientModeManager} or null if none present.
+     */
+    @Nullable
+    public ClientModeManager getScanOnlyClientModeManager() {
+        return getClientModeManagerInRole(ROLE_CLIENT_SCAN_ONLY);
+    }
+
+    /**
+     * Returns tethered softap manager, if any.
+     * @return Instance of {@link SoftApManager} or null if none present.
+     */
+    @Nullable
+    public SoftApManager getTetheredSoftApManager() {
+        return getSoftApManagerInRole(ROLE_SOFTAP_TETHERED);
+    }
+
+    /**
+     * Returns LOHS softap manager, if any.
+     * @return Instance of {@link SoftApManager} or null if none present.
+     */
+    @Nullable
+    public SoftApManager getLocalOnlySoftApManager() {
+        return getSoftApManagerInRole(ActiveModeManager.ROLE_SOFTAP_LOCAL_ONLY);
     }
 
     private boolean hasAnyModeManager() {
-        return !mActiveModeManagers.isEmpty();
-    }
-
-    /**
-     * @return true if any mode managers in specified role.
-     */
-    private boolean hasAnyModeManagerInRole(@ActiveModeManager.Role int role) {
-        for (ActiveModeManager manager : mActiveModeManagers) {
-            if (manager.getRole() == role) return true;
-        }
-        return false;
-    }
-
-    /**
-     * @return true if any mode managers in one of the specified roles.
-     */
-    private boolean hasAnyModeManagerInOneOfRoles(List<Integer> rolesList) {
-        for (ActiveModeManager manager : mActiveModeManagers) {
-            if (rolesList.contains(manager.getRole())) return true;
-        }
-        return false;
+        return !mClientModeManagers.isEmpty() || !mSoftApManagers.isEmpty();
     }
 
     private boolean hasAnyClientModeManager() {
-        return hasAnyModeManagerInOneOfRoles(ActiveModeManager.CLIENT_ROLES);
+        return !mClientModeManagers.isEmpty();
     }
 
     private boolean hasAnyClientModeManagerInConnectivityRole() {
-        return hasAnyModeManagerInOneOfRoles(ActiveModeManager.CLIENT_CONNECTIVITY_ROLES);
+        for (ConcreteClientModeManager manager : mClientModeManagers) {
+            if (manager.getRole() instanceof ClientConnectivityRole) return true;
+        }
+        return false;
     }
 
     private boolean hasAnySoftApManager() {
-        return hasAnyModeManagerInOneOfRoles(ActiveModeManager.SOFTAP_ROLES);
-    }
-
-    /**
-     * @return true if any mode manager is stopping
-     */
-    private boolean hasAnyModeManagerStopping() {
-        for (ActiveModeManager manager : mActiveModeManagers) {
-            if (manager.isStopping()) return true;
-        }
-        return false;
+        return !mSoftApManagers.isEmpty();
     }
 
     /**
@@ -297,19 +855,48 @@
      * role.
      */
     private boolean areAllClientModeManagersInScanOnlyRole() {
-        boolean hasAnyClientModeManager = false;
-        for (ActiveModeManager manager : mActiveModeManagers) {
-            if (ActiveModeManager.CLIENT_ROLES.contains(manager.getRole())) {
-                hasAnyClientModeManager = true;
-                if (manager.getRole() != ActiveModeManager.ROLE_CLIENT_SCAN_ONLY) return false;
-            }
+        if (mClientModeManagers.isEmpty()) return false;
+        for (ConcreteClientModeManager manager : mClientModeManagers) {
+            if (manager.getRole() != ROLE_CLIENT_SCAN_ONLY) return false;
         }
-        return hasAnyClientModeManager;
+        return true;
     }
 
-    private @ActiveModeManager.Role int getRoleForSoftApIpMode(int ipMode) {
+    /** Get any client mode manager in the given role, or null if none was found. */
+    @Nullable
+    public ConcreteClientModeManager getClientModeManagerInRole(ClientRole role) {
+        for (ConcreteClientModeManager manager : mClientModeManagers) {
+            if (manager.getRole() == role) return manager;
+        }
+        return null;
+    }
+
+    /** Get all client mode managers in the specified roles. */
+    @NonNull
+    public List<ConcreteClientModeManager> getClientModeManagersInRoles(ClientRole... roles) {
+        Set<ClientRole> rolesSet = Set.of(roles);
+        List<ConcreteClientModeManager> result = new ArrayList<>();
+        for (ConcreteClientModeManager manager : mClientModeManagers) {
+            ClientRole role = manager.getRole();
+            if (role != null && rolesSet.contains(role)) {
+                result.add(manager);
+            }
+        }
+        return result;
+    }
+
+    @Nullable
+    private SoftApManager getSoftApManagerInRole(SoftApRole role) {
+        for (SoftApManager manager : mSoftApManagers) {
+            if (manager.getRole() == role) return manager;
+        }
+        return null;
+    }
+
+    private SoftApRole getRoleForSoftApIpMode(int ipMode) {
         return ipMode == IFACE_IP_MODE_TETHERED
-                ? ActiveModeManager.ROLE_SOFTAP_TETHERED : ActiveModeManager.ROLE_SOFTAP_LOCAL_ONLY;
+                ? ROLE_SOFTAP_TETHERED
+                : ActiveModeManager.ROLE_SOFTAP_LOCAL_ONLY;
     }
 
     /**
@@ -321,22 +908,19 @@
      *
      * @param softApConfig SoftApModeConfiguration for the hostapd softap
      */
-    private void startSoftApModeManager(@NonNull SoftApModeConfiguration softApConfig) {
-        Log.d(TAG, "Starting SoftApModeManager config = "
-                + softApConfig.getSoftApConfiguration());
+    private void startSoftApModeManager(
+            @NonNull SoftApModeConfiguration softApConfig, @NonNull WorkSource requestorWs) {
+        Log.d(TAG, "Starting SoftApModeManager config = " + softApConfig.getSoftApConfiguration());
         Preconditions.checkState(softApConfig.getTargetMode() == IFACE_IP_MODE_LOCAL_ONLY
                 || softApConfig.getTargetMode() == IFACE_IP_MODE_TETHERED);
 
-        WifiManager.SoftApCallback callback =
+        WifiServiceImpl.SoftApCallbackInternal callback =
                 softApConfig.getTargetMode() == IFACE_IP_MODE_LOCAL_ONLY
                         ? mLohsCallback : mSoftApCallback;
-        SoftApListener listener = new SoftApListener();
-        ActiveModeManager manager =
-                mWifiInjector.makeSoftApManager(listener, callback, softApConfig);
-        listener.setActiveModeManager(manager);
-        manager.start();
-        manager.setRole(getRoleForSoftApIpMode(softApConfig.getTargetMode()));
-        mActiveModeManagers.add(manager);
+        SoftApManager manager = mWifiInjector.makeSoftApManager(
+                new SoftApListener(), callback, softApConfig, requestorWs,
+                getRoleForSoftApIpMode(softApConfig.getTargetMode()), mVerboseLoggingEnabled);
+        mSoftApManagers.add(manager);
     }
 
     /**
@@ -351,10 +935,7 @@
      */
     private void stopSoftApModeManagers(int ipMode) {
         Log.d(TAG, "Shutting down all softap mode managers in mode " + ipMode);
-        for (ActiveModeManager manager : mActiveModeManagers) {
-            if (!(manager instanceof SoftApManager)) continue;
-            SoftApManager softApManager = (SoftApManager) manager;
-
+        for (SoftApManager softApManager : mSoftApManagers) {
             if (ipMode == WifiManager.IFACE_IP_MODE_UNSPECIFIED
                     || getRoleForSoftApIpMode(ipMode) == softApManager.getRole()) {
                 softApManager.stop();
@@ -363,35 +944,53 @@
     }
 
     private void updateCapabilityToSoftApModeManager(SoftApCapability capability) {
-        for (ActiveModeManager manager : mActiveModeManagers) {
-            if (!(manager instanceof SoftApManager)) continue;
-            SoftApManager softApManager = (SoftApManager) manager;
+        for (SoftApManager softApManager : mSoftApManagers) {
             softApManager.updateCapability(capability);
         }
     }
 
     private void updateConfigurationToSoftApModeManager(SoftApConfiguration config) {
-        for (ActiveModeManager manager : mActiveModeManagers) {
-            if (!(manager instanceof SoftApManager)) continue;
-            SoftApManager softApManager = (SoftApManager) manager;
+        for (SoftApManager softApManager : mSoftApManagers) {
             softApManager.updateConfiguration(config);
         }
     }
 
     /**
-     * Method to enable a new client mode manager.
+     * Method to enable a new primary client mode manager in scan only mode.
      */
-    private boolean startClientModeManager() {
-        Log.d(TAG, "Starting ClientModeManager");
-        ClientListener listener = new ClientListener();
-        ClientModeManager manager = mWifiInjector.makeClientModeManager(listener);
-        listener.setActiveModeManager(manager);
-        manager.start();
-        if (!switchClientModeManagerRole(manager)) {
+    private boolean startScanOnlyClientModeManager(WorkSource requestorWs) {
+        Log.d(TAG, "Starting primary ClientModeManager in scan only mode");
+        ConcreteClientModeManager manager = mWifiInjector.makeClientModeManager(
+                new ClientListener(), requestorWs, ROLE_CLIENT_SCAN_ONLY, mVerboseLoggingEnabled);
+        mClientModeManagers.add(manager);
+        mLastScanOnlyClientModeManagerRequestorWs = requestorWs;
+        return true;
+    }
+
+    /**
+     * Method to enable a new primary client mode manager in connect mode.
+     */
+    private boolean startPrimaryClientModeManager(WorkSource requestorWs) {
+        Log.d(TAG, "Starting primary ClientModeManager in connect mode");
+        ConcreteClientModeManager manager = mWifiInjector.makeClientModeManager(
+                new ClientListener(), requestorWs, ROLE_CLIENT_PRIMARY, mVerboseLoggingEnabled);
+        mClientModeManagers.add(manager);
+        mLastPrimaryClientModeManagerRequestorWs = requestorWs;
+        return true;
+    }
+
+    /**
+     * Method to enable a new primary client mode manager.
+     */
+    private boolean startPrimaryOrScanOnlyClientModeManager(WorkSource requestorWs) {
+        ActiveModeManager.ClientRole role = getRoleForPrimaryOrScanOnlyClientModeManager();
+        if (role == ROLE_CLIENT_PRIMARY) {
+            return startPrimaryClientModeManager(requestorWs);
+        } else if (role == ROLE_CLIENT_SCAN_ONLY) {
+            return startScanOnlyClientModeManager(requestorWs);
+        } else {
             return false;
         }
-        mActiveModeManagers.add(manager);
-        return true;
     }
 
     /**
@@ -399,52 +998,117 @@
      */
     private void stopAllClientModeManagers() {
         Log.d(TAG, "Shutting down all client mode managers");
-        for (ActiveModeManager manager : mActiveModeManagers) {
-            if (!(manager instanceof ClientModeManager)) continue;
-            ClientModeManager clientModeManager = (ClientModeManager) manager;
+        for (ConcreteClientModeManager clientModeManager : mClientModeManagers) {
             clientModeManager.stop();
         }
     }
 
     /**
+     * Method to switch all primary client mode manager mode of operation to ScanOnly mode.
+     */
+    private void switchAllPrimaryClientModeManagersToScanOnlyMode(@NonNull WorkSource requestorWs) {
+        Log.d(TAG, "Switching all primary client mode managers to scan only mode");
+        for (ConcreteClientModeManager clientModeManager : mClientModeManagers) {
+            if (clientModeManager.getRole() != ROLE_CLIENT_PRIMARY) {
+                continue;
+            }
+            clientModeManager.setRole(ROLE_CLIENT_SCAN_ONLY, requestorWs);
+        }
+    }
+
+    /**
      * Method to switch all client mode manager mode of operation (from ScanOnly To Connect &
      * vice-versa) based on the toggle state.
      */
-    private boolean switchAllClientModeManagers() {
+    private boolean switchAllPrimaryOrScanOnlyClientModeManagers() {
         Log.d(TAG, "Switching all client mode managers");
-        for (ActiveModeManager manager : mActiveModeManagers) {
-            if (!(manager instanceof ClientModeManager)) continue;
-            ClientModeManager clientModeManager = (ClientModeManager) manager;
-            if (!switchClientModeManagerRole(clientModeManager)) {
+        for (ConcreteClientModeManager clientModeManager : mClientModeManagers) {
+            if (clientModeManager.getRole() != ROLE_CLIENT_PRIMARY
+                    && clientModeManager.getRole() != ROLE_CLIENT_SCAN_ONLY) {
+                continue;
+            }
+            if (!switchPrimaryOrScanOnlyClientModeManagerRole(clientModeManager)) {
                 return false;
             }
         }
-        updateBatteryStats();
         return true;
     }
 
+    private ActiveModeManager.ClientRole getRoleForPrimaryOrScanOnlyClientModeManager() {
+        if (mSettingsStore.isWifiToggleEnabled()) {
+            return ROLE_CLIENT_PRIMARY;
+        } else if (mWifiController.shouldEnableScanOnlyMode()) {
+            return ROLE_CLIENT_SCAN_ONLY;
+        } else {
+            Log.e(TAG, "Something is wrong, no client mode toggles enabled");
+            return null;
+        }
+    }
+
     /**
      * Method to switch a client mode manager mode of operation (from ScanOnly To Connect &
      * vice-versa) based on the toggle state.
      */
-    private boolean switchClientModeManagerRole(@NonNull ClientModeManager modeManager) {
-        if (mSettingsStore.isWifiToggleEnabled()) {
-            modeManager.setRole(ActiveModeManager.ROLE_CLIENT_PRIMARY);
-        } else if (checkScanOnlyModeAvailable()) {
-            modeManager.setRole(ActiveModeManager.ROLE_CLIENT_SCAN_ONLY);
+    private boolean switchPrimaryOrScanOnlyClientModeManagerRole(
+            @NonNull ConcreteClientModeManager modeManager) {
+        ActiveModeManager.ClientRole role = getRoleForPrimaryOrScanOnlyClientModeManager();
+        final WorkSource lastRequestorWs;
+        if (role == ROLE_CLIENT_PRIMARY) {
+            lastRequestorWs = mLastPrimaryClientModeManagerRequestorWs;
+        } else if (role == ROLE_CLIENT_SCAN_ONLY) {
+            lastRequestorWs = mLastScanOnlyClientModeManagerRequestorWs;
         } else {
-            Log.e(TAG, "Something is wrong, no client mode toggles enabled");
             return false;
         }
+        modeManager.setRole(role, lastRequestorWs);
         return true;
     }
 
     /**
+     * Method to start a new client mode manager.
+     */
+    private boolean startAdditionalClientModeManager(
+            ClientConnectivityRole role,
+            @NonNull ExternalClientModeManagerRequestListener externalRequestListener,
+            @NonNull WorkSource requestorWs) {
+        Log.d(TAG, "Starting additional ClientModeManager in role: " + role);
+        ClientListener listener = new ClientListener(externalRequestListener);
+        ConcreteClientModeManager manager = mWifiInjector.makeClientModeManager(
+                listener, requestorWs, role, mVerboseLoggingEnabled);
+        mClientModeManagers.add(manager);
+        return true;
+    }
+
+    /**
+     * Method to switch role for an existing non-primary client mode manager.
+     */
+    private boolean switchRoleForAdditionalClientModeManager(
+            @NonNull ConcreteClientModeManager manager,
+            @NonNull ClientConnectivityRole role,
+            @NonNull ExternalClientModeManagerRequestListener externalRequestListener,
+            @NonNull WorkSource requestorWs) {
+        Log.d(TAG, "Switching role for additional ClientModeManager to role: " + role);
+        ClientListener listener = new ClientListener(externalRequestListener);
+        manager.setRole(role, requestorWs, listener);
+        return true;
+    }
+
+    /**
+     * Method to stop client mode manger.
+     */
+    private void stopAdditionalClientModeManager(ClientModeManager clientModeManager) {
+        if (clientModeManager.getRole() == ROLE_CLIENT_PRIMARY
+                || clientModeManager.getRole() == ROLE_CLIENT_SCAN_ONLY) return;
+        Log.d(TAG, "Shutting down additional client mode manager: " + clientModeManager);
+        clientModeManager.stop();
+    }
+
+    /**
      * Method to stop all active modes, for example, when toggling airplane mode.
      */
     private void shutdownWifi() {
         Log.d(TAG, "Shutting down all mode managers");
-        for (ActiveModeManager manager : mActiveModeManagers) {
+        for (ActiveModeManager manager : getActiveModeManagers()) {
             manager.stop();
         }
     }
@@ -457,11 +1121,27 @@
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("Dump of " + TAG);
         pw.println("Current wifi mode: " + getCurrentMode());
-        pw.println("NumActiveModeManagers: " + mActiveModeManagers.size());
+        pw.println("NumActiveModeManagers: " + getActiveModeManagerCount());
         mWifiController.dump(fd, pw, args);
-        for (ActiveModeManager manager : mActiveModeManagers) {
+        for (ActiveModeManager manager : getActiveModeManagers()) {
             manager.dump(fd, pw, args);
         }
+        mGraveyard.dump(fd, pw, args);
+        boolean isStaStaConcurrencySupported = mWifiNative.isStaStaConcurrencySupported();
+        pw.println("STA + STA Concurrency Supported: " + isStaStaConcurrencySupported);
+        if (isStaStaConcurrencySupported) {
+            pw.println("   MBB use-case enabled: "
+                    + mContext.getResources().getBoolean(
+                            R.bool.config_wifiMultiStaNetworkSwitchingMakeBeforeBreakEnabled));
+            pw.println("   Local only use-case enabled: "
+                    + mContext.getResources().getBoolean(
+                            R.bool.config_wifiMultiStaLocalOnlyConcurrencyEnabled));
+            pw.println("   Restricted use-case enabled: "
+                    + mContext.getResources().getBoolean(
+                            R.bool.config_wifiMultiStaRestrictedConcurrencyEnabled));
+        }
+        pw.println("STA + AP Concurrency Supported: " + isStaApConcurrencySupported());
+        mWifiInjector.getHalDeviceManager().dump(fd, pw, args);
     }
 
     @VisibleForTesting
@@ -472,7 +1152,14 @@
 
     @VisibleForTesting
     Collection<ActiveModeManager> getActiveModeManagers() {
-        return new ArraySet<>(mActiveModeManagers);
+        ArrayList<ActiveModeManager> activeModeManagers = new ArrayList<>();
+        activeModeManagers.addAll(mClientModeManagers);
+        activeModeManagers.addAll(mSoftApManagers);
+        return activeModeManagers;
+    }
+
+    private int getActiveModeManagerCount() {
+        return mSoftApManagers.size() + mClientModeManagers.size();
     }
 
     @VisibleForTesting
@@ -481,21 +1168,6 @@
         return ((WifiController.BaseState) state).isInEmergencyMode();
     }
 
-    /**
-     *  Helper class to wrap the ActiveModeManager callback objects.
-     */
-    private static class ModeCallback {
-        private ActiveModeManager mActiveManager;
-
-        void setActiveModeManager(ActiveModeManager manager) {
-            mActiveManager = manager;
-        }
-
-        ActiveModeManager getActiveModeManager() {
-            return mActiveManager;
-        }
-    }
-
     private void updateBatteryStats() {
         updateBatteryStatsWifiState(hasAnyModeManager());
         if (areAllClientModeManagersInScanOnlyRole()) {
@@ -503,47 +1175,177 @@
         }
     }
 
-    private class SoftApListener extends ModeCallback implements ActiveModeManager.Listener {
+    private class SoftApListener implements ActiveModeManager.Listener<SoftApManager> {
         @Override
-        public void onStarted() {
+        public void onStarted(SoftApManager softApManager) {
             updateBatteryStats();
+            invokeOnAddedCallbacks(softApManager);
         }
 
         @Override
-        public void onStopped() {
-            mActiveModeManagers.remove(getActiveModeManager());
+        public void onRoleChanged(SoftApManager softApManager) {
+            Log.w(TAG, "Role switched received on SoftApManager unexpectedly");
+        }
+
+        @Override
+        public void onStopped(SoftApManager softApManager) {
+            mSoftApManagers.remove(softApManager);
+            mGraveyard.inter(softApManager);
             updateBatteryStats();
             mWifiController.sendMessage(WifiController.CMD_AP_STOPPED);
+            invokeOnRemovedCallbacks(softApManager);
         }
 
         @Override
-        public void onStartFailure() {
-            mActiveModeManagers.remove(getActiveModeManager());
+        public void onStartFailure(SoftApManager softApManager) {
+            mSoftApManagers.remove(softApManager);
+            mGraveyard.inter(softApManager);
             updateBatteryStats();
             mWifiController.sendMessage(WifiController.CMD_AP_START_FAILURE);
+            // onStartFailure can be called when switching between roles. So, remove
+            // update listeners.
+            Log.e(TAG, "SoftApManager start failed!" + softApManager);
+            invokeOnRemovedCallbacks(softApManager);
         }
     }
 
-    private class ClientListener extends ModeCallback implements ActiveModeManager.Listener {
-        @Override
-        public void onStarted() {
+    private class ClientListener implements ActiveModeManager.Listener<ConcreteClientModeManager> {
+        @Nullable
+        private ExternalClientModeManagerRequestListener mExternalRequestListener; // one shot
+
+        ClientListener() {
+            this(null);
+        }
+
+        ClientListener(
+                @Nullable ExternalClientModeManagerRequestListener externalRequestListener) {
+            mExternalRequestListener = externalRequestListener;
+        }
+
+        @WifiNative.MultiStaUseCase
+        private int getMultiStatUseCase() {
+            // Note: The use-case setting finds the first non-primary client mode manager to set
+            // the use-case to HAL. This does not extend to 3 STA concurrency when there are
+            // 2 secondary STA client mode managers.
+            for (ClientModeManager cmm : getClientModeManagers()) {
+                ClientRole clientRole = cmm.getRole();
+                if (clientRole == ROLE_CLIENT_LOCAL_ONLY
+                        || clientRole == ROLE_CLIENT_SECONDARY_LONG_LIVED) {
+                    return WifiNative.DUAL_STA_NON_TRANSIENT_UNBIASED;
+                } else if (clientRole == ROLE_CLIENT_SECONDARY_TRANSIENT) {
+                    return WifiNative.DUAL_STA_TRANSIENT_PREFER_PRIMARY;
+                }
+            }
+            // if single STA, a safe default is PREFER_PRIMARY
+            return WifiNative.DUAL_STA_TRANSIENT_PREFER_PRIMARY;
+        }
+
+        /**
+         * Hardware needs to be configured for STA + STA before sending the callbacks to clients
+         * letting them know that CM is ready for use.
+         */
+        private void configureHwForMultiStaIfNecessary() {
+            mWifiNative.setMultiStaUseCase(getMultiStatUseCase());
+            String primaryIfaceName = getPrimaryClientModeManager().getInterfaceName();
+            // if no primary exists (occurs briefly during Make Before Break), don't update the
+            // primary and keep the previous primary. Only update WifiNative when the new primary is
+            // activated.
+            if (primaryIfaceName != null) {
+                mWifiNative.setMultiStaPrimaryConnection(primaryIfaceName);
+            }
+        }
+
+        private void onStartedOrRoleChanged(ConcreteClientModeManager clientModeManager) {
             updateClientScanMode();
             updateBatteryStats();
+            configureHwForMultiStaIfNecessary();
+            if (mExternalRequestListener != null) {
+                mExternalRequestListener.onAnswer(clientModeManager);
+                mExternalRequestListener = null; // reset after one shot.
+            }
+
+            // Report to SarManager
+            reportWifiStateToSarManager();
+        }
+
+        private void reportWifiStateToSarManager() {
+            if (areAllClientModeManagersInScanOnlyRole()) {
+                // Inform sar manager that scan only is being enabled
+                mWifiInjector.getSarManager().setScanOnlyWifiState(WifiManager.WIFI_STATE_ENABLED);
+            } else {
+                // Inform sar manager that scan only is being disabled
+                mWifiInjector.getSarManager().setScanOnlyWifiState(WifiManager.WIFI_STATE_DISABLED);
+            }
+            if (hasAnyClientModeManagerInConnectivityRole()) {
+                // Inform sar manager that wifi is Enabled
+                mWifiInjector.getSarManager().setClientWifiState(WifiManager.WIFI_STATE_ENABLED);
+            } else {
+                // Inform sar manager that wifi is being disabled
+                mWifiInjector.getSarManager().setClientWifiState(WifiManager.WIFI_STATE_DISABLED);
+            }
+        }
+
+        private void onPrimaryChangedDueToStartedOrRoleChanged(
+                ConcreteClientModeManager clientModeManager) {
+            if (clientModeManager.getRole() != ROLE_CLIENT_PRIMARY
+                    && clientModeManager == mLastPrimaryClientModeManager) {
+                // CMM was primary, but is no longer primary
+                invokeOnPrimaryClientModeManagerChangedCallbacks(clientModeManager, null);
+                mLastPrimaryClientModeManager = null;
+            } else if (clientModeManager.getRole() == ROLE_CLIENT_PRIMARY
+                    && clientModeManager != mLastPrimaryClientModeManager) {
+                // CMM is primary, but wasn't primary before
+                invokeOnPrimaryClientModeManagerChangedCallbacks(
+                        mLastPrimaryClientModeManager, clientModeManager);
+                mLastPrimaryClientModeManager = clientModeManager;
+            }
         }
 
         @Override
-        public void onStopped() {
-            mActiveModeManagers.remove(getActiveModeManager());
+        public void onStarted(@NonNull ConcreteClientModeManager clientModeManager) {
+            onStartedOrRoleChanged(clientModeManager);
+            invokeOnAddedCallbacks(clientModeManager);
+            // invoke "added" callbacks before primary changed
+            onPrimaryChangedDueToStartedOrRoleChanged(clientModeManager);
+        }
+
+        @Override
+        public void onRoleChanged(@NonNull ConcreteClientModeManager clientModeManager) {
+            onStartedOrRoleChanged(clientModeManager);
+            invokeOnRoleChangedCallbacks(clientModeManager);
+            onPrimaryChangedDueToStartedOrRoleChanged(clientModeManager);
+        }
+
+        private void onStoppedOrStartFailure(ConcreteClientModeManager clientModeManager) {
+            mClientModeManagers.remove(clientModeManager);
+            mGraveyard.inter(clientModeManager);
             updateClientScanMode();
             updateBatteryStats();
+            if (clientModeManager == mLastPrimaryClientModeManager) {
+                // CMM was primary, but was stopped
+                invokeOnPrimaryClientModeManagerChangedCallbacks(
+                        mLastPrimaryClientModeManager, null);
+                mLastPrimaryClientModeManager = null;
+            }
+            // invoke "removed" callbacks after primary changed
+            invokeOnRemovedCallbacks(clientModeManager);
+
+            // Report to SarManager
+            reportWifiStateToSarManager();
+        }
+
+        @Override
+        public void onStopped(@NonNull ConcreteClientModeManager clientModeManager) {
+            onStoppedOrStartFailure(clientModeManager);
             mWifiController.sendMessage(WifiController.CMD_STA_STOPPED);
         }
 
         @Override
-        public void onStartFailure() {
-            mActiveModeManagers.remove(getActiveModeManager());
-            updateClientScanMode();
-            updateBatteryStats();
+        public void onStartFailure(@NonNull ConcreteClientModeManager clientModeManager) {
+            Log.e(TAG, "ClientModeManager start failed!" + clientModeManager);
+            // onStartFailure can be called when switching between roles. So, remove
+            // update listeners.
+            onStoppedOrStartFailure(clientModeManager);
             mWifiController.sendMessage(WifiController.CMD_STA_START_FAILURE);
         }
     }
@@ -568,12 +1370,12 @@
      */
     private void updateBatteryStatsWifiState(boolean enabled) {
         if (enabled) {
-            if (mActiveModeManagers.size() == 1) {
+            if (getActiveModeManagerCount() == 1) {
                 // only report wifi on if we haven't already
                 mBatteryStatsManager.reportWifiOn();
             }
         } else {
-            if (mActiveModeManagers.size() == 0) {
+            if (getActiveModeManagerCount() == 0) {
                 // only report if we don't have any active modes
                 mBatteryStatsManager.reportWifiOff();
             }
@@ -584,9 +1386,13 @@
         mBatteryStatsManager.reportWifiState(BatteryStatsManager.WIFI_STATE_OFF_SCANNING, null);
     }
 
-    private boolean checkScanOnlyModeAvailable() {
-        return mWifiPermissionsUtil.isLocationModeEnabled()
-                && mSettingsStore.isScanAlwaysAvailable();
+    /**
+     * Called to pull metrics from ActiveModeWarden to WifiMetrics when a dump is triggered, as
+     * opposed to the more common push metrics which are reported to WifiMetrics as soon as they
+     * occur.
+     */
+    public void updateMetrics() {
+        mWifiMetrics.setIsMakeBeforeBreakSupported(isStaStaConcurrencySupportedForMbb());
     }
 
     /**
@@ -602,6 +1408,7 @@
         private static final int BASE = Protocol.BASE_WIFI_CONTROLLER;
 
         static final int CMD_EMERGENCY_MODE_CHANGED                 = BASE + 1;
+        static final int CMD_EMERGENCY_SCAN_STATE_CHANGED           = BASE + 2;
         static final int CMD_SCAN_ALWAYS_MODE_CHANGED               = BASE + 7;
         static final int CMD_WIFI_TOGGLED                           = BASE + 8;
         static final int CMD_AIRPLANE_TOGGLED                       = BASE + 9;
@@ -615,18 +1422,21 @@
         private static final int CMD_RECOVERY_RESTART_WIFI_CONTINUE = BASE + 18;
         // Command to disable wifi when SelfRecovery is throttled or otherwise not doing full
         // recovery
-        static final int CMD_RECOVERY_DISABLE_WIFI                  = BASE + 19;
-        static final int CMD_STA_STOPPED                            = BASE + 20;
-        static final int CMD_DEFERRED_RECOVERY_RESTART_WIFI         = BASE + 22;
-        static final int CMD_AP_START_FAILURE                       = BASE + 23;
-        static final int CMD_UPDATE_AP_CAPABILITY                   = BASE + 24;
-        static final int CMD_UPDATE_AP_CONFIG                       = BASE + 25;
+        static final int CMD_RECOVERY_DISABLE_WIFI                   = BASE + 19;
+        static final int CMD_STA_STOPPED                             = BASE + 20;
+        static final int CMD_DEFERRED_RECOVERY_RESTART_WIFI          = BASE + 22;
+        static final int CMD_AP_START_FAILURE                        = BASE + 23;
+        static final int CMD_UPDATE_AP_CAPABILITY                    = BASE + 24;
+        static final int CMD_UPDATE_AP_CONFIG                        = BASE + 25;
+        static final int CMD_REQUEST_ADDITIONAL_CLIENT_MODE_MANAGER  = BASE + 26;
+        static final int CMD_REMOVE_ADDITIONAL_CLIENT_MODE_MANAGER   = BASE + 27;
 
         private final EnabledState mEnabledState = new EnabledState();
         private final DisabledState mDisabledState = new DisabledState();
 
         private boolean mIsInEmergencyCall = false;
         private boolean mIsInEmergencyCallbackMode = false;
+        private boolean mIsEmergencyScanInProgress = false;
 
         WifiController() {
             super(TAG, mLooper);
@@ -643,6 +1453,52 @@
         }
 
         @Override
+        protected String getWhatToString(int what) {
+            switch (what) {
+                case CMD_AIRPLANE_TOGGLED:
+                    return "CMD_AIRPLANE_TOGGLED";
+                case CMD_AP_START_FAILURE:
+                    return "CMD_AP_START_FAILURE";
+                case CMD_AP_STOPPED:
+                    return "CMD_AP_STOPPED";
+                case CMD_DEFERRED_RECOVERY_RESTART_WIFI:
+                    return "CMD_DEFERRED_RECOVERY_RESTART_WIFI";
+                case CMD_EMERGENCY_CALL_STATE_CHANGED:
+                    return "CMD_EMERGENCY_CALL_STATE_CHANGED";
+                case CMD_EMERGENCY_MODE_CHANGED:
+                    return "CMD_EMERGENCY_MODE_CHANGED";
+                case CMD_RECOVERY_DISABLE_WIFI:
+                    return "CMD_RECOVERY_DISABLE_WIFI";
+                case CMD_RECOVERY_RESTART_WIFI:
+                    return "CMD_RECOVERY_RESTART_WIFI";
+                case CMD_RECOVERY_RESTART_WIFI_CONTINUE:
+                    return "CMD_RECOVERY_RESTART_WIFI_CONTINUE";
+                case CMD_REMOVE_ADDITIONAL_CLIENT_MODE_MANAGER:
+                    return "CMD_REMOVE_ADDITIONAL_CLIENT_MODE_MANAGER";
+                case CMD_REQUEST_ADDITIONAL_CLIENT_MODE_MANAGER:
+                    return "CMD_REQUEST_ADDITIONAL_CLIENT_MODE_MANAGER";
+                case CMD_EMERGENCY_SCAN_STATE_CHANGED:
+                    return "CMD_EMERGENCY_SCAN_STATE_CHANGED";
+                case CMD_SCAN_ALWAYS_MODE_CHANGED:
+                    return "CMD_SCAN_ALWAYS_MODE_CHANGED";
+                case CMD_SET_AP:
+                    return "CMD_SET_AP";
+                case CMD_STA_START_FAILURE:
+                    return "CMD_STA_START_FAILURE";
+                case CMD_STA_STOPPED:
+                    return "CMD_STA_STOPPED";
+                case CMD_UPDATE_AP_CAPABILITY:
+                    return "CMD_UPDATE_AP_CAPABILITY";
+                case CMD_UPDATE_AP_CONFIG:
+                    return "CMD_UPDATE_AP_CONFIG";
+                case CMD_WIFI_TOGGLED:
+                    return "CMD_WIFI_TOGGLED";
+                default:
+                    return "what:" + what;
+            }
+        }
+
+        @Override
         public void start() {
             boolean isAirplaneModeOn = mSettingsStore.isAirplaneModeOn();
             boolean isWifiEnabled = mSettingsStore.isWifiToggleEnabled();
@@ -654,20 +1510,24 @@
                     + ", isScanningAvailable = " + isScanningAlwaysAvailable
                     + ", isLocationModeActive = " + isLocationModeActive);
 
-            if (shouldEnableSta()) {
-                startClientModeManager();
+            // Initialize these values at bootup to defaults, will be overridden by API calls
+            // for further toggles.
+            mLastPrimaryClientModeManagerRequestorWs = mFacade.getSettingsWorkSource(mContext);
+            mLastScanOnlyClientModeManagerRequestorWs = INTERNAL_REQUESTOR_WS;
+            ActiveModeManager.ClientRole role = getRoleForPrimaryOrScanOnlyClientModeManager();
+            if (role == ROLE_CLIENT_PRIMARY) {
+                startPrimaryClientModeManager(mLastPrimaryClientModeManagerRequestorWs);
+                setInitialState(mEnabledState);
+            } else if (role == ROLE_CLIENT_SCAN_ONLY) {
+                startScanOnlyClientModeManager(mLastScanOnlyClientModeManagerRequestorWs);
                 setInitialState(mEnabledState);
             } else {
                 setInitialState(mDisabledState);
             }
-            mContext.registerReceiver(new BroadcastReceiver() {
-                @Override
-                public void onReceive(Context context, Intent intent) {
-                    // Location mode has been toggled...  trigger with the scan change
-                    // update to make sure we are in the correct mode
-                    scanAlwaysModeChanged();
-                }
-            }, new IntentFilter(LocationManager.MODE_CHANGED_ACTION));
+            mWifiMetrics.noteWifiEnabledDuringBoot(mSettingsStore.isWifiToggleEnabled());
+
+            // Initialize the lower layers before we start.
+            mWifiNative.initialize();
             super.start();
         }
 
@@ -687,6 +1547,11 @@
                 return mIsInEmergencyCall || mIsInEmergencyCallbackMode;
             }
 
+            /** Device is in emergency mode & carrier config requires wifi off in emergency mode */
+            private boolean isInEmergencyModeWhichRequiresWifiDisable() {
+                return isInEmergencyMode() && mFacade.getConfigWiFiDisableInECBM(mContext);
+            }
+
             private void updateEmergencyMode(Message msg) {
                 if (msg.what == CMD_EMERGENCY_CALL_STATE_CHANGED) {
                     mIsInEmergencyCall = msg.arg1 == 1;
@@ -698,18 +1563,105 @@
             private void enterEmergencyMode() {
                 stopSoftApModeManagers(WifiManager.IFACE_IP_MODE_UNSPECIFIED);
                 boolean configWiFiDisableInECBM = mFacade.getConfigWiFiDisableInECBM(mContext);
-                log("WifiController msg getConfigWiFiDisableInECBM " + configWiFiDisableInECBM);
-                if (configWiFiDisableInECBM) {
-                    shutdownWifi();
+                log("Entering emergency callback mode, "
+                        + "CarrierConfigManager.KEY_CONFIG_WIFI_DISABLE_IN_ECBM: "
+                        + configWiFiDisableInECBM);
+                if (!mIsEmergencyScanInProgress) {
+                    if (configWiFiDisableInECBM) {
+                        shutdownWifi();
+                    }
+                } else {
+                    if (configWiFiDisableInECBM) {
+                        switchAllPrimaryClientModeManagersToScanOnlyMode(
+                                mFacade.getSettingsWorkSource(mContext));
+                    }
                 }
             }
 
             private void exitEmergencyMode() {
-                if (shouldEnableSta()) {
-                    startClientModeManager();
-                    transitionTo(mEnabledState);
+                log("Exiting emergency callback mode");
+                // may be in DisabledState or EnabledState (depending on whether Wifi was shut down
+                // in enterEmergencyMode() or not based on getConfigWiFiDisableInECBM).
+                // Let CMD_WIFI_TOGGLED handling decide what the next state should be, or if we're
+                // already in the correct state.
+
+                // Assumes user toggled it on from settings before.
+                wifiToggled(mFacade.getSettingsWorkSource(mContext));
+            }
+
+            private boolean processMessageInEmergencyMode(Message msg) {
+                // In emergency mode: Some messages need special handling in this mode,
+                // all others are dropped.
+                switch (msg.what) {
+                    case CMD_STA_STOPPED:
+                    case CMD_AP_STOPPED:
+                        log("Processing message in Emergency Callback Mode: " + msg);
+                        if (!hasAnyModeManager()) {
+                            log("No active mode managers, return to DisabledState.");
+                            transitionTo(mDisabledState);
+                        }
+                        break;
+                    case CMD_SET_AP:
+                        // arg1 == 1 => enable AP
+                        if (msg.arg1 == 1) {
+                            log("AP cannot be started in Emergency Callback Mode: " + msg);
+                            // SoftAP was disabled upon entering emergency mode. It also cannot
+                            // be re-enabled during emergency mode. Drop the message and invoke
+                            // the failure callback.
+                            Pair<SoftApModeConfiguration, WorkSource> softApConfigAndWs =
+                                    (Pair<SoftApModeConfiguration, WorkSource>) msg.obj;
+                            SoftApModeConfiguration softApConfig = softApConfigAndWs.first;
+                            WifiServiceImpl.SoftApCallbackInternal callback =
+                                    softApConfig.getTargetMode() == IFACE_IP_MODE_LOCAL_ONLY
+                                            ? mLohsCallback : mSoftApCallback;
+                            // need to notify SoftApCallback that start/stop AP failed
+                            callback.onStateChanged(WifiManager.WIFI_AP_STATE_FAILED,
+                                    WifiManager.SAP_START_FAILURE_GENERAL);
+                        }
+                        break;
+                    default:
+                        log("Dropping message in emergency callback mode: " + msg);
+                        break;
+
+                }
+                return HANDLED;
+            }
+
+            private void handleEmergencyModeStateChange(Message msg) {
+                boolean wasInEmergencyMode = isInEmergencyMode();
+                updateEmergencyMode(msg);
+                boolean isInEmergencyMode = isInEmergencyMode();
+                if (!wasInEmergencyMode && isInEmergencyMode) {
+                    enterEmergencyMode();
+                } else if (wasInEmergencyMode && !isInEmergencyMode) {
+                    exitEmergencyMode();
+                }
+            }
+
+            private void handleEmergencyScanStateChange(Message msg) {
+                final boolean scanInProgress = msg.arg1 == 1;
+                final WorkSource requestorWs = (WorkSource) msg.obj;
+                log("Processing scan state change: " + scanInProgress);
+                mIsEmergencyScanInProgress = scanInProgress;
+                if (isInEmergencyModeWhichRequiresWifiDisable())  {
+                    // If wifi was disabled because of emergency mode
+                    // (getConfigWiFiDisableInECBM == true), don't use the
+                    // generic method to handle toggle change since that may put wifi in
+                    // connectivity mode (since wifi toggle may actually be on underneath)
+                    if (getCurrentState() == mDisabledState && scanInProgress) {
+                        // go to scan only mode.
+                        startScanOnlyClientModeManager(requestorWs);
+                        transitionTo(mEnabledState);
+                    } else if (getCurrentState() == mEnabledState && !scanInProgress) {
+                        // shut down to go back to previous state.
+                        stopAllClientModeManagers();
+                    }
                 } else {
-                    transitionTo(mDisabledState);
+                    if (getCurrentState() == mDisabledState) {
+                        handleStaToggleChangeInDisabledState(requestorWs);
+                    } else if (getCurrentState() == mEnabledState) {
+                        handleStaToggleChangeInEnabledState(requestorWs);
+                    }
                 }
             }
 
@@ -718,28 +1670,18 @@
                 // potentially enter emergency mode
                 if (msg.what == CMD_EMERGENCY_CALL_STATE_CHANGED
                         || msg.what == CMD_EMERGENCY_MODE_CHANGED) {
-                    boolean wasInEmergencyMode = isInEmergencyMode();
-                    updateEmergencyMode(msg);
-                    boolean isInEmergencyMode = isInEmergencyMode();
-                    if (!wasInEmergencyMode && isInEmergencyMode) {
-                        enterEmergencyMode();
-                    } else if (wasInEmergencyMode && !isInEmergencyMode) {
-                        exitEmergencyMode();
-                    }
+                    handleEmergencyModeStateChange(msg);
+                    return HANDLED;
+                } else if (msg.what == CMD_EMERGENCY_SCAN_STATE_CHANGED) {
+                    // emergency scans need to be allowed even in emergency mode.
+                    handleEmergencyScanStateChange(msg);
                     return HANDLED;
                 } else if (isInEmergencyMode()) {
-                    // already in emergency mode, drop all messages other than mode stop messages
-                    // triggered by emergency mode start.
-                    if (msg.what == CMD_STA_STOPPED || msg.what == CMD_AP_STOPPED) {
-                        if (!hasAnyModeManager()) {
-                            log("No active mode managers, return to DisabledState.");
-                            transitionTo(mDisabledState);
-                        }
-                    }
-                    return HANDLED;
+                    return processMessageInEmergencyMode(msg);
+                } else {
+                    // not in emergency mode, process messages normally
+                    return processMessageFiltered(msg);
                 }
-                // not in emergency mode, process messages normally
-                return processMessageFiltered(msg);
             }
 
             protected abstract boolean processMessageFiltered(Message msg);
@@ -750,6 +1692,7 @@
             public boolean processMessage(Message msg) {
                 switch (msg.what) {
                     case CMD_SCAN_ALWAYS_MODE_CHANGED:
+                    case CMD_EMERGENCY_SCAN_STATE_CHANGED:
                     case CMD_WIFI_TOGGLED:
                     case CMD_STA_STOPPED:
                     case CMD_STA_START_FAILURE:
@@ -758,6 +1701,12 @@
                     case CMD_RECOVERY_RESTART_WIFI:
                     case CMD_RECOVERY_RESTART_WIFI_CONTINUE:
                     case CMD_DEFERRED_RECOVERY_RESTART_WIFI:
+                    case CMD_REMOVE_ADDITIONAL_CLIENT_MODE_MANAGER:
+                        break;
+                    case CMD_REQUEST_ADDITIONAL_CLIENT_MODE_MANAGER:
+                        AdditionalClientModeManagerRequestInfo requestInfo =
+                                (AdditionalClientModeManagerRequestInfo) msg.obj;
+                        requestInfo.listener.onAnswer(null);
                         break;
                     case CMD_RECOVERY_DISABLE_WIFI:
                         log("Recovery has been throttled, disable wifi");
@@ -772,7 +1721,9 @@
                         } else {
                             log("Airplane mode disabled, determine next state");
                             if (shouldEnableSta()) {
-                                startClientModeManager();
+                                startPrimaryOrScanOnlyClientModeManager(
+                                        // Assumes user toggled it on from settings before.
+                                        mFacade.getSettingsWorkSource(mContext));
                                 transitionTo(mEnabledState);
                             }
                             // wifi should remain disabled, do not need to transition
@@ -791,8 +1742,33 @@
             }
         }
 
+        private boolean shouldEnableScanOnlyMode() {
+            return (mWifiPermissionsUtil.isLocationModeEnabled()
+                    && mSettingsStore.isScanAlwaysAvailable())
+                    || mIsEmergencyScanInProgress;
+        }
+
         private boolean shouldEnableSta() {
-            return mSettingsStore.isWifiToggleEnabled() || checkScanOnlyModeAvailable();
+            return mSettingsStore.isWifiToggleEnabled() || shouldEnableScanOnlyMode();
+        }
+
+        private void handleStaToggleChangeInDisabledState(WorkSource requestorWs) {
+            if (shouldEnableSta()) {
+                startPrimaryOrScanOnlyClientModeManager(requestorWs);
+                transitionTo(mEnabledState);
+            }
+        }
+
+        private void handleStaToggleChangeInEnabledState(WorkSource requestorWs) {
+            if (shouldEnableSta()) {
+                if (hasAnyClientModeManager()) {
+                    switchAllPrimaryOrScanOnlyClientModeManagers();
+                } else {
+                    startPrimaryOrScanOnlyClientModeManager(requestorWs);
+                }
+            } else {
+                stopAllClientModeManagers();
+            }
         }
 
         class DisabledState extends BaseState {
@@ -816,31 +1792,63 @@
                 switch (msg.what) {
                     case CMD_WIFI_TOGGLED:
                     case CMD_SCAN_ALWAYS_MODE_CHANGED:
-                        if (shouldEnableSta()) {
-                            startClientModeManager();
-                            transitionTo(mEnabledState);
-                        }
+                        handleStaToggleChangeInDisabledState((WorkSource) msg.obj);
                         break;
                     case CMD_SET_AP:
                         // note: CMD_SET_AP is handled/dropped in ECM mode - will not start here
                         if (msg.arg1 == 1) {
-                            startSoftApModeManager((SoftApModeConfiguration) msg.obj);
+                            Pair<SoftApModeConfiguration, WorkSource> softApConfigAndWs =
+                                    (Pair) msg.obj;
+                            startSoftApModeManager(
+                                    softApConfigAndWs.first, softApConfigAndWs.second);
                             transitionTo(mEnabledState);
                         }
                         break;
                     case CMD_RECOVERY_RESTART_WIFI:
                         log("Recovery triggered, already in disabled state");
-                        // intentional fallthrough
+                        sendMessageDelayed(CMD_RECOVERY_RESTART_WIFI_CONTINUE,
+                                Collections.emptyList(), readWifiRecoveryDelay());
+                        break;
                     case CMD_DEFERRED_RECOVERY_RESTART_WIFI:
                         // wait mRecoveryDelayMillis for letting driver clean reset.
                         sendMessageDelayed(CMD_RECOVERY_RESTART_WIFI_CONTINUE,
-                                readWifiRecoveryDelay());
+                                msg.obj, readWifiRecoveryDelay());
                         break;
                     case CMD_RECOVERY_RESTART_WIFI_CONTINUE:
-                        if (shouldEnableSta()) {
-                            startClientModeManager();
-                            transitionTo(mEnabledState);
+                        log("Recovery in progress, start wifi");
+                        List<ActiveModeManager> modeManagersBeforeRecovery = (List) msg.obj;
+                        // No user controlled mode managers before recovery, so check if wifi
+                        // was toggled on.
+                        if (modeManagersBeforeRecovery.isEmpty()) {
+                            if (shouldEnableSta()) {
+                                startPrimaryOrScanOnlyClientModeManager(
+                                        // Assumes user toggled it on from settings before.
+                                        mFacade.getSettingsWorkSource(mContext));
+                                transitionTo(mEnabledState);
+                            }
+                            break;
                         }
+                        for (ActiveModeManager activeModeManager : modeManagersBeforeRecovery) {
+                            if (activeModeManager instanceof ConcreteClientModeManager) {
+                                startPrimaryOrScanOnlyClientModeManager(
+                                        activeModeManager.getRequestorWs());
+                            } else if (activeModeManager instanceof SoftApManager) {
+                                SoftApManager softApManager = (SoftApManager) activeModeManager;
+                                startSoftApModeManager(
+                                        softApManager.getSoftApModeConfiguration(),
+                                        softApManager.getRequestorWs());
+                            }
+                        }
+                        transitionTo(mEnabledState);
+                        int numCallbacks = mRestartCallbacks.beginBroadcast();
+                        for (int i = 0; i < numCallbacks; i++) {
+                            try {
+                                mRestartCallbacks.getBroadcastItem(i).onSubsystemRestarted();
+                            } catch (RemoteException e) {
+                                Log.e(TAG, "Failure calling onSubsystemRestarted" + e);
+                            }
+                        }
+                        mRestartCallbacks.finishBroadcast();
                         break;
                     default:
                         return NOT_HANDLED;
@@ -867,30 +1875,135 @@
             public void exit() {
                 log("EnabledState.exit()");
                 if (hasAnyModeManager()) {
-                    Log.e(TAG, "Existing EnabledState, but has active mode managers");
+                    Log.e(TAG, "Exiting EnabledState, but has active mode managers");
                 }
                 super.exit();
             }
 
+            private boolean isClientModeManagerConnectedOrConnectingToBssid(
+                    @NonNull ClientModeManager clientModeManager,
+                    @NonNull String ssid, @NonNull String bssid) {
+                WifiConfiguration connectedOrConnectingWifiConfiguration = coalesce(
+                        clientModeManager.getConnectingWifiConfiguration(),
+                        clientModeManager.getConnectedWifiConfiguration());
+                String connectedOrConnectingBssid = coalesce(
+                        clientModeManager.getConnectingBssid(),
+                        clientModeManager.getConnectedBssid());
+                String connectedOrConnectingSsid =
+                        connectedOrConnectingWifiConfiguration == null
+                                ? null : connectedOrConnectingWifiConfiguration.SSID;
+                return Objects.equals(ssid, connectedOrConnectingSsid)
+                        && Objects.equals(bssid, connectedOrConnectingBssid);
+            }
+
+            @Nullable
+            private ConcreteClientModeManager findAnyClientModeManagerConnectingOrConnectedToBssid(
+                    @NonNull String ssid, @Nullable String bssid) {
+                if (bssid == null) {
+                    return null;
+                }
+                for (ConcreteClientModeManager cmm : mClientModeManagers) {
+                    if (isClientModeManagerConnectedOrConnectingToBssid(cmm, ssid, bssid)) {
+                        return cmm;
+                    }
+                }
+                return null;
+            }
+
+            private void handleAdditionalClientModeManagerRequest(
+                    @NonNull AdditionalClientModeManagerRequestInfo requestInfo) {
+                ClientModeManager primaryManager = getPrimaryClientModeManager();
+                if (requestInfo.clientRole == ROLE_CLIENT_SECONDARY_TRANSIENT
+                        && mDppManager.isSessionInProgress()) {
+                    // When MBB is triggered, we could end up switching the primary interface
+                    // after completion. So if we have any DPP session in progress, they will fail
+                    // when the previous primary iface is removed after MBB completion.
+                    Log.v(TAG, "DPP session in progress, fallback to single STA behavior "
+                            + "using primary ClientModeManager=" + primaryManager);
+                    requestInfo.listener.onAnswer(primaryManager);
+                    return;
+                }
+                ConcreteClientModeManager cmmForSameBssid =
+                        findAnyClientModeManagerConnectingOrConnectedToBssid(
+                                requestInfo.ssid, requestInfo.bssid);
+                if (cmmForSameBssid != null) {
+                    // Can't allow 2 client mode managers triggering connection to same bssid.
+                    Log.v(TAG, "Already connected to bssid=" + requestInfo.bssid
+                            + " on ClientModeManager=" + cmmForSameBssid);
+                    if (cmmForSameBssid.getRole() == ROLE_CLIENT_PRIMARY) {
+                        // fallback to single STA behavior.
+                        requestInfo.listener.onAnswer(cmmForSameBssid);
+                        return;
+                    }
+                    // Existing secondary CMM connected to the same ssid/bssid.
+                    if (!canRequestMoreClientModeManagersInRole(
+                            requestInfo.requestorWs, requestInfo.clientRole)) {
+                        Log.e(TAG, "New request cannot override existing request on "
+                                + "ClientModeManager=" + cmmForSameBssid);
+                        // If the new request does not have priority over the existing request,
+                        // reject it since we cannot have 2 CMM's connected to same ssid/bssid.
+                        requestInfo.listener.onAnswer(null);
+                        return;
+                    }
+                    // If the new request has a higher priority over the existing one, change it's
+                    // role and send it to the new client.
+                    // Switch role for non primary CMM & wait for it to complete before
+                    // handing it to the requestor.
+                    switchRoleForAdditionalClientModeManager(
+                            cmmForSameBssid, requestInfo.clientRole, requestInfo.listener,
+                            requestInfo.requestorWs);
+                    return;
+                }
+
+                ClientModeManager cmmForSameRole =
+                        getClientModeManagerInRole(requestInfo.clientRole);
+                if (cmmForSameRole != null) {
+                    // Already have a client mode manager in the requested role.
+                    // Note: This logic results in the framework not supporting more than 1 CMM in
+                    // the same role concurrently. There is no use-case for that currently &
+                    // none of the clients (i.e WifiNetworkFactory, WifiConnectivityManager, etc)
+                    // are ready to support that either. If this assumption changes in the future
+                    // when the device supports 3 STA's for example, change this logic!
+                    Log.v(TAG, "Already exists ClientModeManager for role: " + cmmForSameRole);
+                    requestInfo.listener.onAnswer(cmmForSameRole);
+                    return;
+                }
+                if (canRequestMoreClientModeManagersInRole(
+                        requestInfo.requestorWs, requestInfo.clientRole)) {
+                    // Can create an additional client mode manager.
+                    Log.v(TAG, "Starting a new ClientModeManager");
+                    startAdditionalClientModeManager(
+                            requestInfo.clientRole,
+                            requestInfo.listener, requestInfo.requestorWs);
+                    return;
+                }
+                // Fall back to single STA behavior.
+                Log.v(TAG, "Falling back to single STA behavior using primary ClientModeManager="
+                        + primaryManager);
+                requestInfo.listener.onAnswer(primaryManager);
+            }
+
             @Override
             public boolean processMessageFiltered(Message msg) {
                 switch (msg.what) {
                     case CMD_WIFI_TOGGLED:
                     case CMD_SCAN_ALWAYS_MODE_CHANGED:
-                        if (shouldEnableSta()) {
-                            if (hasAnyClientModeManager()) {
-                                switchAllClientModeManagers();
-                            } else {
-                                startClientModeManager();
-                            }
-                        } else {
-                            stopAllClientModeManagers();
-                        }
+                        handleStaToggleChangeInEnabledState((WorkSource) msg.obj);
+                        break;
+                    case CMD_REQUEST_ADDITIONAL_CLIENT_MODE_MANAGER:
+                        handleAdditionalClientModeManagerRequest(
+                                (AdditionalClientModeManagerRequestInfo) msg.obj);
+                        break;
+                    case CMD_REMOVE_ADDITIONAL_CLIENT_MODE_MANAGER:
+                        stopAdditionalClientModeManager((ClientModeManager) msg.obj);
                         break;
                     case CMD_SET_AP:
                         // note: CMD_SET_AP is handled/dropped in ECM mode - will not start here
                         if (msg.arg1 == 1) {
-                            startSoftApModeManager((SoftApModeConfiguration) msg.obj);
+                            Pair<SoftApModeConfiguration, WorkSource> softApConfigAndWs =
+                                    (Pair) msg.obj;
+                            startSoftApModeManager(
+                                    softApConfigAndWs.first, softApConfigAndWs.second);
                         } else {
                             stopSoftApModeManagers(msg.arg2);
                         }
@@ -921,7 +2034,9 @@
                         if (!hasAnyModeManager()) {
                             if (shouldEnableSta()) {
                                 log("SoftAp disabled, start client mode");
-                                startClientModeManager();
+                                startPrimaryOrScanOnlyClientModeManager(
+                                        // Assumes user toggled it on from settings before.
+                                        mFacade.getSettingsWorkSource(mContext));
                             } else {
                                 log("SoftAp mode disabled, return to DisabledState");
                                 transitionTo(mDisabledState);
@@ -941,24 +2056,43 @@
                             log("STA disabled, remain in EnabledState.");
                         }
                         break;
-                    case CMD_RECOVERY_RESTART_WIFI:
+                    case CMD_RECOVERY_RESTART_WIFI: {
                         final String bugTitle;
-                        final String bugDetail;
-                        if (msg.arg1 < SelfRecovery.REASON_STRINGS.length && msg.arg1 >= 0) {
-                            bugDetail = SelfRecovery.REASON_STRINGS[msg.arg1];
-                            bugTitle = "Wi-Fi BugReport: " + bugDetail;
-                        } else {
-                            bugDetail = "";
+                        final String bugDetail = (String) msg.obj;
+                        if (TextUtils.isEmpty(bugDetail)) {
                             bugTitle = "Wi-Fi BugReport";
-                        }
-                        if (msg.arg1 != SelfRecovery.REASON_LAST_RESORT_WATCHDOG) {
-                            mHandler.post(() -> mClientModeImpl.takeBugReport(bugTitle, bugDetail));
+                        } else {
+                            bugTitle = "Wi-Fi BugReport: " + bugDetail;
                         }
                         log("Recovery triggered, disable wifi");
-                        deferMessage(obtainMessage(CMD_DEFERRED_RECOVERY_RESTART_WIFI));
+                        boolean bugReportRequested = msg.arg2 != 0;
+                        if (bugReportRequested) {
+                            mHandler.post(() ->
+                                    mWifiDiagnostics.takeBugReport(bugTitle, bugDetail));
+                        }
+                        // Store all instances of tethered SAP + scan only/primary STA mode managers
+                        List<ActiveModeManager> modeManagersBeforeRecovery = Stream.concat(
+                                mClientModeManagers.stream()
+                                        .filter(m -> ROLE_CLIENT_SCAN_ONLY.equals(m.getRole())
+                                                || ROLE_CLIENT_PRIMARY.equals(m.getRole())),
+                                mSoftApManagers.stream()
+                                        .filter(m -> ROLE_SOFTAP_TETHERED.equals(m.getRole())))
+                                .collect(Collectors.toList());
+                        deferMessage(obtainMessage(CMD_DEFERRED_RECOVERY_RESTART_WIFI,
+                                modeManagersBeforeRecovery));
+                        int numCallbacks = mRestartCallbacks.beginBroadcast();
+                        for (int i = 0; i < numCallbacks; i++) {
+                            try {
+                                mRestartCallbacks.getBroadcastItem(i).onSubsystemRestarting();
+                            } catch (RemoteException e) {
+                                Log.e(TAG, "Failure calling onSubsystemRestarting" + e);
+                            }
+                        }
+                        mRestartCallbacks.finishBroadcast();
                         shutdownWifi();
                         // onStopped will move the state machine to "DisabledState".
                         break;
+                    }
                     default:
                         return NOT_HANDLED;
                 }
@@ -966,4 +2100,8 @@
             }
         }
     }
+
+    private static <T> T coalesce(T a, T  b) {
+        return a != null ? a : b;
+    }
 }
diff --git a/service/java/com/android/server/wifi/AdaptiveConnectivityEnabledSettingObserver.java b/service/java/com/android/server/wifi/AdaptiveConnectivityEnabledSettingObserver.java
new file mode 100644
index 0000000..66e7774
--- /dev/null
+++ b/service/java/com/android/server/wifi/AdaptiveConnectivityEnabledSettingObserver.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.provider.Settings;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Observer for adaptive connectivity enable settings changes.
+ * This is enabled by default. Will be toggled off via adb command or a settings
+ * toggle by the user to disable adaptive connectivity.
+ */
+public class AdaptiveConnectivityEnabledSettingObserver {
+
+    private static final String TAG = "WifiAdaptConnObserver";
+    /**
+     * Copy of the settings string. Can't directly use the constant because it is @hide.
+     * See {@link Settings.Secure#ADAPTIVE_CONNECTIVITY_ENABLED}.
+     * TODO(b/167709538): remove this hardcoded string and create new API in Wifi mainline.
+     */
+    @VisibleForTesting
+    public static final String SETTINGS_SECURE_ADAPTIVE_CONNECTIVITY_ENABLED =
+            "adaptive_connectivity_enabled";
+
+    private final WifiMetrics mWifiMetrics;
+    private final FrameworkFacade mFrameworkFacade;
+    private final Context mContext;
+
+    private final ContentObserver mContentObserver;
+
+    private boolean mAdaptiveConnectivityEnabled = true;
+
+    public AdaptiveConnectivityEnabledSettingObserver(Handler handler,
+            WifiMetrics wifiMetrics, FrameworkFacade frameworkFacade,
+            Context context) {
+        mWifiMetrics = wifiMetrics;
+        mFrameworkFacade = frameworkFacade;
+        mContext = context;
+        mContentObserver = new ContentObserver(handler) {
+            @Override
+            public void onChange(boolean selfChange) {
+                handleChange();
+            }
+        };
+    }
+
+    private void handleChange() {
+        mAdaptiveConnectivityEnabled = readValueFromSettings();
+        Log.d(TAG, "Adaptive connectivity status changed: " + mAdaptiveConnectivityEnabled);
+        mWifiMetrics.setAdaptiveConnectivityState(mAdaptiveConnectivityEnabled);
+        mWifiMetrics.logUserActionEvent(
+                WifiMetrics.convertAdaptiveConnectivityStateToUserActionEventType(
+                        mAdaptiveConnectivityEnabled));
+    }
+
+    /** Register settings change observer. */
+    public void initialize() {
+        Uri uri = Settings.Secure.getUriFor(SETTINGS_SECURE_ADAPTIVE_CONNECTIVITY_ENABLED);
+        if (uri == null) {
+            Log.e(TAG, "Adaptive connectivity user toggle does not exist in Settings");
+            return;
+        }
+        mFrameworkFacade.registerContentObserver(
+                mContext, uri, true, mContentObserver);
+        mAdaptiveConnectivityEnabled = readValueFromSettings();
+        Log.d(TAG, "Adaptive connectivity status initialized to: " + mAdaptiveConnectivityEnabled);
+        mWifiMetrics.setAdaptiveConnectivityState(mAdaptiveConnectivityEnabled);
+    }
+
+    private boolean readValueFromSettings() {
+        return mFrameworkFacade.getSecureIntegerSetting(
+                mContext, SETTINGS_SECURE_ADAPTIVE_CONNECTIVITY_ENABLED, 1) == 1;
+    }
+
+    /** True if adaptive connectivity is enabled, false otherwise. */
+    public boolean get() {
+        return mAdaptiveConnectivityEnabled;
+    }
+
+    /** Dump method for debugging */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("Dump of AdaptiveConnectivityEnabledSettingObserver");
+        pw.println("mAdaptiveConnectivityEnabled=" + mAdaptiveConnectivityEnabled);
+    }
+}
diff --git a/service/java/com/android/server/wifi/AssocRejectEventInfo.java b/service/java/com/android/server/wifi/AssocRejectEventInfo.java
new file mode 100644
index 0000000..b99e8f1
--- /dev/null
+++ b/service/java/com/android/server/wifi/AssocRejectEventInfo.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.annotation.NonNull;
+import android.hardware.wifi.supplicant.V1_4.ISupplicantStaIfaceCallback.AssociationRejectionData;
+
+import com.android.server.wifi.util.NativeUtil;
+
+import java.util.Objects;
+
+/**
+ * Stores assoc reject information passed from WifiMonitor.
+ */
+public class AssocRejectEventInfo {
+    @NonNull public final String ssid;
+    @NonNull public final String bssid;
+    public final int statusCode;
+    public final boolean timedOut;
+    public final MboOceController.OceRssiBasedAssocRejectAttr oceRssiBasedAssocRejectInfo;
+    public final MboOceController.MboAssocDisallowedAttr mboAssocDisallowedInfo;
+
+    public AssocRejectEventInfo(@NonNull String ssid, @NonNull String bssid, int statusCode,
+            boolean timedOut) {
+        this.ssid = Objects.requireNonNull(ssid);
+        this.bssid = Objects.requireNonNull(bssid);
+        this.statusCode = statusCode;
+        this.timedOut = timedOut;
+        this.oceRssiBasedAssocRejectInfo = null;
+        this.mboAssocDisallowedInfo = null;
+    }
+
+    public AssocRejectEventInfo(AssociationRejectionData assocRejectData) {
+        String ssid = NativeUtil.encodeSsid(assocRejectData.ssid);
+        String bssid = NativeUtil.macAddressFromByteArray(assocRejectData.bssid);
+        this.ssid = Objects.requireNonNull(ssid);
+        this.bssid = Objects.requireNonNull(bssid);
+        this.statusCode = assocRejectData.statusCode;
+        this.timedOut = assocRejectData.timedOut;
+        if (assocRejectData.isMboAssocDisallowedReasonCodePresent) {
+            this.mboAssocDisallowedInfo = new MboOceController.MboAssocDisallowedAttr(
+                    assocRejectData.mboAssocDisallowedReason);
+        } else {
+            this.mboAssocDisallowedInfo = null;
+        }
+        if (assocRejectData.isOceRssiBasedAssocRejectAttrPresent) {
+            this.oceRssiBasedAssocRejectInfo =
+                    new MboOceController.OceRssiBasedAssocRejectAttr(
+                            assocRejectData.oceRssiBasedAssocRejectData.deltaRssi,
+                            assocRejectData.oceRssiBasedAssocRejectData.retryDelayS);
+        } else {
+            this.oceRssiBasedAssocRejectInfo = null;
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        sb.append(" ssid: ").append(ssid);
+        sb.append(" bssid: ").append(bssid);
+        sb.append(" statusCode: ").append(statusCode);
+        sb.append(" timedOut: ").append(timedOut);
+        sb.append(" oceRssiBasedAssocRejectInfo: ").append(oceRssiBasedAssocRejectInfo);
+        sb.append(" mboAssocDisallowedInfo: ").append(mboAssocDisallowedInfo);
+
+        return sb.toString();
+    }
+}
diff --git a/service/java/com/android/server/wifi/AvailableNetworkNotifier.java b/service/java/com/android/server/wifi/AvailableNetworkNotifier.java
index 9143f07..3f424da 100644
--- a/service/java/com/android/server/wifi/AvailableNetworkNotifier.java
+++ b/service/java/com/android/server/wifi/AvailableNetworkNotifier.java
@@ -24,8 +24,8 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.Notification;
-import android.app.NotificationManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -34,7 +34,6 @@
 import android.net.wifi.IActionListener;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
-import android.os.Binder;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Process;
@@ -46,8 +45,8 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.wifi.proto.nano.WifiMetricsProto
-        .ConnectToNetworkNotificationAndActionCount;
+import com.android.server.wifi.proto.nano.WifiMetricsProto.ConnectToNetworkNotificationAndActionCount;
+import com.android.server.wifi.util.ActionListenerWrapper;
 import com.android.server.wifi.util.ScanResultUtil;
 
 import java.io.FileDescriptor;
@@ -87,7 +86,8 @@
     /** No recommendation is made and no notifications are shown. */
     private static final int STATE_NO_NOTIFICATION = 0;
     /** The initial notification recommending a network to connect to is shown. */
-    private static final int STATE_SHOWING_RECOMMENDATION_NOTIFICATION = 1;
+    @VisibleForTesting
+    static final int STATE_SHOWING_RECOMMENDATION_NOTIFICATION = 1;
     /** The notification of status of connecting to the recommended network is shown. */
     private static final int STATE_CONNECTING_IN_NOTIFICATION = 2;
     /** The notification that the connection to the recommended network was successful is shown. */
@@ -96,7 +96,8 @@
     private static final int STATE_CONNECT_FAILED_NOTIFICATION = 4;
 
     /** Current state of the notification. */
-    @State private int mState = STATE_NO_NOTIFICATION;
+    @VisibleForTesting
+    @State int mState = STATE_NO_NOTIFICATION;
 
     /**
      * The {@link Clock#getWallClockMillis()} must be at least this value for us
@@ -116,19 +117,22 @@
     /** Whether the screen is on or not. */
     private boolean mScreenOn;
 
-    /** List of SSIDs blacklisted from recommendation. */
-    private final Set<String> mBlacklistedSsids = new ArraySet<>();
+    /** List of SSIDs blocklisted from recommendation. */
+    private final Set<String> mBlocklistedSsids = new ArraySet<>();
 
-    private final Context mContext;
+    private final WifiContext mContext;
     private final Handler mHandler;
     private final FrameworkFacade mFrameworkFacade;
     private final WifiMetrics mWifiMetrics;
     private final Clock mClock;
     private final WifiConfigManager mConfigManager;
-    private final ClientModeImpl mClientModeImpl;
+    private final ConnectHelper mConnectHelper;
     private final ConnectToNetworkNotificationBuilder mNotificationBuilder;
+    private final MakeBeforeBreakManager mMakeBeforeBreakManager;
+    private final WifiNotificationManager mWifiNotificationManager;
 
-    private ScanResult mRecommendedNetwork;
+    @VisibleForTesting
+    ScanResult mRecommendedNetwork;
 
     /** Tag used for logs and metrics */
     private final String mTag;
@@ -153,15 +157,17 @@
             String toggleSettingsName,
             int notificationIdentifier,
             int nominatorId,
-            Context context,
+            WifiContext context,
             Looper looper,
             FrameworkFacade framework,
             Clock clock,
             WifiMetrics wifiMetrics,
             WifiConfigManager wifiConfigManager,
             WifiConfigStore wifiConfigStore,
-            ClientModeImpl clientModeImpl,
-            ConnectToNetworkNotificationBuilder connectToNetworkNotificationBuilder) {
+            ConnectHelper connectHelper,
+            ConnectToNetworkNotificationBuilder connectToNetworkNotificationBuilder,
+            MakeBeforeBreakManager makeBeforeBreakManager,
+            WifiNotificationManager wifiNotificationManager) {
         mTag = tag;
         mStoreDataIdentifier = storeDataIdentifier;
         mToggleSettingsName = toggleSettingsName;
@@ -173,8 +179,10 @@
         mWifiMetrics = wifiMetrics;
         mClock = clock;
         mConfigManager = wifiConfigManager;
-        mClientModeImpl = clientModeImpl;
+        mConnectHelper = connectHelper;
         mNotificationBuilder = connectToNetworkNotificationBuilder;
+        mMakeBeforeBreakManager = makeBeforeBreakManager;
+        mWifiNotificationManager = wifiNotificationManager;
         mScreenOn = false;
         wifiConfigStore.registerStoreData(new SsidSetStoreData(mStoreDataIdentifier,
                 new AvailableNetworkNotifierStoreData()));
@@ -247,7 +255,7 @@
         }
 
         if (mState != STATE_NO_NOTIFICATION) {
-            getNotificationManager().cancel(mSystemMessageNotificationId);
+            mWifiNotificationManager.cancel(mSystemMessageNotificationId);
 
             if (mRecommendedNetwork != null) {
                 Log.d(mTag, "Notification with state="
@@ -308,7 +316,7 @@
 
     /**
      * Recommends a network to connect to from a list of available networks, while ignoring the
-     * SSIDs in the blacklist.
+     * SSIDs in the blocklist.
      *
      * @param networks List of networks to select from
      */
@@ -324,7 +332,7 @@
             }
         }
 
-        if (result != null && mBlacklistedSsids.contains(result.SSID)) {
+        if (result != null && mBlocklistedSsids.contains(result.SSID)) {
             result = null;
         }
         return result;
@@ -343,7 +351,7 @@
      * @param ssid The connected network's ssid
      */
     public void handleWifiConnected(String ssid) {
-        removeNetworkFromBlacklist(ssid);
+        removeNetworkFromBlocklist(ssid);
         if (mState != STATE_CONNECTING_IN_NOTIFICATION) {
             clearPendingNotification(true /* resetRepeatTime */);
             return;
@@ -389,10 +397,6 @@
                 TIME_TO_SHOW_FAILED_MILLIS);
     }
 
-    private NotificationManager getNotificationManager() {
-        return (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
-    }
-
     private void postInitialNotification(ScanResult recommendedNetwork) {
         if (mRecommendedNetwork != null
                 && TextUtils.equals(mRecommendedNetwork.SSID, recommendedNetwork.SSID)) {
@@ -414,7 +418,7 @@
     }
 
     private void postNotification(Notification notification) {
-        getNotificationManager().notify(mSystemMessageNotificationId, notification);
+        mWifiNotificationManager.notify(mSystemMessageNotificationId, notification);
     }
 
     private void handleConnectToNetworkAction() {
@@ -432,14 +436,22 @@
                 "User initiated connection to recommended network: "
                         + "\"" + mRecommendedNetwork.SSID + "\"");
         WifiConfiguration network = createRecommendedNetworkConfig(mRecommendedNetwork);
+        if (null == network) {
+            Log.e(mTag, "Cannot create the network from the scan result.");
+            return;
+        }
 
         NetworkUpdateResult result = mConfigManager.addOrUpdateNetwork(network, Process.WIFI_UID);
         if (result.isSuccess()) {
-            mWifiMetrics.setNominatorForNetwork(result.netId, mNominatorId);
-            ConnectActionListener connectActionListener = new ConnectActionListener();
-            mClientModeImpl.connect(null, result.netId, new Binder(), connectActionListener,
-                    connectActionListener.hashCode(), Process.SYSTEM_UID);
-            addNetworkToBlacklist(mRecommendedNetwork.SSID);
+            mWifiMetrics.setNominatorForNetwork(result.getNetworkId(), mNominatorId);
+            ConnectActionListener listener = new ConnectActionListener();
+            mMakeBeforeBreakManager.stopAllSecondaryTransientClientModeManagers(() ->
+                    mConnectHelper.connectToNetwork(
+                            // only keep netId, discard other fields
+                            new NetworkUpdateResult(result.getNetworkId()),
+                            new ActionListenerWrapper(listener),
+                            Process.SYSTEM_UID));
+            addNetworkToBlocklist(mRecommendedNetwork.SSID);
         }
 
         mState = STATE_CONNECTING_IN_NOTIFICATION;
@@ -452,28 +464,28 @@
                 TIME_TO_SHOW_CONNECTING_MILLIS);
     }
 
-    private void addNetworkToBlacklist(String ssid) {
-        mBlacklistedSsids.add(ssid);
-        mWifiMetrics.setNetworkRecommenderBlocklistSize(mTag, mBlacklistedSsids.size());
+    private void addNetworkToBlocklist(String ssid) {
+        mBlocklistedSsids.add(ssid);
+        mWifiMetrics.setNetworkRecommenderBlocklistSize(mTag, mBlocklistedSsids.size());
         mConfigManager.saveToStore(false /* forceWrite */);
-        Log.d(mTag, "Network is added to the network notification blacklist: "
+        Log.d(mTag, "Network is added to the network notification blocklist: "
                 + "\"" + ssid + "\"");
     }
 
-    private void removeNetworkFromBlacklist(String ssid) {
+    private void removeNetworkFromBlocklist(String ssid) {
         if (ssid == null) {
             return;
         }
-        if (!mBlacklistedSsids.remove(ssid)) {
+        if (!mBlocklistedSsids.remove(ssid)) {
             return;
         }
-        mWifiMetrics.setNetworkRecommenderBlocklistSize(mTag, mBlacklistedSsids.size());
+        mWifiMetrics.setNetworkRecommenderBlocklistSize(mTag, mBlocklistedSsids.size());
         mConfigManager.saveToStore(false /* forceWrite */);
-        Log.d(mTag, "Network is removed from the network notification blacklist: "
+        Log.d(mTag, "Network is removed from the network notification blocklist: "
                 + "\"" + ssid + "\"");
     }
 
-    WifiConfiguration createRecommendedNetworkConfig(ScanResult recommendedNetwork) {
+    @Nullable WifiConfiguration createRecommendedNetworkConfig(ScanResult recommendedNetwork) {
         return ScanResultUtil.createNetworkFromScanResult(recommendedNetwork);
     }
 
@@ -509,8 +521,8 @@
         mWifiMetrics.incrementConnectToNetworkNotificationAction(mTag, mState,
                 ConnectToNetworkNotificationAndActionCount.ACTION_USER_DISMISSED_NOTIFICATION);
         if (mState == STATE_SHOWING_RECOMMENDATION_NOTIFICATION) {
-            // blacklist dismissed network
-            addNetworkToBlacklist(mRecommendedNetwork.SSID);
+            // blocklist dismissed network
+            addNetworkToBlocklist(mRecommendedNetwork.SSID);
         }
         resetStateAndDelayNotification();
     }
@@ -528,19 +540,19 @@
         pw.println("currentTime: " + mClock.getWallClockMillis());
         pw.println("mNotificationRepeatTime: " + mNotificationRepeatTime);
         pw.println("mState: " + mState);
-        pw.println("mBlacklistedSsids: " + mBlacklistedSsids.toString());
+        pw.println("mBlocklistedSsids: " + mBlocklistedSsids.toString());
     }
 
     private class AvailableNetworkNotifierStoreData implements SsidSetStoreData.DataSource {
         @Override
         public Set<String> getSsids() {
-            return new ArraySet<>(mBlacklistedSsids);
+            return new ArraySet<>(mBlocklistedSsids);
         }
 
         @Override
         public void setSsids(Set<String> ssidList) {
-            mBlacklistedSsids.addAll(ssidList);
-            mWifiMetrics.setNetworkRecommenderBlocklistSize(mTag, mBlacklistedSsids.size());
+            mBlocklistedSsids.addAll(ssidList);
+            mWifiMetrics.setNetworkRecommenderBlocklistSize(mTag, mBlocklistedSsids.size());
         }
     }
 
diff --git a/service/java/com/android/server/wifi/BaseWifiDiagnostics.java b/service/java/com/android/server/wifi/BaseWifiDiagnostics.java
deleted file mode 100644
index 3abf510..0000000
--- a/service/java/com/android/server/wifi/BaseWifiDiagnostics.java
+++ /dev/null
@@ -1,84 +0,0 @@
-
-package com.android.server.wifi;
-
-import android.annotation.NonNull;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-/**
- *
- */
-public class BaseWifiDiagnostics {
-    public static final byte CONNECTION_EVENT_STARTED = 0;
-    public static final byte CONNECTION_EVENT_SUCCEEDED = 1;
-    public static final byte CONNECTION_EVENT_FAILED = 2;
-    public static final byte CONNECTION_EVENT_TIMEOUT = 3;
-
-    protected final WifiNative mWifiNative;
-
-    protected String mFirmwareVersion;
-    protected String mDriverVersion;
-    protected int mSupportedFeatureSet;
-
-    public BaseWifiDiagnostics(WifiNative wifiNative) {
-        mWifiNative = wifiNative;
-    }
-
-    /**
-     * start wifi HAL dependent logging features
-     * @param ifaceName requesting to start logging
-     */
-    public synchronized void startLogging(@NonNull String ifaceName) {
-        mFirmwareVersion = mWifiNative.getFirmwareVersion();
-        mDriverVersion = mWifiNative.getDriverVersion();
-        mSupportedFeatureSet = mWifiNative.getSupportedLoggerFeatureSet();
-    }
-
-    public synchronized void startPacketLog() { }
-
-    public synchronized void stopPacketLog() { }
-
-    /**
-     * stop wifi HAL dependent logging features
-     * @param ifaceName requesting to stop logging
-     */
-    public synchronized void stopLogging(@NonNull String ifaceName) { }
-
-    /**
-     * Inform the diagnostics module of a connection event.
-     * @param event The type of connection event (see CONNECTION_EVENT_* constants)
-     */
-    public synchronized void reportConnectionEvent(byte event) {}
-
-    public synchronized void captureBugReportData(int reason) { }
-
-    public synchronized void captureAlertData(int errorCode, byte[] alertData) { }
-
-    public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        dump(pw);
-        pw.println("*** logging disabled, no debug data ****");
-    }
-
-    /**
-     * Starts taking a standard android bugreport
-     * android will prompt the user to send the bugreport when it's complete
-     * @param bugTitle Title of bugreport to generate
-     * @param bugDetail Description of the bugreport to generate
-     */
-    public void takeBugReport(String bugTitle, String bugDetail) { }
-
-    protected synchronized void dump(PrintWriter pw) {
-        pw.println("Chipset information :-----------------------------------------------");
-        pw.println("FW Version is: " + mFirmwareVersion);
-        pw.println("Driver Version is: " + mDriverVersion);
-        pw.println("Supported Feature set: " + mSupportedFeatureSet);
-    }
-
-    /** enables/disables wifi verbose logging */
-    public synchronized void enableVerboseLogging(boolean verboseEnabled) { }
-
-    /** enables packet fate monitoring */
-    public void startPktFateMonitoring(@NonNull String ifaceName) {}
-
-}
diff --git a/service/java/com/android/server/wifi/BaseWifiService.java b/service/java/com/android/server/wifi/BaseWifiService.java
index 7614cf6..0368169 100644
--- a/service/java/com/android/server/wifi/BaseWifiService.java
+++ b/service/java/com/android/server/wifi/BaseWifiService.java
@@ -16,10 +16,12 @@
 
 package com.android.server.wifi;
 
-import android.content.pm.ParceledListSlice;
+import android.annotation.NonNull;
 import android.net.DhcpInfo;
 import android.net.Network;
+import android.net.wifi.CoexUnsafeChannel;
 import android.net.wifi.IActionListener;
+import android.net.wifi.ICoexCallback;
 import android.net.wifi.IDppCallback;
 import android.net.wifi.ILocalOnlyHotspotCallback;
 import android.net.wifi.INetworkRequestMatchCallback;
@@ -27,13 +29,16 @@
 import android.net.wifi.IOnWifiUsabilityStatsListener;
 import android.net.wifi.IScanResultsCallback;
 import android.net.wifi.ISoftApCallback;
+import android.net.wifi.ISubsystemRestartCallback;
 import android.net.wifi.ISuggestionConnectionStatusListener;
+import android.net.wifi.ISuggestionUserApprovalStatusListener;
 import android.net.wifi.ITrafficStateCallback;
-import android.net.wifi.ITxPacketCountListener;
 import android.net.wifi.IWifiConnectedNetworkScorer;
 import android.net.wifi.IWifiManager;
+import android.net.wifi.IWifiVerboseLoggingStatusChangedListener;
 import android.net.wifi.ScanResult;
 import android.net.wifi.SoftApConfiguration;
+import android.net.wifi.WifiAvailableChannel;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
@@ -43,9 +48,9 @@
 import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.os.ResultReceiver;
 import android.os.WorkSource;
-import android.os.connectivity.WifiActivityEnergyInfo;
+
+import com.android.modules.utils.ParceledListSlice;
 
 import java.util.List;
 import java.util.Map;
@@ -75,29 +80,23 @@
         throw new UnsupportedOperationException();
     }
 
-    /** @deprecated use {@link #getWifiActivityEnergyInfoAsync} instead */
-    @Deprecated
-    public WifiActivityEnergyInfo reportActivityInfo() {
-        throw new UnsupportedOperationException();
-    }
-
-    /** @deprecated use {@link #getWifiActivityEnergyInfoAsync} instead */
-    @Deprecated
-    public void requestActivityInfo(ResultReceiver result) {
-        throw new UnsupportedOperationException();
-    }
-
     @Override
     public void getWifiActivityEnergyInfoAsync(IOnWifiActivityEnergyInfoListener listener) {
         throw new UnsupportedOperationException();
     }
 
-    @Override
+    @Deprecated
     public ParceledListSlice getConfiguredNetworks(String packageName, String featureId) {
         throw new UnsupportedOperationException();
     }
 
     @Override
+    public ParceledListSlice getConfiguredNetworks(String packageName, String featureId,
+            boolean callerNetworksOnly) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public ParceledListSlice getPrivilegedConfiguredNetworks(String packageName, String featureId) {
         throw new UnsupportedOperationException();
     }
@@ -126,6 +125,12 @@
     }
 
     @Override
+    public WifiManager.AddNetworkResult addOrUpdateNetworkPrivileged(WifiConfiguration config,
+            String packageName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public boolean addOrUpdatePasspointConfiguration(
             PasspointConfiguration config, String packageName) {
         throw new UnsupportedOperationException();
@@ -157,12 +162,12 @@
     }
 
     @Override
-    public void deauthenticateNetwork(long holdoff, boolean ess) {
+    public boolean removeNetwork(int netId, String packageName) {
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public boolean removeNetwork(int netId, String packageName) {
+    public boolean removeNonCallerConfiguredNetworks(String packageName) {
         throw new UnsupportedOperationException();
     }
 
@@ -196,12 +201,6 @@
         throw new UnsupportedOperationException();
     }
 
-    /** @deprecated use {@link #setPasspointMeteredOverride} instead */
-    @Deprecated
-    public void setMeteredOverridePasspoint(String fqdn, int meteredOverride) {
-        throw new UnsupportedOperationException();
-    }
-
     @Override
     public void setPasspointMeteredOverride(String fqdn, int meteredOverride) {
         throw new UnsupportedOperationException();
@@ -243,6 +242,21 @@
     }
 
     @Override
+    public void registerSubsystemRestartCallback(ISubsystemRestartCallback callback) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void unregisterSubsystemRestartCallback(ISubsystemRestartCallback callback) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void restartWifiSubsystem() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public int getWifiEnabledState() {
         throw new UnsupportedOperationException();
     }
@@ -252,9 +266,23 @@
         throw new UnsupportedOperationException();
     }
 
-    /** @deprecated use {@link #is5GHzBandSupported} instead */
-    @Deprecated
-    public boolean isDualBandSupported() {
+    @Override
+    public void setOverrideCountryCode(@NonNull String countryCode) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void clearOverrideCountryCode() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setDefaultCountryCode(@NonNull String countryCode) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean is24GHzBandSupported() {
         throw new UnsupportedOperationException();
     }
 
@@ -269,23 +297,22 @@
     }
 
     @Override
+    public boolean is60GHzBandSupported() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public boolean isWifiStandardSupported(int standard) {
         throw new UnsupportedOperationException();
     }
 
-    /** @deprecated use {@link WifiManager#isStaApConcurrencySupported()} */
-    @Deprecated
-    public boolean needs5GHzToAnyApBandConversion() {
+    @Override
+    public DhcpInfo getDhcpInfo(String packageName) {
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public DhcpInfo getDhcpInfo() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void setScanAlwaysAvailable(boolean isAvailable) {
+    public void setScanAlwaysAvailable(boolean isAvailable, String packageName) {
         throw new UnsupportedOperationException();
     }
 
@@ -335,12 +362,32 @@
     }
 
     @Override
-    public boolean startSoftAp(WifiConfiguration wifiConfig) {
+    public boolean isDefaultCoexAlgorithmEnabled() {
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public boolean startTetheredHotspot(SoftApConfiguration softApConfig) {
+    public void setCoexUnsafeChannels(List<CoexUnsafeChannel> unsafeChannels, int restrictions) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void registerCoexCallback(ICoexCallback callback) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void unregisterCoexCallback(ICoexCallback callback) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean startSoftAp(WifiConfiguration wifiConfig, String packageName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean startTetheredHotspot(SoftApConfiguration softApConfig, String packageName) {
         throw new UnsupportedOperationException();
     }
 
@@ -425,12 +472,6 @@
         throw new UnsupportedOperationException();
     }
 
-    /** @deprecated use {@link #allowAutojoinGlobal(boolean)} instead */
-    @Deprecated
-    public void enableWifiConnectivityManager(boolean enabled) {
-        throw new UnsupportedOperationException();
-    }
-
     @Override
     public void disableEphemeralNetwork(String SSID, String packageName) {
         throw new UnsupportedOperationException();
@@ -478,35 +519,44 @@
     }
 
     @Override
-    public void registerSoftApCallback(
-            IBinder binder, ISoftApCallback callback, int callbackIdentifier) {
+    public void addWifiVerboseLoggingStatusChangedListener(
+            IWifiVerboseLoggingStatusChangedListener callback) {
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public void unregisterSoftApCallback(int callbackIdentifier) {
+    public void removeWifiVerboseLoggingStatusChangedListener(
+            IWifiVerboseLoggingStatusChangedListener callback) {
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public void registerTrafficStateCallback(
-            IBinder binder, ITrafficStateCallback callback, int callbackIdentifier) {
+    public void registerSoftApCallback(ISoftApCallback callback) {
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public void unregisterTrafficStateCallback(int callbackIdentifier) {
+    public void unregisterSoftApCallback(ISoftApCallback callback) {
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public void registerNetworkRequestMatchCallback(
-            IBinder binder, INetworkRequestMatchCallback callback, int callbackIdentifier) {
+    public void registerTrafficStateCallback(ITrafficStateCallback callback) {
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public void unregisterNetworkRequestMatchCallback(int callbackIdentifier) {
+    public void unregisterTrafficStateCallback(ITrafficStateCallback callback) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void registerNetworkRequestMatchCallback(INetworkRequestMatchCallback callback) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void unregisterNetworkRequestMatchCallback(INetworkRequestMatchCallback callback) {
         throw new UnsupportedOperationException();
     }
 
@@ -529,6 +579,18 @@
     }
 
     @Override
+    public void setCarrierNetworkOffloadEnabled(int subId, boolean merged, boolean enabled)
+            throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isCarrierNetworkOffloadEnabled(int subId, boolean merged)
+            throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public String[] getFactoryMacAddresses() {
         throw new UnsupportedOperationException();
     }
@@ -539,8 +601,8 @@
     }
 
     @Override
-    public void startDppAsConfiguratorInitiator(IBinder binder, String enrolleeUri,
-            int selectedNetworkId, int netRole, IDppCallback callback) {
+    public void startDppAsConfiguratorInitiator(IBinder binder, String packageName,
+            String enrolleeUri, int selectedNetworkId, int netRole, IDppCallback callback) {
         throw new UnsupportedOperationException();
     }
 
@@ -551,18 +613,23 @@
     }
 
     @Override
+    public void startDppAsEnrolleeResponder(IBinder binder, String deviceInfo,
+            int curve, IDppCallback callback) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public void stopDppSession() throws RemoteException {
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public void addOnWifiUsabilityStatsListener(
-            IBinder binder, IOnWifiUsabilityStatsListener listener, int listenerIdentifier) {
+    public void addOnWifiUsabilityStatsListener(IOnWifiUsabilityStatsListener listener) {
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public void removeOnWifiUsabilityStatsListener(int listenerIdentifier) {
+    public void removeOnWifiUsabilityStatsListener(IOnWifiUsabilityStatsListener listener) {
         throw new UnsupportedOperationException();
     }
 
@@ -572,30 +639,27 @@
     }
 
     @Override
-    public void connect(WifiConfiguration config, int netId, IBinder binder,
-            IActionListener callback, int callbackIdentifier) {
+    public void connect(WifiConfiguration config, int netId, IActionListener callback) {
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public void save(WifiConfiguration config, IBinder binder, IActionListener callback,
-            int callbackIdentifier) {
+    public void startRestrictingAutoJoinToSubscriptionId(int subId) {
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public void forget(int netId, IBinder binder, IActionListener callback,
-            int callbackIdentifier) {
+    public void stopRestrictingAutoJoinToSubscriptionId() {
         throw new UnsupportedOperationException();
     }
 
-    /**
-     * @deprecated was only used by CTS test, now fully removed. Please also remove
-     * ITxPacketCountListener.aidl when removing this method.
-     */
-    @Deprecated
-    public void getTxPacketCount(String packageName, IBinder binder,
-            ITxPacketCountListener callback, int callbackIdentifier) {
+    @Override
+    public void save(WifiConfiguration config, IActionListener callback) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void forget(int netId, IActionListener callback) {
         throw new UnsupportedOperationException();
     }
 
@@ -610,15 +674,14 @@
     }
 
     @Override
-    public void registerSuggestionConnectionStatusListener(IBinder binder,
-            ISuggestionConnectionStatusListener listener,
-            int listenerIdentifier, String packageName, String featureId) {
+    public void registerSuggestionConnectionStatusListener(
+            ISuggestionConnectionStatusListener listener, String packageName, String featureId) {
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public void unregisterSuggestionConnectionStatusListener(int listenerIdentifier,
-            String packageName) {
+    public void unregisterSuggestionConnectionStatusListener(
+            ISuggestionConnectionStatusListener listener, String packageName) {
         throw new UnsupportedOperationException();
     }
 
@@ -677,4 +740,43 @@
     public boolean isAutoWakeupEnabled() {
         throw new UnsupportedOperationException();
     }
+
+    @Override
+    public void addSuggestionUserApprovalStatusListener(
+            ISuggestionUserApprovalStatusListener listener, String packageName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void removeSuggestionUserApprovalStatusListener(
+            ISuggestionUserApprovalStatusListener listener, String packageName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setEmergencyScanRequestInProgress(boolean inProgress) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void removeAppState(int targetAppUid, @NonNull String targetAppPackageName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean setWifiScoringEnabled(boolean enabled) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void flushPasspointAnqpCache(@NonNull String packageName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public List<WifiAvailableChannel> getUsableChannels(
+            int band, int mode, int filter) {
+        throw new UnsupportedOperationException();
+    }
 }
+
diff --git a/service/java/com/android/server/wifi/BssidBlocklistMonitor.java b/service/java/com/android/server/wifi/BssidBlocklistMonitor.java
deleted file mode 100644
index e3fe32a..0000000
--- a/service/java/com/android/server/wifi/BssidBlocklistMonitor.java
+++ /dev/null
@@ -1,745 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wifi;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiManager;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.LocalLog;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.wifi.resources.R;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-/**
- * This class manages the addition and removal of BSSIDs to the BSSID blocklist, which is used
- * for firmware roaming and network selection.
- */
-public class BssidBlocklistMonitor {
-    // A special type association rejection
-    public static final int REASON_AP_UNABLE_TO_HANDLE_NEW_STA = 0;
-    // No internet
-    public static final int REASON_NETWORK_VALIDATION_FAILURE = 1;
-    // Wrong password error
-    public static final int REASON_WRONG_PASSWORD = 2;
-    // Incorrect EAP credentials
-    public static final int REASON_EAP_FAILURE = 3;
-    // Other association rejection failures
-    public static final int REASON_ASSOCIATION_REJECTION = 4;
-    // Association timeout failures.
-    public static final int REASON_ASSOCIATION_TIMEOUT = 5;
-    // Other authentication failures
-    public static final int REASON_AUTHENTICATION_FAILURE = 6;
-    // DHCP failures
-    public static final int REASON_DHCP_FAILURE = 7;
-    // Abnormal disconnect error
-    public static final int REASON_ABNORMAL_DISCONNECT = 8;
-    // AP initiated disconnect for a given duration.
-    public static final int REASON_FRAMEWORK_DISCONNECT_MBO_OCE = 9;
-    // Avoid connecting to the failed AP when trying to reconnect on other available candidates.
-    public static final int REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT = 10;
-    // The connected scorer has disconnected this network.
-    public static final int REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE = 11;
-    // Constant being used to keep track of how many failure reasons there are.
-    public static final int NUMBER_REASON_CODES = 12;
-    public static final int INVALID_REASON = -1;
-
-    @IntDef(prefix = { "REASON_" }, value = {
-            REASON_AP_UNABLE_TO_HANDLE_NEW_STA,
-            REASON_NETWORK_VALIDATION_FAILURE,
-            REASON_WRONG_PASSWORD,
-            REASON_EAP_FAILURE,
-            REASON_ASSOCIATION_REJECTION,
-            REASON_ASSOCIATION_TIMEOUT,
-            REASON_AUTHENTICATION_FAILURE,
-            REASON_DHCP_FAILURE,
-            REASON_ABNORMAL_DISCONNECT,
-            REASON_FRAMEWORK_DISCONNECT_MBO_OCE,
-            REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT,
-            REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FailureReason {}
-
-    // To be filled with values from the overlay.
-    private static final int[] FAILURE_COUNT_DISABLE_THRESHOLD = new int[NUMBER_REASON_CODES];
-    private boolean mFailureCountDisableThresholdArrayInitialized = false;
-    private static final String[] FAILURE_REASON_STRINGS = {
-            "REASON_AP_UNABLE_TO_HANDLE_NEW_STA",
-            "REASON_NETWORK_VALIDATION_FAILURE",
-            "REASON_WRONG_PASSWORD",
-            "REASON_EAP_FAILURE",
-            "REASON_ASSOCIATION_REJECTION",
-            "REASON_ASSOCIATION_TIMEOUT",
-            "REASON_AUTHENTICATION_FAILURE",
-            "REASON_DHCP_FAILURE",
-            "REASON_ABNORMAL_DISCONNECT",
-            "REASON_FRAMEWORK_DISCONNECT_MBO_OCE",
-            "REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT",
-            "REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE"
-    };
-    private static final Set<Integer> LOW_RSSI_SENSITIVE_FAILURES = new ArraySet<>(Arrays.asList(
-            REASON_NETWORK_VALIDATION_FAILURE,
-            REASON_EAP_FAILURE,
-            REASON_ASSOCIATION_REJECTION,
-            REASON_ASSOCIATION_TIMEOUT,
-            REASON_AUTHENTICATION_FAILURE,
-            REASON_DHCP_FAILURE,
-            REASON_ABNORMAL_DISCONNECT,
-            REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE
-    ));
-    private static final long ABNORMAL_DISCONNECT_RESET_TIME_MS = TimeUnit.HOURS.toMillis(3);
-    private static final int MIN_RSSI_DIFF_TO_UNBLOCK_BSSID = 5;
-    private static final String TAG = "BssidBlocklistMonitor";
-
-    private final Context mContext;
-    private final WifiLastResortWatchdog mWifiLastResortWatchdog;
-    private final WifiConnectivityHelper mConnectivityHelper;
-    private final Clock mClock;
-    private final LocalLog mLocalLog;
-    private final Calendar mCalendar;
-    private final WifiScoreCard mWifiScoreCard;
-    private final ScoringParams mScoringParams;
-
-    // Map of bssid to BssidStatus
-    private Map<String, BssidStatus> mBssidStatusMap = new ArrayMap<>();
-
-    // Keeps history of 30 blocked BSSIDs that were most recently removed.
-    private BssidStatusHistoryLogger mBssidStatusHistoryLogger = new BssidStatusHistoryLogger(30);
-
-    /**
-     * Create a new instance of BssidBlocklistMonitor
-     */
-    BssidBlocklistMonitor(Context context, WifiConnectivityHelper connectivityHelper,
-            WifiLastResortWatchdog wifiLastResortWatchdog, Clock clock, LocalLog localLog,
-            WifiScoreCard wifiScoreCard, ScoringParams scoringParams) {
-        mContext = context;
-        mConnectivityHelper = connectivityHelper;
-        mWifiLastResortWatchdog = wifiLastResortWatchdog;
-        mClock = clock;
-        mLocalLog = localLog;
-        mCalendar = Calendar.getInstance();
-        mWifiScoreCard = wifiScoreCard;
-        mScoringParams = scoringParams;
-    }
-
-    // A helper to log debugging information in the local log buffer, which can
-    // be retrieved in bugreport.
-    private void localLog(String log) {
-        mLocalLog.log(log);
-    }
-
-    /**
-     * calculates the blocklist duration based on the current failure streak with exponential
-     * backoff.
-     * @param failureStreak should be greater or equal to 0.
-     * @return duration to block the BSSID in milliseconds
-     */
-    private long getBlocklistDurationWithExponentialBackoff(int failureStreak,
-            int baseBlocklistDurationMs) {
-        failureStreak = Math.min(failureStreak, mContext.getResources().getInteger(
-                R.integer.config_wifiBssidBlocklistMonitorFailureStreakCap));
-        if (failureStreak < 1) {
-            return baseBlocklistDurationMs;
-        }
-        return (long) (Math.pow(2.0, (double) failureStreak) * baseBlocklistDurationMs);
-    }
-
-    /**
-     * Dump the local log buffer and other internal state of BssidBlocklistMonitor.
-     */
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("Dump of BssidBlocklistMonitor");
-        pw.println("BssidBlocklistMonitor - Bssid blocklist begin ----");
-        mBssidStatusMap.values().stream().forEach(entry -> pw.println(entry));
-        pw.println("BssidBlocklistMonitor - Bssid blocklist end ----");
-        mBssidStatusHistoryLogger.dump(pw);
-    }
-
-    private void addToBlocklist(@NonNull BssidStatus entry, long durationMs,
-            @FailureReason int reason, int rssi) {
-        entry.setAsBlocked(durationMs, reason, rssi);
-        localLog(TAG + " addToBlocklist: bssid=" + entry.bssid + ", ssid=" + entry.ssid
-                + ", durationMs=" + durationMs + ", reason=" + getFailureReasonString(reason)
-                + ", rssi=" + rssi);
-    }
-
-    /**
-     * increments the number of failures for the given bssid and returns the number of failures so
-     * far.
-     * @return the BssidStatus for the BSSID
-     */
-    private @NonNull BssidStatus incrementFailureCountForBssid(
-            @NonNull String bssid, @NonNull String ssid, int reasonCode) {
-        BssidStatus status = getOrCreateBssidStatus(bssid, ssid);
-        status.incrementFailureCount(reasonCode);
-        return status;
-    }
-
-    /**
-     * Get the BssidStatus representing the BSSID or create a new one if it doesn't exist.
-     */
-    private @NonNull BssidStatus getOrCreateBssidStatus(@NonNull String bssid,
-            @NonNull String ssid) {
-        BssidStatus status = mBssidStatusMap.get(bssid);
-        if (status == null || !ssid.equals(status.ssid)) {
-            if (status != null) {
-                localLog("getOrCreateBssidStatus: BSSID=" + bssid + ", SSID changed from "
-                        + status.ssid + " to " + ssid);
-            }
-            status = new BssidStatus(bssid, ssid);
-            mBssidStatusMap.put(bssid, status);
-        }
-        return status;
-    }
-
-    private boolean isValidNetworkAndFailureReason(String bssid, String ssid,
-            @FailureReason int reasonCode) {
-        if (bssid == null || ssid == null || WifiManager.UNKNOWN_SSID.equals(ssid)
-                || bssid.equals(ClientModeImpl.SUPPLICANT_BSSID_ANY)
-                || reasonCode < 0 || reasonCode >= NUMBER_REASON_CODES) {
-            Log.e(TAG, "Invalid input: BSSID=" + bssid + ", SSID=" + ssid
-                    + ", reasonCode=" + reasonCode);
-            return false;
-        }
-        return true;
-    }
-
-    private boolean shouldWaitForWatchdogToTriggerFirst(String bssid,
-            @FailureReason int reasonCode) {
-        boolean isWatchdogRelatedFailure = reasonCode == REASON_ASSOCIATION_REJECTION
-                || reasonCode == REASON_AUTHENTICATION_FAILURE
-                || reasonCode == REASON_DHCP_FAILURE;
-        return isWatchdogRelatedFailure && mWifiLastResortWatchdog.shouldIgnoreBssidUpdate(bssid);
-    }
-
-    /**
-     * Block any attempts to auto-connect to the BSSID for the specified duration.
-     * This is meant to be used by features that need wifi to avoid a BSSID for a certain duration,
-     * and thus will not increase the failure streak counters.
-     * @param bssid identifies the AP to block.
-     * @param ssid identifies the SSID the AP belongs to.
-     * @param durationMs duration in millis to block.
-     * @param blockReason reason for blocking the BSSID.
-     * @param rssi the latest RSSI observed.
-     */
-    public void blockBssidForDurationMs(@NonNull String bssid, @NonNull String ssid,
-            long durationMs, @FailureReason int blockReason, int rssi) {
-        if (durationMs <= 0 || !isValidNetworkAndFailureReason(bssid, ssid, blockReason)) {
-            Log.e(TAG, "Invalid input: BSSID=" + bssid + ", SSID=" + ssid
-                    + ", durationMs=" + durationMs + ", blockReason=" + blockReason
-                    + ", rssi=" + rssi);
-            return;
-        }
-        BssidStatus status = getOrCreateBssidStatus(bssid, ssid);
-        if (status.isInBlocklist
-                && status.blocklistEndTimeMs - mClock.getWallClockMillis() > durationMs) {
-            // Return because this BSSID is already being blocked for a longer time.
-            return;
-        }
-        addToBlocklist(status, durationMs, blockReason, rssi);
-    }
-
-    private String getFailureReasonString(@FailureReason int reasonCode) {
-        if (reasonCode == INVALID_REASON) {
-            return "INVALID_REASON";
-        } else if (reasonCode < 0 || reasonCode >= FAILURE_REASON_STRINGS.length) {
-            return "REASON_UNKNOWN";
-        }
-        return FAILURE_REASON_STRINGS[reasonCode];
-    }
-
-    private int getFailureThresholdForReason(@FailureReason int reasonCode) {
-        if (mFailureCountDisableThresholdArrayInitialized) {
-            return FAILURE_COUNT_DISABLE_THRESHOLD[reasonCode];
-        }
-        FAILURE_COUNT_DISABLE_THRESHOLD[REASON_AP_UNABLE_TO_HANDLE_NEW_STA] =
-                mContext.getResources().getInteger(
-                        R.integer.config_wifiBssidBlocklistMonitorApUnableToHandleNewStaThreshold);
-        FAILURE_COUNT_DISABLE_THRESHOLD[REASON_NETWORK_VALIDATION_FAILURE] =
-                mContext.getResources().getInteger(R.integer
-                        .config_wifiBssidBlocklistMonitorNetworkValidationFailureThreshold);
-        FAILURE_COUNT_DISABLE_THRESHOLD[REASON_WRONG_PASSWORD] =
-                mContext.getResources().getInteger(
-                        R.integer.config_wifiBssidBlocklistMonitorWrongPasswordThreshold);
-        FAILURE_COUNT_DISABLE_THRESHOLD[REASON_EAP_FAILURE] =
-                mContext.getResources().getInteger(
-                        R.integer.config_wifiBssidBlocklistMonitorEapFailureThreshold);
-        FAILURE_COUNT_DISABLE_THRESHOLD[REASON_ASSOCIATION_REJECTION] =
-                mContext.getResources().getInteger(
-                        R.integer.config_wifiBssidBlocklistMonitorAssociationRejectionThreshold);
-        FAILURE_COUNT_DISABLE_THRESHOLD[REASON_ASSOCIATION_TIMEOUT] =
-                mContext.getResources().getInteger(
-                        R.integer.config_wifiBssidBlocklistMonitorAssociationTimeoutThreshold);
-        FAILURE_COUNT_DISABLE_THRESHOLD[REASON_AUTHENTICATION_FAILURE] =
-                mContext.getResources().getInteger(
-                        R.integer.config_wifiBssidBlocklistMonitorAuthenticationFailureThreshold);
-        FAILURE_COUNT_DISABLE_THRESHOLD[REASON_DHCP_FAILURE] =
-                mContext.getResources().getInteger(
-                        R.integer.config_wifiBssidBlocklistMonitorDhcpFailureThreshold);
-        FAILURE_COUNT_DISABLE_THRESHOLD[REASON_ABNORMAL_DISCONNECT] =
-                mContext.getResources().getInteger(
-                        R.integer.config_wifiBssidBlocklistMonitorAbnormalDisconnectThreshold);
-        mFailureCountDisableThresholdArrayInitialized = true;
-        return FAILURE_COUNT_DISABLE_THRESHOLD[reasonCode];
-    }
-
-    private boolean handleBssidConnectionFailureInternal(String bssid, String ssid,
-            @FailureReason int reasonCode, int rssi) {
-        BssidStatus entry = incrementFailureCountForBssid(bssid, ssid, reasonCode);
-        int failureThreshold = getFailureThresholdForReason(reasonCode);
-        int currentStreak = mWifiScoreCard.getBssidBlocklistStreak(ssid, bssid, reasonCode);
-        if (currentStreak > 0 || entry.failureCount[reasonCode] >= failureThreshold) {
-            // To rule out potential device side issues, don't add to blocklist if
-            // WifiLastResortWatchdog is still not triggered
-            if (shouldWaitForWatchdogToTriggerFirst(bssid, reasonCode)) {
-                return false;
-            }
-            int baseBlockDurationMs = getBaseBlockDurationForReason(reasonCode);
-            addToBlocklist(entry,
-                    getBlocklistDurationWithExponentialBackoff(currentStreak, baseBlockDurationMs),
-                    reasonCode, rssi);
-            mWifiScoreCard.incrementBssidBlocklistStreak(ssid, bssid, reasonCode);
-            return true;
-        }
-        return false;
-    }
-
-    private int getBaseBlockDurationForReason(int blockReason) {
-        switch (blockReason) {
-            case REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE:
-                return mContext.getResources().getInteger(R.integer
-                        .config_wifiBssidBlocklistMonitorConnectedScoreBaseBlockDurationMs);
-            default:
-                return mContext.getResources().getInteger(
-                        R.integer.config_wifiBssidBlocklistMonitorBaseBlockDurationMs);
-        }
-    }
-
-    /**
-     * Note a failure event on a bssid and perform appropriate actions.
-     * @return True if the blocklist has been modified.
-     */
-    public boolean handleBssidConnectionFailure(String bssid, String ssid,
-            @FailureReason int reasonCode, int rssi) {
-        if (!isValidNetworkAndFailureReason(bssid, ssid, reasonCode)) {
-            return false;
-        }
-        if (reasonCode == REASON_ABNORMAL_DISCONNECT) {
-            long connectionTime = mWifiScoreCard.getBssidConnectionTimestampMs(ssid, bssid);
-            // only count disconnects that happen shortly after a connection.
-            if (mClock.getWallClockMillis() - connectionTime
-                    > mContext.getResources().getInteger(
-                            R.integer.config_wifiBssidBlocklistAbnormalDisconnectTimeWindowMs)) {
-                return false;
-            }
-        }
-        return handleBssidConnectionFailureInternal(bssid, ssid, reasonCode, rssi);
-    }
-
-    /**
-     * Note a connection success event on a bssid and clear appropriate failure counters.
-     */
-    public void handleBssidConnectionSuccess(@NonNull String bssid, @NonNull String ssid) {
-        /**
-         * First reset the blocklist streak.
-         * This needs to be done even if a BssidStatus is not found, since the BssidStatus may
-         * have been removed due to blocklist timeout.
-         */
-        mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_AP_UNABLE_TO_HANDLE_NEW_STA);
-        mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_WRONG_PASSWORD);
-        mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_EAP_FAILURE);
-        mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_ASSOCIATION_REJECTION);
-        mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_ASSOCIATION_TIMEOUT);
-        mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_AUTHENTICATION_FAILURE);
-
-        long connectionTime = mClock.getWallClockMillis();
-        long prevConnectionTime = mWifiScoreCard.setBssidConnectionTimestampMs(
-                ssid, bssid, connectionTime);
-        if (connectionTime - prevConnectionTime > ABNORMAL_DISCONNECT_RESET_TIME_MS) {
-            mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_ABNORMAL_DISCONNECT);
-            mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid,
-                    REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE);
-        }
-
-        BssidStatus status = mBssidStatusMap.get(bssid);
-        if (status == null) {
-            return;
-        }
-        // Clear the L2 failure counters
-        status.failureCount[REASON_AP_UNABLE_TO_HANDLE_NEW_STA] = 0;
-        status.failureCount[REASON_WRONG_PASSWORD] = 0;
-        status.failureCount[REASON_EAP_FAILURE] = 0;
-        status.failureCount[REASON_ASSOCIATION_REJECTION] = 0;
-        status.failureCount[REASON_ASSOCIATION_TIMEOUT] = 0;
-        status.failureCount[REASON_AUTHENTICATION_FAILURE] = 0;
-        if (connectionTime - prevConnectionTime > ABNORMAL_DISCONNECT_RESET_TIME_MS) {
-            status.failureCount[REASON_ABNORMAL_DISCONNECT] = 0;
-        }
-    }
-
-    /**
-     * Note a successful network validation on a BSSID and clear appropriate failure counters.
-     * And then remove the BSSID from blocklist.
-     */
-    public void handleNetworkValidationSuccess(@NonNull String bssid, @NonNull String ssid) {
-        mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_NETWORK_VALIDATION_FAILURE);
-        BssidStatus status = mBssidStatusMap.get(bssid);
-        if (status == null) {
-            return;
-        }
-        status.failureCount[REASON_NETWORK_VALIDATION_FAILURE] = 0;
-        /**
-         * Network validation may take more than 1 tries to succeed.
-         * remove the BSSID from blocklist to make sure we are not accidentally blocking good
-         * BSSIDs.
-         **/
-        if (status.isInBlocklist) {
-            mBssidStatusHistoryLogger.add(status, "Network validation success");
-            mBssidStatusMap.remove(bssid);
-        }
-    }
-
-    /**
-     * Note a successful DHCP provisioning and clear appropriate faliure counters.
-     */
-    public void handleDhcpProvisioningSuccess(@NonNull String bssid, @NonNull String ssid) {
-        mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_DHCP_FAILURE);
-        BssidStatus status = mBssidStatusMap.get(bssid);
-        if (status == null) {
-            return;
-        }
-        status.failureCount[REASON_DHCP_FAILURE] = 0;
-    }
-
-    /**
-     * Note the removal of a network from the Wifi stack's internal database and reset
-     * appropriate failure counters.
-     * @param ssid
-     */
-    public void handleNetworkRemoved(@NonNull String ssid) {
-        clearBssidBlocklistForSsid(ssid);
-        mWifiScoreCard.resetBssidBlocklistStreakForSsid(ssid);
-    }
-
-    /**
-     * Clears the blocklist for BSSIDs associated with the input SSID only.
-     * @param ssid
-     */
-    public void clearBssidBlocklistForSsid(@NonNull String ssid) {
-        int prevSize = mBssidStatusMap.size();
-        mBssidStatusMap.entrySet().removeIf(e -> {
-            BssidStatus status = e.getValue();
-            if (status.ssid == null) {
-                return false;
-            }
-            if (status.ssid.equals(ssid)) {
-                mBssidStatusHistoryLogger.add(status, "clearBssidBlocklistForSsid");
-                return true;
-            }
-            return false;
-        });
-        int diff = prevSize - mBssidStatusMap.size();
-        if (diff > 0) {
-            localLog(TAG + " clearBssidBlocklistForSsid: SSID=" + ssid
-                    + ", num BSSIDs cleared=" + diff);
-        }
-    }
-
-    /**
-     * Clears the BSSID blocklist and failure counters.
-     */
-    public void clearBssidBlocklist() {
-        if (mBssidStatusMap.size() > 0) {
-            int prevSize = mBssidStatusMap.size();
-            for (BssidStatus status : mBssidStatusMap.values()) {
-                mBssidStatusHistoryLogger.add(status, "clearBssidBlocklist");
-            }
-            mBssidStatusMap.clear();
-            localLog(TAG + " clearBssidBlocklist: num BSSIDs cleared="
-                    + (prevSize - mBssidStatusMap.size()));
-        }
-    }
-
-    /**
-     * @param ssid
-     * @return the number of BSSIDs currently in the blocklist for the |ssid|.
-     */
-    public int updateAndGetNumBlockedBssidsForSsid(@NonNull String ssid) {
-        return (int) updateAndGetBssidBlocklistInternal()
-                .filter(entry -> ssid.equals(entry.ssid)).count();
-    }
-
-    private int getNumBlockedBssidsForSsid(@Nullable String ssid) {
-        if (ssid == null) {
-            return 0;
-        }
-        return (int) mBssidStatusMap.values().stream()
-                .filter(entry -> entry.isInBlocklist && ssid.equals(entry.ssid))
-                .count();
-    }
-
-    /**
-     * Overloaded version of updateAndGetBssidBlocklist.
-     * Accepts a @Nullable String ssid as input, and updates the firmware roaming
-     * configuration if the blocklist for the input ssid has been changed.
-     * @param ssid to update firmware roaming configuration for.
-     * @return Set of BSSIDs currently in the blocklist
-     */
-    public Set<String> updateAndGetBssidBlocklistForSsid(@Nullable String ssid) {
-        int numBefore = getNumBlockedBssidsForSsid(ssid);
-        Set<String> bssidBlocklist = updateAndGetBssidBlocklist();
-        if (getNumBlockedBssidsForSsid(ssid) != numBefore) {
-            updateFirmwareRoamingConfiguration(ssid);
-        }
-        return bssidBlocklist;
-    }
-
-    /**
-     * Gets the BSSIDs that are currently in the blocklist.
-     * @return Set of BSSIDs currently in the blocklist
-     */
-    public Set<String> updateAndGetBssidBlocklist() {
-        return updateAndGetBssidBlocklistInternal()
-                .map(entry -> entry.bssid)
-                .collect(Collectors.toSet());
-    }
-
-    /**
-     * Gets the list of block reasons for BSSIDs currently in the blocklist.
-     * @return The set of unique reasons for blocking BSSIDs with this SSID.
-     */
-    public Set<Integer> getFailureReasonsForSsid(@NonNull String ssid) {
-        if (ssid == null) {
-            return Collections.emptySet();
-        }
-        return mBssidStatusMap.values().stream()
-                .filter(entry -> entry.isInBlocklist && ssid.equals(entry.ssid))
-                .map(entry -> entry.blockReason)
-                .collect(Collectors.toSet());
-    }
-
-    /**
-     * Attempts to re-enable BSSIDs that likely experienced failures due to low RSSI.
-     * @param scanDetails
-     */
-    public void tryEnablingBlockedBssids(List<ScanDetail> scanDetails) {
-        if (scanDetails == null) {
-            return;
-        }
-        for (ScanDetail scanDetail : scanDetails) {
-            ScanResult scanResult = scanDetail.getScanResult();
-            if (scanResult == null) {
-                continue;
-            }
-            BssidStatus status = mBssidStatusMap.get(scanResult.BSSID);
-            if (status == null || !status.isInBlocklist
-                    || !LOW_RSSI_SENSITIVE_FAILURES.contains(status.blockReason)) {
-                continue;
-            }
-            int sufficientRssi = mScoringParams.getSufficientRssi(scanResult.frequency);
-            if (status.lastRssi < sufficientRssi && scanResult.level >= sufficientRssi
-                    && scanResult.level - status.lastRssi >= MIN_RSSI_DIFF_TO_UNBLOCK_BSSID) {
-                mBssidStatusHistoryLogger.add(status, "rssi significantly improved");
-                mBssidStatusMap.remove(status.bssid);
-            }
-        }
-    }
-
-    /**
-     * Removes expired BssidStatus entries and then return remaining entries in the blocklist.
-     * @return Stream of BssidStatus for BSSIDs that are in the blocklist.
-     */
-    private Stream<BssidStatus> updateAndGetBssidBlocklistInternal() {
-        Stream.Builder<BssidStatus> builder = Stream.builder();
-        long curTime = mClock.getWallClockMillis();
-        mBssidStatusMap.entrySet().removeIf(e -> {
-            BssidStatus status = e.getValue();
-            if (status.isInBlocklist) {
-                if (status.blocklistEndTimeMs < curTime) {
-                    mBssidStatusHistoryLogger.add(status, "updateAndGetBssidBlocklistInternal");
-                    return true;
-                }
-                builder.accept(status);
-            }
-            return false;
-        });
-        return builder.build();
-    }
-
-    /**
-     * Sends the BSSIDs belonging to the input SSID down to the firmware to prevent auto-roaming
-     * to those BSSIDs.
-     * @param ssid
-     */
-    public void updateFirmwareRoamingConfiguration(@NonNull String ssid) {
-        if (!mConnectivityHelper.isFirmwareRoamingSupported()) {
-            return;
-        }
-        ArrayList<String> bssidBlocklist = updateAndGetBssidBlocklistInternal()
-                .filter(entry -> ssid.equals(entry.ssid))
-                .sorted((o1, o2) -> (int) (o2.blocklistEndTimeMs - o1.blocklistEndTimeMs))
-                .map(entry -> entry.bssid)
-                .collect(Collectors.toCollection(ArrayList::new));
-        int fwMaxBlocklistSize = mConnectivityHelper.getMaxNumBlacklistBssid();
-        if (fwMaxBlocklistSize <= 0) {
-            Log.e(TAG, "Invalid max BSSID blocklist size:  " + fwMaxBlocklistSize);
-            return;
-        }
-        // Having the blocklist size exceeding firmware max limit is unlikely because we have
-        // already flitered based on SSID. But just in case this happens, we are prioritizing
-        // sending down BSSIDs blocked for the longest time.
-        if (bssidBlocklist.size() > fwMaxBlocklistSize) {
-            bssidBlocklist = new ArrayList<String>(bssidBlocklist.subList(0,
-                    fwMaxBlocklistSize));
-        }
-        // plumb down to HAL
-        if (!mConnectivityHelper.setFirmwareRoamingConfiguration(bssidBlocklist,
-                new ArrayList<String>())) {  // TODO(b/36488259): SSID whitelist management.
-        }
-    }
-
-    @VisibleForTesting
-    public int getBssidStatusHistoryLoggerSize() {
-        return mBssidStatusHistoryLogger.size();
-    }
-
-    private class BssidStatusHistoryLogger {
-        private LinkedList<String> mLogHistory = new LinkedList<>();
-        private int mBufferSize;
-
-        BssidStatusHistoryLogger(int bufferSize) {
-            mBufferSize = bufferSize;
-        }
-
-        public void add(BssidStatus bssidStatus, String trigger) {
-            // only log history for Bssids that had been blocked.
-            if (bssidStatus == null || !bssidStatus.isInBlocklist) {
-                return;
-            }
-            StringBuilder sb = new StringBuilder();
-            mCalendar.setTimeInMillis(mClock.getWallClockMillis());
-            sb.append(", logTimeMs="
-                    + String.format("%tm-%td %tH:%tM:%tS.%tL", mCalendar, mCalendar,
-                    mCalendar, mCalendar, mCalendar, mCalendar));
-            sb.append(", trigger=" + trigger);
-            mLogHistory.add(bssidStatus.toString() + sb.toString());
-            if (mLogHistory.size() > mBufferSize) {
-                mLogHistory.removeFirst();
-            }
-        }
-
-        @VisibleForTesting
-        public int size() {
-            return mLogHistory.size();
-        }
-
-        public void dump(PrintWriter pw) {
-            pw.println("BssidBlocklistMonitor - Bssid blocklist history begin ----");
-            for (String line : mLogHistory) {
-                pw.println(line);
-            }
-            pw.println("BssidBlocklistMonitor - Bssid blocklist history end ----");
-        }
-    }
-
-    /**
-     * Helper class that counts the number of failures per BSSID.
-     */
-    private class BssidStatus {
-        public final String bssid;
-        public final String ssid;
-        public final int[] failureCount = new int[NUMBER_REASON_CODES];
-        public int blockReason = INVALID_REASON; // reason of blocking this BSSID
-        // The latest RSSI that's seen before this BSSID is added to blocklist.
-        public int lastRssi = 0;
-
-        // The following are used to flag how long this BSSID stays in the blocklist.
-        public boolean isInBlocklist;
-        public long blocklistEndTimeMs;
-        public long blocklistStartTimeMs;
-
-        BssidStatus(String bssid, String ssid) {
-            this.bssid = bssid;
-            this.ssid = ssid;
-        }
-
-        /**
-         * increments the failure count for the reasonCode by 1.
-         * @return the incremented failure count
-         */
-        public int incrementFailureCount(int reasonCode) {
-            return ++failureCount[reasonCode];
-        }
-
-        /**
-         * Set this BSSID as blocked for the specified duration.
-         * @param durationMs
-         * @param blockReason
-         * @param rssi
-         */
-        public void setAsBlocked(long durationMs, @FailureReason int blockReason, int rssi) {
-            isInBlocklist = true;
-            blocklistStartTimeMs = mClock.getWallClockMillis();
-            blocklistEndTimeMs = blocklistStartTimeMs + durationMs;
-            this.blockReason = blockReason;
-            lastRssi = rssi;
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder sb = new StringBuilder();
-            sb.append("BSSID=" + bssid);
-            sb.append(", SSID=" + ssid);
-            sb.append(", isInBlocklist=" + isInBlocklist);
-            if (isInBlocklist) {
-                sb.append(", blockReason=" + getFailureReasonString(blockReason));
-                sb.append(", lastRssi=" + lastRssi);
-                mCalendar.setTimeInMillis(blocklistStartTimeMs);
-                sb.append(", blocklistStartTimeMs="
-                        + String.format("%tm-%td %tH:%tM:%tS.%tL", mCalendar, mCalendar,
-                        mCalendar, mCalendar, mCalendar, mCalendar));
-                mCalendar.setTimeInMillis(blocklistEndTimeMs);
-                sb.append(", blocklistEndTimeMs="
-                        + String.format("%tm-%td %tH:%tM:%tS.%tL", mCalendar, mCalendar,
-                        mCalendar, mCalendar, mCalendar, mCalendar));
-            }
-            return sb.toString();
-        }
-    }
-}
diff --git a/service/java/com/android/server/wifi/ClientMode.java b/service/java/com/android/server/wifi/ClientMode.java
new file mode 100644
index 0000000..7b2bb1f
--- /dev/null
+++ b/service/java/com/android/server/wifi/ClientMode.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.DhcpResultsParcelable;
+import android.net.Network;
+import android.net.wifi.IWifiConnectedNetworkScorer;
+import android.net.wifi.WifiAnnotations;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.hotspot2.IProvisioningCallback;
+import android.net.wifi.hotspot2.OsuProvider;
+import android.net.wifi.nl80211.DeviceWiphyCapabilities;
+import android.net.wifi.nl80211.WifiNl80211Manager;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.WorkSource;
+
+import com.android.server.wifi.WifiNative.RxFateReport;
+import com.android.server.wifi.WifiNative.TxFateReport;
+import com.android.server.wifi.util.ActionListenerWrapper;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * This interface is used to respond to calls independent of a STA's current mode.
+ * If the STA is in scan only mode, ClientMode is implemented using {@link ScanOnlyModeImpl}.
+ * If the STA is in client mode, ClientMode is implemented using {@link ClientModeImpl}.
+ */
+public interface ClientMode {
+    void dump(FileDescriptor fd, PrintWriter pw, String[] args);
+
+    void enableVerboseLogging(boolean verbose);
+
+    void connectNetwork(NetworkUpdateResult result, ActionListenerWrapper wrapper, int callingUid);
+
+    void saveNetwork(NetworkUpdateResult result, ActionListenerWrapper wrapper, int callingUid);
+
+    void disconnect();
+
+    void reconnect(WorkSource ws);
+
+    void reassociate();
+
+    void startConnectToNetwork(int networkId, int uid, String bssid);
+
+    void startRoamToNetwork(int networkId, String bssid);
+
+    boolean setWifiConnectedNetworkScorer(IBinder binder, IWifiConnectedNetworkScorer scorer);
+
+    void clearWifiConnectedNetworkScorer();
+
+    void resetSimAuthNetworks(@ClientModeImpl.ResetSimReason int resetReason);
+
+    /**
+     * Notification that the Bluetooth connection state changed. The latest connection state can be
+     * fetched from {@link WifiGlobals#isBluetoothConnected()}.
+     */
+    void onBluetoothConnectionStateChanged();
+
+    WifiInfo syncRequestConnectionInfo();
+
+    boolean syncQueryPasspointIcon(long bssid, String fileName);
+
+    Network syncGetCurrentNetwork();
+
+    DhcpResultsParcelable syncGetDhcpResultsParcelable();
+
+    /** Get the supported feature set synchronously */
+    long getSupportedFeatures();
+
+    boolean syncStartSubscriptionProvisioning(int callingUid, OsuProvider provider,
+            IProvisioningCallback callback);
+
+    boolean isWifiStandardSupported(@WifiAnnotations.WifiStandard int standard);
+
+    void enableTdls(String remoteMacAddress, boolean enable);
+
+    void dumpIpClient(FileDescriptor fd, PrintWriter pw, String[] args);
+
+    void dumpWifiScoreReport(FileDescriptor fd, PrintWriter pw, String[] args);
+
+    String getFactoryMacAddress();
+
+    /**
+     * Returns WifiConfiguration object corresponding to the currently connected network, null if
+     * not connected.
+     */
+    @Nullable
+    WifiConfiguration getConnectedWifiConfiguration();
+
+    /**
+     * Returns WifiConfiguration object corresponding to the currently connecting network, null if
+     * not connecting.
+     */
+    @Nullable WifiConfiguration getConnectingWifiConfiguration();
+
+    /**
+     * Returns bssid corresponding to the currently connected network, null if not connected.
+     */
+    @Nullable String getConnectedBssid();
+
+    /**
+     * Returns bssid corresponding to the currently connecting network, null if not connecting.
+     */
+    @Nullable String getConnectingBssid();
+
+    WifiLinkLayerStats getWifiLinkLayerStats();
+
+    boolean setPowerSave(boolean ps);
+
+    boolean setLowLatencyMode(boolean enabled);
+
+    WifiMulticastLockManager.FilterController getMcastLockManagerFilterController();
+
+    boolean isConnected();
+
+    boolean isConnecting();
+
+    boolean isRoaming();
+
+    boolean isDisconnected();
+
+    boolean isSupplicantTransientState();
+
+    void onCellularConnectivityChanged(@WifiDataStall.CellularDataStatusCode int status);
+
+    /** Result callback for {@link #probeLink(LinkProbeCallback, int)} */
+    interface LinkProbeCallback extends WifiNl80211Manager.SendMgmtFrameCallback {
+
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(prefix = {"LINK_PROBE_ERROR_"},
+                value = {LINK_PROBE_ERROR_NOT_CONNECTED})
+        @interface LinkProbeFailure {}
+
+        /** Attempted to send a link probe when not connected to Wifi. */
+        // Note: this is a restriction in the Wifi framework since link probing is defined to be
+        // targeted at the currently connected AP. Driver/firmware has no problems with sending
+        // management frames to arbitrary APs whether connected or disconnected.
+        int LINK_PROBE_ERROR_NOT_CONNECTED = 1000;
+
+        /**
+         * Called when the link probe failed.
+         * @param reason The error code for the failure. One of
+         * {@link WifiNl80211Manager.SendMgmtFrameError} or {@link LinkProbeFailure}.
+         */
+        void onFailure(int reason);
+
+        static String failureReasonToString(int reason) {
+            switch (reason) {
+                case WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_UNKNOWN:
+                    return "SEND_MGMT_FRAME_ERROR_UNKNOWN";
+                case WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_MCS_UNSUPPORTED:
+                    return "SEND_MGMT_FRAME_ERROR_MCS_UNSUPPORTED";
+                case WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_NO_ACK:
+                    return "SEND_MGMT_FRAME_ERROR_NO_ACK";
+                case WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_TIMEOUT:
+                    return "SEND_MGMT_FRAME_ERROR_TIMEOUT";
+                case WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_ALREADY_STARTED:
+                    return "SEND_MGMT_FRAME_ERROR_ALREADY_STARTED";
+                case LINK_PROBE_ERROR_NOT_CONNECTED:
+                    return "LINK_PROBE_ERROR_NOT_CONNECTED";
+                default:
+                    return "Unrecognized error";
+            }
+        }
+    }
+
+    /** Send a link probe */
+    void probeLink(LinkProbeCallback callback, int mcs);
+
+    /** Send a {@link Message} to ClientModeImpl's StateMachine. */
+    void sendMessageToClientModeImpl(Message msg);
+
+    /** Unique ID for this ClientMode instance, used for debugging. */
+    long getId();
+
+    /**
+     * Set MBO cellular data status
+     * @param available cellular data status, true means cellular data available, false otherwise.
+     */
+    void setMboCellularDataStatus(boolean available);
+
+    /**
+     * Query the firmware roaming capabilities.
+     * @return Roaming Capabilities on success, null on failure.
+     */
+    @Nullable
+    WifiNative.RoamingCapabilities getRoamingCapabilities();
+
+    /** Set firmware roaming configurations. */
+    boolean configureRoaming(WifiNative.RoamingConfig config);
+
+    /** Enable/Disable firmware roaming. */
+    boolean enableRoaming(boolean enabled);
+
+    /**
+     * Set country code.
+     *
+     * @param countryCode 2 byte ASCII string. For ex: US, CA.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    boolean setCountryCode(String countryCode);
+
+    /**
+     * Fetch the most recent TX packet fates from the HAL. Fails unless HAL is started.
+     * @return TxFateReport list on success, empty list on failure. Never returns null.
+     */
+    @NonNull
+    List<TxFateReport> getTxPktFates();
+
+    /**
+     * Fetch the most recent RX packet fates from the HAL. Fails unless HAL is started.
+     * @return RxFateReport list on success, empty list on failure. Never returns null.
+     */
+    @NonNull
+    List<RxFateReport> getRxPktFates();
+
+    /**
+     * Get the Wiphy capabilities of a device for a given interface
+     * If the interface is not associated with one,
+     * it will be read from the device through wificond
+     *
+     * @return the device capabilities for this interface, or null if not available
+     */
+    @Nullable
+    DeviceWiphyCapabilities getDeviceWiphyCapabilities();
+
+    /**
+     * Initiate ANQP query.
+     *
+     * @param bssid BSSID of the AP to be queried
+     * @param anqpIds Set of anqp IDs.
+     * @param hs20Subtypes Set of HS20 subtypes.
+     * @return true on success, false otherwise.
+     */
+    boolean requestAnqp(String bssid, Set<Integer> anqpIds, Set<Integer> hs20Subtypes);
+
+    /**
+     * Initiate Venue URL ANQP query.
+     *
+     * @param bssid BSSID of the AP to be queried
+     * @return true on success, false otherwise.
+     */
+    boolean requestVenueUrlAnqp(String bssid);
+
+    /**
+     * Request a passpoint icon file |filename| from the specified AP |bssid|.
+     *
+     * @param bssid BSSID of the AP
+     * @param fileName name of the icon file
+     * @return true if request is sent successfully, false otherwise
+     */
+    boolean requestIcon(String  bssid, String fileName);
+
+    /**
+     * If set to true, the NetworkAgent score for connections established on this ClientModeManager
+     * will be artificially reduced so that ConnectivityService will prefer any other connection.
+     */
+    void setShouldReduceNetworkScore(boolean shouldReduceNetworkScore);
+}
diff --git a/service/java/com/android/server/wifi/ClientModeDefaults.java b/service/java/com/android/server/wifi/ClientModeDefaults.java
new file mode 100644
index 0000000..3bf9753
--- /dev/null
+++ b/service/java/com/android/server/wifi/ClientModeDefaults.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.net.DhcpResultsParcelable;
+import android.net.Network;
+import android.net.wifi.IWifiConnectedNetworkScorer;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.net.wifi.hotspot2.IProvisioningCallback;
+import android.net.wifi.hotspot2.OsuProvider;
+import android.net.wifi.nl80211.DeviceWiphyCapabilities;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.WorkSource;
+
+import com.android.server.wifi.util.ActionListenerWrapper;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Default implementations for {@link ClientMode} APIs.
+ */
+public interface ClientModeDefaults extends ClientMode {
+    default void dump(FileDescriptor fd, PrintWriter pw, String[] args) { }
+
+    default void connectNetwork(NetworkUpdateResult result, ActionListenerWrapper wrapper,
+            int callingUid) {
+        // wifi off, can't connect.
+        wrapper.sendFailure(WifiManager.BUSY);
+    }
+
+    default void saveNetwork(NetworkUpdateResult result, ActionListenerWrapper wrapper,
+            int callingUid) {
+        // wifi off, nothing more to do here.
+        wrapper.sendSuccess();
+    }
+
+    default void disconnect() { }
+
+    default void reconnect(WorkSource ws) { }
+
+    default void reassociate() { }
+
+    default void startConnectToNetwork(int networkId, int uid, String bssid) { }
+
+    default void startRoamToNetwork(int networkId, String bssid) { }
+
+    default boolean setWifiConnectedNetworkScorer(
+            IBinder binder, IWifiConnectedNetworkScorer scorer) {
+        // don't fail the public API when wifi is off.
+        return true;
+    }
+
+    default void clearWifiConnectedNetworkScorer() { }
+
+    default void resetSimAuthNetworks(@ClientModeImpl.ResetSimReason int resetReason) { }
+
+    default void onBluetoothConnectionStateChanged() { }
+
+    default WifiInfo syncRequestConnectionInfo() {
+        return new WifiInfo();
+    }
+
+    default boolean syncQueryPasspointIcon(long bssid, String fileName) {
+        return false;
+    }
+
+    default Network syncGetCurrentNetwork() {
+        return null;
+    }
+
+    default DhcpResultsParcelable syncGetDhcpResultsParcelable() {
+        return new DhcpResultsParcelable();
+    }
+
+    default boolean syncStartSubscriptionProvisioning(int callingUid, OsuProvider provider,
+            IProvisioningCallback callback) {
+        return false;
+    }
+
+    default void enableTdls(String remoteMacAddress, boolean enable) { }
+
+    default void dumpIpClient(FileDescriptor fd, PrintWriter pw, String[] args) { }
+
+    default void dumpWifiScoreReport(FileDescriptor fd, PrintWriter pw, String[] args) { }
+
+    default void enableVerboseLogging(boolean verbose) { }
+
+    default String getFactoryMacAddress() {
+        return null;
+    }
+
+    default WifiConfiguration getConnectedWifiConfiguration() {
+        return null;
+    }
+
+    default WifiConfiguration getConnectingWifiConfiguration() {
+        return null;
+    }
+
+    default String getConnectedBssid() {
+        return null;
+    }
+
+    default String getConnectingBssid() {
+        return null;
+    }
+
+    default WifiLinkLayerStats getWifiLinkLayerStats() {
+        return null;
+    }
+
+    default boolean setPowerSave(boolean ps) {
+        return false;
+    }
+
+    default boolean setLowLatencyMode(boolean enabled) {
+        return false;
+    }
+
+    default WifiMulticastLockManager.FilterController getMcastLockManagerFilterController() {
+        return new WifiMulticastLockManager.FilterController() {
+            @Override
+            public void startFilteringMulticastPackets() { }
+            @Override
+            public void stopFilteringMulticastPackets() { }
+        };
+    }
+
+    default boolean isConnected() {
+        return false;
+    }
+
+    default boolean isConnecting() {
+        return false;
+    }
+
+    default boolean isRoaming() {
+        return false;
+    }
+
+    default boolean isDisconnected() {
+        return true;
+    }
+
+    default boolean isSupplicantTransientState() {
+        return false;
+    }
+
+    default void onCellularConnectivityChanged(@WifiDataStall.CellularDataStatusCode int status) {}
+
+    default void probeLink(ClientMode.LinkProbeCallback callback, int mcs) {
+        callback.onFailure(ClientMode.LinkProbeCallback.LINK_PROBE_ERROR_NOT_CONNECTED);
+    }
+
+    default void sendMessageToClientModeImpl(Message msg) { }
+
+    default void setMboCellularDataStatus(boolean available) { }
+
+    default WifiNative.RoamingCapabilities getRoamingCapabilities() {
+        return null;
+    }
+
+    default boolean configureRoaming(WifiNative.RoamingConfig config) {
+        return false;
+    }
+
+    default boolean enableRoaming(boolean enabled) {
+        return false;
+    }
+
+    default boolean setCountryCode(String countryCode) {
+        return false;
+    }
+
+    default List<WifiNative.TxFateReport> getTxPktFates() {
+        return new ArrayList<>();
+    }
+
+    default List<WifiNative.RxFateReport> getRxPktFates() {
+        return new ArrayList<>();
+    }
+
+    default DeviceWiphyCapabilities getDeviceWiphyCapabilities() {
+        return null;
+    }
+
+    default boolean requestAnqp(String bssid, Set<Integer> anqpIds, Set<Integer> hs20Subtypes) {
+        return false;
+    }
+
+    default boolean requestVenueUrlAnqp(String bssid) {
+        return false;
+    }
+
+    default boolean requestIcon(String bssid, String fileName) {
+        return false;
+    }
+
+    @Override
+    default void setShouldReduceNetworkScore(boolean shouldReduceNetworkScore) { }
+}
diff --git a/service/java/com/android/server/wifi/ClientModeImpl.java b/service/java/com/android/server/wifi/ClientModeImpl.java
index 259fb5b..3e22b60 100644
--- a/service/java/com/android/server/wifi/ClientModeImpl.java
+++ b/service/java/com/android/server/wifi/ClientModeImpl.java
@@ -21,24 +21,24 @@
 import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.DISABLED_NO_INTERNET_TEMPORARY;
 import static android.net.wifi.WifiManager.WIFI_FEATURE_FILS_SHA256;
 import static android.net.wifi.WifiManager.WIFI_FEATURE_FILS_SHA384;
-import static android.net.wifi.WifiManager.WIFI_STATE_DISABLED;
-import static android.net.wifi.WifiManager.WIFI_STATE_DISABLING;
-import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED;
-import static android.net.wifi.WifiManager.WIFI_STATE_ENABLING;
-import static android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN;
 
-import static com.android.server.wifi.WifiDataStall.INVALID_THROUGHPUT;
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_PRIMARY;
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SECONDARY_LONG_LIVED;
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SECONDARY_TRANSIENT;
+import static com.android.server.wifi.WifiSettingsConfigStore.WIFI_STA_FACTORY_MAC_ADDRESS;
+import static com.android.server.wifi.proto.WifiStatsLog.WIFI_DISCONNECT_REPORTED__FAILURE_CODE__SUPPLICANT_DISCONNECTED;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
-import android.bluetooth.BluetoothAdapter;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.PackageManager;
+import android.hardware.wifi.supplicant.V1_0.ISupplicantStaIfaceCallback.ReasonCode;
+import android.hardware.wifi.supplicant.V1_0.ISupplicantStaIfaceCallback.StatusCode;
+import android.net.CaptivePortalData;
 import android.net.ConnectivityManager;
 import android.net.DhcpResultsParcelable;
 import android.net.InvalidPacketException;
@@ -47,7 +47,6 @@
 import android.net.Layer2PacketParcelable;
 import android.net.LinkProperties;
 import android.net.MacAddress;
-import android.net.MatchAllNetworkSpecifier;
 import android.net.NattKeepalivePacketData;
 import android.net.Network;
 import android.net.NetworkAgent;
@@ -55,7 +54,7 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
-import android.net.NetworkProvider;
+import android.net.RouteInfo;
 import android.net.SocketKeepalive;
 import android.net.StaticIpConfiguration;
 import android.net.TcpKeepalivePacketData;
@@ -67,25 +66,25 @@
 import android.net.shared.Layer2Information;
 import android.net.shared.ProvisioningConfiguration;
 import android.net.shared.ProvisioningConfiguration.ScanResultInfo;
-import android.net.wifi.IActionListener;
-import android.net.wifi.INetworkRequestMatchCallback;
+import android.net.vcn.VcnManager;
+import android.net.vcn.VcnNetworkPolicyResult;
+import android.net.wifi.IWifiConnectedNetworkScorer;
 import android.net.wifi.ScanResult;
+import android.net.wifi.SecurityParams;
 import android.net.wifi.SupplicantState;
 import android.net.wifi.WifiAnnotations.WifiStandard;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
-import android.net.wifi.WifiManager.DeviceMobilityState;
 import android.net.wifi.WifiNetworkAgentSpecifier;
-import android.net.wifi.WifiScanner;
+import android.net.wifi.WifiNetworkSpecifier;
 import android.net.wifi.hotspot2.IProvisioningCallback;
 import android.net.wifi.hotspot2.OsuProvider;
 import android.net.wifi.nl80211.DeviceWiphyCapabilities;
 import android.net.wifi.nl80211.WifiNl80211Manager;
-import android.net.wifi.p2p.WifiP2pManager;
 import android.os.BatteryStatsManager;
-import android.os.Bundle;
+import android.os.Build;
 import android.os.ConditionVariable;
 import android.os.IBinder;
 import android.os.Looper;
@@ -93,32 +92,35 @@
 import android.os.Messenger;
 import android.os.PowerManager;
 import android.os.Process;
-import android.os.RemoteException;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.os.WorkSource;
 import android.provider.Settings;
 import android.system.OsConstants;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
-import android.util.SparseArray;
+
+import androidx.annotation.RequiresApi;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.AsyncChannel;
-import com.android.internal.util.MessageUtils;
+import com.android.internal.util.IState;
 import com.android.internal.util.Protocol;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.android.modules.utils.HandlerExecutor;
 import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.Inet4AddressUtils;
 import com.android.net.module.util.MacAddressUtils;
 import com.android.net.module.util.NetUtils;
+import com.android.server.wifi.ActiveModeManager.ClientRole;
 import com.android.server.wifi.MboOceController.BtmFrameData;
 import com.android.server.wifi.WifiCarrierInfoManager.SimAuthRequestData;
 import com.android.server.wifi.WifiCarrierInfoManager.SimAuthResponseData;
+import com.android.server.wifi.WifiNative.RxFateReport;
+import com.android.server.wifi.WifiNative.TxFateReport;
 import com.android.server.wifi.hotspot2.AnqpEvent;
 import com.android.server.wifi.hotspot2.IconEvent;
 import com.android.server.wifi.hotspot2.NetworkDetail;
@@ -130,12 +132,12 @@
 import com.android.server.wifi.proto.nano.WifiMetricsProto.StaEvent;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.WifiIsUnusableEvent;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.WifiUsabilityStats;
-import com.android.server.wifi.util.ExternalCallbackTracker;
+import com.android.server.wifi.util.ActionListenerWrapper;
 import com.android.server.wifi.util.NativeUtil;
 import com.android.server.wifi.util.RssiUtil;
 import com.android.server.wifi.util.ScanResultUtil;
+import com.android.server.wifi.util.StateMachineObituary;
 import com.android.server.wifi.util.WifiPermissionsUtil;
-import com.android.server.wifi.util.WifiPermissionsWrapper;
 import com.android.wifi.resources.R;
 
 import java.io.BufferedReader;
@@ -149,53 +151,39 @@
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
+import java.net.URL;
 import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
 
 /**
  * Implementation of ClientMode.  Event handling for Client mode logic is done here,
  * and all changes in connectivity state are initiated here.
  *
- * @hide
+ * Note: No external modules should be calling into {@link ClientModeImpl}. Please plumb it via
+ * {@link ClientModeManager} until b/160014176 is fixed.
  */
-public class ClientModeImpl extends StateMachine {
-
+public class ClientModeImpl extends StateMachine implements ClientMode {
     private static final String NETWORKTYPE = "WIFI";
-    @VisibleForTesting public static final short NUM_LOG_RECS_NORMAL = 100;
     @VisibleForTesting public static final short NUM_LOG_RECS_VERBOSE_LOW_MEMORY = 200;
     @VisibleForTesting public static final short NUM_LOG_RECS_VERBOSE = 3000;
 
-    // Association rejection reason codes
-    @VisibleForTesting
-    protected static final int REASON_CODE_AP_UNABLE_TO_HANDLE_NEW_STA = 17;
-
     private static final String TAG = "WifiClientModeImpl";
 
-    private static final int ONE_HOUR_MILLI = 1000 * 60 * 60;
-
-    private static final String GOOGLE_OUI = "DA-A1-19";
-
-    private static final String EXTRA_OSU_ICON_QUERY_BSSID = "BSSID";
-    private static final String EXTRA_OSU_ICON_QUERY_FILENAME = "FILENAME";
-    private static final String EXTRA_OSU_PROVIDER = "OsuProvider";
-    private static final String EXTRA_UID = "uid";
-    private static final String EXTRA_PACKAGE_NAME = "PackageName";
-    private static final String EXTRA_PASSPOINT_CONFIGURATION = "PasspointConfiguration";
-    private static final int IPCLIENT_STARTUP_TIMEOUT_MS = 20 * 60 * 1000; // 20 minutes!
+    private static final int IPCLIENT_STARTUP_TIMEOUT_MS = 2_000;
     private static final int IPCLIENT_SHUTDOWN_TIMEOUT_MS = 60_000; // 60 seconds
+    @VisibleForTesting public static final long CONNECTING_WATCHDOG_TIMEOUT_MS = 30_000; // 30 secs.
+    @VisibleForTesting
+    public static final short NETWORK_NOT_FOUND_EVENT_THRESHOLD = 3;
 
     private boolean mVerboseLoggingEnabled = false;
-    private final WifiPermissionsWrapper mWifiPermissionsWrapper;
-
-    /* debug flag, indicating if handling of ASSOCIATION_REJECT ended up blacklisting
-     * the corresponding BSSID.
-     */
-    private boolean mDidBlackListBSSID = false;
 
     /**
      * Log with error attribute
@@ -204,66 +192,87 @@
      */
     @Override
     protected void loge(String s) {
-        Log.e(getName(), s);
+        Log.e(getTag(), s);
     }
     @Override
     protected void logd(String s) {
-        Log.d(getName(), s);
+        Log.d(getTag(), s);
     }
     @Override
     protected void log(String s) {
-        Log.d(getName(), s);
+        Log.d(getTag(), s);
     }
+    private final Context mContext;
     private final WifiMetrics mWifiMetrics;
-    private final WifiInjector mWifiInjector;
     private final WifiMonitor mWifiMonitor;
     private final WifiNative mWifiNative;
     private final WifiPermissionsUtil mWifiPermissionsUtil;
     private final WifiConfigManager mWifiConfigManager;
     private final WifiConnectivityManager mWifiConnectivityManager;
-    private final BssidBlocklistMonitor mBssidBlocklistMonitor;
-    private ConnectivityManager mCm;
-    private BaseWifiDiagnostics mWifiDiagnostics;
-    private final boolean mP2pSupported;
-    private final AtomicBoolean mP2pConnected = new AtomicBoolean(false);
-    private boolean mTemporarilyDisconnectWifi = false;
+    private final WifiBlocklistMonitor mWifiBlocklistMonitor;
+    private final WifiDiagnostics mWifiDiagnostics;
     private final Clock mClock;
-    private final PropertyService mPropertyService;
-    private final BuildProperties mBuildProperties;
-    private final WifiCountryCode mCountryCode;
     private final WifiScoreCard mWifiScoreCard;
     private final WifiHealthMonitor mWifiHealthMonitor;
     private final WifiScoreReport mWifiScoreReport;
-    private final SarManager mSarManager;
     private final WifiTrafficPoller mWifiTrafficPoller;
-    public WifiScoreReport getWifiScoreReport() {
-        return mWifiScoreReport;
-    }
     private final PasspointManager mPasspointManager;
     private final WifiDataStall mWifiDataStall;
     private final LinkProbeManager mLinkProbeManager;
     private final MboOceController mMboOceController;
-
     private final McastLockManagerFilterController mMcastLockManagerFilterController;
     private final ActivityManager mActivityManager;
+    private final FrameworkFacade mFacade;
+    private final WifiStateTracker mWifiStateTracker;
+    private final WrongPasswordNotifier mWrongPasswordNotifier;
+    private final EapFailureNotifier mEapFailureNotifier;
+    private final SimRequiredNotifier mSimRequiredNotifier;
+    private final ConnectionFailureNotifier mConnectionFailureNotifier;
+    private final WifiNetworkSuggestionsManager mWifiNetworkSuggestionsManager;
+    private final ThroughputPredictor mThroughputPredictor;
+    private final DeviceConfigFacade mDeviceConfigFacade;
+    private final ScoringParams mScoringParams;
+    private final WifiThreadRunner mWifiThreadRunner;
+    private final ScanRequestProxy mScanRequestProxy;
+    private final WifiLastResortWatchdog mWifiLastResortWatchdog;
+    private final WakeupController mWakeupController;
+    private final WifiLockManager mWifiLockManager;
+    private final WifiP2pConnection mWifiP2pConnection;
+    private final WifiGlobals mWifiGlobals;
+    private final ClientModeManagerBroadcastQueue mBroadcastQueue;
+    private final TelephonyManager mTelephonyManager;
+    private final WifiSettingsConfigStore mSettingsConfigStore;
+    private final long mId;
 
     private boolean mScreenOn = false;
 
-    private String mInterfaceName;
+    private final String mInterfaceName;
+    private final ConcreteClientModeManager mClientModeManager;
 
     private int mLastSignalLevel = -1;
+    private int mLastTxKbps = -1;
+    private int mLastRxKbps = -1;
+    private int mLastScanRssi = WifiInfo.INVALID_RSSI;
     private String mLastBssid;
+    // TODO (b/162942761): Ensure this is reset when mTargetNetworkId is set.
     private int mLastNetworkId; // The network Id we successfully joined
     // The subId used by WifiConfiguration with SIM credential which was connected successfully
     private int mLastSubId;
     private String mLastSimBasedConnectionCarrierName;
+    private URL mTermsAndConditionsUrl; // Indicates that the Passpoint network is captive
+    @Nullable
+    private byte[] mCachedPacketFilter;
+    @Nullable
+    private WifiNative.ConnectionCapabilities mLastConnectionCapabilities;
 
-    private boolean mIpReachabilityDisconnectEnabled = true;
+    private String getTag() {
+        return TAG + "[" + (mInterfaceName == null ? "unknown" : mInterfaceName) + "]";
+    }
 
     private void processRssiThreshold(byte curRssi, int reason,
             WifiNative.WifiRssiEventHandler rssiHandler) {
         if (curRssi == Byte.MAX_VALUE || curRssi == Byte.MIN_VALUE) {
-            Log.wtf(TAG, "processRssiThreshold: Invalid rssi " + curRssi);
+            Log.wtf(getTag(), "processRssiThreshold: Invalid rssi " + curRssi);
             return;
         }
         for (int i = 0; i < mRssiRanges.length; i++) {
@@ -278,7 +287,7 @@
                 mWifiInfo.setRssi(curRssi);
                 updateCapabilities();
                 int ret = startRssiMonitoringOffload(maxRssi, minRssi, rssiHandler);
-                Log.d(TAG, "Re-program RSSI thresholds for " + getWhatToString(reason)
+                Log.d(getTag(), "Re-program RSSI thresholds for " + getWhatToString(reason)
                         + ": [" + minRssi + ", " + maxRssi + "], curRssi=" + curRssi
                         + " ret=" + ret);
                 break;
@@ -287,49 +296,11 @@
     }
 
     private boolean mEnableRssiPolling = false;
-    // Accessed via Binder thread ({get,set}PollRssiIntervalMsecs), and the main Wifi thread.
-    private volatile int mPollRssiIntervalMsecs = -1;
     private int mRssiPollToken = 0;
-    /* 3 operational states for STA operation: CONNECT_MODE, SCAN_ONLY_MODE, SCAN_ONLY_WIFI_OFF_MODE
-    * In CONNECT_MODE, the STA can scan and connect to an access point
-    * In SCAN_ONLY_MODE, the STA can only scan for access points
-    * In SCAN_ONLY_WIFI_OFF_MODE, the STA can only scan for access points with wifi toggle being off
-    */
-    private int mOperationalMode = DISABLED_MODE;
-
-    // variable indicating we are expecting a mode switch - do not attempt recovery for failures
-    private boolean mModeChange = false;
-
-    private ClientModeManager.Listener mClientModeCallback = null;
-
-    private boolean mBluetoothConnectionActive = false;
 
     private PowerManager.WakeLock mSuspendWakeLock;
 
     /**
-     * Maximum allowable interval in milliseconds between polling for RSSI and linkspeed
-     * information. This is also used as the polling interval for WifiTrafficPoller, which updates
-     * its data activity on every CMD_RSSI_POLL.
-     */
-    private static final int MAXIMUM_POLL_RSSI_INTERVAL_MSECS = 6000;
-
-    /**
-     * Interval in milliseconds between receiving a disconnect event
-     * while connected to a good AP, and handling the disconnect proper
-     */
-    private static final int LINK_FLAPPING_DEBOUNCE_MSEC = 4000;
-
-    /**
-     * Delay between supplicant restarts upon failure to establish connection
-     */
-    private static final int SUPPLICANT_RESTART_INTERVAL_MSECS = 5000;
-
-    /**
-     * Number of times we attempt to restart supplicant
-     */
-    private static final int SUPPLICANT_RESTART_TRIES = 5;
-
-    /**
      * Value to set in wpa_supplicant "bssid" field when we don't want to restrict connection to
      * a specific AP.
      */
@@ -341,11 +312,6 @@
      */
     private LinkProperties mLinkProperties;
 
-    /* Tracks sequence number on a periodic scan message */
-    private int mPeriodicScanToken = 0;
-
-    private Context mContext;
-
     private final Object mDhcpResultsParcelableLock = new Object();
     @NonNull
     private DhcpResultsParcelable mDhcpResultsParcelable = new DhcpResultsParcelable();
@@ -356,12 +322,16 @@
     // the state actually changed, and to deduce the state of the agent from the state of the
     // machine when generating the NetworkInfo for the broadcast.
     private DetailedState mNetworkAgentState;
-    private SupplicantStateTracker mSupplicantStateTracker;
+    private final SupplicantStateTracker mSupplicantStateTracker;
 
     // Indicates that framework is attempting to roam, set true on CMD_START_ROAM, set false when
     // wifi connects or fails to connect
     private boolean mIsAutoRoaming = false;
 
+    // Indicates that driver is attempting to allowlist roaming, set true on allowlist roam BSSID
+    // associated, set false when wifi connects or fails to connect
+    private boolean mIsLinkedNetworkRoaming = false;
+
     // Roaming failure count
     private int mRoamFailCount = 0;
 
@@ -371,22 +341,10 @@
     // 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
+    // TODO (b/162942761): Ensure this is reset when mLastNetworkId is set.
     private int mTargetNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
-    private long mLastDriverRoamAttempt = 0;
     private WifiConfiguration mTargetWifiConfiguration = null;
-
-    int getPollRssiIntervalMsecs() {
-        if (mPollRssiIntervalMsecs > 0) {
-            return mPollRssiIntervalMsecs;
-        }
-        return Math.min(mContext.getResources().getInteger(
-                R.integer.config_wifiPollRssiIntervalMilliseconds),
-                        MAXIMUM_POLL_RSSI_INTERVAL_MSECS);
-    }
-
-    void setPollRssiIntervalMsecs(int newPollIntervalMsecs) {
-        mPollRssiIntervalMsecs = newPollIntervalMsecs;
-    }
+    @Nullable private VcnManager mVcnManager = null;
 
     /**
      * Method to clear {@link #mTargetBssid} and reset the current connected network's
@@ -401,14 +359,14 @@
         if (config.BSSID != null) {
             bssid = config.BSSID;
             if (mVerboseLoggingEnabled) {
-                Log.d(TAG, "force BSSID to " + bssid + "due to config");
+                Log.d(getTag(), "force BSSID to " + bssid + "due to config");
             }
         }
         if (mVerboseLoggingEnabled) {
-            logd(dbg + " clearTargetBssid " + bssid + " key=" + config.getKey());
+            logd(dbg + " clearTargetBssid " + bssid + " key=" + config.getProfileKey());
         }
         mTargetBssid = bssid;
-        return mWifiNative.setConfiguredNetworkBSSID(mInterfaceName, bssid);
+        return mWifiNative.setNetworkBSSID(mInterfaceName, bssid);
     }
 
     /**
@@ -425,11 +383,12 @@
         if (config.BSSID != null) {
             bssid = config.BSSID;
             if (mVerboseLoggingEnabled) {
-                Log.d(TAG, "force BSSID to " + bssid + "due to config");
+                Log.d(getTag(), "force BSSID to " + bssid + "due to config");
             }
         }
         if (mVerboseLoggingEnabled) {
-            Log.d(TAG, "setTargetBssid set to " + bssid + " key=" + config.getKey());
+            Log.d(getTag(), "setTargetBssid set to " + bssid + " key="
+                    + config.getProfileKey());
         }
         mTargetBssid = bssid;
         config.getNetworkSelectionStatus().setNetworkSelectionBSSID(bssid);
@@ -439,37 +398,26 @@
     private volatile IpClientManager mIpClient;
     private IpClientCallbacksImpl mIpClientCallbacks;
 
-    // Channel for sending replies.
-    private AsyncChannel mReplyChannel = new AsyncChannel();
+    private final WifiNetworkFactory mNetworkFactory;
+    private final UntrustedWifiNetworkFactory mUntrustedNetworkFactory;
+    private final OemWifiNetworkFactory mOemWifiNetworkFactory;
 
-    // Used to initiate a connection with WifiP2pService
-    private AsyncChannel mWifiP2pChannel;
-
-    private WifiNetworkFactory mNetworkFactory;
-    private UntrustedWifiNetworkFactory mUntrustedNetworkFactory;
-    private WifiNetworkAgent mNetworkAgent;
+    @VisibleForTesting
+    @Nullable
+    WifiNetworkAgent mNetworkAgent;
 
     private byte[] mRssiRanges;
 
     // Used to filter out requests we couldn't possibly satisfy.
     private final NetworkCapabilities mNetworkCapabilitiesFilter;
 
-    private final ExternalCallbackTracker<IActionListener> mProcessingActionListeners;
-
     /* The base for wifi message types */
     static final int BASE = Protocol.BASE_WIFI;
-    /* BT state change, e.g., on or off */
-    static final int CMD_BLUETOOTH_ADAPTER_STATE_CHANGE                 = BASE + 31;
-    /* BT connection state change, e.g., connected or disconnected */
-    static final int CMD_BLUETOOTH_ADAPTER_CONNECTION_STATE_CHANGE      = BASE + 32;
 
-    /* Get adaptors */
-    static final int CMD_GET_SUPPORTED_FEATURES                         = BASE + 61;
-    /* Get Link Layer Stats thru HAL */
-    static final int CMD_GET_LINK_LAYER_STATS                           = BASE + 63;
+    /* BT connection state changed, e.g., connected/disconnected */
+    static final int CMD_BLUETOOTH_CONNECTION_STATE_CHANGE              = BASE + 31;
+
     /* Supplicant commands after driver start*/
-    /* Set operational mode. CONNECT, SCAN ONLY, SCAN_ONLY with Wi-Fi off mode */
-    static final int CMD_SET_OPERATIONAL_MODE                           = BASE + 72;
     /* Disconnect from a network */
     static final int CMD_DISCONNECT                                     = BASE + 73;
     /* Reconnect to a network */
@@ -477,18 +425,6 @@
     /* Reassociate to a network */
     static final int CMD_REASSOCIATE                                    = BASE + 75;
 
-    /* Controls suspend mode optimizations
-     *
-     * When high perf mode is enabled, suspend mode optimizations are disabled
-     *
-     * When high perf mode is disabled, suspend mode optimizations are enabled
-     *
-     * Suspend mode optimizations include:
-     * - packet filtering
-     * - turn off roaming
-     * - DTIM wake up settings
-     */
-    static final int CMD_SET_HIGH_PERF_MODE                             = BASE + 77;
     /* Enables RSSI poll */
     static final int CMD_ENABLE_RSSI_POLL                               = BASE + 82;
     /* RSSI poll */
@@ -498,9 +434,6 @@
     /* Enable suspend mode optimizations in the driver */
     static final int CMD_SET_SUSPEND_OPT_ENABLED                        = BASE + 86;
 
-    /* Enable TDLS on a specific MAC address */
-    static final int CMD_ENABLE_TDLS                                    = BASE + 92;
-
     /**
      * Watchdog for protecting against b/16823537
      * Leave time for 4-way handshake to succeed
@@ -514,7 +447,7 @@
     static final int CMD_SCREEN_STATE_CHANGED                           = BASE + 95;
 
     /* Disconnecting state watchdog */
-    static final int CMD_DISCONNECTING_WATCHDOG_TIMER                   = BASE + 96;
+    static final int CMD_CONNECTING_WATCHDOG_TIMER                      = BASE + 96;
 
     /* SIM is removed; reset any cached data for it */
     static final int CMD_RESET_SIM_NETWORKS                             = BASE + 101;
@@ -530,26 +463,8 @@
     static final int RESET_SIM_REASON_SIM_INSERTED             = 1;
     static final int RESET_SIM_REASON_DEFAULT_DATA_SIM_CHANGED = 2;
 
-    /* OSU APIs */
-    static final int CMD_QUERY_OSU_ICON                                 = BASE + 104;
-
-    /* Commands from/to the SupplicantStateTracker */
-    /* Reset the supplicant state tracker */
-    static final int CMD_RESET_SUPPLICANT_STATE                         = BASE + 111;
-
-    int mDisconnectingWatchdogCount = 0;
-    static final int DISCONNECTING_GUARD_TIMER_MSEC = 5000;
-
-    /**
-     * Indicates the end of boot process, should be used to trigger load from config store,
-     * initiate connection attempt, etc.
-     * */
-    static final int CMD_BOOT_COMPLETED                                 = BASE + 134;
-    /**
-     * Initialize ClientModeImpl. This is currently used to initialize the
-     * {@link HalDeviceManager} module.
-     */
-    static final int CMD_INITIALIZE                                     = BASE + 135;
+    /** Connecting watchdog timeout counter */
+    private int mConnectingWatchdogCount = 0;
 
     /* We now have a valid IP configuration. */
     static final int CMD_IP_CONFIGURATION_SUCCESSFUL                    = BASE + 138;
@@ -617,9 +532,6 @@
     /* Indicates that diagnostics should time out a connection start event. */
     static final int CMD_DIAGS_CONNECT_TIMEOUT                          = BASE + 252;
 
-    // Start subscription provisioning with a given provider
-    private static final int CMD_START_SUBSCRIPTION_PROVISIONING        = BASE + 254;
-
     @VisibleForTesting
     static final int CMD_PRE_DHCP_ACTION                                = BASE + 255;
     private static final int CMD_PRE_DHCP_ACTION_COMPLETE               = BASE + 256;
@@ -631,27 +543,7 @@
     /* Start connection to FILS AP*/
     static final int CMD_START_FILS_CONNECTION                          = BASE + 262;
 
-    private static final int CMD_GET_CURRENT_NETWORK                    = BASE + 263;
-
-    // For message logging.
-    private static final Class[] sMessageClasses = {
-            AsyncChannel.class, ClientModeImpl.class };
-    private static final SparseArray<String> sGetWhatToString =
-            MessageUtils.findMessageNames(sMessageClasses);
-
-
-    /* Wifi state machine modes of operation */
-    /* CONNECT_MODE - connect to any 'known' AP when it becomes available */
-    public static final int CONNECT_MODE = 1;
-    /* SCAN_ONLY_MODE - don't connect to any APs; scan, but only while apps hold lock */
-    public static final int SCAN_ONLY_MODE = 2;
-    /* SCAN_ONLY_WITH_WIFI_OFF - scan, but don't connect to any APs */
-    public static final int SCAN_ONLY_WITH_WIFI_OFF_MODE = 3;
-    /* DISABLED_MODE - Don't connect, don't scan, don't be an AP */
-    public static final int DISABLED_MODE = 4;
-
-    private static final int SUCCESS = 1;
-    private static final int FAILURE = -1;
+    static final int CMD_CONNECTABLE_STATE_SETUP                        = BASE + 300;
 
     /* Tracks if suspend optimizations need to be disabled by DHCP,
      * screen or due to high perf mode.
@@ -664,39 +556,28 @@
     private static final int SUSPEND_DUE_TO_HIGH_PERF = 1 << 1;
     private static final int SUSPEND_DUE_TO_SCREEN = 1 << 2;
 
-    /**
-     * Time window in milliseconds for which we send
-     * {@link NetworkAgent#explicitlySelected(boolean, boolean)}
-     * after connecting to the network which the user last selected.
-     */
+    /** @see #isRecentlySelectedByTheUser */
     @VisibleForTesting
     public static final int LAST_SELECTED_NETWORK_EXPIRATION_AGE_MILLIS = 30 * 1000;
 
     /* Tracks if user has enabled Connected Mac Randomization through settings */
 
-    /**
-     * Supplicant scan interval in milliseconds.
-     * Comes from {@link Settings.Global#WIFI_SUPPLICANT_SCAN_INTERVAL_MS} or
-     * from the default config if the setting is not set
-     */
-    private long mSupplicantScanIntervalMs;
-
     int mRunningBeaconCount = 0;
 
-    /* Default parent state */
-    private State mDefaultState = new DefaultState();
+    /* Parent state where connections are allowed */
+    private State mConnectableState = new ConnectableState();
+    /* Connecting/Connected to an access point */
+    private State mConnectingOrConnectedState = new ConnectingOrConnectedState();
     /* Connecting to an access point */
-    private State mConnectModeState = new ConnectModeState();
+    private State mL2ConnectingState = new L2ConnectingState();
     /* Connected at 802.11 (L2) level */
     private State mL2ConnectedState = new L2ConnectedState();
     /* fetching IP after connection to access point (assoc+auth complete) */
-    private State mObtainingIpState = new ObtainingIpState();
+    private State mL3ProvisioningState = new L3ProvisioningState();
     /* Connected with IP addr */
-    private State mConnectedState = new ConnectedState();
+    private State mL3ConnectedState = new L3ConnectedState();
     /* Roaming */
     private State mRoamingState = new RoamingState();
-    /* disconnect issued, waiting for network disconnect confirmation */
-    private State mDisconnectingState = new DisconnectingState();
     /* Network is not connected, supplicant assoc+auth is not complete */
     private State mDisconnectedState = new DisconnectedState();
 
@@ -709,15 +590,6 @@
     private boolean mIpClientWithPreConnection = false;
 
     /**
-     * One of  {@link WifiManager#WIFI_STATE_DISABLED},
-     * {@link WifiManager#WIFI_STATE_DISABLING},
-     * {@link WifiManager#WIFI_STATE_ENABLED},
-     * {@link WifiManager#WIFI_STATE_ENABLING},
-     * {@link WifiManager#WIFI_STATE_UNKNOWN}
-     */
-    private final AtomicInteger mWifiState = new AtomicInteger(WIFI_STATE_DISABLED);
-
-    /**
      * Work source to use to blame usage on the WiFi service
      */
     public static final WorkSource WIFI_WORK_SOURCE = new WorkSource(Process.WIFI_UID);
@@ -726,79 +598,124 @@
 
     private final WifiCarrierInfoManager mWifiCarrierInfoManager;
 
+    private final OnNetworkUpdateListener mOnNetworkUpdateListener;
 
-    // Used for debug and stats gathering
-    private static int sScanAlarmIntentCount = 0;
+    private final OnCarrierOffloadDisabledListener mOnCarrierOffloadDisabledListener;
 
-    private FrameworkFacade mFacade;
-    private WifiStateTracker mWifiStateTracker;
-    private final BackupManagerProxy mBackupManagerProxy;
-    private final WrongPasswordNotifier mWrongPasswordNotifier;
-    private final EapFailureNotifier mEapFailureNotifier;
-    private final SimRequiredNotifier mSimRequiredNotifier;
-    private final ConnectionFailureNotifier mConnectionFailureNotifier;
-    private WifiNetworkSuggestionsManager mWifiNetworkSuggestionsManager;
+    private final ClientModeImplMonitor mCmiMonitor;
+
+    private final WifiNetworkSelector mWifiNetworkSelector;
+
+    private final WifiInjector mWifiInjector;
+
     // Maximum duration to continue to log Wifi usability stats after a data stall is triggered.
     @VisibleForTesting
     public static final long DURATION_TO_WAIT_ADD_STATS_AFTER_DATA_STALL_MS = 30 * 1000;
     private long mDataStallTriggerTimeMs = -1;
     private int mLastStatusDataStall = WifiIsUnusableEvent.TYPE_UNKNOWN;
 
-    public ClientModeImpl(Context context, FrameworkFacade facade, Looper looper,
-                            UserManager userManager, WifiInjector wifiInjector,
-                            BackupManagerProxy backupManagerProxy, WifiCountryCode countryCode,
-                            WifiNative wifiNative, WrongPasswordNotifier wrongPasswordNotifier,
-                            SarManager sarManager, WifiTrafficPoller wifiTrafficPoller,
-                            LinkProbeManager linkProbeManager,
-                            BatteryStatsManager batteryStatsManager,
-                            SupplicantStateTracker supplicantStateTracker,
-                            MboOceController mboOceController,
-                            WifiCarrierInfoManager wifiCarrierInfoManager,
-                            EapFailureNotifier eapFailureNotifier,
-                            SimRequiredNotifier simRequiredNotifier) {
+    @Nullable
+    private StateMachineObituary mObituary = null;
+
+    @Nullable
+    private WifiVcnNetworkPolicyChangeListener mVcnPolicyChangeListener;
+
+    /** NETWORK_NOT_FOUND_EVENT event counter */
+    private int mNetworkNotFoundEventCount = 0;
+
+    /** Note that this constructor will also start() the StateMachine. */
+    public ClientModeImpl(
+            @NonNull Context context,
+            @NonNull WifiMetrics wifiMetrics,
+            @NonNull Clock clock,
+            @NonNull WifiScoreCard wifiScoreCard,
+            @NonNull WifiStateTracker wifiStateTracker,
+            @NonNull WifiPermissionsUtil wifiPermissionsUtil,
+            @NonNull WifiConfigManager wifiConfigManager,
+            @NonNull PasspointManager passpointManager,
+            @NonNull WifiMonitor wifiMonitor,
+            @NonNull WifiDiagnostics wifiDiagnostics,
+            @NonNull WifiDataStall wifiDataStall,
+            @NonNull ScoringParams scoringParams,
+            @NonNull WifiThreadRunner wifiThreadRunner,
+            @NonNull WifiNetworkSuggestionsManager wifiNetworkSuggestionsManager,
+            @NonNull WifiHealthMonitor wifiHealthMonitor,
+            @NonNull ThroughputPredictor throughputPredictor,
+            @NonNull DeviceConfigFacade deviceConfigFacade,
+            @NonNull ScanRequestProxy scanRequestProxy,
+            @NonNull ExtendedWifiInfo wifiInfo,
+            @NonNull WifiConnectivityManager wifiConnectivityManager,
+            @NonNull WifiBlocklistMonitor wifiBlocklistMonitor,
+            @NonNull ConnectionFailureNotifier connectionFailureNotifier,
+            @NonNull NetworkCapabilities networkCapabilitiesFilter,
+            @NonNull WifiNetworkFactory networkFactory,
+            @NonNull UntrustedWifiNetworkFactory untrustedWifiNetworkFactory,
+            @NonNull OemWifiNetworkFactory oemPaidWifiNetworkFactory,
+            @NonNull WifiLastResortWatchdog wifiLastResortWatchdog,
+            @NonNull WakeupController wakeupController,
+            @NonNull WifiLockManager wifiLockManager,
+            @NonNull FrameworkFacade facade,
+            @NonNull Looper looper,
+            @NonNull WifiNative wifiNative,
+            @NonNull WrongPasswordNotifier wrongPasswordNotifier,
+            @NonNull WifiTrafficPoller wifiTrafficPoller,
+            @NonNull LinkProbeManager linkProbeManager,
+            long id,
+            @NonNull BatteryStatsManager batteryStatsManager,
+            @NonNull SupplicantStateTracker supplicantStateTracker,
+            @NonNull MboOceController mboOceController,
+            @NonNull WifiCarrierInfoManager wifiCarrierInfoManager,
+            @NonNull EapFailureNotifier eapFailureNotifier,
+            @NonNull SimRequiredNotifier simRequiredNotifier,
+            @NonNull WifiScoreReport wifiScoreReport,
+            @NonNull WifiP2pConnection wifiP2pConnection,
+            @NonNull WifiGlobals wifiGlobals,
+            @NonNull String ifaceName,
+            @NonNull ConcreteClientModeManager clientModeManager,
+            @NonNull ClientModeImplMonitor cmiMonitor,
+            @NonNull ClientModeManagerBroadcastQueue broadcastQueue,
+            @NonNull WifiNetworkSelector wifiNetworkSelector,
+            @NonNull TelephonyManager telephonyManager,
+            @NonNull WifiInjector wifiInjector,
+            @NonNull WifiSettingsConfigStore settingsConfigStore,
+            boolean verboseLoggingEnabled) {
         super(TAG, looper);
-        mWifiInjector = wifiInjector;
-        mWifiMetrics = mWifiInjector.getWifiMetrics();
-        mClock = wifiInjector.getClock();
-        mPropertyService = wifiInjector.getPropertyService();
-        mBuildProperties = wifiInjector.getBuildProperties();
-        mWifiScoreCard = wifiInjector.getWifiScoreCard();
+        mWifiMetrics = wifiMetrics;
+        mClock = clock;
+        mWifiScoreCard = wifiScoreCard;
         mContext = context;
         mFacade = facade;
         mWifiNative = wifiNative;
-        mBackupManagerProxy = backupManagerProxy;
         mWrongPasswordNotifier = wrongPasswordNotifier;
+        mId = id;
         mEapFailureNotifier = eapFailureNotifier;
         mSimRequiredNotifier = simRequiredNotifier;
-        mSarManager = sarManager;
         mWifiTrafficPoller = wifiTrafficPoller;
         mLinkProbeManager = linkProbeManager;
         mMboOceController = mboOceController;
         mWifiCarrierInfoManager = wifiCarrierInfoManager;
+        mBroadcastQueue = broadcastQueue;
         mNetworkAgentState = DetailedState.DISCONNECTED;
 
         mBatteryStatsManager = batteryStatsManager;
-        mWifiStateTracker = wifiInjector.getWifiStateTracker();
+        mWifiStateTracker = wifiStateTracker;
 
-        mP2pSupported = mContext.getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_WIFI_DIRECT);
+        mWifiPermissionsUtil = wifiPermissionsUtil;
+        mWifiConfigManager = wifiConfigManager;
 
-        mWifiPermissionsUtil = mWifiInjector.getWifiPermissionsUtil();
-        mWifiConfigManager = mWifiInjector.getWifiConfigManager();
+        mPasspointManager = passpointManager;
 
-        mPasspointManager = mWifiInjector.getPasspointManager();
+        mWifiMonitor = wifiMonitor;
+        mWifiDiagnostics = wifiDiagnostics;
+        mWifiDataStall = wifiDataStall;
+        mThroughputPredictor = throughputPredictor;
+        mDeviceConfigFacade = deviceConfigFacade;
 
-        mWifiMonitor = mWifiInjector.getWifiMonitor();
-        mWifiDiagnostics = mWifiInjector.getWifiDiagnostics();
-        mWifiPermissionsWrapper = mWifiInjector.getWifiPermissionsWrapper();
-        mWifiDataStall = mWifiInjector.getWifiDataStall();
-
-        mWifiInfo = new ExtendedWifiInfo(context);
+        mWifiInfo = wifiInfo;
         mSupplicantStateTracker = supplicantStateTracker;
-        mWifiConnectivityManager = mWifiInjector.makeWifiConnectivityManager(this);
-        mBssidBlocklistMonitor = mWifiInjector.getBssidBlocklistMonitor();
-        mConnectionFailureNotifier = mWifiInjector.makeConnectionFailureNotifier(
-                mWifiConnectivityManager);
+        mWifiConnectivityManager = wifiConnectivityManager;
+        mWifiBlocklistMonitor = wifiBlocklistMonitor;
+        mConnectionFailureNotifier = connectionFailureNotifier;
 
         mLinkProperties = new LinkProperties();
         mMcastLockManagerFilterController = new McastLockManagerFilterController();
@@ -810,147 +727,120 @@
         mLastSimBasedConnectionCarrierName = null;
         mLastSignalLevel = -1;
 
-        mCountryCode = countryCode;
+        mScoringParams = scoringParams;
+        mWifiThreadRunner = wifiThreadRunner;
+        mScanRequestProxy = scanRequestProxy;
+        mWifiScoreReport = wifiScoreReport;
 
-        mWifiScoreReport = new WifiScoreReport(mWifiInjector.getScoringParams(), mClock,
-                mWifiMetrics, mWifiInfo, mWifiNative, mBssidBlocklistMonitor,
-                mWifiInjector.getWifiThreadRunner(), mWifiInjector.getDeviceConfigFacade(),
-                mContext, looper, mFacade);
+        mNetworkCapabilitiesFilter = networkCapabilitiesFilter;
+        mNetworkFactory = networkFactory;
 
-        NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder()
-                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
-                // TODO - needs to be a bit more dynamic
-                .setLinkUpstreamBandwidthKbps(1024 * 1024)
-                .setLinkDownstreamBandwidthKbps(1024 * 1024)
-                .setNetworkSpecifier(new MatchAllNetworkSpecifier());
-        if (SdkLevel.isAtLeastS()) {
-            builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
-        }
-        mNetworkCapabilitiesFilter = builder.build();
+        mUntrustedNetworkFactory = untrustedWifiNetworkFactory;
+        mOemWifiNetworkFactory = oemPaidWifiNetworkFactory;
 
-        // Make the network factories.
-        mNetworkFactory = mWifiInjector.makeWifiNetworkFactory(
-                mNetworkCapabilitiesFilter, mWifiConnectivityManager);
-        // We can't filter untrusted network in the capabilities filter because a trusted
-        // network would still satisfy a request that accepts untrusted ones.
-        // We need a second network factory for untrusted network requests because we need a
-        // different score filter for these requests.
-        mUntrustedNetworkFactory = mWifiInjector.makeUntrustedWifiNetworkFactory(
-                mNetworkCapabilitiesFilter, mWifiConnectivityManager);
+        mWifiLastResortWatchdog = wifiLastResortWatchdog;
+        mWakeupController = wakeupController;
+        mWifiLockManager = wifiLockManager;
 
-        mWifiNetworkSuggestionsManager = mWifiInjector.getWifiNetworkSuggestionsManager();
-        mProcessingActionListeners = new ExternalCallbackTracker<>(getHandler());
-        mWifiHealthMonitor = mWifiInjector.getWifiHealthMonitor();
+        mWifiNetworkSuggestionsManager = wifiNetworkSuggestionsManager;
+        mWifiHealthMonitor = wifiHealthMonitor;
+        mWifiP2pConnection = wifiP2pConnection;
+        mWifiGlobals = wifiGlobals;
 
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_SCREEN_ON);
-        filter.addAction(Intent.ACTION_SCREEN_OFF);
-        mContext.registerReceiver(
-                new BroadcastReceiver() {
-                    @Override
-                    public void onReceive(Context context, Intent intent) {
-                        String action = intent.getAction();
-
-                        if (action.equals(Intent.ACTION_SCREEN_ON)) {
-                            sendMessage(CMD_SCREEN_STATE_CHANGED, 1);
-                        } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
-                            sendMessage(CMD_SCREEN_STATE_CHANGED, 0);
-                        }
-                    }
-                }, filter);
+        mInterfaceName = ifaceName;
+        mClientModeManager = clientModeManager;
+        mCmiMonitor = cmiMonitor;
+        mTelephonyManager = telephonyManager;
+        mSettingsConfigStore = settingsConfigStore;
+        updateInterfaceCapabilities();
 
         PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
 
         mSuspendWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WifiSuspend");
         mSuspendWakeLock.setReferenceCounted(false);
 
-        mWifiConfigManager.addOnNetworkUpdateListener(new OnNetworkUpdateListener());
+        mOnNetworkUpdateListener = new OnNetworkUpdateListener();
+        mWifiConfigManager.addOnNetworkUpdateListener(mOnNetworkUpdateListener);
 
-        // CHECKSTYLE:OFF IndentationCheck
-        addState(mDefaultState);
-            addState(mConnectModeState, mDefaultState);
-                addState(mL2ConnectedState, mConnectModeState);
-                    addState(mObtainingIpState, mL2ConnectedState);
-                    addState(mConnectedState, mL2ConnectedState);
+        mOnCarrierOffloadDisabledListener = new OnCarrierOffloadDisabledListener();
+        mWifiCarrierInfoManager.addOnCarrierOffloadDisabledListener(
+                mOnCarrierOffloadDisabledListener);
+
+        mWifiNetworkSelector = wifiNetworkSelector;
+        mWifiInjector = wifiInjector;
+
+        enableVerboseLogging(verboseLoggingEnabled);
+
+        addState(mConnectableState); {
+            addState(mConnectingOrConnectedState, mConnectableState); {
+                addState(mL2ConnectingState, mConnectingOrConnectedState);
+                addState(mL2ConnectedState, mConnectingOrConnectedState); {
+                    addState(mL3ProvisioningState, mL2ConnectedState);
+                    addState(mL3ConnectedState, mL2ConnectedState);
                     addState(mRoamingState, mL2ConnectedState);
-                addState(mDisconnectingState, mConnectModeState);
-                addState(mDisconnectedState, mConnectModeState);
-        // CHECKSTYLE:ON IndentationCheck
+                }
+            }
+            addState(mDisconnectedState, mConnectableState);
+        }
 
-        setInitialState(mDefaultState);
+        setInitialState(mDisconnectedState);
 
-        setLogRecSize(NUM_LOG_RECS_NORMAL);
         setLogOnlyTransitions(false);
+
+        // Start the StateMachine
+        start();
+
+        // update with initial role for ConcreteClientModeManager
+        onRoleChanged();
     }
 
-    @Override
-    public void start() {
-        super.start();
-
-        PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-
-        // Learn the initial state of whether the screen is on.
-        // We update this field when we receive broadcasts from the system.
-        handleScreenStateChanged(powerManager.isInteractive());
-    }
+    private static final int[] WIFI_MONITOR_EVENTS = {
+            WifiMonitor.TARGET_BSSID_EVENT,
+            WifiMonitor.ASSOCIATED_BSSID_EVENT,
+            WifiMonitor.ANQP_DONE_EVENT,
+            WifiMonitor.ASSOCIATION_REJECTION_EVENT,
+            WifiMonitor.AUTHENTICATION_FAILURE_EVENT,
+            WifiMonitor.GAS_QUERY_DONE_EVENT,
+            WifiMonitor.GAS_QUERY_START_EVENT,
+            WifiMonitor.HS20_REMEDIATION_EVENT,
+            WifiMonitor.HS20_DEAUTH_IMMINENT_EVENT,
+            WifiMonitor.HS20_TERMS_AND_CONDITIONS_ACCEPTANCE_REQUIRED_EVENT,
+            WifiMonitor.NETWORK_CONNECTION_EVENT,
+            WifiMonitor.NETWORK_DISCONNECTION_EVENT,
+            WifiMonitor.RX_HS20_ANQP_ICON_EVENT,
+            WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT,
+            WifiMonitor.SUP_REQUEST_IDENTITY,
+            WifiMonitor.SUP_REQUEST_SIM_AUTH,
+            WifiMonitor.MBO_OCE_BSS_TM_HANDLING_DONE,
+            WifiMonitor.TRANSITION_DISABLE_INDICATION,
+            WifiMonitor.NETWORK_NOT_FOUND_EVENT,
+    };
 
     private void registerForWifiMonitorEvents()  {
-        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.TARGET_BSSID_EVENT, getHandler());
-        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.ASSOCIATED_BSSID_EVENT,
-                getHandler());
-        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.ANQP_DONE_EVENT, getHandler());
-        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.ASSOCIATION_REJECTION_EVENT,
-                getHandler());
-        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.AUTHENTICATION_FAILURE_EVENT,
-                getHandler());
-        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.GAS_QUERY_DONE_EVENT,
-                getHandler());
-        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.GAS_QUERY_START_EVENT,
-                getHandler());
-        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.HS20_REMEDIATION_EVENT,
-                getHandler());
-        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.NETWORK_CONNECTION_EVENT,
-                getHandler());
-        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.NETWORK_DISCONNECTION_EVENT,
-                getHandler());
-        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.RX_HS20_ANQP_ICON_EVENT,
-                getHandler());
-        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT,
-                getHandler());
-        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.SUP_REQUEST_IDENTITY,
-                getHandler());
-        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.SUP_REQUEST_SIM_AUTH,
-                getHandler());
-        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.ASSOCIATION_REJECTION_EVENT,
-                mWifiMetrics.getHandler());
-        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.AUTHENTICATION_FAILURE_EVENT,
-                mWifiMetrics.getHandler());
-        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.NETWORK_CONNECTION_EVENT,
-                mWifiMetrics.getHandler());
-        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.NETWORK_DISCONNECTION_EVENT,
-                mWifiMetrics.getHandler());
-        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT,
-                mWifiMetrics.getHandler());
-        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.ASSOCIATED_BSSID_EVENT,
-                mWifiMetrics.getHandler());
-        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.TARGET_BSSID_EVENT,
-                mWifiMetrics.getHandler());
-        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.NETWORK_CONNECTION_EVENT,
-                mWifiInjector.getWifiLastResortWatchdog().getHandler());
-        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.ASSOCIATION_REJECTION_EVENT,
-                mSupplicantStateTracker.getHandler());
-        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.AUTHENTICATION_FAILURE_EVENT,
-                mSupplicantStateTracker.getHandler());
-        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT,
-                mSupplicantStateTracker.getHandler());
-        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.MBO_OCE_BSS_TM_HANDLING_DONE,
-                getHandler());
+        for (int event : WIFI_MONITOR_EVENTS) {
+            mWifiMonitor.registerHandler(mInterfaceName, event, getHandler());
+        }
+
+        mWifiMetrics.registerForWifiMonitorEvents(mInterfaceName);
+        mWifiLastResortWatchdog.registerForWifiMonitorEvents(mInterfaceName);
+    }
+
+    private void deregisterForWifiMonitorEvents()  {
+        for (int event : WIFI_MONITOR_EVENTS) {
+            mWifiMonitor.deregisterHandler(mInterfaceName, event, getHandler());
+        }
+
+        mWifiMetrics.deregisterForWifiMonitorEvents(mInterfaceName);
+        mWifiLastResortWatchdog.deregisterForWifiMonitorEvents(mInterfaceName);
+    }
+
+    private static boolean isValidBssid(String bssidStr) {
+        try {
+            MacAddress bssid = MacAddress.fromString(bssidStr);
+            return !bssid.equals(WifiManager.ALL_ZEROS_MAC_ADDRESS);
+        } catch (IllegalArgumentException e) {
+            return false;
+        }
     }
 
     private void setMulticastFilter(boolean enabled) {
@@ -984,7 +874,14 @@
 
         @Override
         public void onIpClientCreated(IIpClient ipClient) {
-            mIpClient = new IpClientManager(ipClient, getName());
+            // IpClient may take a very long time (many minutes) to start at boot time. But after
+            // that IpClient should start pretty quickly (a few seconds).
+            // Blocking wait for 5 seconds first (for when the wait is short)
+            // If IpClient is still not ready after blocking wait, async wait (for when wait is
+            // long). Will drop all connection requests until IpClient is ready. Other requests
+            // will still be processed.
+            sendMessageAtFrontOfQueue(CMD_CONNECTABLE_STATE_SETUP,
+                    new IpClientManager(ipClient, getName()));
             mWaitForCreationCv.open();
         }
 
@@ -1009,25 +906,27 @@
 
         @Override
         public void onProvisioningSuccess(LinkProperties newLp) {
-            mWifiMetrics.logStaEvent(StaEvent.TYPE_CMD_IP_CONFIGURATION_SUCCESSFUL);
+            addPasspointInfoToLinkProperties(newLp);
+            mWifiMetrics.logStaEvent(mInterfaceName, StaEvent.TYPE_CMD_IP_CONFIGURATION_SUCCESSFUL);
             sendMessage(CMD_UPDATE_LINKPROPERTIES, newLp);
             sendMessage(CMD_IP_CONFIGURATION_SUCCESSFUL);
         }
 
         @Override
         public void onProvisioningFailure(LinkProperties newLp) {
-            mWifiMetrics.logStaEvent(StaEvent.TYPE_CMD_IP_CONFIGURATION_LOST);
+            mWifiMetrics.logStaEvent(mInterfaceName, StaEvent.TYPE_CMD_IP_CONFIGURATION_LOST);
             sendMessage(CMD_IP_CONFIGURATION_LOST);
         }
 
         @Override
         public void onLinkPropertiesChange(LinkProperties newLp) {
+            addPasspointInfoToLinkProperties(newLp);
             sendMessage(CMD_UPDATE_LINKPROPERTIES, newLp);
         }
 
         @Override
         public void onReachabilityLost(String logMsg) {
-            mWifiMetrics.logStaEvent(StaEvent.TYPE_CMD_IP_REACHABILITY_LOST);
+            mWifiMetrics.logStaEvent(mInterfaceName, StaEvent.TYPE_CMD_IP_REACHABILITY_LOST);
             sendMessage(CMD_IP_REACHABILITY_LOST, logMsg);
         }
 
@@ -1072,7 +971,8 @@
 
     private void stopIpClient() {
         if (mVerboseLoggingEnabled) {
-            Log.v(TAG, "stopIpClient IpClientWithPreConnection: " + mIpClientWithPreConnection);
+            Log.v(getTag(), "stopIpClient IpClientWithPreConnection: "
+                    + mIpClientWithPreConnection);
         }
         if (mIpClient != null) {
             if (mIpClientWithPreConnection) {
@@ -1098,17 +998,17 @@
     private class OnNetworkUpdateListener implements
             WifiConfigManager.OnNetworkUpdateListener {
         @Override
-        public void onNetworkAdded(WifiConfiguration config) { }
-
-        @Override
-        public void onNetworkEnabled(WifiConfiguration config) { }
-
-        @Override
         public void onNetworkRemoved(WifiConfiguration config) {
             // The current connected or connecting network has been removed, trigger a disconnect.
             if (config.networkId == mTargetNetworkId || config.networkId == mLastNetworkId) {
                 // Disconnect and let autojoin reselect a new network
-                sendMessage(CMD_DISCONNECT);
+                sendMessage(CMD_DISCONNECT, StaEvent.DISCONNECT_NETWORK_REMOVED);
+            } else {
+                WifiConfiguration currentConfig = getConnectedWifiConfiguration();
+                if (currentConfig != null && currentConfig.isLinked(config)) {
+                    logi("current network linked config removed, update allowlist networks");
+                    updateLinkedNetworks(currentConfig);
+                }
             }
             mWifiNative.removeNetworkCachedData(config.networkId);
         }
@@ -1119,7 +1019,7 @@
             mWifiNative.removeNetworkCachedData(oldConfig.networkId);
 
             if (WifiConfigurationUtil.hasCredentialChanged(oldConfig, newConfig)) {
-                mBssidBlocklistMonitor.handleNetworkRemoved(newConfig.SSID);
+                mWifiBlocklistMonitor.handleNetworkRemoved(newConfig.SSID);
             }
 
             // Check if user/app change meteredOverride for connected network.
@@ -1133,17 +1033,18 @@
             if (isMetered == wasMetered) {
                 // no meteredness change, nothing to do.
                 if (mVerboseLoggingEnabled) {
-                    Log.v(TAG, "User/app changed meteredOverride, but no change in meteredness");
+                    Log.v(getTag(), "User/app changed meteredOverride, "
+                            + "but no change in meteredness");
                 }
                 return;
             }
             // If unmetered->metered trigger a disconnect.
             // If metered->unmetered update capabilities.
             if (isMetered) {
-                Log.w(TAG, "Network marked metered, triggering disconnect");
-                sendMessage(CMD_DISCONNECT);
+                Log.w(getTag(), "Network marked metered, triggering disconnect");
+                sendMessage(CMD_DISCONNECT, StaEvent.DISCONNECT_NETWORK_METERED);
             } else {
-                Log.i(TAG, "Network marked unmetered, triggering capabilities update");
+                Log.i(getTag(), "Network marked unmetered, triggering capabilities update");
                 updateCapabilities(newConfig);
             }
         }
@@ -1153,7 +1054,7 @@
             if (disableReason == DISABLED_NO_INTERNET_TEMPORARY) return;
             if (config.networkId == mTargetNetworkId || config.networkId == mLastNetworkId) {
                 // Disconnect and let autojoin reselect a new network
-                sendMessage(CMD_DISCONNECT);
+                sendMessage(CMD_DISCONNECT, StaEvent.DISCONNECT_NETWORK_TEMPORARY_DISABLED);
             }
 
         }
@@ -1167,16 +1068,28 @@
             if (disableReason == DISABLED_NO_INTERNET_PERMANENT) return;
             if (config.networkId == mTargetNetworkId || config.networkId == mLastNetworkId) {
                 // Disconnect and let autojoin reselect a new network
-                sendMessage(CMD_DISCONNECT);
+                sendMessage(CMD_DISCONNECT, StaEvent.DISCONNECT_NETWORK_PERMANENT_DISABLED);
             }
         }
     }
 
-    /**
-     * Set wpa_supplicant log level using |mVerboseLoggingLevel| flag.
-     */
-    void setSupplicantLogLevel() {
-        mWifiNative.setSupplicantLogLevel(mVerboseLoggingEnabled);
+    private class OnCarrierOffloadDisabledListener implements
+            WifiCarrierInfoManager.OnCarrierOffloadDisabledListener {
+
+        @Override
+        public void onCarrierOffloadDisabled(int subscriptionId, boolean merged) {
+            int networkId = mTargetNetworkId == WifiConfiguration.INVALID_NETWORK_ID
+                    ? mLastNetworkId : mTargetNetworkId;
+            if (networkId == WifiConfiguration.INVALID_NETWORK_ID) {
+                return;
+            }
+            WifiConfiguration configuration = mWifiConfigManager.getConfiguredNetwork(networkId);
+            if (configuration.subscriptionId == subscriptionId
+                    && configuration.carrierMerged == merged) {
+                Log.i(getTag(), "Carrier network offload disabled, triggering disconnect");
+                sendMessage(CMD_DISCONNECT, StaEvent.DISCONNECT_CARRIER_OFFLOAD_DISABLED);
+            }
+        }
     }
 
     /**
@@ -1184,32 +1097,18 @@
      *
      * @param verbose int logging level to use
      */
-    public void enableVerboseLogging(int verbose) {
-        if (verbose > 0) {
+    public void enableVerboseLogging(boolean verbose) {
+        if (verbose) {
             mVerboseLoggingEnabled = true;
             setLogRecSize(mActivityManager.isLowRamDevice()
                     ? NUM_LOG_RECS_VERBOSE_LOW_MEMORY : NUM_LOG_RECS_VERBOSE);
         } else {
             mVerboseLoggingEnabled = false;
-            setLogRecSize(NUM_LOG_RECS_NORMAL);
+            setLogRecSize(mWifiGlobals.getClientModeImplNumLogRecs());
         }
-        setSupplicantLogLevel();
-        mCountryCode.enableVerboseLogging(verbose);
+
         mWifiScoreReport.enableVerboseLogging(mVerboseLoggingEnabled);
-        mWifiDiagnostics.enableVerboseLogging(mVerboseLoggingEnabled);
-        mWifiMonitor.enableVerboseLogging(verbose);
-        mWifiNative.enableVerboseLogging(verbose);
-        mWifiConfigManager.enableVerboseLogging(verbose);
-        mSupplicantStateTracker.enableVerboseLogging(verbose);
-        mPasspointManager.enableVerboseLogging(verbose);
-        mNetworkFactory.enableVerboseLogging(verbose);
-        mLinkProbeManager.enableVerboseLogging(mVerboseLoggingEnabled);
-        mMboOceController.enableVerboseLogging(mVerboseLoggingEnabled);
-        mWifiScoreCard.enableVerboseLogging(mVerboseLoggingEnabled);
-        mWifiHealthMonitor.enableVerboseLogging(mVerboseLoggingEnabled);
-        mWifiInjector.getThroughputPredictor().enableVerboseLogging(mVerboseLoggingEnabled);
-        mWifiDataStall.enableVerboseLogging(mVerboseLoggingEnabled);
-        mWifiConnectivityManager.enableVerboseLogging(mVerboseLoggingEnabled);
+        mSupplicantStateTracker.enableVerboseLogging(mVerboseLoggingEnabled);
     }
 
     /**
@@ -1253,10 +1152,6 @@
         return new Messenger(getHandler());
     }
 
-    // Last connect attempt is used to prevent scan requests:
-    //  - for a period of 10 seconds after attempting to connect
-    private long mLastConnectAttemptTimestamp = 0;
-
     // For debugging, keep track of last message status handling
     // TODO, find an equivalent mechanism as part of parent class
     private static final int MESSAGE_HANDLING_STATUS_PROCESSED = 2;
@@ -1306,7 +1201,7 @@
         return sb.toString();
     }
 
-    WifiLinkLayerStats getWifiLinkLayerStats() {
+    public WifiLinkLayerStats getWifiLinkLayerStats() {
         if (mInterfaceName == null) {
             loge("getWifiLinkLayerStats called without an interface");
             return null;
@@ -1324,43 +1219,16 @@
             long mRxPkts = mFacade.getRxPackets(mInterfaceName);
             mWifiInfo.updatePacketRates(mTxPkts, mRxPkts, mLastLinkLayerStatsUpdate);
         }
+        mWifiMetrics.incrementWifiLinkLayerUsageStats(mInterfaceName, stats);
         return stats;
     }
 
     /**
-     * Check if a Wi-Fi band is supported
-     *
-     * @param band A value from {@link WifiScanner.WIFI_BAND_5_GHZ} or
-     *        {@link WifiScanner.WIFI_BAND_6_GHZ}
-     * @return {@code true} if band is supported, {@code false} otherwise.
-     */
-    public boolean isWifiBandSupported(int band) {
-        if (band == WifiScanner.WIFI_BAND_5_GHZ) {
-            // In some cases, devices override the value by the overlay configs
-            if (mContext.getResources().getBoolean(R.bool.config_wifi5ghzSupport)) {
-                return true;
-            }
-            return (mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ).length > 0);
-        }
-
-        if (band == WifiScanner.WIFI_BAND_6_GHZ) {
-            if (mContext.getResources().getBoolean(R.bool.config_wifi6ghzSupport)) {
-                return true;
-            }
-            return (mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_6_GHZ).length > 0);
-        }
-
-        return false;
-    }
-
-    /**
      * Update interface capabilities
      * This method is used to update some of interface capabilities defined in overlay
-     *
-     * @param ifaceName name of interface to update
      */
-    private void updateInterfaceCapabilities(@NonNull String ifaceName) {
-        DeviceWiphyCapabilities cap = mWifiNative.getDeviceWiphyCapabilities(ifaceName);
+    private void updateInterfaceCapabilities() {
+        DeviceWiphyCapabilities cap = getDeviceWiphyCapabilities();
         if (cap != null) {
             // Some devices don't have support of 11ax indicated by the chip,
             // so an override config value is used
@@ -1368,10 +1236,15 @@
                 cap.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11AX, true);
             }
 
-            mWifiNative.setDeviceWiphyCapabilities(ifaceName, cap);
+            mWifiNative.setDeviceWiphyCapabilities(mInterfaceName, cap);
         }
     }
 
+    @Override
+    public DeviceWiphyCapabilities getDeviceWiphyCapabilities() {
+        return mWifiNative.getDeviceWiphyCapabilities(mInterfaceName);
+    }
+
     /**
      * Check if a Wi-Fi standard is supported
      *
@@ -1449,64 +1322,24 @@
         return mWifiNative.stopRssiMonitoring(mInterfaceName);
     }
 
-    /**
-     * Temporary method that allows the active ClientModeManager to set the wifi state that is
-     * retrieved by API calls. This will be removed when WifiServiceImpl no longer directly calls
-     * this class (b/31479117).
-     *
-     * @param newState new state to set, invalid states are ignored.
-     */
-    public void setWifiStateForApiCalls(int newState) {
-        switch (newState) {
-            case WIFI_STATE_DISABLING:
-            case WIFI_STATE_DISABLED:
-            case WIFI_STATE_ENABLING:
-            case WIFI_STATE_ENABLED:
-            case WIFI_STATE_UNKNOWN:
-                if (mVerboseLoggingEnabled) {
-                    Log.d(TAG, "setting wifi state to: " + newState);
-                }
-                mWifiState.set(newState);
-                return;
-            default:
-                Log.d(TAG, "attempted to set an invalid state: " + newState);
-                return;
-        }
-    }
-
-    /**
-     * Method used by WifiServiceImpl to get the current state of Wifi (in client mode) for API
-     * calls.  This will be removed when WifiService no longer directly calls this class
-     * (b/31479117).
-     */
-    public int syncGetWifiState() {
-        return mWifiState.get();
-    }
-
-    /**
-     * Converts the current wifi state to a printable form.
-     */
-    public String syncGetWifiStateByName() {
-        switch (mWifiState.get()) {
-            case WIFI_STATE_DISABLING:
-                return "disabling";
-            case WIFI_STATE_DISABLED:
-                return "disabled";
-            case WIFI_STATE_ENABLING:
-                return "enabling";
-            case WIFI_STATE_ENABLED:
-                return "enabled";
-            case WIFI_STATE_UNKNOWN:
-                return "unknown state";
-            default:
-                return "[invalid state]";
-        }
-    }
-
+    @Override
     public boolean isConnected() {
-        return getCurrentState() == mConnectedState;
+        return getCurrentState() == mL3ConnectedState;
     }
 
+    @Override
+    public boolean isConnecting() {
+        IState state = getCurrentState();
+        return state == mL2ConnectingState || state == mL2ConnectedState
+                || state == mL3ProvisioningState;
+    }
+
+    @Override
+    public boolean isRoaming() {
+        return getCurrentState() == mRoamingState;
+    }
+
+    @Override
     public boolean isDisconnected() {
         return getCurrentState() == mDisconnectedState;
     }
@@ -1524,12 +1357,12 @@
                 || supplicantState == SupplicantState.GROUP_HANDSHAKE) {
 
             if (mVerboseLoggingEnabled) {
-                Log.d(TAG, "Supplicant is under transient state: " + supplicantState);
+                Log.d(getTag(), "Supplicant is under transient state: " + supplicantState);
             }
             return true;
         } else {
             if (mVerboseLoggingEnabled) {
-                Log.d(TAG, "Supplicant is under steady state: " + supplicantState);
+                Log.d(getTag(), "Supplicant is under steady state: " + supplicantState);
             }
         }
 
@@ -1538,21 +1371,15 @@
 
     /**
      * Get status information for the current connection, if any.
+     * Note: This call is synchronized and hence safe to call from any thread (if called from wifi
+     * thread, will execute synchronously).
      *
      * @return a {@link WifiInfo} object containing information about the current connection
+     * TODO (b/173551144): Change to direct call. Let callers use WifiThreadRunner if necessary.
      */
+    @Override
     public WifiInfo syncRequestConnectionInfo() {
-        WifiInfo result = new WifiInfo(mWifiInfo);
-        return result;
-    }
-
-    /**
-     * Method to retrieve the current WifiInfo
-     *
-     * @returns WifiInfo
-     */
-    public WifiInfo getWifiInfo() {
-        return mWifiInfo;
+        return mWifiThreadRunner.call(() -> new WifiInfo(mWifiInfo), new WifiInfo());
     }
 
     /**
@@ -1572,162 +1399,113 @@
      * mark network agent as disconnected and stop the ip client.
      */
     public void handleIfaceDestroyed() {
-        handleNetworkDisconnect();
+        handleNetworkDisconnect(false,
+                WifiStatsLog.WIFI_DISCONNECT_REPORTED__FAILURE_CODE__IFACE_DESTROYED);
     }
 
-    /**
-     * TODO: doc
-     */
-    public void setOperationalMode(int mode, String ifaceName) {
-        if (mVerboseLoggingEnabled) {
-            log("setting operational mode to " + String.valueOf(mode) + " for iface: " + ifaceName);
+    /** Stop this ClientModeImpl. Do not interact with ClientModeImpl after it has been stopped. */
+    public void stop() {
+        mSupplicantStateTracker.stop();
+        mWifiScoreCard.noteWifiDisabled(mWifiInfo);
+        // capture StateMachine LogRecs since we will lose them after we call quitNow()
+        // This is used for debugging.
+        mObituary = new StateMachineObituary(this);
+
+        // quit discarding all unprocessed messages - this is to preserve the legacy behavior of
+        // using sendMessageAtFrontOfQueue(CMD_SET_OPERATIONAL_MODE) which would force a state
+        // transition immediately
+        quitNow();
+
+        mWifiConfigManager.removeOnNetworkUpdateListener(mOnNetworkUpdateListener);
+        mWifiCarrierInfoManager
+                .removeOnCarrierOffloadDisabledListener(mOnCarrierOffloadDisabledListener);
+        if (mVcnPolicyChangeListener != null) {
+            mVcnManager.removeVcnNetworkPolicyChangeListener(mVcnPolicyChangeListener);
+            mVcnPolicyChangeListener = null;
         }
-        mModeChange = true;
-        if (mode != CONNECT_MODE) {
-            // we are disabling client mode...   need to exit connect mode now
-            transitionTo(mDefaultState);
-        } else {
-            // do a quick check on the iface name, make sure it isn't null
-            if (ifaceName != null) {
-                mInterfaceName = ifaceName;
-                updateInterfaceCapabilities(ifaceName);
-                transitionTo(mDisconnectedState);
-                mWifiScoreReport.setInterfaceName(ifaceName);
-            } else {
-                Log.e(TAG, "supposed to enter connect mode, but iface is null -> DefaultState");
-                transitionTo(mDefaultState);
-            }
-        }
-        // use the CMD_SET_OPERATIONAL_MODE to force the transitions before other messages are
-        // handled.
-        sendMessageAtFrontOfQueue(CMD_SET_OPERATIONAL_MODE);
     }
 
     private void checkAbnormalConnectionFailureAndTakeBugReport(String ssid) {
-        if (mWifiInjector.getDeviceConfigFacade()
-                .isAbnormalConnectionFailureBugreportEnabled()) {
+        if (mDeviceConfigFacade.isAbnormalConnectionFailureBugreportEnabled()) {
             int reasonCode = mWifiScoreCard.detectAbnormalConnectionFailure(ssid);
             if (reasonCode != WifiHealthMonitor.REASON_NO_FAILURE) {
                 String bugTitle = "Wi-Fi BugReport";
                 String bugDetail = "Detect abnormal "
                         + WifiHealthMonitor.FAILURE_REASON_NAME[reasonCode];
-                takeBugReport(bugTitle, bugDetail);
+                mWifiDiagnostics.takeBugReport(bugTitle, bugDetail);
             }
         }
     }
 
     private void checkAbnormalDisconnectionAndTakeBugReport() {
-        if (mWifiInjector.getDeviceConfigFacade()
-                .isAbnormalDisconnectionBugreportEnabled()) {
-            int reasonCode = mWifiScoreCard.detectAbnormalDisconnection();
+        if (mDeviceConfigFacade.isAbnormalDisconnectionBugreportEnabled()) {
+            int reasonCode = mWifiScoreCard.detectAbnormalDisconnection(mInterfaceName);
             if (reasonCode != WifiHealthMonitor.REASON_NO_FAILURE) {
                 String bugTitle = "Wi-Fi BugReport";
                 String bugDetail = "Detect abnormal "
                         + WifiHealthMonitor.FAILURE_REASON_NAME[reasonCode];
-                takeBugReport(bugTitle, bugDetail);
+                mWifiDiagnostics.takeBugReport(bugTitle, bugDetail);
             }
         }
     }
 
     /**
-     * Initiates a system-level bugreport, in a non-blocking fashion.
-     */
-    public void takeBugReport(String bugTitle, String bugDetail) {
-        mWifiDiagnostics.takeBugReport(bugTitle, bugDetail);
-    }
-
-    /**
-     * Allow tests to confirm the operational mode for ClientModeImpl for testing.
-     */
-    @VisibleForTesting
-    protected int getOperationalModeForTest() {
-        return mOperationalMode;
-    }
-
-    /**
      * Retrieve the WifiMulticastLockManager.FilterController callback for registration.
      */
-    protected WifiMulticastLockManager.FilterController getMcastLockManagerFilterController() {
+    public WifiMulticastLockManager.FilterController getMcastLockManagerFilterController() {
         return mMcastLockManagerFilterController;
     }
 
     /**
      * Blocking method to retrieve the passpoint icon.
      *
-     * @param channel AsyncChannel for the response
      * @param bssid representation of the bssid as a long
      * @param fileName name of the file
      *
      * @return boolean returning the result of the call
      */
-    public boolean syncQueryPasspointIcon(AsyncChannel channel, long bssid, String fileName) {
-        Bundle bundle = new Bundle();
-        bundle.putLong(EXTRA_OSU_ICON_QUERY_BSSID, bssid);
-        bundle.putString(EXTRA_OSU_ICON_QUERY_FILENAME, fileName);
-        Message resultMsg = channel.sendMessageSynchronously(CMD_QUERY_OSU_ICON, bundle);
-        int result = resultMsg.arg1;
-        resultMsg.recycle();
-        return result == 1;
+    public boolean syncQueryPasspointIcon(long bssid, String fileName) {
+        return mWifiThreadRunner.call(
+                () -> mPasspointManager.queryPasspointIcon(bssid, fileName), false);
     }
 
-    /**
-     * Deauthenticate and set the re-authentication hold off time for the current network
-     * @param holdoff hold off time in milliseconds
-     * @param ess set if the hold off pertains to an ESS rather than a BSS
-     */
-    public void deauthenticateNetwork(AsyncChannel channel, long holdoff, boolean ess) {
-        // TODO: This needs an implementation
+    @Override
+    public boolean requestAnqp(String bssid, Set<Integer> anqpIds, Set<Integer> hs20Subtypes) {
+        return mWifiNative.requestAnqp(mInterfaceName, bssid, anqpIds, hs20Subtypes);
+    }
+
+    @Override
+    public boolean requestVenueUrlAnqp(String bssid) {
+        return mWifiNative.requestVenueUrlAnqp(mInterfaceName, bssid);
+    }
+
+    @Override
+    public boolean requestIcon(String bssid, String fileName) {
+        return mWifiNative.requestIcon(mInterfaceName, bssid, fileName);
     }
 
     /**
      * Disconnect from Access Point
      */
-    public void disconnectCommand() {
-        sendMessage(CMD_DISCONNECT);
-    }
-
-    /**
-     * Method to trigger a disconnect.
-     *
-     * @param uid UID of requesting caller
-     * @param reason disconnect reason
-     */
-    public void disconnectCommand(int uid, int reason) {
-        sendMessage(CMD_DISCONNECT, uid, reason);
+    public void disconnect() {
+        sendMessage(CMD_DISCONNECT, StaEvent.DISCONNECT_GENERIC);
     }
 
     /**
      * Initiate a reconnection to AP
      */
-    public void reconnectCommand(WorkSource workSource) {
+    public void reconnect(WorkSource workSource) {
         sendMessage(CMD_RECONNECT, workSource);
     }
 
     /**
      * Initiate a re-association to AP
      */
-    public void reassociateCommand() {
+    public void reassociate() {
         sendMessage(CMD_REASSOCIATE);
     }
 
     /**
-     * Checks for a null Message.
-     *
-     * This can happen with sendMessageSynchronously, for example if an
-     * InterruptedException occurs. If this just happens once, silently
-     * ignore it, because it is probably a side effect of shutting down.
-     * If it happens a second time, generate a WTF.
-     */
-    private boolean messageIsNull(Message resultMsg) {
-        if (resultMsg != null) return false;
-        if (mNullMessageCounter.getAndIncrement() > 0) {
-            Log.wtf(TAG, "Persistent null Message", new RuntimeException());
-        }
-        return true;
-    }
-    private AtomicInteger mNullMessageCounter = new AtomicInteger(0);
-
-    /**
      * Start subscription provisioning synchronously
      *
      * @param provider {@link OsuProvider} the provider to provision with
@@ -1735,71 +1513,38 @@
      * @return boolean true indicates provisioning was started, false otherwise
      */
     public boolean syncStartSubscriptionProvisioning(int callingUid, OsuProvider provider,
-            IProvisioningCallback callback, AsyncChannel channel) {
-        Message msg = Message.obtain();
-        msg.what = CMD_START_SUBSCRIPTION_PROVISIONING;
-        msg.arg1 = callingUid;
-        msg.obj = callback;
-        msg.getData().putParcelable(EXTRA_OSU_PROVIDER, provider);
-        Message resultMsg = channel.sendMessageSynchronously(msg);
-        if (messageIsNull(resultMsg)) return false;
-        boolean result = resultMsg.arg1 != 0;
-        resultMsg.recycle();
-        return result;
+            IProvisioningCallback callback) {
+        return mWifiThreadRunner.call(
+                () -> mPasspointManager.startSubscriptionProvisioning(
+                        callingUid, provider, callback), false);
     }
 
     /**
      * Get the supported feature set synchronously
      */
-    public long syncGetSupportedFeatures(AsyncChannel channel) {
-        Message resultMsg = channel.sendMessageSynchronously(CMD_GET_SUPPORTED_FEATURES);
-        if (messageIsNull(resultMsg)) return 0;
-        long supportedFeatureSet = ((Long) resultMsg.obj).longValue();
-        resultMsg.recycle();
-        return supportedFeatureSet;
-    }
-
-    /**
-     * Get link layers stats for adapter synchronously
-     */
-    public WifiLinkLayerStats syncGetLinkLayerStats(AsyncChannel channel) {
-        Message resultMsg = channel.sendMessageSynchronously(CMD_GET_LINK_LAYER_STATS);
-        if (messageIsNull(resultMsg)) return null;
-        WifiLinkLayerStats result = (WifiLinkLayerStats) resultMsg.obj;
-        resultMsg.recycle();
-        return result;
+    public long getSupportedFeatures() {
+        return mWifiNative.getSupportedFeatureSet(mInterfaceName);
     }
 
     /**
      * Method to enable/disable RSSI polling
      * @param enabled boolean idicating if polling should start
      */
+    @VisibleForTesting
     public void enableRssiPolling(boolean enabled) {
         sendMessage(CMD_ENABLE_RSSI_POLL, enabled ? 1 : 0, 0);
     }
 
     /**
-     * Set high performance mode of operation.
-     * Enabling would set active power mode and disable suspend optimizations;
-     * disabling would set auto power mode and enable suspend optimizations
-     *
-     * @param enable true if enable, false otherwise
-     */
-    public void setHighPerfModeEnabled(boolean enable) {
-        sendMessage(CMD_SET_HIGH_PERF_MODE, enable ? 1 : 0, 0);
-    }
-
-
-    /**
      * reset cached SIM credential data
      */
-    public synchronized void resetSimAuthNetworks(@ResetSimReason int resetReason) {
+    public void resetSimAuthNetworks(@ResetSimReason int resetReason) {
         sendMessage(CMD_RESET_SIM_NETWORKS, resetReason);
     }
 
     /**
      * Should only be used internally.
-     * External callers should use {@link #syncGetCurrentNetwork(AsyncChannel)}.
+     * External callers should use {@link #syncGetCurrentNetwork()}.
      */
     private Network getCurrentNetwork() {
         if (mNetworkAgent != null) {
@@ -1813,36 +1558,27 @@
      * Get Network object of currently connected wifi network, or null if not connected.
      * @return Network object of current wifi network
      */
-    public Network syncGetCurrentNetwork(AsyncChannel channel) {
-        Message resultMsg = channel.sendMessageSynchronously(CMD_GET_CURRENT_NETWORK);
-        if (messageIsNull(resultMsg)) return null;
-        Network network = (Network) resultMsg.obj;
-        resultMsg.recycle();
-        return network;
+    public Network syncGetCurrentNetwork() {
+        return mWifiThreadRunner.call(
+                () -> {
+                    if (getCurrentState() == mL3ConnectedState
+                            || getCurrentState() == mRoamingState) {
+                        return getCurrentNetwork();
+                    }
+                    return null;
+                }, null);
     }
 
     /**
      * Enable TDLS for a specific MAC address
      */
     public void enableTdls(String remoteMacAddress, boolean enable) {
-        int enabler = enable ? 1 : 0;
-        sendMessage(CMD_ENABLE_TDLS, enabler, 0, remoteMacAddress);
+        mWifiNative.startTdls(mInterfaceName, remoteMacAddress, enable);
     }
 
-    /**
-     * Send a message indicating bluetooth adapter state changed, e.g., turn on or ff
-     */
-    public void sendBluetoothAdapterStateChange(int state) {
-        sendMessage(CMD_BLUETOOTH_ADAPTER_STATE_CHANGE, state, 0);
-    }
-
-    /**
-     * Send a message indicating bluetooth adapter connection state changed, e.g., connected
-     * or disconnected. Note that turning off BT after pairing success keeps connection state in
-     * connected state.
-     */
-    public void sendBluetoothAdapterConnectionStateChange(int state) {
-        sendMessage(CMD_BLUETOOTH_ADAPTER_CONNECTION_STATE_CHANGE, state, 0);
+    /** Send a message indicating bluetooth connection state changed, e.g. connected/disconnected */
+    public void onBluetoothConnectionStateChanged() {
+        sendMessage(CMD_BLUETOOTH_CONNECTION_STATE_CHANGE);
     }
 
     /**
@@ -1869,46 +1605,38 @@
 
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        super.dump(fd, pw, args);
+        pw.println("Dump of ClientModeImpl id=" + mId);
+        if (mObituary == null) {
+            // StateMachine hasn't quit yet, dump `this` via StateMachineObituary's dump()
+            // method for consistency with `else` branch.
+            new StateMachineObituary(this).dump(fd, pw, args);
+        } else {
+            // StateMachine has quit and cleared all LogRecs.
+            // Get them from the obituary instead.
+            mObituary.dump(fd, pw, args);
+        }
         mSupplicantStateTracker.dump(fd, pw, args);
+        // Polls link layer stats and RSSI. This allows the stats to show up in
+        // WifiScoreReport's dump() output when taking a bug report even if the screen is off.
+        updateLinkLayerStatsRssiAndScoreReport();
         pw.println("mLinkProperties " + mLinkProperties);
         pw.println("mWifiInfo " + mWifiInfo);
         pw.println("mDhcpResultsParcelable "
                 + dhcpResultsParcelableToString(mDhcpResultsParcelable));
         pw.println("mLastSignalLevel " + mLastSignalLevel);
+        pw.println("mLastTxKbps " + mLastTxKbps);
+        pw.println("mLastRxKbps " + mLastRxKbps);
         pw.println("mLastBssid " + mLastBssid);
         pw.println("mLastNetworkId " + mLastNetworkId);
         pw.println("mLastSubId " + mLastSubId);
         pw.println("mLastSimBasedConnectionCarrierName " + mLastSimBasedConnectionCarrierName);
-        pw.println("mOperationalMode " + mOperationalMode);
         pw.println("mSuspendOptimizationsEnabled " + mContext.getResources().getBoolean(
                 R.bool.config_wifiSuspendOptimizationsEnabled));
         pw.println("mSuspendOptNeedsDisabled " + mSuspendOptNeedsDisabled);
-        mCountryCode.dump(fd, pw, args);
-        mNetworkFactory.dump(fd, pw, args);
-        mUntrustedNetworkFactory.dump(fd, pw, args);
-        pw.println("Wlan Wake Reasons:" + mWifiNative.getWlanWakeReasonCount());
-        pw.println();
-
-        mWifiConfigManager.dump(fd, pw, args);
-        pw.println();
-        mPasspointManager.dump(pw);
-        pw.println();
-        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_USER_ACTION);
-        mWifiDiagnostics.dump(fd, pw, args);
         dumpIpClient(fd, pw, args);
-        mWifiConnectivityManager.dump(fd, pw, args);
-        mWifiHealthMonitor.dump(fd, pw, args);
-        mWifiInjector.getWakeupController().dump(fd, pw, args);
-        mLinkProbeManager.dump(fd, pw, args);
-        mWifiInjector.getWifiLastResortWatchdog().dump(fd, pw, args);
-    }
-
-    /**
-     * Trigger message to handle boot completed event.
-     */
-    public void handleBootCompleted() {
-        sendMessage(CMD_BOOT_COMPLETED);
+        pw.println("WifiScoreReport:");
+        mWifiScoreReport.dump(fd, pw, args);
+        pw.println();
     }
 
     /**
@@ -1967,14 +1695,12 @@
                 break;
             case CMD_CONNECT_NETWORK:
             case CMD_SAVE_NETWORK: {
-                NetworkUpdateResult result = (NetworkUpdateResult) msg.obj;
+                ConnectNetworkMessage cnm = (ConnectNetworkMessage) msg.obj;
                 sb.append(" ");
-                sb.append(Integer.toString(result.netId));
-                sb.append(" ");
-                sb.append(Integer.toString(msg.arg2));
-                config = mWifiConfigManager.getConfiguredNetwork(result.netId);
+                sb.append(cnm.result.getNetworkId());
+                config = mWifiConfigManager.getConfiguredNetwork(cnm.result.getNetworkId());
                 if (config != null) {
-                    sb.append(" ").append(config.getKey());
+                    sb.append(" ").append(config.getProfileKey());
                     sb.append(" nid=").append(config.networkId);
                     if (config.hiddenSSID) {
                         sb.append(" hidden");
@@ -1992,33 +1718,28 @@
                 break;
             }
             case WifiMonitor.ASSOCIATION_REJECTION_EVENT:
-                sb.append(" ");
-                sb.append(" timedOut=" + Integer.toString(msg.arg1));
-                sb.append(" ");
-                sb.append(Integer.toString(msg.arg2));
-                String bssid = (String) msg.obj;
-                if (bssid != null && bssid.length() > 0) {
-                    sb.append(" ");
-                    sb.append(bssid);
+                if (msg.obj != null) {
+                    sb.append(" ").append((AssocRejectEventInfo) msg.obj);
                 }
-                sb.append(" blacklist=" + Boolean.toString(mDidBlackListBSSID));
                 break;
-            case WifiMonitor.NETWORK_CONNECTION_EVENT:
+            case WifiMonitor.NETWORK_CONNECTION_EVENT: {
+                NetworkConnectionEventInfo connectionInfo = (NetworkConnectionEventInfo) msg.obj;
                 sb.append(" ");
-                sb.append(Integer.toString(msg.arg1));
+                sb.append(connectionInfo.networkId);
                 sb.append(" ");
-                sb.append(Integer.toString(msg.arg2));
+                sb.append(connectionInfo.isFilsConnection);
                 sb.append(" ").append(mLastBssid);
                 sb.append(" nid=").append(mLastNetworkId);
-                config = getCurrentWifiConfiguration();
+                config = getConnectedWifiConfigurationInternal();
                 if (config != null) {
-                    sb.append(" ").append(config.getKey());
+                    sb.append(" ").append(config.getProfileKey());
                 }
                 key = mWifiConfigManager.getLastSelectedNetworkConfigKey();
                 if (key != null) {
                     sb.append(" last=").append(key);
                 }
                 break;
+            }
             case WifiMonitor.TARGET_BSSID_EVENT:
             case WifiMonitor.ASSOCIATED_BSSID_EVENT:
                 sb.append(" ");
@@ -2035,10 +1756,8 @@
                 break;
             case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
                 if (msg.obj != null) {
-                    sb.append(" ").append((String) msg.obj);
+                    sb.append(" ").append((DisconnectEventInfo) msg.obj);
                 }
-                sb.append(" nid=").append(msg.arg1);
-                sb.append(" reason=").append(msg.arg2);
                 if (mLastBssid != null) {
                     sb.append(" lastbssid=").append(mLastBssid);
                 }
@@ -2084,16 +1803,16 @@
                 sb.append(Integer.toString(msg.arg2));
                 config = mWifiConfigManager.getConfiguredNetwork(msg.arg1);
                 if (config != null) {
-                    sb.append(" targetConfigKey=").append(config.getKey());
+                    sb.append(" targetConfigKey=").append(config.getProfileKey());
                     sb.append(" BSSID=" + config.BSSID);
                 }
                 if (mTargetBssid != null) {
                     sb.append(" targetBssid=").append(mTargetBssid);
                 }
                 sb.append(" roam=").append(Boolean.toString(mIsAutoRoaming));
-                config = getCurrentWifiConfiguration();
+                config = getConnectedWifiConfigurationInternal();
                 if (config != null) {
-                    sb.append(" currentConfigKey=").append(config.getKey());
+                    sb.append(" currentConfigKey=").append(config.getProfileKey());
                 }
                 break;
             case CMD_START_ROAM:
@@ -2101,19 +1820,8 @@
                 sb.append(Integer.toString(msg.arg1));
                 sb.append(" ");
                 sb.append(Integer.toString(msg.arg2));
-                ScanResult result = (ScanResult) msg.obj;
-                if (result != null) {
-                    now = mClock.getWallClockMillis();
-                    sb.append(" bssid=").append(result.BSSID);
-                    sb.append(" rssi=").append(result.level);
-                    sb.append(" freq=").append(result.frequency);
-                    if (result.seen > 0 && result.seen < now) {
-                        sb.append(" seen=").append(now - result.seen);
-                    } else {
-                        // Somehow the timestamp for this scan result is inconsistent
-                        sb.append(" !seen=").append(result.seen);
-                    }
-                }
+                String bssid = (String) msg.obj;
+                sb.append(" bssid=").append(bssid);
                 if (mTargetBssid != null) {
                     sb.append(" ").append(mTargetBssid);
                 }
@@ -2154,7 +1862,7 @@
                 break;
             case CMD_IP_CONFIGURATION_LOST:
                 int count = -1;
-                WifiConfiguration c = getCurrentWifiConfiguration();
+                WifiConfiguration c = getConnectedWifiConfigurationInternal();
                 if (c != null) {
                     count = c.getNetworkSelectionStatus().getDisableReasonCounter(
                             WifiConfiguration.NetworkSelectionStatus.DISABLED_DHCP_FAILURE);
@@ -2201,12 +1909,12 @@
                 sb.append(Integer.toString(msg.arg2));
                 sb.append(" cur=").append(mRoamWatchdogCount);
                 break;
-            case CMD_DISCONNECTING_WATCHDOG_TIMER:
+            case CMD_CONNECTING_WATCHDOG_TIMER:
                 sb.append(" ");
                 sb.append(Integer.toString(msg.arg1));
                 sb.append(" ");
                 sb.append(Integer.toString(msg.arg2));
-                sb.append(" cur=").append(mDisconnectingWatchdogCount);
+                sb.append(" cur=").append(mConnectingWatchdogCount);
                 break;
             case CMD_START_RSSI_MONITORING_OFFLOAD:
             case CMD_STOP_RSSI_MONITORING_OFFLOAD:
@@ -2226,6 +1934,9 @@
                     sb.append(" ").append(frameData.toString());
                 }
                 break;
+            case WifiMonitor.NETWORK_NOT_FOUND_EVENT:
+                sb.append(" ssid=" + msg.obj);
+                break;
             default:
                 sb.append(" ");
                 sb.append(Integer.toString(msg.arg1));
@@ -2239,82 +1950,141 @@
 
     @Override
     protected String getWhatToString(int what) {
-        String s = sGetWhatToString.get(what);
-        if (s != null) {
-            return s;
-        }
         switch (what) {
-            case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
-                s = "CMD_CHANNEL_HALF_CONNECTED";
-                break;
-            case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
-                s = "CMD_CHANNEL_DISCONNECTED";
-                break;
+            case CMD_ACCEPT_UNVALIDATED:
+                return "CMD_ACCEPT_UNVALIDATED";
+            case CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF:
+                return "CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF";
+            case CMD_BLUETOOTH_CONNECTION_STATE_CHANGE:
+                return "CMD_BLUETOOTH_CONNECTION_STATE_CHANGE";
+            case CMD_CONFIG_ND_OFFLOAD:
+                return "CMD_CONFIG_ND_OFFLOAD";
+            case CMD_CONNECTING_WATCHDOG_TIMER:
+                return "CMD_CONNECTING_WATCHDOG_TIMER";
             case CMD_CONNECT_NETWORK:
-                s = "CMD_CONNECT_NETWORK";
-                break;
+                return "CMD_CONNECT_NETWORK";
+            case CMD_DIAGS_CONNECT_TIMEOUT:
+                return "CMD_DIAGS_CONNECT_TIMEOUT";
+            case CMD_DISCONNECT:
+                return "CMD_DISCONNECT";
+            case CMD_ENABLE_RSSI_POLL:
+                return "CMD_ENABLE_RSSI_POLL";
+            case CMD_INSTALL_PACKET_FILTER:
+                return "CMD_INSTALL_PACKET_FILTER";
+            case CMD_IP_CONFIGURATION_LOST:
+                return "CMD_IP_CONFIGURATION_LOST";
+            case CMD_IP_CONFIGURATION_SUCCESSFUL:
+                return "CMD_IP_CONFIGURATION_SUCCESSFUL";
+            case CMD_IP_REACHABILITY_LOST:
+                return "CMD_IP_REACHABILITY_LOST";
+            case CMD_IPV4_PROVISIONING_FAILURE:
+                return "CMD_IPV4_PROVISIONING_FAILURE";
+            case CMD_IPV4_PROVISIONING_SUCCESS:
+                return "CMD_IPV4_PROVISIONING_SUCCESS";
+            case CMD_NETWORK_STATUS:
+                return "CMD_NETWORK_STATUS";
+            case CMD_ONESHOT_RSSI_POLL:
+                return "CMD_ONESHOT_RSSI_POLL";
+            case CMD_POST_DHCP_ACTION:
+                return "CMD_POST_DHCP_ACTION";
+            case CMD_PRE_DHCP_ACTION:
+                return "CMD_PRE_DHCP_ACTION";
+            case CMD_PRE_DHCP_ACTION_COMPLETE:
+                return "CMD_PRE_DHCP_ACTION_COMPLETE";
+            case CMD_READ_PACKET_FILTER:
+                return "CMD_READ_PACKET_FILTER";
+            case CMD_REASSOCIATE:
+                return "CMD_REASSOCIATE";
+            case CMD_RECONNECT:
+                return "CMD_RECONNECT";
+            case CMD_REMOVE_KEEPALIVE_PACKET_FILTER_FROM_APF:
+                return "CMD_REMOVE_KEEPALIVE_PACKET_FILTER_FROM_APF";
+            case CMD_RESET_SIM_NETWORKS:
+                return "CMD_RESET_SIM_NETWORKS";
+            case CMD_ROAM_WATCHDOG_TIMER:
+                return "CMD_ROAM_WATCHDOG_TIMER";
+            case CMD_RSSI_POLL:
+                return "CMD_RSSI_POLL";
+            case CMD_RSSI_THRESHOLD_BREACHED:
+                return "CMD_RSSI_THRESHOLD_BREACHED";
             case CMD_SAVE_NETWORK:
-                s = "CMD_SAVE_NETWORK";
-                break;
+                return "CMD_SAVE_NETWORK";
+            case CMD_SCREEN_STATE_CHANGED:
+                return "CMD_SCREEN_STATE_CHANGED";
+            case CMD_SET_FALLBACK_PACKET_FILTERING:
+                return "CMD_SET_FALLBACK_PACKET_FILTERING";
+            case CMD_SET_SUSPEND_OPT_ENABLED:
+                return "CMD_SET_SUSPEND_OPT_ENABLED";
+            case CMD_START_CONNECT:
+                return "CMD_START_CONNECT";
+            case CMD_START_FILS_CONNECTION:
+                return "CMD_START_FILS_CONNECTION";
+            case CMD_START_IP_PACKET_OFFLOAD:
+                return "CMD_START_IP_PACKET_OFFLOAD";
+            case CMD_START_ROAM:
+                return "CMD_START_ROAM";
+            case CMD_START_RSSI_MONITORING_OFFLOAD:
+                return "CMD_START_RSSI_MONITORING_OFFLOAD";
+            case CMD_STOP_IP_PACKET_OFFLOAD:
+                return "CMD_STOP_IP_PACKET_OFFLOAD";
+            case CMD_STOP_RSSI_MONITORING_OFFLOAD:
+                return "CMD_STOP_RSSI_MONITORING_OFFLOAD";
+            case CMD_UNWANTED_NETWORK:
+                return "CMD_UNWANTED_NETWORK";
+            case CMD_UPDATE_LINKPROPERTIES:
+                return "CMD_UPDATE_LINKPROPERTIES";
             case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
-                s = "SUPPLICANT_STATE_CHANGE_EVENT";
-                break;
+                return "SUPPLICANT_STATE_CHANGE_EVENT";
             case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
-                s = "AUTHENTICATION_FAILURE_EVENT";
-                break;
+                return "AUTHENTICATION_FAILURE_EVENT";
             case WifiMonitor.SUP_REQUEST_IDENTITY:
-                s = "SUP_REQUEST_IDENTITY";
-                break;
+                return "SUP_REQUEST_IDENTITY";
             case WifiMonitor.NETWORK_CONNECTION_EVENT:
-                s = "NETWORK_CONNECTION_EVENT";
-                break;
+                return "NETWORK_CONNECTION_EVENT";
             case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
-                s = "NETWORK_DISCONNECTION_EVENT";
-                break;
+                return "NETWORK_DISCONNECTION_EVENT";
             case WifiMonitor.ASSOCIATION_REJECTION_EVENT:
-                s = "ASSOCIATION_REJECTION_EVENT";
-                break;
+                return "ASSOCIATION_REJECTION_EVENT";
             case WifiMonitor.ANQP_DONE_EVENT:
-                s = "ANQP_DONE_EVENT";
-                break;
+                return "ANQP_DONE_EVENT";
             case WifiMonitor.RX_HS20_ANQP_ICON_EVENT:
-                s = "RX_HS20_ANQP_ICON_EVENT";
-                break;
+                return "RX_HS20_ANQP_ICON_EVENT";
             case WifiMonitor.GAS_QUERY_DONE_EVENT:
-                s = "GAS_QUERY_DONE_EVENT";
-                break;
+                return "GAS_QUERY_DONE_EVENT";
             case WifiMonitor.HS20_REMEDIATION_EVENT:
-                s = "HS20_REMEDIATION_EVENT";
-                break;
+                return "HS20_REMEDIATION_EVENT";
+            case WifiMonitor.HS20_DEAUTH_IMMINENT_EVENT:
+                return "HS20_DEAUTH_IMMINENT_EVENT";
+            case WifiMonitor.HS20_TERMS_AND_CONDITIONS_ACCEPTANCE_REQUIRED_EVENT:
+                return "HS20_TERMS_AND_CONDITIONS_ACCEPTANCE_REQUIRED_EVENT";
             case WifiMonitor.GAS_QUERY_START_EVENT:
-                s = "GAS_QUERY_START_EVENT";
-                break;
+                return "GAS_QUERY_START_EVENT";
             case WifiMonitor.MBO_OCE_BSS_TM_HANDLING_DONE:
-                s = "MBO_OCE_BSS_TM_HANDLING_DONE";
-                break;
+                return "MBO_OCE_BSS_TM_HANDLING_DONE";
+            case WifiMonitor.TRANSITION_DISABLE_INDICATION:
+                return "TRANSITION_DISABLE_INDICATION";
             case WifiP2pServiceImpl.GROUP_CREATING_TIMED_OUT:
-                s = "GROUP_CREATING_TIMED_OUT";
-                break;
+                return "GROUP_CREATING_TIMED_OUT";
             case WifiP2pServiceImpl.P2P_CONNECTION_CHANGED:
-                s = "P2P_CONNECTION_CHANGED";
-                break;
+                return "P2P_CONNECTION_CHANGED";
             case WifiP2pServiceImpl.DISCONNECT_WIFI_REQUEST:
-                s = "DISCONNECT_WIFI_REQUEST";
-                break;
+                return "DISCONNECT_WIFI_REQUEST";
             case WifiP2pServiceImpl.DISCONNECT_WIFI_RESPONSE:
-                s = "DISCONNECT_WIFI_RESPONSE";
-                break;
+                return "DISCONNECT_WIFI_RESPONSE";
             case WifiP2pServiceImpl.SET_MIRACAST_MODE:
-                s = "SET_MIRACAST_MODE";
-                break;
+                return "SET_MIRACAST_MODE";
             case WifiP2pServiceImpl.BLOCK_DISCOVERY:
-                s = "BLOCK_DISCOVERY";
-                break;
+                return "BLOCK_DISCOVERY";
+            case WifiMonitor.NETWORK_NOT_FOUND_EVENT:
+                return "NETWORK_NOT_FOUND_EVENT";
             default:
-                s = "what:" + Integer.toString(what);
-                break;
+                return "what:" + what;
         }
-        return s;
+    }
+
+    /** Check whether this connection is the primary connection on the device. */
+    private boolean isPrimary() {
+        return mClientModeManager.getRole() == ROLE_CLIENT_PRIMARY;
     }
 
     private void handleScreenStateChanged(boolean screenOn) {
@@ -2326,7 +2096,16 @@
                             R.bool.config_wifiSuspendOptimizationsEnabled)
                     + " state " + getCurrentState().getName());
         }
-        enableRssiPolling(screenOn);
+        if (isPrimary()) {
+            // Only enable RSSI polling on primary STA, none of the secondary STA use-cases
+            // can become the default route when other networks types that provide internet
+            // connectivity (e.g. cellular) are available. So, no point in scoring
+            // these connections for the purpose of switching between wifi and other network
+            // types.
+            // TODO(b/179518316): Enable this for secondary transient STA also if external scorer
+            // is in charge of MBB.
+            enableRssiPolling(screenOn);
+        }
         if (mContext.getResources().getBoolean(R.bool.config_wifiSuspendOptimizationsEnabled)) {
             int shouldReleaseWakeLock = 0;
             if (screenOn) {
@@ -2345,34 +2124,9 @@
         mOnTimeScreenStateChange = mOnTime;
         mLastScreenStateChangeTimeStamp = mLastLinkLayerStatsUpdate;
 
-        mWifiMetrics.setScreenState(screenOn);
-
-        mWifiConnectivityManager.handleScreenStateChanged(screenOn);
-        mNetworkFactory.handleScreenStateChanged(screenOn);
-
-        WifiLockManager wifiLockManager = mWifiInjector.getWifiLockManager();
-        if (wifiLockManager == null) {
-            Log.w(TAG, "WifiLockManager not initialized, skipping screen state notification");
-        } else {
-            wifiLockManager.handleScreenStateChanged(screenOn);
-        }
-
-        mSarManager.handleScreenStateChanged(screenOn);
-
         if (mVerboseLoggingEnabled) log("handleScreenStateChanged Exit: " + screenOn);
     }
 
-    private boolean checkAndSetConnectivityInstance() {
-        if (mCm == null) {
-            mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
-        }
-        if (mCm == null) {
-            Log.e(TAG, "Cannot retrieve connectivity service");
-            return false;
-        }
-        return true;
-    }
-
     private void setSuspendOptimizationsNative(int reason, boolean enabled) {
         if (mVerboseLoggingEnabled) {
             log("setSuspendOptimizationsNative: " + reason + " " + enabled
@@ -2420,12 +2174,14 @@
     }
 
     /*
-     * Fetch RSSI, linkspeed, and frequency on current connection
+     * Fetch link layer stats, RSSI, linkspeed, and frequency on current connection
+     * and update Network capabilities
      */
-    private void fetchRssiLinkSpeedAndFrequencyNative() {
+    private WifiLinkLayerStats updateLinkLayerStatsRssiSpeedFrequencyCapabilities() {
+        WifiLinkLayerStats stats = getWifiLinkLayerStats();
         WifiNl80211Manager.SignalPollResult pollResult = mWifiNative.signalPoll(mInterfaceName);
         if (pollResult == null) {
-            return;
+            return stats;
         }
 
         int newRssi = pollResult.currentRssiDbm;
@@ -2434,44 +2190,11 @@
         int newRxLinkSpeed = pollResult.rxBitrateMbps;
 
         if (mVerboseLoggingEnabled) {
-            logd("fetchRssiLinkSpeedAndFrequencyNative rssi=" + newRssi
+            logd("updateLinkLayerStatsRssiSpeedFrequencyCapabilities rssi=" + newRssi
                     + " TxLinkspeed=" + newTxLinkSpeed + " freq=" + newFrequency
                     + " RxLinkSpeed=" + newRxLinkSpeed);
         }
 
-        if (newRssi > WifiInfo.INVALID_RSSI && newRssi < WifiInfo.MAX_RSSI) {
-            /*
-             * Positive RSSI is possible when devices are close(~0m apart) to each other.
-             * And there are some driver/firmware implementation, where they avoid
-             * reporting large negative rssi values by adding 256.
-             * so adjust the valid rssi reports for such implementations.
-             */
-            if (newRssi > (WifiInfo.INVALID_RSSI + 256)) {
-                Log.wtf(TAG, "Error! +ve value RSSI: " + newRssi);
-                newRssi -= 256;
-            }
-            mWifiInfo.setRssi(newRssi);
-            /*
-             * Rather then sending the raw RSSI out every time it
-             * changes, we precalculate the signal level that would
-             * be displayed in the status bar, and only send the
-             * broadcast if that much more coarse-grained number
-             * changes. This cuts down greatly on the number of
-             * broadcasts, at the cost of not informing others
-             * interested in RSSI of all the changes in signal
-             * level.
-             */
-            int newSignalLevel = RssiUtil.calculateSignalLevel(mContext, newRssi);
-            if (newSignalLevel != mLastSignalLevel) {
-                // TODO (b/162602799): Do we need to change the update frequency?
-                updateCapabilities();
-                sendRssiChangeBroadcast(newRssi);
-            }
-            mLastSignalLevel = newSignalLevel;
-        } else {
-            mWifiInfo.setRssi(WifiInfo.INVALID_RSSI);
-            updateCapabilities();
-        }
         /*
          * set Tx link speed only if it is valid
          */
@@ -2488,11 +2211,76 @@
         if (newFrequency > 0) {
             mWifiInfo.setFrequency(newFrequency);
         }
+        // updateLinkBandwidth() requires the latest frequency information
+        if (newRssi > WifiInfo.INVALID_RSSI && newRssi < WifiInfo.MAX_RSSI) {
+            /*
+             * Positive RSSI is possible when devices are close(~0m apart) to each other.
+             * And there are some driver/firmware implementation, where they avoid
+             * reporting large negative rssi values by adding 256.
+             * so adjust the valid rssi reports for such implementations.
+             */
+            if (newRssi > (WifiInfo.INVALID_RSSI + 256)) {
+                Log.wtf(getTag(), "Error! +ve value RSSI: " + newRssi);
+                newRssi -= 256;
+            }
+            mWifiInfo.setRssi(newRssi);
+            /*
+             * Rather then sending the raw RSSI out every time it
+             * changes, we precalculate the signal level that would
+             * be displayed in the status bar, and only send the
+             * broadcast if that much more coarse-grained number
+             * changes. This cuts down greatly on the number of
+             * broadcasts, at the cost of not informing others
+             * interested in RSSI of all the changes in signal
+             * level.
+             */
+            int newSignalLevel = RssiUtil.calculateSignalLevel(mContext, newRssi);
+            if (newSignalLevel != mLastSignalLevel) {
+                // TODO (b/162602799): Do we need to change the update frequency?
+                sendRssiChangeBroadcast(newRssi);
+            }
+            updateLinkBandwidthAndCapabilities(stats, newSignalLevel != mLastSignalLevel);
+            mLastSignalLevel = newSignalLevel;
+        } else {
+            mWifiInfo.setRssi(WifiInfo.INVALID_RSSI);
+            updateCapabilities();
+        }
         mWifiConfigManager.updateScanDetailCacheFromWifiInfo(mWifiInfo);
         /*
          * Increment various performance metrics
          */
-        mWifiMetrics.handlePollResult(mWifiInfo);
+        mWifiMetrics.handlePollResult(mInterfaceName, mWifiInfo);
+        return stats;
+    }
+
+    // Update the link bandwidth. If the link bandwidth changes by a large amount or signal level
+    // changes, also update network capabilities.
+    private void updateLinkBandwidthAndCapabilities(WifiLinkLayerStats stats,
+            boolean hasSignalLevelChanged) {
+        WifiScoreCard.PerNetwork network = mWifiScoreCard.lookupNetwork(mWifiInfo.getSSID());
+        network.updateLinkBandwidth(mLastLinkLayerStats, stats, mWifiInfo);
+        int newTxKbps = network.getTxLinkBandwidthKbps();
+        int newRxKbps = network.getRxLinkBandwidthKbps();
+        int txDeltaKbps = Math.abs(newTxKbps - mLastTxKbps);
+        int rxDeltaKbps = Math.abs(newRxKbps - mLastRxKbps);
+        int bwUpdateThresholdPercent = mContext.getResources().getInteger(
+                R.integer.config_wifiLinkBandwidthUpdateThresholdPercent);
+        if ((txDeltaKbps * 100  >  bwUpdateThresholdPercent * mLastTxKbps)
+                || (rxDeltaKbps * 100  >  bwUpdateThresholdPercent * mLastRxKbps)
+                || hasSignalLevelChanged) {
+            mLastTxKbps = newTxKbps;
+            mLastRxKbps = newRxKbps;
+            updateCapabilities();
+        }
+
+        int l2TxKbps = mWifiDataStall.getTxThroughputKbps();
+        int l2RxKbps = mWifiDataStall.getRxThroughputKbps();
+        if (l2RxKbps < 0 && l2TxKbps > 0) {
+            l2RxKbps = l2TxKbps;
+        }
+        int [] reportedKbps = {mLastTxKbps, mLastRxKbps};
+        int [] l2Kbps = {l2TxKbps, l2RxKbps};
+        network.updateBwMetrics(reportedKbps, l2Kbps);
     }
 
     // Polling has completed, hence we won't have a score anymore
@@ -2512,6 +2300,7 @@
         }
         // We own this instance of LinkProperties because IpClient passes us a copy.
         mLinkProperties = newLp;
+
         if (mNetworkAgent != null) {
             mNetworkAgent.sendLinkProperties(mLinkProperties);
         }
@@ -2558,14 +2347,23 @@
         Intent intent = new Intent(WifiManager.RSSI_CHANGED_ACTION);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
         intent.putExtra(WifiManager.EXTRA_NEW_RSSI, newRssi);
-        mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
-                android.Manifest.permission.ACCESS_WIFI_STATE);
+        mBroadcastQueue.queueOrSendBroadcast(
+                mClientModeManager,
+                () -> mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
+                        android.Manifest.permission.ACCESS_WIFI_STATE));
     }
 
     private void sendLinkConfigurationChangedBroadcast() {
         Intent intent = new Intent(WifiManager.ACTION_LINK_CONFIGURATION_CHANGED);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+        String summary = "broadcast=ACTION_LINK_CONFIGURATION_CHANGED";
+        if (mVerboseLoggingEnabled) Log.d(getTag(), "Queuing " + summary);
+        mBroadcastQueue.queueOrSendBroadcast(
+                mClientModeManager,
+                () -> {
+                    if (mVerboseLoggingEnabled) Log.d(getTag(), "Sending " + summary);
+                    mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+                });
     }
 
     /**
@@ -2578,7 +2376,15 @@
         Intent intent = new Intent(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
         intent.putExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, connected);
-        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+        String summary = "broadcast=SUPPLICANT_CONNECTION_CHANGE_ACTION"
+                + " EXTRA_SUPPLICANT_CONNECTED=" + connected;
+        if (mVerboseLoggingEnabled) Log.d(getTag(), "Queuing " + summary);
+        mBroadcastQueue.queueOrSendBroadcast(
+                mClientModeManager,
+                () -> {
+                    if (mVerboseLoggingEnabled) Log.d(getTag(), "Sending " + summary);
+                    mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+                });
     }
 
     /**
@@ -2605,29 +2411,61 @@
             hidden = true;
         }
         if (mVerboseLoggingEnabled) {
-            log("setDetailed state, old ="
-                    + mNetworkAgentState + " and new state=" + state
+            log("sendNetworkChangeBroadcast"
+                    + " oldState=" + mNetworkAgentState
+                    + " newState=" + state
                     + " hidden=" + hidden);
         }
         if (hidden || state == mNetworkAgentState) return;
         mNetworkAgentState = state;
-
-        Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        NetworkInfo networkInfo = makeNetworkInfo();
-        intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, networkInfo);
-        //TODO(b/69974497) This should be non-sticky, but settings needs fixing first.
-        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+        sendNetworkChangeBroadcastWithCurrentState();
     }
 
-    private NetworkInfo makeNetworkInfo() {
+    private void sendNetworkChangeBroadcastWithCurrentState() {
+        // copy into local variables to force lambda to capture by value and not reference, since
+        // mNetworkAgentState is mutable and can change
+        final DetailedState networkAgentState = mNetworkAgentState;
+        if (mVerboseLoggingEnabled) {
+            Log.d(getTag(), "Queueing broadcast=NETWORK_STATE_CHANGED_ACTION"
+                    + " networkAgentState=" + networkAgentState);
+        }
+        mBroadcastQueue.queueOrSendBroadcast(
+                mClientModeManager,
+                () -> sendNetworkChangeBroadcast(
+                        mContext, networkAgentState, mVerboseLoggingEnabled));
+    }
+
+    /** Send a NETWORK_STATE_CHANGED_ACTION broadcast. */
+    public static void sendNetworkChangeBroadcast(
+            Context context, DetailedState networkAgentState, boolean verboseLoggingEnabled) {
+        Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        NetworkInfo networkInfo = makeNetworkInfo(networkAgentState);
+        intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, networkInfo);
+        if (verboseLoggingEnabled) {
+            Log.d(TAG, "Sending broadcast=NETWORK_STATE_CHANGED_ACTION"
+                    + " networkAgentState=" + networkAgentState);
+        }
+        //TODO(b/69974497) This should be non-sticky, but settings needs fixing first.
+        context.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+    }
+
+    private static NetworkInfo makeNetworkInfo(DetailedState networkAgentState) {
         final NetworkInfo ni = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, NETWORKTYPE, "");
-        ni.setDetailedState(mNetworkAgentState, null, null);
+        ni.setDetailedState(networkAgentState, null, null);
         return ni;
     }
 
-    private SupplicantState handleSupplicantStateChange(Message message) {
-        StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+    private List<ScanResult.InformationElement> findMatchingInfoElements(@Nullable String bssid) {
+        if (bssid == null) return null;
+        ScanResult matchingScanResult = mScanRequestProxy.getScanResult(bssid);
+        if (matchingScanResult == null || matchingScanResult.informationElements == null) {
+            return null;
+        }
+        return Arrays.asList(matchingScanResult.informationElements);
+    }
+
+    private SupplicantState handleSupplicantStateChange(StateChangeResult stateChangeResult) {
         SupplicantState state = stateChangeResult.state;
         mWifiScoreCard.noteSupplicantStateChanging(mWifiInfo, state);
         // Supplicant state change
@@ -2638,40 +2476,38 @@
         // Network id and SSID are only valid when we start connecting
         if (SupplicantState.isConnecting(state)) {
             mWifiInfo.setNetworkId(stateChangeResult.networkId);
-            mWifiInfo.setBSSID(stateChangeResult.BSSID);
+            mWifiInfo.setBSSID(stateChangeResult.bssid);
             mWifiInfo.setSSID(stateChangeResult.wifiSsid);
             if (state == SupplicantState.ASSOCIATED) {
-                updateWifiInfoAfterAssociation();
+                updateWifiInfoLinkParamsAfterAssociation();
             }
+            mWifiInfo.setInformationElements(findMatchingInfoElements(stateChangeResult.bssid));
         } else {
             // Reset parameters according to WifiInfo.reset()
             mWifiInfo.setNetworkId(WifiConfiguration.INVALID_NETWORK_ID);
             mWifiInfo.setBSSID(null);
             mWifiInfo.setSSID(null);
             mWifiInfo.setWifiStandard(ScanResult.WIFI_STANDARD_UNKNOWN);
+            mWifiInfo.setInformationElements(null);
+            mWifiInfo.clearCurrentSecurityType();
         }
         updateLayer2Information();
         // SSID might have been updated, so call updateCapabilities
         updateCapabilities();
 
-        WifiConfiguration config = getCurrentWifiConfiguration();
+        WifiConfiguration config = getConnectedWifiConfigurationInternal();
         if (config == null) {
             // If not connected, this should be non-null.
-            config = getTargetWifiConfiguration();
+            config = getConnectingWifiConfigurationInternal();
         }
         if (config != null && config.networkId == mWifiInfo.getNetworkId()) {
-            mWifiInfo.setEphemeral(config.ephemeral);
-            mWifiInfo.setTrusted(config.trusted);
-            mWifiInfo.setOsuAp(config.osu);
-            if (config.fromWifiNetworkSpecifier || config.fromWifiNetworkSuggestion) {
-                mWifiInfo.setRequestingPackageName(config.creatorName);
-            }
+            updateWifiInfoWhenConnected(config);
 
             // Set meteredHint if scan result says network is expensive
             ScanDetailCache scanDetailCache = mWifiConfigManager.getScanDetailCacheForNetwork(
                     config.networkId);
             if (scanDetailCache != null) {
-                ScanDetail scanDetail = scanDetailCache.getScanDetail(stateChangeResult.BSSID);
+                ScanDetail scanDetail = scanDetailCache.getScanDetail(stateChangeResult.bssid);
                 if (scanDetail != null) {
                     mWifiInfo.setFrequency(scanDetail.getScanResult().frequency);
                     NetworkDetail networkDetail = scanDetail.getNetworkDetail();
@@ -2686,21 +2522,42 @@
         return state;
     }
 
-    private void updateWifiInfoAfterAssociation() {
-        WifiNative.ConnectionCapabilities capabilities =
-                mWifiNative.getConnectionCapabilities(mInterfaceName);
-        ThroughputPredictor throughputPredictor = mWifiInjector.getThroughputPredictor();
-        int maxTxLinkSpeedMbps = throughputPredictor.predictMaxTxThroughput(capabilities);
-        int maxRxLinkSpeedMbps = throughputPredictor.predictMaxRxThroughput(capabilities);
-        mWifiInfo.setWifiStandard(capabilities.wifiStandard);
+    private void updateWifiInfoWhenConnected(@NonNull WifiConfiguration config) {
+        mWifiInfo.setEphemeral(config.ephemeral);
+        mWifiInfo.setTrusted(config.trusted);
+        mWifiInfo.setOemPaid(config.oemPaid);
+        mWifiInfo.setOemPrivate(config.oemPrivate);
+        mWifiInfo.setCarrierMerged(config.carrierMerged);
+        mWifiInfo.setSubscriptionId(config.subscriptionId);
+        mWifiInfo.setOsuAp(config.osu);
+        if (config.fromWifiNetworkSpecifier || config.fromWifiNetworkSuggestion) {
+            mWifiInfo.setRequestingPackageName(config.creatorName);
+        }
+        mWifiInfo.setIsPrimary(isPrimary());
+        SecurityParams securityParams = config.getNetworkSelectionStatus()
+                .getCandidateSecurityParams();
+        if (securityParams != null) {
+            mWifiInfo.setCurrentSecurityType(securityParams.getSecurityType());
+        } else {
+            mWifiInfo.clearCurrentSecurityType();
+            Log.e(TAG, "Network connection candidate with no security parameters");
+        }
+    }
+
+    private void updateWifiInfoLinkParamsAfterAssociation() {
+        mLastConnectionCapabilities = mWifiNative.getConnectionCapabilities(mInterfaceName);
+        int maxTxLinkSpeedMbps = mThroughputPredictor.predictMaxTxThroughput(
+                mLastConnectionCapabilities);
+        int maxRxLinkSpeedMbps = mThroughputPredictor.predictMaxRxThroughput(
+                mLastConnectionCapabilities);
+        mWifiInfo.setWifiStandard(mLastConnectionCapabilities.wifiStandard);
         mWifiInfo.setMaxSupportedTxLinkSpeedMbps(maxTxLinkSpeedMbps);
         mWifiInfo.setMaxSupportedRxLinkSpeedMbps(maxRxLinkSpeedMbps);
-        mWifiMetrics.setConnectionMaxSupportedLinkSpeedMbps(
+        mWifiMetrics.setConnectionMaxSupportedLinkSpeedMbps(mInterfaceName,
                 maxTxLinkSpeedMbps, maxRxLinkSpeedMbps);
-        mWifiDataStall.setConnectionCapabilities(capabilities);
         if (mVerboseLoggingEnabled) {
             StringBuilder sb = new StringBuilder();
-            logd(sb.append("WifiStandard: ").append(capabilities.wifiStandard)
+            logd(sb.append("WifiStandard: ").append(mLastConnectionCapabilities.wifiStandard)
                     .append(" maxTxSpeed: ").append(maxTxLinkSpeedMbps)
                     .append(" maxRxSpeed: ").append(maxRxLinkSpeedMbps)
                     .toString());
@@ -2714,9 +2571,9 @@
         if (mIpClient != null) {
             Pair<String, String> p = mWifiScoreCard.getL2KeyAndGroupHint(mWifiInfo);
             if (!p.equals(mLastL2KeyAndGroupHint)) {
-                final MacAddress lastBssid = getCurrentBssid();
+                final MacAddress currentBssid = getMacAddressFromBssidString(mWifiInfo.getBSSID());
                 final Layer2Information l2Information = new Layer2Information(
-                        p.first, p.second, lastBssid);
+                        p.first, p.second, currentBssid);
                 // Update current BSSID on IpClient side whenever l2Key and groupHint
                 // pair changes (i.e. the initial connection establishment or L2 roaming
                 // happened). If we have COMPLETED the roaming to a different BSSID, start
@@ -2735,30 +2592,34 @@
     /**
      * Resets the Wi-Fi Connections by clearing any state, resetting any sockets
      * using the interface, stopping DHCP & disabling interface
+     *
+     * @param disconnectReason must be one of WifiDisconnectReported.FailureReason values
+     *                         defined in /frameworks/proto_logging/stats/atoms.proto
      */
-    private void handleNetworkDisconnect() {
+    private void handleNetworkDisconnect(boolean newConnectionInProgress, int disconnectReason) {
+        mWifiMetrics.reportNetworkDisconnect(mInterfaceName, disconnectReason,
+                mWifiInfo.getRssi(),
+                mWifiInfo.getLinkSpeed());
+
         if (mVerboseLoggingEnabled) {
-            log("handleNetworkDisconnect:"
-                    + " stack:" + Thread.currentThread().getStackTrace()[2].getMethodName()
-                    + " - " + Thread.currentThread().getStackTrace()[3].getMethodName()
-                    + " - " + Thread.currentThread().getStackTrace()[4].getMethodName()
-                    + " - " + Thread.currentThread().getStackTrace()[5].getMethodName());
+            Log.v(getTag(), "handleNetworkDisconnect: newConnectionInProgress: "
+                    + newConnectionInProgress, new Throwable());
         }
 
-        WifiConfiguration wifiConfig = getCurrentWifiConfiguration();
+        WifiConfiguration wifiConfig = getConnectedWifiConfigurationInternal();
         if (wifiConfig != null) {
             ScanResultMatchInfo matchInfo = ScanResultMatchInfo.fromWifiConfiguration(wifiConfig);
-            mWifiInjector.getWakeupController().setLastDisconnectInfo(matchInfo);
-            mWifiNetworkSuggestionsManager.handleDisconnect(wifiConfig, getCurrentBSSID());
+            // WakeupController should only care about the primary, internet providing network
+            if (isPrimary()) {
+                mWakeupController.setLastDisconnectInfo(matchInfo);
+            }
         }
         stopRssiMonitoringOffload();
 
         clearTargetBssid("handleNetworkDisconnect");
 
         // Don't stop DHCP if Fils connection is in progress.
-        if (mLastNetworkId != WifiConfiguration.INVALID_NETWORK_ID
-                && mTargetNetworkId != WifiConfiguration.INVALID_NETWORK_ID
-                && mLastNetworkId != mTargetNetworkId && mIpClientWithPreConnection) {
+        if (newConnectionInProgress && mIpClientWithPreConnection) {
             if (mVerboseLoggingEnabled) {
                 log("handleNetworkDisconnect: Don't stop IpClient as fils connection in progress: "
                         + " mLastNetworkId: " + mLastNetworkId
@@ -2768,6 +2629,14 @@
             stopDhcpSetup();
         }
 
+        // DISASSOC_AP_BUSY could be received in both after L3 connection is successful or right
+        // after BSSID association if the AP can't accept more stations.
+        if (disconnectReason == ReasonCode.DISASSOC_AP_BUSY) {
+            mWifiConfigManager.setRecentFailureAssociationStatus(
+                    mWifiInfo.getNetworkId(),
+                    WifiConfiguration.RECENT_FAILURE_DISCONNECTION_AP_BUSY);
+        }
+
         mWifiScoreReport.stopConnectedNetworkScorer();
         /* Reset data structures */
         mWifiScoreReport.reset();
@@ -2791,13 +2660,12 @@
         mLastSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
         mLastSimBasedConnectionCarrierName = null;
         checkAbnormalDisconnectionAndTakeBugReport();
-        mWifiScoreCard.resetConnectionState();
-        mWifiDataStall.reset();
+        mWifiScoreCard.resetConnectionState(mInterfaceName);
         updateLayer2Information();
     }
 
     void handlePreDhcpSetup() {
-        if (!mBluetoothConnectionActive) {
+        if (!mWifiGlobals.isBluetoothConnected()) {
             /*
              * There are problems setting the Wi-Fi driver's power
              * mode to active when bluetooth coexistence mode is
@@ -2829,13 +2697,18 @@
         // Update link layer stats
         getWifiLinkLayerStats();
 
-        if (mWifiP2pChannel != null) {
-            /* P2p discovery breaks dhcp, shut it down in order to get through this */
-            Message msg = new Message();
-            msg.what = WifiP2pServiceImpl.BLOCK_DISCOVERY;
-            msg.arg1 = WifiP2pServiceImpl.ENABLED;
-            msg.arg2 = CMD_PRE_DHCP_ACTION_COMPLETE;
-            mWifiP2pChannel.sendMessage(msg);
+        if (mWifiP2pConnection.isConnected()) {
+            // P2P discovery breaks DHCP, so shut it down in order to get through this.
+            // Once P2P service receives this message and processes it accordingly, it is supposed
+            // to send arg2 (i.e. CMD_PRE_DHCP_ACTION_COMPLETE) in a new Message.what back to
+            // ClientModeImpl so that we can continue.
+            // TODO(b/159060934): Need to ensure that CMD_PRE_DHCP_ACTION_COMPLETE is sent back to
+            //  the ClientModeImpl instance that originally sent it. Right now it is sent back to
+            //  all ClientModeImpl instances by WifiP2pConnection.
+            mWifiP2pConnection.sendMessage(
+                    WifiP2pServiceImpl.BLOCK_DISCOVERY,
+                    WifiP2pServiceImpl.ENABLED,
+                    CMD_PRE_DHCP_ACTION_COMPLETE);
         } else {
             // If the p2p service is not running, we can proceed directly.
             sendMessage(CMD_PRE_DHCP_ACTION_COMPLETE);
@@ -2862,7 +2735,8 @@
         setSuspendOptimizationsNative(SUSPEND_DUE_TO_DHCP, true);
         setPowerSave(true);
 
-        p2pSendMessage(WifiP2pServiceImpl.BLOCK_DISCOVERY, WifiP2pServiceImpl.DISABLED);
+        mWifiP2pConnection.sendMessage(
+                WifiP2pServiceImpl.BLOCK_DISCOVERY, WifiP2pServiceImpl.DISABLED);
 
         // Set the coexistence mode back to its default value
         mWifiNative.setBluetoothCoexistenceMode(
@@ -2879,11 +2753,11 @@
     public boolean setPowerSave(boolean ps) {
         if (mInterfaceName != null) {
             if (mVerboseLoggingEnabled) {
-                Log.d(TAG, "Setting power save for: " + mInterfaceName + " to: " + ps);
+                Log.d(getTag(), "Setting power save for: " + mInterfaceName + " to: " + ps);
             }
             mWifiNative.setPowerSave(mInterfaceName, ps);
         } else {
-            Log.e(TAG, "Failed to setPowerSave, interfaceName is null");
+            Log.e(getTag(), "Failed to setPowerSave, interfaceName is null");
             return false;
         }
         return true;
@@ -2898,10 +2772,10 @@
      */
     public boolean setLowLatencyMode(boolean enabled) {
         if (mVerboseLoggingEnabled) {
-            Log.d(TAG, "Setting low latency mode to " + enabled);
+            Log.d(getTag(), "Setting low latency mode to " + enabled);
         }
         if (!mWifiNative.setLowLatencyMode(enabled)) {
-            Log.e(TAG, "Failed to setLowLatencyMode");
+            Log.e(getTag(), "Failed to setLowLatencyMode");
             return false;
         }
         return true;
@@ -2915,17 +2789,19 @@
     private void reportConnectionAttemptStart(
             WifiConfiguration config, String targetBSSID, int roamType) {
         int overlapWithLastConnectionMs =
-                mWifiMetrics.startConnectionEvent(config, targetBSSID, roamType);
-        DeviceConfigFacade deviceConfigFacade = mWifiInjector.getDeviceConfigFacade();
-        if (deviceConfigFacade.isOverlappingConnectionBugreportEnabled()
+                mWifiMetrics.startConnectionEvent(mInterfaceName, config, targetBSSID, roamType);
+        if (mDeviceConfigFacade.isOverlappingConnectionBugreportEnabled()
                 && overlapWithLastConnectionMs
-                > deviceConfigFacade.getOverlappingConnectionDurationThresholdMs()) {
+                > mDeviceConfigFacade.getOverlappingConnectionDurationThresholdMs()) {
             String bugTitle = "Wi-Fi BugReport";
             String bugDetail = "Detect abnormal overlapping connection";
-            takeBugReport(bugTitle, bugDetail);
+            mWifiDiagnostics.takeBugReport(bugTitle, bugDetail);
         }
-        mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_STARTED);
-        mWrongPasswordNotifier.onNewConnectionAttempt();
+        mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_STARTED,
+                mClientModeManager);
+        if (isPrimary()) {
+            mWrongPasswordNotifier.onNewConnectionAttempt();
+        }
         removeMessages(CMD_DIAGS_CONNECT_TIMEOUT);
         sendMessageDelayed(CMD_DIAGS_CONNECT_TIMEOUT, DIAGS_CONNECT_TIMEOUT_MILLIS);
     }
@@ -2940,7 +2816,8 @@
                 break;
             default:
                 removeMessages(CMD_DIAGS_CONNECT_TIMEOUT);
-                mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_FAILED);
+                mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_FAILED,
+                        mClientModeManager);
         }
     }
 
@@ -2951,29 +2828,33 @@
     private void reportConnectionAttemptEnd(int level2FailureCode, int connectivityFailureCode,
             int level2FailureReason) {
         // if connected, this should be non-null.
-        WifiConfiguration configuration = getCurrentWifiConfiguration();
+        WifiConfiguration configuration = getConnectedWifiConfigurationInternal();
         if (configuration == null) {
             // If not connected, this should be non-null.
-            configuration = getTargetWifiConfiguration();
+            configuration = getConnectingWifiConfigurationInternal();
         }
 
         String bssid = mLastBssid == null ? mTargetBssid : mLastBssid;
         String ssid = mWifiInfo.getSSID();
         if (WifiManager.UNKNOWN_SSID.equals(ssid)) {
-            ssid = getTargetSsid();
+            ssid = getConnectingSsidInternal();
         }
         if (level2FailureCode != WifiMetrics.ConnectionEvent.FAILURE_NONE) {
-            int blocklistReason = convertToBssidBlocklistMonitorFailureReason(
+            int blocklistReason = convertToWifiBlocklistMonitorFailureReason(
                     level2FailureCode, level2FailureReason);
             if (blocklistReason != -1) {
-                int networkId = (configuration == null) ? WifiConfiguration.INVALID_NETWORK_ID
-                        : configuration.networkId;
-                int scanRssi = mWifiConfigManager.findScanRssi(networkId,
-                        mWifiHealthMonitor.getScanRssiValidTimeMs());
-                mWifiScoreCard.noteConnectionFailure(mWifiInfo, scanRssi, ssid, blocklistReason);
+                mWifiScoreCard.noteConnectionFailure(mWifiInfo, mLastScanRssi, ssid,
+                        blocklistReason);
                 checkAbnormalConnectionFailureAndTakeBugReport(ssid);
-                mBssidBlocklistMonitor.handleBssidConnectionFailure(bssid, ssid, blocklistReason,
-                        scanRssi);
+                mWifiBlocklistMonitor.handleBssidConnectionFailure(bssid, ssid,
+                        blocklistReason, mLastScanRssi);
+                WifiScoreCard.NetworkConnectionStats recentStats = mWifiScoreCard.lookupNetwork(
+                        ssid).getRecentStats();
+                if (recentStats.getCount(WifiScoreCard.CNT_CONSECUTIVE_CONNECTION_FAILURE)
+                        >= WifiBlocklistMonitor.NUM_CONSECUTIVE_FAILURES_PER_NETWORK_EXP_BACKOFF) {
+                    mWifiConfigManager.updateNetworkSelectionStatus(mTargetNetworkId,
+                            WifiConfiguration.NetworkSelectionStatus.DISABLED_CONSECUTIVE_FAILURES);
+                }
             }
         }
 
@@ -2997,20 +2878,23 @@
                 == WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE
                 && level2FailureReason != WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_WRONG_PSWD;
         if ((isAssociationRejection || isAuthenticationFailure)
-                && mWifiConfigManager.isInFlakyRandomizationSsidHotlist(mTargetNetworkId)) {
+                && mWifiConfigManager.isInFlakyRandomizationSsidHotlist(mTargetNetworkId)
+                && isPrimary()) {
             mConnectionFailureNotifier
                     .showFailedToConnectDueToNoRandomizedMacSupportNotification(mTargetNetworkId);
         }
 
-        mWifiMetrics.endConnectionEvent(level2FailureCode, connectivityFailureCode,
-                level2FailureReason);
-        mWifiConnectivityManager.handleConnectionAttemptEnded(level2FailureCode, bssid, ssid);
+        mWifiMetrics.endConnectionEvent(mInterfaceName, level2FailureCode,
+                connectivityFailureCode, level2FailureReason, mWifiInfo.getFrequency());
+        mWifiConnectivityManager.handleConnectionAttemptEnded(
+                mClientModeManager, level2FailureCode, bssid, ssid);
         if (configuration != null) {
-            mNetworkFactory.handleConnectionAttemptEnded(level2FailureCode, configuration);
+            mNetworkFactory.handleConnectionAttemptEnded(level2FailureCode, configuration, bssid);
             mWifiNetworkSuggestionsManager.handleConnectionAttemptEnded(
-                    level2FailureCode, configuration, getCurrentBSSID());
+                    level2FailureCode, configuration, getConnectedBssidInternal());
             ScanResult candidate = configuration.getNetworkSelectionStatus().getCandidate();
-            if (candidate != null && !TextUtils.equals(candidate.BSSID, getCurrentBSSID())) {
+            if (candidate != null
+                    && !TextUtils.equals(candidate.BSSID, getConnectedBssidInternal())) {
                 mWifiMetrics.incrementNumBssidDifferentSelectionBetweenFrameworkAndFirmware();
             }
         }
@@ -3022,22 +2906,12 @@
         if (config == null) return;
 
         switch(reason) {
-            case 14: // MICHAEL_MIC_FAILURE
-            case 15: // 4WAY_HANDSHAKE_TIMEOUT
-            case 16: // GROUP_KEY_UPDATE_TIMEOUT
-            case 17: // IE_IN_4WAY_DIFFERS
-            case 18: // GROUP_CIPHER_NOT_VALID
-            case 19: // PAIRWISE_CIPHER_NOT_VALID
-            case 20: // AKMP_NOT_VALID
-            case 23: // IEEE_802_1X_AUTH_FAILED
-            case 24: // CIPHER_SUITE_REJECTED
-            case 29: // BAD_CIPHER_OR_AKM
-            case 45: // PEERKEY_MISMATCH
-            case 49: // INVALID_PMKID
-                mWifiNative.removeNetworkCachedData(config.networkId);
+            case ReasonCode.UNSPECIFIED:
+            case ReasonCode.DEAUTH_LEAVING:
+                logi("Keep PMK cache for network disconnection reason " + reason);
                 break;
             default:
-                logi("Keep PMK cache for network disconnection reason " + reason);
+                mWifiNative.removeNetworkCachedData(config.networkId);
                 break;
         }
     }
@@ -3055,30 +2929,35 @@
         if (scanResult == null) {
             return WifiInfo.INVALID_RSSI;
         }
-        return mWifiInjector.getScoringParams().getSufficientRssi(scanResult.frequency);
+        return mScoringParams.getSufficientRssi(scanResult.frequency);
     }
 
-    private int convertToBssidBlocklistMonitorFailureReason(
+    private int convertToWifiBlocklistMonitorFailureReason(
             int level2FailureCode, int failureReason) {
         switch (level2FailureCode) {
             case WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_TIMED_OUT:
-                return BssidBlocklistMonitor.REASON_ASSOCIATION_TIMEOUT;
+                return WifiBlocklistMonitor.REASON_ASSOCIATION_TIMEOUT;
             case WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_REJECTION:
                 if (failureReason == WifiMetricsProto.ConnectionEvent
                         .ASSOCIATION_REJECTION_AP_UNABLE_TO_HANDLE_NEW_STA) {
-                    return BssidBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA;
+                    return WifiBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA;
                 }
-                return BssidBlocklistMonitor.REASON_ASSOCIATION_REJECTION;
+                return WifiBlocklistMonitor.REASON_ASSOCIATION_REJECTION;
             case WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE:
                 if (failureReason == WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_WRONG_PSWD) {
-                    return BssidBlocklistMonitor.REASON_WRONG_PASSWORD;
+                    return WifiBlocklistMonitor.REASON_WRONG_PASSWORD;
                 } else if (failureReason == WifiMetricsProto.ConnectionEvent
                         .AUTH_FAILURE_EAP_FAILURE) {
-                    return BssidBlocklistMonitor.REASON_EAP_FAILURE;
+                    return WifiBlocklistMonitor.REASON_EAP_FAILURE;
                 }
-                return BssidBlocklistMonitor.REASON_AUTHENTICATION_FAILURE;
+                return WifiBlocklistMonitor.REASON_AUTHENTICATION_FAILURE;
             case WifiMetrics.ConnectionEvent.FAILURE_DHCP:
-                return BssidBlocklistMonitor.REASON_DHCP_FAILURE;
+                return WifiBlocklistMonitor.REASON_DHCP_FAILURE;
+            case WifiMetrics.ConnectionEvent.FAILURE_NETWORK_DISCONNECTION:
+                if (failureReason == WifiMetricsProto.ConnectionEvent.DISCONNECTION_NON_LOCAL) {
+                    return WifiBlocklistMonitor.REASON_NONLOCAL_DISCONNECT_CONNECTING;
+                }
+                return -1;
             default:
                 return -1;
         }
@@ -3107,12 +2986,11 @@
 
         mWifiInfo.setInetAddress(addr);
 
-        final WifiConfiguration config = getCurrentWifiConfiguration();
+        final WifiConfiguration config = getConnectedWifiConfigurationInternal();
         if (config != null) {
-            mWifiInfo.setEphemeral(config.ephemeral);
-            mWifiInfo.setTrusted(config.trusted);
+            updateWifiInfoWhenConnected(config);
             mWifiConfigManager.updateRandomizedMacExpireTime(config, dhcpResults.leaseDuration);
-            mBssidBlocklistMonitor.handleDhcpProvisioningSuccess(mLastBssid, mWifiInfo.getSSID());
+            mWifiBlocklistMonitor.handleDhcpProvisioningSuccess(mLastBssid, mWifiInfo.getSSID());
         }
 
         // Set meteredHint if DHCP result says network is metered
@@ -3123,30 +3001,26 @@
             mWifiMetrics.addMeteredStat(config, false);
         }
 
-        updateCapabilities(config);
+        updateCapabilities();
     }
 
     private void handleSuccessfulIpConfiguration() {
         mLastSignalLevel = -1; // Force update of signal strength
-        WifiConfiguration c = getCurrentWifiConfiguration();
+        WifiConfiguration c = getConnectedWifiConfigurationInternal();
         if (c != null) {
             // Reset IP failure tracking
             c.getNetworkSelectionStatus().clearDisableReasonCounter(
                     WifiConfiguration.NetworkSelectionStatus.DISABLED_DHCP_FAILURE);
-
-            // Tell the framework whether the newly connected network is trusted or untrusted.
-            updateCapabilities(c);
         }
-        mWifiScoreCard.noteIpConfiguration(mWifiInfo);
     }
 
     private void handleIPv4Failure() {
         // TODO: Move this to provisioning failure, not DHCP failure.
         // DHCPv4 failure is expected on an IPv6-only network.
-        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_DHCP_FAILURE);
+        mWifiDiagnostics.triggerBugReportDataCapture(WifiDiagnostics.REPORT_REASON_DHCP_FAILURE);
         if (mVerboseLoggingEnabled) {
             int count = -1;
-            WifiConfiguration config = getCurrentWifiConfiguration();
+            WifiConfiguration config = getConnectedWifiConfigurationInternal();
             if (config != null) {
                 count = config.getNetworkSelectionStatus().getDisableReasonCounter(
                         WifiConfiguration.NetworkSelectionStatus.DISABLED_DHCP_FAILURE);
@@ -3259,31 +3133,6 @@
         return true;
     }
 
-    void registerNetworkFactory() {
-        if (!checkAndSetConnectivityInstance()) return;
-        mNetworkFactory.register();
-        mUntrustedNetworkFactory.register();
-    }
-
-    /**
-     * ClientModeImpl needs to enable/disable other services when wifi is in client mode.  This
-     * method allows ClientModeImpl to get these additional system services.
-     *
-     * At this time, this method is used to setup variables for P2P service and Wifi Aware.
-     */
-    private void getAdditionalWifiServiceInterfaces() {
-        // First set up Wifi Direct
-        if (mP2pSupported) {
-            WifiP2pManager wifiP2pService = mContext.getSystemService(WifiP2pManager.class);
-
-            if (wifiP2pService != null) {
-                mWifiP2pChannel = new AsyncChannel();
-                mWifiP2pChannel.connect(mContext, getHandler(),
-                        wifiP2pService.getP2pStateMachineMessenger());
-            }
-        }
-    }
-
      /**
      * Dynamically change the MAC address to use the locally randomized
      * MAC address generated for each network.
@@ -3292,25 +3141,24 @@
      */
     private void configureRandomizedMacAddress(WifiConfiguration config) {
         if (config == null) {
-            Log.e(TAG, "No config to change MAC address to");
+            Log.e(getTag(), "No config to change MAC address to");
             return;
         }
         String currentMacString = mWifiNative.getMacAddress(mInterfaceName);
-        MacAddress currentMac = currentMacString == null ? null :
-                MacAddress.fromString(currentMacString);
+        MacAddress currentMac = getMacAddressFromBssidString(currentMacString);
         MacAddress newMac = mWifiConfigManager.getRandomizedMacAndUpdateIfNeeded(config);
         if (!WifiConfiguration.isValidMacAddressForRandomization(newMac)) {
-            Log.wtf(TAG, "Config generated an invalid MAC address");
+            Log.wtf(getTag(), "Config generated an invalid MAC address");
         } else if (newMac.equals(currentMac)) {
-            Log.d(TAG, "No changes in MAC address");
+            Log.d(getTag(), "No changes in MAC address");
         } else {
-            mWifiMetrics.logStaEvent(StaEvent.TYPE_MAC_CHANGE, config);
+            mWifiMetrics.logStaEvent(mInterfaceName, StaEvent.TYPE_MAC_CHANGE, config);
             boolean setMacSuccess =
-                    mWifiNative.setMacAddress(mInterfaceName, newMac);
+                    mWifiNative.setStaMacAddress(mInterfaceName, newMac);
             if (setMacSuccess) {
                 mWifiNative.removeNetworkCachedDataIfNeeded(config.networkId, newMac);
             }
-            Log.d(TAG, "ConnectedMacRandomization SSID(" + config.getPrintableSsid()
+            Log.d(getTag(), "ConnectedMacRandomization SSID(" + config.getPrintableSsid()
                     + "). setMacAddress(" + newMac.toString() + ") from "
                     + currentMacString + " = " + setMacSuccess);
         }
@@ -3320,309 +3168,47 @@
      * Sets the current MAC to the factory MAC address.
      */
     private void setCurrentMacToFactoryMac(WifiConfiguration config) {
-        MacAddress factoryMac = mWifiNative.getFactoryMacAddress(mInterfaceName);
+        MacAddress factoryMac = retrieveFactoryMacAddressAndStoreIfNecessary();
         if (factoryMac == null) {
-            Log.e(TAG, "Fail to set factory MAC address. Factory MAC is null.");
+            Log.e(getTag(), "Fail to set factory MAC address. Factory MAC is null.");
             return;
         }
         String currentMacStr = mWifiNative.getMacAddress(mInterfaceName);
         if (!TextUtils.equals(currentMacStr, factoryMac.toString())) {
-            if (mWifiNative.setMacAddress(mInterfaceName, factoryMac)) {
+            if (mWifiNative.setStaMacAddress(mInterfaceName, factoryMac)) {
                 mWifiNative.removeNetworkCachedDataIfNeeded(config.networkId, factoryMac);
-                mWifiMetrics.logStaEvent(StaEvent.TYPE_MAC_CHANGE, config);
+                mWifiMetrics.logStaEvent(mInterfaceName, StaEvent.TYPE_MAC_CHANGE, config);
             } else {
-                Log.e(TAG, "Failed to set MAC address to " + "'" + factoryMac.toString() + "'");
+                Log.e(getTag(), "Failed to set MAC address to " + "'"
+                        + factoryMac.toString() + "'");
             }
         }
     }
 
     /**
-     * Helper method to check if Connected MAC Randomization is supported - onDown events are
-     * skipped if this feature is enabled (b/72459123).
-     *
-     * @return boolean true if Connected MAC randomization is supported, false otherwise
-     */
-    public boolean isConnectedMacRandomizationEnabled() {
-        return mContext.getResources().getBoolean(
-                R.bool.config_wifi_connected_mac_randomization_supported);
-    }
-
-    /**
-     * Helper method allowing ClientModeManager to report an error (interface went down) and trigger
-     * recovery.
-     *
-     * @param reason int indicating the SelfRecovery failure type.
-     */
-    public void failureDetected(int reason) {
-        // report a failure
-        mWifiInjector.getSelfRecovery().trigger(SelfRecovery.REASON_STA_IFACE_DOWN);
-    }
-
-    /**
-     * Helper method to check if WPA2 network upgrade feature is enabled in the framework
-     *
-     * @return boolean true if feature is enabled.
-     */
-    private boolean isWpa3SaeUpgradeEnabled() {
-        return mContext.getResources().getBoolean(R.bool.config_wifiSaeUpgradeEnabled);
-    }
-
-    /**
-     * Helper method to check if WPA2 network upgrade offload is enabled in the driver/fw
-     *
-     * @return boolean true if feature is enabled.
-     */
-    private boolean isWpa3SaeUpgradeOffloadEnabled() {
-        return mContext.getResources().getBoolean(R.bool.config_wifiSaeUpgradeOffloadEnabled);
-    }
-
-    /********************************************************
-     * HSM states
-     *******************************************************/
-
-    class DefaultState extends State {
-
-        @Override
-        public boolean processMessage(Message message) {
-            boolean handleStatus = HANDLED;
-            int callbackIdentifier = -1;
-            int netId;
-            boolean ok;
-
-            switch (message.what) {
-                case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: {
-                    AsyncChannel ac = (AsyncChannel) message.obj;
-                    if (ac == mWifiP2pChannel) {
-                        if (message.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
-                            p2pSendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
-                        } else {
-                            // TODO: We should probably do some cleanup or attempt a retry
-                            // b/34283611
-                            loge("WifiP2pService connection failure, error=" + message.arg1);
-                        }
-                    } else {
-                        loge("got HALF_CONNECTED for unknown channel");
-                    }
-                    break;
-                }
-                case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
-                    AsyncChannel ac = (AsyncChannel) message.obj;
-                    if (ac == mWifiP2pChannel) {
-                        loge("WifiP2pService channel lost, message.arg1 =" + message.arg1);
-                        //TODO: Re-establish connection to state machine after a delay (b/34283611)
-                        // mWifiP2pChannel.connect(mContext, getHandler(),
-                        // mWifiP2pManager.getMessenger());
-                    }
-                    break;
-                }
-                case CMD_BLUETOOTH_ADAPTER_STATE_CHANGE:
-                    // If BT was connected and then turned off, there is no CONNECTION_STATE_CHANGE
-                    // message. So we need to rely on STATE_CHANGE message to detect on->off
-                    // transition and update mBluetoothConnectionActive status correctly.
-                    mBluetoothConnectionActive = mBluetoothConnectionActive
-                            && message.arg1 != BluetoothAdapter.STATE_OFF;
-                    mWifiConnectivityManager.setBluetoothConnected(mBluetoothConnectionActive);
-                    break;
-                case CMD_BLUETOOTH_ADAPTER_CONNECTION_STATE_CHANGE:
-                    // Transition to a non-disconnected state does correctly
-                    // indicate BT is connected or being connected.
-                    mBluetoothConnectionActive =
-                            message.arg1 != BluetoothAdapter.STATE_DISCONNECTED;
-                    mWifiConnectivityManager.setBluetoothConnected(mBluetoothConnectionActive);
-                    break;
-                case CMD_ENABLE_RSSI_POLL:
-                    mEnableRssiPolling = (message.arg1 == 1);
-                    break;
-                case CMD_SET_HIGH_PERF_MODE:
-                    if (message.arg1 == 1) {
-                        setSuspendOptimizations(SUSPEND_DUE_TO_HIGH_PERF, false);
-                    } else {
-                        setSuspendOptimizations(SUSPEND_DUE_TO_HIGH_PERF, true);
-                    }
-                    break;
-                case CMD_INITIALIZE:
-                    mWifiNative.initialize();
-                    mWifiScoreReport.initialize();
-                    break;
-                case CMD_BOOT_COMPLETED:
-                    // get other services that we need to manage
-                    getAdditionalWifiServiceInterfaces();
-                    registerNetworkFactory();
-                    mSarManager.handleBootCompleted();
-                    break;
-                case CMD_SCREEN_STATE_CHANGED:
-                    handleScreenStateChanged(message.arg1 != 0);
-                    break;
-                case CMD_DISCONNECT:
-                case CMD_RECONNECT:
-                case CMD_REASSOCIATE:
-                case WifiMonitor.NETWORK_CONNECTION_EVENT:
-                case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
-                case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
-                case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
-                case WifiMonitor.ASSOCIATION_REJECTION_EVENT:
-                case CMD_RSSI_POLL:
-                case CMD_ONESHOT_RSSI_POLL:
-                case CMD_PRE_DHCP_ACTION:
-                case CMD_PRE_DHCP_ACTION_COMPLETE:
-                case CMD_POST_DHCP_ACTION:
-                case WifiMonitor.SUP_REQUEST_IDENTITY:
-                case WifiMonitor.SUP_REQUEST_SIM_AUTH:
-                case WifiMonitor.TARGET_BSSID_EVENT:
-                case CMD_START_CONNECT:
-                case CMD_START_ROAM:
-                case WifiMonitor.ASSOCIATED_BSSID_EVENT:
-                case CMD_UNWANTED_NETWORK:
-                case CMD_DISCONNECTING_WATCHDOG_TIMER:
-                case CMD_ROAM_WATCHDOG_TIMER:
-                case CMD_SET_OPERATIONAL_MODE:
-                    // using the CMD_SET_OPERATIONAL_MODE (sent at front of queue) to trigger the
-                    // state transitions performed in setOperationalMode.
-                    break;
-                case CMD_SET_SUSPEND_OPT_ENABLED:
-                    if (message.arg1 == 1) {
-                        if (message.arg2 == 1) {
-                            mSuspendWakeLock.release();
-                        }
-                        setSuspendOptimizations(SUSPEND_DUE_TO_SCREEN, true);
-                    } else {
-                        setSuspendOptimizations(SUSPEND_DUE_TO_SCREEN, false);
-                    }
-                    break;
-                case CMD_CONNECT_NETWORK:
-                    // wifi off, can't connect.
-                    callbackIdentifier = message.arg2;
-                    sendActionListenerFailure(callbackIdentifier, WifiManager.BUSY);
-                    break;
-                case CMD_SAVE_NETWORK:
-                    // wifi off, nothing more to do here.
-                    callbackIdentifier = message.arg2;
-                    sendActionListenerSuccess(callbackIdentifier);
-                    break;
-                case CMD_GET_SUPPORTED_FEATURES:
-                    long featureSet = (mWifiNative.getSupportedFeatureSet(mInterfaceName));
-                    replyToMessage(message, message.what, Long.valueOf(featureSet));
-                    break;
-                case CMD_GET_LINK_LAYER_STATS:
-                case CMD_GET_CURRENT_NETWORK:
-                    // Not supported hence reply with null message.obj
-                    replyToMessage(message, message.what, null);
-                    break;
-                case WifiP2pServiceImpl.P2P_CONNECTION_CHANGED:
-                    NetworkInfo info = (NetworkInfo) message.obj;
-                    mP2pConnected.set(info.isConnected());
-                    break;
-                case WifiP2pServiceImpl.DISCONNECT_WIFI_REQUEST:
-                    mTemporarilyDisconnectWifi = (message.arg1 == 1);
-                    replyToMessage(message, WifiP2pServiceImpl.DISCONNECT_WIFI_RESPONSE);
-                    break;
-                /* Link configuration (IP address, DNS, ...) changes notified via netlink */
-                case CMD_UPDATE_LINKPROPERTIES:
-                    updateLinkProperties((LinkProperties) message.obj);
-                    break;
-                case CMD_START_SUBSCRIPTION_PROVISIONING:
-                    replyToMessage(message, message.what, 0);
-                    break;
-                case CMD_IP_CONFIGURATION_SUCCESSFUL:
-                case CMD_IP_CONFIGURATION_LOST:
-                case CMD_IP_REACHABILITY_LOST:
-                    mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_DISCARD;
-                    break;
-                case CMD_START_IP_PACKET_OFFLOAD:
-                    /* fall-through */
-                case CMD_STOP_IP_PACKET_OFFLOAD:
-                case CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF:
-                case CMD_REMOVE_KEEPALIVE_PACKET_FILTER_FROM_APF:
-                    if (mNetworkAgent != null) {
-                        mNetworkAgent.sendSocketKeepaliveEvent(message.arg1,
-                                SocketKeepalive.ERROR_INVALID_NETWORK);
-                    }
-                    break;
-                case CMD_START_RSSI_MONITORING_OFFLOAD:
-                    mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_DISCARD;
-                    break;
-                case CMD_STOP_RSSI_MONITORING_OFFLOAD:
-                    mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_DISCARD;
-                    break;
-                case CMD_QUERY_OSU_ICON:
-                    /* reply with arg1 = 0 - it returns API failure to the calling app
-                     * (message.what is not looked at)
-                     */
-                    replyToMessage(message, message.what);
-                    break;
-                case CMD_RESET_SIM_NETWORKS:
-                    /* Defer this message until supplicant is started. */
-                    mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_DEFERRED;
-                    deferMessage(message);
-                    break;
-                case CMD_INSTALL_PACKET_FILTER:
-                    mWifiNative.installPacketFilter(mInterfaceName, (byte[]) message.obj);
-                    break;
-                case CMD_READ_PACKET_FILTER:
-                    byte[] data = mWifiNative.readPacketFilter(mInterfaceName);
-                    if (mIpClient != null) {
-                        mIpClient.readPacketFilterComplete(data);
-                    }
-                    break;
-                case CMD_SET_FALLBACK_PACKET_FILTERING:
-                    if ((boolean) message.obj) {
-                        mWifiNative.startFilteringMulticastV4Packets(mInterfaceName);
-                    } else {
-                        mWifiNative.stopFilteringMulticastV4Packets(mInterfaceName);
-                    }
-                    break;
-                case CMD_DIAGS_CONNECT_TIMEOUT:
-                    mWifiDiagnostics.reportConnectionEvent(
-                            BaseWifiDiagnostics.CONNECTION_EVENT_TIMEOUT);
-                    break;
-                case 0:
-                    // We want to notice any empty messages (with what == 0) that might crop up.
-                    // For example, we may have recycled a message sent to multiple handlers.
-                    Log.wtf(TAG, "Error! empty message encountered");
-                    break;
-                default:
-                    loge("Error! unhandled message" + message);
-                    break;
-            }
-
-            if (handleStatus == HANDLED) {
-                logStateAndMessage(message, this);
-            }
-
-            return handleStatus;
-        }
-    }
-
-    /**
      * Helper method to start other services and get state ready for client mode
      */
     private void setupClientMode() {
-        Log.d(TAG, "setupClientMode() ifacename = " + mInterfaceName);
-
-        setHighPerfModeEnabled(false);
-
-        mWifiStateTracker.updateState(WifiStateTracker.INVALID);
-        mIpClientCallbacks = new IpClientCallbacksImpl();
-        mFacade.makeIpClient(mContext, mInterfaceName, mIpClientCallbacks);
-        if (!mIpClientCallbacks.awaitCreation()) {
-            Log.wtf(getName(), "Timeout waiting for IpClient");
-        }
+        Log.d(getTag(), "setupClientMode() ifacename = " + mInterfaceName);
 
         setMulticastFilter(true);
         registerForWifiMonitorEvents();
-        mWifiInjector.getWifiLastResortWatchdog().clearAllFailureCounts();
-        setSupplicantLogLevel();
+        if (isPrimary()) {
+            mWifiLastResortWatchdog.clearAllFailureCounts();
+        }
+        mWifiNative.setSupplicantLogLevel(mVerboseLoggingEnabled);
 
-        // reset state related to supplicant starting
-        mSupplicantStateTracker.sendMessage(CMD_RESET_SUPPLICANT_STATE);
         // Initialize data structures
         mLastBssid = null;
         mLastNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
         mLastSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
         mLastSimBasedConnectionCarrierName = null;
         mLastSignalLevel = -1;
-        if (isConnectedMacRandomizationEnabled()) {
-            mWifiNative.setMacAddress(mInterfaceName, MacAddressUtils.createRandomUnicastAddress());
+        if (mWifiGlobals.isConnectedMacRandomizationEnabled()) {
+            if (!mWifiNative.setStaMacAddress(
+                    mInterfaceName, MacAddressUtils.createRandomUnicastAddress())) {
+                Log.e(getTag(), "Failed to set random MAC address on bootup");
+            }
         }
         mWifiInfo.setMacAddress(mWifiNative.getMacAddress(mInterfaceName));
         // TODO: b/79504296 This broadcast has been deprecated and should be removed
@@ -3630,20 +3216,16 @@
 
         mWifiNative.setExternalSim(mInterfaceName, true);
 
-        mCountryCode.setReadyForChange(true);
-
         mWifiDiagnostics.startPktFateMonitoring(mInterfaceName);
         mWifiDiagnostics.startLogging(mInterfaceName);
 
         mMboOceController.enable();
-        mWifiDataStall.enablePhoneStateListener();
 
-        /**
-         * Enable bluetooth coexistence scan mode when bluetooth connection is active.
-         * When this mode is on, some of the low-level scan parameters used by the
-         * driver are changed to reduce interference with bluetooth
-         */
-        mWifiNative.setBluetoothCoexistenceScanMode(mInterfaceName, mBluetoothConnectionActive);
+        // Enable bluetooth coexistence scan mode when bluetooth connection is active.
+        // When this mode is on, some of the low-level scan parameters used by the
+        // driver are changed to reduce interference with bluetooth
+        mWifiNative.setBluetoothCoexistenceScanMode(
+                mInterfaceName, mWifiGlobals.isBluetoothConnected());
         sendNetworkChangeBroadcast(DetailedState.DISCONNECTED);
 
         // Disable legacy multicast filtering, which on some chipsets defaults to enabled.
@@ -3664,42 +3246,48 @@
         mWifiNative.enableStaAutoReconnect(mInterfaceName, false);
         // STA has higher priority over P2P
         mWifiNative.setConcurrencyPriority(true);
+
+        // Retrieve and store the factory MAC address (on first bootup).
+        retrieveFactoryMacAddressAndStoreIfNecessary();
     }
 
     /**
      * Helper method to stop external services and clean up state from client mode.
      */
     private void stopClientMode() {
-        handleNetworkDisconnect();
+        handleNetworkDisconnect(false,
+                WifiStatsLog.WIFI_DISCONNECT_REPORTED__FAILURE_CODE__WIFI_DISABLED);
         // exiting supplicant started state is now only applicable to client mode
         mWifiDiagnostics.stopLogging(mInterfaceName);
 
         mMboOceController.disable();
-        mWifiDataStall.disablePhoneStateListener();
         if (mIpClient != null && mIpClient.shutdown()) {
             // Block to make sure IpClient has really shut down, lest cleanup
             // race with, say, bringup code over in tethering.
             mIpClientCallbacks.awaitShutdown();
         }
-        mCountryCode.setReadyForChange(false);
-        mInterfaceName = null;
-        mWifiScoreReport.setInterfaceName(null);
+        deregisterForWifiMonitorEvents(); // uses mInterfaceName, must call before nulling out
         // TODO: b/79504296 This broadcast has been deprecated and should be removed
         sendSupplicantConnectionChangedBroadcast(false);
-
-        // Remove any ephemeral or Passpoint networks, flush ANQP cache
-        mWifiConfigManager.removeAllEphemeralOrPasspointConfiguredNetworks();
-        mWifiConfigManager.clearUserTemporarilyDisabledList();
-        mPasspointManager.clearAnqpRequestsAndFlushCache();
     }
 
+    /**
+     * Helper method called when a L3 connection is successfully established to a network.
+     */
     void registerConnected() {
         if (mLastNetworkId != WifiConfiguration.INVALID_NETWORK_ID) {
-            mWifiConfigManager.updateNetworkAfterConnect(mLastNetworkId);
+            WifiConfiguration config = getConnectedWifiConfigurationInternal();
+            boolean shouldSetUserConnectChoice = config != null
+                    && isRecentlySelectedByTheUser(config)
+                    && config.getNetworkSelectionStatus().hasEverConnected()
+                    && mWifiPermissionsUtil.checkNetworkSettingsPermission(config.lastConnectUid);
+            mWifiConfigManager.updateNetworkAfterConnect(mLastNetworkId,
+                    shouldSetUserConnectChoice, mWifiInfo.getRssi());
             // Notify PasspointManager of Passpoint network connected event.
-            WifiConfiguration currentNetwork = getCurrentWifiConfiguration();
+            WifiConfiguration currentNetwork = getConnectedWifiConfigurationInternal();
             if (currentNetwork != null && currentNetwork.isPasspoint()) {
-                mPasspointManager.onPasspointNetworkConnected(currentNetwork.getKey());
+                mPasspointManager.onPasspointNetworkConnected(
+                        currentNetwork.getProfileKey());
             }
         }
     }
@@ -3714,22 +3302,66 @@
      * Returns WifiConfiguration object corresponding to the currently connected network, null if
      * not connected.
      */
-    public WifiConfiguration getCurrentWifiConfiguration() {
+    @Nullable private WifiConfiguration getConnectedWifiConfigurationInternal() {
         if (mLastNetworkId == WifiConfiguration.INVALID_NETWORK_ID) {
             return null;
         }
         return mWifiConfigManager.getConfiguredNetwork(mLastNetworkId);
     }
 
-    private WifiConfiguration getTargetWifiConfiguration() {
+    /**
+     * Returns WifiConfiguration object corresponding to the currently connecting network, null if
+     * not connecting.
+     */
+    @Nullable private WifiConfiguration getConnectingWifiConfigurationInternal() {
         if (mTargetNetworkId == WifiConfiguration.INVALID_NETWORK_ID) {
             return null;
         }
         return mWifiConfigManager.getConfiguredNetwork(mTargetNetworkId);
     }
 
+    @Nullable private String getConnectedBssidInternal() {
+        return mLastBssid;
+    }
+
+    @Nullable private String getConnectingBssidInternal() {
+        return mTargetBssid;
+    }
+
+    /**
+     * Returns WifiConfiguration object corresponding to the currently connected network, null if
+     * not connected.
+     */
+    @Override
+    @Nullable public WifiConfiguration getConnectedWifiConfiguration() {
+        if (!isConnected()) return null;
+        return getConnectedWifiConfigurationInternal();
+    }
+
+    /**
+     * Returns WifiConfiguration object corresponding to the currently connecting network, null if
+     * not connecting.
+     */
+    @Override
+    @Nullable public WifiConfiguration getConnectingWifiConfiguration() {
+        if (!isConnecting() && !isRoaming()) return null;
+        return getConnectingWifiConfigurationInternal();
+    }
+
+    @Override
+    @Nullable public String getConnectedBssid() {
+        if (!isConnected()) return null;
+        return getConnectedBssidInternal();
+    }
+
+    @Override
+    @Nullable public String getConnectingBssid() {
+        if (!isConnecting() && !isRoaming()) return null;
+        return getConnectingBssidInternal();
+    }
+
     ScanResult getCurrentScanResult() {
-        WifiConfiguration config = getCurrentWifiConfiguration();
+        WifiConfiguration config = getConnectedWifiConfigurationInternal();
         if (config == null) {
             return null;
         }
@@ -3747,29 +3379,25 @@
         return scanDetailCache.getScanResult(bssid);
     }
 
-    String getCurrentBSSID() {
-        return mLastBssid;
-    }
-
-    MacAddress getCurrentBssid() {
-        MacAddress bssid = null;
+    private MacAddress getMacAddressFromBssidString(@Nullable String bssidStr) {
         try {
-            bssid = (mLastBssid != null) ? MacAddress.fromString(mLastBssid) : null;
+            return (bssidStr != null) ? MacAddress.fromString(bssidStr) : null;
         } catch (IllegalArgumentException e) {
-            Log.e(TAG, "Invalid BSSID format: " + mLastBssid);
+            Log.e(getTag(), "Invalid BSSID format: " + bssidStr);
+            return null;
         }
-        return bssid;
     }
 
-    void connectToNetwork(WifiConfiguration config) {
+    private MacAddress getCurrentBssidInternalMacAddress() {
+        return getMacAddressFromBssidString(mLastBssid);
+    }
+
+    private void connectToNetwork(WifiConfiguration config) {
         if ((config != null) && mWifiNative.connectToNetwork(mInterfaceName, config)) {
-            mWifiInjector.getWifiLastResortWatchdog().noteStartConnectTime();
-            mWifiMetrics.logStaEvent(StaEvent.TYPE_CMD_START_CONNECT, config);
-            mLastConnectAttemptTimestamp = mClock.getWallClockMillis();
+            mWifiLastResortWatchdog.noteStartConnectTime(config.networkId);
+            mWifiMetrics.logStaEvent(mInterfaceName, StaEvent.TYPE_CMD_START_CONNECT, config);
             mIsAutoRoaming = false;
-            if (getCurrentState() != mDisconnectedState) {
-                transitionTo(mDisconnectingState);
-            }
+            transitionTo(mL2ConnectingState);
         } else {
             loge("CMD_START_CONNECT Failed to start connection to network " + config);
             mTargetWifiConfiguration = null;
@@ -3781,302 +3409,146 @@
         }
     }
 
-    class ConnectModeState extends State {
+    /********************************************************
+     * HSM states
+     *******************************************************/
+
+    class ConnectableState extends State {
+        private boolean mIsScreenStateChangeReceiverRegistered = false;
+        BroadcastReceiver mScreenStateChangeReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                String action = intent.getAction();
+                if (action.equals(Intent.ACTION_SCREEN_ON)) {
+                    sendMessage(CMD_SCREEN_STATE_CHANGED, 1);
+                } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
+                    sendMessage(CMD_SCREEN_STATE_CHANGED, 0);
+                }
+            }
+        };
 
         @Override
         public void enter() {
-            Log.d(TAG, "entering ConnectModeState: ifaceName = " + mInterfaceName);
-            mOperationalMode = CONNECT_MODE;
+            Log.d(getTag(), "entering ConnectableState: ifaceName = " + mInterfaceName);
+
+            setSuspendOptimizationsNative(SUSPEND_DUE_TO_HIGH_PERF, true);
+
+            mWifiStateTracker.updateState(mInterfaceName, WifiStateTracker.INVALID);
+            mIpClientCallbacks = new IpClientCallbacksImpl();
+            Log.d(getTag(), "Start makeIpClient ifaceName = " + mInterfaceName);
+            mFacade.makeIpClient(mContext, mInterfaceName, mIpClientCallbacks);
+            mIpClientCallbacks.awaitCreation();
+        }
+
+        private void continueEnterSetup(IpClientManager ipClientManager) {
+            mIpClient = ipClientManager;
             setupClientMode();
+
+            IntentFilter filter = new IntentFilter();
+            filter.addAction(Intent.ACTION_SCREEN_ON);
+            filter.addAction(Intent.ACTION_SCREEN_OFF);
+            if (!mIsScreenStateChangeReceiverRegistered) {
+                mContext.registerReceiver(mScreenStateChangeReceiver, filter);
+                mIsScreenStateChangeReceiverRegistered = true;
+            }
+            // Learn the initial state of whether the screen is on.
+            // We update this field when we receive broadcasts from the system.
+            handleScreenStateChanged(mContext.getSystemService(PowerManager.class).isInteractive());
+
             if (!mWifiNative.removeAllNetworks(mInterfaceName)) {
                 loge("Failed to remove networks on entering connect mode");
             }
             mWifiInfo.reset();
             mWifiInfo.setSupplicantState(SupplicantState.DISCONNECTED);
 
-            mWifiInjector.getWakeupController().reset();
             sendNetworkChangeBroadcast(DetailedState.DISCONNECTED);
 
-            // Inform WifiConnectivityManager that Wifi is enabled
-            mWifiConnectivityManager.setWifiEnabled(true);
-            mNetworkFactory.setWifiState(true);
             // Inform metrics that Wifi is Enabled (but not yet connected)
-            mWifiMetrics.setWifiState(WifiMetricsProto.WifiLog.WIFI_DISCONNECTED);
-            mWifiMetrics.logStaEvent(StaEvent.TYPE_WIFI_ENABLED);
+            mWifiMetrics.setWifiState(mInterfaceName, WifiMetricsProto.WifiLog.WIFI_DISCONNECTED);
+            mWifiMetrics.logStaEvent(mInterfaceName, StaEvent.TYPE_WIFI_ENABLED);
             mWifiScoreCard.noteSupplicantStateChanged(mWifiInfo);
-            mWifiHealthMonitor.setWifiEnabled(true);
-            mWifiDataStall.init();
         }
 
         @Override
         public void exit() {
-            mOperationalMode = DISABLED_MODE;
-
-            // Inform WifiConnectivityManager that Wifi is disabled
-            mWifiConnectivityManager.setWifiEnabled(false);
-            mNetworkFactory.setWifiState(false);
             // Inform metrics that Wifi is being disabled (Toggled, airplane enabled, etc)
-            mWifiMetrics.setWifiState(WifiMetricsProto.WifiLog.WIFI_DISABLED);
-            mWifiMetrics.logStaEvent(StaEvent.TYPE_WIFI_DISABLED);
-            // Inform scorecard that wifi is being disabled
-            mWifiScoreCard.noteWifiDisabled(mWifiInfo);
+            mWifiMetrics.setWifiState(mInterfaceName, WifiMetricsProto.WifiLog.WIFI_DISABLED);
+            mWifiMetrics.logStaEvent(mInterfaceName, StaEvent.TYPE_WIFI_DISABLED);
 
             if (!mWifiNative.removeAllNetworks(mInterfaceName)) {
                 loge("Failed to remove networks on exiting connect mode");
             }
-            mWifiInfo.reset();
-            mWifiInfo.setSupplicantState(SupplicantState.DISCONNECTED);
-            mWifiScoreCard.noteSupplicantStateChanged(mWifiInfo);
-            mWifiHealthMonitor.setWifiEnabled(false);
-            mWifiDataStall.reset();
+            if (mIsScreenStateChangeReceiverRegistered) {
+                mContext.unregisterReceiver(mScreenStateChangeReceiver);
+                mIsScreenStateChangeReceiverRegistered = false;
+            }
+
             stopClientMode();
+            mWifiScoreCard.doWrites();
         }
 
         @Override
         public boolean processMessage(Message message) {
-            WifiConfiguration config;
-            int netId;
-            boolean ok;
-            boolean didDisconnect;
-            String bssid;
-            String ssid;
-            NetworkUpdateResult result;
-            Set<Integer> removedNetworkIds;
-            int reasonCode;
-            boolean timedOut;
-            boolean handleStatus = HANDLED;
-            int callbackIdentifier = -1;
-
-            int level2FailureReason =
-                    WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN;
             switch (message.what) {
-                case WifiMonitor.ASSOCIATION_REJECTION_EVENT:
-                    stopIpClient();
-                    mWifiDiagnostics.captureBugReportData(
-                            WifiDiagnostics.REPORT_REASON_ASSOC_FAILURE);
-                    mDidBlackListBSSID = false;
-                    bssid = (String) message.obj;
-                    timedOut = message.arg1 > 0;
-                    reasonCode = message.arg2;
-                    Log.d(TAG, "Association Rejection event: bssid=" + bssid + " reason code="
-                            + reasonCode + " timedOut=" + Boolean.toString(timedOut));
-                    if (bssid == null || TextUtils.isEmpty(bssid)) {
-                        // If BSSID is null, use the target roam BSSID
-                        bssid = mTargetBssid;
-                    } else if (mTargetBssid == SUPPLICANT_BSSID_ANY) {
-                        // This is needed by BssidBlocklistMonitor to block continuously
-                        // failing BSSIDs. Need to set here because mTargetBssid is currently
-                        // not being set until association success.
-                        mTargetBssid = bssid;
-                    }
-                    mWifiConfigManager.updateNetworkSelectionStatus(mTargetNetworkId,
-                            WifiConfiguration.NetworkSelectionStatus
-                            .DISABLED_ASSOCIATION_REJECTION);
-                    mWifiConfigManager.setRecentFailureAssociationStatus(mTargetNetworkId,
-                            reasonCode);
-
-                    if (reasonCode == REASON_CODE_AP_UNABLE_TO_HANDLE_NEW_STA) {
-                        level2FailureReason = WifiMetricsProto.ConnectionEvent
-                                .ASSOCIATION_REJECTION_AP_UNABLE_TO_HANDLE_NEW_STA;
-                    }
-                    // If rejection occurred while Metrics is tracking a ConnnectionEvent, end it.
-                    reportConnectionAttemptEnd(
-                            timedOut
-                                    ? WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_TIMED_OUT
-                                    : WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_REJECTION,
-                            WifiMetricsProto.ConnectionEvent.HLF_NONE,
-                            level2FailureReason);
-                    if (reasonCode != REASON_CODE_AP_UNABLE_TO_HANDLE_NEW_STA) {
-                        mWifiInjector.getWifiLastResortWatchdog()
-                                .noteConnectionFailureAndTriggerIfNeeded(
-                                        getTargetSsid(), bssid,
-                                        WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
-                    }
-                    mTargetNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
-                    break;
-                case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
-                    stopIpClient();
-                    mWifiDiagnostics.captureBugReportData(
-                            WifiDiagnostics.REPORT_REASON_AUTH_FAILURE);
-                    int disableReason = WifiConfiguration.NetworkSelectionStatus
-                            .DISABLED_AUTHENTICATION_FAILURE;
-                    reasonCode = message.arg1;
-                    WifiConfiguration targetedNetwork =
-                            mWifiConfigManager.getConfiguredNetwork(mTargetNetworkId);
-                    // Check if this is a permanent wrong password failure.
-                    if (isPermanentWrongPasswordFailure(mTargetNetworkId, reasonCode)) {
-                        disableReason = WifiConfiguration.NetworkSelectionStatus
-                                .DISABLED_BY_WRONG_PASSWORD;
-                        if (targetedNetwork != null) {
-                            mWrongPasswordNotifier.onWrongPasswordError(
-                                    targetedNetwork.SSID);
-                        }
-                    } else if (reasonCode == WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE) {
-                        int errorCode = message.arg2;
-                        if (targetedNetwork != null && targetedNetwork.enterpriseConfig != null
-                                && targetedNetwork.enterpriseConfig.isAuthenticationSimBased()) {
-                            mEapFailureNotifier.onEapFailure(errorCode, targetedNetwork);
-                        }
-                        handleEapAuthFailure(mTargetNetworkId, errorCode);
-                        if (errorCode == WifiNative.EAP_SIM_NOT_SUBSCRIBED) {
-                            disableReason = WifiConfiguration.NetworkSelectionStatus
-                                .DISABLED_AUTHENTICATION_NO_SUBSCRIPTION;
-                        }
-                    }
-                    mWifiConfigManager.updateNetworkSelectionStatus(
-                            mTargetNetworkId, disableReason);
-                    mWifiConfigManager.clearRecentFailureReason(mTargetNetworkId);
-
-                    //If failure occurred while Metrics is tracking a ConnnectionEvent, end it.
-                    switch (reasonCode) {
-                        case WifiManager.ERROR_AUTH_FAILURE_NONE:
-                            level2FailureReason =
-                                    WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_NONE;
-                            break;
-                        case WifiManager.ERROR_AUTH_FAILURE_TIMEOUT:
-                            level2FailureReason =
-                                    WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_TIMEOUT;
-                            break;
-                        case WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD:
-                            level2FailureReason =
-                                    WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_WRONG_PSWD;
-                            break;
-                        case WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE:
-                            level2FailureReason =
-                                    WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_EAP_FAILURE;
-                            break;
-                        default:
-                            level2FailureReason =
-                                    WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN;
-                            break;
-                    }
-                    reportConnectionAttemptEnd(
-                            WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE,
-                            WifiMetricsProto.ConnectionEvent.HLF_NONE,
-                            level2FailureReason);
-                    if (reasonCode != WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD && reasonCode
-                            != WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE) {
-                        mWifiInjector.getWifiLastResortWatchdog()
-                                .noteConnectionFailureAndTriggerIfNeeded(
-                                        getTargetSsid(),
-                                        (mLastBssid == null) ? mTargetBssid : mLastBssid,
-                                        WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                case CMD_CONNECTABLE_STATE_SETUP:
+                    if (mIpClient != null) {
+                        loge("Setup connectable state again when IpClient is ready?");
+                    } else {
+                        IpClientManager ipClientManager = (IpClientManager) message.obj;
+                        continueEnterSetup(ipClientManager);
                     }
                     break;
-                case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
-                    SupplicantState state = handleSupplicantStateChange(message);
-
-                    // Supplicant can fail to report a NETWORK_DISCONNECTION_EVENT
-                    // when authentication times out after a successful connection,
-                    // we can figure this from the supplicant state. If supplicant
-                    // state is DISCONNECTED, but the agent is not disconnected, we
-                    // need to handle a disconnection
-                    if (state == SupplicantState.DISCONNECTED && mNetworkAgent != null) {
-                        if (mVerboseLoggingEnabled) {
-                            log("Missed CTRL-EVENT-DISCONNECTED, disconnect");
-                        }
-                        handleNetworkDisconnect();
-                        transitionTo(mDisconnectedState);
-                    }
-
-                    if (state == SupplicantState.COMPLETED) {
-                        mWifiScoreReport.noteIpCheck();
-                    }
+                case CMD_ENABLE_RSSI_POLL: {
+                    mEnableRssiPolling = (message.arg1 == 1);
                     break;
-                case WifiP2pServiceImpl.DISCONNECT_WIFI_REQUEST:
-                    if (message.arg1 == 1) {
-                        mWifiMetrics.logStaEvent(StaEvent.TYPE_FRAMEWORK_DISCONNECT,
+                }
+                case CMD_SCREEN_STATE_CHANGED: {
+                    handleScreenStateChanged(message.arg1 != 0);
+                    break;
+                }
+                case WifiP2pServiceImpl.DISCONNECT_WIFI_REQUEST: {
+                    if (mIpClient == null) {
+                        logd("IpClient is not ready, "
+                                + "WifiP2pServiceImpl.DISCONNECT_WIFI_REQUEST dropped");
+                        break;
+                    }
+                    if (mWifiP2pConnection.shouldTemporarilyDisconnectWifi()) {
+                        mWifiMetrics.logStaEvent(mInterfaceName, StaEvent.TYPE_FRAMEWORK_DISCONNECT,
                                 StaEvent.DISCONNECT_P2P_DISCONNECT_WIFI_REQUEST);
                         mWifiNative.disconnect(mInterfaceName);
-                        mTemporarilyDisconnectWifi = true;
                     } else {
                         mWifiNative.reconnect(mInterfaceName);
-                        mTemporarilyDisconnectWifi = false;
                     }
                     break;
-                case WifiMonitor.SUP_REQUEST_IDENTITY:
-                    netId = message.arg2;
-                    boolean identitySent = false;
-                    // For SIM & AKA/AKA' EAP method Only, get identity from ICC
-                    if (mTargetWifiConfiguration != null
-                            && mTargetWifiConfiguration.networkId == netId
-                            && mTargetWifiConfiguration.enterpriseConfig != null
-                            && mTargetWifiConfiguration.enterpriseConfig
-                                    .isAuthenticationSimBased()) {
-                        // Pair<identity, encrypted identity>
-                        Pair<String, String> identityPair = mWifiCarrierInfoManager
-                                .getSimIdentity(mTargetWifiConfiguration);
-                        if (identityPair != null && identityPair.first != null) {
-                            Log.i(TAG, "SUP_REQUEST_IDENTITY: identityPair=["
-                                    + ((identityPair.first.length() >= 7)
-                                    ? identityPair.first.substring(0, 7 /* Prefix+PLMN ID */)
-                                    + "****"
-                                    : identityPair.first) + ", "
-                                    + (!TextUtils.isEmpty(identityPair.second) ? identityPair.second
-                                    : "<NONE>") + "]");
-                            mWifiNative.simIdentityResponse(mInterfaceName, identityPair.first,
-                                    identityPair.second);
-                            identitySent = true;
-                        } else {
-                            Log.e(TAG, "Unable to retrieve identity from Telephony");
-                        }
-                    }
-
-                    if (!identitySent) {
-                        // Supplicant lacks credentials to connect to that network, hence black list
-                        ssid = (String) message.obj;
-                        if (mTargetWifiConfiguration != null && ssid != null
-                                && mTargetWifiConfiguration.SSID != null
-                                && mTargetWifiConfiguration.SSID.equals("\"" + ssid + "\"")) {
-                            mWifiConfigManager.updateNetworkSelectionStatus(
-                                    mTargetWifiConfiguration.networkId,
-                                    WifiConfiguration.NetworkSelectionStatus
-                                            .DISABLED_AUTHENTICATION_NO_CREDENTIALS);
-                        }
-                        mWifiMetrics.logStaEvent(StaEvent.TYPE_FRAMEWORK_DISCONNECT,
-                                StaEvent.DISCONNECT_GENERIC);
-                        mWifiNative.disconnect(mInterfaceName);
-                    }
-                    break;
-                case WifiMonitor.SUP_REQUEST_SIM_AUTH:
-                    logd("Received SUP_REQUEST_SIM_AUTH");
-                    SimAuthRequestData requestData = (SimAuthRequestData) message.obj;
-                    if (requestData != null) {
-                        if (requestData.protocol == WifiEnterpriseConfig.Eap.SIM) {
-                            handleGsmAuthRequest(requestData);
-                        } else if (requestData.protocol == WifiEnterpriseConfig.Eap.AKA
-                                || requestData.protocol == WifiEnterpriseConfig.Eap.AKA_PRIME) {
-                            handle3GAuthRequest(requestData);
-                        }
-                    } else {
-                        loge("Invalid SIM auth request");
-                    }
-                    break;
-                case CMD_START_SUBSCRIPTION_PROVISIONING:
-                    IProvisioningCallback callback = (IProvisioningCallback) message.obj;
-                    OsuProvider provider =
-                            (OsuProvider) message.getData().getParcelable(EXTRA_OSU_PROVIDER);
-                    int res = mPasspointManager.startSubscriptionProvisioning(
-                                    message.arg1, provider, callback) ? 1 : 0;
-                    replyToMessage(message, message.what, res);
-                    break;
-                case CMD_RECONNECT:
+                }
+                case CMD_RECONNECT: {
                     WorkSource workSource = (WorkSource) message.obj;
                     mWifiConnectivityManager.forceConnectivityScan(workSource);
                     break;
-                case CMD_REASSOCIATE:
-                    mLastConnectAttemptTimestamp = mClock.getWallClockMillis();
-                    mWifiNative.reassociate(mInterfaceName);
+                }
+                case CMD_REASSOCIATE: {
+                    if (mIpClient != null) {
+                        logd("IpClient is not ready, REASSOCIATE dropped");
+
+                        mWifiNative.reassociate(mInterfaceName);
+                    }
                     break;
-                case CMD_START_ROAM:
-                    mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_DISCARD;
-                    break;
-                case CMD_START_CONNECT:
+                }
+                case CMD_START_CONNECT: {
+                    if (mIpClient == null) {
+                        logd("IpClient is not ready, START_CONNECT dropped");
+
+                        break;
+                    }
                     /* connect command coming from auto-join */
-                    netId = message.arg1;
+                    int netId = message.arg1;
                     int uid = message.arg2;
-                    bssid = (String) message.obj;
+                    String bssid = (String) message.obj;
                     mSentHLPs = false;
+                    // Stop lingering (if it was lingering before) if we start a new connection.
+                    // This means that the ClientModeManager was reused for another purpose, so it
+                    // should no longer be in lingering mode.
+                    mClientModeManager.setShouldReduceNetworkScore(false);
 
                     if (!hasConnectionRequests()) {
                         if (mNetworkAgent == null) {
@@ -4089,21 +3561,23 @@
                             break;
                         }
                     }
-                    config = mWifiConfigManager.getConfiguredNetworkWithoutMasking(netId);
+                    WifiConfiguration config =
+                            mWifiConfigManager.getConfiguredNetworkWithoutMasking(netId);
                     logd("CMD_START_CONNECT "
                             + " my state " + getCurrentState().getName()
-                            + " nid=" + Integer.toString(netId)
-                            + " roam=" + Boolean.toString(mIsAutoRoaming));
+                            + " nid=" + netId
+                            + " roam=" + mIsAutoRoaming);
                     if (config == null) {
                         loge("CMD_START_CONNECT and no config, bail out...");
                         break;
                     }
                     mTargetNetworkId = netId;
                     // Update scorecard while there is still state from existing connection
-                    int scanRssi = mWifiConfigManager.findScanRssi(netId,
+                    mLastScanRssi = mWifiConfigManager.findScanRssi(netId,
                             mWifiHealthMonitor.getScanRssiValidTimeMs());
-                    mWifiScoreCard.noteConnectionAttempt(mWifiInfo, scanRssi, config.SSID);
-                    mBssidBlocklistMonitor.updateFirmwareRoamingConfiguration(config.SSID);
+                    mWifiScoreCard.noteConnectionAttempt(mWifiInfo, mLastScanRssi, config.SSID);
+                    mWifiBlocklistMonitor.setAllowlistSsids(config.SSID, Collections.emptyList());
+                    mWifiBlocklistMonitor.updateFirmwareRoamingConfiguration(Set.of(config.SSID));
 
                     updateWifiConfigOnStartConnection(config, bssid);
                     reportConnectionAttemptStart(config, mTargetBssid,
@@ -4111,14 +3585,12 @@
 
                     String currentMacAddress = mWifiNative.getMacAddress(mInterfaceName);
                     mWifiInfo.setMacAddress(currentMacAddress);
-                    Log.i(TAG, "Connecting with " + currentMacAddress + " as the mac address");
+                    Log.i(getTag(), "Connecting with " + currentMacAddress + " as the mac address");
 
                     mTargetWifiConfiguration = config;
+                    mNetworkNotFoundEventCount = 0;
                     /* Check for FILS configuration again after updating the config */
-                    if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.FILS_SHA256)
-                            || config.allowedKeyManagement.get(
-                            WifiConfiguration.KeyMgmt.FILS_SHA384)) {
-
+                    if (config.isFilsSha256Enabled() || config.isFilsSha384Enabled()) {
                         boolean isIpClientStarted = startIpClient(config, true);
                         if (isIpClientStarted) {
                             mIpClientWithPreConnection = true;
@@ -4127,32 +3599,48 @@
                     }
                     connectToNetwork(config);
                     break;
-                case CMD_START_FILS_CONNECTION:
+                }
+                case CMD_START_FILS_CONNECTION: {
+                    if (mIpClient == null) {
+                        logd("IpClient is not ready, START_FILS_CONNECTION dropped");
+                        break;
+                    }
                     mWifiMetrics.incrementConnectRequestWithFilsAkmCount();
                     List<Layer2PacketParcelable> packets;
                     packets = (List<Layer2PacketParcelable>) message.obj;
                     if (mVerboseLoggingEnabled) {
-                        Log.d(TAG, "Send HLP IEs to supplicant");
+                        Log.d(getTag(), "Send HLP IEs to supplicant");
                     }
                     addLayer2PacketsToHlpReq(packets);
-                    config = mTargetWifiConfiguration;
+                    WifiConfiguration config = mTargetWifiConfiguration;
                     connectToNetwork(config);
                     break;
-                case CMD_CONNECT_NETWORK:
-                    callbackIdentifier = message.arg2;
-                    result = (NetworkUpdateResult) message.obj;
-                    netId = result.getNetworkId();
+                }
+                case CMD_CONNECT_NETWORK: {
+                    ConnectNetworkMessage cnm = (ConnectNetworkMessage) message.obj;
+                    if (mIpClient == null) {
+                        logd("IpClient is not ready, CONNECT_NETWORK dropped");
+                        cnm.listener.sendFailure(WifiManager.ERROR);
+                        break;
+                    }
+                    NetworkUpdateResult result = cnm.result;
+                    int netId = result.getNetworkId();
                     connectToUserSelectNetwork(
                             netId, message.sendingUid, result.hasCredentialChanged());
-                    mWifiMetrics.logStaEvent(
-                            StaEvent.TYPE_CONNECT_NETWORK,
+                    mWifiMetrics.logStaEvent(mInterfaceName, StaEvent.TYPE_CONNECT_NETWORK,
                             mWifiConfigManager.getConfiguredNetwork(netId));
-                    sendActionListenerSuccess(callbackIdentifier);
+                    cnm.listener.sendSuccess();
                     break;
-                case CMD_SAVE_NETWORK:
-                    callbackIdentifier = message.arg2;
-                    result = (NetworkUpdateResult) message.obj;
-                    netId = result.getNetworkId();
+                }
+                case CMD_SAVE_NETWORK: {
+                    ConnectNetworkMessage cnm = (ConnectNetworkMessage) message.obj;
+                    if (mIpClient == null) {
+                        logd("IpClient is not ready, SAVE_NETWORK dropped");
+                        cnm.listener.sendFailure(WifiManager.ERROR);
+                        break;
+                    }
+                    NetworkUpdateResult result = cnm.result;
+                    int netId = result.getNetworkId();
                     if (mWifiInfo.getNetworkId() == netId) {
                         if (result.hasCredentialChanged()) {
                             // The network credentials changed and we're connected to this network,
@@ -4164,11 +3652,12 @@
                             if (result.hasProxyChanged()) {
                                 if (mIpClient != null) {
                                     log("Reconfiguring proxy on connection");
-                                    WifiConfiguration currentConfig = getCurrentWifiConfiguration();
+                                    WifiConfiguration currentConfig =
+                                            getConnectedWifiConfigurationInternal();
                                     if (currentConfig != null) {
                                         mIpClient.setHttpProxy(currentConfig.getHttpProxy());
                                     } else {
-                                        Log.w(TAG,
+                                        Log.w(getTag(),
                                                 "CMD_SAVE_NETWORK proxy change - but no current "
                                                         + "Wi-Fi config");
                                     }
@@ -4179,11 +3668,12 @@
                                 // We switched from DHCP to static or from static to DHCP, or the
                                 // static IP address has changed.
                                 log("Reconfiguring IP on connection");
-                                WifiConfiguration currentConfig = getCurrentWifiConfiguration();
+                                WifiConfiguration currentConfig =
+                                        getConnectedWifiConfigurationInternal();
                                 if (currentConfig != null) {
-                                    transitionTo(mObtainingIpState);
+                                    transitionTo(mL3ProvisioningState);
                                 } else {
-                                    Log.w(TAG, "CMD_SAVE_NETWORK Ip change - but no current "
+                                    Log.w(getTag(), "CMD_SAVE_NETWORK Ip change - but no current "
                                             + "Wi-Fi config");
                                 }
                             }
@@ -4193,153 +3683,25 @@
                         logi("CMD_SAVE_NETWORK credential changed for nid="
                                 + netId + " while disconnected. Connecting.");
                         startConnectToNetwork(netId, message.sendingUid, SUPPLICANT_BSSID_ANY);
-                    }
-                    sendActionListenerSuccess(callbackIdentifier);
-                    break;
-                case WifiMonitor.ASSOCIATED_BSSID_EVENT:
-                    // This is where we can confirm the connection BSSID. Use it to find the
-                    // right ScanDetail to populate metrics.
-                    String someBssid = (String) message.obj;
-                    if (someBssid != null) {
-                        // Get the ScanDetail associated with this BSSID.
-                        ScanDetailCache scanDetailCache =
-                                mWifiConfigManager.getScanDetailCacheForNetwork(mTargetNetworkId);
-                        if (scanDetailCache != null) {
-                            mWifiMetrics.setConnectionScanDetail(scanDetailCache.getScanDetail(
-                                    someBssid));
+                    } else if (result.hasCredentialChanged()) {
+                        WifiConfiguration currentConfig =
+                                getConnectedWifiConfigurationInternal();
+                        WifiConfiguration updatedConfig =
+                                mWifiConfigManager.getConfiguredNetwork(netId);
+                        if (currentConfig != null && currentConfig.isLinked(updatedConfig)) {
+                            logi("current network linked config saved, update linked networks");
+                            updateLinkedNetworks(currentConfig);
                         }
-                        // Update last associated BSSID
-                        mLastBssid = someBssid;
                     }
-                    handleStatus = NOT_HANDLED;
+                    cnm.listener.sendSuccess();
                     break;
-                case WifiMonitor.NETWORK_CONNECTION_EVENT:
-                    if (mVerboseLoggingEnabled) log("Network connection established");
-                    mLastNetworkId = message.arg1;
-                    mSentHLPs = message.arg2 == 1;
-                    if (mSentHLPs) mWifiMetrics.incrementL2ConnectionThroughFilsAuthCount();
-                    mWifiConfigManager.clearRecentFailureReason(mLastNetworkId);
-                    mLastBssid = (String) message.obj;
-                    reasonCode = message.arg2;
-                    // TODO: This check should not be needed after ClientModeImpl refactor.
-                    // Currently, the last connected network configuration is left in
-                    // wpa_supplicant, this may result in wpa_supplicant initiating connection
-                    // to it after a config store reload. Hence the old network Id lookups may not
-                    // work, so disconnect the network and let network selector reselect a new
-                    // network.
-                    config = getCurrentWifiConfiguration();
-                    if (config != null) {
-                        mWifiInfo.setBSSID(mLastBssid);
-                        mWifiInfo.setNetworkId(mLastNetworkId);
-                        mWifiInfo.setMacAddress(mWifiNative.getMacAddress(mInterfaceName));
-
-                        ScanDetailCache scanDetailCache =
-                                mWifiConfigManager.getScanDetailCacheForNetwork(config.networkId);
-                        if (scanDetailCache != null && mLastBssid != null) {
-                            ScanResult scanResult = scanDetailCache.getScanResult(mLastBssid);
-                            if (scanResult != null) {
-                                mWifiInfo.setFrequency(scanResult.frequency);
-                            }
-                        }
-
-                        // We need to get the updated pseudonym from supplicant for EAP-SIM/AKA/AKA'
-                        if (config.enterpriseConfig != null
-                                && config.enterpriseConfig.isAuthenticationSimBased()) {
-                            mLastSubId = mWifiCarrierInfoManager.getBestMatchSubscriptionId(config);
-                            mLastSimBasedConnectionCarrierName =
-                                mWifiCarrierInfoManager.getCarrierNameforSubId(mLastSubId);
-                            String anonymousIdentity =
-                                    mWifiNative.getEapAnonymousIdentity(mInterfaceName);
-                            if (!TextUtils.isEmpty(anonymousIdentity)
-                                    && !WifiCarrierInfoManager
-                                    .isAnonymousAtRealmIdentity(anonymousIdentity)) {
-                                String decoratedPseudonym = mWifiCarrierInfoManager
-                                        .decoratePseudonymWith3GppRealm(config,
-                                                anonymousIdentity);
-                                if (decoratedPseudonym != null) {
-                                    anonymousIdentity = decoratedPseudonym;
-                                }
-                                if (mVerboseLoggingEnabled) {
-                                    log("EAP Pseudonym: " + anonymousIdentity);
-                                }
-                                // Save the pseudonym only if it is a real one
-                                config.enterpriseConfig.setAnonymousIdentity(anonymousIdentity);
-                            } else {
-                                // Clear any stored pseudonyms
-                                config.enterpriseConfig.setAnonymousIdentity(null);
-                            }
-                            mWifiConfigManager.addOrUpdateNetwork(config, Process.WIFI_UID);
-                        }
-                        transitionTo(mObtainingIpState);
-                    } else {
-                        logw("Connected to unknown networkId " + mLastNetworkId
-                                + ", disconnecting...");
-                        sendMessage(CMD_DISCONNECT);
-                    }
-                    break;
-                case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
-                    // Calling handleNetworkDisconnect here is redundant because we might already
-                    // have called it when leaving L2ConnectedState to go to disconnecting state
-                    // or thru other path
-                    // We should normally check the mWifiInfo or mLastNetworkId so as to check
-                    // if they are valid, and only in this case call handleNEtworkDisconnect,
-                    // TODO: this should be fixed for a L MR release
-                    // The side effect of calling handleNetworkDisconnect twice is that a bunch of
-                    // idempotent commands are executed twice (stopping Dhcp, enabling the SPS mode
-                    // at the chip etc...
-                    if (mVerboseLoggingEnabled) log("ConnectModeState: Network connection lost ");
-                    clearNetworkCachedDataIfNeeded(getTargetWifiConfiguration(), message.arg2);
-                    handleNetworkDisconnect();
-                    transitionTo(mDisconnectedState);
-                    break;
-                case CMD_QUERY_OSU_ICON:
-                    mPasspointManager.queryPasspointIcon(
-                            ((Bundle) message.obj).getLong(EXTRA_OSU_ICON_QUERY_BSSID),
-                            ((Bundle) message.obj).getString(EXTRA_OSU_ICON_QUERY_FILENAME));
-                    break;
-                case WifiMonitor.TARGET_BSSID_EVENT:
-                    // Trying to associate to this BSSID
-                    if (message.obj != null) {
-                        mTargetBssid = (String) message.obj;
-                    }
-                    break;
-                case CMD_GET_LINK_LAYER_STATS:
-                    WifiLinkLayerStats stats = getWifiLinkLayerStats();
-                    replyToMessage(message, message.what, stats);
-                    break;
-                case CMD_RESET_SIM_NETWORKS:
-                    log("resetting EAP-SIM/AKA/AKA' networks since SIM was changed");
-                    int resetReason = message.arg1;
-                    if (resetReason == RESET_SIM_REASON_SIM_INSERTED) {
-                        // whenever a SIM is inserted clear all SIM related notifications
-                        mSimRequiredNotifier.dismissSimRequiredNotification();
-                    } else {
-                        mWifiConfigManager.resetSimNetworks();
-                    }
-                    if (resetReason != RESET_SIM_REASON_DEFAULT_DATA_SIM_CHANGED) {
-                        mWifiNetworkSuggestionsManager.resetCarrierPrivilegedApps();
-                    }
-                    break;
-                case CMD_BLUETOOTH_ADAPTER_STATE_CHANGE:
-                    // If BT was connected and then turned off, there is no CONNECTION_STATE_CHANGE
-                    // message. So we need to rely on STATE_CHANGE message to detect on->off
-                    // transition and update mBluetoothConnectionActive status correctly.
-                    mBluetoothConnectionActive = mBluetoothConnectionActive
-                            && message.arg1 != BluetoothAdapter.STATE_OFF;
+                }
+                case CMD_BLUETOOTH_CONNECTION_STATE_CHANGE: {
                     mWifiNative.setBluetoothCoexistenceScanMode(
-                            mInterfaceName, mBluetoothConnectionActive);
-                    mWifiConnectivityManager.setBluetoothConnected(mBluetoothConnectionActive);
+                            mInterfaceName, mWifiGlobals.isBluetoothConnected());
                     break;
-                case CMD_BLUETOOTH_ADAPTER_CONNECTION_STATE_CHANGE:
-                    // Transition to a non-disconnected state does correctly
-                    // indicate BT is connected or being connected.
-                    mBluetoothConnectionActive =
-                            message.arg1 != BluetoothAdapter.STATE_DISCONNECTED;
-                    mWifiNative.setBluetoothCoexistenceScanMode(
-                            mInterfaceName, mBluetoothConnectionActive);
-                    mWifiConnectivityManager.setBluetoothConnected(mBluetoothConnectionActive);
-                    break;
-                case CMD_SET_SUSPEND_OPT_ENABLED:
+                }
+                case CMD_SET_SUSPEND_OPT_ENABLED: {
                     if (message.arg1 == 1) {
                         setSuspendOptimizationsNative(SUSPEND_DUE_TO_SCREEN, true);
                         if (message.arg2 == 1) {
@@ -4349,24 +3711,11 @@
                         setSuspendOptimizationsNative(SUSPEND_DUE_TO_SCREEN, false);
                     }
                     break;
-                case CMD_SET_HIGH_PERF_MODE:
-                    if (message.arg1 == 1) {
-                        setSuspendOptimizationsNative(SUSPEND_DUE_TO_HIGH_PERF, false);
-                    } else {
-                        setSuspendOptimizationsNative(SUSPEND_DUE_TO_HIGH_PERF, true);
-                    }
-                    break;
-                case CMD_ENABLE_TDLS:
-                    if (message.obj != null) {
-                        String remoteAddress = (String) message.obj;
-                        boolean enable = (message.arg1 == 1);
-                        mWifiNative.startTdls(mInterfaceName, remoteAddress, enable);
-                    }
-                    break;
-                case WifiMonitor.ANQP_DONE_EVENT:
-                    // TODO(zqiu): remove this when switch over to wificond for ANQP requests.
+                }
+                case WifiMonitor.ANQP_DONE_EVENT: {
                     mPasspointManager.notifyANQPDone((AnqpEvent) message.obj);
                     break;
+                }
                 case CMD_STOP_IP_PACKET_OFFLOAD: {
                     int slot = message.arg1;
                     int ret = stopWifiIPPacketOffload(slot);
@@ -4375,40 +3724,139 @@
                     }
                     break;
                 }
-                case WifiMonitor.RX_HS20_ANQP_ICON_EVENT:
-                    // TODO(zqiu): remove this when switch over to wificond for icon requests.
+                case WifiMonitor.RX_HS20_ANQP_ICON_EVENT: {
                     mPasspointManager.notifyIconDone((IconEvent) message.obj);
                     break;
+                }
+                case WifiMonitor.HS20_DEAUTH_IMMINENT_EVENT:
+                    mPasspointManager.handleDeauthImminentEvent((WnmData) message.obj,
+                            getConnectedWifiConfigurationInternal());
+                    break;
+                case WifiMonitor.HS20_TERMS_AND_CONDITIONS_ACCEPTANCE_REQUIRED_EVENT:
+                    mWifiMetrics
+                            .incrementTotalNumberOfPasspointConnectionsWithTermsAndConditionsUrl();
+                    mTermsAndConditionsUrl = mPasspointManager
+                            .handleTermsAndConditionsEvent((WnmData) message.obj,
+                            getConnectedWifiConfigurationInternal());
+                    if (mTermsAndConditionsUrl == null) {
+                        loge("Disconnecting from Passpoint network due to an issue with the "
+                                + "Terms and Conditions URL");
+                        sendMessage(CMD_DISCONNECT, StaEvent.DISCONNECT_PASSPOINT_TAC);
+                    }
+                    break;
                 case WifiMonitor.HS20_REMEDIATION_EVENT:
-                    // TODO(zqiu): remove this when switch over to wificond for WNM frames
-                    // monitoring.
                     mPasspointManager.receivedWnmFrame((WnmData) message.obj);
                     break;
-                case WifiMonitor.MBO_OCE_BSS_TM_HANDLING_DONE:
+                case WifiMonitor.MBO_OCE_BSS_TM_HANDLING_DONE: {
                     handleBssTransitionRequest((BtmFrameData) message.obj);
                     break;
-                case CMD_CONFIG_ND_OFFLOAD:
+                }
+                case CMD_CONFIG_ND_OFFLOAD: {
                     final boolean enabled = (message.arg1 > 0);
                     mWifiNative.configureNeighborDiscoveryOffload(mInterfaceName, enabled);
                     break;
+                }
+                // Link configuration (IP address, DNS, ...) changes notified via netlink
+                case CMD_UPDATE_LINKPROPERTIES: {
+                    updateLinkProperties((LinkProperties) message.obj);
+                    break;
+                }
+                case CMD_START_IP_PACKET_OFFLOAD:
+                case CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF:
+                case CMD_REMOVE_KEEPALIVE_PACKET_FILTER_FROM_APF: {
+                    if (mNetworkAgent != null) {
+                        mNetworkAgent.sendSocketKeepaliveEvent(message.arg1,
+                                SocketKeepalive.ERROR_INVALID_NETWORK);
+                    }
+                    break;
+                }
+                case CMD_INSTALL_PACKET_FILTER: {
+                    mCachedPacketFilter = (byte[]) message.obj;
+                    if (mContext.getResources().getBoolean(
+                            R.bool.config_wifiEnableApfOnNonPrimarySta)
+                            || isPrimary()) {
+                        mWifiNative.installPacketFilter(mInterfaceName, mCachedPacketFilter);
+                    } else {
+                        Log.v(TAG, "Not applying packet filter on non primary CMM");
+                    }
+                    break;
+                }
+                case CMD_READ_PACKET_FILTER: {
+                    final byte[] packetFilter;
+                    if (mContext.getResources().getBoolean(
+                            R.bool.config_wifiEnableApfOnNonPrimarySta)
+                            || isPrimary()) {
+                        packetFilter = mWifiNative.readPacketFilter(mInterfaceName);
+                    } else {
+                        Log.v(TAG, "Retrieving cached packet filter on non primary CMM");
+                        packetFilter = mCachedPacketFilter;
+                    }
+                    if (mIpClient != null) {
+                        mIpClient.readPacketFilterComplete(packetFilter);
+                    }
+                    break;
+                }
+                case CMD_SET_FALLBACK_PACKET_FILTERING: {
+                    if ((boolean) message.obj) {
+                        mWifiNative.startFilteringMulticastV4Packets(mInterfaceName);
+                    } else {
+                        mWifiNative.stopFilteringMulticastV4Packets(mInterfaceName);
+                    }
+                    break;
+                }
+                case CMD_DIAGS_CONNECT_TIMEOUT: {
+                    mWifiDiagnostics.reportConnectionEvent(
+                            WifiDiagnostics.CONNECTION_EVENT_TIMEOUT, mClientModeManager);
+                    break;
+                }
+                case WifiP2pServiceImpl.P2P_CONNECTION_CHANGED:
+                case CMD_RESET_SIM_NETWORKS:
+                case WifiMonitor.NETWORK_CONNECTION_EVENT:
+                case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
+                case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
+                case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
+                case WifiMonitor.ASSOCIATION_REJECTION_EVENT:
+                case CMD_RSSI_POLL:
+                case CMD_ONESHOT_RSSI_POLL:
                 case CMD_PRE_DHCP_ACTION:
                 case CMD_PRE_DHCP_ACTION_COMPLETE:
                 case CMD_POST_DHCP_ACTION:
-                case CMD_IPV4_PROVISIONING_SUCCESS:
+                case WifiMonitor.SUP_REQUEST_IDENTITY:
+                case WifiMonitor.SUP_REQUEST_SIM_AUTH:
+                case WifiMonitor.TARGET_BSSID_EVENT:
+                case WifiMonitor.ASSOCIATED_BSSID_EVENT:
+                case WifiMonitor.TRANSITION_DISABLE_INDICATION:
+                case CMD_UNWANTED_NETWORK:
+                case CMD_CONNECTING_WATCHDOG_TIMER:
+                case WifiMonitor.NETWORK_NOT_FOUND_EVENT:
+                case CMD_ROAM_WATCHDOG_TIMER: {
+                    // no-op: all messages must be handled in the base state in case it was missed
+                    // in one of the child states.
+                    break;
+                }
+                case CMD_START_ROAM:
+                case CMD_START_RSSI_MONITORING_OFFLOAD:
+                case CMD_STOP_RSSI_MONITORING_OFFLOAD:
                 case CMD_IP_CONFIGURATION_SUCCESSFUL:
-                case CMD_IPV4_PROVISIONING_FAILURE:
-                    handleStatus = handleL3MessagesWhenNotConnected(message);
+                case CMD_IP_CONFIGURATION_LOST:
+                case CMD_IP_REACHABILITY_LOST: {
+                    mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_DISCARD;
                     break;
-                default:
-                    handleStatus = NOT_HANDLED;
+                }
+                case 0: {
+                    // We want to notice any empty messages (with what == 0) that might crop up.
+                    // For example, we may have recycled a message sent to multiple handlers.
+                    Log.wtf(getTag(), "Error! empty message encountered");
                     break;
+                }
+                default: {
+                    loge("Error! unhandled message" + message);
+                    break;
+                }
             }
 
-            if (handleStatus == HANDLED) {
-                logStateAndMessage(message, this);
-            }
-
-            return handleStatus;
+            logStateAndMessage(message, this);
+            return HANDLED;
         }
     }
 
@@ -4445,14 +3893,20 @@
     }
 
     private WifiNetworkAgentSpecifier createNetworkAgentSpecifier(
-            @NonNull WifiConfiguration currentWifiConfiguration, @Nullable String currentBssid) {
-        currentWifiConfiguration.BSSID = currentBssid;
+            @NonNull WifiConfiguration currentWifiConfiguration, @Nullable String currentBssid,
+            boolean matchLocationSensitiveInformation) {
+        // Defensive copy to avoid mutating the passed argument
+        final WifiConfiguration conf = new WifiConfiguration(currentWifiConfiguration);
+        conf.BSSID = currentBssid;
         WifiNetworkAgentSpecifier wns =
-                new WifiNetworkAgentSpecifier(currentWifiConfiguration);
+                new WifiNetworkAgentSpecifier(conf,
+                        WifiNetworkSpecifier.getBand(mWifiInfo.getFrequency()),
+                        matchLocationSensitiveInformation);
         return wns;
     }
 
-    private NetworkCapabilities getCapabilities(WifiConfiguration currentWifiConfiguration) {
+    private NetworkCapabilities getCapabilities(
+            WifiConfiguration currentWifiConfiguration, String currentBssid) {
         final NetworkCapabilities.Builder builder =
                 new NetworkCapabilities.Builder(mNetworkCapabilitiesFilter);
         // MatchAllNetworkSpecifier set in the mNetworkCapabilitiesFilter should never be set in the
@@ -4467,6 +3921,20 @@
         } else {
             builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED);
         }
+        if (SdkLevel.isAtLeastS()) {
+            if (mWifiInfo.isOemPaid()) {
+                builder.addCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID);
+                builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+            } else {
+                builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID);
+            }
+            if (mWifiInfo.isOemPrivate()) {
+                builder.addCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE);
+                builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+            } else {
+                builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE);
+            }
+        }
 
         builder.setOwnerUid(currentWifiConfiguration.creatorUid);
         builder.setAdministratorUids(new int[] {currentWifiConfiguration.creatorUid});
@@ -4494,11 +3962,16 @@
         // Only send out WifiInfo in >= Android S devices.
         if (SdkLevel.isAtLeastS()) {
             builder.setTransportInfo(new WifiInfo(mWifiInfo));
+
+            if (mWifiInfo.getSubscriptionId() != SubscriptionManager.INVALID_SUBSCRIPTION_ID
+                    && mWifiInfo.isCarrierMerged()) {
+                builder.setSubscriptionIds(Collections.singleton(mWifiInfo.getSubscriptionId()));
+            }
         }
 
         Pair<Integer, String> specificRequestUidAndPackageName =
                 mNetworkFactory.getSpecificNetworkRequestUidAndPackageName(
-                        currentWifiConfiguration);
+                        currentWifiConfiguration, currentBssid);
         // There is an active specific request.
         if (specificRequestUidAndPackageName.first != Process.INVALID_UID) {
             // Remove internet capability.
@@ -4506,40 +3979,61 @@
             // Fill up the uid/packageName for this connection.
             builder.setRequestorUid(specificRequestUidAndPackageName.first);
             builder.setRequestorPackageName(specificRequestUidAndPackageName.second);
-            // Fill up the network agent specifier for this connection.
-            builder.setNetworkSpecifier(createNetworkAgentSpecifier(
-                    currentWifiConfiguration, getCurrentBSSID()));
+            // Fill up the network agent specifier for this connection, allowing NetworkCallbacks
+            // to match local-only specifiers in requests. TODO(b/187921303): a third-party app can
+            // observe this location-sensitive information by registering a NetworkCallback.
+            builder.setNetworkSpecifier(createNetworkAgentSpecifier(currentWifiConfiguration,
+                    getConnectedBssidInternal(), true /* matchLocalOnlySpecifiers */));
+        } else {
+            // Fill up the network agent specifier for this connection, without allowing
+            // NetworkCallbacks to match local-only specifiers in requests.
+            builder.setNetworkSpecifier(createNetworkAgentSpecifier(currentWifiConfiguration,
+                    getConnectedBssidInternal(), false /* matchLocalOnlySpecifiers */));
         }
+
         updateLinkBandwidth(builder);
+        final NetworkCapabilities networkCapabilities = builder.build();
+        if (mVcnManager == null || !currentWifiConfiguration.carrierMerged) {
+            return networkCapabilities;
+        }
+        final VcnNetworkPolicyResult vcnNetworkPolicy =
+                mVcnManager.applyVcnNetworkPolicy(networkCapabilities, mLinkProperties);
+        if (vcnNetworkPolicy.isTeardownRequested()) {
+            sendMessage(CMD_DISCONNECT, StaEvent.DISCONNECT_VCN_REQUEST);
+        }
+        final NetworkCapabilities vcnCapability = vcnNetworkPolicy.getNetworkCapabilities();
+        if (!vcnCapability.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)) {
+            if (mVerboseLoggingEnabled) {
+                logd("NET_CAPABILITY_NOT_VCN_MANAGED is removed");
+            }
+            builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
+        }
+        if (!vcnCapability.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)) {
+            if (mVerboseLoggingEnabled) {
+                logd("NET_CAPABILITY_NOT_RESTRICTED is removed");
+            }
+            builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+        }
         return builder.build();
     }
 
     private void updateLinkBandwidth(NetworkCapabilities.Builder networkCapabilitiesBuilder) {
-        int rssiDbm = mWifiInfo.getRssi();
-        int txTputKbps = INVALID_THROUGHPUT;
-        int rxTputKbps = INVALID_THROUGHPUT;
-        // If RSSI is available, check if throughput is available
-        if (rssiDbm != WifiInfo.INVALID_RSSI && mWifiDataStall != null) {
-            txTputKbps = mWifiDataStall.getTxThroughputKbps();
-            rxTputKbps = mWifiDataStall.getRxThroughputKbps();
-        }
-        if (txTputKbps == INVALID_THROUGHPUT && rxTputKbps != INVALID_THROUGHPUT) {
-            txTputKbps = rxTputKbps;
-        } else if (rxTputKbps == INVALID_THROUGHPUT && txTputKbps != INVALID_THROUGHPUT) {
-            rxTputKbps = txTputKbps;
-        } else if (txTputKbps == INVALID_THROUGHPUT && rxTputKbps == INVALID_THROUGHPUT) {
+        int txTputKbps = 0;
+        int rxTputKbps = 0;
+        int currRssi = mWifiInfo.getRssi();
+        if (currRssi != WifiInfo.INVALID_RSSI) {
+            WifiScoreCard.PerNetwork network = mWifiScoreCard.lookupNetwork(mWifiInfo.getSSID());
+            txTputKbps = network.getTxLinkBandwidthKbps();
+            rxTputKbps = network.getRxLinkBandwidthKbps();
+        } else {
+            // Fall back to max link speed. This should rarely happen if ever
             int maxTxLinkSpeedMbps = mWifiInfo.getMaxSupportedTxLinkSpeedMbps();
             int maxRxLinkSpeedMbps = mWifiInfo.getMaxSupportedRxLinkSpeedMbps();
-            if (maxTxLinkSpeedMbps > 0) {
-                txTputKbps = maxTxLinkSpeedMbps * 1000;
-            }
-            if (maxRxLinkSpeedMbps > 0) {
-                rxTputKbps = maxRxLinkSpeedMbps * 1000;
-            }
+            txTputKbps = maxTxLinkSpeedMbps * 1000;
+            rxTputKbps = maxRxLinkSpeedMbps * 1000;
         }
         if (mVerboseLoggingEnabled) {
-            logd("tx tput in kbps: " + txTputKbps);
-            logd("rx tput in kbps: " + rxTputKbps);
+            logd("reported txKbps " + txTputKbps + " rxKbps " + rxTputKbps);
         }
         if (txTputKbps > 0) {
             networkCapabilitiesBuilder.setLinkUpstreamBandwidthKbps(txTputKbps);
@@ -4553,18 +4047,18 @@
      * Method to update network capabilities from the current WifiConfiguration.
      */
     public void updateCapabilities() {
-        updateCapabilities(getCurrentWifiConfiguration());
+        updateCapabilities(getConnectedWifiConfigurationInternal());
     }
 
     private void updateCapabilities(WifiConfiguration currentWifiConfiguration) {
-        updateCapabilities(getCapabilities(currentWifiConfiguration));
+        updateCapabilities(getCapabilities(currentWifiConfiguration, getConnectedBssidInternal()));
     }
 
     private void updateCapabilities(NetworkCapabilities networkCapabilities) {
         if (mNetworkAgent == null) {
             return;
         }
-        mNetworkAgent.sendNetworkCapabilities(networkCapabilities);
+        mNetworkAgent.sendNetworkCapabilitiesAndCache(networkCapabilities);
     }
 
     private void handleEapAuthFailure(int networkId, int errorCode) {
@@ -4586,82 +4080,94 @@
         }
     }
 
-    private class WifiNetworkAgent extends NetworkAgent {
-        WifiNetworkAgent(Context c, Looper l, String tag, NetworkCapabilities nc, LinkProperties lp,
-                int score, NetworkAgentConfig config, NetworkProvider provider) {
-            super(c, l, tag, nc, lp, score, config, provider);
-            register();
-        }
+    /**
+     * All callbacks are triggered on the main Wifi thread.
+     * See {@link WifiNetworkAgent#WifiNetworkAgent}'s looper argument in
+     * {@link WifiInjector#makeWifiNetworkAgent}.
+     */
+    private class WifiNetworkAgentCallback implements WifiNetworkAgent.Callback {
         private int mLastNetworkStatus = -1; // To detect when the status really changes
 
+        private boolean isThisCallbackActive() {
+            return mNetworkAgent != null && mNetworkAgent.getCallback() == this;
+        }
+
         @Override
         public void onNetworkUnwanted() {
             // Ignore if we're not the current networkAgent.
-            if (this != mNetworkAgent) return;
+            if (!isThisCallbackActive()) return;
             if (mVerboseLoggingEnabled) {
-                logd("WifiNetworkAgent -> Wifi unwanted score " + Integer.toString(
-                        mWifiInfo.getScore()));
+                logd("WifiNetworkAgent -> Wifi unwanted score " + mWifiInfo.getScore());
             }
             unwantedNetwork(NETWORK_STATUS_UNWANTED_DISCONNECT);
         }
 
         @Override
         public void onValidationStatus(int status, @Nullable Uri redirectUri) {
-            if (this != mNetworkAgent) return;
+            if (!isThisCallbackActive()) return;
             if (status == mLastNetworkStatus) return;
             mLastNetworkStatus = status;
             if (status == NetworkAgent.VALIDATION_STATUS_NOT_VALID) {
                 if (mVerboseLoggingEnabled) {
                     logd("WifiNetworkAgent -> Wifi networkStatus invalid, score="
-                            + Integer.toString(mWifiInfo.getScore()));
+                            + mWifiInfo.getScore());
                 }
                 unwantedNetwork(NETWORK_STATUS_UNWANTED_VALIDATION_FAILED);
             } else if (status == NetworkAgent.VALIDATION_STATUS_VALID) {
                 if (mVerboseLoggingEnabled) {
                     logd("WifiNetworkAgent -> Wifi networkStatus valid, score= "
-                            + Integer.toString(mWifiInfo.getScore()));
+                            + mWifiInfo.getScore());
                 }
-                mWifiMetrics.logStaEvent(StaEvent.TYPE_NETWORK_AGENT_VALID_NETWORK);
+                mWifiMetrics.logStaEvent(mInterfaceName, StaEvent.TYPE_NETWORK_AGENT_VALID_NETWORK);
                 doNetworkStatus(status);
             }
+            boolean captivePortalDetected = redirectUri != null
+                    && redirectUri.toString() != null
+                    && redirectUri.toString().length() > 0;
+            if (captivePortalDetected) {
+                Log.i(getTag(), "Captive Portal detected, status=" + status
+                        + ", redirectUri=" + redirectUri);
+                mWifiConfigManager.noteCaptivePortalDetected(mWifiInfo.getNetworkId());
+                mCmiMonitor.onCaptivePortalDetected(mClientModeManager);
+            }
         }
 
         @Override
         public void onSaveAcceptUnvalidated(boolean accept) {
-            if (this != mNetworkAgent) return;
+            if (!isThisCallbackActive()) return;
             ClientModeImpl.this.sendMessage(CMD_ACCEPT_UNVALIDATED, accept ? 1 : 0);
         }
 
         @Override
         public void onStartSocketKeepalive(int slot, @NonNull Duration interval,
                 @NonNull KeepalivePacketData packet) {
-            if (this != mNetworkAgent) return;
+            if (!isThisCallbackActive()) return;
             ClientModeImpl.this.sendMessage(
                     CMD_START_IP_PACKET_OFFLOAD, slot, (int) interval.getSeconds(), packet);
         }
 
         @Override
         public void onStopSocketKeepalive(int slot) {
-            if (this != mNetworkAgent) return;
+            if (!isThisCallbackActive()) return;
             ClientModeImpl.this.sendMessage(CMD_STOP_IP_PACKET_OFFLOAD, slot);
         }
 
         @Override
         public void onAddKeepalivePacketFilter(int slot, @NonNull KeepalivePacketData packet) {
-            if (this != mNetworkAgent) return;
+            if (!isThisCallbackActive()) return;
             ClientModeImpl.this.sendMessage(
                     CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF, slot, 0, packet);
         }
 
         @Override
         public void onRemoveKeepalivePacketFilter(int slot) {
-            if (this != mNetworkAgent) return;
+            if (!isThisCallbackActive()) return;
             ClientModeImpl.this.sendMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER_FROM_APF, slot);
         }
 
         @Override
         public void onSignalStrengthThresholdsUpdated(@NonNull int[] thresholds) {
-            if (this != mNetworkAgent) return;
+            if (!isThisCallbackActive()) return;
             // 0. If there are no thresholds, or if the thresholds are invalid,
             //    stop RSSI monitoring.
             // 1. Tell the hardware to start RSSI monitoring here, possibly adding MIN_VALUE and
@@ -4689,7 +4195,7 @@
                 if (val <= Byte.MAX_VALUE && val >= Byte.MIN_VALUE) {
                     rssiRange[i] = (byte) val;
                 } else {
-                    Log.e(TAG, "Illegal value " + val + " for RSSI thresholds: "
+                    Log.e(getTag(), "Illegal value " + val + " for RSSI thresholds: "
                             + Arrays.toString(rssiVals));
                     ClientModeImpl.this.sendMessage(CMD_STOP_RSSI_MONITORING_OFFLOAD,
                             mWifiInfo.getRssi());
@@ -4704,25 +4210,594 @@
 
         @Override
         public void onAutomaticReconnectDisabled() {
-            if (this != mNetworkAgent) return;
+            if (!isThisCallbackActive()) return;
             unwantedNetwork(NETWORK_STATUS_UNWANTED_DISABLE_AUTOJOIN);
         }
     }
 
-    void unwantedNetwork(int reason) {
+    private void unwantedNetwork(int reason) {
         sendMessage(CMD_UNWANTED_NETWORK, reason);
     }
 
-    void doNetworkStatus(int status) {
+    private void doNetworkStatus(int status) {
         sendMessage(CMD_NETWORK_STATUS, status);
     }
 
+    class ConnectingOrConnectedState extends State {
+        @Override
+        public void enter() {
+            if (mVerboseLoggingEnabled) Log.v(getTag(), "Entering ConnectingOrConnectedState");
+            mCmiMonitor.onConnectionStart(mClientModeManager);
+        }
+
+        @Override
+        public void exit() {
+            if (mVerboseLoggingEnabled) Log.v(getTag(), "Exiting ConnectingOrConnectedState");
+            mCmiMonitor.onConnectionEnd(mClientModeManager);
+
+            // Not connected/connecting to any network:
+            // 1. Disable the network in supplicant to prevent it from auto-connecting. We don't
+            // remove the network to avoid losing any cached info in supplicant (reauth, etc) in
+            // case we reconnect back to the same network.
+            // 2. Set a random MAC address to ensure that we're not leaking the MAC address.
+            mWifiNative.disableNetwork(mInterfaceName);
+            if (mWifiGlobals.isConnectedMacRandomizationEnabled()) {
+                if (!mWifiNative.setStaMacAddress(
+                        mInterfaceName, MacAddressUtils.createRandomUnicastAddress())) {
+                    Log.e(getTag(), "Failed to set random MAC address on disconnect");
+                }
+            }
+            mWifiInfo.reset();
+            mWifiInfo.setSupplicantState(SupplicantState.DISCONNECTED);
+            mWifiScoreCard.noteSupplicantStateChanged(mWifiInfo);
+
+            // For secondary client roles, they should stop themselves upon disconnection.
+            // - Primary role shouldn't because it is persistent, and should try connecting to other
+            //   networks upon disconnection.
+            // - ROLE_CLIENT_LOCAL_ONLY shouldn't because it has auto-retry logic if the connection
+            //   fails. WifiNetworkFactory will explicitly remove the CMM when the request is
+            //   complete.
+            // TODO(b/160346062): Maybe clean this up by having ClientModeManager register a
+            //  onExitConnectingOrConnectedState() callback with ClientModeImpl and implementing
+            //  this logic in ClientModeManager. ClientModeImpl should be role-agnostic.
+            ClientRole role = mClientModeManager.getRole();
+            if (role == ROLE_CLIENT_SECONDARY_LONG_LIVED
+                    || role == ROLE_CLIENT_SECONDARY_TRANSIENT) {
+                if (mVerboseLoggingEnabled) {
+                    Log.d(getTag(), "Disconnected in ROLE_CLIENT_SECONDARY_*, "
+                            + "stop ClientModeManager=" + mClientModeManager);
+                }
+                // stop owner ClientModeManager, which will in turn stop this ClientModeImpl
+                mClientModeManager.stop();
+            }
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            boolean handleStatus = HANDLED;
+            switch (message.what) {
+                case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT: {
+                    StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+                    SupplicantState state = handleSupplicantStateChange(stateChangeResult);
+                    // Supplicant can fail to report a NETWORK_DISCONNECTION_EVENT
+                    // when authentication times out after a successful connection,
+                    // we can figure this from the supplicant state. If supplicant
+                    // state is DISCONNECTED, but the agent is not disconnected, we
+                    // need to handle a disconnection
+                    if (mVerboseLoggingEnabled) {
+                        log("ConnectingOrConnectedState: Supplicant State change "
+                                + stateChangeResult);
+                    }
+                    if (state == SupplicantState.DISCONNECTED && mNetworkAgent != null) {
+                        if (mVerboseLoggingEnabled) {
+                            log("Missed CTRL-EVENT-DISCONNECTED, disconnect");
+                        }
+                        handleNetworkDisconnect(false,
+                                WIFI_DISCONNECT_REPORTED__FAILURE_CODE__SUPPLICANT_DISCONNECTED);
+                        transitionTo(mDisconnectedState);
+                    }
+                    if (state == SupplicantState.COMPLETED) {
+                        mWifiScoreReport.noteIpCheck();
+                    }
+                    break;
+                }
+                case WifiMonitor.ASSOCIATED_BSSID_EVENT: {
+                    // This is where we can confirm the connection BSSID. Use it to find the
+                    // right ScanDetail to populate metrics.
+                    String someBssid = (String) message.obj;
+                    if (someBssid != null) {
+                        // Get the ScanDetail associated with this BSSID.
+                        ScanDetailCache scanDetailCache =
+                                mWifiConfigManager.getScanDetailCacheForNetwork(mTargetNetworkId);
+                        if (scanDetailCache != null) {
+                            mWifiMetrics.setConnectionScanDetail(mInterfaceName,
+                                    scanDetailCache.getScanDetail(someBssid));
+                        }
+                        // Update last associated BSSID
+                        mLastBssid = someBssid;
+                    }
+                    handleStatus = NOT_HANDLED;
+                    break;
+                }
+                case WifiMonitor.NETWORK_CONNECTION_EVENT: {
+                    if (mVerboseLoggingEnabled) log("Network connection established");
+                    NetworkConnectionEventInfo connectionInfo =
+                            (NetworkConnectionEventInfo) message.obj;
+                    mLastNetworkId = connectionInfo.networkId;
+                    mSentHLPs = connectionInfo.isFilsConnection;
+                    if (mSentHLPs) mWifiMetrics.incrementL2ConnectionThroughFilsAuthCount();
+                    mWifiConfigManager.clearRecentFailureReason(mLastNetworkId);
+                    mLastBssid = connectionInfo.bssid;
+                    // TODO: This check should not be needed after ClientModeImpl refactor.
+                    // Currently, the last connected network configuration is left in
+                    // wpa_supplicant, this may result in wpa_supplicant initiating connection
+                    // to it after a config store reload. Hence the old network Id lookups may not
+                    // work, so disconnect the network and let network selector reselect a new
+                    // network.
+                    WifiConfiguration config = getConnectedWifiConfigurationInternal();
+                    if (config == null) {
+                        logw("Connected to unknown networkId " + mLastNetworkId
+                                + ", disconnecting...");
+                        sendMessage(CMD_DISCONNECT, StaEvent.DISCONNECT_UNKNOWN_NETWORK);
+                        break;
+                    }
+                    mWifiInfo.setBSSID(mLastBssid);
+                    mWifiInfo.setNetworkId(mLastNetworkId);
+                    mWifiInfo.setMacAddress(mWifiNative.getMacAddress(mInterfaceName));
+
+                    ScanDetailCache scanDetailCache =
+                            mWifiConfigManager.getScanDetailCacheForNetwork(config.networkId);
+                    ScanResult scanResult = null;
+                    if (scanDetailCache != null && mLastBssid != null) {
+                        scanResult = scanDetailCache.getScanResult(mLastBssid);
+                        if (scanResult != null) {
+                            mWifiInfo.setFrequency(scanResult.frequency);
+                        }
+                    }
+
+                    // We need to get the updated pseudonym from supplicant for EAP-SIM/AKA/AKA'
+                    if (config.enterpriseConfig != null
+                            && config.enterpriseConfig.isAuthenticationSimBased()) {
+                        mLastSubId = mWifiCarrierInfoManager.getBestMatchSubscriptionId(config);
+                        mLastSimBasedConnectionCarrierName =
+                                mWifiCarrierInfoManager.getCarrierNameForSubId(mLastSubId);
+                        String anonymousIdentity =
+                                mWifiNative.getEapAnonymousIdentity(mInterfaceName);
+                        if (!TextUtils.isEmpty(anonymousIdentity)
+                                && !WifiCarrierInfoManager
+                                .isAnonymousAtRealmIdentity(anonymousIdentity)) {
+                            String decoratedPseudonym = mWifiCarrierInfoManager
+                                    .decoratePseudonymWith3GppRealm(config,
+                                            anonymousIdentity);
+                            if (decoratedPseudonym != null) {
+                                anonymousIdentity = decoratedPseudonym;
+                            }
+                            if (mVerboseLoggingEnabled) {
+                                log("EAP Pseudonym: " + anonymousIdentity);
+                            }
+                            // Save the pseudonym only if it is a real one
+                            config.enterpriseConfig.setAnonymousIdentity(anonymousIdentity);
+                        } else {
+                            // Clear any stored pseudonyms
+                            config.enterpriseConfig.setAnonymousIdentity(null);
+                        }
+                        mWifiConfigManager.addOrUpdateNetwork(config, Process.WIFI_UID);
+                        if (config.isPasspoint()) {
+                            mPasspointManager.setAnonymousIdentity(config);
+                        } else if (config.fromWifiNetworkSuggestion) {
+                            mWifiNetworkSuggestionsManager.setAnonymousIdentity(config);
+                        }
+                    }
+                    // When connecting to Passpoint, ask for the Venue URL
+                    if (config.isPasspoint()) {
+                        mTermsAndConditionsUrl = null;
+                        if (scanResult == null && mLastBssid != null) {
+                            // The cached scan result of connected network would be null at the
+                            // first connection, try to check full scan result list again to look up
+                            // matched scan result associated to the current SSID and BSSID.
+                            scanResult = mScanRequestProxy.getScanResult(mLastBssid);
+                        }
+                        if (scanResult != null) {
+                            mPasspointManager.requestVenueUrlAnqpElement(scanResult);
+                        }
+                    }
+                    transitionTo(mL3ProvisioningState);
+                    break;
+                }
+                case WifiMonitor.NETWORK_DISCONNECTION_EVENT: {
+                    DisconnectEventInfo eventInfo = (DisconnectEventInfo) message.obj;
+                    if (mVerboseLoggingEnabled) {
+                        log("ConnectingOrConnectedState: Network disconnection " + eventInfo);
+                    }
+                    if (eventInfo.reasonCode == ReasonCode.FOURWAY_HANDSHAKE_TIMEOUT) {
+                        String bssid = !isValidBssid(eventInfo.bssid)
+                                ? mTargetBssid : eventInfo.bssid;
+                        mWifiLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
+                                getConnectingSsidInternal(), bssid,
+                                WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION,
+                                isConnected());
+                    }
+                    clearNetworkCachedDataIfNeeded(
+                            getConnectingWifiConfigurationInternal(), eventInfo.reasonCode);
+                    String targetSsid = getConnectingSsidInternal();
+                    // If network is removed while connecting, targetSsid can be null.
+                    boolean newConnectionInProgress =
+                            targetSsid != null && !eventInfo.ssid.equals(targetSsid);
+                    if (!newConnectionInProgress) {
+                        int level2FailureReason = eventInfo.locallyGenerated
+                                ? WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN :
+                                WifiMetricsProto.ConnectionEvent.DISCONNECTION_NON_LOCAL;
+                        if (!eventInfo.locallyGenerated) {
+                            mWifiScoreCard.noteNonlocalDisconnect(
+                                    mInterfaceName, eventInfo.reasonCode);
+                        }
+                        reportConnectionAttemptEnd(
+                                WifiMetrics.ConnectionEvent.FAILURE_NETWORK_DISCONNECTION,
+                                WifiMetricsProto.ConnectionEvent.HLF_NONE, level2FailureReason);
+                    }
+                    handleNetworkDisconnect(newConnectionInProgress, eventInfo.reasonCode);
+                    if (!newConnectionInProgress) {
+                        transitionTo(mDisconnectedState);
+                    }
+                    mTermsAndConditionsUrl = null;
+                    break;
+                }
+                case WifiMonitor.TARGET_BSSID_EVENT: {
+                    // Trying to associate to this BSSID
+                    if (message.obj != null) {
+                        mTargetBssid = (String) message.obj;
+                    }
+                    break;
+                }
+                case CMD_DISCONNECT: {
+                    mWifiMetrics.logStaEvent(mInterfaceName, StaEvent.TYPE_FRAMEWORK_DISCONNECT,
+                            message.arg1);
+                    mWifiNative.disconnect(mInterfaceName);
+                    break;
+                }
+                case CMD_PRE_DHCP_ACTION:
+                case CMD_PRE_DHCP_ACTION_COMPLETE:
+                case CMD_POST_DHCP_ACTION:
+                case CMD_IPV4_PROVISIONING_SUCCESS:
+                case CMD_IP_CONFIGURATION_SUCCESSFUL:
+                case CMD_IPV4_PROVISIONING_FAILURE: {
+                    handleStatus = handleL3MessagesWhenNotConnected(message);
+                    break;
+                }
+                default: {
+                    handleStatus = NOT_HANDLED;
+                    break;
+                }
+            }
+            if (handleStatus == HANDLED) {
+                logStateAndMessage(message, this);
+            }
+            return handleStatus;
+        }
+    }
+
+    class L2ConnectingState extends State {
+        @Override
+        public void enter() {
+            if (mVerboseLoggingEnabled) Log.v(getTag(), "Entering L2ConnectingState");
+            // Make sure we connect: we enter this state prior to connecting to a new
+            // network. In some cases supplicant ignores the connect requests (it might not
+            // find the target SSID in its cache), Therefore we end up stuck that state, hence the
+            // need for the watchdog.
+            mConnectingWatchdogCount++;
+            logd("Start Connecting Watchdog " + mConnectingWatchdogCount);
+            sendMessageDelayed(obtainMessage(CMD_CONNECTING_WATCHDOG_TIMER,
+                    mConnectingWatchdogCount, 0), CONNECTING_WATCHDOG_TIMEOUT_MS);
+        }
+
+        @Override
+        public void exit() {
+            if (mVerboseLoggingEnabled) Log.v(getTag(), "Exiting L2ConnectingState");
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            boolean handleStatus = HANDLED;
+            switch (message.what) {
+                case WifiMonitor.NETWORK_NOT_FOUND_EVENT:
+                    mNetworkNotFoundEventCount++;
+                    String networkName = (String) message.obj;
+                    if (networkName != null && !networkName.equals(getConnectingSsidInternal())) {
+                        loge("Network not found event received, network: " + networkName
+                                + " which is not the target network: "
+                                + getConnectingSsidInternal());
+                        break;
+                    }
+                    Log.d(getTag(), "Network not found event received: network: " + networkName);
+                    if (mNetworkNotFoundEventCount >= NETWORK_NOT_FOUND_EVENT_THRESHOLD
+                            && mTargetWifiConfiguration != null
+                            && mTargetWifiConfiguration.SSID != null
+                            && mTargetWifiConfiguration.SSID.equals(networkName)) {
+                        stopIpClient();
+                        mWifiConfigManager.updateNetworkSelectionStatus(
+                                mTargetWifiConfiguration.networkId,
+                                WifiConfiguration.NetworkSelectionStatus
+                                        .DISABLED_NETWORK_NOT_FOUND);
+                        if (SdkLevel.isAtLeastS()) {
+                            mWifiConfigManager.setRecentFailureAssociationStatus(
+                                    mTargetWifiConfiguration.networkId,
+                                    WifiConfiguration.RECENT_FAILURE_NETWORK_NOT_FOUND);
+                        }
+                        reportConnectionAttemptEnd(
+                                WifiMetrics.ConnectionEvent.FAILURE_NETWORK_NOT_FOUND,
+                                WifiMetricsProto.ConnectionEvent.HLF_NONE,
+                                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
+                        transitionTo(mDisconnectedState); // End of connection attempt.
+                    }
+                    break;
+                case WifiMonitor.ASSOCIATION_REJECTION_EVENT: {
+                    AssocRejectEventInfo assocRejectEventInfo = (AssocRejectEventInfo) message.obj;
+                    log("L2ConnectingState: Association rejection " + assocRejectEventInfo);
+                    if (!assocRejectEventInfo.ssid.equals(getConnectingSsidInternal())) {
+                        loge("Association rejection event received on not target network");
+                        break;
+                    }
+                    stopIpClient();
+                    mWifiDiagnostics.triggerBugReportDataCapture(
+                            WifiDiagnostics.REPORT_REASON_ASSOC_FAILURE);
+                    String bssid = assocRejectEventInfo.bssid;
+                    boolean timedOut = assocRejectEventInfo.timedOut;
+                    int statusCode = assocRejectEventInfo.statusCode;
+                    if (!isValidBssid(bssid)) {
+                        // If BSSID is null, use the target roam BSSID
+                        bssid = mTargetBssid;
+                    } else if (SUPPLICANT_BSSID_ANY.equals(mTargetBssid)) {
+                        // This is needed by WifiBlocklistMonitor to block continuously
+                        // failing BSSIDs. Need to set here because mTargetBssid is currently
+                        // not being set until association success.
+                        mTargetBssid = bssid;
+                    }
+                    mWifiConfigManager.updateNetworkSelectionStatus(mTargetNetworkId,
+                            WifiConfiguration.NetworkSelectionStatus
+                                    .DISABLED_ASSOCIATION_REJECTION);
+                    setAssociationRejectionStatusInConfig(mTargetNetworkId, assocRejectEventInfo);
+                    int level2FailureReason =
+                            WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN;
+                    if (statusCode == StatusCode.AP_UNABLE_TO_HANDLE_NEW_STA
+                            || statusCode == StatusCode.ASSOC_REJECTED_TEMPORARILY
+                            || statusCode == StatusCode.DENIED_INSUFFICIENT_BANDWIDTH) {
+                        level2FailureReason = WifiMetricsProto.ConnectionEvent
+                                .ASSOCIATION_REJECTION_AP_UNABLE_TO_HANDLE_NEW_STA;
+                    }
+                    // If rejection occurred while Metrics is tracking a ConnectionEvent, end it.
+                    reportConnectionAttemptEnd(
+                            timedOut
+                                    ? WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_TIMED_OUT
+                                    : WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_REJECTION,
+                            WifiMetricsProto.ConnectionEvent.HLF_NONE,
+                            level2FailureReason);
+                    if (level2FailureReason != WifiMetricsProto.ConnectionEvent
+                            .ASSOCIATION_REJECTION_AP_UNABLE_TO_HANDLE_NEW_STA) {
+                        mWifiLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
+                                getConnectingSsidInternal(), bssid,
+                                WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION,
+                                isConnected());
+                    }
+                    transitionTo(mDisconnectedState); // End of connection attempt.
+                    break;
+                }
+                case WifiMonitor.AUTHENTICATION_FAILURE_EVENT: {
+                    stopIpClient();
+                    mWifiDiagnostics.triggerBugReportDataCapture(
+                            WifiDiagnostics.REPORT_REASON_AUTH_FAILURE);
+                    int disableReason = WifiConfiguration.NetworkSelectionStatus
+                            .DISABLED_AUTHENTICATION_FAILURE;
+                    int reasonCode = message.arg1;
+                    int errorCode = message.arg2;
+                    log("L2ConnectingState: Authentication failure "
+                            + " reason=" + reasonCode + " error=" + errorCode);
+                    WifiConfiguration targetedNetwork =
+                            mWifiConfigManager.getConfiguredNetwork(mTargetNetworkId);
+                    // Check if this is a permanent wrong password failure.
+                    if (isPermanentWrongPasswordFailure(mTargetNetworkId, reasonCode)) {
+                        disableReason = WifiConfiguration.NetworkSelectionStatus
+                                .DISABLED_BY_WRONG_PASSWORD;
+                        if (targetedNetwork != null && isPrimary()) {
+                            mWrongPasswordNotifier.onWrongPasswordError(targetedNetwork.SSID);
+                        }
+                    } else if (reasonCode == WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE) {
+                        if (targetedNetwork != null && targetedNetwork.enterpriseConfig != null
+                                && targetedNetwork.enterpriseConfig.isAuthenticationSimBased()) {
+                            // only show EAP failure notification if primary
+                            if (mEapFailureNotifier
+                                    .onEapFailure(errorCode, targetedNetwork, isPrimary())) {
+                                disableReason = WifiConfiguration.NetworkSelectionStatus
+                                    .DISABLED_AUTHENTICATION_PRIVATE_EAP_ERROR;
+                                mWifiBlocklistMonitor.loadCarrierConfigsForDisableReasonInfos();
+                            }
+                        }
+                        handleEapAuthFailure(mTargetNetworkId, errorCode);
+                        if (errorCode == WifiNative.EAP_SIM_NOT_SUBSCRIBED) {
+                            disableReason = WifiConfiguration.NetworkSelectionStatus
+                                    .DISABLED_AUTHENTICATION_NO_SUBSCRIPTION;
+                        }
+                    }
+                    mWifiConfigManager.updateNetworkSelectionStatus(
+                            mTargetNetworkId, disableReason);
+                    mWifiConfigManager.clearRecentFailureReason(mTargetNetworkId);
+
+                    //If failure occurred while Metrics is tracking a ConnnectionEvent, end it.
+                    int level2FailureReason;
+                    switch (reasonCode) {
+                        case WifiManager.ERROR_AUTH_FAILURE_NONE:
+                            level2FailureReason =
+                                    WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_NONE;
+                            break;
+                        case WifiManager.ERROR_AUTH_FAILURE_TIMEOUT:
+                            level2FailureReason =
+                                    WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_TIMEOUT;
+                            break;
+                        case WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD:
+                            level2FailureReason =
+                                    WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_WRONG_PSWD;
+                            break;
+                        case WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE:
+                            level2FailureReason =
+                                    WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_EAP_FAILURE;
+                            break;
+                        default:
+                            level2FailureReason =
+                                    WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN;
+                            break;
+                    }
+                    reportConnectionAttemptEnd(
+                            WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE,
+                            WifiMetricsProto.ConnectionEvent.HLF_NONE,
+                            level2FailureReason);
+                    if (reasonCode != WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD && reasonCode
+                            != WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE) {
+                        mWifiLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
+                                getConnectingSsidInternal(),
+                                (mLastBssid == null) ? mTargetBssid : mLastBssid,
+                                WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION,
+                                isConnected());
+                    }
+                    transitionTo(mDisconnectedState); // End of connection attempt.
+                    break;
+                }
+                case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT: {
+                    StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+                    if (SupplicantState.isConnecting(stateChangeResult.state)) {
+                        WifiConfiguration config = mWifiConfigManager.getConfiguredNetwork(
+                                stateChangeResult.networkId);
+                        // Update Passpoint information before setNetworkDetailedState as
+                        // WifiTracker monitors NETWORK_STATE_CHANGED_ACTION to update UI.
+                        mWifiInfo.setFQDN(null);
+                        mWifiInfo.setPasspointUniqueId(null);
+                        mWifiInfo.setOsuAp(false);
+                        mWifiInfo.setProviderFriendlyName(null);
+                        if (config != null && (config.isPasspoint() || config.osu)) {
+                            if (config.isPasspoint()) {
+                                mWifiInfo.setFQDN(config.FQDN);
+                                mWifiInfo.setPasspointUniqueId(config.getPasspointUniqueId());
+                            } else {
+                                mWifiInfo.setOsuAp(true);
+                            }
+                            mWifiInfo.setProviderFriendlyName(config.providerFriendlyName);
+                        }
+                    }
+                    sendNetworkChangeBroadcast(
+                            WifiInfo.getDetailedStateOf(stateChangeResult.state));
+                    // Let the parent state handle the rest of the state changed.
+                    handleStatus = NOT_HANDLED;
+                    break;
+                }
+                case WifiMonitor.SUP_REQUEST_IDENTITY: {
+                    int netId = message.arg2;
+                    boolean identitySent = false;
+                    // For SIM & AKA/AKA' EAP method Only, get identity from ICC
+                    if (mTargetWifiConfiguration != null
+                            && mTargetWifiConfiguration.networkId == netId
+                            && mTargetWifiConfiguration.enterpriseConfig != null
+                            && mTargetWifiConfiguration.enterpriseConfig
+                            .isAuthenticationSimBased()) {
+                        // Pair<identity, encrypted identity>
+                        Pair<String, String> identityPair = mWifiCarrierInfoManager
+                                .getSimIdentity(mTargetWifiConfiguration);
+                        if (identityPair != null && identityPair.first != null) {
+                            Log.i(getTag(), "SUP_REQUEST_IDENTITY: identityPair=["
+                                    + ((identityPair.first.length() >= 7)
+                                    ? identityPair.first.substring(0, 7 /* Prefix+PLMN ID */)
+                                    + "****"
+                                    : identityPair.first) + ", "
+                                    + (!TextUtils.isEmpty(identityPair.second) ? identityPair.second
+                                    : "<NONE>") + "]");
+                            mWifiNative.simIdentityResponse(mInterfaceName, identityPair.first,
+                                    identityPair.second);
+                            identitySent = true;
+                        } else {
+                            Log.e(getTag(), "Unable to retrieve identity from Telephony");
+                        }
+                    }
+
+                    if (!identitySent) {
+                        // Supplicant lacks credentials to connect to that network, hence black list
+                        String ssid = (String) message.obj;
+                        if (mTargetWifiConfiguration != null && ssid != null
+                                && mTargetWifiConfiguration.SSID != null
+                                && mTargetWifiConfiguration.SSID.equals("\"" + ssid + "\"")) {
+                            mWifiConfigManager.updateNetworkSelectionStatus(
+                                    mTargetWifiConfiguration.networkId,
+                                    WifiConfiguration.NetworkSelectionStatus
+                                            .DISABLED_AUTHENTICATION_NO_CREDENTIALS);
+                        }
+                        mWifiMetrics.logStaEvent(mInterfaceName, StaEvent.TYPE_FRAMEWORK_DISCONNECT,
+                                StaEvent.DISCONNECT_GENERIC);
+                        mWifiNative.disconnect(mInterfaceName);
+                    }
+                    break;
+                }
+                case WifiMonitor.SUP_REQUEST_SIM_AUTH: {
+                    logd("Received SUP_REQUEST_SIM_AUTH");
+                    SimAuthRequestData requestData = (SimAuthRequestData) message.obj;
+                    if (requestData != null) {
+                        if (requestData.protocol == WifiEnterpriseConfig.Eap.SIM) {
+                            handleGsmAuthRequest(requestData);
+                        } else if (requestData.protocol == WifiEnterpriseConfig.Eap.AKA
+                                || requestData.protocol == WifiEnterpriseConfig.Eap.AKA_PRIME) {
+                            handle3GAuthRequest(requestData);
+                        }
+                    } else {
+                        loge("Invalid SIM auth request");
+                    }
+                    break;
+                }
+                case CMD_CONNECTING_WATCHDOG_TIMER: {
+                    if (mConnectingWatchdogCount == message.arg1) {
+                        if (mVerboseLoggingEnabled) log("Connecting watchdog! -> disconnect");
+                        handleNetworkDisconnect(false,
+                                WifiStatsLog.WIFI_DISCONNECT_REPORTED__FAILURE_CODE__CONNECTING_WATCHDOG_TIMER);
+                        transitionTo(mDisconnectedState);
+                    }
+                    break;
+                }
+                case WifiMonitor.TRANSITION_DISABLE_INDICATION: {
+                    log("Received TRANSITION_DISABLE_INDICATION: networkId=" + message.arg1
+                            + ", indication=" + message.arg2);
+                    mWifiConfigManager.updateNetworkTransitionDisable(message.arg1, message.arg2);
+                    break;
+                }
+                case WifiMonitor.NETWORK_CONNECTION_EVENT: {
+                    NetworkConnectionEventInfo connectionInfo =
+                            (NetworkConnectionEventInfo) message.obj;
+                    String quotedOrHexConnectingSsid = getConnectingSsidInternal();
+                    String quotedOrHexConnectedSsid = NativeUtil.encodeSsid(
+                            NativeUtil.byteArrayToArrayList(connectionInfo.wifiSsid.getOctets()));
+                    if (quotedOrHexConnectingSsid != null
+                            && !quotedOrHexConnectingSsid.equals(quotedOrHexConnectedSsid)) {
+                        // possibly a NETWORK_CONNECTION_EVENT for a successful roam on the previous
+                        // network while connecting to a new network, ignore it.
+                        Log.d(TAG, "Connecting to ssid=" + quotedOrHexConnectingSsid + ", but got "
+                                + "NETWORK_CONNECTION_EVENT for ssid=" + quotedOrHexConnectedSsid
+                                + ", ignoring");
+                        break;
+                    }
+                    handleStatus = NOT_HANDLED;
+                    break;
+                }
+                default: {
+                    handleStatus = NOT_HANDLED;
+                    break;
+                }
+            }
+            if (handleStatus == HANDLED) {
+                logStateAndMessage(message, this);
+            }
+            return handleStatus;
+        }
+    }
+
     class L2ConnectedState extends State {
         class RssiEventHandler implements WifiNative.WifiRssiEventHandler {
             @Override
             public void onRssiThresholdBreached(byte curRssi) {
                 if (mVerboseLoggingEnabled) {
-                    Log.e(TAG, "onRssiThresholdBreach event. Cur Rssi = " + curRssi);
+                    Log.e(getTag(), "onRssiThresholdBreach event. Cur Rssi = " + curRssi);
                 }
                 sendMessage(CMD_RSSI_THRESHOLD_BREACHED, curRssi);
             }
@@ -4734,17 +4809,21 @@
         public void enter() {
             mRssiPollToken++;
             if (mEnableRssiPolling) {
-                mLinkProbeManager.resetOnNewConnection();
+                if (isPrimary()) {
+                    mLinkProbeManager.resetOnNewConnection();
+                }
                 sendMessage(CMD_RSSI_POLL, mRssiPollToken, 0);
             }
             sendNetworkChangeBroadcast(DetailedState.CONNECTING);
 
             // If this network was explicitly selected by the user, evaluate whether to inform
             // ConnectivityService of that fact so the system can treat it appropriately.
-            final WifiConfiguration config = getCurrentWifiConfiguration();
+            final WifiConfiguration config = getConnectedWifiConfigurationInternal();
 
-            boolean explicitlySelected = false;
-            if (shouldEvaluateWhetherToSendExplicitlySelected(config)) {
+            final boolean explicitlySelected;
+            // Non primary CMMs is never user selected. This prevents triggering the No Internet
+            // dialog for those networks, which is difficult to handle.
+            if (isPrimary() && isRecentlySelectedByTheUser(config)) {
                 // If explicitlySelected is true, the network was selected by the user via Settings
                 // or QuickSettings. If this network has Internet access, switch to it. Otherwise,
                 // switch to it only if the user confirms that they really want to switch, or has
@@ -4755,6 +4834,8 @@
                     log("Network selected by UID " + config.lastConnectUid + " explicitlySelected="
                             + explicitlySelected);
                 }
+            } else {
+                explicitlySelected = false;
             }
 
             if (mVerboseLoggingEnabled) {
@@ -4762,39 +4843,62 @@
                         + config.noInternetAccessExpected);
             }
 
-            final NetworkAgentConfig naConfig = new NetworkAgentConfig.Builder()
+            NetworkAgentConfig.Builder naConfigBuilder = new NetworkAgentConfig.Builder()
                     .setLegacyType(ConnectivityManager.TYPE_WIFI)
                     .setLegacyTypeName(NETWORKTYPE)
                     .setExplicitlySelected(explicitlySelected)
                     .setUnvalidatedConnectivityAcceptable(
                             explicitlySelected && config.noInternetAccessExpected)
-                    .setPartialConnectivityAcceptable(config.noInternetAccessExpected)
-                    .build();
-            final NetworkCapabilities nc = getCapabilities(getCurrentWifiConfiguration());
+                    .setPartialConnectivityAcceptable(config.noInternetAccessExpected);
+            if (config.carrierMerged) {
+                String subscriberId = null;
+                TelephonyManager subMgr = mTelephonyManager.createForSubscriptionId(
+                        config.subscriptionId);
+                if (subMgr != null) {
+                    subscriberId = subMgr.getSubscriberId();
+                }
+                if (subscriberId != null) {
+                    naConfigBuilder.setSubscriberId(subscriberId);
+                }
+            }
+            if (mVcnManager == null && SdkLevel.isAtLeastS()) {
+                mVcnManager = mContext.getSystemService(VcnManager.class);
+            }
+            if (mVcnManager != null) {
+                mVcnPolicyChangeListener = new WifiVcnNetworkPolicyChangeListener();
+                mVcnManager.addVcnNetworkPolicyChangeListener(new HandlerExecutor(getHandler()),
+                        mVcnPolicyChangeListener);
+            }
+            final NetworkAgentConfig naConfig = naConfigBuilder.build();
+            final NetworkCapabilities nc = getCapabilities(
+                    getConnectedWifiConfigurationInternal(), getConnectedBssidInternal());
             // This should never happen.
             if (mNetworkAgent != null) {
-                Log.wtf(TAG, "mNetworkAgent is not null: " + mNetworkAgent);
+                Log.wtf(getTag(), "mNetworkAgent is not null: " + mNetworkAgent);
                 mNetworkAgent.unregister();
             }
-            mNetworkAgent = new WifiNetworkAgent(mContext, getHandler().getLooper(),
-                    "WifiNetworkAgent", nc, mLinkProperties, 60, naConfig,
-                    mNetworkFactory.getProvider());
+            mNetworkAgent = mWifiInjector.makeWifiNetworkAgent(nc, mLinkProperties, naConfig,
+                    mNetworkFactory.getProvider(), new WifiNetworkAgentCallback());
             mWifiScoreReport.setNetworkAgent(mNetworkAgent);
 
             // We must clear the config BSSID, as the wifi chipset may decide to roam
             // from this point on and having the BSSID specified in the network block would
             // cause the roam to faile and the device to disconnect
             clearTargetBssid("L2ConnectedState");
-            mCountryCode.setReadyForChange(false);
-            mWifiMetrics.setWifiState(WifiMetricsProto.WifiLog.WIFI_ASSOCIATED);
+            mWifiMetrics.setWifiState(mInterfaceName, WifiMetricsProto.WifiLog.WIFI_ASSOCIATED);
             mWifiScoreCard.noteNetworkAgentCreated(mWifiInfo,
                     mNetworkAgent.getNetwork().getNetId());
-            mBssidBlocklistMonitor.handleBssidConnectionSuccess(mLastBssid, mWifiInfo.getSSID());
+            mWifiBlocklistMonitor.handleBssidConnectionSuccess(mLastBssid, mWifiInfo.getSSID());
+            // too many places to record connection failure with too many failure reasons.
+            // So only record success here.
+            mWifiMetrics.noteFirstL2ConnectionAfterBoot(true);
+            mCmiMonitor.onL2Connected(mClientModeManager);
+            mIsLinkedNetworkRoaming = false;
         }
 
         @Override
         public void exit() {
-            // This is handled by receiving a NETWORK_DISCONNECTION_EVENT in ConnectModeState
+            // This is handled by receiving a NETWORK_DISCONNECTION_EVENT in ConnectableState
             // Bug: 15347363
             // For paranoia's sake, call handleNetworkDisconnect
             // only if BSSID is null or last networkId
@@ -4806,53 +4910,52 @@
                     sb.append(" ").append(mLastBssid);
                 }
             }
-            mCountryCode.setReadyForChange(true);
-            mWifiMetrics.setWifiState(WifiMetricsProto.WifiLog.WIFI_DISCONNECTED);
-            mWifiStateTracker.updateState(WifiStateTracker.DISCONNECTED);
-            //Inform WifiLockManager
-            WifiLockManager wifiLockManager = mWifiInjector.getWifiLockManager();
-            wifiLockManager.updateWifiClientConnected(false);
+            mWifiMetrics.setWifiState(mInterfaceName, WifiMetricsProto.WifiLog.WIFI_DISCONNECTED);
+            mWifiStateTracker.updateState(mInterfaceName, WifiStateTracker.DISCONNECTED);
+            // Inform WifiLockManager
+            mWifiLockManager.updateWifiClientConnected(mClientModeManager, false);
+            mLastConnectionCapabilities = null;
         }
 
         @Override
         public boolean processMessage(Message message) {
             boolean handleStatus = HANDLED;
-            int callbackIdentifier = -1;
 
             switch (message.what) {
-                case CMD_PRE_DHCP_ACTION:
+                case CMD_PRE_DHCP_ACTION: {
                     handlePreDhcpSetup();
                     break;
-                case CMD_PRE_DHCP_ACTION_COMPLETE:
+                }
+                case CMD_PRE_DHCP_ACTION_COMPLETE: {
                     if (mIpClient != null) {
                         mIpClient.completedPreDhcpAction();
                     }
                     break;
-                case CMD_POST_DHCP_ACTION:
+                }
+                case CMD_POST_DHCP_ACTION: {
                     handlePostDhcpSetup();
-                    // We advance to mConnectedState because IpClient will also send a
+                    // We advance to mL3ConnectedState because IpClient will also send a
                     // CMD_IPV4_PROVISIONING_SUCCESS message, which calls handleIPv4Success(),
                     // which calls updateLinkProperties, which then sends
                     // CMD_IP_CONFIGURATION_SUCCESSFUL.
-                    //
-                    // In the event of failure, we transition to mDisconnectingState
-                    // similarly--via messages sent back from IpClient.
                     break;
+                }
                 case CMD_IPV4_PROVISIONING_SUCCESS: {
                     handleIPv4Success((DhcpResultsParcelable) message.obj);
+                    sendNetworkChangeBroadcastWithCurrentState();
                     break;
                 }
                 case CMD_IPV4_PROVISIONING_FAILURE: {
                     handleIPv4Failure();
-                    mWifiInjector.getWifiLastResortWatchdog()
-                            .noteConnectionFailureAndTriggerIfNeeded(
-                                    getTargetSsid(),
-                                    (mLastBssid == null) ? mTargetBssid : mLastBssid,
-                                    WifiLastResortWatchdog.FAILURE_CODE_DHCP);
+                    mWifiLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
+                            getConnectingSsidInternal(),
+                            (mLastBssid == null) ? mTargetBssid : mLastBssid,
+                            WifiLastResortWatchdog.FAILURE_CODE_DHCP,
+                            isConnected());
                     break;
                 }
-                case CMD_IP_CONFIGURATION_SUCCESSFUL:
-                    if (getCurrentWifiConfiguration() == null) {
+                case CMD_IP_CONFIGURATION_SUCCESSFUL: {
+                    if (getConnectedWifiConfigurationInternal() == null || mNetworkAgent == null) {
                         // The current config may have been removed while we were connecting,
                         // trigger a disconnect to clear up state.
                         reportConnectionAttemptEnd(
@@ -4860,14 +4963,14 @@
                                 WifiMetricsProto.ConnectionEvent.HLF_NONE,
                                 WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
                         mWifiNative.disconnect(mInterfaceName);
-                        transitionTo(mDisconnectingState);
                     } else {
                         handleSuccessfulIpConfiguration();
                         sendConnectedState();
-                        transitionTo(mConnectedState);
+                        transitionTo(mL3ConnectedState);
                     }
                     break;
-                case CMD_IP_CONFIGURATION_LOST:
+                }
+                case CMD_IP_CONFIGURATION_LOST: {
                     // Get Link layer stats so that we get fresh tx packet counters.
                     getWifiLinkLayerStats();
                     handleIpConfigurationLost();
@@ -4875,121 +4978,118 @@
                             WifiMetrics.ConnectionEvent.FAILURE_DHCP,
                             WifiMetricsProto.ConnectionEvent.HLF_NONE,
                             WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
-                    mWifiInjector.getWifiLastResortWatchdog()
-                            .noteConnectionFailureAndTriggerIfNeeded(
-                                    getTargetSsid(),
-                                    (mLastBssid == null) ? mTargetBssid : mLastBssid,
-                                    WifiLastResortWatchdog.FAILURE_CODE_DHCP);
-                    transitionTo(mDisconnectingState);
+                    mWifiLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
+                            getConnectingSsidInternal(),
+                            (mLastBssid == null) ? mTargetBssid : mLastBssid,
+                            WifiLastResortWatchdog.FAILURE_CODE_DHCP,
+                            isConnected());
                     break;
-                case CMD_IP_REACHABILITY_LOST:
+                }
+                case CMD_IP_REACHABILITY_LOST: {
                     if (mVerboseLoggingEnabled && message.obj != null) log((String) message.obj);
-                    mWifiDiagnostics.captureBugReportData(
+                    mWifiDiagnostics.triggerBugReportDataCapture(
                             WifiDiagnostics.REPORT_REASON_REACHABILITY_LOST);
-                    mWifiMetrics.logWifiIsUnusableEvent(
+                    mWifiMetrics.logWifiIsUnusableEvent(mInterfaceName,
                             WifiIsUnusableEvent.TYPE_IP_REACHABILITY_LOST);
-                    mWifiMetrics.addToWifiUsabilityStatsList(WifiUsabilityStats.LABEL_BAD,
+                    mWifiMetrics.addToWifiUsabilityStatsList(mInterfaceName,
+                            WifiUsabilityStats.LABEL_BAD,
                             WifiUsabilityStats.TYPE_IP_REACHABILITY_LOST, -1);
-                    if (mIpReachabilityDisconnectEnabled) {
+                    if (mWifiGlobals.getIpReachabilityDisconnectEnabled()) {
                         handleIpReachabilityLost();
-                        transitionTo(mDisconnectingState);
                     } else {
                         logd("CMD_IP_REACHABILITY_LOST but disconnect disabled -- ignore");
                     }
                     break;
-                case CMD_DISCONNECT:
-                    mWifiMetrics.logStaEvent(StaEvent.TYPE_FRAMEWORK_DISCONNECT,
-                            StaEvent.DISCONNECT_GENERIC);
-                    mWifiNative.disconnect(mInterfaceName);
-                    transitionTo(mDisconnectingState);
-                    break;
-                case WifiP2pServiceImpl.DISCONNECT_WIFI_REQUEST:
-                    if (message.arg1 == 1) {
-                        mWifiMetrics.logStaEvent(StaEvent.TYPE_FRAMEWORK_DISCONNECT,
+                }
+                case WifiP2pServiceImpl.DISCONNECT_WIFI_REQUEST: {
+                    if (mWifiP2pConnection.shouldTemporarilyDisconnectWifi()) {
+                        mWifiMetrics.logStaEvent(mInterfaceName, StaEvent.TYPE_FRAMEWORK_DISCONNECT,
                                 StaEvent.DISCONNECT_P2P_DISCONNECT_WIFI_REQUEST);
                         mWifiNative.disconnect(mInterfaceName);
-                        mTemporarilyDisconnectWifi = true;
-                        transitionTo(mDisconnectingState);
                     }
                     break;
-                case WifiMonitor.NETWORK_CONNECTION_EVENT:
-                    mWifiInfo.setBSSID((String) message.obj);
-                    mLastNetworkId = message.arg1;
+                }
+                case WifiMonitor.NETWORK_CONNECTION_EVENT: {
+                    NetworkConnectionEventInfo connectionInfo =
+                            (NetworkConnectionEventInfo) message.obj;
+                    mWifiInfo.setBSSID(connectionInfo.bssid);
+                    mLastNetworkId = connectionInfo.networkId;
                     mWifiInfo.setNetworkId(mLastNetworkId);
                     mWifiInfo.setMacAddress(mWifiNative.getMacAddress(mInterfaceName));
-                    mLastBssid = (String) message.obj;
-                    break;
-                case CMD_ONESHOT_RSSI_POLL:
-                    if (!mEnableRssiPolling) {
-                        updateLinkLayerStatsRssiAndScoreReportInternal();
+                    if (!Objects.equals(mLastBssid, connectionInfo.bssid)) {
+                        mLastBssid = connectionInfo.bssid;
+                        sendNetworkChangeBroadcastWithCurrentState();
+                    }
+                    if (mIsLinkedNetworkRoaming) {
+                        mIsLinkedNetworkRoaming = false;
+                        mTargetNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
+                        mTargetWifiConfiguration = null;
+                        clearTargetBssid("AllowlistRoamingCompleted");
+                        sendNetworkChangeBroadcast(DetailedState.CONNECTED);
                     }
                     break;
-                case CMD_RSSI_POLL:
+                }
+                case CMD_ONESHOT_RSSI_POLL: {
+                    if (!mEnableRssiPolling) {
+                        updateLinkLayerStatsRssiDataStallScoreReport();
+                    }
+                    break;
+                }
+                case CMD_RSSI_POLL: {
+                    // TODO(b/179792830): getBSSID() shouldn't be null in L2ConnectedState,
+                    //  add debug logs in the meantime. Remove once root cause identified.
+                    if (mWifiInfo.getBSSID() == null) {
+                        Log.wtf(getTag(), "WifiInfo.getBSSID() is null in L2ConnectedState!");
+                        break;
+                    }
                     if (message.arg1 == mRssiPollToken) {
-                        WifiLinkLayerStats stats = updateLinkLayerStatsRssiAndScoreReportInternal();
-                        mWifiMetrics.updateWifiUsabilityStatsEntries(mWifiInfo, stats);
-                        if (mWifiScoreReport.shouldCheckIpLayer()) {
-                            if (mIpClient != null) {
-                                mIpClient.confirmConfiguration();
-                            }
-                            mWifiScoreReport.noteIpCheck();
-                        }
-                        int statusDataStall = mWifiDataStall.checkDataStallAndThroughputSufficiency(
-                                mLastLinkLayerStats, stats, mWifiInfo);
-                        if (mDataStallTriggerTimeMs == -1
-                                && statusDataStall != WifiIsUnusableEvent.TYPE_UNKNOWN) {
-                            mDataStallTriggerTimeMs = mClock.getElapsedSinceBootMillis();
-                            mLastStatusDataStall = statusDataStall;
-                        }
-                        if (mDataStallTriggerTimeMs != -1) {
-                            long elapsedTime =  mClock.getElapsedSinceBootMillis()
-                                    - mDataStallTriggerTimeMs;
-                            if (elapsedTime >= DURATION_TO_WAIT_ADD_STATS_AFTER_DATA_STALL_MS) {
-                                mDataStallTriggerTimeMs = -1;
-                                mWifiMetrics.addToWifiUsabilityStatsList(
-                                        WifiUsabilityStats.LABEL_BAD,
-                                        convertToUsabilityStatsTriggerType(mLastStatusDataStall),
-                                        -1);
-                                mLastStatusDataStall = WifiIsUnusableEvent.TYPE_UNKNOWN;
-                            }
-                        }
-                        mWifiMetrics.incrementWifiLinkLayerUsageStats(stats);
-                        mLastLinkLayerStats = stats;
+                        updateLinkLayerStatsRssiDataStallScoreReport();
                         mWifiScoreCard.noteSignalPoll(mWifiInfo);
-                        mLinkProbeManager.updateConnectionStats(
-                                mWifiInfo, mInterfaceName);
+                        if (isPrimary()) {
+                            mLinkProbeManager.updateConnectionStats(mWifiInfo, mInterfaceName);
+                        }
                         sendMessageDelayed(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0),
-                                getPollRssiIntervalMsecs());
+                                mWifiGlobals.getPollRssiIntervalMillis());
                         if (mVerboseLoggingEnabled) sendRssiChangeBroadcast(mWifiInfo.getRssi());
-                        mWifiTrafficPoller.notifyOnDataActivity(mWifiInfo.txSuccess,
-                                mWifiInfo.rxSuccess);
+                        if (isPrimary()) {
+                            mWifiTrafficPoller.notifyOnDataActivity(
+                                    mWifiInfo.txSuccess, mWifiInfo.rxSuccess);
+                        }
                     } else {
                         // Polling has completed
                     }
                     break;
-                case CMD_ENABLE_RSSI_POLL:
+                }
+                case CMD_ENABLE_RSSI_POLL: {
                     cleanWifiScore();
                     mEnableRssiPolling = (message.arg1 == 1);
                     mRssiPollToken++;
                     if (mEnableRssiPolling) {
                         // First poll
                         mLastSignalLevel = -1;
-                        mLinkProbeManager.resetOnScreenTurnedOn();
-                        fetchRssiLinkSpeedAndFrequencyNative();
+                        if (isPrimary()) {
+                            mLinkProbeManager.resetOnScreenTurnedOn();
+                        }
+                        updateLinkLayerStatsRssiSpeedFrequencyCapabilities();
                         sendMessageDelayed(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0),
-                                getPollRssiIntervalMsecs());
+                                mWifiGlobals.getPollRssiIntervalMillis());
                     }
                     break;
-                case WifiMonitor.ASSOCIATED_BSSID_EVENT:
+                }
+                case WifiMonitor.ASSOCIATED_BSSID_EVENT: {
                     if ((String) message.obj == null) {
                         logw("Associated command w/o BSSID");
                         break;
                     }
                     mLastBssid = (String) message.obj;
+                    if (checkAndHandleLinkedNetworkRoaming(mLastBssid)) {
+                        Log.i(TAG, "Driver initiated allowlist SSID roaming");
+                        break;
+                    }
                     if (mLastBssid != null && (mWifiInfo.getBSSID() == null
                             || !mLastBssid.equals(mWifiInfo.getBSSID()))) {
                         mWifiInfo.setBSSID(mLastBssid);
-                        WifiConfiguration config = getCurrentWifiConfiguration();
+                        WifiConfiguration config = getConnectedWifiConfigurationInternal();
                         if (config != null) {
                             ScanDetailCache scanDetailCache = mWifiConfigManager
                                     .getScanDetailCacheForNetwork(config.networkId);
@@ -5000,20 +5100,25 @@
                                 }
                             }
                         }
+                        sendNetworkChangeBroadcastWithCurrentState();
                     }
                     break;
+                }
                 case CMD_START_RSSI_MONITORING_OFFLOAD:
-                case CMD_RSSI_THRESHOLD_BREACHED:
+                case CMD_RSSI_THRESHOLD_BREACHED: {
                     byte currRssi = (byte) message.arg1;
                     processRssiThreshold(currRssi, message.what, mRssiEventHandler);
                     break;
-                case CMD_STOP_RSSI_MONITORING_OFFLOAD:
+                }
+                case CMD_STOP_RSSI_MONITORING_OFFLOAD: {
                     stopRssiMonitoringOffload();
                     break;
-                case CMD_RECONNECT:
+                }
+                case CMD_RECONNECT: {
                     log(" Ignore CMD_RECONNECT request because wifi is already connected");
                     break;
-                case CMD_RESET_SIM_NETWORKS:
+                }
+                case CMD_RESET_SIM_NETWORKS: {
                     if (message.arg1 != RESET_SIM_REASON_SIM_INSERTED
                             && mLastNetworkId != WifiConfiguration.INVALID_NETWORK_ID) {
                         WifiConfiguration config =
@@ -5022,22 +5127,23 @@
                             && ((message.arg1 == RESET_SIM_REASON_DEFAULT_DATA_SIM_CHANGED
                                 && config.carrierId != TelephonyManager.UNKNOWN_CARRIER_ID)
                                 || (config.enterpriseConfig != null
-                                        && config.enterpriseConfig.isAuthenticationSimBased()
-                                        && !mWifiCarrierInfoManager.isSimPresent(mLastSubId)))) {
-                            mWifiMetrics.logStaEvent(StaEvent.TYPE_FRAMEWORK_DISCONNECT,
+                                && config.enterpriseConfig.isAuthenticationSimBased()
+                                && !mWifiCarrierInfoManager.isSimReady(mLastSubId)))) {
+                            mWifiMetrics.logStaEvent(mInterfaceName,
+                                    StaEvent.TYPE_FRAMEWORK_DISCONNECT,
                                     StaEvent.DISCONNECT_RESET_SIM_NETWORKS);
                             // remove local PMKSA cache in framework
                             mWifiNative.removeNetworkCachedData(mLastNetworkId);
                             // remove network so that supplicant's PMKSA cache is cleared
                             mWifiNative.removeAllNetworks(mInterfaceName);
-                            mSimRequiredNotifier.showSimRequiredNotification(
-                                    config, mLastSimBasedConnectionCarrierName);
-                            transitionTo(mDisconnectingState);
+                            if (isPrimary()) {
+                                mSimRequiredNotifier.showSimRequiredNotification(
+                                        config, mLastSimBasedConnectionCarrierName);
+                            }
                         }
                     }
-                    /* allow parent state to reset data for other networks */
-                    handleStatus = NOT_HANDLED;
                     break;
+                }
                 case CMD_START_IP_PACKET_OFFLOAD: {
                     int slot = message.arg1;
                     int intervalSeconds = message.arg2;
@@ -5082,9 +5188,10 @@
                     }
                     break;
                 }
-                default:
+                default: {
                     handleStatus = NOT_HANDLED;
                     break;
+                }
             }
 
             if (handleStatus == HANDLED) {
@@ -5095,14 +5202,45 @@
         }
 
         /**
-         * Fetches link stats and updates Wifi Score Report.
+         * Fetches link stats, updates Wifi Data Stall, Score Card and Score Report.
          */
-        private WifiLinkLayerStats updateLinkLayerStatsRssiAndScoreReportInternal() {
-            WifiLinkLayerStats stats = getWifiLinkLayerStats();
+        private WifiLinkLayerStats updateLinkLayerStatsRssiDataStallScoreReport() {
             // Get Info and continue polling
-            fetchRssiLinkSpeedAndFrequencyNative();
+            WifiLinkLayerStats stats = updateLinkLayerStatsRssiSpeedFrequencyCapabilities();
+            mWifiMetrics.updateWifiUsabilityStatsEntries(mInterfaceName, mWifiInfo, stats);
+            // checkDataStallAndThroughputSufficiency() should be called before
+            // mWifiScoreReport.calculateAndReportScore() which needs the latest throughput
+            int statusDataStall = mWifiDataStall.checkDataStallAndThroughputSufficiency(
+                    mInterfaceName, mLastConnectionCapabilities, mLastLinkLayerStats, stats,
+                    mWifiInfo);
+            if (mDataStallTriggerTimeMs == -1
+                    && statusDataStall != WifiIsUnusableEvent.TYPE_UNKNOWN) {
+                mDataStallTriggerTimeMs = mClock.getElapsedSinceBootMillis();
+                mLastStatusDataStall = statusDataStall;
+            }
+            if (mDataStallTriggerTimeMs != -1) {
+                long elapsedTime =  mClock.getElapsedSinceBootMillis()
+                        - mDataStallTriggerTimeMs;
+                if (elapsedTime >= DURATION_TO_WAIT_ADD_STATS_AFTER_DATA_STALL_MS) {
+                    mDataStallTriggerTimeMs = -1;
+                    mWifiMetrics.addToWifiUsabilityStatsList(mInterfaceName,
+                            WifiUsabilityStats.LABEL_BAD,
+                            convertToUsabilityStatsTriggerType(mLastStatusDataStall),
+                            -1);
+                    mLastStatusDataStall = WifiIsUnusableEvent.TYPE_UNKNOWN;
+                }
+            }
             // Send the update score to network agent.
             mWifiScoreReport.calculateAndReportScore();
+
+            if (mWifiScoreReport.shouldCheckIpLayer()) {
+                if (mIpClient != null) {
+                    mIpClient.confirmConfiguration();
+                }
+                mWifiScoreReport.noteIpCheck();
+            }
+
+            mLastLinkLayerStats = stats;
             return stats;
         }
     }
@@ -5110,11 +5248,11 @@
     /**
      * Fetches link stats and updates Wifi Score Report.
      */
-    public void updateLinkLayerStatsRssiAndScoreReport() {
+    private void updateLinkLayerStatsRssiAndScoreReport() {
         sendMessage(CMD_ONESHOT_RSSI_POLL);
     }
 
-    private static int convertToUsabilityStatsTriggerType(int unusableEventTriggerType) {
+    private int convertToUsabilityStatsTriggerType(int unusableEventTriggerType) {
         int triggerType;
         switch (unusableEventTriggerType) {
             case WifiIsUnusableEvent.TYPE_DATA_STALL_BAD_TX:
@@ -5134,15 +5272,15 @@
                 break;
             default:
                 triggerType = WifiUsabilityStats.TYPE_UNKNOWN;
-                Log.e(TAG, "Unknown WifiIsUnusableEvent: " + unusableEventTriggerType);
+                Log.e(getTag(), "Unknown WifiIsUnusableEvent: " + unusableEventTriggerType);
         }
         return triggerType;
     }
 
-    class ObtainingIpState extends State {
+    class L3ProvisioningState extends State {
         @Override
         public void enter() {
-            WifiConfiguration currentConfig = getCurrentWifiConfiguration();
+            WifiConfiguration currentConfig = getConnectedWifiConfigurationInternal();
             if (mIpClientWithPreConnection && mIpClient != null) {
                 mIpClient.notifyPreconnectionComplete(mSentHLPs);
                 mIpClientWithPreConnection = false;
@@ -5159,26 +5297,25 @@
             boolean handleStatus = HANDLED;
 
             switch(message.what) {
-                case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
+                case WifiMonitor.NETWORK_DISCONNECTION_EVENT: {
+                    DisconnectEventInfo eventInfo = (DisconnectEventInfo) message.obj;
                     reportConnectionAttemptEnd(
                             WifiMetrics.ConnectionEvent.FAILURE_NETWORK_DISCONNECTION,
                             WifiMetricsProto.ConnectionEvent.HLF_NONE,
                             WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
-                    mWifiInjector.getWifiLastResortWatchdog()
-                            .noteConnectionFailureAndTriggerIfNeeded(
-                                    getTargetSsid(),
-                                    (message.obj == null)
-                                    ? mTargetBssid : (String) message.obj,
-                                    WifiLastResortWatchdog.FAILURE_CODE_DHCP);
+                    mWifiLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
+                            getConnectingSsidInternal(),
+                            !isValidBssid(eventInfo.bssid)
+                            ? mTargetBssid : eventInfo.bssid,
+                            WifiLastResortWatchdog.FAILURE_CODE_DHCP,
+                            isConnected());
                     handleStatus = NOT_HANDLED;
                     break;
-                case CMD_SET_HIGH_PERF_MODE:
-                    mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_DEFERRED;
-                    deferMessage(message);
-                    break;
-                default:
+                }
+                default: {
                     handleStatus = NOT_HANDLED;
                     break;
+                }
             }
 
             if (handleStatus == HANDLED) {
@@ -5189,21 +5326,15 @@
     }
 
     /**
-     * Helper function to check if we need to invoke
-     * {@link NetworkAgent#explicitlySelected(boolean, boolean)} to indicate that we connected to a
-     * network which the user just chose
+     * Helper function to check if a network has been recently selected by the user.
      * (i.e less than {@link #LAST_SELECTED_NETWORK_EXPIRATION_AGE_MILLIS) before).
      */
     @VisibleForTesting
-    public boolean shouldEvaluateWhetherToSendExplicitlySelected(WifiConfiguration currentConfig) {
-        if (currentConfig == null) {
-            Log.wtf(TAG, "Current WifiConfiguration is null, but IP provisioning just succeeded");
-            return false;
-        }
+    public boolean isRecentlySelectedByTheUser(@NonNull WifiConfiguration currentConfig) {
         long currentTimeMillis = mClock.getElapsedSinceBootMillis();
-        return (mWifiConfigManager.getLastSelectedNetwork() == currentConfig.networkId
+        return mWifiConfigManager.getLastSelectedNetwork() == currentConfig.networkId
                 && currentTimeMillis - mWifiConfigManager.getLastSelectedTimeStamp()
-                < LAST_SELECTED_NETWORK_EXPIRATION_AGE_MILLIS);
+                < LAST_SELECTED_NETWORK_EXPIRATION_AGE_MILLIS;
     }
 
     private void sendConnectedState() {
@@ -5228,24 +5359,25 @@
         }
         @Override
         public boolean processMessage(Message message) {
-            WifiConfiguration config;
             boolean handleStatus = HANDLED;
 
             switch (message.what) {
-                case CMD_IP_CONFIGURATION_LOST:
-                    config = getCurrentWifiConfiguration();
+                case CMD_IP_CONFIGURATION_LOST: {
+                    WifiConfiguration config = getConnectedWifiConfigurationInternal();
                     if (config != null) {
-                        mWifiDiagnostics.captureBugReportData(
+                        mWifiDiagnostics.triggerBugReportDataCapture(
                                 WifiDiagnostics.REPORT_REASON_AUTOROAM_FAILURE);
                     }
                     handleStatus = NOT_HANDLED;
                     break;
-                case CMD_UNWANTED_NETWORK:
+                }
+                case CMD_UNWANTED_NETWORK: {
                     if (mVerboseLoggingEnabled) {
                         log("Roaming and CS doesn't want the network -> ignore");
                     }
                     break;
-                case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
+                }
+                case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT: {
                     /**
                      * If we get a SUPPLICANT_STATE_CHANGE_EVENT indicating a DISCONNECT
                      * before NETWORK_DISCONNECTION_EVENT
@@ -5254,54 +5386,58 @@
                      * and handle the rest of the events there.
                      */
                     StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
-                    if (stateChangeResult.state == SupplicantState.DISCONNECTED
-                            || stateChangeResult.state == SupplicantState.INACTIVE
-                            || stateChangeResult.state == SupplicantState.INTERFACE_DISABLED) {
+                    SupplicantState state = handleSupplicantStateChange(stateChangeResult);
+                    if (state == SupplicantState.DISCONNECTED
+                            || state == SupplicantState.INACTIVE
+                            || state == SupplicantState.INTERFACE_DISABLED) {
                         if (mVerboseLoggingEnabled) {
-                            log("STATE_CHANGE_EVENT in roaming state "
-                                    + stateChangeResult.toString());
+                            log("RoamingState: Supplicant State change " + stateChangeResult);
                         }
-                        if (stateChangeResult.BSSID != null
-                                && stateChangeResult.BSSID.equals(mTargetBssid)) {
-                            handleNetworkDisconnect();
-                            transitionTo(mDisconnectedState);
-                        }
+                        handleNetworkDisconnect(false,
+                                WIFI_DISCONNECT_REPORTED__FAILURE_CODE__SUPPLICANT_DISCONNECTED);
+                        transitionTo(mDisconnectedState);
                     }
                     if (stateChangeResult.state == SupplicantState.ASSOCIATED) {
                         // We completed the layer2 roaming part
                         mAssociated = true;
-                        if (stateChangeResult.BSSID != null) {
-                            mTargetBssid = stateChangeResult.BSSID;
-                        }
+                        mTargetBssid = stateChangeResult.bssid;
                     }
                     break;
-                case CMD_ROAM_WATCHDOG_TIMER:
+                }
+                case CMD_ROAM_WATCHDOG_TIMER: {
                     if (mRoamWatchdogCount == message.arg1) {
                         if (mVerboseLoggingEnabled) log("roaming watchdog! -> disconnect");
                         mWifiMetrics.endConnectionEvent(
+                                mInterfaceName,
                                 WifiMetrics.ConnectionEvent.FAILURE_ROAM_TIMEOUT,
                                 WifiMetricsProto.ConnectionEvent.HLF_NONE,
-                                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
+                                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN,
+                                mWifiInfo.getFrequency());
                         mRoamFailCount++;
-                        handleNetworkDisconnect();
-                        mWifiMetrics.logStaEvent(StaEvent.TYPE_FRAMEWORK_DISCONNECT,
+                        handleNetworkDisconnect(false,
+                                WifiStatsLog.WIFI_DISCONNECT_REPORTED__FAILURE_CODE__ROAM_WATCHDOG_TIMER);
+                        mWifiMetrics.logStaEvent(mInterfaceName, StaEvent.TYPE_FRAMEWORK_DISCONNECT,
                                 StaEvent.DISCONNECT_ROAM_WATCHDOG_TIMER);
                         mWifiNative.disconnect(mInterfaceName);
                         transitionTo(mDisconnectedState);
                     }
                     break;
-                case WifiMonitor.NETWORK_CONNECTION_EVENT:
+                }
+                case WifiMonitor.NETWORK_CONNECTION_EVENT: {
                     if (mAssociated) {
                         if (mVerboseLoggingEnabled) {
                             log("roaming and Network connection established");
                         }
-                        mLastNetworkId = message.arg1;
-                        mLastBssid = (String) message.obj;
+                        NetworkConnectionEventInfo connectionInfo =
+                                (NetworkConnectionEventInfo) message.obj;
+                        mLastNetworkId = connectionInfo.networkId;
+                        mLastBssid = connectionInfo.bssid;
                         mWifiInfo.setBSSID(mLastBssid);
                         mWifiInfo.setNetworkId(mLastNetworkId);
+                        sendNetworkChangeBroadcastWithCurrentState();
 
                         // Successful framework roam! (probably)
-                        mBssidBlocklistMonitor.handleBssidConnectionSuccess(mLastBssid,
+                        mWifiBlocklistMonitor.handleBssidConnectionSuccess(mLastBssid,
                                 mWifiInfo.getSSID());
                         reportConnectionAttemptEnd(
                                 WifiMetrics.ConnectionEvent.FAILURE_NONE,
@@ -5311,12 +5447,11 @@
                         // We must clear the config BSSID, as the wifi chipset may decide to roam
                         // from this point on and having the BSSID specified by QNS would cause
                         // the roam to fail and the device to disconnect.
-                        // When transition from RoamingState to DisconnectingState or
-                        // DisconnectedState, the config BSSID is cleared by
-                        // handleNetworkDisconnect().
+                        // When transition from RoamingState to DisconnectedState, the config BSSID
+                        // is cleared by handleNetworkDisconnect().
                         clearTargetBssid("RoamingCompleted");
 
-                        // We used to transition to ObtainingIpState in an
+                        // We used to transition to L3ProvisioningState in an
                         // attempt to do DHCPv4 RENEWs on framework roams.
                         // DHCP can take too long to time out, and we now rely
                         // upon IpClient's use of IpReachabilityMonitor to
@@ -5324,33 +5459,34 @@
                         //
                         // mIpClient.confirmConfiguration() is called within
                         // the handling of SupplicantState.COMPLETED.
-                        transitionTo(mConnectedState);
+                        transitionTo(mL3ConnectedState);
                     } else {
                         mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_DISCARD;
                     }
                     break;
-                case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
+                }
+                case WifiMonitor.NETWORK_DISCONNECTION_EVENT: {
                     // Throw away but only if it corresponds to the network we're roaming to
-                    String bssid = (String) message.obj;
+                    DisconnectEventInfo eventInfo = (DisconnectEventInfo) message.obj;
                     if (true) {
                         String target = "";
                         if (mTargetBssid != null) target = mTargetBssid;
                         log("NETWORK_DISCONNECTION_EVENT in roaming state"
-                                + " BSSID=" + bssid
+                                + " BSSID=" + eventInfo.bssid
                                 + " target=" + target);
                     }
-                    clearNetworkCachedDataIfNeeded(getTargetWifiConfiguration(), message.arg2);
-                    if (bssid != null && bssid.equals(mTargetBssid)) {
-                        handleNetworkDisconnect();
+                    clearNetworkCachedDataIfNeeded(
+                            getConnectingWifiConfigurationInternal(), eventInfo.reasonCode);
+                    if (eventInfo.bssid.equals(mTargetBssid)) {
+                        handleNetworkDisconnect(false, eventInfo.reasonCode);
                         transitionTo(mDisconnectedState);
                     }
                     break;
-                case CMD_GET_CURRENT_NETWORK:
-                    replyToMessage(message, message.what, getCurrentNetwork());
-                    break;
-                default:
+                }
+                default: {
                     handleStatus = NOT_HANDLED;
                     break;
+                }
             }
 
             if (handleStatus == HANDLED) {
@@ -5365,7 +5501,7 @@
         }
     }
 
-    class ConnectedState extends State {
+    class L3ConnectedState extends State {
         @Override
         public void enter() {
             if (mVerboseLoggingEnabled) {
@@ -5377,9 +5513,9 @@
                     WifiMetricsProto.ConnectionEvent.HLF_NONE,
                     WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
             mWifiConnectivityManager.handleConnectionStateChanged(
+                    mClientModeManager,
                     WifiConnectivityManager.WIFI_STATE_CONNECTED);
             registerConnected();
-            mLastConnectAttemptTimestamp = 0;
             mTargetWifiConfiguration = null;
             mWifiScoreReport.reset();
             mLastSignalLevel = -1;
@@ -5387,34 +5523,42 @@
             // Not roaming anymore
             mIsAutoRoaming = false;
 
-            mLastDriverRoamAttempt = 0;
             mTargetNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
-            mWifiInjector.getWifiLastResortWatchdog().connectedStateTransition(true);
-            mWifiStateTracker.updateState(WifiStateTracker.CONNECTED);
-            //Inform WifiLockManager
-            WifiLockManager wifiLockManager = mWifiInjector.getWifiLockManager();
-            wifiLockManager.updateWifiClientConnected(true);
-            mWifiScoreReport.startConnectedNetworkScorer(mNetworkAgent.getNetwork().getNetId());
+            mWifiLastResortWatchdog.connectedStateTransition(true);
+            mWifiStateTracker.updateState(mInterfaceName, WifiStateTracker.CONNECTED);
+            // Inform WifiLockManager
+            mWifiLockManager.updateWifiClientConnected(mClientModeManager, true);
+            WifiConfiguration config = getConnectedWifiConfigurationInternal();
+            mWifiScoreReport.startConnectedNetworkScorer(
+                    mNetworkAgent.getNetwork().getNetId(), isRecentlySelectedByTheUser(config));
             updateLinkLayerStatsRssiAndScoreReport();
+            mWifiScoreCard.noteIpConfiguration(mWifiInfo);
+            // too many places to record L3 failure with too many failure reasons.
+            // So only record success here.
+            mWifiMetrics.noteFirstL3ConnectionAfterBoot(true);
         }
         @Override
         public boolean processMessage(Message message) {
-            WifiConfiguration config = null;
             boolean handleStatus = HANDLED;
 
             switch (message.what) {
-                case CMD_UNWANTED_NETWORK:
+                case CMD_UNWANTED_NETWORK: {
                     if (message.arg1 == NETWORK_STATUS_UNWANTED_DISCONNECT) {
-                        mWifiMetrics.logStaEvent(StaEvent.TYPE_FRAMEWORK_DISCONNECT,
+                        mWifiMetrics.logStaEvent(mInterfaceName, StaEvent.TYPE_FRAMEWORK_DISCONNECT,
                                 StaEvent.DISCONNECT_UNWANTED);
+                        if (mClientModeManager.getRole() == ROLE_CLIENT_SECONDARY_TRANSIENT
+                                && mClientModeManager.getPreviousRole() == ROLE_CLIENT_PRIMARY) {
+                            mWifiMetrics.incrementMakeBeforeBreakLingerCompletedCount(
+                                    mClock.getElapsedSinceBootMillis()
+                                            - mClientModeManager.getLastRoleChangeSinceBootMs());
+                        }
                         mWifiNative.disconnect(mInterfaceName);
-                        transitionTo(mDisconnectingState);
                     } else if (message.arg1 == NETWORK_STATUS_UNWANTED_DISABLE_AUTOJOIN
                             || message.arg1 == NETWORK_STATUS_UNWANTED_VALIDATION_FAILED) {
-                        Log.d(TAG, (message.arg1 == NETWORK_STATUS_UNWANTED_DISABLE_AUTOJOIN
+                        Log.d(getTag(), (message.arg1 == NETWORK_STATUS_UNWANTED_DISABLE_AUTOJOIN
                                 ? "NETWORK_STATUS_UNWANTED_DISABLE_AUTOJOIN"
                                 : "NETWORK_STATUS_UNWANTED_VALIDATION_FAILED"));
-                        config = getCurrentWifiConfiguration();
+                        WifiConfiguration config = getConnectedWifiConfigurationInternal();
                         if (config != null) {
                             // Disable autojoin
                             if (message.arg1 == NETWORK_STATUS_UNWANTED_DISABLE_AUTOJOIN) {
@@ -5426,38 +5570,50 @@
                                 // stop collect last-mile stats since validation fail
                                 removeMessages(CMD_DIAGS_CONNECT_TIMEOUT);
                                 mWifiDiagnostics.reportConnectionEvent(
-                                        WifiDiagnostics.CONNECTION_EVENT_FAILED);
+                                        WifiDiagnostics.CONNECTION_EVENT_FAILED,
+                                        mClientModeManager);
                                 mWifiConfigManager.incrementNetworkNoInternetAccessReports(
                                         config.networkId);
-                                // If this was not the last selected network, update network
+                                // If this was not recently selected by the user, update network
                                 // selection status to temporarily disable the network.
-                                if (mWifiConfigManager.getLastSelectedNetwork() != config.networkId
+                                if (!isRecentlySelectedByTheUser(config)
                                         && !config.noInternetAccessExpected) {
-                                    Log.i(TAG, "Temporarily disabling network because of"
-                                            + "no-internet access");
+                                    Log.i(getTag(), "Temporarily disabling network because of"
+                                            + " no-internet access");
                                     mWifiConfigManager.updateNetworkSelectionStatus(
                                             config.networkId,
                                             DISABLED_NO_INTERNET_TEMPORARY);
-                                    mBssidBlocklistMonitor.handleBssidConnectionFailure(
+                                    mWifiBlocklistMonitor.handleBssidConnectionFailure(
                                             mLastBssid, config.SSID,
-                                            BssidBlocklistMonitor.REASON_NETWORK_VALIDATION_FAILURE,
+                                            WifiBlocklistMonitor.REASON_NETWORK_VALIDATION_FAILURE,
                                             mWifiInfo.getRssi());
                                 }
                                 mWifiScoreCard.noteValidationFailure(mWifiInfo);
                             }
                         }
+                        if (mClientModeManager.getRole() == ROLE_CLIENT_SECONDARY_TRANSIENT) {
+                            Log.d(getTag(), "Internet validation failed during MBB,"
+                                    + " disconnecting ClientModeManager=" + mClientModeManager);
+                            mWifiMetrics.logStaEvent(
+                                    mInterfaceName,
+                                    StaEvent.TYPE_FRAMEWORK_DISCONNECT,
+                                    StaEvent.DISCONNECT_MBB_NO_INTERNET);
+                            mWifiMetrics.incrementMakeBeforeBreakNoInternetCount();
+                            mWifiNative.disconnect(mInterfaceName);
+                        }
                     }
                     break;
-                case CMD_NETWORK_STATUS:
+                }
+                case CMD_NETWORK_STATUS: {
                     if (message.arg1 == NetworkAgent.VALIDATION_STATUS_VALID) {
                         // stop collect last-mile stats since validation pass
                         removeMessages(CMD_DIAGS_CONNECT_TIMEOUT);
                         mWifiDiagnostics.reportConnectionEvent(
-                                WifiDiagnostics.CONNECTION_EVENT_SUCCEEDED);
+                                WifiDiagnostics.CONNECTION_EVENT_SUCCEEDED, mClientModeManager);
                         mWifiScoreCard.noteValidationSuccess(mWifiInfo);
-                        mBssidBlocklistMonitor.handleNetworkValidationSuccess(mLastBssid,
+                        mWifiBlocklistMonitor.handleNetworkValidationSuccess(mLastBssid,
                                 mWifiInfo.getSSID());
-                        config = getCurrentWifiConfiguration();
+                        WifiConfiguration config = getConnectedWifiConfigurationInternal();
                         if (config != null) {
                             // re-enable autojoin
                             mWifiConfigManager.updateNetworkSelectionStatus(
@@ -5466,90 +5622,96 @@
                                             .DISABLED_NONE);
                             mWifiConfigManager.setNetworkValidatedInternetAccess(
                                     config.networkId, true);
+                            if (config.isPasspoint()
+                                    && mTermsAndConditionsUrl != null) {
+                                // Clear the T&C after the user accepted them and the we are
+                                // notified that the network validation is successful
+                                mTermsAndConditionsUrl = null;
+                                LinkProperties newLp = new LinkProperties(mLinkProperties);
+                                addPasspointInfoToLinkProperties(newLp);
+                                sendMessage(CMD_UPDATE_LINKPROPERTIES, newLp);
+                                mWifiMetrics
+                                        .incrementTotalNumberOfPasspointAcceptanceOfTermsAndConditions();
+                            }
+                            if (retrieveConnectedNetworkDefaultGateway()) {
+                                updateLinkedNetworks(config);
+                            }
                         }
+                        mCmiMonitor.onInternetValidated(mClientModeManager);
                     }
                     break;
-                case CMD_ACCEPT_UNVALIDATED:
+                }
+                case CMD_ACCEPT_UNVALIDATED: {
                     boolean accept = (message.arg1 != 0);
                     mWifiConfigManager.setNetworkNoInternetAccessExpected(mLastNetworkId, accept);
                     break;
-                case WifiMonitor.ASSOCIATED_BSSID_EVENT:
-                    // ASSOCIATING to a new BSSID while already connected, indicates
-                    // that driver is roaming
-                    mLastDriverRoamAttempt = mClock.getWallClockMillis();
-                    handleStatus = NOT_HANDLED;
-                    break;
-                case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
-                    long lastRoam = 0;
+                }
+                case WifiMonitor.NETWORK_DISCONNECTION_EVENT: {
+                    DisconnectEventInfo eventInfo = (DisconnectEventInfo) message.obj;
                     reportConnectionAttemptEnd(
                             WifiMetrics.ConnectionEvent.FAILURE_NETWORK_DISCONNECTION,
                             WifiMetricsProto.ConnectionEvent.HLF_NONE,
                             WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
-                    if (mLastDriverRoamAttempt != 0) {
-                        // Calculate time since last driver roam attempt
-                        lastRoam = mClock.getWallClockMillis() - mLastDriverRoamAttempt;
-                        mLastDriverRoamAttempt = 0;
-                    }
-                    if (unexpectedDisconnectedReason(message.arg2)) {
-                        mWifiDiagnostics.captureBugReportData(
+                    if (unexpectedDisconnectedReason(eventInfo.reasonCode)) {
+                        mWifiDiagnostics.triggerBugReportDataCapture(
                                 WifiDiagnostics.REPORT_REASON_UNEXPECTED_DISCONNECT);
                     }
 
-                    boolean localGen = message.arg1 == 1;
-                    if (!localGen) { // ignore disconnects initiated by wpa_supplicant.
-                        mWifiScoreCard.noteNonlocalDisconnect(message.arg2);
-                        mBssidBlocklistMonitor.handleBssidConnectionFailure(mWifiInfo.getBSSID(),
+                    if (!eventInfo.locallyGenerated) {
+                        // ignore disconnects initiated by wpa_supplicant.
+                        mWifiScoreCard.noteNonlocalDisconnect(mInterfaceName, eventInfo.reasonCode);
+                        int rssi = mWifiInfo.getRssi();
+                        mWifiBlocklistMonitor.handleBssidConnectionFailure(mWifiInfo.getBSSID(),
                                 mWifiInfo.getSSID(),
-                                BssidBlocklistMonitor.REASON_ABNORMAL_DISCONNECT,
-                                mWifiInfo.getRssi());
+                                WifiBlocklistMonitor.REASON_ABNORMAL_DISCONNECT, rssi);
                     }
-                    config = getCurrentWifiConfiguration();
+                    WifiConfiguration config = getConnectedWifiConfigurationInternal();
 
                     if (mVerboseLoggingEnabled) {
                         log("NETWORK_DISCONNECTION_EVENT in connected state"
                                 + " BSSID=" + mWifiInfo.getBSSID()
                                 + " RSSI=" + mWifiInfo.getRssi()
                                 + " freq=" + mWifiInfo.getFrequency()
-                                + " reason=" + message.arg2
+                                + " reason=" + eventInfo.reasonCode
                                 + " Network Selection Status=" + (config == null ? "Unavailable"
-                                    : config.getNetworkSelectionStatus().getNetworkStatusString()));
+                                : config.getNetworkSelectionStatus().getNetworkStatusString()));
                     }
+                    handleNetworkDisconnect(false, eventInfo.reasonCode);
+                    transitionTo(mDisconnectedState);
                     break;
-                case CMD_START_ROAM:
-                    // Clear the driver roam indication since we are attempting a framework roam
-                    mLastDriverRoamAttempt = 0;
-
+                }
+                case CMD_START_ROAM: {
                     /* Connect command coming from auto-join */
                     int netId = message.arg1;
-                    ScanResult candidate = (ScanResult) message.obj;
-                    String bssid = SUPPLICANT_BSSID_ANY;
-                    if (candidate != null) {
-                        bssid = candidate.BSSID;
+                    String bssid = (String) message.obj;
+                    if (bssid == null) {
+                        bssid = SUPPLICANT_BSSID_ANY;
                     }
-                    config = mWifiConfigManager.getConfiguredNetworkWithoutMasking(netId);
+                    WifiConfiguration config =
+                            mWifiConfigManager.getConfiguredNetworkWithoutMasking(netId);
                     if (config == null) {
                         loge("CMD_START_ROAM and no config, bail out...");
                         break;
                     }
-                    int scanRssi = mWifiConfigManager.findScanRssi(netId,
+                    mLastScanRssi = mWifiConfigManager.findScanRssi(netId,
                             mWifiHealthMonitor.getScanRssiValidTimeMs());
-                    mWifiScoreCard.noteConnectionAttempt(mWifiInfo, scanRssi, config.SSID);
+                    mWifiScoreCard.noteConnectionAttempt(mWifiInfo, mLastScanRssi, config.SSID);
                     setTargetBssid(config, bssid);
                     mTargetNetworkId = netId;
 
                     logd("CMD_START_ROAM sup state "
                             + " my state " + getCurrentState().getName()
                             + " nid=" + Integer.toString(netId)
-                            + " config " + config.getKey()
+                            + " config " + config.getProfileKey()
                             + " targetRoamBSSID " + mTargetBssid);
 
                     reportConnectionAttemptStart(config, mTargetBssid,
                             WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
                     if (mWifiNative.roamToNetwork(mInterfaceName, config)) {
-                        mLastConnectAttemptTimestamp = mClock.getWallClockMillis();
                         mTargetWifiConfiguration = config;
                         mIsAutoRoaming = true;
-                        mWifiMetrics.logStaEvent(StaEvent.TYPE_CMD_START_ROAM, config);
+                        mWifiMetrics.logStaEvent(
+                                mInterfaceName, StaEvent.TYPE_CMD_START_ROAM, config);
                         transitionTo(mRoamingState);
                     } else {
                         loge("CMD_START_ROAM Failed to start roaming to network " + config);
@@ -5561,16 +5723,16 @@
                         break;
                     }
                     break;
-                case CMD_IP_CONFIGURATION_LOST:
+                }
+                case CMD_IP_CONFIGURATION_LOST: {
                     mWifiMetrics.incrementIpRenewalFailure();
                     handleStatus = NOT_HANDLED;
                     break;
-                case CMD_GET_CURRENT_NETWORK:
-                    replyToMessage(message, message.what, getCurrentNetwork());
-                    break;
-                default:
+                }
+                default: {
                     handleStatus = NOT_HANDLED;
                     break;
+                }
             }
 
             if (handleStatus == HANDLED) {
@@ -5584,92 +5746,24 @@
         public void exit() {
             logd("ClientModeImpl: Leaving Connected state");
             mWifiConnectivityManager.handleConnectionStateChanged(
+                    mClientModeManager,
                      WifiConnectivityManager.WIFI_STATE_TRANSITIONING);
 
-            mLastDriverRoamAttempt = 0;
-            mWifiInjector.getWifiLastResortWatchdog().connectedStateTransition(false);
-        }
-    }
-
-    class DisconnectingState extends State {
-
-        @Override
-        public void enter() {
-
-            if (mVerboseLoggingEnabled) {
-                logd(" Enter DisconnectingState State screenOn=" + mScreenOn);
-            }
-
-            // Make sure we disconnect: we enter this state prior to connecting to a new
-            // network, waiting for either a DISCONNECT event or a SUPPLICANT_STATE_CHANGE
-            // event which in this case will be indicating that supplicant started to associate.
-            // In some cases supplicant doesn't ignore the connect requests (it might not
-            // find the target SSID in its cache),
-            // Therefore we end up stuck that state, hence the need for the watchdog.
-            mDisconnectingWatchdogCount++;
-            logd("Start Disconnecting Watchdog " + mDisconnectingWatchdogCount);
-            sendMessageDelayed(obtainMessage(CMD_DISCONNECTING_WATCHDOG_TIMER,
-                    mDisconnectingWatchdogCount, 0), DISCONNECTING_GUARD_TIMER_MSEC);
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            boolean handleStatus = HANDLED;
-
-            switch (message.what) {
-                case CMD_CONNECT_NETWORK:
-                case CMD_SAVE_NETWORK:
-                    mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_DEFERRED;
-                    deferMessage(message);
-                    break;
-                case CMD_DISCONNECT:
-                    if (mVerboseLoggingEnabled) {
-                        log("Ignore CMD_DISCONNECT when already disconnecting.");
-                    }
-                    break;
-                case WifiMonitor.NETWORK_CONNECTION_EVENT:
-                    if (mVerboseLoggingEnabled) {
-                        log("Ignore NETWORK_CONNECTION_EVENT when already disconnecting.");
-                    }
-                    break;
-                case CMD_DISCONNECTING_WATCHDOG_TIMER:
-                    if (mDisconnectingWatchdogCount == message.arg1) {
-                        if (mVerboseLoggingEnabled) log("disconnecting watchdog! -> disconnect");
-                        handleNetworkDisconnect();
-                        transitionTo(mDisconnectedState);
-                    }
-                    break;
-                case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
-                    /**
-                     * If we get a SUPPLICANT_STATE_CHANGE_EVENT before NETWORK_DISCONNECTION_EVENT
-                     * we have missed the network disconnection, transition to mDisconnectedState
-                     * and handle the rest of the events there
-                     */
-                    mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_DEFERRED;
-                    deferMessage(message);
-                    handleNetworkDisconnect();
-                    transitionTo(mDisconnectedState);
-                    break;
-                default:
-                    handleStatus = NOT_HANDLED;
-                    break;
-            }
-
-            if (handleStatus == HANDLED) {
-                logStateAndMessage(message, this);
-            }
-            return handleStatus;
+            mWifiLastResortWatchdog.connectedStateTransition(false);
         }
     }
 
     class DisconnectedState extends State {
         @Override
         public void enter() {
-            Log.i(TAG, "disconnectedstate enter");
+            Log.i(getTag(), "disconnectedstate enter");
             // We don't scan frequently if this is a temporary disconnect
             // due to p2p
-            if (mTemporarilyDisconnectWifi) {
-                p2pSendMessage(WifiP2pServiceImpl.DISCONNECT_WIFI_RESPONSE);
+            if (mWifiP2pConnection.shouldTemporarilyDisconnectWifi()) {
+                // TODO(b/161569371): P2P should wait for all ClientModeImpls to enter
+                //  DisconnectedState, not just one instance.
+                // (Does P2P Service support STA+P2P concurrency?)
+                mWifiP2pConnection.sendMessage(WifiP2pServiceImpl.DISCONNECT_WIFI_RESPONSE);
                 return;
             }
 
@@ -5679,8 +5773,10 @@
 
             /** clear the roaming state, if we were roaming, we failed */
             mIsAutoRoaming = false;
+            mTargetNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
 
             mWifiConnectivityManager.handleConnectionStateChanged(
+                    mClientModeManager,
                     WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
         }
 
@@ -5689,78 +5785,22 @@
             boolean handleStatus = HANDLED;
 
             switch (message.what) {
-                case CMD_DISCONNECT:
-                    mWifiMetrics.logStaEvent(StaEvent.TYPE_FRAMEWORK_DISCONNECT,
-                            StaEvent.DISCONNECT_GENERIC);
-                    mWifiNative.disconnect(mInterfaceName);
-                    break;
-                case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
-                    stopIpClient();
-                    if (message.arg2 == 15 /* FOURWAY_HANDSHAKE_TIMEOUT */) {
-                        String bssid = (message.obj == null)
-                                ? mTargetBssid : (String) message.obj;
-                        mWifiInjector.getWifiLastResortWatchdog()
-                                .noteConnectionFailureAndTriggerIfNeeded(
-                                        getTargetSsid(), bssid,
-                                        WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
-                    }
-                    clearNetworkCachedDataIfNeeded(getTargetWifiConfiguration(), message.arg2);
-                    mTargetNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
-                    mWifiInfo.reset();
-                    break;
-                case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
-                    StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
-                    if (mVerboseLoggingEnabled) {
-                        logd("SUPPLICANT_STATE_CHANGE_EVENT state=" + stateChangeResult.state
-                                + " -> state= "
-                                + WifiInfo.getDetailedStateOf(stateChangeResult.state));
-                    }
-                    if (SupplicantState.isConnecting(stateChangeResult.state)) {
-                        WifiConfiguration config = mWifiConfigManager.getConfiguredNetwork(
-                                stateChangeResult.networkId);
-
-                        // Update Passpoint information before setNetworkDetailedState as
-                        // WifiTracker monitors NETWORK_STATE_CHANGED_ACTION to update UI.
-                        mWifiInfo.setFQDN(null);
-                        mWifiInfo.setPasspointUniqueId(null);
-                        mWifiInfo.setOsuAp(false);
-                        mWifiInfo.setProviderFriendlyName(null);
-                        if (config != null && (config.isPasspoint() || config.osu)) {
-                            if (config.isPasspoint()) {
-                                mWifiInfo.setFQDN(config.FQDN);
-                                mWifiInfo.setPasspointUniqueId(config.getPasspointUniqueId());
-                            } else {
-                                mWifiInfo.setOsuAp(true);
-                            }
-                            mWifiInfo.setProviderFriendlyName(config.providerFriendlyName);
-                        }
-                    }
-                    sendNetworkChangeBroadcast(
-                            WifiInfo.getDetailedStateOf(stateChangeResult.state));
-                    /* ConnectModeState does the rest of the handling */
-                    handleStatus = NOT_HANDLED;
-                    break;
-                case WifiP2pServiceImpl.P2P_CONNECTION_CHANGED:
-                    NetworkInfo info = (NetworkInfo) message.obj;
-                    mP2pConnected.set(info.isConnected());
-                    break;
                 case CMD_RECONNECT:
-                case CMD_REASSOCIATE:
-                    if (mTemporarilyDisconnectWifi) {
+                case CMD_REASSOCIATE: {
+                    if (mWifiP2pConnection.shouldTemporarilyDisconnectWifi()) {
                         // Drop a third party reconnect/reassociate if STA is
                         // temporarily disconnected for p2p
                         break;
                     } else {
-                        // ConnectModeState handles it
+                        // ConnectableState handles it
                         handleStatus = NOT_HANDLED;
                     }
                     break;
-                case CMD_SCREEN_STATE_CHANGED:
-                    handleScreenStateChanged(message.arg1 != 0);
-                    break;
-                default:
+                }
+                default: {
                     handleStatus = NOT_HANDLED;
                     break;
+                }
             }
 
             if (handleStatus == HANDLED) {
@@ -5772,70 +5812,11 @@
         @Override
         public void exit() {
             mWifiConnectivityManager.handleConnectionStateChanged(
+                    mClientModeManager,
                      WifiConnectivityManager.WIFI_STATE_TRANSITIONING);
         }
     }
 
-    /**
-     * State machine initiated requests can have replyTo set to null, indicating
-     * there are no recipients, we ignore those reply actions.
-     */
-    private void replyToMessage(Message msg, int what) {
-        if (msg.replyTo == null) return;
-        Message dstMsg = obtainMessageWithWhatAndArg2(msg, what);
-        mReplyChannel.replyToMessage(msg, dstMsg);
-    }
-
-    private void replyToMessage(Message msg, int what, int arg1) {
-        if (msg.replyTo == null) return;
-        Message dstMsg = obtainMessageWithWhatAndArg2(msg, what);
-        dstMsg.arg1 = arg1;
-        mReplyChannel.replyToMessage(msg, dstMsg);
-    }
-
-    private void replyToMessage(Message msg, int what, Object obj) {
-        if (msg.replyTo == null) return;
-        Message dstMsg = obtainMessageWithWhatAndArg2(msg, what);
-        dstMsg.obj = obj;
-        mReplyChannel.replyToMessage(msg, dstMsg);
-    }
-
-    /**
-     * arg2 on the source message has a unique id that needs to be retained in replies
-     * to match the request
-     * <p>see WifiManager for details
-     */
-    private Message obtainMessageWithWhatAndArg2(Message srcMsg, int what) {
-        Message msg = Message.obtain();
-        msg.what = what;
-        msg.arg2 = srcMsg.arg2;
-        return msg;
-    }
-
-    /**
-     * Notify interested parties if a wifi config has been changed.
-     *
-     * @param wifiCredentialEventType WIFI_CREDENTIAL_SAVED or WIFI_CREDENTIAL_FORGOT
-     * @param config Must have a WifiConfiguration object to succeed
-     * TODO: b/35258354 investigate if this can be removed.  Is the broadcast sent by
-     * WifiConfigManager sufficient?
-     */
-    private void broadcastWifiCredentialChanged(int wifiCredentialEventType,
-            WifiConfiguration config) {
-        Intent intent = new Intent(WifiManager.WIFI_CREDENTIAL_CHANGED_ACTION);
-        if (config != null && config.SSID != null && mWifiPermissionsUtil.isLocationModeEnabled()) {
-            intent.putExtra(WifiManager.EXTRA_WIFI_CREDENTIAL_SSID, config.SSID);
-        }
-        intent.putExtra(WifiManager.EXTRA_WIFI_CREDENTIAL_EVENT_TYPE, wifiCredentialEventType);
-        mContext.createContextAsUser(UserHandle.CURRENT, 0)
-                .sendBroadcastWithMultiplePermissions(
-                        intent,
-                        new String[]{
-                                android.Manifest.permission.RECEIVE_WIFI_CREDENTIAL_CHANGE,
-                                android.Manifest.permission.ACCESS_FINE_LOCATION,
-                        });
-    }
-
     void handleGsmAuthRequest(SimAuthRequestData requestData) {
         WifiConfiguration requestingWifiConfiguration = null;
         if (mTargetWifiConfiguration != null
@@ -5845,7 +5826,7 @@
             logd("id matches targetWifiConfiguration");
         } else if (mLastNetworkId != WifiConfiguration.INVALID_NETWORK_ID
                 && mLastNetworkId == requestData.networkId) {
-            requestingWifiConfiguration = getCurrentWifiConfiguration();
+            requestingWifiConfiguration = getConnectedWifiConfigurationInternal();
             logd("id matches currentWifiConfiguration");
         }
 
@@ -5896,7 +5877,7 @@
             logd("id matches targetWifiConfiguration");
         } else if (mLastNetworkId != WifiConfiguration.INVALID_NETWORK_ID
                 && mLastNetworkId == requestData.networkId) {
-            requestingWifiConfiguration = getCurrentWifiConfiguration();
+            requestingWifiConfiguration = getConnectedWifiConfigurationInternal();
             logd("id matches currentWifiConfiguration");
         }
 
@@ -5930,19 +5911,10 @@
      * Automatically roam to the network specified
      *
      * @param networkId ID of the network to roam to
-     * @param scanResult scan result which identifies the network to roam to
+     * @param bssid BSSID of the access point to roam to.
      */
-    public void startRoamToNetwork(int networkId, ScanResult scanResult) {
-        sendMessage(CMD_START_ROAM, networkId, 0, scanResult);
-    }
-
-    /**
-     * Dynamically turn on/off WifiConnectivityManager
-     *
-     * @param choice true-enable; false-disable
-     */
-    public void allowAutoJoinGlobal(boolean choice) {
-        mWifiConnectivityManager.setAutoJoinEnabledExternal(choice);
+    public void startRoamToNetwork(int networkId, String bssid) {
+        sendMessage(CMD_START_ROAM, networkId, 0, bssid);
     }
 
     /**
@@ -5950,18 +5922,18 @@
      * @return true if this is a suspicious disconnect
      */
     static boolean unexpectedDisconnectedReason(int reason) {
-        return reason == 2              // PREV_AUTH_NOT_VALID
-                || reason == 6          // CLASS2_FRAME_FROM_NONAUTH_STA
-                || reason == 7          // FRAME_FROM_NONASSOC_STA
-                || reason == 8          // STA_HAS_LEFT
-                || reason == 9          // STA_REQ_ASSOC_WITHOUT_AUTH
-                || reason == 14         // MICHAEL_MIC_FAILURE
-                || reason == 15         // 4WAY_HANDSHAKE_TIMEOUT
-                || reason == 16         // GROUP_KEY_UPDATE_TIMEOUT
-                || reason == 18         // GROUP_CIPHER_NOT_VALID
-                || reason == 19         // PAIRWISE_CIPHER_NOT_VALID
-                || reason == 23         // IEEE_802_1X_AUTH_FAILED
-                || reason == 34;        // DISASSOC_LOW_ACK
+        return reason == ReasonCode.PREV_AUTH_NOT_VALID
+                || reason == ReasonCode.CLASS2_FRAME_FROM_NONAUTH_STA
+                || reason == ReasonCode.CLASS3_FRAME_FROM_NONASSOC_STA
+                || reason == ReasonCode.DISASSOC_STA_HAS_LEFT
+                || reason == ReasonCode.STA_REQ_ASSOC_WITHOUT_AUTH
+                || reason == ReasonCode.MICHAEL_MIC_FAILURE
+                || reason == ReasonCode.FOURWAY_HANDSHAKE_TIMEOUT
+                || reason == ReasonCode.GROUP_KEY_UPDATE_TIMEOUT
+                || reason == ReasonCode.GROUP_CIPHER_NOT_VALID
+                || reason == ReasonCode.PAIRWISE_CIPHER_NOT_VALID
+                || reason == ReasonCode.IEEE_802_1X_AUTH_FAILED
+                || reason == ReasonCode.DISASSOC_LOW_ACK;
     }
 
     private static String getLinkPropertiesSummary(LinkProperties lp) {
@@ -5992,38 +5964,9 @@
      * Gets the SSID from the WifiConfiguration pointed at by 'mTargetNetworkId'
      * This should match the network config framework is attempting to connect to.
      */
-    private String getTargetSsid() {
-        WifiConfiguration currentConfig = mWifiConfigManager.getConfiguredNetwork(mTargetNetworkId);
-        if (currentConfig != null) {
-            return currentConfig.SSID;
-        }
-        return null;
-    }
-
-    /**
-     * Send message to WifiP2pServiceImpl.
-     * @return true if message is sent.
-     *         false if there is no channel configured for WifiP2pServiceImpl.
-     */
-    private boolean p2pSendMessage(int what) {
-        if (mWifiP2pChannel != null) {
-            mWifiP2pChannel.sendMessage(what);
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Send message to WifiP2pServiceImpl with an additional param |arg1|.
-     * @return true if message is sent.
-     *         false if there is no channel configured for WifiP2pServiceImpl.
-     */
-    private boolean p2pSendMessage(int what, int arg1) {
-        if (mWifiP2pChannel != null) {
-            mWifiP2pChannel.sendMessage(what, arg1);
-            return true;
-        }
-        return false;
+    private String getConnectingSsidInternal() {
+        WifiConfiguration config = getConnectingWifiConfigurationInternal();
+        return config != null ? config.SSID : null;
     }
 
     /**
@@ -6031,252 +5974,95 @@
      */
     private boolean hasConnectionRequests() {
         return mNetworkFactory.hasConnectionRequests()
-                || mUntrustedNetworkFactory.hasConnectionRequests();
+                || mUntrustedNetworkFactory.hasConnectionRequests()
+                || mOemWifiNetworkFactory.hasConnectionRequests();
     }
 
     /**
-     * Returns whether CMD_IP_REACHABILITY_LOST events should trigger disconnects.
+     * Retrieve the factory MAC address from config store (stored on first bootup). If we don't have
+     * a factory MAC address stored in config store, retrieve it now and store it.
+     *
+     * Note:
+     * <li> This is needed to ensure that we use the same MAC address for connecting to
+     * networks with MAC randomization disabled regardless of whether the connection is
+     * occurring on "wlan0" or "wlan1" due to STA + STA. </li>
+     * <li> Retries added to deal with any transient failures when invoking
+     * {@link WifiNative#getStaFactoryMacAddress(String)}.
      */
-    public boolean getIpReachabilityDisconnectEnabled() {
-        return mIpReachabilityDisconnectEnabled;
-    }
+    @Nullable
+    private MacAddress retrieveFactoryMacAddressAndStoreIfNecessary() {
+        // Already present, just return.
+        String factoryMacAddressStr = mSettingsConfigStore.get(WIFI_STA_FACTORY_MAC_ADDRESS);
+        if (factoryMacAddressStr != null) return MacAddress.fromString(factoryMacAddressStr);
 
-    /**
-     * Sets whether CMD_IP_REACHABILITY_LOST events should trigger disconnects.
-     */
-    public void setIpReachabilityDisconnectEnabled(boolean enabled) {
-        mIpReachabilityDisconnectEnabled = enabled;
-    }
-
-    /**
-     * Sends a message to initialize the ClientModeImpl.
-     */
-    public void initialize() {
-        sendMessage(CMD_INITIALIZE);
-    }
-
-    /**
-     * Add a network request match callback to {@link WifiNetworkFactory}.
-     */
-    public void addNetworkRequestMatchCallback(IBinder binder,
-                                               INetworkRequestMatchCallback callback,
-                                               int callbackIdentifier) {
-        mNetworkFactory.addCallback(binder, callback, callbackIdentifier);
-    }
-
-    /**
-     * Remove a network request match callback from {@link WifiNetworkFactory}.
-     */
-    public void removeNetworkRequestMatchCallback(int callbackIdentifier) {
-        mNetworkFactory.removeCallback(callbackIdentifier);
-    }
-
-    /**
-     * Approve all access points from {@link WifiNetworkFactory} for the provided package.
-     * Used by shell commands.
-     */
-    public void setNetworkRequestUserApprovedApp(@NonNull String packageName, boolean approved) {
-        mNetworkFactory.setUserApprovedApp(packageName, approved);
-    }
-
-    /**
-     * Whether all access points are approved for the specified app.
-     * Used by shell commands.
-     */
-    public boolean hasNetworkRequestUserApprovedApp(@NonNull String packageName) {
-        return mNetworkFactory.hasUserApprovedApp(packageName);
-    }
-
-    /**
-     * Remove all approved access points from {@link WifiNetworkFactory} for the provided package.
-     */
-    public void removeNetworkRequestUserApprovedAccessPointsForApp(@NonNull String packageName) {
-        mNetworkFactory.removeUserApprovedAccessPointsForApp(packageName);
-    }
-
-    /**
-     * Clear all approved access points from {@link WifiNetworkFactory}.
-     */
-    public void clearNetworkRequestUserApprovedAccessPoints() {
-        mNetworkFactory.clear();
+        MacAddress factoryMacAddress = mWifiNative.getStaFactoryMacAddress(mInterfaceName);
+        if (factoryMacAddress == null) {
+            // the device may be running an older HAL (version < 1.3).
+            Log.w(TAG, "Failed to retrieve factory MAC address");
+            return null;
+        }
+        Log.i(TAG, "Factory MAC address retrieved and stored in config store: "
+                + factoryMacAddress);
+        mSettingsConfigStore.put(WIFI_STA_FACTORY_MAC_ADDRESS, factoryMacAddress.toString());
+        return factoryMacAddress;
     }
 
     /**
      * Gets the factory MAC address of wlan0 (station interface).
      * @return String representation of the factory MAC address.
      */
+    @Nullable
     public String getFactoryMacAddress() {
-        MacAddress macAddress = mWifiNative.getFactoryMacAddress(mInterfaceName);
-        if (macAddress != null) {
-            return macAddress.toString();
-        }
-        if (!isConnectedMacRandomizationEnabled()) {
+        MacAddress factoryMacAddress = retrieveFactoryMacAddressAndStoreIfNecessary();
+        if (factoryMacAddress != null) return factoryMacAddress.toString();
+
+        // For devices with older HAL's (version < 1.3), no API exists to retrieve factory MAC
+        // address (and also does not support MAC randomization - needs verson 1.2). So, just
+        // return the regular MAC address from the interface.
+        if (!mWifiGlobals.isConnectedMacRandomizationEnabled()) {
+            Log.w(TAG, "Can't get factory MAC address, return the MAC address");
             return mWifiNative.getMacAddress(mInterfaceName);
         }
         return null;
     }
 
-    /**
-     * Sets the current device mobility state.
-     * @param state the new device mobility state
-     */
-    public void setDeviceMobilityState(@DeviceMobilityState int state) {
-        mWifiConnectivityManager.setDeviceMobilityState(state);
-        mWifiHealthMonitor.setDeviceMobilityState(state);
-        mWifiDataStall.setDeviceMobilityState(state);
-    }
-
-    /**
-     * Updates the Wi-Fi usability score.
-     * @param seqNum Sequence number of the Wi-Fi usability score.
-     * @param score The Wi-Fi usability score.
-     * @param predictionHorizonSec Prediction horizon of the Wi-Fi usability score.
-     */
-    public void updateWifiUsabilityScore(int seqNum, int score, int predictionHorizonSec) {
-        mWifiMetrics.incrementWifiUsabilityScoreCount(seqNum, score, predictionHorizonSec);
-    }
-
-    /**
-     * Sends a link probe.
-     */
-    @VisibleForTesting
-    public void probeLink(WifiNl80211Manager.SendMgmtFrameCallback callback, int mcs) {
-        mWifiNative.probeLink(mInterfaceName, MacAddress.fromString(mWifiInfo.getBSSID()),
-                callback, mcs);
-    }
-
-    private void sendActionListenerFailure(int callbackIdentifier, int reason) {
-        IActionListener actionListener;
-        synchronized (mProcessingActionListeners) {
-            actionListener = mProcessingActionListeners.remove(callbackIdentifier);
+    /** Sends a link probe. */
+    public void probeLink(LinkProbeCallback callback, int mcs) {
+        String bssid = mWifiInfo.getBSSID();
+        if (bssid == null) {
+            Log.w(getTag(), "Attempted to send link probe when not connected!");
+            callback.onFailure(LinkProbeCallback.LINK_PROBE_ERROR_NOT_CONNECTED);
+            return;
         }
-        if (actionListener != null) {
-            try {
-                actionListener.onFailure(reason);
-            } catch (RemoteException e) {
-                // no-op (client may be dead, nothing to be done)
-            }
+        mWifiNative.probeLink(mInterfaceName, MacAddress.fromString(bssid), callback, mcs);
+    }
+
+    private static class ConnectNetworkMessage {
+        public final NetworkUpdateResult result;
+        public final ActionListenerWrapper listener;
+
+        ConnectNetworkMessage(NetworkUpdateResult result, ActionListenerWrapper listener) {
+            this.result = result;
+            this.listener = listener;
         }
     }
 
-    private void sendActionListenerSuccess(int callbackIdentifier) {
-        IActionListener actionListener;
-        synchronized (mProcessingActionListeners) {
-            actionListener = mProcessingActionListeners.remove(callbackIdentifier);
-        }
-        if (actionListener != null) {
-            try {
-                actionListener.onSuccess();
-            } catch (RemoteException e) {
-                // no-op (client may be dead, nothing to be done)
-            }
-        }
+    /** Trigger network connection and provide status via the provided callback. */
+    public void connectNetwork(NetworkUpdateResult result, ActionListenerWrapper wrapper,
+            int callingUid) {
+        Message message =
+                obtainMessage(CMD_CONNECT_NETWORK, new ConnectNetworkMessage(result, wrapper));
+        message.sendingUid = callingUid;
+        sendMessage(message);
     }
 
-    /**
-     * Trigger network connection and provide status via the provided callback.
-     */
-    public void connect(WifiConfiguration config, int netId, @Nullable IBinder binder,
-            @Nullable IActionListener callback, int callbackIdentifier, int callingUid) {
-        mWifiInjector.getWifiThreadRunner().post(() -> {
-            if (callback != null && binder != null) {
-                mProcessingActionListeners.add(binder, callback, callbackIdentifier);
-            }
-            /**
-             * The connect message can contain a network id passed as arg1 on message or
-             * or a config passed as obj on message.
-             * For a new network, a config is passed to create and connect.
-             * For an existing network, a network id is passed
-             */
-            NetworkUpdateResult result = null;
-            if (config != null) {
-                result = mWifiConfigManager.addOrUpdateNetwork(config, callingUid);
-                if (!result.isSuccess()) {
-                    loge("connectNetwork adding/updating config=" + config + " failed");
-                    sendActionListenerFailure(callbackIdentifier, WifiManager.ERROR);
-                    return;
-                }
-                broadcastWifiCredentialChanged(WifiManager.WIFI_CREDENTIAL_SAVED, config);
-            } else {
-                if (mWifiConfigManager.getConfiguredNetwork(netId) == null) {
-                    loge("connectNetwork Invalid network Id=" + netId);
-                    sendActionListenerFailure(callbackIdentifier, WifiManager.ERROR);
-                    return;
-                }
-                result = new NetworkUpdateResult(netId);
-            }
-            final int networkId = result.getNetworkId();
-            mWifiConfigManager.userEnabledNetwork(networkId);
-            if (!mWifiConfigManager.enableNetwork(networkId, true, callingUid, null)
-                    || !mWifiConfigManager.updateLastConnectUid(networkId, callingUid)) {
-                logi("connect Allowing uid " + callingUid
-                        + " with insufficient permissions to connect=" + networkId);
-            } else if (mWifiPermissionsUtil.checkNetworkSettingsPermission(callingUid)) {
-                // Note user connect choice here, so that it will be considered in the
-                // next network selection.
-                mWifiConnectivityManager.setUserConnectChoice(networkId);
-            }
-            Message message =
-                    obtainMessage(CMD_CONNECT_NETWORK, -1, callbackIdentifier, result);
-            message.sendingUid = callingUid;
-            sendMessage(message);
-        });
-    }
-
-    /**
-     * Trigger network save and provide status via the provided callback.
-     */
-    public void save(WifiConfiguration config, @Nullable IBinder binder,
-            @Nullable IActionListener callback, int callbackIdentifier, int callingUid) {
-        mWifiInjector.getWifiThreadRunner().post(() -> {
-            if (callback != null && binder != null) {
-                mProcessingActionListeners.add(binder, callback, callbackIdentifier);
-            }
-            if (config == null) {
-                loge("saveNetwork with null configuration my state "
-                        + getCurrentState().getName());
-                sendActionListenerFailure(callbackIdentifier, WifiManager.ERROR);
-                return;
-            }
-            NetworkUpdateResult result =
-                    mWifiConfigManager.addOrUpdateNetwork(config, callingUid);
-            if (!result.isSuccess()) {
-                loge("saveNetwork adding/updating config=" + config + " failed");
-                sendActionListenerFailure(callbackIdentifier, WifiManager.ERROR);
-                return;
-            }
-            if (!mWifiConfigManager.enableNetwork(
-                    result.getNetworkId(), false, callingUid, null)) {
-                loge("saveNetwork enabling config=" + config + " failed");
-                sendActionListenerFailure(callbackIdentifier, WifiManager.ERROR);
-                return;
-            }
-            broadcastWifiCredentialChanged(WifiManager.WIFI_CREDENTIAL_SAVED, config);
-            Message message =
-                    obtainMessage(CMD_SAVE_NETWORK, -1 , callbackIdentifier, result);
-            message.sendingUid = callingUid;
-            sendMessage(message);
-        });
-    }
-
-    /**
-     * Trigger network forget and provide status via the provided callback.
-     */
-    public void forget(int netId, @Nullable IBinder binder, @Nullable IActionListener callback,
-            int callbackIdentifier, int callingUid) {
-        mWifiInjector.getWifiThreadRunner().post(() -> {
-            if (callback != null && binder != null) {
-                mProcessingActionListeners.add(binder, callback, callbackIdentifier);
-            }
-            WifiConfiguration config = mWifiConfigManager.getConfiguredNetwork(netId);
-            boolean success = mWifiConfigManager.removeNetwork(netId, callingUid, null);
-            if (!success) {
-                loge("Failed to remove network");
-                sendActionListenerFailure(callbackIdentifier, WifiManager.ERROR);
-            }
-            sendActionListenerSuccess(callbackIdentifier);
-            broadcastWifiCredentialChanged(WifiManager.WIFI_CREDENTIAL_FORGOT, config);
-        });
+    /** Trigger network save and provide status via the provided callback. */
+    public void saveNetwork(NetworkUpdateResult result, ActionListenerWrapper wrapper,
+            int callingUid) {
+        Message message =
+                obtainMessage(CMD_SAVE_NETWORK, new ConnectNetworkMessage(result, wrapper));
+        message.sendingUid = callingUid;
+        sendMessage(message);
     }
 
     /**
@@ -6292,10 +6078,12 @@
         String bssid = mWifiInfo.getBSSID();
         String ssid = mWifiInfo.getSSID();
         if ((bssid == null) || (ssid == null) || WifiManager.UNKNOWN_SSID.equals(ssid)) {
-            Log.e(TAG, "Failed to handle BSS transition: bssid: " + bssid + " ssid: " + ssid);
+            Log.e(getTag(), "Failed to handle BSS transition: bssid: " + bssid + " ssid: " + ssid);
             return;
         }
 
+        mWifiMetrics.incrementSteeringRequestCount();
+
         if ((frameData.mBssTmDataFlagsMask
                 & MboOceConstants.BTM_DATA_FLAG_MBO_CELL_DATA_CONNECTION_PREFERENCE_INCLUDED)
                 != 0) {
@@ -6320,8 +6108,8 @@
                 duration = MboOceConstants.DEFAULT_BLOCKLIST_DURATION_MS;
             }
             // Blocklist the current BSS
-            mBssidBlocklistMonitor.blockBssidForDurationMs(bssid, ssid, duration,
-                    BssidBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_MBO_OCE, 0);
+            mWifiBlocklistMonitor.blockBssidForDurationMs(bssid, ssid, duration,
+                    WifiBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_MBO_OCE, 0);
         }
 
         if (frameData.mStatus != MboOceConstants.BTM_RESPONSE_STATUS_ACCEPT) {
@@ -6335,30 +6123,32 @@
      * @return true if this device supports FILS-SHA256
      */
     private boolean isFilsSha256Supported() {
-        return (mWifiNative.getSupportedFeatureSet(mInterfaceName) & WIFI_FEATURE_FILS_SHA256) != 0;
+        return (getSupportedFeatures() & WIFI_FEATURE_FILS_SHA256) != 0;
     }
 
     /**
      * @return true if this device supports FILS-SHA384
      */
     private boolean isFilsSha384Supported() {
-        return (mWifiNative.getSupportedFeatureSet(mInterfaceName) & WIFI_FEATURE_FILS_SHA384) != 0;
+        return (getSupportedFeatures() & WIFI_FEATURE_FILS_SHA384) != 0;
     }
 
     /**
      * Helper method to set the allowed key management schemes from
      * scan result.
+     * When the AKM is updated, changes should be propagated to the
+     * actual saved network, and the correct AKM could be retrieved
+     * on selecting the security params.
      */
     private void updateAllowedKeyManagementSchemesFromScanResult(
             WifiConfiguration config, ScanResult scanResult) {
-        if (isFilsSha256Supported()
-                && ScanResultUtil.isScanResultForFilsSha256Network(scanResult)) {
-            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.FILS_SHA256);
-        }
-        if (isFilsSha384Supported()
-                && ScanResultUtil.isScanResultForFilsSha384Network(scanResult)) {
-            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.FILS_SHA384);
-        }
+        config.enableFils(
+                isFilsSha256Supported()
+                && ScanResultUtil.isScanResultForFilsSha256Network(scanResult),
+                isFilsSha384Supported()
+                && ScanResultUtil.isScanResultForFilsSha384Network(scanResult));
+        mWifiConfigManager.updateFilsAkms(config.networkId,
+                config.isFilsSha256Enabled(), config.isFilsSha384Enabled());
     }
     /**
      * Update wifi configuration based on the matching scan result.
@@ -6369,12 +6159,52 @@
     private void updateWifiConfigFromMatchingScanResult(WifiConfiguration config,
             ScanResult scanResult) {
         updateAllowedKeyManagementSchemesFromScanResult(config, scanResult);
-        if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.FILS_SHA256)
-                || config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.FILS_SHA384)) {
+        if (config.isFilsSha256Enabled() || config.isFilsSha384Enabled()) {
             config.enterpriseConfig.setFieldValue(WifiEnterpriseConfig.EAP_ERP, "1");
         }
     }
 
+    private void selectCandidateSecurityParamsIfNecessary(
+            WifiConfiguration config,
+            List<ScanResult> scanResults) {
+        if (null != config.getNetworkSelectionStatus().getCandidateSecurityParams()) return;
+
+        // This comes from wifi picker directly so there is no candidate security params.
+        // Run network selection against this SSID.
+        List<ScanDetail> scanDetailsList = scanResults.stream()
+                .filter(scanResult -> config.SSID.equals(
+                        ScanResultUtil.createQuotedSSID(scanResult.SSID)))
+                .map(ScanResultUtil::toScanDetail)
+                .collect(Collectors.toList());
+        List<WifiNetworkSelector.ClientModeManagerState> cmmState = new ArrayList<>();
+        cmmState.add(new WifiNetworkSelector.ClientModeManagerState(mClientModeManager));
+        List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
+                scanDetailsList,
+                new HashSet<String>(),
+                cmmState,
+                true, true, true);
+        WifiConfiguration selectedConfig = mWifiNetworkSelector.selectNetwork(candidates);
+        if (null != selectedConfig && selectedConfig.networkId == config.networkId) {
+            config.getNetworkSelectionStatus().setCandidateSecurityParams(
+                    selectedConfig.getNetworkSelectionStatus().getCandidateSecurityParams());
+            return;
+        }
+
+        // When a connecting request comes from network request or adding a network via
+        // API directly, there might be no scan result to know the proper security params.
+        // In this case, we use the first available security params to have a try first.
+        Log.i(getTag(), "Cannot select a candidate security params from scan results,"
+                + "try to select the first available security params.");
+        SecurityParams defaultParams = config.getSecurityParamsList().stream()
+                .filter(WifiConfigurationUtil::isSecurityParamsValid)
+                .findFirst().orElse(null);
+        config.getNetworkSelectionStatus().setCandidateSecurityParams(defaultParams);
+        // populate the target security params to the internal configuration manually,
+        // and then wifi info could retrieve this information.
+        mWifiConfigManager.setNetworkCandidateScanResult(
+                config.networkId, null, 0, defaultParams);
+    }
+
     /**
      * Update the wifi configuration before sending connect to
      * supplicant/driver.
@@ -6383,72 +6213,15 @@
      * @param bssid BSSID to assocaite with.
      */
     void updateWifiConfigOnStartConnection(WifiConfiguration config, String bssid) {
-        boolean canUpgradePskToSae = false;
-        boolean isFrameworkWpa3SaeUpgradePossible = false;
-        boolean isLegacyWpa2ApInScanResult = false;
-
         setTargetBssid(config, bssid);
 
-        if (isWpa3SaeUpgradeEnabled() && config.allowedKeyManagement.get(
-                WifiConfiguration.KeyMgmt.WPA_PSK)) {
-            isFrameworkWpa3SaeUpgradePossible = true;
-        }
-
-        if (isFrameworkWpa3SaeUpgradePossible && isWpa3SaeUpgradeOffloadEnabled()) {
-            // Driver offload of upgrading legacy WPA/WPA2 connection to WPA3
-            if (mVerboseLoggingEnabled) {
-                Log.d(TAG, "Driver upgrade legacy WPA/WPA2 connection to WPA3");
-            }
-            config.allowedAuthAlgorithms.clear();
-            // Note: KeyMgmt.WPA2_PSK is already enabled, enable SAE as well
-            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.SAE);
-            isFrameworkWpa3SaeUpgradePossible = false;
-        }
-        // Check if network selection selected a good WPA3 candidate AP for a WPA2
-        // saved network.
-        ScanResult scanResultCandidate = config.getNetworkSelectionStatus().getCandidate();
-        if (isFrameworkWpa3SaeUpgradePossible && scanResultCandidate != null) {
-            ScanResultMatchInfo scanResultMatchInfo = ScanResultMatchInfo
-                    .fromScanResult(scanResultCandidate);
-            if ((scanResultMatchInfo.networkType == WifiConfiguration.SECURITY_TYPE_SAE)) {
-                canUpgradePskToSae = true;
-            } else {
-                // No SAE candidate
-                isFrameworkWpa3SaeUpgradePossible = false;
-            }
-        }
-
-        /**
-         *  Go through the matching scan results and update wifi config.
-         */
+        // Go through the matching scan results and update wifi config.
         ScanResultMatchInfo key1 = ScanResultMatchInfo.fromWifiConfiguration(config);
-        ScanRequestProxy scanRequestProxy = mWifiInjector.getScanRequestProxy();
-        List<ScanResult> scanResults = scanRequestProxy.getScanResults();
+        List<ScanResult> scanResults = mScanRequestProxy.getScanResults();
         for (ScanResult scanResult : scanResults) {
             if (!config.SSID.equals(ScanResultUtil.createQuotedSSID(scanResult.SSID))) {
                 continue;
             }
-            if (isFrameworkWpa3SaeUpgradePossible && !isLegacyWpa2ApInScanResult) {
-                if (ScanResultUtil.isScanResultForPskNetwork(scanResult)
-                        && !ScanResultUtil.isScanResultForSaeNetwork(scanResult)) {
-                    // Found a legacy WPA2 AP in range. Do not upgrade the connection to WPA3 to
-                    // allow seamless roaming within the ESS.
-                    if (mVerboseLoggingEnabled) {
-                        Log.d(TAG, "Found legacy WPA2 AP, do not upgrade to WPA3");
-                    }
-                    isLegacyWpa2ApInScanResult = true;
-                    canUpgradePskToSae = false;
-                }
-                if (ScanResultUtil.isScanResultForSaeNetwork(scanResult)
-                        && scanResultCandidate == null) {
-                    // When the user manually selected a network from the Wi-Fi picker, evaluate
-                    // if to upgrade based on the scan results. The most typical use case during
-                    // the WPA3 transition mode is to have a WPA2/WPA3 AP in transition mode. In
-                    // this case, we would like to upgrade the connection.
-                    canUpgradePskToSae = true;
-                }
-            }
-
             ScanResultMatchInfo key2 = ScanResultMatchInfo.fromScanResult(scanResult);
             if (!key1.equals(key2)) {
                 continue;
@@ -6456,19 +6229,10 @@
             updateWifiConfigFromMatchingScanResult(config, scanResult);
         }
 
-        if (isFrameworkWpa3SaeUpgradePossible && canUpgradePskToSae
-                && !(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.FILS_SHA256)
-                            || config.allowedKeyManagement.get(
-                            WifiConfiguration.KeyMgmt.FILS_SHA384))) {
-            // Upgrade legacy WPA/WPA2 connection to WPA3
-            if (mVerboseLoggingEnabled) {
-                Log.d(TAG, "Upgrade legacy WPA/WPA2 connection to WPA3");
-            }
-            config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
-        }
+        selectCandidateSecurityParamsIfNecessary(config, scanResults);
 
-        if (isConnectedMacRandomizationEnabled()) {
-            if (config.macRandomizationSetting == WifiConfiguration.RANDOMIZATION_PERSISTENT) {
+        if (mWifiGlobals.isConnectedMacRandomizationEnabled()) {
+            if (config.macRandomizationSetting != WifiConfiguration.RANDOMIZATION_NONE) {
                 configureRandomizedMacAddress(config);
             } else {
                 setCurrentMacToFactoryMac(config);
@@ -6505,10 +6269,10 @@
                 (config.getIpAssignment() == IpConfiguration.IpAssignment.STATIC);
         final boolean isUsingMacRandomization =
                 config.macRandomizationSetting
-                        == WifiConfiguration.RANDOMIZATION_PERSISTENT
-                        && isConnectedMacRandomizationEnabled();
+                        != WifiConfiguration.RANDOMIZATION_NONE
+                        && mWifiGlobals.isConnectedMacRandomizationEnabled();
         if (mVerboseLoggingEnabled) {
-            final String key = config.getKey();
+            final String key = config.getProfileKey();
             log("startIpClient netId=" + Integer.toString(mLastNetworkId)
                     + " " + key + " "
                     + " roam=" + mIsAutoRoaming
@@ -6517,7 +6281,7 @@
                     + " isFilsConnection=" + isFilsConnection);
         }
 
-        final MacAddress currentBssid = getCurrentBssid();
+        final MacAddress currentBssid = getCurrentBssidInternalMacAddress();
         final String l2Key = mLastL2KeyAndGroupHint != null
                 ? mLastL2KeyAndGroupHint.first : null;
         final String groupHint = mLastL2KeyAndGroupHint != null
@@ -6571,17 +6335,9 @@
 
                 // The cached scan result of connected network would be null at the first
                 // connection, try to check full scan result list again to look up matched
-                // scan result associated to the current SSID and BSSID.
+                // scan result associated to the current BSSID.
                 if (scanResult == null) {
-                    ScanRequestProxy scanRequestProxy = mWifiInjector.getScanRequestProxy();
-                    List<ScanResult> scanResults = scanRequestProxy.getScanResults();
-                    for (ScanResult result : scanResults) {
-                        if (result.SSID.equals(WifiInfo.removeDoubleQuotes(config.SSID))
-                                && result.BSSID.equals(mLastBssid)) {
-                            scanResult = result;
-                            break;
-                        }
-                    }
+                    scanResult = mScanRequestProxy.getScanResult(mLastBssid);
                 }
             }
 
@@ -6626,4 +6382,356 @@
         return true;
     }
 
+    @Override
+    public boolean setWifiConnectedNetworkScorer(IBinder binder,
+            IWifiConnectedNetworkScorer scorer) {
+        return mWifiScoreReport.setWifiConnectedNetworkScorer(binder, scorer);
+    }
+
+    @Override
+    public void clearWifiConnectedNetworkScorer() {
+        mWifiScoreReport.clearWifiConnectedNetworkScorer();
+    }
+
+    @Override
+    public void sendMessageToClientModeImpl(Message msg) {
+        sendMessage(msg);
+    }
+
+    @Override
+    public long getId() {
+        return mId;
+    }
+
+    @Override
+    public void dumpWifiScoreReport(FileDescriptor fd, PrintWriter pw, String[] args) {
+        mWifiScoreReport.dump(fd, pw, args);
+    }
+
+    /**
+     * Notifies changes in data connectivity of the default data SIM.
+     */
+    @Override
+    public void onCellularConnectivityChanged(@WifiDataStall.CellularDataStatusCode int status) {
+        mWifiConfigManager.onCellularConnectivityChanged(status);
+        // do a scan if no cell data and currently not connect to wifi
+        if (status == WifiDataStall.CELLULAR_DATA_NOT_AVAILABLE
+                && getConnectedWifiConfigurationInternal() == null) {
+            if (mContext.getResources().getBoolean(
+                    R.bool.config_wifiScanOnCellularDataLossEnabled)) {
+                mWifiConnectivityManager.forceConnectivityScan(WIFI_WORK_SOURCE);
+            }
+        }
+    }
+
+    @Override
+    public void setMboCellularDataStatus(boolean available) {
+        mWifiNative.setMboCellularDataStatus(mInterfaceName, available);
+    }
+
+    @Override
+    public WifiNative.RoamingCapabilities getRoamingCapabilities() {
+        return mWifiNative.getRoamingCapabilities(mInterfaceName);
+    }
+
+    @Override
+    public boolean configureRoaming(WifiNative.RoamingConfig config) {
+        return mWifiNative.configureRoaming(mInterfaceName, config);
+    }
+
+    @Override
+    public boolean enableRoaming(boolean enabled) {
+        int status = mWifiNative.enableFirmwareRoaming(
+                mInterfaceName, enabled
+                        ? WifiNative.ENABLE_FIRMWARE_ROAMING
+                        : WifiNative.DISABLE_FIRMWARE_ROAMING);
+        return status == WifiNative.SET_FIRMWARE_ROAMING_SUCCESS;
+    }
+
+    @Override
+    public boolean setCountryCode(String countryCode) {
+        return mWifiNative.setStaCountryCode(mInterfaceName, countryCode);
+    }
+
+    @Override
+    public List<TxFateReport> getTxPktFates() {
+        return mWifiNative.getTxPktFates(mInterfaceName);
+    }
+
+    @Override
+    public List<RxFateReport> getRxPktFates() {
+        return mWifiNative.getRxPktFates(mInterfaceName);
+    }
+
+    @Override
+    public void setShouldReduceNetworkScore(boolean shouldReduceNetworkScore) {
+        mWifiScoreReport.setShouldReduceNetworkScore(shouldReduceNetworkScore);
+    }
+
+    private void applyCachedPacketFilter() {
+        // If packet filter is supported on both connections, ignore since we would have already
+        // applied the filter.
+        if (mContext.getResources().getBoolean(R.bool.config_wifiEnableApfOnNonPrimarySta)) return;
+        if (mCachedPacketFilter == null) {
+            Log.w(TAG, "No cached packet filter to apply");
+            return;
+        }
+        Log.i(TAG, "Applying cached packet filter");
+        mWifiNative.installPacketFilter(mInterfaceName, mCachedPacketFilter);
+    }
+
+    /**
+     * Invoked by parent ConcreteClientModeManager whenever a role change occurs.
+     */
+    public void onRoleChanged() {
+        ClientRole role = mClientModeManager.getRole();
+        if (role == ROLE_CLIENT_PRIMARY) {
+            applyCachedPacketFilter();
+            if (mScreenOn) {
+                // Start RSSI polling for the new primary network to enable scoring.
+                enableRssiPolling(true);
+            }
+        } else {
+            if (mScreenOn) {
+                // Stop RSSI polling (if enabled) for the secondary network.
+                enableRssiPolling(false);
+            }
+        }
+        WifiConfiguration connectedNetwork = getConnectedWifiConfiguration();
+        if (connectedNetwork != null) {
+            updateWifiInfoWhenConnected(connectedNetwork);
+            // Update capabilities after a role change.
+            updateCapabilities(connectedNetwork);
+        }
+        mWifiScoreReport.onRoleChanged(role);
+    }
+
+    private void addPasspointInfoToLinkProperties(LinkProperties linkProperties) {
+        // CaptivePortalData.Builder.setVenueFriendlyName API not available on R
+        if (!SdkLevel.isAtLeastS()) {
+            return;
+        }
+        WifiConfiguration currentNetwork = getConnectedWifiConfigurationInternal();
+        if (currentNetwork == null || !currentNetwork.isPasspoint()) {
+            return;
+        }
+        ScanResult scanResult = mScanRequestProxy.getScanResult(mLastBssid);
+
+        if (scanResult == null) {
+            return;
+        }
+        URL venueUrl = mPasspointManager.getVenueUrl(scanResult);
+
+        // Update the friendly name to populate the notification
+        CaptivePortalData.Builder captivePortalDataBuilder = new CaptivePortalData.Builder()
+                .setVenueFriendlyName(currentNetwork.providerFriendlyName);
+
+        // Update the Venue URL if available
+        if (venueUrl != null) {
+            captivePortalDataBuilder.setVenueInfoUrl(Uri.parse(venueUrl.toString()),
+                    CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT);
+        }
+
+        // Update the T&C URL if available. The network is captive if T&C URL is available
+        if (mTermsAndConditionsUrl != null) {
+            captivePortalDataBuilder.setUserPortalUrl(
+                    Uri.parse(mTermsAndConditionsUrl.toString()),
+                    CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT).setCaptive(true);
+        }
+
+        linkProperties.setCaptivePortalData(captivePortalDataBuilder.build());
+    }
+
+    private boolean mHasQuit = false;
+
+    @Override
+    protected void onQuitting() {
+        mHasQuit = true;
+        mClientModeManager.onClientModeImplQuit();
+    }
+
+    /** Returns true if the ClientModeImpl has fully stopped, false otherwise. */
+    public boolean hasQuit() {
+        return mHasQuit;
+    }
+
+    /**
+     * WifiVcnNetworkPolicyChangeListener tracks VCN-defined Network policies for a
+     * WifiNetworkAgent. These policies are used to restart Networks or update their
+     * NetworkCapabilities.
+     */
+    private class WifiVcnNetworkPolicyChangeListener
+            implements VcnManager.VcnNetworkPolicyChangeListener {
+        @Override
+        public void onPolicyChanged() {
+            if (mNetworkAgent == null) {
+                return;
+            }
+            // Update the NetworkAgent's NetworkCapabilities which will merge the current
+            // capabilities with VcnManagementService's underlying Network policy.
+            Log.i(getTag(), "VCN policy changed, updating NetworkCapabilities.");
+            updateCapabilities();
+        }
+    }
+
+    /**
+     * Updates the default gateway mac address of the connected network config and updates the
+     * linked networks resulting from the new default gateway.
+     */
+    private boolean retrieveConnectedNetworkDefaultGateway() {
+        WifiConfiguration currentConfig = getConnectedWifiConfiguration();
+        if (currentConfig == null) {
+            logi("can't fetch config of current network id " + mLastNetworkId);
+            return false;
+        }
+
+        // Find IPv4 default gateway.
+        if (mLinkProperties == null) {
+            logi("cannot retrieve default gateway from null link properties");
+            return false;
+        }
+        String gatewayIPv4 = null;
+        for (RouteInfo routeInfo : mLinkProperties.getRoutes()) {
+            if (routeInfo.isDefaultRoute()
+                    && routeInfo.getDestination().getAddress() instanceof Inet4Address
+                    && routeInfo.hasGateway()) {
+                gatewayIPv4 = routeInfo.getGateway().getHostAddress();
+                break;
+            }
+        }
+
+        if (TextUtils.isEmpty(gatewayIPv4)) {
+            logi("default gateway ipv4 is null");
+            return false;
+        }
+
+        String gatewayMac = macAddressFromRoute(gatewayIPv4);
+        if (TextUtils.isEmpty(gatewayMac)) {
+            logi("default gateway mac fetch failed for ipv4 addr = " + gatewayIPv4);
+            return false;
+        }
+
+        logi("Default Gateway MAC address of " + mLastBssid + " from routes is : " + gatewayMac);
+        if (!mWifiConfigManager.setNetworkDefaultGwMacAddress(mLastNetworkId, gatewayMac)) {
+            logi("default gateway mac set failed for " + currentConfig.getKey() + " network");
+            return false;
+        }
+
+        return mWifiConfigManager.saveToStore(true);
+    }
+
+    /**
+     * Links the supplied config to all matching saved configs and updates the WifiBlocklistMonitor
+     * SSID allowlist with the linked networks.
+     */
+    private void updateLinkedNetworks(@NonNull WifiConfiguration config) {
+        if (!mContext.getResources().getBoolean(R.bool.config_wifiEnableLinkedNetworkRoaming)
+                || !config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
+            return;
+        }
+
+        mWifiConfigManager.updateLinkedNetworks(config.networkId);
+        Map<String, WifiConfiguration> linkedNetworks = mWifiConfigManager
+                .getLinkedNetworksWithoutMasking(config.networkId);
+        if (!mWifiNative.updateLinkedNetworks(mInterfaceName, config.networkId, linkedNetworks)) {
+            return;
+        }
+
+        List<String> allowlistSsids = new ArrayList<>(linkedNetworks.values().stream()
+                .map(linkedConfig -> linkedConfig.SSID)
+                .collect(Collectors.toList()));
+        if (linkedNetworks.size() > 0) {
+            allowlistSsids.add(config.SSID);
+        }
+        mWifiBlocklistMonitor.setAllowlistSsids(config.SSID, allowlistSsids);
+        mWifiBlocklistMonitor.updateFirmwareRoamingConfiguration(new ArraySet<>(allowlistSsids));
+    }
+
+    private boolean checkAndHandleLinkedNetworkRoaming(String associatedBssid) {
+        if (!mContext.getResources().getBoolean(R.bool.config_wifiEnableLinkedNetworkRoaming)) {
+            return false;
+        }
+
+        ScanResult scanResult = mScanRequestProxy.getScanResult(associatedBssid);
+        if (scanResult == null) {
+            return false;
+        }
+
+        WifiConfiguration config = mWifiConfigManager
+                .getSavedNetworkForScanResult(scanResult);
+        if (config == null || !config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)
+                || mLastNetworkId == config.networkId) {
+            return false;
+        }
+
+        mIsLinkedNetworkRoaming = true;
+        setTargetBssid(config, associatedBssid);
+        mTargetNetworkId = config.networkId;
+        mTargetWifiConfiguration = config;
+        mLastNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
+        sendNetworkChangeBroadcast(DetailedState.CONNECTING);
+        mWifiInfo.setFrequency(scanResult.frequency);
+        mWifiInfo.setBSSID(associatedBssid);
+        return true;
+    }
+
+    @RequiresApi(Build.VERSION_CODES.S)
+    private @WifiConfiguration.RecentFailureReason int
+            mboAssocDisallowedReasonCodeToWifiConfigurationRecentFailureReason(
+            @MboOceConstants.MboAssocDisallowedReasonCode int reasonCode) {
+        switch (reasonCode) {
+            case MboOceConstants.MBO_ASSOC_DISALLOWED_REASON_MAX_NUM_STA_ASSOCIATED:
+                return WifiConfiguration.RECENT_FAILURE_MBO_ASSOC_DISALLOWED_MAX_NUM_STA_ASSOCIATED;
+            case MboOceConstants.MBO_ASSOC_DISALLOWED_REASON_AIR_INTERFACE_OVERLOADED:
+                return WifiConfiguration
+                        .RECENT_FAILURE_MBO_ASSOC_DISALLOWED_AIR_INTERFACE_OVERLOADED;
+            case MboOceConstants.MBO_ASSOC_DISALLOWED_REASON_AUTH_SERVER_OVERLOADED:
+                return WifiConfiguration.RECENT_FAILURE_MBO_ASSOC_DISALLOWED_AUTH_SERVER_OVERLOADED;
+            case MboOceConstants.MBO_ASSOC_DISALLOWED_REASON_INSUFFICIENT_RSSI:
+                return WifiConfiguration.RECENT_FAILURE_MBO_ASSOC_DISALLOWED_INSUFFICIENT_RSSI;
+            case MboOceConstants.MBO_ASSOC_DISALLOWED_REASON_UNSPECIFIED:
+            case MboOceConstants.MBO_ASSOC_DISALLOWED_REASON_RESERVED_0:
+            case MboOceConstants.MBO_ASSOC_DISALLOWED_REASON_RESERVED:
+            default:
+                return WifiConfiguration.RECENT_FAILURE_MBO_ASSOC_DISALLOWED_UNSPECIFIED;
+        }
+    }
+
+    /**
+     * To set association rejection status in wifi config.
+     * @param netId The network ID.
+     * @param assocRejectEventInfo Association rejection information.
+     */
+    private void setAssociationRejectionStatusInConfig(int netId,
+            AssocRejectEventInfo assocRejectEventInfo) {
+        int statusCode = assocRejectEventInfo.statusCode;
+        @WifiConfiguration.RecentFailureReason int reason;
+
+        switch (statusCode) {
+            case StatusCode.AP_UNABLE_TO_HANDLE_NEW_STA:
+                reason = WifiConfiguration.RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA;
+                break;
+            case StatusCode.ASSOC_REJECTED_TEMPORARILY:
+                reason = WifiConfiguration.RECENT_FAILURE_REFUSED_TEMPORARILY;
+                break;
+            case StatusCode.DENIED_POOR_CHANNEL_CONDITIONS:
+                reason = WifiConfiguration.RECENT_FAILURE_POOR_CHANNEL_CONDITIONS;
+                break;
+            default:
+                // do nothing
+                return;
+        }
+
+        if (SdkLevel.isAtLeastS()) {
+            if (assocRejectEventInfo.mboAssocDisallowedInfo != null) {
+                reason = mboAssocDisallowedReasonCodeToWifiConfigurationRecentFailureReason(
+                        assocRejectEventInfo.mboAssocDisallowedInfo.mReasonCode);
+            } else if (assocRejectEventInfo.oceRssiBasedAssocRejectInfo != null) {
+                reason = WifiConfiguration.RECENT_FAILURE_OCE_RSSI_BASED_ASSOCIATION_REJECTION;
+            }
+        }
+
+        mWifiConfigManager.setRecentFailureAssociationStatus(netId, reason);
+
+    }
 }
diff --git a/service/java/com/android/server/wifi/ClientModeImplListener.java b/service/java/com/android/server/wifi/ClientModeImplListener.java
new file mode 100644
index 0000000..a999ba4
--- /dev/null
+++ b/service/java/com/android/server/wifi/ClientModeImplListener.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.annotation.NonNull;
+
+/** Listener for events on ClientModeImpl. */
+public interface ClientModeImplListener {
+    /**
+     * Called when a ClientModeImpl has been L2 connected.
+     * @param clientModeManager the ClientModeManager associated with the ClientModeImpl
+     */
+    default void onL2Connected(@NonNull ConcreteClientModeManager clientModeManager) {}
+
+    /**
+     * Called when a ClientModeImpl has been L3 connected.
+     * @param clientModeManager the ClientModeManager associated with the ClientModeImpl
+     */
+    default void onL3Connected(@NonNull ConcreteClientModeManager clientModeManager) {}
+
+    /**
+     * Called when a ClientModeImpl's internet connection has been validated.
+     * @param clientModeManager the ClientModeManager associated with the ClientModeImpl
+     */
+    default void onInternetValidated(@NonNull ConcreteClientModeManager clientModeManager) {}
+
+    /**
+     * Called when a ClientModeImpl starts a new connection attempt.
+     * @param clientModeManager the ClientModeManager associated with the ClientModeImpl
+     */
+    default void onConnectionStart(@NonNull ConcreteClientModeManager clientModeManager) {}
+
+    /**
+     * Called when a ClientModeImpl ends a connection (could be result of disconnect from an active
+     * connection or a connection attempt failure),
+     *
+     * @param clientModeManager the ClientModeManager associated with the ClientModeImpl
+     */
+    default void onConnectionEnd(@NonNull ConcreteClientModeManager clientModeManager) {}
+
+    /**
+     * Called when a Captive Portal is detected on this connection.
+     *
+     * @param clientModeManager the ClientModeManager associated with the ClientModeImpl
+     */
+    default void onCaptivePortalDetected(@NonNull ConcreteClientModeManager clientModeManager) {}
+}
diff --git a/service/java/com/android/server/wifi/ClientModeImplMonitor.java b/service/java/com/android/server/wifi/ClientModeImplMonitor.java
new file mode 100644
index 0000000..d4dc428
--- /dev/null
+++ b/service/java/com/android/server/wifi/ClientModeImplMonitor.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.annotation.NonNull;
+import android.util.ArraySet;
+
+import java.util.Set;
+
+/**
+ * This class is used for other modules to monitor events occurring in {@link ClientModeImpl},
+ * without putting that code directly in ClientModeImpl and cluttering it.
+ *
+ * TODO(b/175896748): Eventually, our goal is to make ClientModeImpl contain only code that is
+ *  critical to its main functionality (connection tracking), and move other auxiliary code
+ *  elsewhere (such as here).
+ */
+public class ClientModeImplMonitor implements ClientModeImplListener {
+
+    private final Set<ClientModeImplListener> mListeners = new ArraySet<>();
+
+    /** Register a listener. */
+    public void registerListener(@NonNull ClientModeImplListener listener) {
+        mListeners.add(listener);
+    }
+
+    /** Unregister a listener. */
+    public void unregisterListener(@NonNull ClientModeImplListener listener) {
+        mListeners.remove(listener);
+    }
+
+    @Override
+    public void onL2Connected(@NonNull ConcreteClientModeManager clientModeManager) {
+        for (ClientModeImplListener listener : mListeners) {
+            listener.onL2Connected(clientModeManager);
+        }
+    }
+
+    // TODO(b/175896748): not yet triggered by ClientModeImpl
+    @Override
+    public void onL3Connected(@NonNull ConcreteClientModeManager clientModeManager) {
+        for (ClientModeImplListener listener : mListeners) {
+            listener.onL3Connected(clientModeManager);
+        }
+    }
+
+    @Override
+    public void onInternetValidated(@NonNull ConcreteClientModeManager clientModeManager) {
+        for (ClientModeImplListener listener : mListeners) {
+            listener.onInternetValidated(clientModeManager);
+        }
+    }
+
+    @Override
+    public void onConnectionStart(@NonNull ConcreteClientModeManager clientModeManager) {
+        for (ClientModeImplListener listener : mListeners) {
+            listener.onConnectionStart(clientModeManager);
+        }
+    }
+
+    @Override
+    public void onConnectionEnd(@NonNull ConcreteClientModeManager clientModeManager) {
+        for (ClientModeImplListener listener : mListeners) {
+            listener.onConnectionEnd(clientModeManager);
+        }
+    }
+
+    @Override
+    public void onCaptivePortalDetected(@NonNull ConcreteClientModeManager clientModeManager) {
+        for (ClientModeImplListener listener : mListeners) {
+            listener.onCaptivePortalDetected(clientModeManager);
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/ClientModeManager.java b/service/java/com/android/server/wifi/ClientModeManager.java
index 3ca9817..52bdb84 100644
--- a/service/java/com/android/server/wifi/ClientModeManager.java
+++ b/service/java/com/android/server/wifi/ClientModeManager.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,670 +16,12 @@
 
 package com.android.server.wifi;
 
-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.Looper;
-import android.os.Message;
-import android.os.PersistableBundle;
-import android.os.UserHandle;
-import android.telephony.AccessNetworkConstants;
-import android.telephony.CarrierConfigManager;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
-import android.telephony.ims.ImsException;
-import android.telephony.ims.ImsMmTelManager;
-import android.telephony.ims.ImsReasonInfo;
-import android.telephony.ims.RegistrationManager;
-import android.telephony.ims.feature.MmTelFeature;
-import android.telephony.ims.stub.ImsRegistrationImplBase;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.internal.util.IState;
-import com.android.internal.util.Preconditions;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
-import com.android.modules.utils.HandlerExecutor;
-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.
+ * Base class for ClientModeManager.
  */
-public class ClientModeManager implements ActiveModeManager {
-    private static final String TAG = "WifiClientModeManager";
-
-    private final ClientModeStateMachine mStateMachine;
-
-    private final Context mContext;
-    private final Clock mClock;
-    private final WifiNative mWifiNative;
-    private final WifiMetrics mWifiMetrics;
-    private final SarManager mSarManager;
-    private final WakeupController mWakeupController;
-    private final Listener mModeListener;
-    private final ClientModeImpl mClientModeImpl;
-
-    private String mClientInterfaceName;
-    private boolean mIfaceIsUp = false;
-    private DeferStopHandler mDeferStopHandler;
-    private @Role int mRole = ROLE_UNSPECIFIED;
-    private @Role 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,
-            WakeupController wakeupController, ClientModeImpl clientModeImpl) {
-        mContext = context;
-        mClock = clock;
-        mWifiNative = wifiNative;
-        mModeListener = listener;
-        mWifiMetrics = wifiMetrics;
-        mSarManager = sarManager;
-        mWakeupController = wakeupController;
-        mClientModeImpl = clientModeImpl;
-        mStateMachine = new ClientModeStateMachine(looper);
-        mDeferStopHandler = new DeferStopHandler(TAG, looper);
-    }
-
-    /**
-     * Start client mode.
-     */
-    @Override
-    public void start() {
-        mTargetRole = ROLE_CLIENT_SCAN_ONLY;
-        mStateMachine.sendMessage(ClientModeStateMachine.CMD_START);
-    }
-
-    /**
-     * Disconnect from any currently connected networks and stop client mode.
-     */
-    @Override
-    public void stop() {
-        Log.d(TAG, " currentstate: " + getCurrentStateName());
-        mTargetRole = ROLE_UNSPECIFIED;
-        if (mIfaceIsUp) {
-            updateConnectModeState(WifiManager.WIFI_STATE_DISABLING,
-                    WifiManager.WIFI_STATE_ENABLED);
-        } else {
-            updateConnectModeState(WifiManager.WIFI_STATE_DISABLING,
-                    WifiManager.WIFI_STATE_ENABLING);
-        }
-        mDeferStopHandler.start(getWifiOffDeferringTimeMs());
-    }
+public interface ClientModeManager extends ActiveModeManager, ClientMode {
+    int syncGetWifiState();
 
     @Override
-    public boolean isStopping() {
-        return mTargetRole == ROLE_UNSPECIFIED && mRole != ROLE_UNSPECIFIED;
-    }
-
-    private class DeferStopHandler extends WifiHandler {
-        private boolean mIsDeferring = false;
-        private ImsMmTelManager mImsMmTelManager = null;
-        private Looper mLooper = null;
-        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() {
-                    @Override
-                    public void onRegistered(int imsRadioTech) {
-                        Log.d(TAG, "on IMS registered on type " + imsRadioTech);
-                        if (!mIsDeferring) return;
-
-                        if (imsRadioTech != AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
-                            continueToStopWifi();
-                        }
-                    }
-
-                    @Override
-                    public void onUnregistered(ImsReasonInfo imsReasonInfo) {
-                        Log.d(TAG, "on IMS unregistered");
-                        // 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;
-        }
-
-        public void start(int delayMs) {
-            if (mIsDeferring) return;
-
-            mMaximumDeferringTimeMillis = delayMs;
-            mDeferringStartTimeMillis = mClock.getElapsedSinceBootMillis();
-            // Most cases don't need delay, check it first to avoid unnecessary work.
-            if (delayMs == 0) {
-                continueToStopWifi();
-                return;
-            }
-
-            mImsMmTelManager = ImsMmTelManager.createForSubscriptionId(mActiveSubId);
-            if (mImsMmTelManager == null || !postDelayed(mRunnable, delayMs)) {
-                // if no delay or failed to add runnable, stop Wifi immediately.
-                continueToStopWifi();
-                return;
-            }
-
-            mIsDeferring = true;
-            Log.d(TAG, "Start DeferWifiOff handler with deferring time "
-                    + delayMs + " ms for subId: " + mActiveSubId);
-            try {
-                mImsMmTelManager.registerImsRegistrationCallback(
-                        new HandlerExecutor(new Handler(mLooper)),
-                        mImsRegistrationCallback);
-            } 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() {
-            Log.d(TAG, "The target role " + mTargetRole);
-
-            int deferringDurationMillis =
-                    (int) (mClock.getElapsedSinceBootMillis() - mDeferringStartTimeMillis);
-            boolean isTimedOut = mMaximumDeferringTimeMillis > 0
-                    && deferringDurationMillis >= mMaximumDeferringTimeMillis;
-            if (mTargetRole == ROLE_UNSPECIFIED) {
-                Log.d(TAG, "Continue to stop wifi");
-                mStateMachine.quitNow();
-                mWifiMetrics.noteWifiOff(mIsDeferring, isTimedOut, deferringDurationMillis);
-            } else if (mTargetRole == ROLE_CLIENT_SCAN_ONLY) {
-                if (!mWifiNative.switchClientInterfaceToScanMode(mClientInterfaceName)) {
-                    mModeListener.onStartFailure();
-                } else {
-                    mStateMachine.sendMessage(
-                            ClientModeStateMachine.CMD_SWITCH_TO_SCAN_ONLY_MODE_CONTINUE);
-                    mWifiMetrics.noteWifiOff(mIsDeferring, isTimedOut, deferringDurationMillis);
-                }
-            } else {
-                updateConnectModeState(WifiManager.WIFI_STATE_ENABLED,
-                        WifiManager.WIFI_STATE_DISABLING);
-            }
-
-            if (!mIsDeferring) return;
-
-            Log.d(TAG, "Stop DeferWifiOff handler.");
-            removeCallbacks(mRunnable);
-            if (mImsMmTelManager != null) {
-                try {
-                    mImsMmTelManager.unregisterImsRegistrationCallback(mImsRegistrationCallback);
-                } catch (RuntimeException e) {
-                    Log.e(TAG, "unregisterImsRegistrationCallback failed", e);
-                }
-            }
-
-            if (mConnectivityManager != null) {
-                mConnectivityManager.unregisterNetworkCallback(mImsNetworkCallback);
-            }
-
-            mIsDeferring = false;
-        }
-    }
-
-    /**
-     * Get deferring time before turning off WiFi.
-     */
-    private int getWifiOffDeferringTimeMs() {
-        SubscriptionManager subscriptionManager = (SubscriptionManager) mContext.getSystemService(
-                Context.TELEPHONY_SUBSCRIPTION_SERVICE);
-        if (subscriptionManager == null) {
-            Log.d(TAG, "SubscriptionManager not found");
-            return 0;
-        }
-
-        List<SubscriptionInfo> subInfoList = subscriptionManager.getActiveSubscriptionInfoList();
-        if (subInfoList == null) {
-            Log.d(TAG, "Active SubscriptionInfo list not found");
-            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) {
-            Log.d(TAG, "Invalid Subscription ID: " + subId);
-            return 0;
-        }
-
-        ImsMmTelManager imsMmTelManager = ImsMmTelManager.createForSubscriptionId(subId);
-        // If no wifi calling, no delay
-        if (!imsMmTelManager.isAvailable(
-                    MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
-                    ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN)) {
-            Log.d(TAG, "IMS not registered over IWLAN for subId: " + subId);
-            return 0;
-        }
-
-        CarrierConfigManager configManager =
-                (CarrierConfigManager) mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        PersistableBundle config = configManager.getConfigForSubId(subId);
-        return (config != null)
-                ? config.getInt(CarrierConfigManager.Ims.KEY_WIFI_OFF_DEFERRING_TIME_MILLIS_INT)
-                : 0;
-    }
-
-    @Override
-    public @Role int getRole() {
-        return mRole;
-    }
-
-    @Override
-    public void setRole(@Role int role) {
-        Preconditions.checkState(CLIENT_ROLES.contains(role));
-        if (role == ROLE_CLIENT_SCAN_ONLY) {
-            mTargetRole = role;
-            // Switch client mode manager to scan only mode.
-            mStateMachine.sendMessage(ClientModeStateMachine.CMD_SWITCH_TO_SCAN_ONLY_MODE);
-        } else if (CLIENT_CONNECTIVITY_ROLES.contains(role)) {
-            mTargetRole = role;
-            // Switch client mode manager to connect mode.
-            mStateMachine.sendMessage(ClientModeStateMachine.CMD_SWITCH_TO_CONNECT_MODE, role);
-        }
-    }
-
-    /**
-     * Dump info about this ClientMode manager.
-     */
-    @Override
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("--Dump of ClientModeManager--");
-
-        pw.println("current StateMachine mode: " + getCurrentStateName());
-        pw.println("mRole: " + mRole);
-        pw.println("mTargetRole: " + mTargetRole);
-        pw.println("mClientInterfaceName: " + mClientInterfaceName);
-        pw.println("mIfaceIsUp: " + mIfaceIsUp);
-        mStateMachine.dump(fd, pw, args);
-    }
-
-    private String getCurrentStateName() {
-        IState currentState = mStateMachine.getCurrentState();
-
-        if (currentState != null) {
-            return currentState.getName();
-        }
-
-        return "StateMachine not active";
-    }
-
-    /**
-     * Update Wifi state and send the broadcast.
-     * @param newState new Wifi state
-     * @param currentState current wifi state
-     */
-    private void updateConnectModeState(int newState, int currentState) {
-        if (newState == WifiManager.WIFI_STATE_UNKNOWN) {
-            // do not need to broadcast failure to system
-            return;
-        }
-        if (mRole != ROLE_CLIENT_PRIMARY) {
-            // do not raise public broadcast unless this is the primary client mode manager
-            return;
-        }
-
-        mClientModeImpl.setWifiStateForApiCalls(newState);
-
-        final Intent intent = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        intent.putExtra(WifiManager.EXTRA_WIFI_STATE, newState);
-        intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_STATE, currentState);
-        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
-    }
-
-    private class ClientModeStateMachine extends StateMachine {
-        // Commands for the state machine.
-        public static final int CMD_START = 0;
-        public static final int CMD_SWITCH_TO_SCAN_ONLY_MODE = 1;
-        public static final int CMD_SWITCH_TO_CONNECT_MODE = 2;
-        public static final int CMD_INTERFACE_STATUS_CHANGED = 3;
-        public static final int CMD_INTERFACE_DESTROYED = 4;
-        public static final int CMD_INTERFACE_DOWN = 5;
-        public static final int CMD_SWITCH_TO_SCAN_ONLY_MODE_CONTINUE = 6;
-        private final State mIdleState = new IdleState();
-        private final State mStartedState = new StartedState();
-        private final State mScanOnlyModeState = new ScanOnlyModeState();
-        private final State mConnectModeState = new ConnectModeState();
-
-        private final InterfaceCallback mWifiNativeInterfaceCallback = new InterfaceCallback() {
-            @Override
-            public void onDestroyed(String ifaceName) {
-                if (mClientInterfaceName != null && mClientInterfaceName.equals(ifaceName)) {
-                    Log.d(TAG, "STA iface " + ifaceName + " was destroyed, stopping client mode");
-
-                    // we must immediately clean up state in ClientModeImpl to unregister
-                    // all client mode related objects
-                    // Note: onDestroyed is only called from the main Wifi thread
-                    mClientModeImpl.handleIfaceDestroyed();
-
-                    sendMessage(CMD_INTERFACE_DESTROYED);
-                }
-            }
-
-            @Override
-            public void onUp(String ifaceName) {
-                if (mClientInterfaceName != null && mClientInterfaceName.equals(ifaceName)) {
-                    sendMessage(CMD_INTERFACE_STATUS_CHANGED, 1);
-                }
-            }
-
-            @Override
-            public void onDown(String ifaceName) {
-                if (mClientInterfaceName != null && mClientInterfaceName.equals(ifaceName)) {
-                    sendMessage(CMD_INTERFACE_STATUS_CHANGED, 0);
-                }
-            }
-        };
-
-        ClientModeStateMachine(Looper looper) {
-            super(TAG, looper);
-
-            // CHECKSTYLE:OFF IndentationCheck
-            addState(mIdleState);
-            addState(mStartedState);
-                addState(mScanOnlyModeState, mStartedState);
-                addState(mConnectModeState, mStartedState);
-            // CHECKSTYLE:ON IndentationCheck
-
-            setInitialState(mIdleState);
-            start();
-        }
-
-        private class IdleState extends State {
-            @Override
-            public void enter() {
-                Log.d(TAG, "entering IdleState");
-                mClientInterfaceName = null;
-                mIfaceIsUp = false;
-            }
-
-            @Override
-            public boolean processMessage(Message message) {
-                switch (message.what) {
-                    case CMD_START:
-                        // Always start in scan mode first.
-                        mClientInterfaceName =
-                                mWifiNative.setupInterfaceForClientInScanMode(
-                                mWifiNativeInterfaceCallback);
-                        if (TextUtils.isEmpty(mClientInterfaceName)) {
-                            Log.e(TAG, "Failed to create ClientInterface. Sit in Idle");
-                            mModeListener.onStartFailure();
-                            break;
-                        }
-                        transitionTo(mScanOnlyModeState);
-                        break;
-                    default:
-                        Log.d(TAG, "received an invalid message: " + message);
-                        return NOT_HANDLED;
-                }
-                return HANDLED;
-            }
-        }
-
-        private class StartedState extends State {
-
-            private void onUpChanged(boolean isUp) {
-                if (isUp == mIfaceIsUp) {
-                    return;  // no change
-                }
-                mIfaceIsUp = isUp;
-                if (!isUp) {
-                    // if the interface goes down we should exit and go back to idle state.
-                    Log.d(TAG, "interface down!");
-                    mStateMachine.sendMessage(CMD_INTERFACE_DOWN);
-                }
-            }
-
-            @Override
-            public void enter() {
-                Log.d(TAG, "entering StartedState");
-                mIfaceIsUp = false;
-                onUpChanged(mWifiNative.isInterfaceUp(mClientInterfaceName));
-            }
-
-            @Override
-            public boolean processMessage(Message message) {
-                switch(message.what) {
-                    case CMD_START:
-                        // Already started, ignore this command.
-                        break;
-                    case CMD_SWITCH_TO_CONNECT_MODE:
-                        mRole = message.arg1; // could be any one of possible connect mode roles.
-                        updateConnectModeState(WifiManager.WIFI_STATE_ENABLING,
-                                WifiManager.WIFI_STATE_DISABLED);
-                        if (!mWifiNative.switchClientInterfaceToConnectivityMode(
-                                mClientInterfaceName)) {
-                            updateConnectModeState(WifiManager.WIFI_STATE_UNKNOWN,
-                                    WifiManager.WIFI_STATE_ENABLING);
-                            updateConnectModeState(WifiManager.WIFI_STATE_DISABLED,
-                                    WifiManager.WIFI_STATE_UNKNOWN);
-                            mModeListener.onStartFailure();
-                            break;
-                        }
-                        transitionTo(mConnectModeState);
-                        break;
-                    case CMD_SWITCH_TO_SCAN_ONLY_MODE:
-                        updateConnectModeState(WifiManager.WIFI_STATE_DISABLING,
-                                WifiManager.WIFI_STATE_ENABLED);
-                        mDeferStopHandler.start(getWifiOffDeferringTimeMs());
-                        break;
-                    case CMD_SWITCH_TO_SCAN_ONLY_MODE_CONTINUE:
-                        transitionTo(mScanOnlyModeState);
-                        break;
-                    case CMD_INTERFACE_DOWN:
-                        Log.e(TAG, "Detected an interface down, reporting failure to "
-                                + "SelfRecovery");
-                        mClientModeImpl.failureDetected(SelfRecovery.REASON_STA_IFACE_DOWN);
-                        transitionTo(mIdleState);
-                        break;
-                    case CMD_INTERFACE_STATUS_CHANGED:
-                        boolean isUp = message.arg1 == 1;
-                        onUpChanged(isUp);
-                        break;
-                    case CMD_INTERFACE_DESTROYED:
-                        Log.d(TAG, "interface destroyed - client mode stopping");
-                        mClientInterfaceName = null;
-                        transitionTo(mIdleState);
-                        break;
-                    default:
-                        return NOT_HANDLED;
-                }
-                return HANDLED;
-            }
-
-            /**
-             * Clean up state, unregister listeners and update wifi state.
-             */
-            @Override
-            public void exit() {
-                mClientModeImpl.setOperationalMode(ClientModeImpl.DISABLED_MODE, null);
-
-                if (mClientInterfaceName != null) {
-                    mWifiNative.teardownInterface(mClientInterfaceName);
-                    mClientInterfaceName = null;
-                    mIfaceIsUp = false;
-                }
-
-                // once we leave started, nothing else to do...  stop the state machine
-                mRole = ROLE_UNSPECIFIED;
-                mStateMachine.quitNow();
-                mModeListener.onStopped();
-            }
-        }
-
-        private class ScanOnlyModeState extends State {
-            @Override
-            public void enter() {
-                Log.d(TAG, "entering ScanOnlyModeState");
-                mClientModeImpl.setOperationalMode(ClientModeImpl.SCAN_ONLY_MODE,
-                        mClientInterfaceName);
-                mRole = ROLE_CLIENT_SCAN_ONLY;
-                mModeListener.onStarted();
-
-                // Inform sar manager that scan only is being enabled
-                mSarManager.setScanOnlyWifiState(WifiManager.WIFI_STATE_ENABLED);
-                mWakeupController.start();
-            }
-
-            @Override
-            public boolean processMessage(Message message) {
-                switch (message.what) {
-                    case CMD_SWITCH_TO_SCAN_ONLY_MODE:
-                        // Already in scan only mode, ignore this command.
-                        break;
-                    default:
-                        return NOT_HANDLED;
-                }
-                return HANDLED;
-            }
-
-            @Override
-            public void exit() {
-                // Inform sar manager that scan only is being disabled
-                mSarManager.setScanOnlyWifiState(WifiManager.WIFI_STATE_DISABLED);
-                mWakeupController.stop();
-            }
-        }
-
-        private class ConnectModeState extends State {
-            @Override
-            public void enter() {
-                Log.d(TAG, "entering ConnectModeState");
-                mClientModeImpl.setOperationalMode(ClientModeImpl.CONNECT_MODE,
-                        mClientInterfaceName);
-                mModeListener.onStarted();
-                updateConnectModeState(WifiManager.WIFI_STATE_ENABLED,
-                        WifiManager.WIFI_STATE_ENABLING);
-
-                // Inform sar manager that wifi is Enabled
-                mSarManager.setClientWifiState(WifiManager.WIFI_STATE_ENABLED);
-            }
-
-            @Override
-            public boolean processMessage(Message message) {
-                switch (message.what) {
-                    case CMD_SWITCH_TO_CONNECT_MODE:
-                        int newRole = message.arg1;
-                        // Already in connect mode, only switching the connectivity roles.
-                        if (newRole != mRole) {
-                            mRole = newRole;
-                            mModeListener.onStarted();
-                        }
-                        break;
-                    case CMD_SWITCH_TO_SCAN_ONLY_MODE:
-                        updateConnectModeState(WifiManager.WIFI_STATE_DISABLING,
-                                WifiManager.WIFI_STATE_ENABLED);
-                        return NOT_HANDLED; // Handled in StartedState.
-                    case CMD_INTERFACE_DOWN:
-                        updateConnectModeState(WifiManager.WIFI_STATE_DISABLING,
-                                WifiManager.WIFI_STATE_UNKNOWN);
-                        return NOT_HANDLED; // Handled in StartedState.
-                    case CMD_INTERFACE_STATUS_CHANGED:
-                        boolean isUp = message.arg1 == 1;
-                        if (isUp == mIfaceIsUp) {
-                            break;  // no change
-                        }
-                        if (!isUp) {
-                            if (!mClientModeImpl.isConnectedMacRandomizationEnabled()) {
-                                // Handle the error case where our underlying interface went down if
-                                // we do not have mac randomization enabled (b/72459123).
-                                // if the interface goes down we should exit and go back to idle
-                                // state.
-                                updateConnectModeState(WifiManager.WIFI_STATE_UNKNOWN,
-                                        WifiManager.WIFI_STATE_ENABLED);
-                            } else {
-                                return HANDLED; // For MAC randomization, ignore...
-                            }
-                        }
-                        return NOT_HANDLED; // Handled in StartedState.
-                    case CMD_INTERFACE_DESTROYED:
-                        updateConnectModeState(WifiManager.WIFI_STATE_DISABLING,
-                                WifiManager.WIFI_STATE_ENABLED);
-                        return NOT_HANDLED; // Handled in StartedState.
-                    default:
-                        return NOT_HANDLED;
-                }
-                return HANDLED;
-            }
-
-            @Override
-            public void exit() {
-                updateConnectModeState(WifiManager.WIFI_STATE_DISABLED,
-                        WifiManager.WIFI_STATE_DISABLING);
-
-                // Inform sar manager that wifi is being disabled
-                mSarManager.setClientWifiState(WifiManager.WIFI_STATE_DISABLED);
-            }
-        }
-    }
+    ClientRole getRole();
 }
diff --git a/service/java/com/android/server/wifi/ClientModeManagerBroadcastQueue.java b/service/java/com/android/server/wifi/ClientModeManagerBroadcastQueue.java
new file mode 100644
index 0000000..668ec5f
--- /dev/null
+++ b/service/java/com/android/server/wifi/ClientModeManagerBroadcastQueue.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_PRIMARY;
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SCAN_ONLY;
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SECONDARY_TRANSIENT;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.net.NetworkInfo;
+import android.util.ArrayMap;
+
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Used to buffer public broadcasts when multiple concurrent client interfaces are active to
+ * preserve legacy behavior expected by apps when there is a single client interface active.
+ */
+public class ClientModeManagerBroadcastQueue {
+
+    private static final String TAG = "WifiBroadcastQueue";
+
+    private final ActiveModeWarden mActiveModeWarden;
+    private final Context mContext;
+    /** List of buffered broadcasts, per-ClientModeManager. */
+    private final Map<ClientModeManager, List<QueuedBroadcast>> mBufferedBroadcasts =
+            new ArrayMap<>();
+
+    /** Lambda representing a broadcast to be sent. */
+    public interface QueuedBroadcast {
+        /** Send the broadcast using one of the many different Context#send* implementations. */
+        void send();
+    }
+
+    private boolean mVerboseLoggingEnabled = false;
+
+    public ClientModeManagerBroadcastQueue(@NonNull ActiveModeWarden activeModeWarden,
+            @NonNull Context context) {
+        mActiveModeWarden = activeModeWarden;
+        mContext = context;
+
+        mActiveModeWarden.registerModeChangeCallback(new ModeChangeCallback());
+        mActiveModeWarden.registerPrimaryClientModeManagerChangedCallback(
+                new PrimaryClientModeManagerChangedCallback());
+    }
+
+    public void setVerboseLoggingEnabled(boolean verboseLoggingEnabled) {
+        mVerboseLoggingEnabled = verboseLoggingEnabled;
+    }
+
+    /**
+     * If the ClientModeManager is primary or scan only, the broadcast will be sent immediately.
+     * Otherwise, the broadcast will be queued, and sent out if and when the ClientModeManager
+     * becomes primary.
+     */
+    public void queueOrSendBroadcast(
+            @NonNull ClientModeManager manager,
+            @NonNull QueuedBroadcast broadcast) {
+
+        if (manager.getRole() == ROLE_CLIENT_PRIMARY
+                || manager.getRole() == ROLE_CLIENT_SCAN_ONLY) {
+            // Primary or scan only, send existing queued broadcasts and send the new broadcast
+            // immediately. Assume that queue is empty for this manager (flushed when it originally
+            // became primary).
+            // TODO: b/192612399 - look into the race issue causing the ClientModeManager to be
+            // already in ROLE_CLIENT_SCAN_ONLY when ClientModeImpl sends the broadcast.
+            broadcast.send();
+        } else if (manager.getRole() == ROLE_CLIENT_SECONDARY_TRANSIENT) {
+            // buffer the broadcast until the ClientModeManager becomes primary.
+            mBufferedBroadcasts
+                    .computeIfAbsent(manager, k -> new ArrayList<>())
+                    .add(broadcast);
+        }
+        // for all other roles, they will never become primary, so discard their broadcasts
+    }
+
+    private void sendAllBroadcasts(ClientModeManager manager) {
+        List<QueuedBroadcast> queuedBroadcasts = mBufferedBroadcasts.getOrDefault(
+                manager, Collections.emptyList());
+        for (QueuedBroadcast broadcast : queuedBroadcasts) {
+            broadcast.send();
+        }
+        // clear the sent broadcasts
+        clearQueue(manager);
+    }
+
+    /**
+     * Clear the broadcast queue for the given manager when e.g. the Make-Before-Break attempt
+     * fails, or the ClientModeManager is deleted.
+     *
+     * TODO(b/174041877): Call this when connection fails during Make Before Break
+     */
+    public void clearQueue(@NonNull ClientModeManager manager) {
+        mBufferedBroadcasts.remove(manager);
+    }
+
+    /**
+     * Send broadcasts to fake the disconnection of the previous network, since apps expect there
+     * to be only one connection at a time.
+     */
+    public void fakeDisconnectionBroadcasts() {
+        ClientModeImpl.sendNetworkChangeBroadcast(
+                mContext, NetworkInfo.DetailedState.DISCONNECTED, mVerboseLoggingEnabled);
+    }
+
+    private class PrimaryClientModeManagerChangedCallback
+            implements ActiveModeWarden.PrimaryClientModeManagerChangedCallback {
+
+        @Override
+        public void onChange(
+                @Nullable ConcreteClientModeManager prevPrimaryClientModeManager,
+                @Nullable ConcreteClientModeManager newPrimaryClientModeManager) {
+            if (newPrimaryClientModeManager == null) {
+                return;
+            }
+            // when the a ClientModeManager becomes primary, send all its queued broadcasts
+            sendAllBroadcasts(newPrimaryClientModeManager);
+        }
+    }
+
+    private class ModeChangeCallback implements ActiveModeWarden.ModeChangeCallback {
+
+        @Override
+        public void onActiveModeManagerAdded(@NonNull ActiveModeManager activeModeManager) {
+            // no-op
+        }
+
+        @Override
+        public void onActiveModeManagerRoleChanged(@NonNull ActiveModeManager activeModeManager) {
+            // no-op
+        }
+
+        @Override
+        public void onActiveModeManagerRemoved(@NonNull ActiveModeManager activeModeManager) {
+            if (!(activeModeManager instanceof ClientModeManager)) {
+                return;
+            }
+            ClientModeManager clientModeManager = (ClientModeManager) activeModeManager;
+            clearQueue(clientModeManager);
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/Clock.java b/service/java/com/android/server/wifi/Clock.java
index b1d4596..63e958b 100644
--- a/service/java/com/android/server/wifi/Clock.java
+++ b/service/java/com/android/server/wifi/Clock.java
@@ -17,9 +17,13 @@
 package com.android.server.wifi;
 
 import android.os.SystemClock;
+
+import javax.annotation.concurrent.ThreadSafe;
+
 /**
  * Wrapper class for time operations. Allows replacement of clock operations for testing.
  */
+@ThreadSafe
 public class Clock {
 
     /**
diff --git a/service/java/com/android/server/wifi/ConcreteClientModeManager.java b/service/java/com/android/server/wifi/ConcreteClientModeManager.java
new file mode 100644
index 0000000..03b20f9
--- /dev/null
+++ b/service/java/com/android/server/wifi/ConcreteClientModeManager.java
@@ -0,0 +1,1421 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static android.net.wifi.WifiManager.WIFI_STATE_DISABLED;
+import static android.net.wifi.WifiManager.WIFI_STATE_DISABLING;
+import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED;
+import static android.net.wifi.WifiManager.WIFI_STATE_ENABLING;
+import static android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.DhcpResultsParcelable;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.wifi.IWifiConnectedNetworkScorer;
+import android.net.wifi.WifiAnnotations;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.net.wifi.hotspot2.IProvisioningCallback;
+import android.net.wifi.hotspot2.OsuProvider;
+import android.net.wifi.nl80211.DeviceWiphyCapabilities;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.os.UserHandle;
+import android.os.WorkSource;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.ims.ImsException;
+import android.telephony.ims.ImsMmTelManager;
+import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.RegistrationManager;
+import android.telephony.ims.feature.MmTelFeature;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.util.IState;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.modules.utils.HandlerExecutor;
+import com.android.server.wifi.WifiNative.InterfaceCallback;
+import com.android.server.wifi.WifiNative.RxFateReport;
+import com.android.server.wifi.WifiNative.TxFateReport;
+import com.android.server.wifi.util.ActionListenerWrapper;
+import com.android.server.wifi.util.StateMachineObituary;
+import com.android.server.wifi.util.WifiHandler;
+import com.android.wifi.resources.R;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayDeque;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Manage WiFi in Client Mode where we connect to configured networks and in Scan Only Mode where
+ * we do not connect to configured networks but do perform scanning.
+ *
+ * An instance of this class is active to manage each client interface. This is in contrast to
+ * {@link DefaultClientModeManager} which handles calls when no client interfaces are active.
+ *
+ * This class will dynamically instantiate {@link ClientModeImpl} when it enters client mode, and
+ * tear it down when it exits client mode. No instance of ClientModeImpl will be active in
+ * scan-only mode, instead {@link ScanOnlyModeImpl} will be used to respond to calls.
+ *
+ * <pre>
+ *                                           ActiveModeWarden
+ *                                      /                        \
+ *                                     /                          \
+ *                        ConcreteClientModeManager         DefaultClientModeManager
+ *                      (Client Mode + Scan Only Mode)            (Wifi off)
+ *                             /            \
+ *                           /               \
+ *                     ClientModeImpl       ScanOnlyModeImpl
+ * </pre>
+ */
+public class ConcreteClientModeManager implements ClientModeManager {
+    private static final String TAG = "WifiClientModeManager";
+
+    private final ClientModeStateMachine mStateMachine;
+
+    private final Context mContext;
+    private final Clock mClock;
+    private final WifiNative mWifiNative;
+    private final WifiMetrics mWifiMetrics;
+    private final WakeupController mWakeupController;
+    private final WifiInjector mWifiInjector;
+    private final SelfRecovery mSelfRecovery;
+    private final WifiGlobals mWifiGlobals;
+    private final DefaultClientModeManager mDefaultClientModeManager;
+    private final ClientModeManagerBroadcastQueue mBroadcastQueue;
+    private final long mId;
+    private final Graveyard mGraveyard = new Graveyard();
+
+    private String mClientInterfaceName;
+    private boolean mIfaceIsUp = false;
+    private boolean mShouldReduceNetworkScore = false;
+    private final DeferStopHandler mDeferStopHandler;
+    @Nullable
+    private ClientRole mRole = null;
+    @Nullable
+    private ClientRole mPreviousRole = null;
+    private long mLastRoleChangeSinceBootMs = 0;
+    @Nullable
+    private WorkSource mRequestorWs = null;
+    @NonNull
+    private Listener<ConcreteClientModeManager> mModeListener;
+    /** Caches the latest role change request. This is needed for the IMS dereg delay */
+    @Nullable
+    private RoleChangeInfo mTargetRoleChangeInfo;
+    private boolean mVerboseLoggingEnabled = false;
+    private int mActiveSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    private boolean mWifiStateChangeBroadcastEnabled = true;
+    /**
+     * mClientModeImpl is only non-null when in {@link ClientModeStateMachine.ConnectModeState} -
+     * it will be null in all other states
+     */
+    @Nullable
+    private ClientModeImpl mClientModeImpl = null;
+
+    @Nullable
+    private ScanOnlyModeImpl mScanOnlyModeImpl = null;
+
+    /**
+     * One of  {@link WifiManager#WIFI_STATE_DISABLED},
+     * {@link WifiManager#WIFI_STATE_DISABLING},
+     * {@link WifiManager#WIFI_STATE_ENABLED},
+     * {@link WifiManager#WIFI_STATE_ENABLING},
+     * {@link WifiManager#WIFI_STATE_UNKNOWN}
+     */
+    private final AtomicInteger mWifiState = new AtomicInteger(WIFI_STATE_DISABLED);
+
+    private boolean mIsStopped = true;
+
+    ConcreteClientModeManager(
+            Context context, @NonNull Looper looper, Clock clock,
+            WifiNative wifiNative, @NonNull Listener<ConcreteClientModeManager> listener,
+            WifiMetrics wifiMetrics,
+            WakeupController wakeupController, WifiInjector wifiInjector,
+            SelfRecovery selfRecovery, WifiGlobals wifiGlobals,
+            DefaultClientModeManager defaultClientModeManager, long id,
+            @NonNull WorkSource requestorWs, @NonNull ClientRole role,
+            @NonNull ClientModeManagerBroadcastQueue broadcastQueue,
+            boolean verboseLoggingEnabled) {
+        mContext = context;
+        mClock = clock;
+        mWifiNative = wifiNative;
+        mModeListener = listener;
+        mWifiMetrics = wifiMetrics;
+        mWakeupController = wakeupController;
+        mWifiInjector = wifiInjector;
+        mStateMachine = new ClientModeStateMachine(looper);
+        mDeferStopHandler = new DeferStopHandler(looper);
+        mSelfRecovery = selfRecovery;
+        mWifiGlobals = wifiGlobals;
+        mDefaultClientModeManager = defaultClientModeManager;
+        mId = id;
+        mTargetRoleChangeInfo = new RoleChangeInfo(role, requestorWs, listener);
+        mBroadcastQueue = broadcastQueue;
+        enableVerboseLogging(verboseLoggingEnabled);
+        mStateMachine.sendMessage(ClientModeStateMachine.CMD_START, mTargetRoleChangeInfo);
+    }
+
+    private String getTag() {
+        return TAG + "[" + (mClientInterfaceName == null ? "unknown" : mClientInterfaceName) + "]";
+    }
+
+    /**
+     * Sets whether to send WIFI_STATE_CHANGED broadcast for this ClientModeManager.
+     * @param enabled
+     */
+    public void setWifiStateChangeBroadcastEnabled(boolean enabled) {
+        mWifiStateChangeBroadcastEnabled = enabled;
+    }
+
+    /**
+     * Disconnect from any currently connected networks and stop client mode.
+     */
+    @Override
+    public void stop() {
+        Log.d(getTag(), " currentstate: " + getCurrentStateName());
+        mTargetRoleChangeInfo = null;
+        if (mIfaceIsUp) {
+            updateConnectModeState(mRole, WifiManager.WIFI_STATE_DISABLING,
+                    WifiManager.WIFI_STATE_ENABLED);
+        } else {
+            updateConnectModeState(mRole, WifiManager.WIFI_STATE_DISABLING,
+                    WifiManager.WIFI_STATE_ENABLING);
+        }
+        mDeferStopHandler.start(getWifiOffDeferringTimeMs());
+    }
+
+    private class DeferStopHandler extends WifiHandler {
+        private boolean mIsDeferring = false;
+        private ImsMmTelManager mImsMmTelManager = null;
+        private Looper mLooper = null;
+        private final Runnable mRunnable = () -> continueToStopWifi();
+        private int mMaximumDeferringTimeMillis = 0;
+        private long mDeferringStartTimeMillis = 0;
+        private ConnectivityManager mConnectivityManager = null;
+        private boolean mIsImsNetworkLost = false;
+        private boolean mIsImsNetworkUnregistered = false;
+
+        private final RegistrationManager.RegistrationCallback mImsRegistrationCallback =
+                new RegistrationManager.RegistrationCallback() {
+                    @Override
+                    public void onRegistered(int imsRadioTech) {
+                        Log.d(getTag(), "on IMS registered on type " + imsRadioTech);
+                        if (!mIsDeferring) return;
+
+                        if (imsRadioTech != AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
+                            continueToStopWifi();
+                        }
+                    }
+
+                    @Override
+                    public void onUnregistered(ImsReasonInfo imsReasonInfo) {
+                        Log.d(getTag(), "on IMS unregistered");
+                        mIsImsNetworkUnregistered = true;
+                        checkAndContinueToStopWifi();
+                    }
+                };
+
+        private final class ImsNetworkCallback extends NetworkCallback {
+            private int mRegisteredImsNetworkCount = 0;
+
+            @Override
+            public void onAvailable(Network network) {
+                synchronized (this) {
+                    Log.d(getTag(), "IMS network available: " + network);
+                    mRegisteredImsNetworkCount++;
+                }
+            }
+
+            @Override
+            public void onLost(Network network) {
+                synchronized (this) {
+                    Log.d(getTag(), "IMS network lost: " + network
+                            + " ,isDeferring: " + mIsDeferring
+                            + " ,registered IMS network count: " + mRegisteredImsNetworkCount);
+                    mRegisteredImsNetworkCount--;
+                    if (mIsDeferring && mRegisteredImsNetworkCount <= 0) {
+                        mRegisteredImsNetworkCount = 0;
+                        mIsImsNetworkLost = true;
+                        checkAndContinueToStopWifi();
+                    }
+                }
+            }
+        }
+
+        private NetworkCallback mImsNetworkCallback = null;
+
+        DeferStopHandler(Looper looper) {
+            super(TAG, looper);
+            mLooper = looper;
+        }
+
+        public void start(int delayMs) {
+            if (mIsDeferring) return;
+
+            mMaximumDeferringTimeMillis = delayMs;
+            mDeferringStartTimeMillis = mClock.getElapsedSinceBootMillis();
+            // Most cases don't need delay, check it first to avoid unnecessary work.
+            if (delayMs == 0) {
+                continueToStopWifi();
+                return;
+            }
+
+            mImsMmTelManager = ImsMmTelManager.createForSubscriptionId(mActiveSubId);
+            if (mImsMmTelManager == null || !postDelayed(mRunnable, delayMs)) {
+                // if no delay or failed to add runnable, stop Wifi immediately.
+                continueToStopWifi();
+                return;
+            }
+
+            mIsDeferring = true;
+            Log.d(getTag(), "Start DeferWifiOff handler with deferring time "
+                    + delayMs + " ms for subId: " + mActiveSubId);
+            try {
+                mImsMmTelManager.registerImsRegistrationCallback(
+                        new HandlerExecutor(new Handler(mLooper)),
+                        mImsRegistrationCallback);
+            } catch (RuntimeException | ImsException e) {
+                Log.e(getTag(), "registerImsRegistrationCallback failed", e);
+                continueToStopWifi();
+                return;
+            }
+
+            NetworkRequest imsRequest = new NetworkRequest.Builder()
+                    .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
+                    .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                    .build();
+
+            mConnectivityManager =
+                    (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+
+            mImsNetworkCallback = new ImsNetworkCallback();
+            mConnectivityManager.registerNetworkCallback(imsRequest, mImsNetworkCallback,
+                    new Handler(mLooper));
+        }
+
+        private void checkAndContinueToStopWifi() {
+            if (mIsImsNetworkLost && mIsImsNetworkUnregistered) {
+                // 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();
+                }
+            }
+        }
+
+        private void continueToStopWifi() {
+            Log.d(getTag(), "The target role change info " + mTargetRoleChangeInfo);
+
+            int deferringDurationMillis =
+                    (int) (mClock.getElapsedSinceBootMillis() - mDeferringStartTimeMillis);
+            boolean isTimedOut = mMaximumDeferringTimeMillis > 0
+                    && deferringDurationMillis >= mMaximumDeferringTimeMillis;
+            if (mTargetRoleChangeInfo == null) {
+                Log.d(getTag(), "Continue to stop wifi");
+                mStateMachine.captureObituaryAndQuitNow();
+                mWifiMetrics.noteWifiOff(mIsDeferring, isTimedOut, deferringDurationMillis);
+            } else if (mTargetRoleChangeInfo.role == ROLE_CLIENT_SCAN_ONLY) {
+                if (!mWifiNative.switchClientInterfaceToScanMode(
+                        mClientInterfaceName, mTargetRoleChangeInfo.requestorWs)) {
+                    mModeListener.onStartFailure(ConcreteClientModeManager.this);
+                } else {
+                    mStateMachine.sendMessage(
+                            ClientModeStateMachine.CMD_SWITCH_TO_SCAN_ONLY_MODE_CONTINUE,
+                            mTargetRoleChangeInfo);
+                    mWifiMetrics.noteWifiOff(mIsDeferring, isTimedOut, deferringDurationMillis);
+                }
+            } else {
+                updateConnectModeState(mRole, WifiManager.WIFI_STATE_ENABLED,
+                        WifiManager.WIFI_STATE_DISABLING);
+            }
+
+            if (!mIsDeferring) return;
+
+            Log.d(getTag(), "Stop DeferWifiOff handler.");
+            removeCallbacks(mRunnable);
+            if (mImsMmTelManager != null) {
+                try {
+                    mImsMmTelManager.unregisterImsRegistrationCallback(mImsRegistrationCallback);
+                } catch (RuntimeException e) {
+                    Log.e(getTag(), "unregisterImsRegistrationCallback failed", e);
+                }
+            }
+
+            if (mConnectivityManager != null && mImsNetworkCallback != null) {
+                mConnectivityManager.unregisterNetworkCallback(mImsNetworkCallback);
+                mImsNetworkCallback = null;
+            }
+
+            mIsDeferring = false;
+            mIsImsNetworkLost = false;
+            mIsImsNetworkUnregistered = false;
+        }
+    }
+
+    /**
+     * Get deferring time before turning off WiFi.
+     */
+    private int getWifiOffDeferringTimeMs() {
+        SubscriptionManager subscriptionManager = (SubscriptionManager) mContext.getSystemService(
+                Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+        if (subscriptionManager == null) {
+            Log.d(getTag(), "SubscriptionManager not found");
+            return 0;
+        }
+
+        List<SubscriptionInfo> subInfoList = subscriptionManager.getActiveSubscriptionInfoList();
+        if (subInfoList == null) {
+            Log.d(getTag(), "Active SubscriptionInfo list not found");
+            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) {
+            Log.d(getTag(), "Invalid Subscription ID: " + subId);
+            return 0;
+        }
+
+        ImsMmTelManager imsMmTelManager = ImsMmTelManager.createForSubscriptionId(subId);
+        // If no wifi calling, no delay
+        try {
+            if (!imsMmTelManager.isAvailable(
+                    MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
+                    ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN)) {
+                Log.d(getTag(), "IMS not registered over IWLAN for subId: " + subId);
+                return 0;
+            }
+        } catch (RuntimeException ex) {
+            Log.e(TAG, "IMS Manager is not available.", ex);
+            return 0;
+        }
+
+        CarrierConfigManager configManager =
+                (CarrierConfigManager) mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        PersistableBundle config = configManager.getConfigForSubId(subId);
+        return (config != null)
+                ? config.getInt(CarrierConfigManager.Ims.KEY_WIFI_OFF_DEFERRING_TIME_MILLIS_INT)
+                : 0;
+    }
+
+    @Override
+    @Nullable public ClientRole getRole() {
+        return mRole;
+    }
+
+    @Override
+    @Nullable public ClientRole getPreviousRole() {
+        return mPreviousRole;
+    }
+
+    @Override
+    public long getLastRoleChangeSinceBootMs() {
+        return mLastRoleChangeSinceBootMs;
+    }
+
+    /**
+     * Class to hold info needed for role change.
+     */
+    private static class RoleChangeInfo {
+        @Nullable public final ClientRole role;
+        @Nullable public final WorkSource requestorWs;
+        @Nullable public final Listener<ConcreteClientModeManager> modeListener;
+
+        RoleChangeInfo(@Nullable ClientRole role) {
+            this(role, null, null);
+        }
+
+        RoleChangeInfo(@Nullable ClientRole role, @Nullable WorkSource requestorWs,
+                @Nullable Listener<ConcreteClientModeManager> modeListener) {
+            this.role = role;
+            this.requestorWs = requestorWs;
+            this.modeListener = modeListener;
+        }
+
+        @Override
+        public String toString() {
+            return "Role: " + role + ", RequestorWs: " + requestorWs
+                    + ", ModeListener: " + modeListener;
+        }
+    }
+
+    /** Set the role of this ClientModeManager */
+    public void setRole(@NonNull ClientRole role, @NonNull WorkSource requestorWs) {
+        setRole(role, requestorWs, null);
+    }
+
+    /** Set the role of this ClientModeManager */
+    public void setRole(@NonNull ClientRole role, @NonNull WorkSource requestorWs,
+            @Nullable Listener<ConcreteClientModeManager> modeListener) {
+        mTargetRoleChangeInfo = new RoleChangeInfo(role, requestorWs, modeListener);
+        if (role == ROLE_CLIENT_SCAN_ONLY) {
+            // Switch client mode manager to scan only mode.
+            mStateMachine.sendMessage(
+                    ClientModeStateMachine.CMD_SWITCH_TO_SCAN_ONLY_MODE);
+        } else {
+            // Switch client mode manager to connect mode.
+            mStateMachine.sendMessage(
+                    ClientModeStateMachine.CMD_SWITCH_TO_CONNECT_MODE,
+                    mTargetRoleChangeInfo);
+        }
+    }
+
+    @Override
+    public String getInterfaceName() {
+        return mClientInterfaceName;
+    }
+
+    @Override
+    public WorkSource getRequestorWs() {
+        return mRequestorWs;
+    }
+
+    /**
+     * Keep stopped {@link ClientModeImpl} instances so that they can be dumped to aid debugging.
+     *
+     * TODO(b/160283853): Find a smarter way to evict old ClientModeImpls
+     */
+    private static class Graveyard {
+        private static final int INSTANCES_TO_KEEP = 3;
+
+        private final ArrayDeque<ClientModeImpl> mClientModeImpls = new ArrayDeque<>();
+
+        /**
+         * Add this stopped {@link ClientModeImpl} to the graveyard, and evict the oldest
+         * ClientModeImpl if the graveyard is full.
+         */
+        void inter(ClientModeImpl clientModeImpl) {
+            if (mClientModeImpls.size() == INSTANCES_TO_KEEP) {
+                mClientModeImpls.removeFirst();
+            }
+            mClientModeImpls.addLast(clientModeImpl);
+        }
+
+        /** Dump the contents of the graveyard. */
+        void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            pw.println("Dump of ConcreteClientModeManager.Graveyard");
+            pw.println("Stopped ClientModeImpls: " + mClientModeImpls.size() + " total");
+            for (ClientModeImpl clientModeImpl : mClientModeImpls) {
+                clientModeImpl.dump(fd, pw, args);
+            }
+            pw.println();
+        }
+
+        boolean hasAllClientModeImplsQuit() {
+            for (ClientModeImpl cmi : mClientModeImpls) {
+                if (!cmi.hasQuit()) return false;
+            }
+            return true;
+        }
+    }
+
+    /**
+     * Dump info about this ClientMode manager.
+     */
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("Dump of ClientModeManager id=" + mId);
+        pw.println("current StateMachine mode: " + getCurrentStateName());
+        pw.println("mRole: " + mRole);
+        pw.println("mTargetRoleChangeInfo: " + mTargetRoleChangeInfo);
+        pw.println("mClientInterfaceName: " + mClientInterfaceName);
+        pw.println("mIfaceIsUp: " + mIfaceIsUp);
+        mStateMachine.dump(fd, pw, args);
+        pw.println();
+        pw.println("Wi-Fi is " + syncGetWifiStateByName());
+        if (mClientModeImpl == null) {
+            pw.println("No active ClientModeImpl instance");
+        } else {
+            mClientModeImpl.dump(fd, pw, args);
+        }
+        mGraveyard.dump(fd, pw, args);
+        pw.println();
+    }
+
+    private String getCurrentStateName() {
+        IState currentState = mStateMachine.getCurrentState();
+
+        if (currentState != null) {
+            return currentState.getName();
+        }
+
+        return "StateMachine not active";
+    }
+
+    /**
+     * Update Wifi state and send the broadcast.
+     *
+     * @param role         Target/Set role for this client mode manager instance.
+     * @param newState     new Wifi state
+     * @param currentState current wifi state
+     */
+    private void updateConnectModeState(ClientRole role, int newState, int currentState) {
+        setWifiStateForApiCalls(newState);
+
+        if (newState == WifiManager.WIFI_STATE_UNKNOWN) {
+            // do not need to broadcast failure to system
+            return;
+        }
+        if (role != ROLE_CLIENT_PRIMARY || !mWifiStateChangeBroadcastEnabled) {
+            // do not raise public broadcast unless this is the primary client mode manager
+            return;
+        }
+
+        // TODO(b/175839153): this broadcast should only be sent out when wifi is toggled on/off,
+        //  not per CMM
+        final Intent intent = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        intent.putExtra(WifiManager.EXTRA_WIFI_STATE, newState);
+        intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_STATE, currentState);
+        String summary = "broadcast=WIFI_STATE_CHANGED_ACTION"
+                + " EXTRA_WIFI_STATE=" + newState
+                + " EXTRA_PREVIOUS_WIFI_STATE=" + currentState;
+        if (mVerboseLoggingEnabled) Log.d(getTag(), "Queuing " + summary);
+        ClientModeManagerBroadcastQueue.QueuedBroadcast broadcast =
+                () -> {
+                    if (mVerboseLoggingEnabled) Log.d(getTag(), "Sending " + summary);
+                    mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+                };
+        if (mRole == null && role == ROLE_CLIENT_PRIMARY) {
+            // This CMM is intended to be the primary, but has not completed the mode transition
+            // yet. Need to force broadcast to be sent.
+            broadcast.send();
+        } else {
+            mBroadcastQueue.queueOrSendBroadcast(this, broadcast);
+        }
+    }
+
+    private void setWifiStateForApiCalls(int newState) {
+        switch (newState) {
+            case WIFI_STATE_DISABLING:
+            case WIFI_STATE_DISABLED:
+            case WIFI_STATE_ENABLING:
+            case WIFI_STATE_ENABLED:
+            case WIFI_STATE_UNKNOWN:
+                if (mVerboseLoggingEnabled) {
+                    Log.d(getTag(), "setting wifi state to: " + newState);
+                }
+                mWifiState.set(newState);
+                break;
+            default:
+                Log.d(getTag(), "attempted to set an invalid state: " + newState);
+                break;
+        }
+    }
+
+    private String syncGetWifiStateByName() {
+        switch (mWifiState.get()) {
+            case WIFI_STATE_DISABLING:
+                return "disabling";
+            case WIFI_STATE_DISABLED:
+                return "disabled";
+            case WIFI_STATE_ENABLING:
+                return "enabling";
+            case WIFI_STATE_ENABLED:
+                return "enabled";
+            case WIFI_STATE_UNKNOWN:
+                return "unknown state";
+            default:
+                return "[invalid state]";
+        }
+    }
+
+    private class ClientModeStateMachine extends StateMachine {
+        // Commands for the state machine.
+        public static final int CMD_START = 0;
+        public static final int CMD_SWITCH_TO_SCAN_ONLY_MODE = 1;
+        public static final int CMD_SWITCH_TO_CONNECT_MODE = 2;
+        public static final int CMD_INTERFACE_STATUS_CHANGED = 3;
+        public static final int CMD_INTERFACE_DESTROYED = 4;
+        public static final int CMD_INTERFACE_DOWN = 5;
+        public static final int CMD_SWITCH_TO_SCAN_ONLY_MODE_CONTINUE = 6;
+        private final State mIdleState = new IdleState();
+        private final State mStartedState = new StartedState();
+        private final State mScanOnlyModeState = new ScanOnlyModeState();
+        private final State mConnectModeState = new ConnectModeState();
+        // Workaround since we cannot use transitionTo(mScanOnlyModeState, RoleChangeInfo)
+        private RoleChangeInfo mScanRoleChangeInfoToSetOnTransition = null;
+        // Workaround since we cannot use transitionTo(mConnectModeState, RoleChangeInfo)
+        private RoleChangeInfo mConnectRoleChangeInfoToSetOnTransition = null;
+
+        @Nullable
+        private StateMachineObituary mObituary = null;
+
+        private final InterfaceCallback mWifiNativeInterfaceCallback = new InterfaceCallback() {
+            @Override
+            public void onDestroyed(String ifaceName) {
+                if (mClientInterfaceName != null && mClientInterfaceName.equals(ifaceName)) {
+                    Log.d(getTag(), "STA iface " + ifaceName + " was destroyed, "
+                            + "stopping client mode");
+
+                    // we must immediately clean up state in ClientModeImpl to unregister
+                    // all client mode related objects
+                    // Note: onDestroyed is only called from the main Wifi thread
+                    if (mClientModeImpl == null) {
+                        Log.w(getTag(), "Received mWifiNativeInterfaceCallback.onDestroyed "
+                                + "callback when no ClientModeImpl instance is active.");
+                    } else {
+                        mClientModeImpl.handleIfaceDestroyed();
+                    }
+
+                    sendMessage(CMD_INTERFACE_DESTROYED);
+                }
+            }
+
+            @Override
+            public void onUp(String ifaceName) {
+                if (mClientInterfaceName != null && mClientInterfaceName.equals(ifaceName)) {
+                    sendMessage(CMD_INTERFACE_STATUS_CHANGED, 1);
+                }
+            }
+
+            @Override
+            public void onDown(String ifaceName) {
+                if (mClientInterfaceName != null && mClientInterfaceName.equals(ifaceName)) {
+                    sendMessage(CMD_INTERFACE_STATUS_CHANGED, 0);
+                }
+            }
+        };
+
+        ClientModeStateMachine(Looper looper) {
+            super(TAG, looper);
+
+            // CHECKSTYLE:OFF IndentationCheck
+            addState(mIdleState);
+                addState(mStartedState, mIdleState);
+                    addState(mScanOnlyModeState, mStartedState);
+                    addState(mConnectModeState, mStartedState);
+            // CHECKSTYLE:ON IndentationCheck
+
+            setInitialState(mIdleState);
+            start();
+        }
+
+        void captureObituaryAndQuitNow() {
+            // capture StateMachine LogRecs since we will lose them after we call quitNow()
+            // This is used for debugging.
+            mObituary = new StateMachineObituary(this);
+
+            quitNow();
+        }
+
+        @Override
+        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            if (mObituary == null) {
+                // StateMachine hasn't quit yet, dump `this` via StateMachineObituary's dump()
+                // method for consistency with `else` branch.
+                new StateMachineObituary(this).dump(fd, pw, args);
+            } else {
+                // StateMachine has quit and cleared all LogRecs.
+                // Get them from the obituary instead.
+                mObituary.dump(fd, pw, args);
+            }
+        }
+
+        /**
+         * Reset this ConcreteClientModeManager when its role changes, so that it can be reused for
+         * another purpose.
+         */
+        private void reset() {
+            // Therefore, the caller must ensure that the role change has been completed and these
+            // settings have already reset before setting them, otherwise the new setting would be
+            // lost.
+            setShouldReduceNetworkScore(false);
+        }
+
+        private void setRoleInternal(@NonNull RoleChangeInfo roleChangeInfo) {
+            mPreviousRole = mRole;
+            mLastRoleChangeSinceBootMs = mClock.getElapsedSinceBootMillis();
+            mRole = roleChangeInfo.role;
+            if (roleChangeInfo.requestorWs != null) {
+                mRequestorWs = roleChangeInfo.requestorWs;
+            }
+            if (roleChangeInfo.modeListener != null) {
+                mModeListener = roleChangeInfo.modeListener;
+            }
+        }
+
+        private void setRoleInternalAndInvokeCallback(@NonNull RoleChangeInfo roleChangeInfo) {
+            if (roleChangeInfo.role == mRole) return;
+            if (mRole == null) {
+                Log.v(getTag(), "ClientModeManager started in role: " + roleChangeInfo);
+                setRoleInternal(roleChangeInfo);
+                mModeListener.onStarted(ConcreteClientModeManager.this);
+            } else {
+                Log.v(getTag(), "ClientModeManager role changed: " + roleChangeInfo);
+                setRoleInternal(roleChangeInfo);
+                reset();
+                mModeListener.onRoleChanged(ConcreteClientModeManager.this);
+            }
+            if (mClientModeImpl != null) {
+                mClientModeImpl.onRoleChanged();
+            }
+        }
+
+        private class IdleState extends State {
+            @Override
+            public void enter() {
+                Log.d(getTag(), "entering IdleState");
+                mClientInterfaceName = null;
+                mIfaceIsUp = false;
+            }
+
+            @Override
+            public void exit() {
+                // Sometimes the wifi handler thread may become blocked that the statemachine
+                // will exit in the IdleState without first entering StartedState. Trigger a
+                // cleanup here in case the above sequence happens. This the statemachine was
+                // started normally this will will not send a duplicate broadcast since mIsStopped
+                // will get set to false the first time the exit happens.
+                cleanupOnQuitIfApplicable();
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                switch (message.what) {
+                    case CMD_START:
+                        // Always start in scan mode first.
+                        RoleChangeInfo roleChangeInfo = (RoleChangeInfo) message.obj;
+                        mClientInterfaceName = mWifiNative.setupInterfaceForClientInScanMode(
+                                mWifiNativeInterfaceCallback, roleChangeInfo.requestorWs);
+                        if (TextUtils.isEmpty(mClientInterfaceName)) {
+                            Log.e(getTag(), "Failed to create ClientInterface. Sit in Idle");
+                            mModeListener.onStartFailure(ConcreteClientModeManager.this);
+                            break;
+                        }
+                        if (roleChangeInfo.role instanceof ClientConnectivityRole) {
+                            sendMessage(CMD_SWITCH_TO_CONNECT_MODE, roleChangeInfo);
+                            transitionTo(mStartedState);
+                        } else {
+                            mScanRoleChangeInfoToSetOnTransition = roleChangeInfo;
+                            transitionTo(mScanOnlyModeState);
+                        }
+                        break;
+                    default:
+                        Log.d(getTag(), "received an invalid message: " + message);
+                        return NOT_HANDLED;
+                }
+                return HANDLED;
+            }
+        }
+
+        private class StartedState extends State {
+
+            private void onUpChanged(boolean isUp) {
+                if (isUp == mIfaceIsUp) {
+                    return;  // no change
+                }
+                mIfaceIsUp = isUp;
+                if (!isUp) {
+                    // if the interface goes down we should exit and go back to idle state.
+                    Log.d(getTag(), "interface down!");
+                    mStateMachine.sendMessage(CMD_INTERFACE_DOWN);
+                }
+            }
+
+            @Override
+            public void enter() {
+                Log.d(getTag(), "entering StartedState");
+                mIfaceIsUp = false;
+                mIsStopped = false;
+                onUpChanged(mWifiNative.isInterfaceUp(mClientInterfaceName));
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                switch (message.what) {
+                    case CMD_START:
+                        // Already started, ignore this command.
+                        break;
+                    case CMD_SWITCH_TO_CONNECT_MODE: {
+                        RoleChangeInfo roleChangeInfo = (RoleChangeInfo) message.obj;
+                        updateConnectModeState(roleChangeInfo.role,
+                                WifiManager.WIFI_STATE_ENABLING,
+                                WifiManager.WIFI_STATE_DISABLED);
+                        if (!mWifiNative.switchClientInterfaceToConnectivityMode(
+                                mClientInterfaceName, roleChangeInfo.requestorWs)) {
+                            updateConnectModeState(roleChangeInfo.role,
+                                    WifiManager.WIFI_STATE_UNKNOWN,
+                                    WifiManager.WIFI_STATE_ENABLING);
+                            updateConnectModeState(roleChangeInfo.role,
+                                    WifiManager.WIFI_STATE_DISABLED,
+                                    WifiManager.WIFI_STATE_UNKNOWN);
+                            mModeListener.onStartFailure(ConcreteClientModeManager.this);
+                            break;
+                        }
+                        // Role set in the enter of ConnectModeState.
+                        mConnectRoleChangeInfoToSetOnTransition = roleChangeInfo;
+                        transitionTo(mConnectModeState);
+                        break;
+                    }
+                    case CMD_SWITCH_TO_SCAN_ONLY_MODE:
+                        updateConnectModeState(mRole, WifiManager.WIFI_STATE_DISABLING,
+                                WifiManager.WIFI_STATE_ENABLED);
+                        mDeferStopHandler.start(getWifiOffDeferringTimeMs());
+                        break;
+                    case CMD_SWITCH_TO_SCAN_ONLY_MODE_CONTINUE: {
+                        RoleChangeInfo roleChangeInfo = (RoleChangeInfo) message.obj;
+                        mScanRoleChangeInfoToSetOnTransition = roleChangeInfo;
+                        transitionTo(mScanOnlyModeState);
+                        break;
+                    }
+                    case CMD_INTERFACE_DOWN:
+                        Log.e(getTag(), "Detected an interface down, reporting failure to "
+                                + "SelfRecovery");
+                        mSelfRecovery.trigger(SelfRecovery.REASON_STA_IFACE_DOWN);
+                        // once interface down, nothing else to do...  stop the state machine
+                        captureObituaryAndQuitNow();
+                        break;
+                    case CMD_INTERFACE_STATUS_CHANGED:
+                        boolean isUp = message.arg1 == 1;
+                        onUpChanged(isUp);
+                        break;
+                    case CMD_INTERFACE_DESTROYED:
+                        Log.e(getTag(), "interface destroyed - client mode stopping");
+                        mClientInterfaceName = null;
+                        // once interface destroyed, nothing else to do...  stop the state machine
+                        captureObituaryAndQuitNow();
+                        break;
+                    default:
+                        return NOT_HANDLED;
+                }
+                return HANDLED;
+            }
+
+            /**
+             * Clean up state, unregister listeners and update wifi state.
+             */
+            @Override
+            public void exit() {
+                if (mClientInterfaceName != null) {
+                    mWifiNative.teardownInterface(mClientInterfaceName);
+                    mClientInterfaceName = null;
+                    mIfaceIsUp = false;
+                }
+
+                Log.i(getTag(), "StartedState#exit(), setting mRole = null");
+                mIsStopped = true;
+                cleanupOnQuitIfApplicable();
+            }
+        }
+
+        private class ScanOnlyModeState extends State {
+            @Override
+            public void enter() {
+                Log.d(getTag(), "entering ScanOnlyModeState");
+
+                if (mClientInterfaceName != null) {
+                    mScanOnlyModeImpl = mWifiInjector.makeScanOnlyModeImpl(
+                            mClientInterfaceName);
+                } else {
+                    Log.e(getTag(), "Entered ScanOnlyModeState with a null interface name!");
+                }
+
+                if (mScanRoleChangeInfoToSetOnTransition == null
+                        || (mScanRoleChangeInfoToSetOnTransition.role != ROLE_CLIENT_SCAN_ONLY)) {
+                    Log.wtf(TAG, "Unexpected mScanRoleChangeInfoToSetOnTransition: "
+                            + mScanRoleChangeInfoToSetOnTransition);
+                    // Should never happen, but fallback to scan only to avoid a crash.
+                    mScanRoleChangeInfoToSetOnTransition =
+                            new RoleChangeInfo(ROLE_CLIENT_SCAN_ONLY);
+                }
+
+                setRoleInternalAndInvokeCallback(mScanRoleChangeInfoToSetOnTransition);
+                // If we're in ScanOnlyModeState, there is only 1 CMM. So it's ok to call
+                // WakeupController directly, there won't be multiple CMMs trampling over each other
+                mWakeupController.start();
+                mWifiNative.setScanMode(mClientInterfaceName, true);
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                switch (message.what) {
+                    case CMD_SWITCH_TO_SCAN_ONLY_MODE:
+                        // Already in scan only mode, ignore this command.
+                        break;
+                    default:
+                        return NOT_HANDLED;
+                }
+                return HANDLED;
+            }
+
+            @Override
+            public void exit() {
+                mScanOnlyModeImpl = null;
+                mScanRoleChangeInfoToSetOnTransition = null;
+
+                // If we're in ScanOnlyModeState, there is only 1 CMM. So it's ok to call
+                // WakeupController directly, there won't be multiple CMMs trampling over each other
+                mWakeupController.stop();
+                mWifiNative.setScanMode(mClientInterfaceName, false);
+            }
+        }
+
+        private class ConnectModeState extends State {
+            @Override
+            public void enter() {
+                Log.d(getTag(), "entering ConnectModeState, starting ClientModeImpl");
+                if (mClientInterfaceName == null) {
+                    Log.e(getTag(), "Supposed to start ClientModeImpl, but iface is null!");
+                } else {
+                    if (mClientModeImpl != null) {
+                        Log.e(getTag(), "ConnectModeState.enter(): mClientModeImpl is already "
+                                + "instantiated?!");
+                    }
+                    mClientModeImpl = mWifiInjector.makeClientModeImpl(
+                            mClientInterfaceName, ConcreteClientModeManager.this,
+                            mVerboseLoggingEnabled);
+                    mClientModeImpl.setShouldReduceNetworkScore(mShouldReduceNetworkScore);
+                }
+                if (mConnectRoleChangeInfoToSetOnTransition == null
+                        || !(mConnectRoleChangeInfoToSetOnTransition.role
+                        instanceof ClientConnectivityRole)) {
+                    Log.wtf(TAG, "Unexpected mConnectRoleChangeInfoToSetOnTransition: "
+                            + mConnectRoleChangeInfoToSetOnTransition);
+                    // Should never happen, but fallback to primary to avoid a crash.
+                    mConnectRoleChangeInfoToSetOnTransition =
+                            new RoleChangeInfo(ROLE_CLIENT_PRIMARY);
+                }
+
+                // Could be any one of possible connect mode roles.
+                setRoleInternalAndInvokeCallback(mConnectRoleChangeInfoToSetOnTransition);
+                updateConnectModeState(mConnectRoleChangeInfoToSetOnTransition.role,
+                        WIFI_STATE_ENABLED, WIFI_STATE_ENABLING);
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                switch (message.what) {
+                    case CMD_SWITCH_TO_CONNECT_MODE:
+                        RoleChangeInfo roleChangeInfo = (RoleChangeInfo) message.obj;
+                        // switching to connect mode when already in connect mode, just update the
+                        // requestor WorkSource.
+                        boolean success = mWifiNative.replaceStaIfaceRequestorWs(
+                                mClientInterfaceName, roleChangeInfo.requestorWs);
+                        if (success) {
+                            setRoleInternalAndInvokeCallback(roleChangeInfo);
+                        } else {
+                            // If this call failed, the iface would be torn down.
+                            // Thus, simply abort and let the iface down handling take care of the
+                            // rest.
+                            Log.e(getTag(), "Failed to switch ClientModeManager="
+                                    + ConcreteClientModeManager.this + "'s requestorWs");
+                        }
+                        break;
+                    case CMD_SWITCH_TO_SCAN_ONLY_MODE:
+                    case CMD_INTERFACE_DESTROYED:
+                        updateConnectModeState(mRole, WifiManager.WIFI_STATE_DISABLING,
+                                WifiManager.WIFI_STATE_ENABLED);
+                        return NOT_HANDLED; // Handled in StartedState.
+                    case CMD_INTERFACE_DOWN:
+                        updateConnectModeState(mRole, WifiManager.WIFI_STATE_DISABLING,
+                                WifiManager.WIFI_STATE_UNKNOWN);
+                        return NOT_HANDLED; // Handled in StartedState.
+                    case CMD_INTERFACE_STATUS_CHANGED:
+                        boolean isUp = message.arg1 == 1;
+                        if (isUp == mIfaceIsUp) {
+                            break;  // no change
+                        }
+                        if (!isUp) {
+                            if (mWifiGlobals.isConnectedMacRandomizationEnabled()
+                                    && getClientMode().isConnecting()) {
+                                return HANDLED; // For MAC randomization, ignore...
+                            } else {
+                                // Handle the error case where our underlying interface went down if
+                                // we do not have mac randomization enabled (b/72459123).
+                                // if the interface goes down we should exit and go back to idle
+                                // state.
+                                updateConnectModeState(mRole, WifiManager.WIFI_STATE_UNKNOWN,
+                                        WifiManager.WIFI_STATE_ENABLED);
+                            }
+                        }
+                        return NOT_HANDLED; // Handled in StartedState.
+                    default:
+                        return NOT_HANDLED;
+                }
+                return HANDLED;
+            }
+
+            @Override
+            public void exit() {
+                updateConnectModeState(mRole, WifiManager.WIFI_STATE_DISABLED,
+                        WifiManager.WIFI_STATE_DISABLING);
+
+                if (mClientModeImpl == null) {
+                    Log.w(getTag(), "ConnectModeState.exit(): mClientModeImpl is already null?!");
+                } else {
+                    Log.d(getTag(), "Stopping ClientModeImpl");
+                    mClientModeImpl.stop();
+                    mGraveyard.inter(mClientModeImpl);
+                    mClientModeImpl = null;
+                }
+
+                mConnectRoleChangeInfoToSetOnTransition = null;
+            }
+        }
+    }
+
+    /** Called by a ClientModeImpl owned by this CMM informing it has fully stopped. */
+    public void onClientModeImplQuit() {
+        cleanupOnQuitIfApplicable();
+    }
+
+    /**
+     * Only clean up this CMM once the CMM and all associated ClientModeImpls have been stopped.
+     * This is necessary because ClientModeImpl sends broadcasts during stop, and the role must
+     * remain primary for {@link ClientModeManagerBroadcastQueue} to send them out.
+     */
+    private void cleanupOnQuitIfApplicable() {
+        if (mIsStopped && mGraveyard.hasAllClientModeImplsQuit()) {
+            mPreviousRole = mRole;
+            mLastRoleChangeSinceBootMs = mClock.getElapsedSinceBootMillis();
+            mRole = null;
+            // only call onStopped() after role has been reset to null since ActiveModeWarden
+            // expects the CMM to be fully stopped before onStopped().
+            mModeListener.onStopped(ConcreteClientModeManager.this);
+
+            // reset to false so that onStopped() won't be triggered again.
+            mIsStopped = false;
+        }
+    }
+
+    @Override
+    public int syncGetWifiState() {
+        return mWifiState.get();
+    }
+
+    @NonNull
+    private ClientMode getClientMode() {
+        if (mClientModeImpl != null) {
+            return mClientModeImpl;
+        }
+        if (mScanOnlyModeImpl != null) {
+            return mScanOnlyModeImpl;
+        }
+        return mDefaultClientModeManager;
+    }
+
+    /*
+     * Note: These are simple wrappers over methods to {@link ClientModeImpl}.
+     */
+
+    @Override
+    public void connectNetwork(NetworkUpdateResult result, ActionListenerWrapper wrapper,
+            int callingUid) {
+        getClientMode().connectNetwork(result, wrapper, callingUid);
+    }
+
+    @Override
+    public void saveNetwork(NetworkUpdateResult result, ActionListenerWrapper wrapper,
+            int callingUid) {
+        getClientMode().saveNetwork(result, wrapper, callingUid);
+    }
+
+    @Override
+    public void disconnect() {
+        getClientMode().disconnect();
+    }
+
+    @Override
+    public void reconnect(WorkSource ws) {
+        getClientMode().reconnect(ws);
+    }
+
+    @Override
+    public void reassociate() {
+        getClientMode().reassociate();
+    }
+
+    @Override
+    public void startConnectToNetwork(int networkId, int uid, String bssid) {
+        getClientMode().startConnectToNetwork(networkId, uid, bssid);
+    }
+
+    @Override
+    public void startRoamToNetwork(int networkId, String bssid) {
+        getClientMode().startRoamToNetwork(networkId, bssid);
+    }
+
+    @Override
+    public boolean setWifiConnectedNetworkScorer(
+            IBinder binder, IWifiConnectedNetworkScorer scorer) {
+        return getClientMode().setWifiConnectedNetworkScorer(binder, scorer);
+    }
+
+    @Override
+    public void clearWifiConnectedNetworkScorer() {
+        getClientMode().clearWifiConnectedNetworkScorer();
+    }
+
+    @Override
+    public void resetSimAuthNetworks(@ClientModeImpl.ResetSimReason int resetReason) {
+        getClientMode().resetSimAuthNetworks(resetReason);
+    }
+
+    @Override
+    public void onBluetoothConnectionStateChanged() {
+        getClientMode().onBluetoothConnectionStateChanged();
+    }
+
+    @Override
+    public WifiInfo syncRequestConnectionInfo() {
+        return getClientMode().syncRequestConnectionInfo();
+    }
+
+    @Override
+    public boolean syncQueryPasspointIcon(long bssid, String fileName) {
+        return getClientMode().syncQueryPasspointIcon(bssid, fileName);
+    }
+
+    @Override
+    public Network syncGetCurrentNetwork() {
+        return getClientMode().syncGetCurrentNetwork();
+    }
+
+    @Override
+    public DhcpResultsParcelable syncGetDhcpResultsParcelable() {
+        return getClientMode().syncGetDhcpResultsParcelable();
+    }
+
+    @Override
+    public long getSupportedFeatures() {
+        return getClientMode().getSupportedFeatures();
+    }
+
+    @Override
+    public boolean syncStartSubscriptionProvisioning(int callingUid, OsuProvider provider,
+            IProvisioningCallback callback) {
+        return getClientMode().syncStartSubscriptionProvisioning(
+                callingUid, provider, callback);
+    }
+
+    @Override
+    public boolean isWifiStandardSupported(@WifiAnnotations.WifiStandard int standard) {
+        return getClientMode().isWifiStandardSupported(standard);
+    }
+
+    @Override
+    public void enableTdls(String remoteMacAddress, boolean enable) {
+        getClientMode().enableTdls(remoteMacAddress, enable);
+    }
+
+    @Override
+    public void dumpIpClient(FileDescriptor fd, PrintWriter pw, String[] args) {
+        getClientMode().dumpIpClient(fd, pw, args);
+    }
+
+    @Override
+    public void dumpWifiScoreReport(FileDescriptor fd, PrintWriter pw, String[] args) {
+        getClientMode().dumpWifiScoreReport(fd, pw, args);
+    }
+
+    @Override
+    public void enableVerboseLogging(boolean verbose) {
+        mVerboseLoggingEnabled = verbose;
+        getClientMode().enableVerboseLogging(verbose);
+    }
+
+    @Override
+    public String getFactoryMacAddress() {
+        return getClientMode().getFactoryMacAddress();
+    }
+
+    @Override
+    public WifiConfiguration getConnectedWifiConfiguration() {
+        return getClientMode().getConnectedWifiConfiguration();
+    }
+
+    @Override
+    public WifiConfiguration getConnectingWifiConfiguration() {
+        return getClientMode().getConnectingWifiConfiguration();
+    }
+
+    @Override
+    public String getConnectedBssid() {
+        return getClientMode().getConnectedBssid();
+    }
+
+    @Override
+    public String getConnectingBssid() {
+        return getClientMode().getConnectingBssid();
+    }
+
+    @Override
+    public WifiLinkLayerStats getWifiLinkLayerStats() {
+        return getClientMode().getWifiLinkLayerStats();
+    }
+
+    @Override
+    public boolean setPowerSave(boolean ps) {
+        return getClientMode().setPowerSave(ps);
+    }
+
+    @Override
+    public boolean setLowLatencyMode(boolean enabled) {
+        return getClientMode().setLowLatencyMode(enabled);
+    }
+
+    @Override
+    public WifiMulticastLockManager.FilterController getMcastLockManagerFilterController() {
+        return getClientMode().getMcastLockManagerFilterController();
+    }
+
+    @Override
+    public boolean isConnected() {
+        return getClientMode().isConnected();
+    }
+
+    @Override
+    public boolean isConnecting() {
+        return getClientMode().isConnecting();
+    }
+
+    @Override
+    public boolean isRoaming() {
+        return getClientMode().isRoaming();
+    }
+
+    @Override
+    public boolean isDisconnected() {
+        return getClientMode().isDisconnected();
+    }
+
+    @Override
+    public boolean isSupplicantTransientState() {
+        return getClientMode().isSupplicantTransientState();
+    }
+
+    @Override
+    public void onCellularConnectivityChanged(@WifiDataStall.CellularDataStatusCode int status) {
+        getClientMode().onCellularConnectivityChanged(status);
+    }
+
+    @Override
+    public void probeLink(LinkProbeCallback callback, int mcs) {
+        getClientMode().probeLink(callback, mcs);
+    }
+
+    @Override
+    public void sendMessageToClientModeImpl(Message msg) {
+        getClientMode().sendMessageToClientModeImpl(msg);
+    }
+
+    @Override
+    public long getId() {
+        return mId;
+    }
+
+    @Override
+    public void setMboCellularDataStatus(boolean available) {
+        getClientMode().setMboCellularDataStatus(available);
+    }
+
+    @Override
+    public WifiNative.RoamingCapabilities getRoamingCapabilities() {
+        return getClientMode().getRoamingCapabilities();
+    }
+
+    @Override
+    public boolean configureRoaming(WifiNative.RoamingConfig config) {
+        return getClientMode().configureRoaming(config);
+    }
+
+    @Override
+    public boolean enableRoaming(boolean enabled) {
+        return getClientMode().enableRoaming(enabled);
+    }
+
+    @Override
+    public boolean setCountryCode(String countryCode) {
+        return getClientMode().setCountryCode(countryCode);
+    }
+
+    @Override
+    public List<TxFateReport> getTxPktFates() {
+        return getClientMode().getTxPktFates();
+    }
+
+    @Override
+    public List<RxFateReport> getRxPktFates() {
+        return getClientMode().getRxPktFates();
+    }
+
+    @Override
+    public DeviceWiphyCapabilities getDeviceWiphyCapabilities() {
+        return getClientMode().getDeviceWiphyCapabilities();
+    }
+
+    @Override
+    public boolean requestAnqp(String bssid, Set<Integer> anqpIds, Set<Integer> hs20Subtypes) {
+        return getClientMode().requestAnqp(bssid, anqpIds, hs20Subtypes);
+    }
+
+    @Override
+    public boolean requestVenueUrlAnqp(String bssid) {
+        return getClientMode().requestVenueUrlAnqp(bssid);
+    }
+
+    @Override
+    public boolean requestIcon(String bssid, String fileName) {
+        return getClientMode().requestIcon(bssid, fileName);
+    }
+
+    @Override
+    public void setShouldReduceNetworkScore(boolean shouldReduceNetworkScore) {
+        mShouldReduceNetworkScore = shouldReduceNetworkScore;
+        getClientMode().setShouldReduceNetworkScore(shouldReduceNetworkScore);
+    }
+
+    @Override
+    public String toString() {
+        return "ConcreteClientModeManager{id=" + getId()
+                + " iface=" + getInterfaceName()
+                + " role=" + getRole()
+                + "}";
+    }
+}
diff --git a/service/java/com/android/server/wifi/ConfigurationMap.java b/service/java/com/android/server/wifi/ConfigurationMap.java
index cce8958..88c6956 100644
--- a/service/java/com/android/server/wifi/ConfigurationMap.java
+++ b/service/java/com/android/server/wifi/ConfigurationMap.java
@@ -62,7 +62,8 @@
             mPerIDForCurrentUser.put(config.networkId, config);
             // TODO (b/142035508): Add a more generic fix. This cache should only hold saved
             // networks.
-            if (!config.fromWifiNetworkSpecifier) {
+            if (!config.fromWifiNetworkSpecifier && !config.fromWifiNetworkSuggestion
+                    && !config.isPasspoint()) {
                 mScanResultMatchInfoMapForCurrentUser.put(
                         ScanResultMatchInfo.fromWifiConfiguration(config), config);
             }
@@ -126,7 +127,7 @@
             return null;
         }
         for (WifiConfiguration config : mPerIDForCurrentUser.values()) {
-            if (config.getKey().equals(key)) {
+            if (config.getProfileKey().equals(key)) {
                 return config;
             }
         }
diff --git a/service/java/com/android/server/wifi/ConnectHelper.java b/service/java/com/android/server/wifi/ConnectHelper.java
new file mode 100644
index 0000000..a9b71b5
--- /dev/null
+++ b/service/java/com/android/server/wifi/ConnectHelper.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.annotation.NonNull;
+import android.net.wifi.WifiManager;
+import android.util.Log;
+
+import com.android.server.wifi.util.ActionListenerWrapper;
+
+/**
+ * This class is used to hold connection logic that is shared between {@link WifiServiceImpl} and
+ * other helper classes, because helper classes should not call WifiServiceImpl directly.
+ */
+public class ConnectHelper {
+    private static final String TAG = "WifiConnectHelper";
+
+    private final ActiveModeWarden mActiveModeWarden;
+    private final WifiConfigManager mWifiConfigManager;
+
+    public ConnectHelper(
+            ActiveModeWarden activeModeWarden,
+            WifiConfigManager wifiConfigManager) {
+        mActiveModeWarden = activeModeWarden;
+        mWifiConfigManager = wifiConfigManager;
+    }
+
+    /**
+     * Trigger connection to an existing network and provide status via the provided callback.
+     * This uses the primary client mode manager for making the connection.
+     */
+    public void connectToNetwork(
+            @NonNull NetworkUpdateResult result,
+            @NonNull ActionListenerWrapper wrapper,
+            int callingUid) {
+        connectToNetwork(
+                mActiveModeWarden.getPrimaryClientModeManager(), result, wrapper, callingUid);
+    }
+
+    /**
+     * Trigger connection to an existing network and provide status via the provided callback.
+     * This uses the provided client mode manager for making the connection.
+     */
+    public void connectToNetwork(
+            @NonNull ClientModeManager clientModeManager,
+            @NonNull NetworkUpdateResult result,
+            @NonNull ActionListenerWrapper wrapper,
+            int callingUid) {
+        int netId = result.getNetworkId();
+        if (mWifiConfigManager.getConfiguredNetwork(netId) == null) {
+            Log.e(TAG, "connectToNetwork Invalid network Id=" + netId);
+            wrapper.sendFailure(WifiManager.ERROR);
+            return;
+        }
+        mWifiConfigManager.updateBeforeConnect(netId, callingUid);
+        clientModeManager.connectNetwork(result, wrapper, callingUid);
+    }
+}
diff --git a/service/java/com/android/server/wifi/ConnectToNetworkNotificationBuilder.java b/service/java/com/android/server/wifi/ConnectToNetworkNotificationBuilder.java
index 053314d..948abc1 100644
--- a/service/java/com/android/server/wifi/ConnectToNetworkNotificationBuilder.java
+++ b/service/java/com/android/server/wifi/ConnectToNetworkNotificationBuilder.java
@@ -25,6 +25,7 @@
 import android.net.wifi.ScanResult;
 import android.util.Log;
 
+import com.android.modules.utils.build.SdkLevel;
 import com.android.wifi.resources.R;
 
 /**
@@ -52,16 +53,13 @@
     public static final String AVAILABLE_NETWORK_NOTIFIER_TAG =
             "com.android.server.wifi.ConnectToNetworkNotification.AVAILABLE_NETWORK_NOTIFIER_TAG";
 
-    private WifiContext mContext;
-    private WifiInjector mWifiInjector;
-    private FrameworkFacade mFrameworkFacade;
+    private final WifiContext mContext;
+    private final FrameworkFacade mFrameworkFacade;
 
     public ConnectToNetworkNotificationBuilder(
             WifiContext context,
-            WifiInjector wifiInjector,
             FrameworkFacade framework) {
         mContext = context;
-        mWifiInjector = wifiInjector;
         mFrameworkFacade = framework;
     }
 
@@ -87,18 +85,29 @@
                         + notifierTag);
                 return null;
         }
-        Notification.Action connectAction = new Notification.Action.Builder(null /* icon */,
+        Notification.Action.Builder connectActionBuilder =
+                new Notification.Action.Builder(null /* icon */,
                 mContext.getResources().getText(R.string.wifi_available_action_connect),
-                getPrivateBroadcast(ACTION_CONNECT_TO_NETWORK, notifierTag)).build();
+                getPrivateBroadcast(ACTION_CONNECT_TO_NETWORK, notifierTag));
+        // >= Android 12: Want the user to unlock before triggering connection.
+        if (SdkLevel.isAtLeastS()) {
+            connectActionBuilder.setAuthenticationRequired(true);
+        }
+        Notification.Action connectAction = connectActionBuilder.build();
         Notification.Action allNetworksAction = new Notification.Action.Builder(null /* icon */,
                 mContext.getResources().getText(R.string.wifi_available_action_all_networks),
                 getPrivateBroadcast(ACTION_PICK_WIFI_NETWORK, notifierTag)).build();
-        return createNotificationBuilder(title, network.SSID, notifierTag)
+        Notification.Builder notificationBuilder =
+                createNotificationBuilder(title, network.SSID, notifierTag)
                 .setContentIntent(getPrivateBroadcast(ACTION_PICK_WIFI_NETWORK, notifierTag))
                 .addAction(connectAction)
-                .addAction(allNetworksAction)
-                .setVisibility(VISIBILITY_SECRET)
-                .build();
+                .addAction(allNetworksAction);
+        // < Android 12: Hide the notification in lock screen since (setAuthenticationRequired) is
+        // not available.
+        if (!SdkLevel.isAtLeastS()) {
+            notificationBuilder.setVisibility(VISIBILITY_SECRET);
+        }
+        return notificationBuilder.build();
     }
 
     /**
@@ -174,14 +183,13 @@
     }
 
     private PendingIntent getPrivateBroadcast(String action, String extraData) {
-        Intent intent = new Intent(action)
-                .setPackage(mWifiInjector.getWifiStackPackageName());
+        Intent intent = new Intent(action).setPackage(mContext.getServiceWifiPackageName());
         int requestCode = 0;  // Makes the different kinds of notifications distinguishable
         if (extraData != null) {
             intent.putExtra(AVAILABLE_NETWORK_NOTIFIER_TAG, extraData);
             requestCode = getNotifierRequestCode(extraData);
         }
         return mFrameworkFacade.getBroadcast(mContext, requestCode, intent,
-                PendingIntent.FLAG_UPDATE_CURRENT);
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
     }
 }
diff --git a/service/java/com/android/server/wifi/ConnectedScore.java b/service/java/com/android/server/wifi/ConnectedScore.java
index b02db58..8b37d5d 100644
--- a/service/java/com/android/server/wifi/ConnectedScore.java
+++ b/service/java/com/android/server/wifi/ConnectedScore.java
@@ -26,6 +26,9 @@
     /** Maximum NetworkAgent score that should be generated by wifi */
     public static final int WIFI_MAX_SCORE = 60;
 
+    /** Initial Wifi score when starting up NetworkAgent. */
+    public static final int WIFI_INITIAL_SCORE = WIFI_MAX_SCORE;
+
     /** Score at which wifi is considered poor enough to give up ant try something else */
     public static final int WIFI_TRANSITION_SCORE = WIFI_MAX_SCORE - 10;
 
diff --git a/service/java/com/android/server/wifi/ConnectionFailureNotificationBuilder.java b/service/java/com/android/server/wifi/ConnectionFailureNotificationBuilder.java
index 6f0a487..8620ddc 100644
--- a/service/java/com/android/server/wifi/ConnectionFailureNotificationBuilder.java
+++ b/service/java/com/android/server/wifi/ConnectionFailureNotificationBuilder.java
@@ -19,13 +19,11 @@
 import android.annotation.NonNull;
 import android.app.AlertDialog;
 import android.app.Notification;
-import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.graphics.drawable.Icon;
 import android.net.wifi.WifiConfiguration;
-import android.os.Handler;
 import android.view.WindowManager;
 
 import com.android.wifi.resources.R;
@@ -43,17 +41,12 @@
     public static final String RANDOMIZATION_SETTINGS_NETWORK_SSID =
             "com.android.server.wifi.RANDOMIZATION_SETTINGS_NETWORK_SSID";
 
-    private WifiContext mContext;
-    private String mPackageName;
-    private FrameworkFacade mFrameworkFacade;
-    private WifiConnectivityManager mWifiConnectivityManager;
-    private NotificationManager mNotificationManager;
-    private Handler mHandler;
+    private final WifiContext mContext;
+    private final FrameworkFacade mFrameworkFacade;
 
-    public ConnectionFailureNotificationBuilder(WifiContext context, String packageName,
+    public ConnectionFailureNotificationBuilder(WifiContext context,
             FrameworkFacade framework) {
         mContext = context;
-        mPackageName = packageName;
         mFrameworkFacade = framework;
     }
 
@@ -72,11 +65,12 @@
                 R.string.wifi_cannot_connect_with_randomized_mac_message);
 
         Intent showDetailIntent = new Intent(ACTION_SHOW_SET_RANDOMIZATION_DETAILS)
-                .setPackage(mPackageName);
+                .setPackage(mContext.getServiceWifiPackageName());
         showDetailIntent.putExtra(RANDOMIZATION_SETTINGS_NETWORK_ID, config.networkId);
         showDetailIntent.putExtra(RANDOMIZATION_SETTINGS_NETWORK_SSID, ssidAndSecurityType);
         PendingIntent pendingShowDetailIntent = mFrameworkFacade.getBroadcast(
-                mContext, 0, showDetailIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+                mContext, 0, showDetailIntent,
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
 
         return mFrameworkFacade.makeNotificationBuilder(
                 mContext, WifiService.NOTIFICATION_NETWORK_ALERTS)
diff --git a/service/java/com/android/server/wifi/ConnectionFailureNotifier.java b/service/java/com/android/server/wifi/ConnectionFailureNotifier.java
index 6cdbfed..123b8f6 100644
--- a/service/java/com/android/server/wifi/ConnectionFailureNotifier.java
+++ b/service/java/com/android/server/wifi/ConnectionFailureNotifier.java
@@ -18,7 +18,6 @@
 
 import android.app.AlertDialog;
 import android.app.Notification;
-import android.app.NotificationManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -38,28 +37,29 @@
 public class ConnectionFailureNotifier {
     private static final String TAG = "ConnectionFailureNotifier";
 
-    private Context mContext;
-    private WifiInjector mWifiInjector;
-    private FrameworkFacade mFrameworkFacade;
-    private WifiConfigManager mWifiConfigManager;
-    private WifiConnectivityManager mWifiConnectivityManager;
-    private NotificationManager mNotificationManager;
-    private Handler mHandler;
-    private ConnectionFailureNotificationBuilder mConnectionFailureNotificationBuilder;
+    private final WifiContext mContext;
+    private final FrameworkFacade mFrameworkFacade;
+    private final WifiConfigManager mWifiConfigManager;
+    private final WifiConnectivityManager mWifiConnectivityManager;
+    private final WifiNotificationManager mNotificationManager;
+    private final Handler mHandler;
+    private final ConnectionFailureNotificationBuilder mConnectionFailureNotificationBuilder;
 
     public ConnectionFailureNotifier(
-            Context context, WifiInjector wifiInjector, FrameworkFacade framework,
-            WifiConfigManager wifiConfigManager, WifiConnectivityManager wifiConnectivityManager,
-            Handler handler) {
+            WifiContext context,
+            FrameworkFacade framework,
+            WifiConfigManager wifiConfigManager,
+            WifiConnectivityManager wifiConnectivityManager,
+            Handler handler,
+            WifiNotificationManager notificationManager,
+            ConnectionFailureNotificationBuilder connectionFailureNotificationBuilder) {
         mContext = context;
-        mWifiInjector = wifiInjector;
         mFrameworkFacade = framework;
         mWifiConfigManager = wifiConfigManager;
         mWifiConnectivityManager = wifiConnectivityManager;
-        mNotificationManager = mWifiInjector.getNotificationManager();
+        mNotificationManager = notificationManager;
         mHandler = handler;
-        mConnectionFailureNotificationBuilder =
-                mWifiInjector.getConnectionFailureNotificationBuilder();
+        mConnectionFailureNotificationBuilder = connectionFailureNotificationBuilder;
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(ConnectionFailureNotificationBuilder
diff --git a/service/java/com/android/server/wifi/DefaultClientModeManager.java b/service/java/com/android/server/wifi/DefaultClientModeManager.java
new file mode 100644
index 0000000..b29ed83
--- /dev/null
+++ b/service/java/com/android/server/wifi/DefaultClientModeManager.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.annotation.Nullable;
+import android.net.wifi.WifiManager;
+import android.os.WorkSource;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * This is used for creating a public {@link ClientModeManager} instance when wifi is off.
+ *
+ * Note: this class is currently a singleton as it has no state.
+ */
+public class DefaultClientModeManager implements ClientModeManager, ClientModeDefaults {
+
+    private static final long ID = -1;
+
+    @Override
+    public void stop() {
+        throw new IllegalStateException();
+    }
+
+    @Override
+    @Nullable public ClientRole getRole() {
+        return null;
+    }
+
+    @Override
+    @Nullable public ClientRole getPreviousRole() {
+        return null;
+    }
+
+    @Override
+    public long getLastRoleChangeSinceBootMs() {
+        return 0;
+    }
+
+    @Override
+    public String getInterfaceName() {
+        return null;
+    }
+
+    @Override
+    public WorkSource getRequestorWs() {
+        return null;
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { }
+
+    @Override
+    public void enableVerboseLogging(boolean verbose) { }
+
+    @Override
+    public int syncGetWifiState() {
+        return WifiManager.WIFI_STATE_DISABLED;
+    }
+
+    @Override
+    public long getId() {
+        return ID;
+    }
+
+    @Override
+    public String toString() {
+        return "DefaultClientModeManager{id=" + getId()
+                + " iface=" + getInterfaceName()
+                + " role=" + getRole()
+                + "}";
+    }
+
+    @Override
+    public long getSupportedFeatures() {
+        return 0L;
+    }
+
+    @Override
+    public boolean isWifiStandardSupported(int standard) {
+        return false;
+    }
+}
diff --git a/service/java/com/android/server/wifi/DefaultModeManager.java b/service/java/com/android/server/wifi/DefaultModeManager.java
deleted file mode 100644
index 00b2117..0000000
--- a/service/java/com/android/server/wifi/DefaultModeManager.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wifi;
-
-import android.content.Context;
-
-import com.android.internal.util.Preconditions;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-/**
- *  Manager to handle API calls when wifi is disabled (other mode managers could be active, but this
- *  class triggers calls to the default implementations).
- */
-public class DefaultModeManager implements ActiveModeManager {
-
-    private static final String TAG = "WifiDefaultModeManager";
-
-    private final Context mContext;
-
-    /**
-     * Start is not used in default mode.
-     */
-    @Override
-    public void start() { };
-
-    /**
-     * Stop is not used in default mode.
-     */
-    @Override
-    public void stop() { };
-
-    @Override
-    public boolean isStopping() {
-        return false;
-    }
-
-    /**
-     * No role specified in default mode.
-     */
-    @Override
-    public @Role int getRole() {
-        return ROLE_UNSPECIFIED;
-    }
-
-    /**
-     * No role specified in default mode.
-     */
-    @Override
-    public void setRole(@Role int role) {
-        Preconditions.checkState(role == ROLE_UNSPECIFIED);
-    }
-
-    /**
-     * Dump is not used in default mode.
-     */
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { }
-
-    DefaultModeManager(Context context) {
-        mContext = context;
-    }
-}
diff --git a/service/java/com/android/server/wifi/DeviceConfigFacade.java b/service/java/com/android/server/wifi/DeviceConfigFacade.java
index a7bcfc7..6fba546 100644
--- a/service/java/com/android/server/wifi/DeviceConfigFacade.java
+++ b/service/java/com/android/server/wifi/DeviceConfigFacade.java
@@ -54,13 +54,13 @@
     // 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 Tx throughput in Kbps
-    public static final int DEFAULT_TX_TPUT_SUFFICIENT_THR_LOW_KBPS = 1000;
+    public static final int DEFAULT_TX_TPUT_SUFFICIENT_THR_LOW_KBPS = 2000;
     // Default high threshold of L2 sufficient Tx throughput in Kbps
-    public static final int DEFAULT_TX_TPUT_SUFFICIENT_THR_HIGH_KBPS = 4000;
+    public static final int DEFAULT_TX_TPUT_SUFFICIENT_THR_HIGH_KBPS = 8000;
     // Default low threshold of L2 sufficient Rx throughput in Kbps
-    public static final int DEFAULT_RX_TPUT_SUFFICIENT_THR_LOW_KBPS = 1000;
+    public static final int DEFAULT_RX_TPUT_SUFFICIENT_THR_LOW_KBPS = 2000;
     // Default high threshold of L2 sufficient Rx throughput in Kbps
-    public static final int DEFAULT_RX_TPUT_SUFFICIENT_THR_HIGH_KBPS = 4000;
+    public static final int DEFAULT_RX_TPUT_SUFFICIENT_THR_HIGH_KBPS = 8000;
     // 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
@@ -68,10 +68,11 @@
     // Default threshold of Tx packet per second
     public static final int DEFAULT_TX_PACKET_PER_SECOND_THR = 2;
     // Default threshold of Rx packet per second
-    public static final int DEFAULT_RX_PACKET_PER_SECOND_THR = 1;
+    public static final int DEFAULT_RX_PACKET_PER_SECOND_THR = 2;
     // Default high threshold values for various connection/disconnection cases
     // All of them are in percent with respect to connection attempts
     static final int DEFAULT_CONNECTION_FAILURE_HIGH_THR_PERCENT = 40;
+    static final int DEFAULT_CONNECTION_FAILURE_DISCONNECTION_HIGH_THR_PERCENT = 30;
     static final int DEFAULT_ASSOC_REJECTION_HIGH_THR_PERCENT = 30;
     static final int DEFAULT_ASSOC_TIMEOUT_HIGH_THR_PERCENT = 30;
     static final int DEFAULT_AUTH_FAILURE_HIGH_THR_PERCENT = 30;
@@ -79,6 +80,7 @@
     static final int DEFAULT_DISCONNECTION_NONLOCAL_HIGH_THR_PERCENT = 25;
     // Default health monitor abnormal count minimum for various cases
     static final int DEFAULT_CONNECTION_FAILURE_COUNT_MIN = 6;
+    static final int DEFAULT_CONNECTION_FAILURE_DISCONNECTION_COUNT_MIN = 5;
     static final int DEFAULT_ASSOC_REJECTION_COUNT_MIN  = 3;
     static final int DEFAULT_ASSOC_TIMEOUT_COUNT_MIN  = 3;
     static final int DEFAULT_AUTH_FAILURE_COUNT_MIN  = 3;
@@ -142,6 +144,9 @@
     // Default RSSI threshold in dBm above which low score is not sent to connectivity service
     // when external scorer takes action.
     static final int DEFAULT_RSSI_THRESHOLD_NOT_SEND_LOW_SCORE_TO_CS_DBM = -67;
+    // Maximum traffic stats threshold for link bandwidth estimator
+    static final int DEFAULT_TRAFFIC_STATS_THRESHOLD_MAX_KB = 8000;
+    static final int DEFAULT_BANDWIDTH_ESTIMATOR_TIME_CONSTANT_LARGE_SEC = 6;
     // Cached values of fields updated via updateDeviceConfigFlags()
     private boolean mIsAbnormalConnectionBugreportEnabled;
     private int mAbnormalConnectionDurationMs;
@@ -160,6 +165,8 @@
     private int mRxPktPerSecondThr;
     private int mConnectionFailureHighThrPercent;
     private int mConnectionFailureCountMin;
+    private int mConnectionFailureDisconnectionHighThrPercent;
+    private int mConnectionFailureDisconnectionCountMin;
     private int mAssocRejectionHighThrPercent;
     private int mAssocRejectionCountMin;
     private int mAssocTimeoutHighThrPercent;
@@ -180,6 +187,7 @@
     private int mHealthMonitorMinNumConnectionAttempt;
     private int mBugReportMinWindowMs;
     private int mBugReportThresholdExtraRatio;
+    private boolean mWifiBatterySaverEnabled;
     private boolean mIsOverlappingConnectionBugreportEnabled;
     private int mOverlappingConnectionDurationThresholdMs;
     private int mTxLinkSpeedLowThresholdMbps;
@@ -193,6 +201,9 @@
     private int mMinConfirmationDurationSendLowScoreMs;
     private int mMinConfirmationDurationSendHighScoreMs;
     private int mRssiThresholdNotSendLowScoreToCsDbm;
+    private boolean mAllowEnhancedMacRandomizationOnOpenSsids;
+    private int mTrafficStatsThresholdMaxKbyte;
+    private int mBandwidthEstimatorLargeTimeConstantSec;
 
     public DeviceConfigFacade(Context context, Handler handler, WifiMetrics wifiMetrics) {
         mContext = context;
@@ -253,6 +264,12 @@
         mConnectionFailureCountMin = DeviceConfig.getInt(NAMESPACE,
                 "connection_failure_count_min",
                 DEFAULT_CONNECTION_FAILURE_COUNT_MIN);
+        mConnectionFailureDisconnectionHighThrPercent = DeviceConfig.getInt(NAMESPACE,
+                "connection_failure_disconnection_high_thr_percent",
+                DEFAULT_CONNECTION_FAILURE_DISCONNECTION_HIGH_THR_PERCENT);
+        mConnectionFailureDisconnectionCountMin = DeviceConfig.getInt(NAMESPACE,
+                "connection_failure_disconnection_count_min",
+                DEFAULT_CONNECTION_FAILURE_DISCONNECTION_COUNT_MIN);
         mAssocRejectionHighThrPercent = DeviceConfig.getInt(NAMESPACE,
                 "assoc_rejection_high_thr_percent",
                 DEFAULT_ASSOC_REJECTION_HIGH_THR_PERCENT);
@@ -321,6 +338,8 @@
         mRxLinkSpeedLowThresholdMbps = DeviceConfig.getInt(NAMESPACE,
                 "rx_link_speed_low_threshold_mbps",
                 DEFAULT_RX_LINK_SPEED_LOW_THRESHOLD_MBPS);
+        mWifiBatterySaverEnabled = DeviceConfig.getBoolean(NAMESPACE, "battery_saver_enabled",
+                false);
         mHealthMonitorShortConnectionDurationThrMs = DeviceConfig.getInt(NAMESPACE,
                 "health_monitor_short_connection_duration_thr_ms",
                 DEFAULT_HEALTH_MONITOR_SHORT_CONNECTION_DURATION_THR_MS);
@@ -349,6 +368,14 @@
         mRssiThresholdNotSendLowScoreToCsDbm = DeviceConfig.getInt(NAMESPACE,
                 "rssi_threshold_not_send_low_score_to_cs_dbm",
                 DEFAULT_RSSI_THRESHOLD_NOT_SEND_LOW_SCORE_TO_CS_DBM);
+        mAllowEnhancedMacRandomizationOnOpenSsids = DeviceConfig.getBoolean(NAMESPACE,
+                "allow_enhanced_mac_randomization_on_open_ssids", false);
+        mTrafficStatsThresholdMaxKbyte = DeviceConfig.getInt(NAMESPACE,
+                "traffic_stats_threshold_max_kbyte", DEFAULT_TRAFFIC_STATS_THRESHOLD_MAX_KB);
+        mBandwidthEstimatorLargeTimeConstantSec = DeviceConfig.getInt(NAMESPACE,
+                "bandwidth_estimator_time_constant_large_sec",
+                DEFAULT_BANDWIDTH_ESTIMATOR_TIME_CONSTANT_LARGE_SEC);
+
     }
 
     private Set<String> getUnmodifiableSetQuoted(String key) {
@@ -481,6 +508,20 @@
     }
 
     /**
+     * Gets connection-failure-due-to-disconnection min count
+     */
+    public int getConnectionFailureDisconnectionCountMin() {
+        return mConnectionFailureDisconnectionCountMin;
+    }
+
+    /**
+     * Gets the high threshold of connection-failure-due-to-disconnection rate in percent
+     */
+    public int getConnectionFailureDisconnectionHighThrPercent() {
+        return mConnectionFailureDisconnectionHighThrPercent;
+    }
+
+    /**
      * Gets connection failure min count
      */
     public int getConnectionFailureCountMin() {
@@ -656,6 +697,13 @@
     }
 
     /**
+     * Gets the feature flag for Wifi battery saver.
+     */
+    public boolean isWifiBatterySaverEnabled() {
+        return mWifiBatterySaverEnabled;
+    }
+
+    /**
      * Gets health monitor short connection duration threshold in ms
      */
     public int getHealthMonitorShortConnectionDurationThrMs() {
@@ -721,4 +769,26 @@
     public int getRssiThresholdNotSendLowScoreToCsDbm() {
         return mRssiThresholdNotSendLowScoreToCsDbm;
     }
+
+    /**
+     * Gets whether enhanced MAC randomization should be allowed on open networks.
+     */
+    public boolean allowEnhancedMacRandomizationOnOpenSsids() {
+        return mAllowEnhancedMacRandomizationOnOpenSsids;
+    }
+
+    /**
+     * Gets traffic stats maximum threshold in KByte
+     */
+    public int getTrafficStatsThresholdMaxKbyte() {
+        return mTrafficStatsThresholdMaxKbyte;
+    }
+
+    /**
+     * Gets bandwidth estimator large time constant in second
+     */
+    public int getBandwidthEstimatorLargeTimeConstantSec() {
+        return mBandwidthEstimatorLargeTimeConstantSec;
+    }
+
 }
diff --git a/service/java/com/android/server/wifi/DisconnectEventInfo.java b/service/java/com/android/server/wifi/DisconnectEventInfo.java
new file mode 100644
index 0000000..2edaefc
--- /dev/null
+++ b/service/java/com/android/server/wifi/DisconnectEventInfo.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.wifi;
+
+import android.annotation.NonNull;
+
+import java.util.Objects;
+
+/**
+ * Stores disconnect information passed from WifiMonitor.
+ */
+public class DisconnectEventInfo {
+    @NonNull public final String ssid;
+    @NonNull public final String bssid;
+    public final int reasonCode;
+    public final boolean locallyGenerated;
+
+    public DisconnectEventInfo(@NonNull String ssid, @NonNull String bssid, int reasonCode,
+            boolean locallyGenerated) {
+        this.ssid = Objects.requireNonNull(ssid);
+        this.bssid = Objects.requireNonNull(bssid);
+        this.reasonCode = reasonCode;
+        this.locallyGenerated = locallyGenerated;
+    }
+
+    @Override
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        sb.append(" ssid: ").append(ssid);
+        sb.append(" bssid: ").append(bssid);
+        sb.append(" reasonCode: ").append(reasonCode);
+        sb.append(" locallyGenerated: ").append(locallyGenerated);
+        return sb.toString();
+    }
+}
diff --git a/service/java/com/android/server/wifi/DppManager.java b/service/java/com/android/server/wifi/DppManager.java
index 02b1191..b0e1556 100644
--- a/service/java/com/android/server/wifi/DppManager.java
+++ b/service/java/com/android/server/wifi/DppManager.java
@@ -18,30 +18,39 @@
 
 import static android.net.wifi.WifiManager.EASY_CONNECT_NETWORK_ROLE_AP;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.wifi.supplicant.V1_2.DppAkm;
 import android.hardware.wifi.supplicant.V1_2.DppNetRole;
-import android.hardware.wifi.supplicant.V1_3.DppFailureCode;
 import android.hardware.wifi.supplicant.V1_3.DppProgressCode;
 import android.hardware.wifi.supplicant.V1_3.DppSuccessCode;
+import android.hardware.wifi.supplicant.V1_4.DppCurve;
+import android.hardware.wifi.supplicant.V1_4.DppFailureCode;
 import android.net.wifi.EasyConnectStatusCallback;
 import android.net.wifi.IDppCallback;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
+import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseArray;
 
+import androidx.annotation.RequiresApi;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.WakeupMessage;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.WifiNative.DppEventCallback;
 import com.android.server.wifi.util.ApConfigUtil;
+import com.android.server.wifi.util.WifiPermissionsUtil;
 
 import java.util.ArrayList;
 import java.util.List;
+
 /**
  * DPP Manager class
  * Implements the DPP Initiator APIs and callbacks
@@ -61,8 +70,13 @@
     private final Clock mClock;
     private static final String DPP_TIMEOUT_TAG = TAG + " Request Timeout";
     private static final int DPP_TIMEOUT_MS = 40_000; // 40 seconds
+    private static final int DPP_RESPONDER_TIMEOUT_MS = 300_000; // 5 minutes
+    public static final int DPP_AUTH_ROLE_INACTIVE = -1;
+    public static final int DPP_AUTH_ROLE_INITIATOR = 0;
+    public static final int DPP_AUTH_ROLE_RESPONDER = 1;
     private final DppMetrics mDppMetrics;
     private final ScanRequestProxy mScanRequestProxy;
+    private final WifiPermissionsUtil mWifiPermissionsUtil;
 
     private final DppEventCallback mDppEventCallback = new DppEventCallback() {
         @Override
@@ -95,7 +109,8 @@
     };
 
     DppManager(Handler handler, WifiNative wifiNative, WifiConfigManager wifiConfigManager,
-            Context context, DppMetrics dppMetrics, ScanRequestProxy scanRequestProxy) {
+            Context context, DppMetrics dppMetrics, ScanRequestProxy scanRequestProxy,
+            WifiPermissionsUtil wifiPermissionsUtil) {
         mHandler = handler;
         mWifiNative = wifiNative;
         mWifiConfigManager = wifiConfigManager;
@@ -104,6 +119,7 @@
         mClock = new Clock();
         mDppMetrics = dppMetrics;
         mScanRequestProxy = scanRequestProxy;
+        mWifiPermissionsUtil = wifiPermissionsUtil;
 
         // Setup timer
         mDppTimeoutMessage = new WakeupMessage(mContext, mHandler,
@@ -149,22 +165,26 @@
      * Start DPP request in Configurator-Initiator mode. The goal of this call is to send the
      * selected Wi-Fi configuration to a remote peer so it could join that network.
      *
-     * @param uid                 User ID
+     * @param uid                 UID
+     * @param packageName         Package name of the calling app
+     * @param clientIfaceName     Client interface to use for this operation.
      * @param binder              Binder object
      * @param enrolleeUri         The Enrollee URI, scanned externally (e.g. via QR code)
      * @param selectedNetworkId   The selected Wi-Fi network ID to be sent
      * @param enrolleeNetworkRole Network role of remote enrollee: STA or AP
      * @param callback            DPP Callback object
      */
-    public void startDppAsConfiguratorInitiator(int uid, IBinder binder,
-            String enrolleeUri, int selectedNetworkId,
-            @WifiManager.EasyConnectNetworkRole int enrolleeNetworkRole, IDppCallback callback) {
+    public void startDppAsConfiguratorInitiator(int uid, @Nullable String packageName,
+            @Nullable String clientIfaceName, IBinder binder, String enrolleeUri,
+            int selectedNetworkId, @WifiManager.EasyConnectNetworkRole int enrolleeNetworkRole,
+            IDppCallback callback) {
         mDppMetrics.updateDppConfiguratorInitiatorRequests();
-        if (mDppRequestInfo != null) {
+        if (isSessionInProgress()) {
             try {
                 Log.e(TAG, "DPP request already in progress");
-                Log.e(TAG, "Ongoing request UID: " + mDppRequestInfo.uid + ", new UID: "
-                        + uid);
+                Log.e(TAG, "Ongoing request - UID: " + mDppRequestInfo.uid
+                        + " Package: " + mDppRequestInfo.packageName
+                        + ", New request - UID: " + uid + " Package: " + packageName);
 
                 mDppMetrics.updateDppFailure(EasyConnectStatusCallback
                         .EASY_CONNECT_EVENT_FAILURE_BUSY);
@@ -177,7 +197,7 @@
             return;
         }
 
-        mClientIfaceName = mWifiNative.getClientInterfaceName();
+        mClientIfaceName = clientIfaceName;
         if (mClientIfaceName == null) {
             try {
                 Log.e(TAG, "Wi-Fi client interface does not exist");
@@ -214,11 +234,8 @@
         int securityAkm;
 
         // Currently support either SAE mode or PSK mode
-        if (selectedNetwork.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
-            // SAE
-            password = selectedNetwork.preSharedKey;
-            securityAkm = DppAkm.SAE;
-        } else if (selectedNetwork.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
+        // Check PSK first because PSK config always has a SAE type as a upgrading type.
+        if (selectedNetwork.isSecurityType(WifiConfiguration.SECURITY_TYPE_PSK)) {
             if (selectedNetwork.preSharedKey.matches(String.format("[0-9A-Fa-f]{%d}", 64))) {
                 // PSK
                 psk = selectedNetwork.preSharedKey;
@@ -227,6 +244,10 @@
                 password = selectedNetwork.preSharedKey;
             }
             securityAkm = DppAkm.PSK;
+        } else if (selectedNetwork.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE)) {
+            // SAE
+            password = selectedNetwork.preSharedKey;
+            securityAkm = DppAkm.SAE;
         } else {
             try {
                 // Key management must be either PSK or SAE
@@ -244,8 +265,10 @@
 
         mDppRequestInfo = new DppRequestInfo();
         mDppRequestInfo.uid = uid;
+        mDppRequestInfo.packageName = packageName;
         mDppRequestInfo.binder = binder;
         mDppRequestInfo.callback = callback;
+        mDppRequestInfo.authRole = DPP_AUTH_ROLE_INITIATOR;
 
         if (!linkToDeath(mDppRequestInfo)) {
             // Notify failure and clean up
@@ -300,15 +323,16 @@
      * Start DPP request in Enrollee-Initiator mode. The goal of this call is to receive a
      * Wi-Fi configuration object from the peer configurator in order to join a network.
      *
-     * @param uid             User ID
+     * @param uid             UID
+     * @param clientIfaceName     Client interface to use for this operation.
      * @param binder          Binder object
      * @param configuratorUri The Configurator URI, scanned externally (e.g. via QR code)
      * @param callback        DPP Callback object
      */
-    public void startDppAsEnrolleeInitiator(int uid, IBinder binder,
-            String configuratorUri, IDppCallback callback) {
+    public void startDppAsEnrolleeInitiator(int uid, @Nullable String clientIfaceName,
+            IBinder binder, String configuratorUri, IDppCallback callback) {
         mDppMetrics.updateDppEnrolleeInitiatorRequests();
-        if (mDppRequestInfo != null) {
+        if (isSessionInProgress()) {
             try {
                 Log.e(TAG, "DPP request already in progress");
                 Log.e(TAG, "Ongoing request UID: " + mDppRequestInfo.uid + ", new UID: "
@@ -329,6 +353,7 @@
         mDppRequestInfo.uid = uid;
         mDppRequestInfo.binder = binder;
         mDppRequestInfo.callback = callback;
+        mDppRequestInfo.authRole = DPP_AUTH_ROLE_INITIATOR;
 
         if (!linkToDeath(mDppRequestInfo)) {
             // Notify failure and clean up
@@ -339,7 +364,7 @@
         mDppRequestInfo.startTime = mClock.getElapsedSinceBootMillis();
         mDppTimeoutMessage.schedule(mDppRequestInfo.startTime + DPP_TIMEOUT_MS);
 
-        mClientIfaceName = mWifiNative.getClientInterfaceName();
+        mClientIfaceName = clientIfaceName;
         logd("Interface " + mClientIfaceName + ": Initializing URI: " + configuratorUri);
 
         // Send Configurator URI and get a peer ID
@@ -369,12 +394,103 @@
     }
 
     /**
+     * Start DPP request in Enrollee-Responder mode. The goal of this call is to receive a
+     * Wi-Fi configuration object from the peer configurator by showing a QR code and being scanned
+     * by the peer configurator.
+     *
+     * @param uid             UID
+     * @param clientIfaceName Client interface to use for this operation.
+     * @param binder          Binder object
+     * @param deviceInfo      The Device specific info to attach to the generated URI
+     * @param curve           Elliptic curve cryptography type used to generate DPP
+     *                        public/private key pair.
+     * @param callback        DPP Callback object
+     */
+    public void startDppAsEnrolleeResponder(int uid, @Nullable String clientIfaceName,
+            IBinder binder, @Nullable String deviceInfo,
+            @WifiManager.EasyConnectCryptographyCurve int curve, IDppCallback callback) {
+        mDppMetrics.updateDppEnrolleeResponderRequests();
+        if (isSessionInProgress()) {
+            try {
+                Log.e(TAG, "DPP request already in progress");
+                Log.e(TAG, "Ongoing request UID: " + mDppRequestInfo.uid + ", new UID: "
+                        + uid);
+
+                mDppMetrics.updateDppFailure(EasyConnectStatusCallback
+                        .EASY_CONNECT_EVENT_FAILURE_BUSY);
+                // On going DPP. Call the failure callback directly
+                callback.onFailure(EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_BUSY,
+                        null, null, new int[0]);
+            } catch (RemoteException e) {
+                // Empty
+            }
+            return;
+        }
+
+        mDppRequestInfo = new DppRequestInfo();
+        mDppRequestInfo.uid = uid;
+        mDppRequestInfo.binder = binder;
+        mDppRequestInfo.callback = callback;
+        mDppRequestInfo.authRole = DPP_AUTH_ROLE_RESPONDER;
+
+        if (!linkToDeath(mDppRequestInfo)) {
+            // Notify failure and clean up
+            onFailure(DppFailureCode.FAILURE);
+            return;
+        }
+
+        mDppRequestInfo.startTime = mClock.getElapsedSinceBootMillis();
+        mDppTimeoutMessage.schedule(mDppRequestInfo.startTime + DPP_RESPONDER_TIMEOUT_MS);
+
+        mClientIfaceName = clientIfaceName;
+        logd("Interface " + mClientIfaceName + " Product Info: " + deviceInfo
+                + " Curve: " + curve);
+
+        String info = deviceInfo == null ? "" : deviceInfo;
+        // Generate a QR code based bootstrap info
+        WifiNative.DppBootstrapQrCodeInfo bootstrapInfo = null;
+        if (SdkLevel.isAtLeastS()) {
+            bootstrapInfo =
+                    mWifiNative.generateDppBootstrapInfoForResponder(mClientIfaceName, deviceInfo,
+                            convertEasyConnectCryptographyCurveToHidlDppCurve(curve));
+        }
+
+        if (bootstrapInfo == null || bootstrapInfo.bootstrapId < 0
+                || TextUtils.isEmpty(bootstrapInfo.uri)) {
+            Log.e(TAG, "DPP request to generate URI failed");
+            onFailure(DppFailureCode.URI_GENERATION);
+            return;
+        }
+
+        mDppRequestInfo.bootstrapId = bootstrapInfo.bootstrapId;
+        logd("BootstrapId:" + mDppRequestInfo.bootstrapId + " URI: " + bootstrapInfo.uri);
+
+        if (!mWifiNative.startDppEnrolleeResponder(mClientIfaceName, bootstrapInfo.listenChannel)) {
+            Log.e(TAG, "DPP Start Enrollee Responder failure");
+            // Notify failure and clean up
+            onFailure(DppFailureCode.FAILURE);
+            return;
+        }
+
+        logd("Success: Started DPP Enrollee Responder on listen channel "
+                + bootstrapInfo.listenChannel);
+
+        try {
+            mDppRequestInfo.callback.onBootstrapUriGenerated(bootstrapInfo.uri);
+        } catch (RemoteException e) {
+            Log.e(TAG, " onBootstrapUriGenerated Callback failure");
+            onFailure(DppFailureCode.FAILURE);
+            return;
+        }
+    }
+
+    /**
      * Stop a current DPP session
      *
      * @param uid User ID
      */
     public void stopDppSession(int uid) {
-        if (mDppRequestInfo == null) {
+        if (!isSessionInProgress()) {
             logd("UID " + uid + " called stop DPP session with no active DPP session");
             return;
         }
@@ -386,18 +502,20 @@
         }
 
         // Clean up supplicant resources
-        if (!mWifiNative.stopDppInitiator(mClientIfaceName)) {
-            Log.e(TAG, "Failed to stop DPP Initiator");
+        if (mDppRequestInfo.authRole == DPP_AUTH_ROLE_INITIATOR) {
+            if (!mWifiNative.stopDppInitiator(mClientIfaceName)) {
+                Log.e(TAG, "Failed to stop DPP Initiator");
+            }
         }
 
         cleanupDppResources();
 
-        logd("Success: Stopped DPP Initiator");
+        logd("Success: Stopped DPP Session");
     }
 
     private void cleanupDppResources() {
         logd("DPP clean up resources");
-        if (mDppRequestInfo == null) {
+        if (!isSessionInProgress()) {
             return;
         }
 
@@ -405,8 +523,14 @@
         mDppTimeoutMessage.cancel();
 
         // Remove the URI from the supplicant list
-        if (!mWifiNative.removeDppUri(mClientIfaceName, mDppRequestInfo.peerId)) {
-            Log.e(TAG, "Failed to remove DPP URI ID " + mDppRequestInfo.peerId);
+        if (mDppRequestInfo.authRole == DPP_AUTH_ROLE_INITIATOR) {
+            if (!mWifiNative.removeDppUri(mClientIfaceName, mDppRequestInfo.peerId)) {
+                Log.e(TAG, "Failed to remove DPP URI ID " + mDppRequestInfo.peerId);
+            }
+        } else if (mDppRequestInfo.authRole == DPP_AUTH_ROLE_RESPONDER) {
+            if (!mWifiNative.stopDppResponder(mClientIfaceName, mDppRequestInfo.bootstrapId)) {
+                Log.e(TAG, "Failed to stop DPP Responder");
+            }
         }
 
         mDppRequestInfo.binder.unlinkToDeath(mDppRequestInfo.dr, 0);
@@ -414,20 +538,32 @@
         mDppRequestInfo = null;
     }
 
+    /**
+     * Indicates whether there is a dpp session in progress or not.
+     */
+    public boolean isSessionInProgress() {
+        return mDppRequestInfo != null;
+    }
+
     private static class DppRequestInfo {
         public int uid;
+        public String packageName;
         public IBinder binder;
         public IBinder.DeathRecipient dr;
         public int peerId;
         public IDppCallback callback;
         public long startTime;
+        public int authRole = DPP_AUTH_ROLE_INACTIVE;
+        public int bootstrapId;
 
         @Override
         public String toString() {
             return new StringBuilder("DppRequestInfo: uid=").append(uid).append(", binder=").append(
                     binder).append(", dr=").append(dr)
                     .append(", callback=").append(callback)
-                    .append(", peerId=").append(peerId).toString();
+                    .append(", peerId=").append(peerId)
+                    .append(", authRole=").append(authRole)
+                    .append(", bootstrapId=").append(bootstrapId).toString();
         }
     }
 
@@ -453,8 +589,14 @@
 
                 if (networkUpdateResult.isSuccess()) {
                     mDppMetrics.updateDppEnrolleeSuccess();
+                    if (mDppRequestInfo.authRole == DPP_AUTH_ROLE_RESPONDER) {
+                        mDppMetrics.updateDppEnrolleeResponderSuccess();
+                    }
                     mDppRequestInfo.callback.onSuccessConfigReceived(
-                            networkUpdateResult.getNetworkId());
+                            WifiConfigurationUtil.addSecurityTypeToNetworkId(
+                                    networkUpdateResult.getNetworkId(),
+                                    newWifiConfiguration.getDefaultSecurityParams()
+                                            .getSecurityType()));
                 } else {
                     Log.e(TAG, "DPP configuration received, but failed to update network");
                     mDppMetrics.updateDppFailure(EasyConnectStatusCallback
@@ -642,14 +784,29 @@
         }
 
         if (isNetworkInScanCache & !channelMatch) {
-            Log.d(TAG, "Update the error code to NOT_COMPATIBLE"
-                    + " as enrollee didn't scan network's operating channel");
+            Log.d(TAG, "Optionally update the error code to "
+                    + "ENROLLEE_FAILED_TO_SCAN_NETWORK_CHANNEL as enrollee didn't scan"
+                    + "network's operating channel");
             mDppMetrics.updateDppR2EnrolleeResponderIncompatibleConfiguration();
             return false;
         }
         return true;
     }
 
+    private @EasyConnectStatusCallback.EasyConnectFailureStatusCode int
+            getFailureStatusCodeOnEnrolleeInCompatibleWithNetwork() {
+        if (!SdkLevel.isAtLeastS() || mDppRequestInfo.packageName != null
+                && mWifiPermissionsUtil.isTargetSdkLessThan(
+                mDppRequestInfo.packageName, Build.VERSION_CODES.S,
+                mDppRequestInfo.uid)) {
+            return EasyConnectStatusCallback
+                    .EASY_CONNECT_EVENT_FAILURE_NOT_COMPATIBLE;
+        } else {
+            return EasyConnectStatusCallback
+                    .EASY_CONNECT_EVENT_FAILURE_ENROLLEE_FAILED_TO_SCAN_NETWORK_CHANNEL;
+        }
+    }
+
     private void onFailure(int dppStatusCode, String ssid, String channelList, int[] bandList) {
         try {
             if (mDppRequestInfo == null) {
@@ -707,9 +864,7 @@
                                 EasyConnectStatusCallback
                                 .EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK;
                     } else {
-                        dppFailureCode =
-                                EasyConnectStatusCallback
-                                .EASY_CONNECT_EVENT_FAILURE_NOT_COMPATIBLE;
+                        dppFailureCode = getFailureStatusCodeOnEnrolleeInCompatibleWithNetwork();
                     }
                     break;
 
@@ -723,6 +878,16 @@
                             .EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION;
                     break;
 
+                case DppFailureCode.URI_GENERATION:
+                    if (SdkLevel.isAtLeastS()) {
+                        dppFailureCode = EasyConnectStatusCallback
+                                .EASY_CONNECT_EVENT_FAILURE_URI_GENERATION;
+                    } else {
+                        dppFailureCode = EasyConnectStatusCallback
+                                .EASY_CONNECT_EVENT_FAILURE_GENERIC;
+                    }
+                    break;
+
                 case DppFailureCode.FAILURE:
                 default:
                     dppFailureCode = EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_GENERIC;
@@ -776,4 +941,24 @@
 
         return true;
     }
+
+    @RequiresApi(Build.VERSION_CODES.S)
+    private int convertEasyConnectCryptographyCurveToHidlDppCurve(
+            @WifiManager.EasyConnectCryptographyCurve int curve) {
+        switch (curve) {
+            case WifiManager.EASY_CONNECT_CRYPTOGRAPHY_CURVE_SECP384R1:
+                return DppCurve.SECP384R1;
+            case WifiManager.EASY_CONNECT_CRYPTOGRAPHY_CURVE_SECP521R1:
+                return DppCurve.SECP521R1;
+            case WifiManager.EASY_CONNECT_CRYPTOGRAPHY_CURVE_BRAINPOOLP256R1:
+                return DppCurve.BRAINPOOLP256R1;
+            case WifiManager.EASY_CONNECT_CRYPTOGRAPHY_CURVE_BRAINPOOLP384R1:
+                return DppCurve.BRAINPOOLP384R1;
+            case WifiManager.EASY_CONNECT_CRYPTOGRAPHY_CURVE_BRAINPOOLP512R1:
+                return DppCurve.BRAINPOOLP512R1;
+            case WifiManager.EASY_CONNECT_CRYPTOGRAPHY_CURVE_PRIME256V1:
+            default:
+                return DppCurve.PRIME256V1;
+        }
+    }
 }
diff --git a/service/java/com/android/server/wifi/DppMetrics.java b/service/java/com/android/server/wifi/DppMetrics.java
index bd62313..5adba10 100644
--- a/service/java/com/android/server/wifi/DppMetrics.java
+++ b/service/java/com/android/server/wifi/DppMetrics.java
@@ -21,6 +21,7 @@
 import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK;
 import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_CONFIGURATION;
 import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION;
+import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_ENROLLEE_FAILED_TO_SCAN_NETWORK_CHANNEL;
 import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION;
 import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_GENERIC;
 import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK;
@@ -28,6 +29,7 @@
 import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_NOT_COMPATIBLE;
 import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_NOT_SUPPORTED;
 import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_TIMEOUT;
+import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_URI_GENERATION;
 import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_APPLIED;
 import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT;
 
@@ -85,6 +87,24 @@
     }
 
     /**
+     * Update DPP Enrollee-Responder requests
+     */
+    public void updateDppEnrolleeResponderRequests() {
+        synchronized (mLock) {
+            mWifiDppLogProto.numDppEnrolleeResponderRequests++;
+        }
+    }
+
+    /**
+     * Update DPP Enrollee-Responder success counter
+     */
+    public void updateDppEnrolleeResponderSuccess() {
+        synchronized (mLock) {
+            mWifiDppLogProto.numDppEnrolleeResponderSuccess++;
+        }
+    }
+
+    /**
      * Update DPP Enrollee success counter
      */
     public void updateDppEnrolleeSuccess() {
@@ -226,6 +246,19 @@
                                     .EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION)
                                     + 1);
                     break;
+                case EASY_CONNECT_EVENT_FAILURE_URI_GENERATION:
+                    mHistogramDppFailureCode.put(WifiMetricsProto.WifiDppLog
+                                    .EASY_CONNECT_EVENT_FAILURE_URI_GENERATION,
+                            mHistogramDppFailureCode.get(WifiMetricsProto.WifiDppLog
+                                    .EASY_CONNECT_EVENT_FAILURE_URI_GENERATION) + 1);
+                    break;
+                case EASY_CONNECT_EVENT_FAILURE_ENROLLEE_FAILED_TO_SCAN_NETWORK_CHANNEL:
+                    mHistogramDppFailureCode.put(WifiMetricsProto.WifiDppLog
+                            .EASY_CONNECT_EVENT_FAILURE_ENROLLEE_FAILED_TO_SCAN_NETWORK_CHANNEL,
+                            mHistogramDppFailureCode.get(WifiMetricsProto.WifiDppLog
+                            .EASY_CONNECT_EVENT_FAILURE_ENROLLEE_FAILED_TO_SCAN_NETWORK_CHANNEL)
+                            + 1);
+                    break;
                 default:
                     break;
             }
@@ -255,6 +288,10 @@
                     + mWifiDppLogProto.numDppConfiguratorInitiatorRequests);
             pw.println("mWifiDppLogProto.numDppEnrolleeInitiatorRequests="
                     + mWifiDppLogProto.numDppEnrolleeInitiatorRequests);
+            pw.println("mWifiDppLogProto.numDppEnrolleeResponderRequests="
+                    + mWifiDppLogProto.numDppEnrolleeResponderRequests);
+            pw.println("mWifiDppLogProto.numDppEnrolleeResponderSuccess="
+                    + mWifiDppLogProto.numDppEnrolleeResponderSuccess);
             pw.println("mWifiDppLogProto.numDppEnrolleeSuccess="
                     + mWifiDppLogProto.numDppEnrolleeSuccess);
             pw.println("mWifiDppLogProto.numDppR1CapableEnrolleeResponderDevices="
@@ -289,6 +326,8 @@
         synchronized (mLock) {
             mWifiDppLogProto.numDppConfiguratorInitiatorRequests = 0;
             mWifiDppLogProto.numDppEnrolleeInitiatorRequests = 0;
+            mWifiDppLogProto.numDppEnrolleeResponderRequests = 0;
+            mWifiDppLogProto.numDppEnrolleeResponderSuccess = 0;
             mWifiDppLogProto.numDppEnrolleeSuccess = 0;
             mWifiDppLogProto.numDppR1CapableEnrolleeResponderDevices = 0;
             mWifiDppLogProto.numDppR2CapableEnrolleeResponderDevices = 0;
@@ -342,6 +381,8 @@
             log.numDppConfiguratorInitiatorRequests =
                     mWifiDppLogProto.numDppConfiguratorInitiatorRequests;
             log.numDppEnrolleeInitiatorRequests = mWifiDppLogProto.numDppEnrolleeInitiatorRequests;
+            log.numDppEnrolleeResponderRequests = mWifiDppLogProto.numDppEnrolleeResponderRequests;
+            log.numDppEnrolleeResponderSuccess = mWifiDppLogProto.numDppEnrolleeResponderSuccess;
             log.numDppEnrolleeSuccess = mWifiDppLogProto.numDppEnrolleeSuccess;
             log.numDppR1CapableEnrolleeResponderDevices =
                     mWifiDppLogProto.numDppR1CapableEnrolleeResponderDevices;
diff --git a/service/java/com/android/server/wifi/EapFailureNotifier.java b/service/java/com/android/server/wifi/EapFailureNotifier.java
index 1d82082..e76d4c8 100644
--- a/service/java/com/android/server/wifi/EapFailureNotifier.java
+++ b/service/java/com/android/server/wifi/EapFailureNotifier.java
@@ -16,29 +16,22 @@
 
 package com.android.server.wifi;
 
-import android.app.ActivityManager;
 import android.app.Notification;
-import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.graphics.drawable.Icon;
 import android.net.wifi.WifiConfiguration;
-import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
 import android.telephony.SubscriptionManager;
 import android.text.TextUtils;
-import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 
-import java.util.List;
-
 /**
  * This class may be used to launch notifications when EAP failure occurs.
  */
@@ -48,7 +41,7 @@
 
     private static final long CANCEL_TIMEOUT_MILLISECONDS = 5 * 60 * 1000;
     private final WifiContext mContext;
-    private final NotificationManager mNotificationManager;
+    private final WifiNotificationManager mNotificationManager;
     private final FrameworkFacade mFrameworkFacade;
     private final WifiCarrierInfoManager mWifiCarrierInfoManager;
 
@@ -57,58 +50,43 @@
     private String mCurrentShownSsid;
 
     public EapFailureNotifier(WifiContext context, FrameworkFacade frameworkFacade,
-            WifiCarrierInfoManager wifiCarrierInfoManager) {
+            WifiCarrierInfoManager wifiCarrierInfoManager,
+            WifiNotificationManager wifiNotificationManager) {
         mContext = context;
         mFrameworkFacade = frameworkFacade;
         mWifiCarrierInfoManager = wifiCarrierInfoManager;
-        mNotificationManager =
-                mContext.getSystemService(NotificationManager.class);
+        mNotificationManager = wifiNotificationManager;
     }
 
     /**
      * Invoked when EAP failure occurs.
      *
      * @param errorCode error code which delivers from supplicant
+     * @param showNotification whether to display the notification
+     * @return true if the receiving error code is found in wifi resource
      */
-    public void onEapFailure(int errorCode, WifiConfiguration config) {
+    public boolean onEapFailure(int errorCode, WifiConfiguration config, boolean showNotification) {
+        Resources res = getResourcesForSubId(mContext,
+                mWifiCarrierInfoManager.getBestMatchSubscriptionId(config));
+        if (res == null) return false;
+        int resourceId = res.getIdentifier(ERROR_MESSAGE_OVERLAY_PREFIX + errorCode,
+                "string", mContext.getWifiOverlayApkPkgName());
+
+        if (resourceId == 0) return false;
+        String errorMessage = res.getString(resourceId, config.SSID);
+        if (TextUtils.isEmpty(errorMessage)) return false;
         StatusBarNotification[] activeNotifications = mNotificationManager.getActiveNotifications();
         for (StatusBarNotification activeNotification : activeNotifications) {
             if ((activeNotification.getId() == NOTIFICATION_ID)
                     && TextUtils.equals(config.SSID, mCurrentShownSsid)) {
-                return;
+                return true;
             }
         }
-        Resources res = getResourcesForSubId(mContext,
-                mWifiCarrierInfoManager.getBestMatchSubscriptionId(config));
-        if (res == null) return;
-        int resourceId = res.getIdentifier(
-                ERROR_MESSAGE_OVERLAY_PREFIX + errorCode,
-                "string",
-                // getIdentifier seems to use the Java package name rather than the Android
-                // application package name. i.e. what you would have used if the resource name was
-                // statically known:
-                // import com.android.wifi.resources.R;
-                // ...
-                // R.string.wifi_eap_error_message_code_###
-                mContext.getWifiOverlayJavaPkgName());
 
-        if (resourceId == 0) return;
-        String errorMessage = res.getString(resourceId, config.SSID);
-        if (TextUtils.isEmpty(errorMessage)) return;
-        showNotification(errorMessage, config.SSID);
-    }
-
-    private String getSettingsPackageName() {
-        Intent intent = new Intent(Settings.ACTION_WIFI_SETTINGS);
-        List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentActivitiesAsUser(
-                intent, PackageManager.MATCH_SYSTEM_ONLY | PackageManager.MATCH_DEFAULT_ONLY,
-                UserHandle.of(ActivityManager.getCurrentUser()));
-        if (resolveInfos == null || resolveInfos.isEmpty()) {
-            Log.e(TAG, "Failed to resolve wifi settings activity");
-            return null;
+        if (showNotification) {
+            showNotification(errorMessage, config.SSID);
         }
-        // Pick the first one if there are more than 1 since the list is ordered from best to worst.
-        return resolveInfos.get(0).activityInfo.packageName;
+        return true;
     }
 
     /**
@@ -117,7 +95,7 @@
      * @param ssid Error Message which defined by carrier
      */
     private void showNotification(String errorMessage, String ssid) {
-        String settingsPackage = getSettingsPackageName();
+        String settingsPackage = mFrameworkFacade.getSettingsPackageName(mContext);
         if (settingsPackage == null) return;
         Intent intent = new Intent(Settings.ACTION_WIFI_SETTINGS)
                 .setPackage(settingsPackage);
@@ -132,10 +110,12 @@
                 .setContentText(errorMessage)
                 .setStyle(new Notification.BigTextStyle().bigText(errorMessage))
                 .setContentIntent(mFrameworkFacade.getActivity(
-                        mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT))
+                        mContext, 0, intent,
+                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
                 .setColor(mContext.getResources().getColor(
                         android.R.color.system_notification_accent_color));
-        mNotificationManager.notify(NOTIFICATION_ID, builder.build());
+        mNotificationManager.notify(NOTIFICATION_ID,
+                builder.build());
         mCurrentShownSsid = ssid;
     }
 
diff --git a/service/java/com/android/server/wifi/ExtendedWifiInfo.java b/service/java/com/android/server/wifi/ExtendedWifiInfo.java
index ef0ef0f..2f9f016 100644
--- a/service/java/com/android/server/wifi/ExtendedWifiInfo.java
+++ b/service/java/com/android/server/wifi/ExtendedWifiInfo.java
@@ -17,11 +17,8 @@
 package com.android.server.wifi;
 
 import android.annotation.NonNull;
-import android.content.Context;
 import android.net.wifi.WifiInfo;
 
-import com.android.wifi.resources.R;
-
 /**
  * Extends WifiInfo with the methods for computing the averaged packet rates
  */
@@ -32,14 +29,15 @@
     private static final int SOURCE_TRAFFIC_COUNTERS = 1;
     private static final int SOURCE_LLSTATS = 2;
 
-    private final Context mContext;
+    private final WifiGlobals mWifiGlobals;
+    private final String mIfaceName;
 
     private int mLastSource = SOURCE_UNKNOWN;
     private long mLastPacketCountUpdateTimeStamp = RESET_TIME_STAMP;
 
-    ExtendedWifiInfo(Context context) {
-        super();
-        mContext = context;
+    ExtendedWifiInfo(WifiGlobals wifiGlobals, String ifaceName) {
+        mWifiGlobals = wifiGlobals;
+        mIfaceName = ifaceName;
     }
 
     @Override
@@ -47,8 +45,7 @@
         super.reset();
         mLastSource = SOURCE_UNKNOWN;
         mLastPacketCountUpdateTimeStamp = RESET_TIME_STAMP;
-        if (mContext.getResources().getBoolean(
-                R.bool.config_wifi_connected_mac_randomization_supported)) {
+        if (mWifiGlobals.isConnectedMacRandomizationEnabled()) {
             setMacAddress(DEFAULT_MAC_ADDRESS);
         }
     }
@@ -113,4 +110,8 @@
         txRetries = txretries;
         mLastPacketCountUpdateTimeStamp = timeStamp;
     }
+
+    public String getIfaceName() {
+        return mIfaceName;
+    }
 }
diff --git a/service/java/com/android/server/wifi/ExternalScoreUpdateObserverProxy.java b/service/java/com/android/server/wifi/ExternalScoreUpdateObserverProxy.java
new file mode 100644
index 0000000..259c625
--- /dev/null
+++ b/service/java/com/android/server/wifi/ExternalScoreUpdateObserverProxy.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.annotation.NonNull;
+import android.net.wifi.IScoreUpdateObserver;
+import android.net.wifi.WifiManager;
+import android.util.Log;
+
+import com.android.modules.utils.build.SdkLevel;
+
+
+/**
+ * This is the callback proxy used to listen to external scorer actions triggered via
+ * {@link android.net.wifi.WifiManager.ScoreUpdateObserver}.
+ *
+ * <p>
+ * Note:
+ * <li>This was extracted out of {@link WifiScoreReport} class since that is not a singleton and
+ * is associated with a {@link ClientModeImpl} instance. However, this proxy sent to external scorer
+ * needs to be a singleton (i.e we cannot send the external scorer a new proxy every time
+ * a new primary {@link ClientModeImpl} is created).</li>
+ * <li> Whenever a new primary CMM is created, it needs to register to this proxy to listen for
+ * actions from the external scorer.</li>
+ * </p>
+ */
+public class ExternalScoreUpdateObserverProxy extends IScoreUpdateObserver.Stub {
+    private static final String TAG = "WifiExternalScoreUpdateObserverProxy";
+
+    private final WifiThreadRunner mWifiThreadRunner;
+    private WifiManager.ScoreUpdateObserver mCallback;
+
+    ExternalScoreUpdateObserverProxy(WifiThreadRunner wifiThreadRunner) {
+        mWifiThreadRunner = wifiThreadRunner;
+    }
+
+    /**
+     * Register a new callback to listen for events from external scorer.
+     */
+    public void registerCallback(@NonNull WifiManager.ScoreUpdateObserver callback) {
+        if (mCallback != null) {
+            Log.i(TAG, "Replacing an existing callback (new primary CMM created)");
+        }
+        mCallback = callback;
+    }
+
+    /**
+     * Unregister callback to listen for events from external scorer.
+     */
+    public void unregisterCallback(@NonNull WifiManager.ScoreUpdateObserver callback) {
+        // mCallback can be overwritten by another CMM, when we remove it we should only
+        // remove if it is the most recently registered mCallback
+        if (mCallback == callback) {
+            mCallback = null;
+        }
+    }
+
+    @Override
+    public void notifyScoreUpdate(int sessionId, int score) {
+        mWifiThreadRunner.post(() -> {
+            if (mCallback == null) {
+                Log.wtf(TAG, "No callback registered, dropping notifyScoreUpdate");
+                return;
+            }
+            mCallback.notifyScoreUpdate(sessionId, score);
+        });
+    }
+
+    @Override
+    public void triggerUpdateOfWifiUsabilityStats(int sessionId) {
+        mWifiThreadRunner.post(() -> {
+            if (mCallback == null) {
+                Log.wtf(TAG, "No callback registered, dropping triggerUpdateOfWifiUsability");
+                return;
+            }
+            mCallback.triggerUpdateOfWifiUsabilityStats(sessionId);
+        });
+    }
+
+    @Override
+    public void notifyStatusUpdate(int sessionId, boolean isUsable) {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        mWifiThreadRunner.post(() -> {
+            if (mCallback == null) {
+                Log.wtf(TAG, "No callback registered, dropping notifyStatusUpdate");
+                return;
+            }
+            mCallback.notifyStatusUpdate(sessionId, isUsable);
+        });
+    }
+
+    @Override
+    public void requestNudOperation(int sessionId) {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        mWifiThreadRunner.post(() -> {
+            if (mCallback == null) {
+                Log.wtf(TAG, "No callback registered, dropping requestNudOperation");
+                return;
+            }
+            mCallback.requestNudOperation(sessionId);
+        });
+    }
+
+    @Override
+    public void blocklistCurrentBssid(int sessionId) {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        mWifiThreadRunner.post(() -> {
+            if (mCallback == null) {
+                Log.wtf(TAG, "No callback registered, dropping requestNudOperation");
+                return;
+            }
+            mCallback.blocklistCurrentBssid(sessionId);
+        });
+    }
+}
diff --git a/service/java/com/android/server/wifi/FrameworkFacade.java b/service/java/com/android/server/wifi/FrameworkFacade.java
index 875154e..5bc57b5 100644
--- a/service/java/com/android/server/wifi/FrameworkFacade.java
+++ b/service/java/com/android/server/wifi/FrameworkFacade.java
@@ -19,6 +19,7 @@
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
 import static android.content.pm.PackageManager.FEATURE_DEVICE_ADMIN;
 
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.AlertDialog;
 import android.app.Notification;
@@ -27,19 +28,29 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
 import android.database.ContentObserver;
 import android.net.TrafficStats;
 import android.net.Uri;
 import android.net.ip.IpClientCallbacks;
 import android.net.ip.IpClientUtil;
 import android.os.PersistableBundle;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.WorkSource;
 import android.provider.Settings;
+import android.security.KeyChain;
 import android.telephony.CarrierConfigManager;
 import android.util.Log;
 import android.widget.Toast;
 
 import com.android.server.wifi.util.WifiAsyncChannel;
 
+import java.util.List;
+import java.util.NoSuchElementException;
+
 /**
  * This class allows overriding objects with mocks to write unit tests
  */
@@ -235,8 +246,13 @@
     /**
      * Starts supplicant
      */
-    public void startSupplicant() {
-        SupplicantManager.start();
+    public boolean startSupplicant() {
+        try {
+            SupplicantManager.start();
+            return true;
+        } catch (NoSuchElementException e) {
+            return false;
+        }
     }
 
     /**
@@ -252,7 +268,10 @@
      * @return an instance of AlertDialog.Builder
      */
     public AlertDialog.Builder makeAlertDialogBuilder(Context context) {
-        return new AlertDialog.Builder(context);
+        boolean isDarkTheme = (context.getResources().getConfiguration().uiMode
+                & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
+        return new AlertDialog.Builder(context, isDarkTheme
+                ? android.R.style.Theme_DeviceDefault_Dialog_Alert : 0);
     }
 
     /**
@@ -292,4 +311,48 @@
     public long getTotalRxBytes() {
         return TrafficStats.getTotalRxBytes();
     }
+
+    private String mSettingsPackageName;
+
+    /**
+     * @return Get settings package name.
+     */
+    public String getSettingsPackageName(@NonNull Context context) {
+        if (mSettingsPackageName != null) return mSettingsPackageName;
+
+        Intent intent = new Intent(Settings.ACTION_WIFI_SETTINGS);
+        List<ResolveInfo> resolveInfos = context.getPackageManager().queryIntentActivitiesAsUser(
+                intent, PackageManager.MATCH_SYSTEM_ONLY | PackageManager.MATCH_DEFAULT_ONLY,
+                UserHandle.of(ActivityManager.getCurrentUser()));
+        if (resolveInfos == null || resolveInfos.isEmpty()) {
+            Log.e(TAG, "Failed to resolve wifi settings activity");
+            return null;
+        }
+        // Pick the first one if there are more than 1 since the list is ordered from best to worst.
+        mSettingsPackageName = resolveInfos.get(0).activityInfo.packageName;
+        return mSettingsPackageName;
+    }
+
+    /**
+     * @return Get a worksource to blame settings apps.
+     */
+    public WorkSource getSettingsWorkSource(Context context) {
+        String settingsPackageName = getSettingsPackageName(context);
+        if (settingsPackageName == null) return new WorkSource(Process.SYSTEM_UID);
+        return new WorkSource(Process.SYSTEM_UID, settingsPackageName);
+    }
+
+    /**
+     * Returns whether a KeyChain key is granted to the WiFi stack.
+     */
+    public boolean hasWifiKeyGrantAsUser(Context context, UserHandle user, String alias) {
+        return KeyChain.hasWifiKeyGrantAsUser(context, user, alias);
+    }
+
+    /**
+     * Returns grant string for a given KeyChain alias or null if key not granted.
+     */
+    public String getWifiKeyGrantAsUser(Context context, UserHandle user, String alias) {
+        return KeyChain.getWifiKeyGrantAsUser(context, user, alias);
+    }
 }
diff --git a/service/java/com/android/server/wifi/HalDeviceManager.java b/service/java/com/android/server/wifi/HalDeviceManager.java
index 3d0c89c..5462bb9 100644
--- a/service/java/com/android/server/wifi/HalDeviceManager.java
+++ b/service/java/com/android/server/wifi/HalDeviceManager.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wifi;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.hardware.wifi.V1_0.IWifi;
@@ -37,18 +38,21 @@
 import android.os.Handler;
 import android.os.IHwBinder.DeathRecipient;
 import android.os.RemoteException;
+import android.os.WorkSource;
 import android.util.Log;
-import android.util.LongSparseArray;
-import android.util.MutableBoolean;
-import android.util.MutableInt;
 import android.util.Pair;
 import android.util.SparseArray;
+import android.util.SparseIntArray;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.util.GeneralUtil.Mutable;
+import com.android.server.wifi.util.WorkSourceHelper;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -65,6 +69,9 @@
     private static final boolean VDBG = false;
     private boolean mDbg = false;
 
+    public static final long CHIP_CAPABILITY_ANY = 0L;
+    private static final long CHIP_CAPABILITY_UNINITIALIZED = -1L;
+
     private static final int START_HAL_RETRY_INTERVAL_MS = 20;
     // Number of attempts a start() is re-tried. A value of 0 means no retries after a single
     // attempt.
@@ -72,6 +79,7 @@
     public static final int START_HAL_RETRY_TIMES = 3;
 
     private final Clock mClock;
+    private final WifiInjector mWifiInjector;
     private final Handler mEventHandler;
     private WifiDeathRecipient mIWifiDeathRecipient;
     private ServiceManagerDeathRecipient mServiceManagerDeathRecipient;
@@ -79,17 +87,41 @@
     // cache the value for supporting vendor HAL or not
     private boolean mIsVendorHalSupported = false;
 
+    @VisibleForTesting
+    public static final int HDM_CREATE_IFACE_STA = 0;
+    @VisibleForTesting
+    public static final int HDM_CREATE_IFACE_AP = 1;
+    @VisibleForTesting
+    public static final int HDM_CREATE_IFACE_AP_BRIDGE = 2;
+    @VisibleForTesting
+    public static final int HDM_CREATE_IFACE_P2P = 3;
+    @VisibleForTesting
+    public static final int HDM_CREATE_IFACE_NAN = 4;
+
+    @IntDef(flag = false, prefix = { "HDM_CREATE_IFACE_TYPE_" }, value = {
+            HDM_CREATE_IFACE_STA,
+            HDM_CREATE_IFACE_AP,
+            HDM_CREATE_IFACE_AP_BRIDGE,
+            HDM_CREATE_IFACE_P2P,
+            HDM_CREATE_IFACE_NAN,
+    })
+    private @interface HdmIfaceTypeForCreation {};
+
+    private SparseIntArray mHalIfaceMap = new SparseIntArray() {{
+            put(HDM_CREATE_IFACE_STA, IfaceType.STA);
+            put(HDM_CREATE_IFACE_AP, IfaceType.AP);
+            put(HDM_CREATE_IFACE_AP_BRIDGE, IfaceType.AP);
+            put(HDM_CREATE_IFACE_P2P, IfaceType.P2P);
+            put(HDM_CREATE_IFACE_NAN, IfaceType.NAN);
+            }};
+
     // public API
-    public HalDeviceManager(Clock clock, Handler handler) {
+    public HalDeviceManager(Clock clock, WifiInjector wifiInjector, Handler handler) {
         mClock = clock;
+        mWifiInjector = wifiInjector;
         mEventHandler = handler;
         mIWifiDeathRecipient = new WifiDeathRecipient();
         mServiceManagerDeathRecipient = new ServiceManagerDeathRecipient();
-
-        mInterfaceAvailableForRequestListeners.put(IfaceType.STA, new HashMap<>());
-        mInterfaceAvailableForRequestListeners.put(IfaceType.AP, new HashMap<>());
-        mInterfaceAvailableForRequestListeners.put(IfaceType.P2P, new HashMap<>());
-        mInterfaceAvailableForRequestListeners.put(IfaceType.NAN, new HashMap<>());
     }
 
     /* package */ void enableVerboseLogging(int verbose) {
@@ -219,51 +251,104 @@
      * Create a STA interface if possible. Changes chip mode and removes conflicting interfaces if
      * needed and permitted by priority.
      *
+     * @param requiredChipCapabilities The bitmask of Capabilities which are required.
+     *                                 See IWifiChip.hal for documentation.
      * @param destroyedListener Optional (nullable) listener to call when the allocated interface
      *                          is removed. Will only be registered and used if an interface is
      *                          created successfully.
      * @param handler Handler on which to dispatch listener. Null implies the listener will be
      *                invoked synchronously from the context of the client which triggered the
      *                iface destruction.
+     * @param requestorWs Requestor worksource. This will be used to determine priority of this
+     *                    interface using rules based on the requestor app's context.
      * @return A newly created interface - or null if the interface could not be created.
      */
     public IWifiStaIface createStaIface(
-            @Nullable InterfaceDestroyedListener destroyedListener, @Nullable Handler handler) {
-        return (IWifiStaIface) createIface(IfaceType.STA, destroyedListener, handler);
+            long requiredChipCapabilities,
+            @Nullable InterfaceDestroyedListener destroyedListener, @Nullable Handler handler,
+            @NonNull WorkSource requestorWs) {
+        return (IWifiStaIface) createIface(HDM_CREATE_IFACE_STA, requiredChipCapabilities,
+                destroyedListener, handler, requestorWs);
+    }
+
+    /**
+     * Create a STA interface if possible. Changes chip mode and removes conflicting interfaces if
+     * needed and permitted by priority.
+     *
+     * @param destroyedListener Optional (nullable) listener to call when the allocated interface
+     *                          is removed. Will only be registered and used if an interface is
+     *                          created successfully.
+     * @param handler Handler on which to dispatch listener. Null implies the listener will be
+     *                invoked synchronously from the context of the client which triggered the
+     *                iface destruction.
+     * @param requestorWs Requestor worksource. This will be used to determine priority of this
+     *                    interface using rules based on the requestor app's context.
+     * @return A newly created interface - or null if the interface could not be created.
+     */
+    public IWifiStaIface createStaIface(
+            @Nullable InterfaceDestroyedListener destroyedListener, @Nullable Handler handler,
+            @NonNull WorkSource requestorWs) {
+        return (IWifiStaIface) createStaIface(CHIP_CAPABILITY_ANY,
+                destroyedListener, handler, requestorWs);
     }
 
     /**
      * Create AP interface if possible (see createStaIface doc).
      */
-    public IWifiApIface createApIface(@Nullable InterfaceDestroyedListener destroyedListener,
-            @Nullable Handler handler) {
-        return (IWifiApIface) createIface(IfaceType.AP, destroyedListener, handler);
+    public IWifiApIface createApIface(
+            long requiredChipCapabilities,
+            @Nullable InterfaceDestroyedListener destroyedListener, @Nullable Handler handler,
+            @NonNull WorkSource requestorWs, boolean isBridged) {
+        return (IWifiApIface) createIface(isBridged ? HDM_CREATE_IFACE_AP_BRIDGE
+                : HDM_CREATE_IFACE_AP, requiredChipCapabilities, destroyedListener,
+                handler, requestorWs);
+    }
+
+    /**
+     * Create AP interface if possible (see createStaIface doc).
+     */
+    public IWifiApIface createApIface(
+            @Nullable InterfaceDestroyedListener destroyedListener, @Nullable Handler handler,
+            @NonNull WorkSource requestorWs, boolean isBridged) {
+        return (IWifiApIface) createApIface(CHIP_CAPABILITY_ANY,
+                destroyedListener, handler, requestorWs, isBridged);
+    }
+
+    /**
+     * Create P2P interface if possible (see createStaIface doc).
+     */
+    public IWifiP2pIface createP2pIface(
+            long requiredChipCapabilities,
+            @Nullable InterfaceDestroyedListener destroyedListener,
+            @Nullable Handler handler, @NonNull WorkSource requestorWs) {
+        return (IWifiP2pIface) createIface(HDM_CREATE_IFACE_P2P, requiredChipCapabilities,
+                destroyedListener, handler, requestorWs);
     }
 
     /**
      * Create P2P interface if possible (see createStaIface doc).
      */
     public IWifiP2pIface createP2pIface(@Nullable InterfaceDestroyedListener destroyedListener,
-            @Nullable Handler handler) {
-        return (IWifiP2pIface) createIface(IfaceType.P2P, destroyedListener, handler);
+            @Nullable Handler handler, @NonNull WorkSource requestorWs) {
+        return (IWifiP2pIface) createP2pIface(CHIP_CAPABILITY_ANY,
+                destroyedListener, handler, requestorWs);
     }
 
     /**
      * Create NAN interface if possible (see createStaIface doc).
      */
     public IWifiNanIface createNanIface(@Nullable InterfaceDestroyedListener destroyedListener,
-            @Nullable Handler handler) {
-        return (IWifiNanIface) createIface(IfaceType.NAN, destroyedListener, handler);
+            @Nullable Handler handler, @NonNull WorkSource requestorWs) {
+        return (IWifiNanIface) createIface(HDM_CREATE_IFACE_NAN, CHIP_CAPABILITY_ANY,
+                destroyedListener, handler, requestorWs);
     }
 
     /**
      * Removes (releases/destroys) the given interface. Will trigger any registered
-     * InterfaceDestroyedListeners and possibly some InterfaceAvailableForRequestListeners if we
-     * can potentially create some other interfaces as a result of removing this interface.
+     * InterfaceDestroyedListeners.
      */
     public boolean removeIface(IWifiIface iface) {
         boolean success = removeIfaceInternal(iface);
-        dispatchAvailableForRequestListeners();
         return success;
     }
 
@@ -291,6 +376,59 @@
     }
 
     /**
+     * Replace the requestorWs info for the associated info.
+     *
+     * When a new iface is requested via
+     * {@link #createIface(int, InterfaceDestroyedListener, Handler, WorkSource)}, the clients
+     * pass in a worksource which includes all the apps that triggered the iface creation. However,
+     * this list of apps can change during the lifetime of the iface (as new apps request the same
+     * iface or existing apps release their request for the iface). This API can be invoked multiple
+     * times to replace the entire requestor info for the provided iface.
+     *
+     * Note: This is is wholesale replace of the requestor info. The corresponding client is
+     * responsible for individual add/remove of apps in the WorkSource passed in.
+     */
+    public boolean replaceRequestorWs(@NonNull IWifiIface iface,
+            @NonNull WorkSource newRequestorWs) {
+        String name = getName(iface);
+        int type = getType(iface);
+        if (VDBG) {
+            Log.d(TAG, "replaceRequestorWs: iface(name)=" + name + ", newRequestorWs="
+                    + newRequestorWs);
+        }
+
+        synchronized (mLock) {
+            InterfaceCacheEntry cacheEntry = mInterfaceInfoCache.get(Pair.create(name, type));
+            if (cacheEntry == null) {
+                Log.e(TAG, "replaceRequestorWs: no entry for iface(name)=" + name);
+                return false;
+            }
+            cacheEntry.requestorWsHelper = mWifiInjector.makeWsHelper(newRequestorWs);
+            return true;
+        }
+    }
+
+    /**
+     * Register a SubsystemRestartListener to listen to the subsystem restart event from HAL.
+     * Use the action() to forward the event to SelfRecovery when receiving the event from HAL.
+     *
+     * @param listener SubsystemRestartListener listener object.
+     * @param handler Handler on which to dispatch listener. Null implies the listener will be
+     *                invoked synchronously from the context of the client which triggered the
+     *                state change.
+     */
+    public void registerSubsystemRestartListener(@NonNull SubsystemRestartListener listener,
+            @Nullable Handler handler) {
+        if (listener == null) {
+            Log.wtf(TAG, "registerSubsystemRestartListener with nulls!? listener=" + listener);
+            return;
+        }
+        if (!mSubsystemRestartListener.add(new SubsystemRestartListenerProxy(listener, handler))) {
+            Log.w(TAG, "registerSubsystemRestartListener: duplicate registration ignored");
+        }
+    }
+
+    /**
      * Register an InterfaceDestroyedListener to the specified iface - returns true on success
      * and false on failure. This listener is in addition to the one registered when the interface
      * was created - allowing non-creators to monitor interface status.
@@ -322,75 +460,6 @@
     }
 
     /**
-     * Register a listener to be called when an interface of the specified type could be requested.
-     * No guarantees are provided (some other entity could request it first). The listener is
-     * active from registration until either
-     * <li>unregistration (using
-     * {@link #unregisterInterfaceAvailableForRequestListener(int,
-     * InterfaceAvailableForRequestListener)})</li>
-     * <li>HAL stop (using {@link #stop()}.</li>
-     *
-     * Only a single instance of a listener will be registered (even if the specified looper is
-     * different).
-     *
-     * Note that if it is possible to create the specified interface type at registration time
-     * then the callback will be triggered immediately.
-     *
-     * @param ifaceType The interface type (IfaceType) to be monitored.
-     * @param listener Listener to call when an interface of the requested
-     *                 type could be created
-     * @param handler Handler on which to dispatch listener. Null implies the listener will be
-     *                invoked synchronously from the context of the client which triggered the
-     *                mode change.
-     */
-    public void registerInterfaceAvailableForRequestListener(int ifaceType,
-            @NonNull InterfaceAvailableForRequestListener listener, @Nullable Handler handler) {
-        if (VDBG) {
-            Log.d(TAG, "registerInterfaceAvailableForRequestListener: ifaceType=" + ifaceType
-                    + ", listener=" + listener + ", handler=" + handler);
-        }
-
-        synchronized (mLock) {
-            InterfaceAvailableForRequestListenerProxy proxy =
-                    new InterfaceAvailableForRequestListenerProxy(listener, handler);
-            if (mInterfaceAvailableForRequestListeners.get(ifaceType).containsKey(proxy)) {
-                if (VDBG) {
-                    Log.d(TAG,
-                            "registerInterfaceAvailableForRequestListener: dup listener skipped: "
-                                    + listener);
-                }
-                return;
-            }
-            mInterfaceAvailableForRequestListeners.get(ifaceType).put(proxy, null);
-        }
-
-        WifiChipInfo[] chipInfos = getAllChipInfo();
-        if (chipInfos == null) {
-            Log.e(TAG,
-                    "registerInterfaceAvailableForRequestListener: no chip info found - but "
-                            + "possibly registered pre-started - ignoring");
-            return;
-        }
-        dispatchAvailableForRequestListenersForType(ifaceType, chipInfos);
-    }
-
-    /**
-     * Unregisters a listener registered with registerInterfaceAvailableForRequestListener().
-     */
-    public void unregisterInterfaceAvailableForRequestListener(
-            int ifaceType,
-            InterfaceAvailableForRequestListener listener) {
-        if (VDBG) {
-            Log.d(TAG, "unregisterInterfaceAvailableForRequestListener: ifaceType=" + ifaceType);
-        }
-
-        synchronized (mLock) {
-            mInterfaceAvailableForRequestListeners.get(ifaceType).remove(
-                    new InterfaceAvailableForRequestListenerProxy(listener, null));
-        }
-    }
-
-    /**
      * Register a callback object for RTT life-cycle events. The callback object registration
      * indicates that an RTT controller should be created whenever possible. The callback object
      * will be called with a new RTT controller whenever it is created (or at registration time
@@ -458,6 +527,17 @@
     }
 
     /**
+     * Called when subsystem restart
+     */
+    public interface SubsystemRestartListener {
+        /**
+         * Called for subsystem restart event from the HAL.
+         * It will trigger recovery mechanism in framework.
+         */
+        void onSubsystemRestart();
+    }
+
+    /**
      * Called when interface is destroyed.
      */
     public interface InterfaceDestroyedListener {
@@ -475,18 +555,6 @@
     }
 
     /**
-     * Called when an interface type availability for creation is changed.
-     */
-    public interface InterfaceAvailableForRequestListener {
-        /**
-         * Called when an interface type availability for creation is updated. Registered with
-         * registerInterfaceAvailableForRequestListener() and unregistered with
-         * unregisterInterfaceAvailableForRequestListener().
-         */
-        void onAvailabilityChanged(boolean isAvailable);
-    }
-
-    /**
      * Called on RTT controller lifecycle events. RTT controller is a singleton which will be
      * created when possible (after first lifecycle registration) and destroyed if necessary.
      *
@@ -518,25 +586,90 @@
      * Returns whether the provided Iface combo can be supported by the device.
      * Note: This only returns an answer based on the iface combination exposed by the HAL.
      * The actual iface creation/deletion rules depend on the iface priorities set in
-     * {@link #allowedToDeleteIfaceTypeForRequestedType(int, int, WifiIfaceInfo[][], int)}
+     * {@link #allowedToDeleteIfaceTypeForRequestedType(int, WorkSource, int, WifiIfaceInfo[][])}
+     *
+     * @param ifaceCombo SparseArray keyed in by the iface type to number of ifaces needed.
+     * @param requiredChipCapabilities The bitmask of Capabilities which are required.
+     *                                 See IWifiChip.hal for documentation.
+     * @return true if the device supports the provided combo, false otherwise.
+     */
+    public boolean canSupportIfaceCombo(SparseArray<Integer> ifaceCombo,
+            long requiredChipCapabilities) {
+        if (VDBG) {
+            Log.d(TAG, "canSupportIfaceCombo: ifaceCombo=" + ifaceCombo
+                    + ", requiredChipCapabilities=" + requiredChipCapabilities);
+        }
+
+        synchronized (mLock) {
+            if (mWifi == null) return false;
+            int[] ifaceComboArr = new int[IFACE_TYPES_BY_PRIORITY.length];
+            for (int type : IFACE_TYPES_BY_PRIORITY) {
+                ifaceComboArr[type] = ifaceCombo.get(type, 0);
+            }
+            WifiChipInfo[] chipInfos = getAllChipInfoCached();
+            if (chipInfos == null) return false;
+            return isItPossibleToCreateIfaceCombo(
+                    chipInfos, requiredChipCapabilities, ifaceComboArr);
+        }
+    }
+
+    /**
+     * Returns whether the provided Iface combo can be supported by the device.
+     * Note: This only returns an answer based on the iface combination exposed by the HAL.
+     * The actual iface creation/deletion rules depend on the iface priorities set in
+     * {@link #allowedToDeleteIfaceTypeForRequestedType(int, WorkSource, int, WifiIfaceInfo[][])}
      *
      * @param ifaceCombo SparseArray keyed in by the iface type to number of ifaces needed.
      * @return true if the device supports the provided combo, false otherwise.
      */
     public boolean canSupportIfaceCombo(SparseArray<Integer> ifaceCombo) {
-        if (VDBG) Log.d(TAG, "canSupportIfaceCombo: ifaceCombo=" + ifaceCombo);
+        return canSupportIfaceCombo(ifaceCombo, CHIP_CAPABILITY_ANY);
+    }
 
+    /**
+     * Returns whether the provided Iface can be requested by specifier requestor.
+     *
+     * @param ifaceType Type of iface requested.
+     * @param requiredChipCapabilities The bitmask of Capabilities which are required.
+     *                                 See IWifiChip.hal for documentation.
+     * @param requestorWs Requestor worksource. This will be used to determine priority of this
+     *                    interface using rules based on the requestor app's context.
+     * @return true if the device supports the provided combo, false otherwise.
+     */
+    public boolean isItPossibleToCreateIface(
+            int ifaceType, long requiredChipCapabilities, WorkSource requestorWs) {
+        if (VDBG) {
+            Log.d(TAG, "isItPossibleToCreateIface: ifaceType=" + ifaceType
+                    + ", requiredChipCapabilities=" + requiredChipCapabilities);
+        }
         synchronized (mLock) {
-            int[] ifaceComboArr = new int[IFACE_TYPES_BY_PRIORITY.length];
-            for (int type : IFACE_TYPES_BY_PRIORITY) {
-                ifaceComboArr[type] = ifaceCombo.get(type, 0);
-            }
+            if (mWifi == null) return false;
             WifiChipInfo[] chipInfos = getAllChipInfo();
             if (chipInfos == null) return false;
-            return isItPossibleToCreateIfaceCombo(chipInfos, ifaceComboArr);
+            if (!validateInterfaceCacheAndRetrieveRequestorWs(chipInfos)) {
+                Log.e(TAG, "isItPossibleToCreateIface: local cache is invalid!");
+                stopWifi(); // major error: shutting down
+                return false;
+            }
+            return isItPossibleToCreateIface(
+                    chipInfos, ifaceType, requiredChipCapabilities, requestorWs);
         }
     }
 
+    /**
+     * Returns whether the provided Iface can be requested by specifier requestor.
+     *
+     * @param ifaceType Type of iface requested.
+     * @param requestorWs Requestor worksource. This will be used to determine priority of this
+     *                    interface using rules based on the requestor app's context.
+     * @return true if the device supports the provided combo, false otherwise.
+     */
+    public boolean isItPossibleToCreateIface(
+            int ifaceType, WorkSource requestorWs) {
+        return isItPossibleToCreateIface(
+                ifaceType, CHIP_CAPABILITY_ANY, requestorWs);
+    }
+
     // internal state
 
     /* This "PRIORITY" is not for deciding interface elimination (that is controlled by
@@ -553,13 +686,13 @@
     private IWifi mWifi;
     private IWifiRttController mIWifiRttController;
     private final WifiEventCallback mWifiEventCallback = new WifiEventCallback();
+    private final WifiEventCallbackV15 mWifiEventCallbackV15 = new WifiEventCallbackV15();
     private final Set<ManagerStatusListenerProxy> mManagerStatusListeners = new HashSet<>();
     private final Set<InterfaceRttControllerLifecycleCallbackProxy>
             mRttControllerLifecycleCallbacks = new HashSet<>();
-    private final SparseArray<Map<InterfaceAvailableForRequestListenerProxy, Boolean>>
-            mInterfaceAvailableForRequestListeners = new SparseArray<>();
     private final SparseArray<IWifiChipEventCallback.Stub> mDebugCallbacks = new SparseArray<>();
     private boolean mIsReady;
+    private final Set<SubsystemRestartListenerProxy> mSubsystemRestartListener = new HashSet<>();
 
     /*
      * This is the only place where we cache HIDL information in this manager. Necessary since
@@ -568,7 +701,6 @@
      */
     private final Map<Pair<String, Integer>, InterfaceCacheEntry> mInterfaceInfoCache =
             new HashMap<>();
-    private WifiChipInfo[] mDebugChipsInfo = null;
 
     private class InterfaceCacheEntry {
         public IWifiChip chip;
@@ -577,12 +709,14 @@
         public int type;
         public Set<InterfaceDestroyedListenerProxy> destroyedListeners = new HashSet<>();
         public long creationTime;
+        public WorkSourceHelper requestorWsHelper;
 
         @Override
         public String toString() {
             StringBuilder sb = new StringBuilder();
             sb.append("{name=").append(name).append(", type=").append(type)
                     .append(", destroyedListeners.size()=").append(destroyedListeners.size())
+                    .append(", RequestorWs=").append(requestorWsHelper)
                     .append(", creationTime=").append(creationTime).append("}");
             return sb.toString();
         }
@@ -591,10 +725,12 @@
     private class WifiIfaceInfo {
         public String name;
         public IWifiIface iface;
+        public WorkSourceHelper requestorWsHelper;
 
         @Override
         public String toString() {
-            return "{name=" + name + ", iface=" + iface + "}";
+            return "{name=" + name + ", iface=" + iface + ", requestorWs=" + requestorWsHelper
+                    + " }";
         }
     }
 
@@ -605,13 +741,15 @@
         public boolean currentModeIdValid;
         public int currentModeId;
         public WifiIfaceInfo[][] ifaces = new WifiIfaceInfo[IFACE_TYPES_BY_PRIORITY.length][];
+        public long chipCapabilities;
 
         @Override
         public String toString() {
             StringBuilder sb = new StringBuilder();
             sb.append("{chipId=").append(chipId).append(", availableModes=").append(availableModes)
                     .append(", currentModeIdValid=").append(currentModeIdValid)
-                    .append(", currentModeId=").append(currentModeId);
+                    .append(", currentModeId=").append(currentModeId)
+                    .append(", chipCapabilities=").append(chipCapabilities);
             for (int type: IFACE_TYPES_BY_PRIORITY) {
                 sb.append(", ifaces[" + type + "].length=").append(ifaces[type].length);
             }
@@ -632,6 +770,11 @@
         }
     }
 
+    protected android.hardware.wifi.V1_5.IWifi getWifiServiceForV1_5Mockable(IWifi iWifi) {
+        if (null == iWifi) return null;
+        return android.hardware.wifi.V1_5.IWifi.castFrom(iWifi);
+    }
+
     protected IServiceManager getServiceManagerMockable() {
         try {
             return IServiceManager.getService();
@@ -641,6 +784,11 @@
         }
     }
 
+    protected android.hardware.wifi.V1_5.IWifiChip getWifiChipForV1_5Mockable(IWifiChip chip) {
+        if (null == chip) return null;
+        return android.hardware.wifi.V1_5.IWifiChip.castFrom(chip);
+    }
+
     // internal implementation
 
     private void initializeInternal() {
@@ -653,10 +801,6 @@
     private void teardownInternal() {
         managerStatusListenerDispatch();
         dispatchAllDestroyedListeners();
-        mInterfaceAvailableForRequestListeners.get(IfaceType.STA).clear();
-        mInterfaceAvailableForRequestListeners.get(IfaceType.AP).clear();
-        mInterfaceAvailableForRequestListeners.get(IfaceType.P2P).clear();
-        mInterfaceAvailableForRequestListeners.get(IfaceType.NAN).clear();
 
         mIWifiRttController = null;
         dispatchRttControllerLifecycleOnDestroyed();
@@ -798,7 +942,14 @@
                     return;
                 }
 
-                WifiStatus status = mWifi.registerEventCallback(mWifiEventCallback);
+                WifiStatus status;
+                android.hardware.wifi.V1_5.IWifi iWifiV15 = getWifiServiceForV1_5Mockable(mWifi);
+                if (iWifiV15 != null) {
+                    status = iWifiV15.registerEventCallback_1_5(mWifiEventCallbackV15);
+                } else {
+                    status = mWifi.registerEventCallback(mWifiEventCallback);
+                }
+
                 if (status.code != WifiStatusCode.SUCCESS) {
                     Log.e(TAG, "IWifi.registerEventCallback failed: " + statusString(status));
                     mWifi = null;
@@ -830,7 +981,7 @@
 
         synchronized (mLock) {
             try {
-                MutableBoolean statusOk = new MutableBoolean(false);
+                Mutable<Boolean> statusOk = new Mutable<>(false);
                 Mutable<ArrayList<Integer>> chipIdsResp = new Mutable<>();
 
                 // get all chip IDs
@@ -921,6 +1072,23 @@
         }
     }
 
+    @Nullable
+    private WifiChipInfo[] mCachedWifiChipInfos = null;
+
+    /**
+     * Get current information about all the chips in the system: modes, current mode (if any), and
+     * any existing interfaces.
+     *
+     * Intended to be called for any external iface support related queries. This information is
+     * cached to reduce performance overhead (unlike {@link #getAllChipInfo()}).
+     */
+    private WifiChipInfo[] getAllChipInfoCached() {
+        if (mCachedWifiChipInfos == null) {
+            mCachedWifiChipInfos = getAllChipInfo();
+        }
+        return mCachedWifiChipInfos;
+    }
+
     /**
      * Get current information about all the chips in the system: modes, current mode (if any), and
      * any existing interfaces.
@@ -932,13 +1100,12 @@
         if (VDBG) Log.d(TAG, "getAllChipInfo");
 
         synchronized (mLock) {
-            if (mWifi == null) {
-                Log.e(TAG, "getAllChipInfo: called but mWifi is null!?");
+            if (!isWifiStarted()) {
                 return null;
             }
 
             try {
-                MutableBoolean statusOk = new MutableBoolean(false);
+                Mutable<Boolean> statusOk = new Mutable<>(false);
                 Mutable<ArrayList<Integer>> chipIdsResp = new Mutable<>();
 
                 // get all chip IDs
@@ -991,8 +1158,8 @@
                         return null;
                     }
 
-                    MutableBoolean currentModeValidResp = new MutableBoolean(false);
-                    MutableInt currentModeResp = new MutableInt(0);
+                    Mutable<Boolean> currentModeValidResp = new Mutable<>(false);
+                    Mutable<Integer> currentModeResp = new Mutable<>(0);
                     chipResp.value.getMode((WifiStatus status, int modeId) -> {
                         statusOk.value = status.code == WifiStatusCode.SUCCESS;
                         if (statusOk.value) {
@@ -1008,8 +1175,11 @@
                         return null;
                     }
 
+                    Mutable<Long> chipCapabilities = new Mutable<>(0L);
+                    chipCapabilities.value = getChipCapabilities(chipResp.value);
+
                     Mutable<ArrayList<String>> ifaceNamesResp = new Mutable<>();
-                    MutableInt ifaceIndex = new MutableInt(0);
+                    Mutable<Integer> ifaceIndex = new Mutable<>(0);
 
                     chipResp.value.getStaIfaceNames(
                             (WifiStatus status, ArrayList<String> ifnames) -> {
@@ -1150,13 +1320,12 @@
                     chipInfo.availableModes = availableModesResp.value;
                     chipInfo.currentModeIdValid = currentModeValidResp.value;
                     chipInfo.currentModeId = currentModeResp.value;
+                    chipInfo.chipCapabilities = chipCapabilities.value;
                     chipInfo.ifaces[IfaceType.STA] = staIfaces;
                     chipInfo.ifaces[IfaceType.AP] = apIfaces;
                     chipInfo.ifaces[IfaceType.P2P] = p2pIfaces;
                     chipInfo.ifaces[IfaceType.NAN] = nanIfaces;
                 }
-
-                if (mDebugChipsInfo == null) mDebugChipsInfo = chipsInfo;
                 return chipsInfo;
             } catch (RemoteException e) {
                 Log.e(TAG, "getAllChipInfoAndValidateCache exception: " + e);
@@ -1173,8 +1342,10 @@
      *
      * A discrepancy is if any local state contains references to a chip or interface which are not
      * found on the information read from the chip.
+     *
+     * Also, fills in the |requestorWs| corresponding to each active iface in |WifiChipInfo|.
      */
-    private boolean validateInterfaceCache(WifiChipInfo[] chipInfos) {
+    private boolean validateInterfaceCacheAndRetrieveRequestorWs(WifiChipInfo[] chipInfos) {
         if (VDBG) Log.d(TAG, "validateInterfaceCache");
 
         synchronized (mLock) {
@@ -1202,6 +1373,7 @@
                 boolean matchFound = false;
                 for (WifiIfaceInfo ifaceInfo: ifaceInfoList) {
                     if (ifaceInfo.name.equals(entry.name)) {
+                        ifaceInfo.requestorWsHelper = entry.requestorWsHelper;
                         matchFound = true;
                         break;
                     }
@@ -1222,7 +1394,6 @@
         synchronized (mLock) {
             try {
                 if (mWifi == null) {
-                    Log.w(TAG, "isWifiStarted called but mWifi is null!?");
                     return false;
                 } else {
                     return mWifi.isStarted();
@@ -1324,10 +1495,40 @@
             mEventHandler.post(() -> {
                 Log.e(TAG, "IWifiEventCallback.onFailure: " + statusString(status));
                 synchronized (mLock) {
+                    mIsReady = false;
                     teardownInternal();
                 }
             });
-            // No need to do anything else: listeners may (will) re-start Wi-Fi
+        }
+    }
+
+    private class WifiEventCallbackV15 extends
+            android.hardware.wifi.V1_5.IWifiEventCallback.Stub {
+        private final WifiEventCallback mWifiEventCallback = new WifiEventCallback();
+        @Override
+        public void onStart() throws RemoteException {
+            mWifiEventCallback.onStart();
+        }
+
+        @Override
+        public void onStop() throws RemoteException {
+            mWifiEventCallback.onStop();
+        }
+
+        @Override
+        public void onFailure(WifiStatus status) throws RemoteException {
+            mWifiEventCallback.onFailure(status);
+        }
+
+        @Override
+        public void onSubsystemRestart(WifiStatus status) throws RemoteException {
+            mEventHandler.post(() -> {
+                synchronized (mLock) {
+                    for (SubsystemRestartListenerProxy cb : mSubsystemRestartListener) {
+                        cb.action();
+                    }
+                }
+            });
         }
     }
 
@@ -1351,18 +1552,18 @@
         }
     }
 
-    Set<Integer> getSupportedIfaceTypesInternal(IWifiChip chip) {
+    private Set<Integer> getSupportedIfaceTypesInternal(IWifiChip chip) {
         Set<Integer> results = new HashSet<>();
 
-        WifiChipInfo[] chipInfos = getAllChipInfo();
+        WifiChipInfo[] chipInfos = getAllChipInfoCached();
         if (chipInfos == null) {
             Log.e(TAG, "getSupportedIfaceTypesInternal: no chip info found");
             return results;
         }
 
-        MutableInt chipIdIfProvided = new MutableInt(0); // NOT using 0 as a magic value
+        Mutable<Integer> chipIdIfProvided = new Mutable<>(0); // NOT using 0 as a magic value
         if (chip != null) {
-            MutableBoolean statusOk = new MutableBoolean(false);
+            Mutable<Boolean> statusOk = new Mutable<>(false);
             try {
                 chip.getId((WifiStatus status, int id) -> {
                     if (status.code == WifiStatusCode.SUCCESS) {
@@ -1402,10 +1603,13 @@
         return results;
     }
 
-    private IWifiIface createIface(int ifaceType, InterfaceDestroyedListener destroyedListener,
-            Handler handler) {
+    private IWifiIface createIface(@HdmIfaceTypeForCreation int createIfaceType,
+            long requiredChipCapabilities, InterfaceDestroyedListener destroyedListener,
+            Handler handler, WorkSource requestorWs) {
         if (mDbg) {
-            Log.d(TAG, "createIface: ifaceType=" + ifaceType);
+            Log.d(TAG, "createIface: createIfaceType=" + createIfaceType
+                    + ", requiredChipCapabilities=" + requiredChipCapabilities
+                    + ", requestorWs=" + requestorWs);
         }
 
         synchronized (mLock) {
@@ -1416,33 +1620,46 @@
                 return null;
             }
 
-            if (!validateInterfaceCache(chipInfos)) {
+            if (!validateInterfaceCacheAndRetrieveRequestorWs(chipInfos)) {
                 Log.e(TAG, "createIface: local cache is invalid!");
                 stopWifi(); // major error: shutting down
                 return null;
             }
 
-            IWifiIface iface = createIfaceIfPossible(chipInfos, ifaceType, destroyedListener,
-                    handler);
-            if (iface != null) { // means that some configuration has changed
-                if (!dispatchAvailableForRequestListeners()) {
-                    return null; // catastrophic failure - shut down
-                }
-            }
-
-            return iface;
+            return createIfaceIfPossible(
+                    chipInfos, createIfaceType, requiredChipCapabilities,
+                    destroyedListener, handler, requestorWs);
         }
     }
 
-    private IWifiIface createIfaceIfPossible(WifiChipInfo[] chipInfos, int ifaceType,
-            InterfaceDestroyedListener destroyedListener, Handler handler) {
+    private static boolean isChipCapabilitiesSupported(@NonNull WifiChipInfo chipInfo,
+            long requiredChipCapabilities) {
+        if (chipInfo == null) return false;
+
+        if (requiredChipCapabilities == CHIP_CAPABILITY_ANY) return true;
+
+        if (CHIP_CAPABILITY_UNINITIALIZED == chipInfo.chipCapabilities) return true;
+
+        return (chipInfo.chipCapabilities & requiredChipCapabilities)
+                == requiredChipCapabilities;
+    }
+
+    private IWifiIface createIfaceIfPossible(
+            WifiChipInfo[] chipInfos, @HdmIfaceTypeForCreation int createIfaceType,
+            long requiredChipCapabilities, InterfaceDestroyedListener destroyedListener,
+            Handler handler, WorkSource requestorWs) {
+        int targetHalIfaceType = mHalIfaceMap.get(createIfaceType);
         if (VDBG) {
             Log.d(TAG, "createIfaceIfPossible: chipInfos=" + Arrays.deepToString(chipInfos)
-                    + ", ifaceType=" + ifaceType);
+                    + ", createIfaceType=" + createIfaceType
+                    + ", targetHalIfaceType=" + targetHalIfaceType
+                    + ", requiredChipCapabilities=" + requiredChipCapabilities
+                    + ", requestorWs=" + requestorWs);
         }
         synchronized (mLock) {
             IfaceCreationData bestIfaceCreationProposal = null;
             for (WifiChipInfo chipInfo: chipInfos) {
+                if (!isChipCapabilitiesSupported(chipInfo, requiredChipCapabilities)) continue;
                 for (IWifiChip.ChipMode chipMode: chipInfo.availableModes) {
                     for (IWifiChip.ChipIfaceCombination chipIfaceCombo : chipMode
                             .availableCombinations) {
@@ -1454,7 +1671,8 @@
 
                         for (int[] expandedIfaceCombo: expandedIfaceCombos) {
                             IfaceCreationData currentProposal = canIfaceComboSupportRequest(
-                                    chipInfo, chipMode, expandedIfaceCombo, ifaceType);
+                                    chipInfo, chipMode, expandedIfaceCombo, targetHalIfaceType,
+                                    requestorWs);
                             if (compareIfaceCreationData(currentProposal,
                                     bestIfaceCreationProposal)) {
                                 if (VDBG) Log.d(TAG, "new proposal accepted");
@@ -1466,14 +1684,16 @@
             }
 
             if (bestIfaceCreationProposal != null) {
-                IWifiIface iface = executeChipReconfiguration(bestIfaceCreationProposal, ifaceType);
+                IWifiIface iface = executeChipReconfiguration(bestIfaceCreationProposal,
+                        createIfaceType);
                 if (iface != null) {
                     InterfaceCacheEntry cacheEntry = new InterfaceCacheEntry();
 
                     cacheEntry.chip = bestIfaceCreationProposal.chipInfo.chip;
                     cacheEntry.chipId = bestIfaceCreationProposal.chipInfo.chipId;
                     cacheEntry.name = getName(iface);
-                    cacheEntry.type = ifaceType;
+                    cacheEntry.type = targetHalIfaceType;
+                    cacheEntry.requestorWsHelper = mWifiInjector.makeWsHelper(requestorWs);
                     if (destroyedListener != null) {
                         cacheEntry.destroyedListeners.add(
                                 new InterfaceDestroyedListenerProxy(
@@ -1489,18 +1709,24 @@
             }
         }
 
+        Log.d(TAG, "createIfaceIfPossible: Failed to create iface for ifaceType=" + createIfaceType
+                + ", requestorWs=" + requestorWs);
         return null;
     }
 
     // similar to createIfaceIfPossible - but simpler code: not looking for best option just
     // for any option (so terminates on first one).
-    private boolean isItPossibleToCreateIface(WifiChipInfo[] chipInfos, int ifaceType) {
+    private boolean isItPossibleToCreateIface(WifiChipInfo[] chipInfos,
+            int ifaceType, long requiredChipCapabilities,
+            WorkSource requestorWs) {
         if (VDBG) {
             Log.d(TAG, "isItPossibleToCreateIface: chipInfos=" + Arrays.deepToString(chipInfos)
-                    + ", ifaceType=" + ifaceType);
+                    + ", ifaceType=" + ifaceType
+                    + ", requiredChipCapabilities=" + requiredChipCapabilities);
         }
 
         for (WifiChipInfo chipInfo: chipInfos) {
+            if (!isChipCapabilitiesSupported(chipInfo, requiredChipCapabilities)) continue;
             for (IWifiChip.ChipMode chipMode: chipInfo.availableModes) {
                 for (IWifiChip.ChipIfaceCombination chipIfaceCombo : chipMode
                         .availableCombinations) {
@@ -1512,7 +1738,7 @@
 
                     for (int[] expandedIfaceCombo: expandedIfaceCombos) {
                         if (canIfaceComboSupportRequest(chipInfo, chipMode, expandedIfaceCombo,
-                                ifaceType) != null) {
+                                ifaceType, requestorWs) != null) {
                             return true;
                         }
                     }
@@ -1583,11 +1809,12 @@
      * - Mode configuration: i.e. could the mode support the interface type in principle
      */
     private IfaceCreationData canIfaceComboSupportRequest(WifiChipInfo chipInfo,
-            IWifiChip.ChipMode chipMode, int[] chipIfaceCombo, int ifaceType) {
+            IWifiChip.ChipMode chipMode, int[] chipIfaceCombo, int ifaceType,
+            WorkSource requestorWs) {
         if (VDBG) {
             Log.d(TAG, "canIfaceComboSupportRequest: chipInfo=" + chipInfo + ", chipMode="
                     + chipMode + ", chipIfaceCombo=" + Arrays.toString(chipIfaceCombo)
-                    + ", ifaceType=" + ifaceType);
+                    + ", ifaceType=" + ifaceType + ", requestorWs=" + requestorWs);
         }
 
         // short-circuit: does the chipIfaceCombo even support the requested type?
@@ -1604,8 +1831,8 @@
         if (isChipModeChangeProposed) {
             for (int type: IFACE_TYPES_BY_PRIORITY) {
                 if (chipInfo.ifaces[type].length != 0) {
-                    if (!allowedToDeleteIfaceTypeForRequestedType(type, ifaceType,
-                            chipInfo.ifaces, chipInfo.ifaces[type].length)) {
+                    if (!allowedToDeleteIfaceTypeForRequestedType(
+                            ifaceType, requestorWs, type, chipInfo.ifaces)) {
                         if (VDBG) {
                             Log.d(TAG, "Couldn't delete existing type " + type
                                     + " interfaces for requested type");
@@ -1635,8 +1862,8 @@
             }
 
             if (tooManyInterfaces > 0) { // may need to delete some
-                if (!allowedToDeleteIfaceTypeForRequestedType(type, ifaceType, chipInfo.ifaces,
-                        tooManyInterfaces)) {
+                if (!allowedToDeleteIfaceTypeForRequestedType(
+                        ifaceType, requestorWs, type, chipInfo.ifaces)) {
                     if (VDBG) {
                         Log.d(TAG, "Would need to delete some higher priority interfaces");
                     }
@@ -1645,7 +1872,7 @@
 
                 // delete the most recently created interfaces
                 interfacesToBeRemovedFirst.addAll(selectInterfacesToDelete(tooManyInterfaces,
-                        chipInfo.ifaces[type]));
+                        ifaceType, requestorWs, type, chipInfo.ifaces[type]));
             }
         }
 
@@ -1708,6 +1935,75 @@
         return false;
     }
 
+    private static final int PRIORITY_PRIVILEGED = 0;
+    private static final int PRIORITY_SYSTEM = 1;
+    private static final int PRIORITY_FG_APP = 2;
+    private static final int PRIORITY_FG_SERVICE = 3;
+    private static final int PRIORITY_BG = 4;
+    private static final int PRIORITY_INTERNAL = 5;
+    // Keep these in sync with any additions/deletions to above buckets.
+    private static final int PRIORITY_MIN = PRIORITY_PRIVILEGED;
+    private static final int PRIORITY_MAX = PRIORITY_INTERNAL;
+    @IntDef(prefix = { "PRIORITY_" }, value = {
+            PRIORITY_PRIVILEGED,
+            PRIORITY_SYSTEM,
+            PRIORITY_FG_APP,
+            PRIORITY_FG_SERVICE,
+            PRIORITY_BG,
+            PRIORITY_INTERNAL,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RequestorWsPriority {}
+
+    /**
+     * Returns integer priority level for the provided |ws| based on rules mentioned in
+     * {@link #allowedToDeleteIfaceTypeForRequestedType(int, WorkSource, int, WifiIfaceInfo[][])}.
+     */
+    private static @RequestorWsPriority int getRequestorWsPriority(WorkSourceHelper ws) {
+        if (ws.hasAnyPrivilegedAppRequest()) return PRIORITY_PRIVILEGED;
+        if (ws.hasAnySystemAppRequest()) return PRIORITY_SYSTEM;
+        if (ws.hasAnyForegroundAppRequest()) return PRIORITY_FG_APP;
+        if (ws.hasAnyForegroundServiceRequest()) return PRIORITY_FG_SERVICE;
+        if (ws.hasAnyInternalRequest()) return PRIORITY_INTERNAL;
+        return PRIORITY_BG;
+    }
+
+    /**
+     * Returns whether interface request from |newRequestorWsPriority| is allowed to delete an
+     * interface request from |existingRequestorWsPriority|.
+     *
+     * Rule:
+     *  - If |newRequestorWsPriority| < |existingRequestorWsPriority|, then YES.
+     *  - If they are at the same priority level, then
+     *      - If both are privileged and not for the same interface type, then YES.
+     *      - Else, NO.
+     */
+    private static boolean allowedToDelete(
+            int requestedIfaceType, @RequestorWsPriority int newRequestorWsPriority,
+            int existingIfaceType, @RequestorWsPriority int existingRequestorWsPriority) {
+        if (!SdkLevel.isAtLeastS()) {
+            return allowedToDeleteForR(requestedIfaceType, existingIfaceType);
+        }
+        // If the new request is higher priority than existing priority, then the new requestor
+        // wins. This is because at all other priority levels (except privileged), existing caller
+        // wins if both the requests are at the same priority level.
+        if (newRequestorWsPriority < existingRequestorWsPriority) {
+            return true;
+        }
+        if (newRequestorWsPriority == existingRequestorWsPriority) {
+            // If both the requests are same priority for the same iface type, the existing
+            // requestor wins.
+            if (requestedIfaceType == existingIfaceType) {
+                return false;
+            }
+            // If both the requests are privileged, the new requestor wins.
+            if (newRequestorWsPriority == PRIORITY_PRIVILEGED) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Returns true if we're allowed to delete the existing interface type for the requested
      * interface type.
@@ -1716,42 +2012,24 @@
      *
      * General rules:
      * 1. No interface will be destroyed for a requested interface of the same type
-     * 2. No interface will be destroyed if one of the requested interfaces already exists
-     * 3. If there are >1 interface of an existing type, then it is ok to destroy that type
-     *    interface
      *
-     * Type-specific rules (but note that the general rules are appied first):
-     * 4. Request for AP or STA will destroy any other interface
-     * 5. Request for P2P will destroy NAN-only (but will destroy a second STA per #3)
-     * 6. Request for NAN will destroy P2P-only (but will destroy a second STA per #3)
-     *
-     * Note: the 'numNecessaryInterfaces' is used to specify how many interfaces would be needed to
-     * be deleted. This is used to determine whether there are that many low priority interfaces
-     * of the requested type to delete.
+     * Type-specific rules (but note that the general rules are applied first):
+     * 2. Request for AP or STA will destroy any other interface
+     * 3. Request for P2P will destroy NAN-only
+     * 4. Request for NAN will destroy P2P-only
      */
-    private boolean allowedToDeleteIfaceTypeForRequestedType(int existingIfaceType,
-            int requestedIfaceType, WifiIfaceInfo[][] currentIfaces, int numNecessaryInterfaces) {
+    private static boolean allowedToDeleteForR(int requestedIfaceType, int existingIfaceType) {
         // rule 1
         if (existingIfaceType == requestedIfaceType) {
             return false;
         }
 
         // rule 2
-        if (currentIfaces[requestedIfaceType].length != 0) {
-            return false;
-        }
-
-        // rule 3
-        if (currentIfaces[existingIfaceType].length > 1) {
-            return true;
-        }
-
-        // rule 5
         if (requestedIfaceType == IfaceType.P2P) {
             return existingIfaceType == IfaceType.NAN;
         }
 
-        // rule 6
+        // rule 3
         if (requestedIfaceType == IfaceType.NAN) {
             return existingIfaceType == IfaceType.P2P;
         }
@@ -1761,22 +2039,94 @@
     }
 
     /**
+     * Returns true if we're allowed to delete the existing interface type for the requested
+     * interface type.
+     *
+     * General rules:
+     * 1. Requests for interfaces have the following priority which are based on corresponding
+     * requesting  app's context. Priorities in decreasing order (i.e (i) has the highest priority,
+     * (v) has the lowest priority).
+     *  - (i) Requests from privileged apps (i.e settings, setup wizard, connectivity stack, etc)
+     *  - (ii) Requests from system apps.
+     *  - (iii) Requests from foreground apps.
+     *  - (iv) Requests from foreground services.
+     *  - (v) Requests from everything else (lumped together as "background").
+     * Note: If there are more than 1 app requesting for a particular interface, then we consider
+     * the priority of the highest priority app among them.
+     * For ex: If there is a system app and a foreground requesting for NAN iface, then we use the
+     * system app to determine the priority of the interface request.
+     * 2. If there are 2 conflicting interface requests from apps with the same priority, then
+     *    - (i) If both the apps are privileged and not for the same interface type, the new request
+     *          wins (last caller wins).
+     *    - (ii) Else, the existing request wins (first caller wins).
+     * Note: Privileged apps are the ones that the user is directly interacting with, hence we use
+     * last caller wins to decide among those, for all other apps we try to minimize disruption to
+     * existing requests.
+     * For ex: User turns on wifi, then hotspot on legacy devices which do not support STA + AP, we
+     * want the last request from the user (i.e hotspot) to be honored.
+     */
+    private boolean allowedToDeleteIfaceTypeForRequestedType(
+            int requestedIfaceType, WorkSource requestorWs, int existingIfaceType,
+            WifiIfaceInfo[][] existingIfaces) {
+        WorkSourceHelper newRequestorWsHelper = mWifiInjector.makeWsHelper(requestorWs);
+        WifiIfaceInfo[] ifaceInfosForExistingIfaceType = existingIfaces[existingIfaceType];
+        // No ifaces of the existing type, error!
+        if (ifaceInfosForExistingIfaceType.length == 0) {
+            Log.wtf(TAG, "allowedToDeleteIfaceTypeForRequestedType: Num existings ifaces is 0!");
+            return false;
+        }
+        for (WifiIfaceInfo ifaceInfo : ifaceInfosForExistingIfaceType) {
+            int newRequestorWsPriority = getRequestorWsPriority(newRequestorWsHelper);
+            int existingRequestorWsPriority = getRequestorWsPriority(ifaceInfo.requestorWsHelper);
+            if (allowedToDelete(
+                    requestedIfaceType, newRequestorWsPriority, existingIfaceType,
+                    existingRequestorWsPriority)) {
+                if (mDbg) {
+                    Log.d(TAG, "allowedToDeleteIfaceTypeForRequestedType: Allowed to delete "
+                            + "requestedIfaceType=" + requestedIfaceType
+                            + "existingIfaceType=" + existingIfaceType
+                            + ", newRequestorWsPriority=" + newRequestorWsHelper
+                            + ", existingRequestorWsPriority" + existingRequestorWsPriority);
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * Selects the interfaces to delete.
      *
-     * Rule: select low priority interfaces and then other interfaces in order of creation time.
+     * Rule:
+     *  - Select interfaces that are lower priority than the request priority.
+     *  - If they are at the same priority level, then
+     *      - If both are privileged and different iface type, then delete existing interfaces.
+     *      - Else, not allowed to delete.
+     *  - Delete ifaces based on the descending requestor priority
+     *    (i.e bg app requests are deleted first, privileged app requests are deleted last)
+     *  - If there are > 1 ifaces within the same priority group to delete, select them randomly.
      *
      * @param excessInterfaces Number of interfaces which need to be selected.
+     * @param requestedIfaceType Requested iface type.
+     * @param requestorWs Requestor worksource.
+     * @param existingIfaceType Existing iface type.
      * @param interfaces Array of interfaces.
      */
     private List<WifiIfaceInfo> selectInterfacesToDelete(int excessInterfaces,
+            int requestedIfaceType, WorkSource requestorWs, int existingIfaceType,
             WifiIfaceInfo[] interfaces) {
         if (VDBG) {
             Log.d(TAG, "selectInterfacesToDelete: excessInterfaces=" + excessInterfaces
+                    + ", requestedIfaceType=" + requestedIfaceType
+                    + ", requestorWs=" + requestorWs
+                    + ", existingIfaceType=" + existingIfaceType
                     + ", interfaces=" + Arrays.toString(interfaces));
         }
+        WorkSourceHelper newRequestorWsHelper = mWifiInjector.makeWsHelper(requestorWs);
 
         boolean lookupError = false;
-        LongSparseArray<WifiIfaceInfo> orderedList = new LongSparseArray<>();
+        // Map of priority levels to ifaces to delete.
+        Map<Integer, List<WifiIfaceInfo>> ifacesToDeleteMap = new HashMap<>();
         for (WifiIfaceInfo info : interfaces) {
             InterfaceCacheEntry cacheEntry;
             synchronized (mLock) {
@@ -1788,18 +2138,37 @@
                 lookupError = true;
                 break;
             }
-            orderedList.append(cacheEntry.creationTime, info);
+            int newRequestorWsPriority = getRequestorWsPriority(newRequestorWsHelper);
+            int existingRequestorWsPriority = getRequestorWsPriority(cacheEntry.requestorWsHelper);
+            if (allowedToDelete(requestedIfaceType, newRequestorWsPriority, existingIfaceType,
+                    existingRequestorWsPriority)) {
+                ifacesToDeleteMap.computeIfAbsent(
+                        existingRequestorWsPriority, v -> new ArrayList<>()).add(info);
+            }
         }
 
         if (lookupError) {
             Log.e(TAG, "selectInterfacesToDelete: falling back to arbitrary selection");
             return Arrays.asList(Arrays.copyOf(interfaces, excessInterfaces));
         } else {
-            List<WifiIfaceInfo> result = new ArrayList<>(excessInterfaces);
-            for (int i = 0; i < excessInterfaces; ++i) {
-                result.add(orderedList.valueAt(orderedList.size() - i - 1));
+            int numIfacesToDelete = 0;
+            List<WifiIfaceInfo> ifacesToDelete = new ArrayList<>(excessInterfaces);
+            // Iterate from lowest priority to highest priority ifaces.
+            for (int i = PRIORITY_MAX; i >= PRIORITY_MIN; i--) {
+                List<WifiIfaceInfo> ifacesToDeleteListWithinPriority =
+                        ifacesToDeleteMap.getOrDefault(i, new ArrayList<>());
+                int numIfacesToDeleteWithinPriority =
+                        Math.min(excessInterfaces - numIfacesToDelete,
+                                ifacesToDeleteListWithinPriority.size());
+                ifacesToDelete.addAll(
+                        ifacesToDeleteListWithinPriority.subList(
+                                0, numIfacesToDeleteWithinPriority));
+                numIfacesToDelete += numIfacesToDeleteWithinPriority;
+                if (numIfacesToDelete == excessInterfaces) {
+                    break;
+                }
             }
-            return result;
+            return ifacesToDelete;
         }
     }
 
@@ -1822,13 +2191,16 @@
     }
 
     // Is it possible to create iface combo just looking at the device capabilities.
-    private boolean isItPossibleToCreateIfaceCombo(WifiChipInfo[] chipInfos, int[] ifaceCombo) {
+    private boolean isItPossibleToCreateIfaceCombo(WifiChipInfo[] chipInfos,
+            long requiredChipCapabilities, int[] ifaceCombo) {
         if (VDBG) {
             Log.d(TAG, "isItPossibleToCreateIfaceCombo: chipInfos=" + Arrays.deepToString(chipInfos)
-                    + ", ifaceType=" + ifaceCombo);
+                    + ", ifaceType=" + ifaceCombo
+                    + ", requiredChipCapabilities=" + requiredChipCapabilities);
         }
 
         for (WifiChipInfo chipInfo: chipInfos) {
+            if (!isChipCapabilitiesSupported(chipInfo, requiredChipCapabilities)) continue;
             for (IWifiChip.ChipMode chipMode: chipInfo.availableModes) {
                 for (IWifiChip.ChipIfaceCombination chipIfaceCombo
                         : chipMode.availableCombinations) {
@@ -1859,10 +2231,10 @@
      * Returns the newly created interface or a null on any error.
      */
     private IWifiIface executeChipReconfiguration(IfaceCreationData ifaceCreationData,
-            int ifaceType) {
+            @HdmIfaceTypeForCreation int createIfaceType) {
         if (mDbg) {
             Log.d(TAG, "executeChipReconfiguration: ifaceCreationData=" + ifaceCreationData
-                    + ", ifaceType=" + ifaceType);
+                    + ", createIfaceType=" + createIfaceType);
         }
         synchronized (mLock) {
             try {
@@ -1901,29 +2273,44 @@
                 // create new interface
                 Mutable<WifiStatus> statusResp = new Mutable<>();
                 Mutable<IWifiIface> ifaceResp = new Mutable<>();
-                switch (ifaceType) {
-                    case IfaceType.STA:
+                switch (createIfaceType) {
+                    case HDM_CREATE_IFACE_STA:
                         ifaceCreationData.chipInfo.chip.createStaIface(
                                 (WifiStatus status, IWifiStaIface iface) -> {
                                     statusResp.value = status;
                                     ifaceResp.value = iface;
                                 });
                         break;
-                    case IfaceType.AP:
+                    case HDM_CREATE_IFACE_AP_BRIDGE:
+                        android.hardware.wifi.V1_5.IWifiChip chip15 =
+                                getWifiChipForV1_5Mockable(ifaceCreationData.chipInfo.chip);
+                        if (chip15 != null) {
+                            chip15.createBridgedApIface(
+                                    (WifiStatus status,
+                                    android.hardware.wifi.V1_5.IWifiApIface iface) -> {
+                                        statusResp.value = status;
+                                        ifaceResp.value = iface;
+                                    });
+                        } else {
+                            Log.e(TAG, "Hal doesn't support to create AP bridge mode");
+                            statusResp.value.code = WifiStatusCode.ERROR_NOT_SUPPORTED;
+                        }
+                        break;
+                    case HDM_CREATE_IFACE_AP:
                         ifaceCreationData.chipInfo.chip.createApIface(
                                 (WifiStatus status, IWifiApIface iface) -> {
                                     statusResp.value = status;
                                     ifaceResp.value = iface;
                                 });
                         break;
-                    case IfaceType.P2P:
+                    case HDM_CREATE_IFACE_P2P:
                         ifaceCreationData.chipInfo.chip.createP2pIface(
                                 (WifiStatus status, IWifiP2pIface iface) -> {
                                     statusResp.value = status;
                                     ifaceResp.value = iface;
                                 });
                         break;
-                    case IfaceType.NAN:
+                    case HDM_CREATE_IFACE_NAN:
                         ifaceCreationData.chipInfo.chip.createNanIface(
                                 (WifiStatus status, IWifiNanIface iface) -> {
                                     statusResp.value = status;
@@ -1933,8 +2320,9 @@
                 }
 
                 if (statusResp.value.code != WifiStatusCode.SUCCESS) {
-                    Log.e(TAG, "executeChipReconfiguration: failed to create interface ifaceType="
-                            + ifaceType + ": " + statusString(statusResp.value));
+                    Log.e(TAG, "executeChipReconfiguration: failed to create interface"
+                            + " createIfaceType=" + createIfaceType + ": "
+                            + statusString(statusResp.value));
                     return null;
                 }
 
@@ -2008,63 +2396,6 @@
         }
     }
 
-    // dispatch all available for request listeners of the specified type AND clean-out the list:
-    // listeners are called once at most!
-    private boolean dispatchAvailableForRequestListeners() {
-        if (VDBG) Log.d(TAG, "dispatchAvailableForRequestListeners");
-
-        synchronized (mLock) {
-            WifiChipInfo[] chipInfos = getAllChipInfo();
-            if (chipInfos == null) {
-                Log.e(TAG, "dispatchAvailableForRequestListeners: no chip info found");
-                stopWifi(); // major error: shutting down
-                return false;
-            }
-            if (VDBG) {
-                Log.d(TAG, "dispatchAvailableForRequestListeners: chipInfos="
-                        + Arrays.deepToString(chipInfos));
-            }
-
-            for (int ifaceType : IFACE_TYPES_BY_PRIORITY) {
-                dispatchAvailableForRequestListenersForType(ifaceType, chipInfos);
-            }
-        }
-
-        return true;
-    }
-
-
-    private void dispatchAvailableForRequestListenersForType(int ifaceType,
-            WifiChipInfo[] chipInfos) {
-        if (VDBG) Log.d(TAG, "dispatchAvailableForRequestListenersForType: ifaceType=" + ifaceType);
-
-        synchronized (mLock) {
-            Map<InterfaceAvailableForRequestListenerProxy, Boolean> listeners =
-                    mInterfaceAvailableForRequestListeners.get(ifaceType);
-
-            if (listeners.size() == 0) {
-                return;
-            }
-
-            boolean isAvailable = isItPossibleToCreateIface(chipInfos, ifaceType);
-
-            if (VDBG) {
-                Log.d(TAG, "Interface available for: ifaceType=" + ifaceType + " = " + isAvailable);
-            }
-            for (Map.Entry<InterfaceAvailableForRequestListenerProxy, Boolean> listenerEntry :
-                    listeners.entrySet()) {
-                if (listenerEntry.getValue() == null || listenerEntry.getValue() != isAvailable) {
-                    if (VDBG) {
-                        Log.d(TAG, "Interface available listener dispatched: ifaceType=" + ifaceType
-                                + ", listener=" + listenerEntry.getKey());
-                    }
-                    listenerEntry.getKey().triggerWithArg(isAvailable);
-                }
-                listenerEntry.setValue(isAvailable);
-            }
-        }
-    }
-
     // dispatch all destroyed listeners registered for the specified interface AND remove the
     // cache entry
     private void dispatchDestroyedListeners(String name, int type) {
@@ -2150,6 +2481,19 @@
         }
     }
 
+    private class SubsystemRestartListenerProxy extends
+            ListenerProxy<SubsystemRestartListener> {
+        SubsystemRestartListenerProxy(@NonNull SubsystemRestartListener subsystemRestartListener,
+                                        Handler handler) {
+            super(subsystemRestartListener, handler, "SubsystemRestartListenerProxy");
+        }
+
+        @Override
+        protected void action() {
+            mListener.onSubsystemRestart();
+        }
+    }
+
     private class InterfaceDestroyedListenerProxy extends
             ListenerProxy<InterfaceDestroyedListener> {
         private final String mIfaceName;
@@ -2166,19 +2510,6 @@
         }
     }
 
-    private class InterfaceAvailableForRequestListenerProxy extends
-            ListenerProxy<InterfaceAvailableForRequestListener> {
-        InterfaceAvailableForRequestListenerProxy(
-                InterfaceAvailableForRequestListener destroyedListener, Handler handler) {
-            super(destroyedListener, handler, "InterfaceAvailableForRequestListenerProxy");
-        }
-
-        @Override
-        protected void actionWithArg(boolean isAvailable) {
-            mListener.onAvailabilityChanged(isAvailable);
-        }
-    }
-
     private class InterfaceRttControllerLifecycleCallbackProxy implements
             InterfaceRttControllerLifecycleCallback {
         private InterfaceRttControllerLifecycleCallback mCallback;
@@ -2336,7 +2667,7 @@
 
     // Will return -1 for invalid results! Otherwise will return one of the 4 valid values.
     private static int getType(IWifiIface iface) {
-        MutableInt typeResp = new MutableInt(-1);
+        Mutable<Integer> typeResp = new Mutable<>(-1);
         try {
             iface.getType((WifiStatus status, int type) -> {
                 if (status.code == WifiStatusCode.SUCCESS) {
@@ -2353,16 +2684,62 @@
     }
 
     /**
+     * Checks for a successful status result.
+     *
+     * Failures are logged to mLog.
+     *
+     * @param status is the WifiStatus generated by a hal call
+     * @return true for success, false for failure
+     */
+    private boolean ok(String method, WifiStatus status) {
+        if (status.code == WifiStatusCode.SUCCESS) return true;
+
+        Log.e(TAG, "Error on " + method + ": " + statusString(status));
+        return false;
+    }
+
+    /**
+     * Get the chip capabilities
+     *
+     * This is called before creating an interface and needs at least v1.5 HAL.
+     *
+     * @param wifiChip WifiChip
+     * @return bitmask defined by HAL interface
+     */
+    public long getChipCapabilities(@NonNull IWifiChip wifiChip) {
+        long featureSet = 0;
+        if (wifiChip == null) return featureSet;
+
+        try {
+            final Mutable<Long> feat = new Mutable<>(CHIP_CAPABILITY_UNINITIALIZED);
+            synchronized (mLock) {
+                android.hardware.wifi.V1_5.IWifiChip iWifiChipV15 =
+                        getWifiChipForV1_5Mockable(wifiChip);
+                // HAL newer than v1.5 support getting capabilities before creating an interface.
+                if (iWifiChipV15 != null) {
+                    iWifiChipV15.getCapabilities_1_5((status, capabilities) -> {
+                        if (!ok("getCapabilities_1_5", status)) return;
+                        feat.value = (long) capabilities;
+                    });
+                }
+            }
+            featureSet = feat.value;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception on getCapabilities: " + e);
+            return 0;
+        }
+        return featureSet;
+    }
+
+    /**
      * Dump the internal state of the class.
      */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("HalDeviceManager:");
+        pw.println("Dump of HalDeviceManager:");
         pw.println("  mServiceManager: " + mServiceManager);
         pw.println("  mWifi: " + mWifi);
         pw.println("  mManagerStatusListeners: " + mManagerStatusListeners);
-        pw.println("  mInterfaceAvailableForRequestListeners: "
-                + mInterfaceAvailableForRequestListeners);
         pw.println("  mInterfaceInfoCache: " + mInterfaceInfoCache);
-        pw.println("  mDebugChipsInfo: " + Arrays.toString(mDebugChipsInfo));
+        pw.println("  mDebugChipsInfo: " + Arrays.toString(getAllChipInfo()));
     }
 }
diff --git a/service/java/com/android/server/wifi/HostapdHal.java b/service/java/com/android/server/wifi/HostapdHal.java
index 1ff8368..2c97b77 100644
--- a/service/java/com/android/server/wifi/HostapdHal.java
+++ b/service/java/com/android/server/wifi/HostapdHal.java
@@ -15,7 +15,6 @@
  */
 package com.android.server.wifi;
 
-
 import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.wifi.hostapd.V1_0.HostapdStatus;
@@ -23,11 +22,15 @@
 import android.hardware.wifi.hostapd.V1_0.IHostapd;
 import android.hardware.wifi.hostapd.V1_2.DebugLevel;
 import android.hardware.wifi.hostapd.V1_2.Ieee80211ReasonCode;
+import android.hardware.wifi.hostapd.V1_3.Bandwidth;
+import android.hardware.wifi.hostapd.V1_3.Generation;
 import android.hidl.manager.V1_0.IServiceManager;
 import android.hidl.manager.V1_0.IServiceNotification;
 import android.net.MacAddress;
+import android.net.wifi.ScanResult;
 import android.net.wifi.SoftApConfiguration;
 import android.net.wifi.SoftApConfiguration.BandType;
+import android.net.wifi.SoftApInfo;
 import android.net.wifi.WifiManager;
 import android.os.Handler;
 import android.os.IHwBinder.DeathRecipient;
@@ -36,7 +39,9 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.WifiNative.HostapdDeathEventHandler;
+import com.android.server.wifi.WifiNative.SoftApListener;
 import com.android.server.wifi.util.ApConfigUtil;
 import com.android.server.wifi.util.NativeUtil;
 import com.android.wifi.resources.R;
@@ -67,17 +72,12 @@
     private boolean mVerboseLoggingEnabled = false;
     private final Context mContext;
     private final Handler mEventHandler;
-    private boolean mForceApChannel = false;
-    private int mForcedApBand;
-    private int mForcedApChannel;
-    private String mConfig2gChannelList;
-    private String mConfig5gChannelList;
-    private String mConfig6gChannelList;
 
     // Hostapd HAL interface objects
     private IServiceManager mIServiceManager = null;
     private IHostapd mIHostapd;
     private HashMap<String, Runnable> mSoftApFailureListeners = new HashMap<>();
+    private SoftApListener mSoftApEventListener;
     private HostapdDeathEventHandler mDeathEventHandler;
     private ServiceManagerDeathRecipient mServiceManagerDeathRecipient;
     private HostapdDeathRecipient mHostapdDeathRecipient;
@@ -164,6 +164,16 @@
                 android.hardware.wifi.hostapd.V1_2.IHostapd.kInterfaceName);
     }
 
+    /**
+     * Uses the IServiceManager to check if the device is running V1_3 of the HAL from the VINTF for
+     * the device.
+     * @return true if supported, false otherwise.
+     */
+    private boolean isV1_3() {
+        return checkHalVersionByInterfaceName(
+                android.hardware.wifi.hostapd.V1_3.IHostapd.kInterfaceName);
+    }
+
     private boolean checkHalVersionByInterfaceName(String interfaceName) {
         if (interfaceName == null) {
             return false;
@@ -210,6 +220,13 @@
     }
 
     /**
+     * Returns whether or not the hostapd supported to get the AP info from the callback.
+     */
+    public boolean isApInfoCallbackSupported() {
+        return isV1_3();
+    }
+
+    /**
      * Registers a service notification for the IHostapd service, which triggers intialization of
      * the IHostapd
      * @return true if the service notification was successfully registered
@@ -291,6 +308,23 @@
         }
     }
 
+    private boolean registerCallback_1_3(
+            android.hardware.wifi.hostapd.V1_3.IHostapdCallback callback) {
+        synchronized (mLock) {
+            String methodStr = "registerCallback_1_3";
+            try {
+                android.hardware.wifi.hostapd.V1_3.IHostapd iHostapdV1_3 = getHostapdMockableV1_3();
+                if (iHostapdV1_3 == null) return false;
+                android.hardware.wifi.hostapd.V1_2.HostapdStatus status =
+                        iHostapdV1_3.registerCallback_1_3(callback);
+                return checkStatusAndLogFailure12(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
     /**
      * Initialize the IHostapd object.
      * @return true on success, false otherwise.
@@ -316,7 +350,13 @@
                 return false;
             }
             // Register for callbacks for 1.1 hostapd.
-            if (isV1_1() && !registerCallback(new HostapdCallback())) {
+            if (isV1_3()) {
+                if (!registerCallback_1_3(new HostapdCallback_1_3())) {
+                    Log.e(TAG, "Fail to regiester Callback 1_3, Stopping hostapd HIDL startup");
+                    mIHostapd = null;
+                    return false;
+                }
+            } else if (isV1_1() && !registerCallback(new HostapdCallback())) {
                 Log.e(TAG, "Fail to regiester Callback, Stopping hostapd HIDL startup");
                 mIHostapd = null;
                 return false;
@@ -329,76 +369,29 @@
     }
 
     /**
-     * Enable force-soft-AP-channel mode which takes effect when soft AP starts next time
-     * @param forcedApChannel The forced IEEE channel number
+     * Register the provided callback handler for SoftAp events.
+     * <p>
+     * Note that only one callback can be registered at a time - any registration overrides previous
+     * registrations.
+     *
+     * @param ifaceName Name of the interface.
+     * @param listener Callback listener for AP events.
+     * @return true on success, false on failure.
      */
-    void enableForceSoftApChannel(int forcedApChannel, int forcedApBand) {
-        mForceApChannel = true;
-        mForcedApChannel = forcedApChannel;
-        mForcedApBand = forcedApBand;
-    }
-
-    /**
-     * Disable force-soft-AP-channel mode which take effect when soft AP starts next time
-     */
-    void disableForceSoftApChannel() {
-        mForceApChannel = false;
-    }
-
-    private boolean isSendFreqRangesNeeded(@BandType int band) {
-        // Fist we check if one of the selected bands has restrictions in the overlay file.
-        // Note,
-        //   - We store the config string here for future use, hence we need to check all bands.
-        //   - If there is no OEM restriction, we store the full band
-        boolean retVal = false;
-        if ((band & SoftApConfiguration.BAND_2GHZ) != 0) {
-            mConfig2gChannelList =
-                mContext.getResources().getString(R.string.config_wifiSoftap2gChannelList);
-            if (TextUtils.isEmpty(mConfig2gChannelList)) {
-                mConfig2gChannelList = "1-14";
-            } else {
-                retVal = true;
-            }
+    public boolean registerApCallback(@NonNull String ifaceName,
+            @NonNull SoftApListener listener) {
+        if (listener == null) {
+            Log.e(TAG, "registerApCallback called with a null callback");
+            return false;
         }
 
-        if ((band & SoftApConfiguration.BAND_5GHZ) != 0) {
-            mConfig5gChannelList =
-                mContext.getResources().getString(R.string.config_wifiSoftap5gChannelList);
-            if (TextUtils.isEmpty(mConfig5gChannelList)) {
-                mConfig5gChannelList = "34-173";
-            } else {
-                retVal = true;
-            }
+        if (!isV1_3()) {
+            Log.d(TAG, "The current HAL doesn't support event callback.");
+            return false;
         }
-
-        if ((band & SoftApConfiguration.BAND_6GHZ) != 0) {
-            mConfig6gChannelList =
-                mContext.getResources().getString(R.string.config_wifiSoftap6gChannelList);
-            if (TextUtils.isEmpty(mConfig6gChannelList)) {
-                mConfig6gChannelList = "1-254";
-            } else {
-                retVal = true;
-            }
-        }
-
-        // If any of the selected band has restriction in the overlay file, we return true.
-        if (retVal) {
-            return true;
-        }
-
-        // Next, if only one of 5G or 6G is selected, then we need freqList to separate them
-        // Since there is no other way.
-        if (((band & SoftApConfiguration.BAND_5GHZ) != 0)
-                && ((band & SoftApConfiguration.BAND_6GHZ) == 0)) {
-            return true;
-        }
-        if (((band & SoftApConfiguration.BAND_5GHZ) == 0)
-                && ((band & SoftApConfiguration.BAND_6GHZ) != 0)) {
-            return true;
-        }
-
-        // In all other cases, we don't need to set the freqList
-        return false;
+        mSoftApEventListener = listener;
+        Log.i(TAG, "registerApCallback Successful in " + ifaceName);
+        return true;
     }
 
     /**
@@ -406,118 +399,68 @@
      *
      * @param ifaceName Name of the interface.
      * @param config Configuration to use for the AP.
+     * @param isMetered Indicates the network is metered or not.
      * @param onFailureListener A runnable to be triggered on failure.
      * @return true on success, false otherwise.
      */
     public boolean addAccessPoint(@NonNull String ifaceName, @NonNull SoftApConfiguration config,
-                                  @NonNull Runnable onFailureListener) {
+                                  boolean isMetered, @NonNull Runnable onFailureListener) {
         synchronized (mLock) {
             final String methodStr = "addAccessPoint";
-            IHostapd.IfaceParams ifaceParams = new IHostapd.IfaceParams();
-            ifaceParams.ifaceName = ifaceName;
-            ifaceParams.hwModeParams.enable80211N = true;
-            ifaceParams.hwModeParams.enable80211AC =
-                    mContext.getResources().getBoolean(
-                            R.bool.config_wifi_softap_ieee80211ac_supported);
-            int band;
-            boolean enableAcs = ApConfigUtil.isAcsSupported(mContext) && config.getChannel() == 0
-                    && !mForceApChannel;
-            if (enableAcs) {
-                ifaceParams.channelParams.enableAcs = true;
-                ifaceParams.channelParams.acsShouldExcludeDfs = !mContext.getResources()
-                        .getBoolean(R.bool.config_wifiSoftapAcsIncludeDfs);
-            }
-            ifaceParams.channelParams.channel =
-                    mForceApChannel ? mForcedApChannel : config.getChannel();
-            band = mForceApChannel ? mForcedApBand : config.getBand();
-
+            IHostapd.IfaceParams ifaceParamsV1_0 = prepareIfaceParamsV1_0(ifaceName, config);
             android.hardware.wifi.hostapd.V1_2.IHostapd.NetworkParams nwParamsV1_2 =
-                    prepareNetworkParams(config);
+                    prepareNetworkParamsV1_2(config);
             if (nwParamsV1_2 == null) return false;
             if (!checkHostapdAndLogFailure(methodStr)) return false;
             try {
                 HostapdStatus status;
-                if (!isV1_1() && !isV1_2()) {
-                    ifaceParams.channelParams.band = getHalBand(band);
-                    status = mIHostapd.addAccessPoint(ifaceParams, nwParamsV1_2.V1_0);
+                if (!isV1_1()) {
+                    // V1_0 case
+                    status = mIHostapd.addAccessPoint(ifaceParamsV1_0, nwParamsV1_2.V1_0);
                     if (!checkStatusAndLogFailure(status, methodStr)) {
                         return false;
                     }
                 } else {
-                    android.hardware.wifi.hostapd.V1_1.IHostapd.IfaceParams ifaceParams1_1 =
-                            new android.hardware.wifi.hostapd.V1_1.IHostapd.IfaceParams();
-                    ifaceParams1_1.V1_0 = ifaceParams;
+                    android.hardware.wifi.hostapd.V1_1.IHostapd.IfaceParams ifaceParamsV1_1 =
+                            prepareIfaceParamsV1_1(ifaceParamsV1_0, config);
                     if (!isV1_2()) {
-                        ifaceParams.channelParams.band = getHalBand(band);
-
-                        if (ifaceParams.channelParams.enableAcs) {
-                            if ((band & SoftApConfiguration.BAND_2GHZ) != 0) {
-                                ifaceParams1_1.channelParams.acsChannelRanges.addAll(
-                                        toAcsChannelRanges(mContext.getResources().getString(
-                                            R.string.config_wifiSoftap2gChannelList)));
-                            }
-                            if ((band & SoftApConfiguration.BAND_5GHZ) != 0) {
-                                ifaceParams1_1.channelParams.acsChannelRanges.addAll(
-                                        toAcsChannelRanges(mContext.getResources().getString(
-                                            R.string.config_wifiSoftap5gChannelList)));
-                            }
-                        }
-
+                        // V1_1 case
                         android.hardware.wifi.hostapd.V1_1.IHostapd iHostapdV1_1 =
                                 getHostapdMockableV1_1();
                         if (iHostapdV1_1 == null) return false;
-
-                        status = iHostapdV1_1.addAccessPoint_1_1(ifaceParams1_1, nwParamsV1_2.V1_0);
+                        status = iHostapdV1_1.addAccessPoint_1_1(ifaceParamsV1_1,
+                                nwParamsV1_2.V1_0);
                         if (!checkStatusAndLogFailure(status, methodStr)) {
                             return false;
                         }
                     } else {
+                        // V1_2 & V1_3 case
                         android.hardware.wifi.hostapd.V1_2.HostapdStatus status12;
-                        android.hardware.wifi.hostapd.V1_2.IHostapd.IfaceParams ifaceParams1_2 =
-                                new android.hardware.wifi.hostapd.V1_2.IHostapd.IfaceParams();
-                        ifaceParams1_2.V1_1 = ifaceParams1_1;
-
-                        ifaceParams1_2.hwModeParams.enable80211AX =
-                                mContext.getResources().getBoolean(
-                                    R.bool.config_wifiSoftapIeee80211axSupported);
-                        ifaceParams1_2.hwModeParams.enable6GhzBand =
-                                mContext.getResources().getBoolean(
-                                    R.bool.config_wifiSoftap6ghzSupported);
-                        ifaceParams1_2.hwModeParams.enableHeSingleUserBeamformer =
-                                mContext.getResources().getBoolean(
-                                    R.bool.config_wifiSoftapHeSuBeamformerSupported);
-                        ifaceParams1_2.hwModeParams.enableHeSingleUserBeamformee =
-                                mContext.getResources().getBoolean(
-                                    R.bool.config_wifiSoftapHeSuBeamformeeSupported);
-                        ifaceParams1_2.hwModeParams.enableHeMultiUserBeamformer =
-                                mContext.getResources().getBoolean(
-                                    R.bool.config_wifiSoftapHeMuBeamformerSupported);
-                        ifaceParams1_2.hwModeParams.enableHeTargetWakeTime =
-                                mContext.getResources().getBoolean(
-                                    R.bool.config_wifiSoftapHeTwtSupported);
-                        ifaceParams1_2.channelParams.bandMask = getHalBandMask(band);
-
-                        // Prepare freq ranges/lists if needed
-                        if (ifaceParams.channelParams.enableAcs
-                                && isSendFreqRangesNeeded(band)) {
-                            if ((band & SoftApConfiguration.BAND_2GHZ) != 0) {
-                                ifaceParams1_2.channelParams.acsChannelFreqRangesMhz.addAll(
-                                        toAcsFreqRanges(SoftApConfiguration.BAND_2GHZ));
-                            }
-                            if ((band & SoftApConfiguration.BAND_5GHZ) != 0) {
-                                ifaceParams1_2.channelParams.acsChannelFreqRangesMhz.addAll(
-                                        toAcsFreqRanges(SoftApConfiguration.BAND_5GHZ));
-                            }
-                            if ((band & SoftApConfiguration.BAND_6GHZ) != 0) {
-                                ifaceParams1_2.channelParams.acsChannelFreqRangesMhz.addAll(
-                                        toAcsFreqRanges(SoftApConfiguration.BAND_6GHZ));
-                            }
+                        android.hardware.wifi.hostapd.V1_2.IHostapd.IfaceParams ifaceParamsV1_2 =
+                                prepareIfaceParamsV1_2(ifaceParamsV1_1, config);
+                        if (!isV1_3()) {
+                            // V1_2 case
+                            android.hardware.wifi.hostapd.V1_2.IHostapd iHostapdV1_2 =
+                                    getHostapdMockableV1_2();
+                            if (iHostapdV1_2 == null) return false;
+                            status12 = iHostapdV1_2.addAccessPoint_1_2(ifaceParamsV1_2,
+                                    nwParamsV1_2);
+                        } else {
+                            // V1_3 case
+                            android.hardware.wifi.hostapd.V1_3
+                                    .IHostapd.NetworkParams nwParamsV1_3 =
+                                    new android.hardware.wifi.hostapd.V1_3
+                                    .IHostapd.NetworkParams();
+                            nwParamsV1_3.V1_2 = nwParamsV1_2;
+                            nwParamsV1_3.isMetered = isMetered;
+                            android.hardware.wifi.hostapd.V1_3.IHostapd.IfaceParams ifaceParams1_3 =
+                                    prepareIfaceParamsV1_3(ifaceParamsV1_2, config);
+                            android.hardware.wifi.hostapd.V1_3.IHostapd iHostapdV1_3 =
+                                    getHostapdMockableV1_3();
+                            if (iHostapdV1_3 == null) return false;
+                            status12 = iHostapdV1_3.addAccessPoint_1_3(ifaceParams1_3,
+                                    nwParamsV1_3);
                         }
-
-                        android.hardware.wifi.hostapd.V1_2.IHostapd iHostapdV1_2 =
-                                getHostapdMockableV1_2();
-                        if (iHostapdV1_2 == null) return false;
-                        status12 = iHostapdV1_2.addAccessPoint_1_2(ifaceParams1_2, nwParamsV1_2);
                         if (!checkStatusAndLogFailure12(status12, methodStr)) {
                             return false;
                         }
@@ -527,7 +470,7 @@
                 mSoftApFailureListeners.put(ifaceName, onFailureListener);
                 return true;
             } catch (IllegalArgumentException e) {
-                Log.e(TAG, "Unrecognized apBand: " + band);
+                Log.e(TAG, "Unrecognized apBand: " + config.getBand());
                 return false;
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -552,6 +495,7 @@
                     return false;
                 }
                 mSoftApFailureListeners.remove(ifaceName);
+                mSoftApEventListener = null;
                 return true;
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -774,8 +718,139 @@
         }
     }
 
+    @VisibleForTesting
+    protected android.hardware.wifi.hostapd.V1_3.IHostapd getHostapdMockableV1_3()
+            throws RemoteException {
+        synchronized (mLock) {
+            try {
+                return android.hardware.wifi.hostapd.V1_3.IHostapd.castFrom(mIHostapd);
+            } catch (NoSuchElementException e) {
+                Log.e(TAG, "Failed to get IHostapd", e);
+                return null;
+            }
+        }
+    }
+
+    private void updateIfaceParams_1_2FromResource(
+            android.hardware.wifi.hostapd.V1_2.IHostapd.IfaceParams ifaceParams12) {
+        ifaceParams12.hwModeParams.enable80211AX =
+                mContext.getResources().getBoolean(
+                R.bool.config_wifiSoftapIeee80211axSupported);
+        ifaceParams12.hwModeParams.enable6GhzBand =
+                ApConfigUtil.isBandSupported(SoftApConfiguration.BAND_6GHZ, mContext);
+        ifaceParams12.hwModeParams.enableHeSingleUserBeamformer =
+                mContext.getResources().getBoolean(
+                R.bool.config_wifiSoftapHeSuBeamformerSupported);
+        ifaceParams12.hwModeParams.enableHeSingleUserBeamformee =
+                mContext.getResources().getBoolean(
+                R.bool.config_wifiSoftapHeSuBeamformeeSupported);
+        ifaceParams12.hwModeParams.enableHeMultiUserBeamformer =
+                mContext.getResources().getBoolean(
+                R.bool.config_wifiSoftapHeMuBeamformerSupported);
+        ifaceParams12.hwModeParams.enableHeTargetWakeTime =
+                mContext.getResources().getBoolean(R.bool.config_wifiSoftapHeTwtSupported);
+    }
+
+    private android.hardware.wifi.hostapd.V1_0.IHostapd.IfaceParams
+            prepareIfaceParamsV1_0(String ifaceName, SoftApConfiguration config) {
+        IHostapd.IfaceParams ifaceParamsV1_0 = new IHostapd.IfaceParams();
+        ifaceParamsV1_0.ifaceName = ifaceName;
+        ifaceParamsV1_0.hwModeParams.enable80211N = true;
+        ifaceParamsV1_0.hwModeParams.enable80211AC = mContext.getResources().getBoolean(
+                R.bool.config_wifi_softap_ieee80211ac_supported);
+        boolean enableAcs = ApConfigUtil.isAcsSupported(mContext) && config.getChannel() == 0;
+        if (enableAcs) {
+            ifaceParamsV1_0.channelParams.enableAcs = true;
+            ifaceParamsV1_0.channelParams.acsShouldExcludeDfs = !mContext.getResources()
+                    .getBoolean(R.bool.config_wifiSoftapAcsIncludeDfs);
+        }
+        ifaceParamsV1_0.channelParams.channel = config.getChannel();
+        ifaceParamsV1_0.channelParams.band = getHalBand(config.getBand());
+        return ifaceParamsV1_0;
+    }
+
+    private android.hardware.wifi.hostapd.V1_1.IHostapd.IfaceParams
+            prepareIfaceParamsV1_1(
+            android.hardware.wifi.hostapd.V1_0.IHostapd.IfaceParams ifaceParamsV10,
+            SoftApConfiguration config) {
+        android.hardware.wifi.hostapd.V1_1.IHostapd.IfaceParams ifaceParamsV1_1 =
+                new android.hardware.wifi.hostapd.V1_1.IHostapd.IfaceParams();
+        ifaceParamsV1_1.V1_0 = ifaceParamsV10;
+        ifaceParamsV10.channelParams.band = getHalBand(config.getBand());
+
+        if (ifaceParamsV10.channelParams.enableAcs) {
+            if ((config.getBand() & SoftApConfiguration.BAND_2GHZ) != 0) {
+                ifaceParamsV1_1.channelParams.acsChannelRanges.addAll(
+                        toAcsChannelRanges(mContext.getResources().getString(
+                        R.string.config_wifiSoftap2gChannelList)));
+            }
+            if ((config.getBand() & SoftApConfiguration.BAND_5GHZ) != 0) {
+                ifaceParamsV1_1.channelParams.acsChannelRanges.addAll(
+                        toAcsChannelRanges(mContext.getResources().getString(
+                        R.string.config_wifiSoftap5gChannelList)));
+            }
+        }
+        return ifaceParamsV1_1;
+    }
+
+    private android.hardware.wifi.hostapd.V1_2.IHostapd.IfaceParams
+            prepareIfaceParamsV1_2(
+            android.hardware.wifi.hostapd.V1_1.IHostapd.IfaceParams ifaceParamsV11,
+            SoftApConfiguration config) {
+        android.hardware.wifi.hostapd.V1_2.IHostapd.IfaceParams ifaceParamsV1_2 =
+                new android.hardware.wifi.hostapd.V1_2.IHostapd.IfaceParams();
+        ifaceParamsV1_2.V1_1 = ifaceParamsV11;
+        updateIfaceParams_1_2FromResource(ifaceParamsV1_2);
+        //Update 80211ax support with the configuration.
+        ifaceParamsV1_2.hwModeParams.enable80211AX &= config.isIeee80211axEnabledInternal();
+
+        ifaceParamsV1_2.channelParams.bandMask = getHalBandMask(config.getBand());
+
+        // Prepare freq ranges/lists if needed
+        if (ifaceParamsV11.V1_0.channelParams.enableAcs && ApConfigUtil.isSendFreqRangesNeeded(
+                config.getBand(), mContext)) {
+            prepareAcsChannelFreqRangesMhz(ifaceParamsV1_2.channelParams, config.getBand());
+        }
+        return ifaceParamsV1_2;
+    }
+
+    private android.hardware.wifi.hostapd.V1_3.IHostapd.IfaceParams
+            prepareIfaceParamsV1_3(
+            android.hardware.wifi.hostapd.V1_2.IHostapd.IfaceParams ifaceParamsV12,
+            SoftApConfiguration config) {
+        android.hardware.wifi.hostapd.V1_3.IHostapd.IfaceParams ifaceParamsV1_3 =
+                new android.hardware.wifi.hostapd.V1_3.IHostapd.IfaceParams();
+        ifaceParamsV1_3.V1_2 = ifaceParamsV12;
+        ArrayList<android.hardware.wifi.hostapd.V1_3.IHostapd.ChannelParams>
+                channelParams1_3List = new ArrayList<>();
+        if (!SdkLevel.isAtLeastS()) {
+            return ifaceParamsV1_3;
+        }
+        for (int i = 0; i < config.getChannels().size(); i++) {
+            android.hardware.wifi.hostapd.V1_3.IHostapd.ChannelParams channelParam13 =
+                    new android.hardware.wifi.hostapd.V1_3.IHostapd.ChannelParams();
+            // Prepare channel
+            channelParam13.channel = config.getChannels().valueAt(i);
+            // Prepare enableAcs
+            channelParam13.enableAcs = ApConfigUtil.isAcsSupported(mContext)
+                    && channelParam13.channel == 0;
+            // Prepare the bandMask
+            channelParam13.V1_2.bandMask = getHalBandMask(config.getChannels().keyAt(i));
+            channelParam13.bandMask = channelParam13.V1_2.bandMask;
+            // Prepare  AcsChannelFreqRangesMhz
+            if (channelParam13.enableAcs && ApConfigUtil.isSendFreqRangesNeeded(
+                    config.getChannels().keyAt(i), mContext)) {
+                prepareAcsChannelFreqRangesMhz(
+                        channelParam13.V1_2, config.getChannels().keyAt(i));
+            }
+            channelParams1_3List.add(channelParam13);
+        }
+        ifaceParamsV1_3.channelParamsList = channelParams1_3List;
+        return ifaceParamsV1_3;
+    }
+
     private android.hardware.wifi.hostapd.V1_2.IHostapd.NetworkParams
-            prepareNetworkParams(SoftApConfiguration config) {
+            prepareNetworkParamsV1_2(SoftApConfiguration config) {
         android.hardware.wifi.hostapd.V1_2.IHostapd.NetworkParams nwParamsV1_2 =
                 new android.hardware.wifi.hostapd.V1_2.IHostapd.NetworkParams();
         nwParamsV1_2.V1_0.ssid.addAll(NativeUtil.stringToByteArrayList(config.getSsid()));
@@ -844,6 +919,9 @@
         if (ApConfigUtil.containsBand(apBand, SoftApConfiguration.BAND_6GHZ)) {
             bandMask |= android.hardware.wifi.hostapd.V1_2.IHostapd.BandMask.BAND_6_GHZ;
         }
+        if (ApConfigUtil.containsBand(apBand, SoftApConfiguration.BAND_60GHZ)) {
+            bandMask |= android.hardware.wifi.hostapd.V1_3.IHostapd.BandMask.BAND_60_GHZ;
+        }
 
         return bandMask;
     }
@@ -889,7 +967,7 @@
                     }
                     acsChannelRange.start = start;
                     acsChannelRange.end = end;
-                } else {
+                } else if (!TextUtils.isEmpty(channelRange)) {
                     acsChannelRange.start = Integer.parseInt(channelRange.trim());
                     acsChannelRange.end = acsChannelRange.start;
                 }
@@ -903,6 +981,27 @@
         return acsChannelRanges;
     }
 
+
+    /**
+     * Prepare the acsChannelFreqRangesMhz in V1_2.IHostapd.ChannelParams.
+     */
+    private void prepareAcsChannelFreqRangesMhz(
+            android.hardware.wifi.hostapd.V1_2.IHostapd.ChannelParams channelParams12,
+            @BandType int band) {
+        if ((band & SoftApConfiguration.BAND_2GHZ) != 0) {
+            channelParams12.acsChannelFreqRangesMhz.addAll(
+                    toAcsFreqRanges(SoftApConfiguration.BAND_2GHZ));
+        }
+        if ((band & SoftApConfiguration.BAND_5GHZ) != 0) {
+            channelParams12.acsChannelFreqRangesMhz.addAll(
+                    toAcsFreqRanges(SoftApConfiguration.BAND_5GHZ));
+        }
+        if ((band & SoftApConfiguration.BAND_6GHZ) != 0) {
+            channelParams12.acsChannelFreqRangesMhz.addAll(
+                    toAcsFreqRanges(SoftApConfiguration.BAND_6GHZ));
+        }
+    }
+
     /**
      * Convert channel list string like '1-6,11' to list of AcsFreqRange
      */
@@ -919,13 +1018,28 @@
         String channelListStr;
         switch (band) {
             case SoftApConfiguration.BAND_2GHZ:
-                channelListStr = mConfig2gChannelList;
+                channelListStr = mContext.getResources().getString(
+                        R.string.config_wifiSoftap2gChannelList);
+                if (TextUtils.isEmpty(channelListStr)) {
+                    channelListStr = ScanResult.BAND_24_GHZ_FIRST_CH_NUM + "-"
+                            + ScanResult.BAND_24_GHZ_LAST_CH_NUM;
+                }
                 break;
             case SoftApConfiguration.BAND_5GHZ:
-                channelListStr = mConfig5gChannelList;
+                channelListStr = mContext.getResources().getString(
+                        R.string.config_wifiSoftap5gChannelList);
+                if (TextUtils.isEmpty(channelListStr)) {
+                    channelListStr = ScanResult.BAND_5_GHZ_FIRST_CH_NUM + "-"
+                            + ScanResult.BAND_5_GHZ_LAST_CH_NUM;
+                }
                 break;
             case SoftApConfiguration.BAND_6GHZ:
-                channelListStr = mConfig6gChannelList;
+                channelListStr = mContext.getResources().getString(
+                        R.string.config_wifiSoftap6gChannelList);
+                if (TextUtils.isEmpty(channelListStr)) {
+                    channelListStr = ScanResult.BAND_6_GHZ_FIRST_CH_NUM + "-"
+                            + ScanResult.BAND_6_GHZ_LAST_CH_NUM;
+                }
                 break;
             default:
                 return acsFrequencyRanges;
@@ -949,7 +1063,7 @@
                     }
                     acsFrequencyRange.start = ApConfigUtil.convertChannelToFrequency(start, band);
                     acsFrequencyRange.end = ApConfigUtil.convertChannelToFrequency(end, band);
-                } else {
+                } else if (!TextUtils.isEmpty(channelRange)) {
                     int channel = Integer.parseInt(channelRange.trim());
                     acsFrequencyRange.start = ApConfigUtil.convertChannelToFrequency(channel, band);
                     acsFrequencyRange.end = acsFrequencyRange.start;
@@ -1037,6 +1151,108 @@
     }
 
     /**
+     * Map hal bandwidth to SoftApInfo.
+     *
+     * @param bandwidth The channel bandwidth of the AP which is defined in the HAL.
+     * @return The channel bandwidth in the SoftApinfo.
+     */
+    @VisibleForTesting
+    public int mapHalBandwidthToSoftApInfo(int bandwidth) {
+        switch (bandwidth) {
+            case Bandwidth.WIFI_BANDWIDTH_20_NOHT:
+                return SoftApInfo.CHANNEL_WIDTH_20MHZ_NOHT;
+            case Bandwidth.WIFI_BANDWIDTH_20:
+                return SoftApInfo.CHANNEL_WIDTH_20MHZ;
+            case Bandwidth.WIFI_BANDWIDTH_40:
+                return SoftApInfo.CHANNEL_WIDTH_40MHZ;
+            case Bandwidth.WIFI_BANDWIDTH_80:
+                return SoftApInfo.CHANNEL_WIDTH_80MHZ;
+            case Bandwidth.WIFI_BANDWIDTH_80P80:
+                return SoftApInfo.CHANNEL_WIDTH_80MHZ_PLUS_MHZ;
+            case Bandwidth.WIFI_BANDWIDTH_160:
+                return SoftApInfo.CHANNEL_WIDTH_160MHZ;
+            case Bandwidth.WIFI_BANDWIDTH_2160:
+                return SoftApInfo.CHANNEL_WIDTH_2160MHZ;
+            case Bandwidth.WIFI_BANDWIDTH_4320:
+                return SoftApInfo.CHANNEL_WIDTH_4320MHZ;
+            case Bandwidth.WIFI_BANDWIDTH_6480:
+                return SoftApInfo.CHANNEL_WIDTH_6480MHZ;
+            case Bandwidth.WIFI_BANDWIDTH_8640:
+                return SoftApInfo.CHANNEL_WIDTH_8640MHZ;
+            default:
+                return SoftApInfo.CHANNEL_WIDTH_INVALID;
+        }
+    }
+
+    /**
+     * Map hal generation to wifi standard.
+     *
+     * @param generation The operation mode of the AP which is defined in HAL.
+     * @return The wifi standard in the ScanResult.
+     */
+    @VisibleForTesting
+    public int mapHalGenerationToWifiStandard(int generation) {
+        switch (generation) {
+            case Generation.WIFI_STANDARD_LEGACY:
+                return ScanResult.WIFI_STANDARD_LEGACY;
+            case Generation.WIFI_STANDARD_11N:
+                return ScanResult.WIFI_STANDARD_11N;
+            case Generation.WIFI_STANDARD_11AC:
+                return ScanResult.WIFI_STANDARD_11AC;
+            case Generation.WIFI_STANDARD_11AX:
+                return ScanResult.WIFI_STANDARD_11AX;
+            case Generation.WIFI_STANDARD_11AD:
+                return ScanResult.WIFI_STANDARD_11AD;
+            default:
+                return ScanResult.WIFI_STANDARD_UNKNOWN;
+        }
+    }
+
+    private class HostapdCallback_1_3 extends
+            android.hardware.wifi.hostapd.V1_3.IHostapdCallback.Stub {
+        @Override
+        public void onFailure(String ifaceName) {
+            Log.w(TAG, "Failure on iface " + ifaceName);
+            Runnable onFailureListener = mSoftApFailureListeners.get(ifaceName);
+            if (onFailureListener != null) {
+                onFailureListener.run();
+            }
+        }
+
+        @Override
+        public void onApInstanceInfoChanged(String ifaceName, String apIfaceInstance,
+                int frequency, int bandwidth, int generation, byte[] apIfaceInstanceMacAddress) {
+            Log.d(TAG, "onApInstanceInfoChanged on " + ifaceName + " / " + apIfaceInstance);
+            try {
+                if (mSoftApEventListener != null) {
+                    mSoftApEventListener.onInfoChanged(apIfaceInstance, frequency,
+                            mapHalBandwidthToSoftApInfo(bandwidth),
+                            mapHalGenerationToWifiStandard(generation),
+                            MacAddress.fromBytes(apIfaceInstanceMacAddress));
+                }
+            } catch (IllegalArgumentException iae) {
+                Log.e(TAG, " Invalid apIfaceInstanceMacAddress, " + iae);
+            }
+        }
+
+        @Override
+        public void onConnectedClientsChanged(String ifaceName, String apIfaceInstance,
+                    byte[] clientAddress, boolean isConnected) {
+            try {
+                Log.d(TAG, "onConnectedClientsChanged on " + ifaceName + " / " + apIfaceInstance
+                        + " and Mac is " + MacAddress.fromBytes(clientAddress).toString()
+                        + " isConnected: " + isConnected);
+                if (mSoftApEventListener != null) {
+                    mSoftApEventListener.onConnectedClientsChanged(apIfaceInstance,
+                            MacAddress.fromBytes(clientAddress), isConnected);
+                }
+            } catch (IllegalArgumentException iae) {
+                Log.e(TAG, " Invalid clientAddress, " + iae);
+            }
+        }
+    }
+
+    /**
      * Set the debug log level for hostapd.
      *
      * @return true if request is sent successfully, false otherwise.
diff --git a/service/java/com/android/server/wifi/ImsiPrivacyProtectionExemptionStoreData.java b/service/java/com/android/server/wifi/ImsiPrivacyProtectionExemptionStoreData.java
index a9c99ae..582bee4 100644
--- a/service/java/com/android/server/wifi/ImsiPrivacyProtectionExemptionStoreData.java
+++ b/service/java/com/android/server/wifi/ImsiPrivacyProtectionExemptionStoreData.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,7 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package com.android.server.wifi;
 
 import android.annotation.NonNull;
@@ -28,10 +27,8 @@
 import org.xmlpull.v1.XmlSerializer;
 
 import java.io.IOException;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
-
 /**
  * This class performs serialization and parsing of XML data block that contain the map of IMSI
  * protection exemption user approval info.
@@ -41,7 +38,6 @@
     private static final String XML_TAG_SECTION_HEADER_IMSI_PROTECTION_EXEMPTION_CARRIER_MAP =
             "ImsiPrivacyProtectionExemptionMap";
     private static final String XML_TAG_CARRIER_EXEMPTION_MAP = "CarrierExemptionMap";
-
     /**
      * Interface define the data source for the carrier IMSI protection exemption map store data.
      */
@@ -52,56 +48,45 @@
          * @return Map of carrier Id to if allowed.
          */
         Map<Integer, Boolean> toSerialize();
-
         /**
          * Set the IMSI protection exemption map in the data source after serializing them from disk
          *
          * @param imsiProtectionExemptionMap Map of carrier Id to allowed or not.
          */
         void fromDeserialized(Map<Integer, Boolean> imsiProtectionExemptionMap);
-
         /**
          * Clear internal data structure in preparation for user switch or initial store read.
          */
         void reset();
-
         /**
          * Indicates whether there is new data to serialize.
          */
         boolean hasNewDataToSerialize();
     }
-
     private final DataSource mDataSource;
-
     /**
      * Set the data source fot store data.
      */
     public ImsiPrivacyProtectionExemptionStoreData(@NonNull DataSource dataSource) {
         mDataSource = dataSource;
     }
-
     @Override
     public void serializeData(XmlSerializer out, WifiConfigStoreEncryptionUtil encryptionUtil)
             throws XmlPullParserException, IOException {
         Map<String, Boolean> dataToSerialize = integerMapToStringMap(mDataSource.toSerialize());
         XmlUtil.writeNextValue(out, XML_TAG_CARRIER_EXEMPTION_MAP, dataToSerialize);
     }
-
     @Override
     public void deserializeData(XmlPullParser in, int outerTagDepth, int version,
             WifiConfigStoreEncryptionUtil encryptionUtil)
             throws XmlPullParserException, IOException {
         // Ignore empty reads.
         if (in == null) {
-            mDataSource.fromDeserialized(Collections.emptyMap());
             return;
         }
-
         mDataSource.fromDeserialized(parseCarrierImsiProtectionExemptionMap(in, outerTagDepth,
                 version, encryptionUtil));
-
     }
-
     private Map<Integer, Boolean> parseCarrierImsiProtectionExemptionMap(XmlPullParser in,
             int outerTagDepth,
             @WifiConfigStore.Version int version,
@@ -129,7 +114,6 @@
         }
         return stringMapToIntegerMap(protectionExemptionMap);
     }
-
     private Map<String, Boolean> integerMapToStringMap(Map<Integer, Boolean> input) {
         Map<String, Boolean> output = new HashMap<>();
         if (input == null) {
@@ -140,7 +124,6 @@
         }
         return output;
     }
-
     private Map<Integer, Boolean> stringMapToIntegerMap(Map<String, Boolean> input) {
         Map<Integer, Boolean> output = new HashMap<>();
         if (input == null) {
@@ -155,23 +138,18 @@
         }
         return output;
     }
-
-
     @Override
     public void resetData() {
         mDataSource.reset();
     }
-
     @Override
     public boolean hasNewDataToSerialize() {
         return mDataSource.hasNewDataToSerialize();
     }
-
     @Override
     public String getName() {
         return XML_TAG_SECTION_HEADER_IMSI_PROTECTION_EXEMPTION_CARRIER_MAP;
     }
-
     @Override
     public int getStoreFileId() {
         // User general store.
diff --git a/service/java/com/android/server/wifi/LastMileLogger.java b/service/java/com/android/server/wifi/LastMileLogger.java
index 1a43b6f..0c9c0ad 100644
--- a/service/java/com/android/server/wifi/LastMileLogger.java
+++ b/service/java/com/android/server/wifi/LastMileLogger.java
@@ -17,6 +17,8 @@
 package com.android.server.wifi;
 
 
+import android.util.ArrayMap;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.wifi.util.FileUtils;
 
@@ -26,6 +28,7 @@
 import java.io.PrintWriter;
 import java.nio.file.Files;
 import java.nio.file.Paths;
+import java.util.Map;
 
 /**
  * Provides a facility for capturing kernel trace events related to Wifi control and data paths.
@@ -50,25 +53,34 @@
 
     /**
      * Informs LastMileLogger that a connection event has occurred.
-     * @param event an event defined in BaseWifiDiagnostics
+     * @param event an event defined in WifiDiagnostics
      */
-    public void reportConnectionEvent(byte event) {
-        switch (event) {
-            case BaseWifiDiagnostics.CONNECTION_EVENT_STARTED:
-                enableTracing();
-                return;
-            case BaseWifiDiagnostics.CONNECTION_EVENT_SUCCEEDED:
-                disableTracing();
-                return;
-            case BaseWifiDiagnostics.CONNECTION_EVENT_FAILED:
-                disableTracing();
-                mLastMileLogForLastFailure = readTrace();
-                return;
-            case BaseWifiDiagnostics.CONNECTION_EVENT_TIMEOUT:
-                disableTracing();
-                mLastMileLogForLastFailure = readTrace();
-                return;
+    public void reportConnectionEvent(String ifaceName, byte event) {
+        boolean wasTracingEnabled = anyConnectionInProgress();
+
+        mIfaceToConnectionStatus.put(ifaceName, event);
+
+        boolean shouldTracingBeEnabled = anyConnectionInProgress();
+
+        if (!wasTracingEnabled && shouldTracingBeEnabled) {
+            enableTracing();
+        } else if (wasTracingEnabled && !shouldTracingBeEnabled) {
+            disableTracing();
         }
+
+        if (event == WifiDiagnostics.CONNECTION_EVENT_FAILED
+                || event == WifiDiagnostics.CONNECTION_EVENT_TIMEOUT) {
+            mLastMileLogForLastFailure = readTrace();
+        }
+    }
+
+    private boolean anyConnectionInProgress() {
+        for (byte status : mIfaceToConnectionStatus.values()) {
+            if (status == WifiDiagnostics.CONNECTION_EVENT_STARTED) {
+                return true;
+            }
+        }
+        return false;
     }
 
     /**
@@ -94,13 +106,17 @@
     private static final String WIFI_EVENT_RELEASE_PATH_DEBUGFS =
             "/sys/kernel/debug/tracing/instances/wifi/free_buffer";
 
-
     private String mEventBufferPath;
     private String mEventEnablePath;
     private String mEventReleasePath;
     private WifiLog mLog;
     private byte[] mLastMileLogForLastFailure;
     private FileInputStream mLastMileTraceHandle;
+    /**
+     * String key: iface name
+     * byte value: Connection status, one of WifiDiagnostics.CONNECTION_EVENT_*
+     */
+    private final Map<String, Byte> mIfaceToConnectionStatus = new ArrayMap<>();
 
     private void initLastMileLogger(WifiInjector injector, String bufferPath, String enablePath,
                           String releasePath) {
diff --git a/service/java/com/android/server/wifi/LinkProbeManager.java b/service/java/com/android/server/wifi/LinkProbeManager.java
index 8632688..58540a1 100644
--- a/service/java/com/android/server/wifi/LinkProbeManager.java
+++ b/service/java/com/android/server/wifi/LinkProbeManager.java
@@ -238,9 +238,8 @@
                             Log.d(TAG, "link probing success, elapsedTimeMs="
                                     + elapsedTimeMs);
                         }
-                        mWifiMetrics.logLinkProbeSuccess(
-                                timeSinceLastTxSuccessIncreaseMs, rssi, linkSpeed,
-                                elapsedTimeMs);
+                        mWifiMetrics.logLinkProbeSuccess(interfaceName,
+                                timeSinceLastTxSuccessIncreaseMs, rssi, linkSpeed, elapsedTimeMs);
                     }
 
                     @Override
@@ -248,7 +247,7 @@
                         if (mVerboseLoggingEnabled) {
                             Log.d(TAG, "link probing failure, reason=" + reason);
                         }
-                        mWifiMetrics.logLinkProbeFailure(
+                        mWifiMetrics.logLinkProbeFailure(interfaceName,
                                 timeSinceLastTxSuccessIncreaseMs, rssi, linkSpeed, reason);
                     }
                 },
diff --git a/service/java/com/android/server/wifi/LocalOnlyHotspotRequestInfo.java b/service/java/com/android/server/wifi/LocalOnlyHotspotRequestInfo.java
index 97d5f66..c17f07b 100644
--- a/service/java/com/android/server/wifi/LocalOnlyHotspotRequestInfo.java
+++ b/service/java/com/android/server/wifi/LocalOnlyHotspotRequestInfo.java
@@ -23,6 +23,7 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.WorkSource;
 
 import com.android.internal.util.Preconditions;
 
@@ -35,6 +36,7 @@
     static final int HOTSPOT_NO_ERROR = -1;
 
     private final int mPid;
+    private final WorkSource mWs;
     private final ILocalOnlyHotspotCallback mCallback;
     private final RequestingApplicationDeathCallback mDeathCallback;
     private final SoftApConfiguration mCustomConfig;
@@ -49,10 +51,11 @@
         void onLocalOnlyHotspotRequestorDeath(LocalOnlyHotspotRequestInfo requestor);
     }
 
-    LocalOnlyHotspotRequestInfo(@NonNull ILocalOnlyHotspotCallback callback,
+    LocalOnlyHotspotRequestInfo(@NonNull WorkSource ws, @NonNull ILocalOnlyHotspotCallback callback,
             @NonNull RequestingApplicationDeathCallback deathCallback,
             @Nullable SoftApConfiguration customConfig) {
         mPid = Binder.getCallingPid();
+        mWs = Preconditions.checkNotNull(ws);
         mCallback = Preconditions.checkNotNull(callback);
         mDeathCallback = Preconditions.checkNotNull(deathCallback);
         mCustomConfig = customConfig;
@@ -114,6 +117,10 @@
         return mPid;
     }
 
+    public @NonNull WorkSource getWorkSource() {
+        return mWs;
+    }
+
     public SoftApConfiguration getCustomConfig() {
         return mCustomConfig;
     }
diff --git a/service/java/com/android/server/wifi/MakeBeforeBreakManager.java b/service/java/com/android/server/wifi/MakeBeforeBreakManager.java
new file mode 100644
index 0000000..ec20612
--- /dev/null
+++ b/service/java/com/android/server/wifi/MakeBeforeBreakManager.java
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_PRIMARY;
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SECONDARY_TRANSIENT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Manages Make-Before-Break connection switching.
+ */
+public class MakeBeforeBreakManager {
+    private static final String TAG = "WifiMbbManager";
+
+    private final ActiveModeWarden mActiveModeWarden;
+    private final FrameworkFacade mFrameworkFacade;
+    private final Context mContext;
+    private final ClientModeImplMonitor mCmiMonitor;
+    private final ClientModeManagerBroadcastQueue mBroadcastQueue;
+    private final WifiMetrics mWifiMetrics;
+
+    private final List<Runnable> mOnAllSecondaryTransientCmmsStoppedListeners = new ArrayList<>();
+    private boolean mVerboseLoggingEnabled = false;
+
+    private static class MakeBeforeBreakInfo {
+        @NonNull
+        public final ConcreteClientModeManager oldPrimary;
+        @NonNull
+        public final ConcreteClientModeManager newPrimary;
+
+        MakeBeforeBreakInfo(
+                @NonNull ConcreteClientModeManager oldPrimary,
+                @NonNull ConcreteClientModeManager newPrimary) {
+            this.oldPrimary = oldPrimary;
+            this.newPrimary = newPrimary;
+        }
+
+        @Override
+        public String toString() {
+            return "MakeBeforeBreakInfo{"
+                    + "oldPrimary=" + oldPrimary
+                    + ", newPrimary=" + newPrimary
+                    + '}';
+        }
+    }
+
+    @Nullable
+    private MakeBeforeBreakInfo mMakeBeforeBreakInfo = null;
+
+    public MakeBeforeBreakManager(
+            @NonNull ActiveModeWarden activeModeWarden,
+            @NonNull FrameworkFacade frameworkFacade,
+            @NonNull Context context,
+            @NonNull ClientModeImplMonitor cmiMonitor,
+            @NonNull ClientModeManagerBroadcastQueue broadcastQueue,
+            @NonNull WifiMetrics wifiMetrics) {
+        mActiveModeWarden = activeModeWarden;
+        mFrameworkFacade = frameworkFacade;
+        mContext = context;
+        mCmiMonitor = cmiMonitor;
+        mBroadcastQueue = broadcastQueue;
+        mWifiMetrics = wifiMetrics;
+
+        mActiveModeWarden.registerModeChangeCallback(new ModeChangeCallback());
+        mCmiMonitor.registerListener(new ClientModeImplListener() {
+            @Override
+            public void onInternetValidated(@NonNull ConcreteClientModeManager clientModeManager) {
+                MakeBeforeBreakManager.this.onInternetValidated(clientModeManager);
+            }
+
+            @Override
+            public void onCaptivePortalDetected(
+                    @NonNull ConcreteClientModeManager clientModeManager) {
+                MakeBeforeBreakManager.this.onCaptivePortalDetected(clientModeManager);
+            }
+        });
+    }
+
+    public void setVerboseLoggingEnabled(boolean enabled) {
+        mVerboseLoggingEnabled = enabled;
+    }
+
+    private class ModeChangeCallback implements ActiveModeWarden.ModeChangeCallback {
+        @Override
+        public void onActiveModeManagerAdded(@NonNull ActiveModeManager activeModeManager) {
+            if (!mActiveModeWarden.isStaStaConcurrencySupportedForMbb()) {
+                return;
+            }
+            if (!(activeModeManager instanceof ConcreteClientModeManager)) {
+                return;
+            }
+            // just in case
+            recoverPrimary();
+        }
+
+        @Override
+        public void onActiveModeManagerRemoved(@NonNull ActiveModeManager activeModeManager) {
+            if (!mActiveModeWarden.isStaStaConcurrencySupportedForMbb()) {
+                return;
+            }
+            if (!(activeModeManager instanceof ConcreteClientModeManager)) {
+                return;
+            }
+            // if either the old or new primary stopped during MBB, abort the MBB attempt
+            ConcreteClientModeManager clientModeManager =
+                    (ConcreteClientModeManager) activeModeManager;
+            if (mMakeBeforeBreakInfo != null) {
+                boolean oldPrimaryStopped = clientModeManager == mMakeBeforeBreakInfo.oldPrimary;
+                boolean newPrimaryStopped = clientModeManager == mMakeBeforeBreakInfo.newPrimary;
+                if (oldPrimaryStopped || newPrimaryStopped) {
+                    Log.i(TAG, "MBB CMM stopped, aborting:"
+                            + " oldPrimary=" + mMakeBeforeBreakInfo.oldPrimary
+                            + " stopped=" + oldPrimaryStopped
+                            + " newPrimary=" + mMakeBeforeBreakInfo.newPrimary
+                            + " stopped=" + newPrimaryStopped);
+                    mMakeBeforeBreakInfo = null;
+                }
+            }
+            recoverPrimary();
+            triggerOnStoppedListenersIfNoMoreSecondaryTransientCmms();
+        }
+
+        @Override
+        public void onActiveModeManagerRoleChanged(@NonNull ActiveModeManager activeModeManager) {
+            if (!mActiveModeWarden.isStaStaConcurrencySupportedForMbb()) {
+                return;
+            }
+            if (!(activeModeManager instanceof ConcreteClientModeManager)) {
+                return;
+            }
+            ConcreteClientModeManager clientModeManager =
+                    (ConcreteClientModeManager) activeModeManager;
+            recoverPrimary();
+            maybeContinueMakeBeforeBreak(clientModeManager);
+            triggerOnStoppedListenersIfNoMoreSecondaryTransientCmms();
+        }
+    }
+
+    /**
+     * Failsafe: if there is no primary CMM but there exists exactly one CMM in
+     * {@link ActiveModeManager#ROLE_CLIENT_SECONDARY_TRANSIENT}, or multiple and MBB is not
+     * in progress (to avoid interfering with MBB), make it primary.
+     */
+    private void recoverPrimary() {
+        // already have a primary, do nothing
+        if (mActiveModeWarden.getPrimaryClientModeManagerNullable() != null) {
+            return;
+        }
+        List<ConcreteClientModeManager> secondaryTransientCmms =
+                mActiveModeWarden.getClientModeManagersInRoles(ROLE_CLIENT_SECONDARY_TRANSIENT);
+        // exactly 1 secondary transient, or > 1 secondary transient and MBB is not in progress
+        if (secondaryTransientCmms.size() == 1
+                || (mMakeBeforeBreakInfo == null && secondaryTransientCmms.size() > 1)) {
+            ConcreteClientModeManager manager = secondaryTransientCmms.get(0);
+            manager.setRole(ROLE_CLIENT_PRIMARY, mFrameworkFacade.getSettingsWorkSource(mContext));
+            Log.i(TAG, "recoveryPrimary kicking in, making " + manager + " primary and stopping"
+                    + " all other SECONDARY_TRANSIENT ClientModeManagers");
+            mWifiMetrics.incrementMakeBeforeBreakRecoverPrimaryCount();
+            // tear down the extra secondary transient CMMs (if they exist)
+            for (int i = 1; i < secondaryTransientCmms.size(); i++) {
+                secondaryTransientCmms.get(i).stop();
+            }
+        }
+    }
+
+    /**
+     * A ClientModeImpl instance has been validated to have internet connection. This will begin the
+     * Make-Before-Break transition to make this the new primary network.
+     *
+     * Change the previous primary ClientModeManager to role
+     * {@link ActiveModeManager#ROLE_CLIENT_SECONDARY_TRANSIENT} and change the new
+     * primary to role {@link ActiveModeManager#ROLE_CLIENT_PRIMARY}.
+     *
+     * @param newPrimary the corresponding ConcreteClientModeManager instance for the ClientModeImpl
+     *                   that had its internet connection validated.
+     */
+    private void onInternetValidated(@NonNull ConcreteClientModeManager newPrimary) {
+        if (!mActiveModeWarden.isStaStaConcurrencySupportedForMbb()) {
+            return;
+        }
+        if (newPrimary.getRole() != ROLE_CLIENT_SECONDARY_TRANSIENT) {
+            return;
+        }
+
+        ConcreteClientModeManager currentPrimary =
+                mActiveModeWarden.getPrimaryClientModeManagerNullable();
+
+        if (currentPrimary == null) {
+            Log.e(TAG, "changePrimaryClientModeManager(): current primary CMM is null!");
+            newPrimary.setRole(
+                    ROLE_CLIENT_PRIMARY, mFrameworkFacade.getSettingsWorkSource(mContext));
+            return;
+        }
+
+        Log.i(TAG, "Starting MBB switch primary from " + currentPrimary + " to " + newPrimary
+                + " by setting current primary's role to ROLE_CLIENT_SECONDARY_TRANSIENT");
+
+        mWifiMetrics.incrementMakeBeforeBreakInternetValidatedCount();
+
+        // Since role change is not atomic, we must first make the previous primary CMM into a
+        // secondary transient CMM. Thus, after this call to setRole() completes, there is no
+        // primary CMM and 2 secondary transient CMMs.
+        currentPrimary.setRole(
+                ROLE_CLIENT_SECONDARY_TRANSIENT, ActiveModeWarden.INTERNAL_REQUESTOR_WS);
+        // immediately send fake disconnection broadcasts upon changing primary CMM's role to
+        // SECONDARY_TRANSIENT, because as soon as the CMM becomes SECONDARY_TRANSIENT, its
+        // broadcasts will never be sent out again (BroadcastQueue only sends broadcasts for the
+        // current primary CMM). This is to preserve the legacy single STA behavior.
+        mBroadcastQueue.fakeDisconnectionBroadcasts();
+        mMakeBeforeBreakInfo = new MakeBeforeBreakInfo(currentPrimary, newPrimary);
+    }
+
+    private void onCaptivePortalDetected(@NonNull ConcreteClientModeManager newPrimary) {
+        if (!mActiveModeWarden.isStaStaConcurrencySupportedForMbb()) {
+            return;
+        }
+        if (newPrimary.getRole() != ROLE_CLIENT_SECONDARY_TRANSIENT) {
+            return;
+        }
+
+        ConcreteClientModeManager currentPrimary =
+                mActiveModeWarden.getPrimaryClientModeManagerNullable();
+
+        if (currentPrimary == null) {
+            Log.i(TAG, "onCaptivePortalDetected: Current primary is null, nothing to stop");
+        } else {
+            Log.i(TAG, "onCaptivePortalDetected: stopping current primary CMM");
+            currentPrimary.setWifiStateChangeBroadcastEnabled(false);
+            currentPrimary.stop();
+        }
+        // Once the currentPrimary teardown completes, recoverPrimary() will make the Captive
+        // Portal CMM the new primary, because it is the only SECONDARY_TRANSIENT CMM and no
+        // primary CMM exists.
+    }
+
+    private void maybeContinueMakeBeforeBreak(
+            @NonNull ConcreteClientModeManager roleChangedClientModeManager) {
+        // not in the middle of MBB
+        if (mMakeBeforeBreakInfo == null) {
+            return;
+        }
+        // not the CMM we're looking for, keep monitoring
+        if (roleChangedClientModeManager != mMakeBeforeBreakInfo.oldPrimary) {
+            return;
+        }
+        try {
+            // if old primary didn't transition to secondary transient, abort the MBB attempt
+            if (mMakeBeforeBreakInfo.oldPrimary.getRole() != ROLE_CLIENT_SECONDARY_TRANSIENT) {
+                Log.i(TAG, "old primary is no longer secondary transient, aborting MBB: "
+                        + mMakeBeforeBreakInfo.oldPrimary);
+                return;
+            }
+
+            // if somehow the next primary is no longer secondary transient, abort the MBB attempt
+            if (mMakeBeforeBreakInfo.newPrimary.getRole() != ROLE_CLIENT_SECONDARY_TRANSIENT) {
+                Log.i(TAG, "new primary is no longer secondary transient, abort MBB: "
+                        + mMakeBeforeBreakInfo.newPrimary);
+                return;
+            }
+
+            Log.i(TAG, "Continue MBB switch primary from " + mMakeBeforeBreakInfo.oldPrimary
+                    + " to " + mMakeBeforeBreakInfo.newPrimary
+                    + " by setting new Primary's role to ROLE_CLIENT_PRIMARY and reducing network"
+                    + " score");
+
+            // TODO(b/180974604): In theory, newPrimary.setRole() could still fail, but that would
+            //  still count as a MBB success in the metrics. But we don't really handle that
+            //  scenario well anyways, see TODO below.
+            mWifiMetrics.incrementMakeBeforeBreakSuccessCount();
+
+            // otherwise, actually set the new primary's role to primary.
+            mMakeBeforeBreakInfo.newPrimary.setRole(
+                    ROLE_CLIENT_PRIMARY, mFrameworkFacade.getSettingsWorkSource(mContext));
+
+            // linger old primary
+            // TODO(b/160346062): maybe do this after the new primary was fully transitioned to
+            //  ROLE_CLIENT_PRIMARY (since setRole() is asynchronous)
+            mMakeBeforeBreakInfo.oldPrimary.setShouldReduceNetworkScore(true);
+        } finally {
+            // end the MBB attempt
+            mMakeBeforeBreakInfo = null;
+        }
+    }
+
+    /** Dump fields for debugging. */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("Dump of MakeBeforeBreakManager");
+        pw.println("mMakeBeforeBreakInfo=" + mMakeBeforeBreakInfo);
+    }
+
+    /**
+     * Stop all ClientModeManagers with role
+     * {@link ActiveModeManager#ROLE_CLIENT_SECONDARY_TRANSIENT}.
+     *
+     * This is useful when an explicit connection was requested by an external caller
+     * (e.g. Settings, legacy app calling {@link android.net.wifi.WifiManager#enableNetwork}).
+     * We should abort any ongoing Make Before Break attempt to avoid interrupting the explicit
+     * connection.
+     *
+     * @param onStoppedListener triggered when all secondary transient CMMs have been stopped.
+     */
+    public void stopAllSecondaryTransientClientModeManagers(Runnable onStoppedListener) {
+        // no secondary transient CMM exists, trigger the callback immediately and return
+        if (mActiveModeWarden.getClientModeManagerInRole(ROLE_CLIENT_SECONDARY_TRANSIENT) == null) {
+            if (mVerboseLoggingEnabled) {
+                Log.d(TAG, "No secondary transient CMM active, trigger callback immediately");
+            }
+            onStoppedListener.run();
+            return;
+        }
+
+        // there exists at least 1 secondary transient CMM, but no primary
+        // TODO(b/177692017): Since switching roles is not atomic, there is a short period of time
+        //  during the Make Before Break transition when there are 2 SECONDARY_TRANSIENT CMMs and 0
+        //  primary CMMs. If this method is called at that time, it will destroy all CMMs, resulting
+        //  in no primary, and causing any subsequent connections to fail. Hopefully this does
+        //  not occur frequently.
+        if (mActiveModeWarden.getPrimaryClientModeManagerNullable() == null) {
+            Log.wtf(TAG, "Called stopAllSecondaryTransientClientModeManagers with no primary CMM!");
+        }
+
+        mOnAllSecondaryTransientCmmsStoppedListeners.add(onStoppedListener);
+        mActiveModeWarden.stopAllClientModeManagersInRole(ROLE_CLIENT_SECONDARY_TRANSIENT);
+    }
+
+    private void triggerOnStoppedListenersIfNoMoreSecondaryTransientCmms() {
+        // not all secondary transient CMMs stopped, keep waiting
+        if (mActiveModeWarden.getClientModeManagerInRole(ROLE_CLIENT_SECONDARY_TRANSIENT) != null) {
+            return;
+        }
+
+        if (mVerboseLoggingEnabled) {
+            Log.i(TAG, "All secondary transient CMMs stopped, triggering queued callbacks");
+        }
+
+        for (Runnable onStoppedListener : mOnAllSecondaryTransientCmmsStoppedListeners) {
+            onStoppedListener.run();
+        }
+        mOnAllSecondaryTransientCmmsStoppedListeners.clear();
+    }
+}
diff --git a/service/java/com/android/server/wifi/MboOceConstants.java b/service/java/com/android/server/wifi/MboOceConstants.java
index 66948b0..2f04878 100644
--- a/service/java/com/android/server/wifi/MboOceConstants.java
+++ b/service/java/com/android/server/wifi/MboOceConstants.java
@@ -176,6 +176,29 @@
     @Retention(RetentionPolicy.SOURCE)
     @interface MboCellularDataConnectionPreference{}
 
+    /** MBO spec v1.2, 4.2.4 Table 13: MBO Association Disallowed attribute */
+    public static final int MBO_ASSOC_DISALLOWED_REASON_INVALID = -1;
+    public static final int MBO_ASSOC_DISALLOWED_REASON_RESERVED_0 = 0;
+    public static final int MBO_ASSOC_DISALLOWED_REASON_UNSPECIFIED = 1;
+    public static final int MBO_ASSOC_DISALLOWED_REASON_MAX_NUM_STA_ASSOCIATED = 2;
+    public static final int MBO_ASSOC_DISALLOWED_REASON_AIR_INTERFACE_OVERLOADED = 3;
+    public static final int MBO_ASSOC_DISALLOWED_REASON_AUTH_SERVER_OVERLOADED = 4;
+    public static final int MBO_ASSOC_DISALLOWED_REASON_INSUFFICIENT_RSSI = 5;
+    public static final int MBO_ASSOC_DISALLOWED_REASON_RESERVED = 254;
+
+    @IntDef(prefix = { "MBO_ASSOC_DISALLOWED_REASON_" }, value = {
+            MBO_ASSOC_DISALLOWED_REASON_INVALID,
+            MBO_ASSOC_DISALLOWED_REASON_RESERVED_0,
+            MBO_ASSOC_DISALLOWED_REASON_UNSPECIFIED,
+            MBO_ASSOC_DISALLOWED_REASON_MAX_NUM_STA_ASSOCIATED,
+            MBO_ASSOC_DISALLOWED_REASON_AIR_INTERFACE_OVERLOADED,
+            MBO_ASSOC_DISALLOWED_REASON_AUTH_SERVER_OVERLOADED,
+            MBO_ASSOC_DISALLOWED_REASON_INSUFFICIENT_RSSI,
+            MBO_ASSOC_DISALLOWED_REASON_RESERVED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface MboAssocDisallowedReasonCode{}
+
     /** default Blocklist duration when AP doesn't advertise non zero MBO assoc retry delay */
     public static final long DEFAULT_BLOCKLIST_DURATION_MS = 300_000; // 5 minutes
 
diff --git a/service/java/com/android/server/wifi/MboOceController.java b/service/java/com/android/server/wifi/MboOceController.java
index d71034d..b107f49 100644
--- a/service/java/com/android/server/wifi/MboOceController.java
+++ b/service/java/com/android/server/wifi/MboOceController.java
@@ -19,6 +19,7 @@
 import static android.net.wifi.WifiManager.WIFI_FEATURE_MBO;
 import static android.net.wifi.WifiManager.WIFI_FEATURE_OCE;
 
+import android.hardware.wifi.supplicant.V1_4.ISupplicantStaIfaceCallback.MboAssocDisallowedReasonCode;
 import android.telephony.PhoneStateListener;
 import android.telephony.TelephonyManager;
 import android.util.Log;
@@ -35,28 +36,29 @@
     private boolean mIsOceSupported = false;
     private boolean mVerboseLoggingEnabled = false;
 
-    private final WifiNative mWifiNative;
     private final TelephonyManager mTelephonyManager;
+    private final ActiveModeWarden mActiveModeWarden;
 
     /**
      * Create new instance of MboOceController.
      */
-    public MboOceController(TelephonyManager telephonyManager,
-                            WifiNative wifiNative) {
+    public MboOceController(TelephonyManager telephonyManager, ActiveModeWarden activeModeWarden) {
         mTelephonyManager = telephonyManager;
-        mWifiNative = wifiNative;
+        mActiveModeWarden = activeModeWarden;
     }
 
     /**
      * Enable MBO and OCE functionality.
      */
     public void enable() {
-        String iface = mWifiNative.getClientInterfaceName();
-        if (iface == null) {
+        ClientModeManager clientModeManager =
+                mActiveModeWarden.getPrimaryClientModeManagerNullable();
+        if (clientModeManager == null) {
             return;
         }
-        mIsMboSupported = (mWifiNative.getSupportedFeatureSet(iface) & WIFI_FEATURE_MBO) != 0;
-        mIsOceSupported = (mWifiNative.getSupportedFeatureSet(iface) & WIFI_FEATURE_OCE) != 0;
+        long supportedFeatures = clientModeManager.getSupportedFeatures();
+        mIsMboSupported = (supportedFeatures & WIFI_FEATURE_MBO) != 0;
+        mIsOceSupported = (supportedFeatures & WIFI_FEATURE_OCE) != 0;
         mEnabled = true;
         if (mVerboseLoggingEnabled) {
             Log.d(TAG, "Enable MBO-OCE MBO support: " + mIsMboSupported
@@ -99,8 +101,9 @@
         public void onDataConnectionStateChanged(int state, int networkType) {
             boolean dataAvailable;
 
-            String iface = mWifiNative.getClientInterfaceName();
-            if (iface == null) {
+            ClientModeManager clientModeManager =
+                    mActiveModeWarden.getPrimaryClientModeManagerNullable();
+            if (clientModeManager == null) {
                 return;
             }
             if (!mEnabled) {
@@ -118,7 +121,7 @@
             if (mVerboseLoggingEnabled) {
                 Log.d(TAG, "Cell Data: " + dataAvailable);
             }
-            mWifiNative.setMboCellularDataStatus(iface, dataAvailable);
+            clientModeManager.setMboCellularDataStatus(dataAvailable);
         }
     };
 
@@ -145,4 +148,81 @@
                     ", cellPref=").append(mCellPreference).toString();
         }
     }
+
+    /**
+     * OceRssiBasedAssocRejectAttr is extracted from (Re-)Association response frame from an OCE AP
+     * to indicate that the AP has rejected the (Re-)Association request on the basis of
+     * insufficient RSSI.
+     * Refer OCE spec v1.0 section 4.2.2 Table 7.
+     */
+    public static class OceRssiBasedAssocRejectAttr {
+        /*
+         * Delta RSSI - The difference in dB between the minimum RSSI at which
+         * the AP would accept a (Re-)Association request from the STA before
+         * Retry Delay expires and the AP's measurement of the RSSI at which the
+         * (Re-)Association request was received.
+         */
+        public int mDeltaRssi;
+        /*
+         * Retry Delay - The time period in seconds for which the AP will not
+         * accept any subsequent (Re-)Association requests from the STA, unless
+         * the received RSSI has improved by Delta RSSI.
+         */
+        public int mRetryDelayS;
+
+        public OceRssiBasedAssocRejectAttr(int deltaRssi, int retryDelayS) {
+            this.mDeltaRssi = deltaRssi;
+            this.mRetryDelayS = retryDelayS;
+        }
+
+        @Override
+        public String toString() {
+            return new StringBuilder("OceRssiBasedAssocRejectAttr Delta Rssi=")
+                    .append(mDeltaRssi).append(
+                    ", Retry Delay=").append(mRetryDelayS).toString();
+        }
+    }
+
+    /**
+     * MboAssocDisallowedAttr is extracted from (Re-)Association response frame from the MBO AP
+     * to indicate that the AP is not accepting new associations.
+     * Refer MBO spec v1.2 section 4.2.4 Table 13 for the details of reason code.
+     */
+    public static class MboAssocDisallowedAttr {
+        /*
+         * Reason Code - The reason why the AP is not accepting new
+         * associations.
+         */
+        public @MboOceConstants.MboAssocDisallowedReasonCode int mReasonCode;
+
+        public MboAssocDisallowedAttr(int reasonCode) {
+            mReasonCode = halToFrameworkMboAssocRDisallowedReasonCode(reasonCode);
+        }
+
+        @Override
+        public String toString() {
+            return new StringBuilder("MboAssocDisallowedAttr Reason code=")
+                    .append(mReasonCode).toString();
+        }
+
+        private @MboOceConstants.MboAssocDisallowedReasonCode int
+                halToFrameworkMboAssocRDisallowedReasonCode(int reasonCode) {
+            switch (reasonCode) {
+                case MboAssocDisallowedReasonCode.RESERVED:
+                    return MboOceConstants.MBO_ASSOC_DISALLOWED_REASON_RESERVED_0;
+                case MboAssocDisallowedReasonCode.UNSPECIFIED:
+                    return MboOceConstants.MBO_ASSOC_DISALLOWED_REASON_UNSPECIFIED;
+                case MboAssocDisallowedReasonCode.MAX_NUM_STA_ASSOCIATED:
+                    return MboOceConstants.MBO_ASSOC_DISALLOWED_REASON_MAX_NUM_STA_ASSOCIATED;
+                case MboAssocDisallowedReasonCode.AIR_INTERFACE_OVERLOADED:
+                    return MboOceConstants.MBO_ASSOC_DISALLOWED_REASON_AIR_INTERFACE_OVERLOADED;
+                case MboAssocDisallowedReasonCode.AUTH_SERVER_OVERLOADED:
+                    return MboOceConstants.MBO_ASSOC_DISALLOWED_REASON_AUTH_SERVER_OVERLOADED;
+                case MboAssocDisallowedReasonCode.INSUFFICIENT_RSSI:
+                    return MboOceConstants.MBO_ASSOC_DISALLOWED_REASON_INSUFFICIENT_RSSI;
+                default:
+                    return MboOceConstants.MBO_ASSOC_DISALLOWED_REASON_RESERVED;
+            }
+        }
+    }
 }
diff --git a/service/java/com/android/server/wifi/NetworkConnectionEventInfo.java b/service/java/com/android/server/wifi/NetworkConnectionEventInfo.java
new file mode 100644
index 0000000..c73e7da
--- /dev/null
+++ b/service/java/com/android/server/wifi/NetworkConnectionEventInfo.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.annotation.NonNull;
+import android.net.wifi.WifiSsid;
+
+/** Data class to hold information for a {@link WifiMonitor#NETWORK_CONNECTION_EVENT}. */
+public class NetworkConnectionEventInfo {
+    public final int networkId;
+    @NonNull
+    public final WifiSsid wifiSsid;
+    @NonNull
+    public final String bssid;
+    public final boolean isFilsConnection;
+
+    public NetworkConnectionEventInfo(int networkId, WifiSsid wifiSsid, String bssid,
+            boolean isFilsConnection) {
+        this.networkId = networkId;
+        this.wifiSsid = wifiSsid;
+        this.bssid = bssid;
+        this.isFilsConnection = isFilsConnection;
+    }
+
+    @Override
+    public String toString() {
+        return "NetworkConnectionEventInfo{"
+                + "networkId=" + networkId
+                + ", wifiSsid=" + wifiSsid
+                + ", bssid='" + bssid + '\''
+                + ", isFilsConnection=" + isFilsConnection
+                + '}';
+    }
+}
diff --git a/service/java/com/android/server/wifi/NetworkListStoreData.java b/service/java/com/android/server/wifi/NetworkListStoreData.java
index 95c3ba3..ccdd09f 100644
--- a/service/java/com/android/server/wifi/NetworkListStoreData.java
+++ b/service/java/com/android/server/wifi/NetworkListStoreData.java
@@ -247,7 +247,7 @@
                     }
                     parsedConfig = WifiConfigurationXmlUtil.parseFromXml(in, outerTagDepth + 1,
                             version >= ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION,
-                            encryptionUtil);
+                            encryptionUtil, false);
                     break;
                 case XML_TAG_SECTION_HEADER_NETWORK_STATUS:
                     if (status != null) {
@@ -285,21 +285,19 @@
         String configKeyParsed = parsedConfig.first;
         WifiConfiguration configuration = parsedConfig.second;
 
-        if (configuration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
-            fixSaeNetworkSecurityBits(configuration);
-        }
+        configuration.convertLegacyFieldsToSecurityParamsIfNeeded();
+
         // b/153435438: Added to deal with badly formed WifiConfiguration from apps.
         if (configuration.preSharedKey != null && !configuration.needsPreSharedKey()) {
             Log.e(TAG, "preSharedKey set with an invalid KeyMgmt, resetting KeyMgmt to WPA_PSK");
-            configuration.allowedKeyManagement.clear();
-            configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+            configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
             // Recreate configKey to pass the check below.
             configKeyParsed = configuration.getKey();
         }
 
         String configKeyCalculated = configuration.getKey();
         if (!configKeyParsed.equals(configKeyCalculated)) {
-            throw new XmlPullParserException(
+            throw new IllegalStateException(
                     "Configuration key does not match. Retrieved: " + configKeyParsed
                             + ", Calculated: " + configKeyCalculated);
         }
@@ -325,34 +323,5 @@
         }
         return configuration;
     }
-
-    private void fixSaeNetworkSecurityBits(WifiConfiguration saeNetwork) {
-        // SAE saved networks Auth Algorithm set to OPEN need to be have this field cleared.
-        if (saeNetwork.allowedAuthAlgorithms.get(WifiConfiguration.AuthAlgorithm.OPEN)) {
-            saeNetwork.allowedAuthAlgorithms.clear();
-        }
-        // SAE saved networks Pairwise Cipher with TKIP enabled need to be have this bit
-        // cleared.
-        if (saeNetwork.allowedPairwiseCiphers.get(WifiConfiguration.PairwiseCipher.TKIP)) {
-            saeNetwork.allowedPairwiseCiphers.clear(WifiConfiguration.PairwiseCipher.TKIP);
-        }
-        // SAE saved networks Protocols with WPA enabled need to be have this bit cleared.
-        if (saeNetwork.allowedProtocols.get(WifiConfiguration.Protocol.WPA)) {
-            saeNetwork.allowedProtocols.clear(WifiConfiguration.Protocol.WPA);
-        }
-        // SAE saved networks Group Ciphers with legacy ciphers enabled, need to be have these
-        // bits cleared.
-        if (saeNetwork.allowedGroupCiphers.get(WifiConfiguration.GroupCipher.WEP40)) {
-            saeNetwork.allowedGroupCiphers.clear(WifiConfiguration.GroupCipher.WEP40);
-        }
-        if (saeNetwork.allowedGroupCiphers.get(WifiConfiguration.GroupCipher.WEP104)) {
-            saeNetwork.allowedGroupCiphers.clear(WifiConfiguration.GroupCipher.WEP104);
-        }
-        if (saeNetwork.allowedGroupCiphers.get(WifiConfiguration.GroupCipher.TKIP)) {
-            saeNetwork.allowedGroupCiphers.clear(WifiConfiguration.GroupCipher.TKIP);
-        }
-        saeNetwork.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.GCMP_256);
-        saeNetwork.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.GCMP_256);
-    }
 }
 
diff --git a/service/java/com/android/server/wifi/NetworkSuggestionNominator.java b/service/java/com/android/server/wifi/NetworkSuggestionNominator.java
index 8c901cf..8d28d7a 100644
--- a/service/java/com/android/server/wifi/NetworkSuggestionNominator.java
+++ b/service/java/com/android/server/wifi/NetworkSuggestionNominator.java
@@ -16,22 +16,27 @@
 
 package com.android.server.wifi;
 
+import static com.android.server.wifi.WifiNetworkSelector.toNetworkString;
+
 import android.annotation.NonNull;
 import android.net.wifi.WifiConfiguration;
+import android.telephony.TelephonyManager;
 import android.util.LocalLog;
 import android.util.Log;
 import android.util.Pair;
+import android.util.SparseArray;
 
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.WifiNetworkSuggestionsManager.ExtendedWifiNetworkSuggestion;
 import com.android.server.wifi.hotspot2.PasspointNetworkNominateHelper;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -54,239 +59,278 @@
     private final PasspointNetworkNominateHelper mPasspointNetworkNominateHelper;
     private final LocalLog mLocalLog;
     private final WifiCarrierInfoManager mWifiCarrierInfoManager;
+    private final WifiMetrics mWifiMetrics;
 
     NetworkSuggestionNominator(WifiNetworkSuggestionsManager networkSuggestionsManager,
             WifiConfigManager wifiConfigManager, PasspointNetworkNominateHelper nominateHelper,
-            LocalLog localLog, WifiCarrierInfoManager wifiCarrierInfoManager) {
+            LocalLog localLog, WifiCarrierInfoManager wifiCarrierInfoManager,
+            WifiMetrics wifiMetrics) {
         mWifiNetworkSuggestionsManager = networkSuggestionsManager;
         mWifiConfigManager = wifiConfigManager;
         mPasspointNetworkNominateHelper = nominateHelper;
         mLocalLog = localLog;
         mWifiCarrierInfoManager = wifiCarrierInfoManager;
+        mWifiMetrics = wifiMetrics;
     }
 
     @Override
     public void update(List<ScanDetail> scanDetails) {
-        // TODO(b/115504887): This could be used to re-evaluate any temporary blacklists.
+        // Update the matching profiles into WifiConfigManager, help displaying Suggestion and
+        // Passpoint networks in Wifi Picker
+        addOrUpdateSuggestionsToWifiConfigManger(scanDetails);
+        mPasspointNetworkNominateHelper.getPasspointNetworkCandidates(scanDetails, true);
     }
 
     @Override
     public void nominateNetworks(List<ScanDetail> scanDetails,
-            WifiConfiguration currentNetwork, String currentBssid, boolean connected,
-            boolean untrustedNetworkAllowed,
+            boolean untrustedNetworkAllowed, boolean oemPaidNetworkAllowed,
+            boolean oemPrivateNetworkAllowed,
             @NonNull OnConnectableListener onConnectableListener) {
         if (scanDetails.isEmpty()) {
             return;
         }
         MatchMetaInfo matchMetaInfo = new MatchMetaInfo();
-        Set<ExtendedWifiNetworkSuggestion> autoJoinDisabledSuggestions = new HashSet<>();
 
-        findMatchedPasspointSuggestionNetworks(scanDetails, matchMetaInfo, untrustedNetworkAllowed);
-        findMatchedSuggestionNetworks(scanDetails, matchMetaInfo,
-                autoJoinDisabledSuggestions, untrustedNetworkAllowed);
+        findMatchedPasspointSuggestionNetworks(
+                scanDetails, matchMetaInfo, untrustedNetworkAllowed, oemPaidNetworkAllowed,
+                oemPrivateNetworkAllowed);
+        findMatchedSuggestionNetworks(scanDetails, matchMetaInfo, untrustedNetworkAllowed,
+                oemPaidNetworkAllowed,
+                oemPrivateNetworkAllowed);
 
         if (matchMetaInfo.isEmpty()) {
             mLocalLog.log("did not see any matching auto-join enabled network suggestions.");
         } else {
             matchMetaInfo.findConnectableNetworksAndHighestPriority(onConnectableListener);
         }
-
-        addAutojoinDisabledSuggestionToWifiConfigManager(autoJoinDisabledSuggestions);
     }
 
-    private void findMatchedPasspointSuggestionNetworks(List<ScanDetail> scanDetails,
-            MatchMetaInfo matchMetaInfo, boolean untrustedNetworkAllowed) {
-        List<Pair<ScanDetail, WifiConfiguration>> candidates =
-                mPasspointNetworkNominateHelper.getPasspointNetworkCandidates(scanDetails, true);
-        for (Pair<ScanDetail, WifiConfiguration> candidate : candidates) {
-            WifiConfiguration config = candidate.second;
-            Set<ExtendedWifiNetworkSuggestion> matchingPasspointExtSuggestions =
-                    mWifiNetworkSuggestionsManager
-                            .getNetworkSuggestionsForFqdn(config.FQDN);
-            if (matchingPasspointExtSuggestions == null
-                    || matchingPasspointExtSuggestions.isEmpty()) {
-                mLocalLog.log("Suggestion is missing for passpoint: " + config.FQDN);
-                continue;
-            }
-
-            if (WifiConfiguration.isMetered(config, null)
-                    && mWifiCarrierInfoManager.isCarrierNetworkFromNonDefaultDataSim(config)) {
-                continue;
-            }
-            if (!isSimBasedNetworkAvailableToAutoConnect(config)) {
-                continue;
-            }
-            // If untrusted network is not allowed, ignore untrusted suggestion.
-            if (!untrustedNetworkAllowed && !config.trusted) {
-                continue;
-            }
-            Set<ExtendedWifiNetworkSuggestion> autoJoinEnabledExtSuggestions =
-                    matchingPasspointExtSuggestions.stream()
-                            .filter(ewns -> ewns.isAutojoinEnabled)
-                            .collect(Collectors.toSet());
-            if (autoJoinEnabledExtSuggestions.isEmpty()) {
-                continue;
-            }
-
-            matchMetaInfo.putAll(autoJoinEnabledExtSuggestions,
-                    config, candidate.first);
-        }
-    }
-
-    private void findMatchedSuggestionNetworks(List<ScanDetail> scanDetails,
-            MatchMetaInfo matchMetaInfo,
-            Set<ExtendedWifiNetworkSuggestion> autoJoinDisabledSuggestions,
-            boolean untrustedNetworkAllowed) {
+    private void addOrUpdateSuggestionsToWifiConfigManger(List<ScanDetail> scanDetails) {
         for (ScanDetail scanDetail : scanDetails) {
             Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestions =
                     mWifiNetworkSuggestionsManager.getNetworkSuggestionsForScanDetail(scanDetail);
             if (matchingExtNetworkSuggestions == null || matchingExtNetworkSuggestions.isEmpty()) {
                 continue;
             }
-            Set<ExtendedWifiNetworkSuggestion> autojoinEnableSuggestions = new HashSet<>();
             for (ExtendedWifiNetworkSuggestion ewns : matchingExtNetworkSuggestions) {
-                // Ignore insecure enterprise config.
-                if (ewns.wns.wifiConfiguration.isEnterprise()
-                        && ewns.wns.wifiConfiguration.enterpriseConfig.isInsecure()) {
-                    continue;
-                }
-                // If untrusted network is not allowed, ignore untrusted suggestion.
-                WifiConfiguration config = ewns.wns.wifiConfiguration;
-                if (!untrustedNetworkAllowed && !config.trusted) {
-                    continue;
-                }
-                if (WifiConfiguration.isMetered(config, null)
-                        && mWifiCarrierInfoManager.isCarrierNetworkFromNonDefaultDataSim(config)) {
-                    continue;
-                }
-                if (!ewns.isAutojoinEnabled
-                        || !isSimBasedNetworkAvailableToAutoConnect(config)) {
-                    autoJoinDisabledSuggestions.add(ewns);
-                    continue;
-                }
-                if (mWifiConfigManager.isNetworkTemporarilyDisabledByUser(config.SSID)) {
-                    mLocalLog.log("Ignoring user disabled SSID: "
-                            + config.SSID);
-                    autoJoinDisabledSuggestions.add(ewns);
-                    continue;
-                }
-                autojoinEnableSuggestions.add(ewns);
+                addOrUpdateSuggestionToWifiConfigManger(ewns);
             }
+        }
+    }
 
-            if (autojoinEnableSuggestions.isEmpty()) {
+    private void addOrUpdateSuggestionToWifiConfigManger(ExtendedWifiNetworkSuggestion ewns) {
+        WifiConfiguration config = ewns.createInternalWifiConfiguration(mWifiCarrierInfoManager);
+        WifiConfiguration wCmConfiguredNetwork =
+                mWifiConfigManager.getConfiguredNetwork(config.getProfileKey());
+        NetworkUpdateResult result = mWifiConfigManager.addOrUpdateNetwork(
+                config, ewns.perAppInfo.uid, ewns.perAppInfo.packageName);
+        if (!result.isSuccess()) {
+            mLocalLog.log("Failed to add network suggestion");
+            return;
+        }
+        mLocalLog.log(config.getProfileKey()
+                + " is added/updated in the WifiConfigManager");
+        mWifiConfigManager.allowAutojoin(result.getNetworkId(), config.allowAutojoin);
+        WifiConfiguration currentWCmConfiguredNetwork =
+                mWifiConfigManager.getConfiguredNetwork(result.getNetworkId());
+        // Try to enable network selection
+        if (wCmConfiguredNetwork == null) {
+            if (!mWifiConfigManager.updateNetworkSelectionStatus(result.getNetworkId(),
+                    WifiConfiguration.NetworkSelectionStatus.DISABLED_NONE)) {
+                mLocalLog.log("Failed to make network suggestion selectable");
+            }
+        } else {
+            if (!currentWCmConfiguredNetwork.getNetworkSelectionStatus().isNetworkEnabled()
+                    && !mWifiConfigManager.tryEnableNetwork(wCmConfiguredNetwork.networkId)) {
+                mLocalLog.log("Ignoring blocked network: "
+                        + toNetworkString(wCmConfiguredNetwork));
+            }
+        }
+    }
+
+    /** Helper method to avoid code duplication in regular & passpoint based suggestions filter. */
+    private boolean shouldIgnoreBasedOnChecksForTrustedOrOemPaidOrOemPrivate(
+            WifiConfiguration config, boolean untrustedNetworkAllowed,
+            boolean oemPaidNetworkAllowed, boolean oemPrivateNetworkAllowed) {
+        // If untrusted network is not allowed, ignore untrusted suggestion.
+        if (!untrustedNetworkAllowed && !config.trusted) {
+            return true;
+        }
+        // For suggestions with both oem paid & oem private set, ignore them If both oem paid
+        // & oem private network is not allowed. If oem paid network is allowed, then mark
+        // the suggestion oem paid for this connection attempt, else mark oem private for this
+        // connection attempt.
+        if (config.oemPaid && config.oemPrivate) {
+            if (!oemPaidNetworkAllowed && !oemPrivateNetworkAllowed) {
+                return true;
+            }
+            if (oemPaidNetworkAllowed) {
+                config.oemPrivate = false; // only oemPaid set.
+            } else if (oemPrivateNetworkAllowed) {
+                config.oemPaid = false; // only oemPrivate set.
+            }
+        } else {
+            // If oem paid network is not allowed, ignore oem paid suggestion.
+            if (!oemPaidNetworkAllowed && config.oemPaid) {
+                return true;
+            }
+            // If oem paid network is not allowed, ignore oem paid suggestion.
+            if (!oemPrivateNetworkAllowed && config.oemPrivate) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void findMatchedPasspointSuggestionNetworks(List<ScanDetail> scanDetails,
+            MatchMetaInfo matchMetaInfo, boolean untrustedNetworkAllowed,
+            boolean oemPaidNetworkAllowed, boolean oemPrivateNetworkAllowed) {
+        List<Pair<ScanDetail, WifiConfiguration>> candidates =
+                mPasspointNetworkNominateHelper.getPasspointNetworkCandidates(scanDetails, true);
+        for (Pair<ScanDetail, WifiConfiguration> candidate : candidates) {
+            WifiConfiguration config = candidate.second;
+            Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestions =
+                    mWifiNetworkSuggestionsManager.getNetworkSuggestionsForFqdn(config.FQDN);
+            if (matchingExtNetworkSuggestions.isEmpty()) {
+                mLocalLog.log("No user approved suggestion for FQDN:" + config.FQDN);
                 continue;
             }
-            // All matching suggestions have the same network credentials type. So, use any one of
-            // them to lookup/add the credentials to WifiConfigManager.
-            // Note: Apps could provide different credentials (password, ceritificate) for the same
-            // network, need to handle that in the future.
-            String configKey = autojoinEnableSuggestions.stream().findAny().get()
-                    .wns.wifiConfiguration.getKey();
-            // Check if we already have a network with the same credentials in WifiConfigManager
-            // database.
-            WifiConfiguration wCmConfiguredNetwork =
-                    mWifiConfigManager.getConfiguredNetwork(configKey);
-            if (wCmConfiguredNetwork != null) {
-                // If existing network is not from suggestion, ignore.
-                if (!(wCmConfiguredNetwork.fromWifiNetworkSuggestion
-                        && wCmConfiguredNetwork.allowAutojoin)) {
+            Optional<ExtendedWifiNetworkSuggestion> matchingPasspointExtSuggestion =
+                    matchingExtNetworkSuggestions.stream()
+                            .filter(ewns -> Objects.equals(
+                                    ewns.wns.wifiConfiguration.getPasspointUniqueId(),
+                                    config.getPasspointUniqueId()))
+                            .findFirst();
+            if (!matchingPasspointExtSuggestion.isPresent()) {
+                mLocalLog.log("Suggestion is missing for passpoint FQDN: " + config.FQDN
+                        + " profile key: " + config.getProfileKey());
+                continue;
+            }
+            if (!isNetworkAvailableToAutoConnect(config, untrustedNetworkAllowed,
+                    oemPaidNetworkAllowed, oemPrivateNetworkAllowed)) {
+                continue;
+            }
+
+            matchMetaInfo.put(matchingPasspointExtSuggestion.get(), config, candidate.first);
+        }
+    }
+
+    private void findMatchedSuggestionNetworks(List<ScanDetail> scanDetails,
+            MatchMetaInfo matchMetaInfo, boolean untrustedNetworkAllowed,
+            boolean oemPaidNetworkAllowed,
+            boolean oemPrivateNetworkAllowed) {
+        for (ScanDetail scanDetail : scanDetails) {
+            Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestions =
+                    mWifiNetworkSuggestionsManager.getNetworkSuggestionsForScanDetail(scanDetail);
+            if (matchingExtNetworkSuggestions.isEmpty()) {
+                continue;
+            }
+            if (matchingExtNetworkSuggestions.size() > 1) {
+                mWifiMetrics.incrementNetworkSuggestionMoreThanOneSuggestionForSingleScanResult();
+            }
+            for (ExtendedWifiNetworkSuggestion ewns : matchingExtNetworkSuggestions) {
+                WifiConfiguration config = ewns.createInternalWifiConfiguration(
+                        mWifiCarrierInfoManager);
+                WifiConfiguration wCmConfiguredNetwork =
+                        mWifiConfigManager.getConfiguredNetwork(config.getProfileKey());
+                if (wCmConfiguredNetwork == null) {
+                    mLocalLog.log(config.getProfileKey()
+                            + "hasn't add to WifiConfigManager?");
                     continue;
                 }
-                int creatorUid = wCmConfiguredNetwork.creatorUid;
-                Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestionsFromSamePackage =
-                        autojoinEnableSuggestions.stream()
-                                .filter(ewns -> ewns.perAppInfo.uid == creatorUid)
-                                .collect(Collectors.toSet());
-                if (matchingExtNetworkSuggestionsFromSamePackage.isEmpty()) {
-                    continue;
+                if (SdkLevel.isAtLeastS()
+                        && mWifiConfigManager.getConfiguredNetwork(config.getKey()) != null) {
+                    // If a saved profile is available for the same network
+                    mWifiMetrics.addSuggestionExistsForSavedNetwork(
+                            config.getKey());
                 }
-                // If the network is currently blacklisted, ignore.
                 if (!wCmConfiguredNetwork.getNetworkSelectionStatus().isNetworkEnabled()
                         && !mWifiConfigManager.tryEnableNetwork(wCmConfiguredNetwork.networkId)) {
-                    mLocalLog.log("Ignoring blacklisted network: "
-                            + WifiNetworkSelector.toNetworkString(wCmConfiguredNetwork));
+                    mLocalLog.log("Ignoring blocklisted network: "
+                            + toNetworkString(wCmConfiguredNetwork));
                     continue;
                 }
-                matchingExtNetworkSuggestions = matchingExtNetworkSuggestionsFromSamePackage;
+                if (!isNetworkAvailableToAutoConnect(wCmConfiguredNetwork, untrustedNetworkAllowed,
+                        oemPaidNetworkAllowed, oemPrivateNetworkAllowed)) {
+                    continue;
+                }
+                matchMetaInfo.put(ewns, wCmConfiguredNetwork, scanDetail);
             }
-            matchMetaInfo.putAll(matchingExtNetworkSuggestions, wCmConfiguredNetwork, scanDetail);
         }
     }
 
+    private boolean isNetworkAvailableToAutoConnect(WifiConfiguration config,
+            boolean untrustedNetworkAllowed, boolean oemPaidNetworkAllowed,
+            boolean oemPrivateNetworkAllowed) {
+        // Ignore insecure enterprise config.
+        if (config.isEnterprise() && config.enterpriseConfig.isEapMethodServerCertUsed()
+                && !config.enterpriseConfig
+                .isMandatoryParameterSetForServerCertValidation()) {
+            mLocalLog.log("Ignoring insecure enterprise network: " + config);
+            return false;
+        }
+        // If oem paid network is not allowed, ignore oem paid suggestion.
+        if (shouldIgnoreBasedOnChecksForTrustedOrOemPaidOrOemPrivate(config,
+                untrustedNetworkAllowed, oemPaidNetworkAllowed, oemPrivateNetworkAllowed)) {
+            mLocalLog.log("Ignoring network since it needs corresponding NetworkRequest: "
+                    + toNetworkString(config));
+            return false;
+        }
+        if (!isCarrierNetworkAvailableToAutoConnect(config)) {
+            return false;
+        }
+        String network = config.isPasspoint() ? config.FQDN : config.SSID;
+        if (mWifiConfigManager.isNonCarrierMergedNetworkTemporarilyDisabled(config)) {
+            mLocalLog.log("Ignoring non-carrier-merged network: " + network);
+            return false;
+        }
+        if (mWifiConfigManager.isNetworkTemporarilyDisabledByUser(network)) {
+            mLocalLog.log("Ignoring user disabled network: " + network);
+            return false;
+        }
+        return config.allowAutojoin;
+    }
+
+    private boolean isCarrierNetworkAvailableToAutoConnect(WifiConfiguration config) {
+        if (config.carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) {
+            return true;
+        }
+
+        if (!mWifiCarrierInfoManager.isSimReady(config.subscriptionId)) {
+            mLocalLog.log("SIM is not present for subId: " + config.subscriptionId);
+            return false;
+        }
+        if (WifiConfiguration.isMetered(config, null)
+                && mWifiCarrierInfoManager.isCarrierNetworkFromNonDefaultDataSim(config)) {
+            mLocalLog.log("Ignoring carrier network from non default data SIM, network: "
+                    + toNetworkString(config));
+            return false;
+        }
+        if (!mWifiCarrierInfoManager
+                .isCarrierNetworkOffloadEnabled(config.subscriptionId, config.carrierMerged)) {
+            mLocalLog.log("Carrier offload is disabled for "
+                    + (config.carrierMerged ? "merged" : "unmerged")
+                    + " network from subId: " + config.subscriptionId);
+            return false;
+        }
+        return isSimBasedNetworkAvailableToAutoConnect(config);
+    }
+
     private boolean isSimBasedNetworkAvailableToAutoConnect(WifiConfiguration config) {
         if (config.enterpriseConfig == null
                 || !config.enterpriseConfig.isAuthenticationSimBased()) {
             return true;
         }
-        int subId = mWifiCarrierInfoManager.getBestMatchSubscriptionId(config);
-        if (!mWifiCarrierInfoManager.isSimPresent(subId)) {
-            mLocalLog.log("SIM is not present for subId: " + subId);
+        int subId = config.subscriptionId;
+        if (mWifiCarrierInfoManager.requiresImsiEncryption(subId)
+                && !mWifiCarrierInfoManager.isImsiEncryptionInfoAvailable(subId)) {
+            mLocalLog.log("Ignoring SIM based network IMSI encryption info not Available, subId: "
+                    + subId);
             return false;
         }
-        if (mWifiCarrierInfoManager.requiresImsiEncryption(subId)) {
-            return mWifiCarrierInfoManager.isImsiEncryptionInfoAvailable(subId);
-        }
         return true;
     }
 
-    // Add auto-join disabled suggestions also to WifiConfigManager if the app allows credential
-    // sharing.This will surface these networks on the UI, to allow the user manually connect to it.
-    private void addAutojoinDisabledSuggestionToWifiConfigManager(
-            Set<ExtendedWifiNetworkSuggestion> autoJoinDisabledSuggestions) {
-        for (ExtendedWifiNetworkSuggestion ewns : autoJoinDisabledSuggestions) {
-            if (!ewns.wns.isUserAllowedToManuallyConnect) {
-                continue;
-            }
-            WifiConfiguration config = ewns.createInternalWifiConfiguration();
-            WifiConfiguration wCmConfiguredNetwork =
-                    mWifiConfigManager.getConfiguredNetwork(config.getKey());
-            NetworkUpdateResult result = mWifiConfigManager.addOrUpdateNetwork(
-                    config, ewns.perAppInfo.uid, ewns.perAppInfo.packageName);
-            if (!result.isSuccess()) {
-                mLocalLog.log("Failed to add network suggestion");
-                continue;
-            }
-            mWifiConfigManager.allowAutojoin(result.getNetworkId(), config.allowAutojoin);
-            WifiConfiguration currentWCmConfiguredNetwork =
-                    mWifiConfigManager.getConfiguredNetwork(result.netId);
-            // Try to enable network selection
-            if (wCmConfiguredNetwork == null) {
-                if (!mWifiConfigManager.updateNetworkSelectionStatus(result.getNetworkId(),
-                        WifiConfiguration.NetworkSelectionStatus.DISABLED_NONE)) {
-                    mLocalLog.log("Failed to make network suggestion selectable");
-                }
-            } else {
-                if (!currentWCmConfiguredNetwork.getNetworkSelectionStatus().isNetworkEnabled()
-                        && !mWifiConfigManager.tryEnableNetwork(wCmConfiguredNetwork.networkId)) {
-                    mLocalLog.log("Ignoring blacklisted network: "
-                            + WifiNetworkSelector.toNetworkString(wCmConfiguredNetwork));
-                }
-            }
-        }
-    }
-
-    // Add and enable this network to the central database (i.e WifiConfigManager).
-    // Returns the copy of WifiConfiguration with the allocated network ID filled in.
-    private WifiConfiguration addCandidateToWifiConfigManager(
-            @NonNull ExtendedWifiNetworkSuggestion ewns) {
-        WifiConfiguration wifiConfiguration = ewns.createInternalWifiConfiguration();
-        NetworkUpdateResult result =
-                mWifiConfigManager.addOrUpdateNetwork(wifiConfiguration, ewns.perAppInfo.uid,
-                        ewns.perAppInfo.packageName);
-        if (!result.isSuccess()) {
-            mLocalLog.log("Failed to add network suggestion");
-            return null;
-        }
-        mWifiConfigManager.allowAutojoin(result.getNetworkId(), wifiConfiguration.allowAutojoin);
-        if (!mWifiConfigManager.updateNetworkSelectionStatus(result.getNetworkId(),
-                WifiConfiguration.NetworkSelectionStatus.DISABLED_NONE)) {
-            mLocalLog.log("Failed to make network suggestion selectable");
-            return null;
-        }
-        int candidateNetworkId = result.getNetworkId();
-        return mWifiConfigManager.getConfiguredNetwork(candidateNetworkId);
-    }
-
     @Override
     public @NominatorId int getId() {
         return NOMINATOR_ID_SUGGESTION;
@@ -346,7 +390,7 @@
                                     }));
             if (matchedNetworkInfosPerPriority.isEmpty()) { // should never happen.
                 Log.wtf(TAG, "Unexepectedly got empty");
-                return Collections.EMPTY_LIST;
+                return List.of();
             }
             // Return the list associated with the highest priority value.
             return matchedNetworkInfosPerPriority.get(Collections.max(
@@ -355,29 +399,28 @@
     }
 
     private class MatchMetaInfo {
-        private Map<String, PerAppMatchMetaInfo> mAppInfos = new HashMap<>();
+        private SparseArray<PerAppMatchMetaInfo> mAppInfos = new SparseArray<>();
 
         /**
          * Add all the network suggestion & associated info.
          */
-        public void putAll(Set<ExtendedWifiNetworkSuggestion> wifiNetworkSuggestions,
+        public void put(ExtendedWifiNetworkSuggestion wifiNetworkSuggestion,
                            WifiConfiguration wCmConfiguredNetwork,
                            ScanDetail matchingScanDetail) {
-            // Separate the suggestions into buckets for each app to allow sorting based on
+            // Put the suggestion into buckets for each app to allow sorting based on
             // priorities set by app.
-            for (ExtendedWifiNetworkSuggestion wifiNetworkSuggestion : wifiNetworkSuggestions) {
-                PerAppMatchMetaInfo appInfo = mAppInfos.computeIfAbsent(
-                        wifiNetworkSuggestion.perAppInfo.packageName,
-                        k -> new PerAppMatchMetaInfo());
-                appInfo.put(wifiNetworkSuggestion, wCmConfiguredNetwork, matchingScanDetail);
-            }
+            int key = Objects.hash(wifiNetworkSuggestion.perAppInfo.packageName,
+                    wifiNetworkSuggestion.wns.priorityGroup);
+            PerAppMatchMetaInfo appInfo = mAppInfos.get(key, new PerAppMatchMetaInfo());
+            appInfo.put(wifiNetworkSuggestion, wCmConfiguredNetwork, matchingScanDetail);
+            mAppInfos.put(key, appInfo);
         }
 
         /**
          * Are there any matched candidates?
          */
         public boolean isEmpty() {
-            return mAppInfos.isEmpty();
+            return mAppInfos.size() == 0;
         }
 
         /**
@@ -386,23 +429,13 @@
          */
         public void findConnectableNetworksAndHighestPriority(
                 @NonNull OnConnectableListener onConnectableListener) {
-            for (PerAppMatchMetaInfo appInfo : mAppInfos.values()) {
+            for (int i = 0; i < mAppInfos.size(); i++) {
                 List<PerNetworkSuggestionMatchMetaInfo> matchedNetworkInfos =
-                        appInfo.getHighestPriorityNetworks();
+                        mAppInfos.valueAt(i).getHighestPriorityNetworks();
                 for (PerNetworkSuggestionMatchMetaInfo matchedNetworkInfo : matchedNetworkInfos) {
-                    // if the network does not already exist in WifiConfigManager, add now.
-                    if (matchedNetworkInfo.wCmConfiguredNetwork == null) {
-                        matchedNetworkInfo.wCmConfiguredNetwork = addCandidateToWifiConfigManager(
-                                matchedNetworkInfo.extWifiNetworkSuggestion);
-                        if (matchedNetworkInfo.wCmConfiguredNetwork == null) continue;
-                        mLocalLog.log(String.format("network suggestion candidate %s (new)",
-                                WifiNetworkSelector.toNetworkString(
-                                        matchedNetworkInfo.wCmConfiguredNetwork)));
-                    } else {
-                        mLocalLog.log(String.format("network suggestion candidate %s (existing)",
-                                WifiNetworkSelector.toNetworkString(
-                                        matchedNetworkInfo.wCmConfiguredNetwork)));
-                    }
+                    mLocalLog.log(String.format("network suggestion candidate %s nominated",
+                                toNetworkString(matchedNetworkInfo.wCmConfiguredNetwork)));
+
                     onConnectableListener.onConnectable(
                             matchedNetworkInfo.matchingScanDetail,
                             matchedNetworkInfo.wCmConfiguredNetwork);
diff --git a/service/java/com/android/server/wifi/NetworkSuggestionStoreData.java b/service/java/com/android/server/wifi/NetworkSuggestionStoreData.java
index 11067f6..ad773e4 100644
--- a/service/java/com/android/server/wifi/NetworkSuggestionStoreData.java
+++ b/service/java/com/android/server/wifi/NetworkSuggestionStoreData.java
@@ -40,11 +40,11 @@
 import org.xmlpull.v1.XmlSerializer;
 
 import java.io.IOException;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Map.Entry;
-import java.util.Set;
 
 /**
  * This class performs serialization and parsing of XML data block that contain the list of WiFi
@@ -75,6 +75,9 @@
     private static final String XML_TAG_SUGGESTOR_MAX_SIZE = "SuggestorMaxSize";
     private static final String XML_TAG_SECTION_HEADER_PASSPOINT_CONFIGURATION =
             "PasspointConfiguration";
+    private static final String XML_TAG_PRIORITY_GROUP = "PriorityGroup";
+    private static final String XML_TAG_CONNECT_CHOICE = "ConnectChoice";
+    private static final String XML_TAG_CONNECT_CHOICE_RSSI = "ConnectChoiceRssi";
 
     /**
      * Interface define the data source for the network suggestions store data.
@@ -171,8 +174,8 @@
             int maxSize = entry.getValue().maxSize;
             int uid = entry.getValue().uid;
             int carrierId = entry.getValue().carrierId;
-            Set<ExtendedWifiNetworkSuggestion> networkSuggestions =
-                    entry.getValue().extNetworkSuggestions;
+            Collection<ExtendedWifiNetworkSuggestion> networkSuggestions =
+                    entry.getValue().extNetworkSuggestions.values();
             XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_NETWORK_SUGGESTION_PER_APP);
             XmlUtil.writeNextValue(out, XML_TAG_SUGGESTOR_PACKAGE_NAME, packageName);
             XmlUtil.writeNextValue(out, XML_TAG_SUGGESTOR_FEATURE_ID, featureId);
@@ -191,8 +194,8 @@
      * @throws XmlPullParserException
      * @throws IOException
      */
-    private void serializeExtNetworkSuggestions(
-            XmlSerializer out, final Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions,
+    private void serializeExtNetworkSuggestions(XmlSerializer out,
+            final Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestions,
             @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
             throws XmlPullParserException, IOException {
         for (ExtendedWifiNetworkSuggestion extNetworkSuggestion : extNetworkSuggestions) {
@@ -247,6 +250,9 @@
                 suggestion.isInitialAutoJoinEnabled);
         XmlUtil.writeNextValue(out, XML_TAG_IS_AUTO_JOIN,
                 extSuggestion.isAutojoinEnabled);
+        XmlUtil.writeNextValue(out, XML_TAG_PRIORITY_GROUP, suggestion.priorityGroup);
+        XmlUtil.writeNextValue(out, XML_TAG_CONNECT_CHOICE, extSuggestion.connectChoice);
+        XmlUtil.writeNextValue(out, XML_TAG_CONNECT_CHOICE_RSSI, extSuggestion.connectChoiceRssi);
         XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_NETWORK_SUGGESTION);
     }
 
@@ -322,14 +328,9 @@
                         }
                         switch (tagName) {
                             case XML_TAG_SECTION_HEADER_NETWORK_SUGGESTION:
-                                Pair<WifiNetworkSuggestion, Boolean> networkSuggestionData =
-                                        parseNetworkSuggestion(
-                                                in, outerTagDepth + 2, version, encryptionUtil,
-                                                perAppInfo);
-                                perAppInfo.extNetworkSuggestions.add(
-                                        ExtendedWifiNetworkSuggestion.fromWns(
-                                                networkSuggestionData.first, perAppInfo,
-                                                networkSuggestionData.second));
+                                ExtendedWifiNetworkSuggestion ewns = parseNetworkSuggestion(in,
+                                        outerTagDepth + 2, version, encryptionUtil, perAppInfo);
+                                perAppInfo.extNetworkSuggestions.put(ewns.hashCode(), ewns);
                                 break;
                             default:
                                 Log.w(TAG, "Ignoring unknown tag under "
@@ -365,7 +366,7 @@
      * @throws XmlPullParserException
      * @throws IOException
      */
-    private Pair<WifiNetworkSuggestion, Boolean> parseNetworkSuggestion(XmlPullParser in,
+    private ExtendedWifiNetworkSuggestion parseNetworkSuggestion(XmlPullParser in,
             int outerTagDepth, @WifiConfigStore.Version int version,
             @Nullable WifiConfigStoreEncryptionUtil encryptionUtil, PerAppInfo perAppInfo)
             throws XmlPullParserException, IOException {
@@ -378,7 +379,10 @@
         boolean isInitializedAutoJoinEnabled = true; // backward compat
         boolean isAutoJoinEnabled = true; // backward compat
         boolean isNetworkUntrusted = false;
+        int priorityGroup = WifiNetworkSuggestionsManager.DEFAULT_PRIORITY_GROUP;
         int suggestorUid = Process.INVALID_UID;
+        String connectChoice = null;
+        int connectChoiceRssi = 0;
 
         // Loop through and parse out all the elements from the stream within this section.
         while (XmlUtil.nextElementWithin(in, outerTagDepth)) {
@@ -406,6 +410,15 @@
                         // Only needed for migration of data from Q to R.
                         suggestorUid = (int) value;
                         break;
+                    case XML_TAG_PRIORITY_GROUP:
+                        priorityGroup = (int) value;
+                        break;
+                    case XML_TAG_CONNECT_CHOICE:
+                        connectChoice = (String) value;
+                        break;
+                    case XML_TAG_CONNECT_CHOICE_RSSI:
+                        connectChoiceRssi = (int) value;
+                        break;
                     default:
                         Log.w(TAG, "Ignoring unknown value name found: " + valueName[0]);
                         break;
@@ -424,8 +437,8 @@
                         }
                         parsedConfig = WifiConfigurationXmlUtil.parseFromXml(
                                 in, outerTagDepth + 1,
-                            version >= ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION,
-                            encryptionUtil);
+                                version >= ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION,
+                                encryptionUtil, true);
                         break;
                     case XML_TAG_SECTION_HEADER_WIFI_ENTERPRISE_CONFIGURATION:
                         if (enterpriseConfig != null) {
@@ -466,9 +479,14 @@
         if (enterpriseConfig != null) {
             wifiConfiguration.enterpriseConfig = enterpriseConfig;
         }
-        return Pair.create(new WifiNetworkSuggestion(wifiConfiguration, passpointConfiguration,
-                isAppInteractionRequired, isUserInteractionRequired, isUserAllowedToManuallyConnect,
-                isInitializedAutoJoinEnabled), isAutoJoinEnabled);
+        ExtendedWifiNetworkSuggestion ewns = ExtendedWifiNetworkSuggestion
+                .fromWns(new WifiNetworkSuggestion(wifiConfiguration, passpointConfiguration,
+                        isAppInteractionRequired, isUserInteractionRequired,
+                        isUserAllowedToManuallyConnect, isInitializedAutoJoinEnabled,
+                        priorityGroup), perAppInfo, isAutoJoinEnabled);
+        ewns.connectChoice = connectChoice;
+        ewns.connectChoiceRssi = connectChoiceRssi;
+        return ewns;
     }
 }
 
diff --git a/service/java/com/android/server/wifi/NetworkUpdateResult.java b/service/java/com/android/server/wifi/NetworkUpdateResult.java
index 851b13b..9090ab8 100644
--- a/service/java/com/android/server/wifi/NetworkUpdateResult.java
+++ b/service/java/com/android/server/wifi/NetworkUpdateResult.java
@@ -18,57 +18,86 @@
 
 import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
 
+import java.util.Objects;
+
 public class NetworkUpdateResult {
-    int netId;
-    boolean ipChanged;
-    boolean proxyChanged;
-    boolean credentialChanged;
-    boolean isNewNetwork = false;
+    private final int mNetId;
+    private final boolean mIpChanged;
+    private final boolean mProxyChanged;
+    private final boolean mCredentialChanged;
+    private final boolean mIsNewNetwork;
 
-    public NetworkUpdateResult(int id) {
-        netId = id;
-        ipChanged = false;
-        proxyChanged = false;
-        credentialChanged = false;
+    public NetworkUpdateResult(int netId) {
+        this(netId, false, false, false, false);
     }
 
-    public NetworkUpdateResult(boolean ip, boolean proxy, boolean credential) {
-        netId = INVALID_NETWORK_ID;
-        ipChanged = ip;
-        proxyChanged = proxy;
-        credentialChanged = credential;
+    public NetworkUpdateResult(
+            int netId,
+            boolean ip,
+            boolean proxy,
+            boolean credential,
+            boolean isNewNetwork) {
+        mNetId = netId;
+        mIpChanged = ip;
+        mProxyChanged = proxy;
+        mCredentialChanged = credential;
+        mIsNewNetwork = isNewNetwork;
     }
 
-    public void setNetworkId(int id) {
-        netId = id;
+    /** Make an instance of NetworkUpdateResult whose {@link #isSuccess()} method returns false. */
+    public static NetworkUpdateResult makeFailed() {
+        return new NetworkUpdateResult(INVALID_NETWORK_ID);
     }
 
     public int getNetworkId() {
-        return netId;
+        return mNetId;
     }
 
     public boolean hasIpChanged() {
-        return ipChanged;
+        return mIpChanged;
     }
 
     public boolean hasProxyChanged() {
-        return proxyChanged;
+        return mProxyChanged;
     }
 
     public boolean hasCredentialChanged() {
-        return credentialChanged;
+        return mCredentialChanged;
     }
 
     public boolean isNewNetwork() {
-        return isNewNetwork;
-    }
-
-    public void setIsNewNetwork(boolean isNew) {
-        isNewNetwork = isNew;
+        return mIsNewNetwork;
     }
 
     public boolean isSuccess() {
-        return netId != INVALID_NETWORK_ID;
+        return mNetId != INVALID_NETWORK_ID;
     }
 
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        NetworkUpdateResult that = (NetworkUpdateResult) o;
+        return mNetId == that.mNetId
+                && mIpChanged == that.mIpChanged
+                && mProxyChanged == that.mProxyChanged
+                && mCredentialChanged == that.mCredentialChanged
+                && mIsNewNetwork == that.mIsNewNetwork;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mNetId, mIpChanged, mProxyChanged, mCredentialChanged, mIsNewNetwork);
+    }
+
+    @Override
+    public String toString() {
+        return "NetworkUpdateResult{"
+                + "mNetId=" + mNetId
+                + ", mIpChanged=" + mIpChanged
+                + ", mProxyChanged=" + mProxyChanged
+                + ", mCredentialChanged=" + mCredentialChanged
+                + ", mIsNewNetwork=" + mIsNewNetwork
+                + '}';
+    }
 }
diff --git a/service/java/com/android/server/wifi/NonCarrierMergedNetworksStatusTracker.java b/service/java/com/android/server/wifi/NonCarrierMergedNetworksStatusTracker.java
new file mode 100644
index 0000000..e3d64a4
--- /dev/null
+++ b/service/java/com/android/server/wifi/NonCarrierMergedNetworksStatusTracker.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.net.wifi.WifiConfiguration;
+import android.telephony.SubscriptionManager;
+import android.util.ArraySet;
+
+import com.android.server.wifi.util.MissingCounterTimerLockList;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Set;
+
+/**
+ * Keep track of the disabled duration for all non-carrier-merged networks.
+ */
+public class NonCarrierMergedNetworksStatusTracker {
+    private final Clock mClock;
+    private int mSubscriptionId;
+    private long mDisableStartTimeMs;
+    private long mMinDisableDurationMs;
+    private long mMaxDisableDurationMs;
+    private final MissingCounterTimerLockList<String> mTemporarilyDisabledNonCarrierMergedList;
+    private final Set<String> mTemporarilyDisabledNonCarrierMergedListAtStart;
+
+    public NonCarrierMergedNetworksStatusTracker(Clock clock) {
+        mClock = clock;
+        mTemporarilyDisabledNonCarrierMergedList =
+                new MissingCounterTimerLockList<>(
+                        WifiConfigManager.SCAN_RESULT_MISSING_COUNT_THRESHOLD, mClock);
+        mTemporarilyDisabledNonCarrierMergedListAtStart = new ArraySet<>();
+        mSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    }
+
+    /**
+     * Disable autojoin for all non-carrier-merged networks for the specified duration.
+     * @param subscriptionId the subscription ID of the carrier network.
+     * @param minDisableDurationMs the minimum duration in milliseconds to stay on the carrier
+     *                             network even if wifi is available.
+     * @param maxDisableDurationMs the maximum duration to disable all non-carrier merged wifi
+     *                             networks. After this duration passes, all non-carrier merged
+     *                             networks will get re-enabled for auto connections.
+     */
+    public void disableAllNonCarrierMergedNetworks(int subscriptionId, long minDisableDurationMs,
+            long maxDisableDurationMs) {
+        mSubscriptionId = subscriptionId;
+        mDisableStartTimeMs = mClock.getElapsedSinceBootMillis();
+        mMinDisableDurationMs = minDisableDurationMs;
+        mMaxDisableDurationMs = maxDisableDurationMs;
+    }
+
+    /**
+     * Add a SSID or FQDN to the temporary disabled list for the given timer duration. The SSID
+     * or FQDN will be re-enabled when after it is out of range for the specified duration.
+     */
+    public void temporarilyDisableNetwork(WifiConfiguration config, long timerDurationMs,
+            long maxDisableDurationMs) {
+        String key = getKeyFromConfig(config);
+        mTemporarilyDisabledNonCarrierMergedList.add(key, timerDurationMs, maxDisableDurationMs);
+        mTemporarilyDisabledNonCarrierMergedListAtStart.add(key);
+    }
+
+    /**
+     * Used to detect whether a disabled network is still in range.
+     * A disabled network that does not show up in the list passed in here for |timerDurationMs|
+     * will be re-enabled.
+     */
+    public void update(Set<String> networks) {
+        mTemporarilyDisabledNonCarrierMergedList.update(networks);
+    }
+
+    /**
+     * Resets this class and re-enables auto-join for all non-carrier-merged networks.
+     */
+    public void clear() {
+        mSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+        mDisableStartTimeMs = 0;
+        mMinDisableDurationMs = 0;
+        mMaxDisableDurationMs = 0;
+        mTemporarilyDisabledNonCarrierMergedList.clear();
+        mTemporarilyDisabledNonCarrierMergedListAtStart.clear();
+    }
+
+    /**
+     * Returns whether the given network should be not allowed for auto-connect.
+     * A network could be disable either because all non-carrier-merged networks are not allowed,
+     * or this specific network is still in the temporarily disabled list.
+     * @param config the network to check whether auto-connect should be disabled on
+     */
+    public boolean isNetworkDisabled(WifiConfiguration config) {
+        // All wifi networks are enabled after the max effective duration of the API call passes.
+        if (mClock.getElapsedSinceBootMillis() - mDisableStartTimeMs >= mMaxDisableDurationMs) {
+            clear();
+            return false;
+        }
+        // always allow a carrier-merged network with matching subscription ID through.
+        if (config.carrierMerged && config.subscriptionId == mSubscriptionId) {
+            return false;
+        }
+        if (shouldDisableAllNonCarrierMergedNetworks()) {
+            return true;
+        }
+        String key = getKeyFromConfig(config);
+        if (mTemporarilyDisabledNonCarrierMergedList.isLocked(key)) {
+            return true;
+        }
+        mTemporarilyDisabledNonCarrierMergedList.remove(key);
+        return false;
+    }
+
+    private String getKeyFromConfig(WifiConfiguration config) {
+        return config.isPasspoint() ? config.FQDN : config.SSID;
+    }
+
+    private boolean shouldDisableAllNonCarrierMergedNetworks() {
+        return mClock.getElapsedSinceBootMillis() - mDisableStartTimeMs < mMinDisableDurationMs;
+    }
+
+    /**
+     * Dump information for debugging.
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("NonCarrierMergedNetworksStatusTracker - Log Begin ----");
+        pw.println("mSubscriptionId=" + mSubscriptionId);
+        pw.println("dumpTimeMs=" + mClock.getElapsedSinceBootMillis());
+        pw.println("mDisableStartTimeMs=" + mDisableStartTimeMs);
+        pw.println("mMinDisableDurationMs=" + mMinDisableDurationMs);
+        pw.println("mMaxDisableDurationMs=" + mMaxDisableDurationMs);
+        pw.println("mTemporarilyDisabledNonCarrierMergedListAtStart=");
+        for (String s : mTemporarilyDisabledNonCarrierMergedListAtStart) {
+            pw.println(s);
+        }
+        pw.println("NonCarrierMergedNetworksStatusTracker - Log End ----");
+    }
+}
diff --git a/service/java/com/android/server/wifi/OemWifiNetworkFactory.java b/service/java/com/android/server/wifi/OemWifiNetworkFactory.java
new file mode 100644
index 0000000..e897bd3
--- /dev/null
+++ b/service/java/com/android/server/wifi/OemWifiNetworkFactory.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.content.Context;
+import android.net.NetworkCapabilities;
+import android.net.NetworkFactory;
+import android.net.NetworkRequest;
+import android.os.Looper;
+import android.os.WorkSource;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Network factory to handle oem paid/private wifi network requests.
+ */
+public class OemWifiNetworkFactory extends NetworkFactory {
+    private static final String TAG = "OemPaidWifiNetworkFactory";
+    private static final int SCORE_FILTER = Integer.MAX_VALUE;
+
+    private final WifiConnectivityManager mWifiConnectivityManager;
+    private int mOemPaidConnectionReqCount = 0;
+    private int mOemPrivateConnectionReqCount = 0;
+
+    public OemWifiNetworkFactory(Looper l, Context c, NetworkCapabilities f,
+            WifiConnectivityManager connectivityManager) {
+        super(l, c, TAG, f);
+        mWifiConnectivityManager = connectivityManager;
+
+        setScoreFilter(SCORE_FILTER);
+    }
+
+    @Override
+    protected void needNetworkFor(NetworkRequest networkRequest) {
+        if (networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID)) {
+            mOemPaidConnectionReqCount++;
+            if (mOemPaidConnectionReqCount == 1) {
+                mWifiConnectivityManager.setOemPaidConnectionAllowed(
+                        true, new WorkSource(networkRequest.getRequestorUid(),
+                                networkRequest.getRequestorPackageName()));
+            }
+        }
+        if (networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE)) {
+            mOemPrivateConnectionReqCount++;
+            if (mOemPrivateConnectionReqCount == 1) {
+                mWifiConnectivityManager.setOemPrivateConnectionAllowed(
+                        true, new WorkSource(networkRequest.getRequestorUid(),
+                                networkRequest.getRequestorPackageName()));
+            }
+        }
+    }
+
+    @Override
+    protected void releaseNetworkFor(NetworkRequest networkRequest) {
+        if (networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID)) {
+            if (mOemPaidConnectionReqCount == 0) {
+                Log.e(TAG, "No valid network request to release");
+                return;
+            }
+            mOemPaidConnectionReqCount--;
+            if (mOemPaidConnectionReqCount == 0) {
+                mWifiConnectivityManager.setOemPaidConnectionAllowed(false, null);
+            }
+        }
+        if (networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE)) {
+            if (mOemPrivateConnectionReqCount == 0) {
+                Log.e(TAG, "No valid network request to release");
+                return;
+            }
+            mOemPrivateConnectionReqCount--;
+            if (mOemPrivateConnectionReqCount == 0) {
+                mWifiConnectivityManager.setOemPrivateConnectionAllowed(false, null);
+            }
+        }
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        super.dump(fd, pw, args);
+        pw.println(TAG + ": mOemPaidConnectionReqCount " + mOemPaidConnectionReqCount);
+        pw.println(TAG + ": mOemPrivateConnectionReqCount " + mOemPrivateConnectionReqCount);
+    }
+
+    /**
+     * Check if there is at-least one connection request.
+     */
+    public boolean hasConnectionRequests() {
+        return mOemPaidConnectionReqCount > 0 || mOemPrivateConnectionReqCount > 0;
+    }
+}
+
diff --git a/service/java/com/android/server/wifi/OpenNetworkNotifier.java b/service/java/com/android/server/wifi/OpenNetworkNotifier.java
index 5e36f13..25fd972 100644
--- a/service/java/com/android/server/wifi/OpenNetworkNotifier.java
+++ b/service/java/com/android/server/wifi/OpenNetworkNotifier.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wifi;
 
-import android.content.Context;
 import android.os.Looper;
 import android.provider.Settings;
 
@@ -35,20 +34,23 @@
             Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON;
 
     public OpenNetworkNotifier(
-            Context context,
+            WifiContext context,
             Looper looper,
             FrameworkFacade framework,
             Clock clock,
             WifiMetrics wifiMetrics,
             WifiConfigManager wifiConfigManager,
             WifiConfigStore wifiConfigStore,
-            ClientModeImpl clientModeImpl,
-            ConnectToNetworkNotificationBuilder connectToNetworkNotificationBuilder) {
+            ConnectHelper connectHelper,
+            ConnectToNetworkNotificationBuilder connectToNetworkNotificationBuilder,
+            MakeBeforeBreakManager makeBeforeBreakManager,
+            WifiNotificationManager wifiNotificationManager) {
         super(TAG, STORE_DATA_IDENTIFIER, TOGGLE_SETTINGS_NAME,
                 SystemMessage.NOTE_NETWORK_AVAILABLE,
                 WifiMetricsProto.ConnectionEvent.NOMINATOR_OPEN_NETWORK_AVAILABLE,
                 context, looper, framework, clock,
-                wifiMetrics, wifiConfigManager, wifiConfigStore, clientModeImpl,
-                connectToNetworkNotificationBuilder);
+                wifiMetrics, wifiConfigManager, wifiConfigStore, connectHelper,
+                connectToNetworkNotificationBuilder, makeBeforeBreakManager,
+                wifiNotificationManager);
     }
 }
diff --git a/service/java/com/android/server/wifi/SarManager.java b/service/java/com/android/server/wifi/SarManager.java
index 3bd9871..df73179 100644
--- a/service/java/com/android/server/wifi/SarManager.java
+++ b/service/java/com/android/server/wifi/SarManager.java
@@ -31,6 +31,7 @@
 import android.net.wifi.WifiManager;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.PowerManager;
 import android.telephony.PhoneStateListener;
 import android.telephony.TelephonyManager;
 import android.util.Log;
@@ -93,7 +94,6 @@
     private final WifiPhoneStateListener mPhoneStateListener;
     private final WifiNative mWifiNative;
     private final Handler mHandler;
-    private final Looper mLooper;
 
     /**
      * Create new instance of SarManager.
@@ -105,7 +105,6 @@
         mContext = context;
         mTelephonyManager = telephonyManager;
         mWifiNative = wifiNative;
-        mLooper = looper;
         mAudioManager = mContext.getSystemService(AudioManager.class);
         mHandler = new WifiHandler(TAG, looper);
         mPhoneStateListener = new WifiPhoneStateListener(looper);
@@ -126,7 +125,7 @@
     /**
      * Notify SarManager of screen status change
      */
-    public void handleScreenStateChanged(boolean screenOn) {
+    private void handleScreenStateChanged(boolean screenOn) {
         if (!mSupportSarVoiceCall) {
             return;
         }
@@ -233,9 +232,32 @@
             /* Listen for Phone State changes */
             registerPhoneStateListener();
             registerVoiceStreamListener();
+            registerScreenListener();
         }
     }
 
+    private void registerScreenListener() {
+        // Listen to screen changes
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_SCREEN_ON);
+        filter.addAction(Intent.ACTION_SCREEN_OFF);
+
+        mContext.registerReceiver(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        String action = intent.getAction();
+                        if (action.equals(Intent.ACTION_SCREEN_ON)) {
+                            handleScreenStateChanged(true);
+                        } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
+                            handleScreenStateChanged(false);
+                        }
+                    }
+                }, filter, null, mHandler);
+        PowerManager powerManager = mContext.getSystemService(PowerManager.class);
+        handleScreenStateChanged(powerManager.isInteractive());
+    }
+
     private void registerVoiceStreamListener() {
         Log.i(TAG, "Registering for voice stream status");
 
diff --git a/service/java/com/android/server/wifi/SavedNetworkNominator.java b/service/java/com/android/server/wifi/SavedNetworkNominator.java
index 3c94bed..9af1212 100644
--- a/service/java/com/android/server/wifi/SavedNetworkNominator.java
+++ b/service/java/com/android/server/wifi/SavedNetworkNominator.java
@@ -78,7 +78,11 @@
      * Update the Nominator.
      */
     @Override
-    public void update(List<ScanDetail> scanDetails) { }
+    public void update(List<ScanDetail> scanDetails) {
+        // Update the matching profiles into WifiConfigManager, help displaying Passpoint networks
+        // in Wifi Picker
+        mPasspointNetworkNominateHelper.getPasspointNetworkCandidates(scanDetails, false);
+    }
 
     /**
      * Run through all scanDetails and nominate all connectable network as candidates.
@@ -86,9 +90,10 @@
      */
     @Override
     public void nominateNetworks(List<ScanDetail> scanDetails,
-                    WifiConfiguration currentNetwork, String currentBssid, boolean connected,
-                    boolean untrustedNetworkAllowed,
-                    @NonNull OnConnectableListener onConnectableListener) {
+                    boolean untrustedNetworkAllowed /* unused */,
+                    boolean oemPaidNetworkAllowed /* unused */,
+                    boolean oemPrivateNetworkAllowed /* unused */,
+            @NonNull OnConnectableListener onConnectableListener) {
         findMatchedSavedNetworks(scanDetails, onConnectableListener);
         findMatchedPasspointNetworks(scanDetails, onConnectableListener);
     }
@@ -101,7 +106,7 @@
             // One ScanResult can be associated with more than one network, hence we calculate all
             // the scores and use the highest one as the ScanResult's score.
             WifiConfiguration network =
-                    mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(scanDetail);
+                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(scanDetail);
 
             if (network == null) {
                 continue;
@@ -127,6 +132,10 @@
                     network.getNetworkSelectionStatus();
             status.setSeenInLastQualifiedNetworkSelection(true);
 
+            if (mWifiConfigManager.isNonCarrierMergedNetworkTemporarilyDisabled(network)) {
+                localLog("Ignoring non-carrier-merged SSID: " + network.SSID);
+                continue;
+            }
             if (mWifiConfigManager.isNetworkTemporarilyDisabledByUser(network.SSID)) {
                 localLog("Ignoring user disabled SSID: " + network.SSID);
                 continue;
@@ -192,7 +201,7 @@
                 ? mWifiCarrierInfoManager.getDefaultDataSimCarrierId() : network.carrierId;
         int subId = mWifiCarrierInfoManager.getMatchingSubId(carrierId);
         // Ignore security type is EAP SIM/AKA/AKA' when SIM is not present.
-        if (!mWifiCarrierInfoManager.isSimPresent(subId)) {
+        if (!mWifiCarrierInfoManager.isSimReady(subId)) {
             localLog("No SIM card is good for Network "
                     + WifiNetworkSelector.toNetworkString(network));
             return false;
diff --git a/service/java/com/android/server/wifi/ScanDetailCache.java b/service/java/com/android/server/wifi/ScanDetailCache.java
index c8490a5..1b518cf 100644
--- a/service/java/com/android/server/wifi/ScanDetailCache.java
+++ b/service/java/com/android/server/wifi/ScanDetailCache.java
@@ -120,7 +120,7 @@
         }
         ArrayList<ScanDetail> list = new ArrayList<ScanDetail>(mMap.values());
         if (list.size() != 0) {
-            // Sort by descending timestamp
+            // Sort by ascending timestamp (oldest scan results first)
             Collections.sort(list, new Comparator() {
                 public int compare(Object o1, Object o2) {
                     ScanDetail a = (ScanDetail) o1;
@@ -142,7 +142,22 @@
         }
     }
 
-    /* @hide */
+    /**
+     * Return the most recent ScanResult for this network, or null if non exists.
+     */
+    public ScanResult getMostRecentScanResult() {
+        ArrayList<ScanDetail> list = sort();
+        if (list.size() == 0) {
+            return null;
+        }
+        return list.get(0).getScanResult();
+    }
+
+    /**
+     * Returns the list of sorted ScanDetails in descending order of timestamp, followed by
+     * descending order of RSSI.
+     * @hide
+     **/
     private ArrayList<ScanDetail> sort() {
         ArrayList<ScanDetail> list = new ArrayList<ScanDetail>(mMap.values());
         if (list.size() != 0) {
diff --git a/service/java/com/android/server/wifi/ScanOnlyModeImpl.java b/service/java/com/android/server/wifi/ScanOnlyModeImpl.java
new file mode 100644
index 0000000..6449b6a
--- /dev/null
+++ b/service/java/com/android/server/wifi/ScanOnlyModeImpl.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.annotation.NonNull;
+
+/**
+ * Used to respond to calls to ClientMode interface when ClientModeImpl is not up
+ * i.e. in scan only mode.
+ */
+public class ScanOnlyModeImpl implements ClientModeDefaults {
+
+    private final long mId;
+    private final WifiNative mWifiNative;
+    private final String mIfaceName;
+
+    public ScanOnlyModeImpl(long id, @NonNull WifiNative wifiNative, @NonNull String ifaceName) {
+        mId = id;
+        mWifiNative = wifiNative;
+        mIfaceName = ifaceName;
+    }
+
+    @Override
+    public long getId() {
+        return mId;
+    }
+
+    @Override
+    public long getSupportedFeatures() {
+        return mWifiNative.getSupportedFeatureSet(mIfaceName);
+    }
+
+    @Override
+    public boolean isWifiStandardSupported(int standard) {
+        return mWifiNative.isWifiStandardSupported(mIfaceName, standard);
+    }
+
+    @Override
+    public boolean setCountryCode(String countryCode) {
+        return mWifiNative.setChipCountryCode(countryCode);
+    }
+
+    @Override
+    public String toString() {
+        return "ScanOnlyModeImpl{"
+                + "id=" + mId
+                + " iface=" + mIfaceName
+                + '}';
+    }
+}
diff --git a/service/java/com/android/server/wifi/ScanRequestProxy.java b/service/java/com/android/server/wifi/ScanRequestProxy.java
index aa9de6d..95380ef 100644
--- a/service/java/com/android/server/wifi/ScanRequestProxy.java
+++ b/service/java/com/android/server/wifi/ScanRequestProxy.java
@@ -19,6 +19,7 @@
 import static com.android.server.wifi.WifiSettingsConfigStore.WIFI_SCAN_THROTTLE_ENABLED;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.content.Context;
@@ -38,13 +39,18 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.modules.utils.HandlerExecutor;
+import com.android.modules.utils.build.SdkLevel;
+import com.android.server.wifi.util.ScanResultUtil;
 import com.android.server.wifi.util.WifiPermissionsUtil;
+import com.android.wifi.resources.R;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 
 import javax.annotation.concurrent.NotThreadSafe;
 
@@ -107,7 +113,9 @@
     private final ArrayMap<Pair<Integer, String>, LinkedList<Long>> mLastScanTimestampsForFgApps =
             new ArrayMap();
     // Scan results cached from the last full single scan request.
-    private final List<ScanResult> mLastScanResults = new ArrayList<>();
+    // Stored as a map of bssid -> ScanResult to allow other clients to perform ScanResult lookup
+    // for bssid more efficiently.
+    private final Map<String, ScanResult> mLastScanResultsMap = new HashMap<>();
     // external ScanResultCallback tracker
     private final RemoteCallbackList<IScanResultsCallback> mRegisteredScanResultsCallbacks;
     // Global scan listener for listening to all scan requests.
@@ -139,10 +147,10 @@
                 Log.d(TAG, "Received " + scanResults.length + " scan results");
             }
             // Only process full band scan results.
-            if (WifiScanner.isFullBandScan(scanData.getBandScanned(), false)) {
+            if (WifiScanner.isFullBandScan(scanData.getScannedBandsInternal(), false)) {
                 // Store the last scan results & send out the scan completion broadcast.
-                mLastScanResults.clear();
-                mLastScanResults.addAll(Arrays.asList(scanResults));
+                mLastScanResultsMap.clear();
+                Arrays.stream(scanResults).forEach(s -> mLastScanResultsMap.put(s.BSSID, s));
                 sendScanResultBroadcast(true);
                 sendScanResultsAvailableToCallbacks();
             }
@@ -257,7 +265,7 @@
         }
         mWifiScanner.setScanningEnabled(enable);
         sendScanAvailableBroadcast(mContext, enable);
-        clearScanResults();
+        if (!enable) clearScanResults();
         Log.i(TAG, "Scanning is " + (enable ? "enabled" : "disabled"));
     }
 
@@ -337,6 +345,9 @@
      */
     private boolean shouldScanRequestBeThrottledForForegroundApp(
             int callingUid, String packageName) {
+        if (isPackageNameInExceptionList(packageName, true)) {
+            return false;
+        }
         LinkedList<Long> scanRequestTimestamps =
                 getOrCreateScanRequestTimestampsForForegroundApp(callingUid, packageName);
         long currentTimeMillis = mClock.getElapsedSinceBootMillis();
@@ -350,10 +361,31 @@
         return false;
     }
 
+    private boolean isPackageNameInExceptionList(String packageName, boolean isForeground) {
+        if (packageName == null) {
+            return false;
+        }
+        String[] exceptionList = mContext.getResources().getStringArray(isForeground
+                ? R.array.config_wifiForegroundScanThrottleExceptionList
+                : R.array.config_wifiBackgroundScanThrottleExceptionList);
+        if (exceptionList == null) {
+            return false;
+        }
+        for (String name : exceptionList) {
+            if (packageName.equals(name)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Checks if the scan request from a background app needs to be throttled.
      */
-    private boolean shouldScanRequestBeThrottledForBackgroundApp() {
+    private boolean shouldScanRequestBeThrottledForBackgroundApp(String packageName) {
+        if (isPackageNameInExceptionList(packageName, false)) {
+            return false;
+        }
         long lastScanMs = mLastScanTimestampForBgApps;
         long elapsedRealtime = mClock.getElapsedSinceBootMillis();
         if (lastScanMs != 0
@@ -366,16 +398,15 @@
     }
 
     /**
-     * Check if the request comes from background app.
+     * Safely retrieve package importance.
      */
-    private boolean isRequestFromBackground(int callingUid, String packageName) {
+    private int getPackageImportance(int callingUid, String packageName) {
         mAppOps.checkPackage(callingUid, packageName);
         try {
-            return mActivityManager.getPackageImportance(packageName)
-                    > ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
+            return mActivityManager.getPackageImportance(packageName);
         } catch (SecurityException e) {
             Log.e(TAG, "Failed to check the app state", e);
-            return true;
+            return ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
         }
     }
 
@@ -383,10 +414,12 @@
      * Checks if the scan request from the app (specified by callingUid & packageName) needs
      * to be throttled.
      */
-    private boolean shouldScanRequestBeThrottledForApp(int callingUid, String packageName) {
+    private boolean shouldScanRequestBeThrottledForApp(int callingUid, String packageName,
+            int packageImportance) {
         boolean isThrottled;
-        if (isRequestFromBackground(callingUid, packageName)) {
-            isThrottled = shouldScanRequestBeThrottledForBackgroundApp();
+        if (packageImportance
+                > ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE) {
+            isThrottled = shouldScanRequestBeThrottledForBackgroundApp(packageName);
             if (isThrottled) {
                 if (mVerboseLoggingEnabled) {
                     Log.v(TAG, "Background scan app request [" + callingUid + ", "
@@ -426,22 +459,29 @@
         // Check and throttle scan request unless,
         // a) App has either NETWORK_SETTINGS or NETWORK_SETUP_WIZARD permission.
         // b) Throttling has been disabled by user.
+        int packageImportance = getPackageImportance(callingUid, packageName);
         if (!fromSettingsOrSetupWizard && mThrottleEnabled
-                && shouldScanRequestBeThrottledForApp(callingUid, packageName)) {
+                && shouldScanRequestBeThrottledForApp(callingUid, packageName, packageImportance)) {
             Log.i(TAG, "Scan request from " + packageName + " throttled");
             sendScanResultFailureBroadcastToPackage(packageName);
             return false;
         }
         // Create a worksource using the caller's UID.
         WorkSource workSource = new WorkSource(callingUid, packageName);
+        mWifiMetrics.getScanMetrics().setWorkSource(workSource);
+        mWifiMetrics.getScanMetrics().setImportance(packageImportance);
 
         // Create the scan settings.
         WifiScanner.ScanSettings settings = new WifiScanner.ScanSettings();
         // Scan requests from apps with network settings will be of high accuracy type.
         if (fromSettingsOrSetupWizard) {
             settings.type = WifiScanner.SCAN_TYPE_HIGH_ACCURACY;
+        } else {
+            if (SdkLevel.isAtLeastS()) {
+                // since the scan request is from a normal app, do not scan all 6Ghz channels.
+                settings.set6GhzPscOnlyEnabled(true);
+            }
         }
-        // always do full scans
         settings.band = WifiScanner.WIFI_BAND_ALL;
         settings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN
                 | WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT;
@@ -465,14 +505,28 @@
      */
     public List<ScanResult> getScanResults() {
         // return a copy to prevent external modification
-        return new ArrayList<>(mLastScanResults);
+        return new ArrayList<>(mLastScanResultsMap.values());
     }
 
     /**
+     * Return the ScanResult from the most recent access point scan for the provided bssid.
+     *
+     * @param bssid BSSID as string {@link ScanResult#BSSID}.
+     * @return ScanResult for the corresponding bssid if found, null otherwise.
+     */
+    public @Nullable ScanResult getScanResult(@NonNull String bssid) {
+        ScanResult scanResult = mLastScanResultsMap.get(bssid);
+        if (scanResult == null) return null;
+        // return a copy to prevent external modification
+        return new ScanResult(scanResult);
+    }
+
+
+    /**
      * Clear the stored scan results.
      */
     private void clearScanResults() {
-        mLastScanResults.clear();
+        mLastScanResultsMap.clear();
         mLastScanTimestampForBgApps = 0;
         mLastScanTimestampsForFgApps.clear();
     }
@@ -536,4 +590,54 @@
     public boolean isScanThrottleEnabled() {
         return mThrottleEnabled;
     }
+
+    /** Indicate whether there are WPA2 personal only networks. */
+    public boolean isWpa2PersonalOnlyNetworkInRange(String ssid) {
+        return mLastScanResultsMap.values().stream().anyMatch(r ->
+                ssid.equals(ScanResultUtil.createQuotedSSID(r.SSID))
+                && ScanResultUtil.isScanResultForPskNetwork(r)
+                && !ScanResultUtil.isScanResultForSaeNetwork(r));
+    }
+
+    /** Indicate whether there are WPA3 only networks. */
+    public boolean isWpa3PersonalOnlyNetworkInRange(String ssid) {
+        return mLastScanResultsMap.values().stream().anyMatch(r ->
+                ssid.equals(ScanResultUtil.createQuotedSSID(r.SSID))
+                && ScanResultUtil.isScanResultForSaeNetwork(r)
+                && !ScanResultUtil.isScanResultForPskNetwork(r));
+    }
+
+    /** Indicate whether there are OPEN only networks. */
+    public boolean isOpenOnlyNetworkInRange(String ssid) {
+        return mLastScanResultsMap.values().stream().anyMatch(r ->
+                ssid.equals(ScanResultUtil.createQuotedSSID(r.SSID))
+                && ScanResultUtil.isScanResultForOpenNetwork(r)
+                && !ScanResultUtil.isScanResultForOweNetwork(r));
+    }
+
+    /** Indicate whether there are OWE only networks. */
+    public boolean isOweOnlyNetworkInRange(String ssid) {
+        return mLastScanResultsMap.values().stream().anyMatch(r ->
+                ssid.equals(ScanResultUtil.createQuotedSSID(r.SSID))
+                && ScanResultUtil.isScanResultForOweNetwork(r)
+                && !ScanResultUtil.isScanResultForOweTransitionNetwork(r));
+    }
+
+    /** Indicate whether there are WPA2 Enterprise only networks. */
+    public boolean isWpa2EnterpriseOnlyNetworkInRange(String ssid) {
+        return mLastScanResultsMap.values().stream().anyMatch(r ->
+                ssid.equals(ScanResultUtil.createQuotedSSID(r.SSID))
+                && ScanResultUtil.isScanResultForEapNetwork(r)
+                && !ScanResultUtil.isScanResultForWpa3EnterpriseTransitionNetwork(r)
+                && !ScanResultUtil.isScanResultForWpa3EnterpriseOnlyNetwork(r));
+    }
+
+    /** Indicate whether there are WPA3 Enterprise only networks. */
+    public boolean isWpa3EnterpriseOnlyNetworkInRange(String ssid) {
+        return mLastScanResultsMap.values().stream().anyMatch(r ->
+                ssid.equals(ScanResultUtil.createQuotedSSID(r.SSID))
+                && ScanResultUtil.isScanResultForWpa3EnterpriseOnlyNetwork(r)
+                && !ScanResultUtil.isScanResultForWpa3EnterpriseTransitionNetwork(r)
+                && !ScanResultUtil.isScanResultForEapNetwork(r));
+    }
 }
diff --git a/service/java/com/android/server/wifi/ScanResultMatchInfo.java b/service/java/com/android/server/wifi/ScanResultMatchInfo.java
index cd48953..a9f7720 100644
--- a/service/java/com/android/server/wifi/ScanResultMatchInfo.java
+++ b/service/java/com/android/server/wifi/ScanResultMatchInfo.java
@@ -16,11 +16,15 @@
 package com.android.server.wifi;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.net.wifi.ScanResult;
+import android.net.wifi.SecurityParams;
 import android.net.wifi.WifiConfiguration;
 
 import com.android.server.wifi.util.ScanResultUtil;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -32,47 +36,14 @@
      */
     public String networkSsid;
     /**
-     * Security Type of the network.
+     * Security params list.
      */
-    public @WifiConfiguration.SecurityType int networkType;
-    /**
-     * Special flag for PSK-SAE in transition mode
-     */
-    public boolean pskSaeInTransitionMode;
-    /**
-     * Special flag for OWE in transition mode
-     */
-    public boolean oweInTransitionMode;
+    public List<SecurityParams> securityParamsList = new ArrayList<>();
 
     /**
      * True if created from a scan result
      */
     private boolean mFromScanResult = false;
-    /**
-     * Fetch network type from network configuration.
-     */
-    private static @WifiConfiguration.SecurityType int getNetworkType(WifiConfiguration config) {
-        if (WifiConfigurationUtil.isConfigForSaeNetwork(config)) {
-            return WifiConfiguration.SECURITY_TYPE_SAE;
-        } else if (WifiConfigurationUtil.isConfigForPskNetwork(config)) {
-            return WifiConfiguration.SECURITY_TYPE_PSK;
-        } else if (WifiConfigurationUtil.isConfigForWapiPskNetwork(config)) {
-            return WifiConfiguration.SECURITY_TYPE_WAPI_PSK;
-        } else if (WifiConfigurationUtil.isConfigForWapiCertNetwork(config)) {
-            return WifiConfiguration.SECURITY_TYPE_WAPI_CERT;
-        } else if (WifiConfigurationUtil.isConfigForEapNetwork(config)) {
-            return WifiConfiguration.SECURITY_TYPE_EAP;
-        } else if (WifiConfigurationUtil.isConfigForEapSuiteBNetwork(config)) {
-            return WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B;
-        } else if (WifiConfigurationUtil.isConfigForWepNetwork(config)) {
-            return WifiConfiguration.SECURITY_TYPE_WEP;
-        } else if (WifiConfigurationUtil.isConfigForOweNetwork(config)) {
-            return WifiConfiguration.SECURITY_TYPE_OWE;
-        } else if (WifiConfigurationUtil.isConfigForOpenNetwork(config)) {
-            return WifiConfiguration.SECURITY_TYPE_OPEN;
-        }
-        throw new IllegalArgumentException("Invalid WifiConfiguration: " + config);
-    }
 
     /**
      * Get the ScanResultMatchInfo for the given WifiConfiguration
@@ -80,38 +51,11 @@
     public static ScanResultMatchInfo fromWifiConfiguration(WifiConfiguration config) {
         ScanResultMatchInfo info = new ScanResultMatchInfo();
         info.networkSsid = config.SSID;
-        info.networkType = getNetworkType(config);
+        info.securityParamsList = config.getSecurityParamsList();
         return info;
     }
 
     /**
-     * Fetch network type from scan result.
-     */
-    private static @WifiConfiguration.SecurityType int getNetworkType(ScanResult scanResult) {
-        if (ScanResultUtil.isScanResultForSaeNetwork(scanResult)) {
-            return WifiConfiguration.SECURITY_TYPE_SAE;
-        } else if (ScanResultUtil.isScanResultForWapiPskNetwork(scanResult)) {
-            return WifiConfiguration.SECURITY_TYPE_WAPI_PSK;
-        } else if (ScanResultUtil.isScanResultForWapiCertNetwork(scanResult)) {
-            return WifiConfiguration.SECURITY_TYPE_WAPI_CERT;
-        } else if (ScanResultUtil.isScanResultForPskNetwork(scanResult)) {
-            return WifiConfiguration.SECURITY_TYPE_PSK;
-        } else if (ScanResultUtil.isScanResultForEapSuiteBNetwork(scanResult)) {
-            return WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B;
-        } else if (ScanResultUtil.isScanResultForEapNetwork(scanResult)) {
-            return WifiConfiguration.SECURITY_TYPE_EAP;
-        } else if (ScanResultUtil.isScanResultForWepNetwork(scanResult)) {
-            return WifiConfiguration.SECURITY_TYPE_WEP;
-        } else if (ScanResultUtil.isScanResultForOweNetwork(scanResult)) {
-            return WifiConfiguration.SECURITY_TYPE_OWE;
-        } else if (ScanResultUtil.isScanResultForOpenNetwork(scanResult)) {
-            return WifiConfiguration.SECURITY_TYPE_OPEN;
-        } else {
-            throw new IllegalArgumentException("Invalid ScanResult: " + scanResult);
-        }
-    }
-
-    /**
      * Get the ScanResultMatchInfo for the given ScanResult
      */
     public static ScanResultMatchInfo fromScanResult(ScanResult scanResult) {
@@ -121,76 +65,167 @@
         // However, according to our public documentation ths {@link WifiConfiguration#SSID} can
         // either have a hex string or quoted ASCII string SSID.
         info.networkSsid = ScanResultUtil.createQuotedSSID(scanResult.SSID);
-        info.networkType = getNetworkType(scanResult);
-        info.oweInTransitionMode = false;
-        info.pskSaeInTransitionMode = false;
+        info.securityParamsList =
+                ScanResultUtil.generateSecurityParamsListFromScanResult(scanResult);
         info.mFromScanResult = true;
-        if (info.networkType == WifiConfiguration.SECURITY_TYPE_SAE) {
-            // Note that scan result util will always choose the highest security protocol.
-            info.pskSaeInTransitionMode =
-                    ScanResultUtil.isScanResultForPskSaeTransitionNetwork(scanResult);
-        } else  if (info.networkType == WifiConfiguration.SECURITY_TYPE_OWE) {
-            // Note that scan result util will always choose OWE.
-            info.oweInTransitionMode =
-                    ScanResultUtil.isScanResultForOweTransitionNetwork(scanResult);
-        }
         return info;
     }
 
     /**
+     * Check if an auto-upgraded security parameters configuration is allowed by the overlay
+     * configurations for WPA3-Personal (SAE) and Enhanced Open (OWE).
+     *
+     * @param securityParams Security parameters object
+     * @return true if allowed, false if not allowed
+     */
+    private static boolean isAutoUpgradeSecurityParamsAllowed(SecurityParams securityParams) {
+        WifiGlobals wifiGlobals = WifiInjector.getInstance().getWifiGlobals();
+        // In mixed security network environments, we need to filter out APs with the stronger
+        // security type when the current network supports the weaker security type, and the
+        // stronger security type was added by auto-upgrade, and
+        // auto-upgrade feature is disabled.
+        if (securityParams.getSecurityType() == WifiConfiguration.SECURITY_TYPE_SAE
+                && securityParams.isAddedByAutoUpgrade()
+                && !wifiGlobals.isWpa3SaeUpgradeEnabled()) {
+            return false;
+        }
+        if (securityParams.getSecurityType() == WifiConfiguration.SECURITY_TYPE_OWE
+                && securityParams.isAddedByAutoUpgrade()
+                && !wifiGlobals.isOweUpgradeEnabled()) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * The matching algorithm is that the type with a bigger index in the allowed
+     * params list has the higher priority. We try to match each type from the end of
+     * the allowed params list against the params in the scan result params list.
+     *
+     * There are three cases which will skip the match:
+     * 1. the security type is different.
+     * 2. the params is disabled, ex. disabled by Transition Disable Indication.
+     * 3. The params is added by the auto-upgrade mechanism, but the corresponding
+     *    feature is not enabled.
+     */
+    private static @Nullable SecurityParams findBestMatchingSecurityParams(
+            List<SecurityParams> allowedParamsList,
+            List<SecurityParams> scanResultParamsList) {
+        if (null == allowedParamsList) return null;
+        if (null == scanResultParamsList) return null;
+        for (int i = allowedParamsList.size() - 1; i >= 0; i--) {
+            SecurityParams allowedParams = allowedParamsList.get(i);
+            if (!WifiConfigurationUtil.isSecurityParamsValid(allowedParams)
+                    || !isAutoUpgradeSecurityParamsAllowed(allowedParams)) {
+                continue;
+            }
+            for (SecurityParams scanResultParams: scanResultParamsList) {
+                if (!allowedParams.isSecurityType(scanResultParams.getSecurityType())) {
+                    continue;
+                }
+                return allowedParams;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Get the best-matching security type between ScanResult and WifiConifiguration.
+     */
+    public static @Nullable SecurityParams getBestMatchingSecurityParams(
+            WifiConfiguration config,
+            ScanResult scanResult) {
+        if (null == config || null == scanResult) return null;
+
+        return findBestMatchingSecurityParams(
+                config.getSecurityParamsList(),
+                ScanResultUtil.generateSecurityParamsListFromScanResult(scanResult));
+    }
+
+    /**
+     * Get the best-matching security type between ScanResult and WifiConifiguration.
+     */
+    public static @Nullable SecurityParams getBestMatchingSecurityParams(
+            WifiConfiguration config,
+            List<SecurityParams> scanResultParamsList) {
+        if (null == config || null == scanResultParamsList) return null;
+
+        return findBestMatchingSecurityParams(
+                config.getSecurityParamsList(),
+                scanResultParamsList);
+    }
+
+    public @Nullable SecurityParams getDefaultSecurityParams() {
+        return securityParamsList.isEmpty() ? null : securityParamsList.get(0);
+    }
+
+    public @Nullable SecurityParams getFirstAvailableSecurityParams() {
+        return securityParamsList.stream()
+                .filter(WifiConfigurationUtil::isSecurityParamsValid)
+                .findFirst()
+                .orElse(null);
+    }
+
+    /**
      * Checks for equality of network type.
      */
-    public boolean networkTypeEquals(@NonNull ScanResultMatchInfo other,
-            boolean saeAutoUpgradeEnabled) {
-        boolean networkTypeEquals;
-        // Detect <SSID, PSK+SAE> scan result and say it is equal to <SSID, PSK> configuration
-        if (other.pskSaeInTransitionMode && networkType == WifiConfiguration.SECURITY_TYPE_PSK
-                || (pskSaeInTransitionMode
-                && other.networkType == WifiConfiguration.SECURITY_TYPE_PSK)) {
-            networkTypeEquals = true;
-        } else if ((networkType == WifiConfiguration.SECURITY_TYPE_OPEN
-                && other.oweInTransitionMode) || (oweInTransitionMode
-                && other.networkType == WifiConfiguration.SECURITY_TYPE_OPEN)) {
-            // Special case we treat Enhanced Open and Open as equals. This is done to support the
-            // case where a saved network is Open but we found an OWE in transition network.
-            networkTypeEquals = true;
-        } else if ((saeAutoUpgradeEnabled)
-                && ((mFromScanResult && networkType == WifiConfiguration.SECURITY_TYPE_SAE
-                && other.networkType == WifiConfiguration.SECURITY_TYPE_PSK)
-                || (other.mFromScanResult
-                && other.networkType == WifiConfiguration.SECURITY_TYPE_SAE
-                && networkType == WifiConfiguration.SECURITY_TYPE_PSK))) {
-            // Allow upgrading WPA2 PSK connections to WPA3 SAE AP
-            networkTypeEquals = true;
-        } else {
-            networkTypeEquals = networkType == other.networkType;
+    public boolean networkTypeEquals(@NonNull ScanResultMatchInfo other) {
+        if (null == securityParamsList || null == other.securityParamsList) return false;
+
+        // If both are from the same sources, do normal comparison.
+        if (mFromScanResult == other.mFromScanResult) {
+            return securityParamsList.equals(other.securityParamsList);
         }
-        return networkTypeEquals;
+
+        final List<SecurityParams> allowedParamsList = mFromScanResult
+                ? other.securityParamsList : securityParamsList;
+        final List<SecurityParams> scanResultParamsList = mFromScanResult
+                ? securityParamsList : other.securityParamsList;
+
+        return null != findBestMatchingSecurityParams(
+                allowedParamsList,
+                scanResultParamsList);
     }
 
     @Override
     public boolean equals(Object otherObj) {
-        return matchForNetworkSelection(otherObj, false);
-    }
-
-    /**
-     * Match two ScanResultMatchInfo objects while considering configuration in overlays
-     *
-     * @param otherObj Other object to compare against
-     * @param saeAutoUpgradeEnabled A boolean that indicates if WPA3 auto upgrade feature is enabled
-     * @return true if objects are equal for network selection purposes, false otherwise
-     */
-    public boolean matchForNetworkSelection(Object otherObj, boolean saeAutoUpgradeEnabled) {
         if (this == otherObj) {
             return true;
         } else if (!(otherObj instanceof ScanResultMatchInfo)) {
             return false;
         }
         ScanResultMatchInfo other = (ScanResultMatchInfo) otherObj;
-        if (!Objects.equals(networkSsid, other.networkSsid)) {
-            return false;
+        if (mFromScanResult == other.mFromScanResult) {
+            return Objects.equals(networkSsid, other.networkSsid)
+                    && securityParamsList.equals(other.securityParamsList);
         }
-        return networkTypeEquals(other, saeAutoUpgradeEnabled);
+        return null != matchForNetworkSelection(other);
+    }
+
+    /**
+     * Match two ScanResultMatchInfo objects while considering configuration in overlays
+     *
+     * @param other Other object to compare against
+     * @return return best matching security params, null if no matching one.
+     */
+    public SecurityParams matchForNetworkSelection(ScanResultMatchInfo other) {
+        if (!Objects.equals(networkSsid, other.networkSsid)) return null;
+        if (null == securityParamsList) return null;
+        if (null == other.securityParamsList) return null;
+
+        final List<SecurityParams> allowedParamsList = mFromScanResult
+                ? other.securityParamsList : securityParamsList;
+        final List<SecurityParams> scanResultParamsList = mFromScanResult
+                ? securityParamsList : other.securityParamsList;
+
+        return findBestMatchingSecurityParams(
+                allowedParamsList,
+                scanResultParamsList);
+    }
+
+    /** Check whether this matchinfo contains the type or not. */
+    public boolean isSecurityType(@WifiConfiguration.SecurityType int securityType) {
+        return securityParamsList.stream().anyMatch(p -> p.isSecurityType(securityType));
     }
 
     @Override
@@ -200,9 +235,12 @@
 
     @Override
     public String toString() {
-        return "ScanResultMatchInfo: SSID: " + networkSsid + ", type: " + networkType
-                + ", WPA3 in transition mode: " + pskSaeInTransitionMode
-                + ", OWE in transition mode: " + oweInTransitionMode + ", from scan result: "
-                + mFromScanResult;
+        StringBuffer sbuf = new StringBuffer();
+        sbuf.append("ScanResultMatchInfo: SSID: ").append(networkSsid);
+        sbuf.append(", from scan result: ").append(mFromScanResult);
+        sbuf.append(", SecurityParams List:");
+        securityParamsList.stream()
+                .forEach(params -> sbuf.append(params.toString()));
+        return sbuf.toString();
     }
 }
diff --git a/service/java/com/android/server/wifi/ScoredNetworkNominator.java b/service/java/com/android/server/wifi/ScoredNetworkNominator.java
index b2b28c0..4a538a3 100644
--- a/service/java/com/android/server/wifi/ScoredNetworkNominator.java
+++ b/service/java/com/android/server/wifi/ScoredNetworkNominator.java
@@ -24,10 +24,10 @@
 import android.net.NetworkKey;
 import android.net.NetworkScoreManager;
 import android.net.wifi.ScanResult;
+import android.net.wifi.SecurityParams;
 import android.net.wifi.WifiConfiguration;
 import android.os.Handler;
 import android.provider.Settings;
-import android.text.TextUtils;
 import android.util.LocalLog;
 import android.util.Log;
 import android.util.Pair;
@@ -51,6 +51,11 @@
             "use_open_wifi_package";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
+    /**
+     * Attribution tag used for checks on the scorers access to location permissions.
+     */
+    private static final String ATTRIBUTION_FEATURE_ID = "system_scored_network_nominator";
+
     private final NetworkScoreManager mNetworkScoreManager;
     private final PackageManager mPackageManager;
     private final WifiConfigManager mWifiConfigManager;
@@ -132,7 +137,8 @@
         try {
             // TODO moltmann: Can we set a featureID here instead of null?
             mWifiPermissionsUtil.enforceCanAccessScanResults(
-                    scorerUidAndPackage.second, null, scorerUidAndPackage.first, null);
+                    scorerUidAndPackage.second, ATTRIBUTION_FEATURE_ID,
+                    scorerUidAndPackage.first, null);
             return true;
         } catch (SecurityException e) {
             return false;
@@ -141,8 +147,8 @@
 
     @Override
     public void nominateNetworks(List<ScanDetail> scanDetails,
-            WifiConfiguration currentNetwork, String currentBssid, boolean connected,
-            boolean untrustedNetworkAllowed,
+            boolean untrustedNetworkAllowed, boolean oemPaidNetworkAllowed /* unused */,
+            boolean oemPrivateNetworkAllowed /* unused */,
             @NonNull OnConnectableListener onConnectableListener) {
         if (!mNetworkRecommendationsEnabled) {
             mLocalLog.log("Skipping nominateNetworks; Network recommendations disabled.");
@@ -160,7 +166,7 @@
                 continue;
             }
             final WifiConfiguration configuredNetwork =
-                    mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(scanDetail);
+                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(scanDetail);
             boolean untrustedScanResult = configuredNetwork == null || !configuredNetwork.trusted;
 
             if (!untrustedNetworkAllowed && untrustedScanResult) {
@@ -185,17 +191,20 @@
                 debugLog("Ignoring disabled SSID: " + configuredNetwork.SSID);
                 continue;
             }
+            if (mWifiConfigManager.isNonCarrierMergedNetworkTemporarilyDisabled(
+                    configuredNetwork)) {
+                debugLog("Ignoring non-carrier-merged SSID: " + configuredNetwork.SSID);
+                continue;
+            }
 
-            // TODO(b/37485956): consider applying a boost for networks with only the same SSID
-            boolean isCurrentNetwork = currentNetwork != null
-                    && currentNetwork.networkId == configuredNetwork.networkId
-                    && TextUtils.equals(currentBssid, scanResult.BSSID);
+            // score boosts for current network is done by the candidate scorer. Don't artificially
+            // boost the score in the nominator.
             if (!configuredNetwork.trusted) {
                 scoreTracker.trackUntrustedCandidate(
-                        scanResult, configuredNetwork, isCurrentNetwork);
+                        scanResult, configuredNetwork, false /* isCurrentNetwork */);
             } else {
                 scoreTracker.trackExternallyScoredCandidate(
-                        scanResult, configuredNetwork, isCurrentNetwork);
+                        scanResult, configuredNetwork, false /* isCurrentNetwork */);
             }
             onConnectableListener.onConnectable(scanDetail, configuredNetwork);
         }
@@ -262,7 +271,10 @@
                 mScanDetailCandidate = null;
                 mBestCandidateType = EXTERNAL_SCORED_UNTRUSTED_NETWORK;
                 mEphemeralConfig = config;
-                mWifiConfigManager.setNetworkCandidateScanResult(config.networkId, scanResult, 0);
+                SecurityParams params = ScanResultMatchInfo.getBestMatchingSecurityParams(
+                        config, scanResult);
+                mWifiConfigManager.setNetworkCandidateScanResult(
+                        config.networkId, scanResult, 0, params);
                 debugLog(WifiNetworkSelector.toScanId(scanResult)
                         + " becomes the new untrusted candidate.");
             }
@@ -283,7 +295,10 @@
                 mScanResultCandidate = scanResult;
                 mScanDetailCandidate = null;
                 mBestCandidateType = EXTERNAL_SCORED_SAVED_NETWORK;
-                mWifiConfigManager.setNetworkCandidateScanResult(config.networkId, scanResult, 0);
+                SecurityParams params = ScanResultMatchInfo.getBestMatchingSecurityParams(
+                        config, scanResult);
+                mWifiConfigManager.setNetworkCandidateScanResult(
+                        config.networkId, scanResult, 0, params);
                 debugLog(WifiNetworkSelector.toScanId(scanResult)
                         + " becomes the new externally scored saved network candidate.");
             }
@@ -313,6 +328,12 @@
 
                     mEphemeralConfig =
                             ScanResultUtil.createNetworkFromScanResult(mScanResultCandidate);
+                    if (null == mEphemeralConfig) {
+                        mLocalLog.log("Failed to create ephemeral network from the scan result:"
+                                + " SSID=" + mScanResultCandidate.SSID
+                                + ", caps=" + mScanResultCandidate.capabilities);
+                        break;
+                    }
                     // Mark this config as ephemeral so it isn't persisted.
                     mEphemeralConfig.ephemeral = true;
                     // Mark this network as untrusted.
@@ -336,8 +357,11 @@
                         // A message here might help with the diagnosis.
                         Log.e(TAG, "mScanDetailCandidate is null!");
                     }
+                    SecurityParams params = ScanResultMatchInfo.getBestMatchingSecurityParams(
+                            mWifiConfigManager.getConfiguredNetwork(candidateNetworkId),
+                            mScanResultCandidate);
                     mWifiConfigManager.setNetworkCandidateScanResult(candidateNetworkId,
-                            mScanResultCandidate, 0);
+                            mScanResultCandidate, 0, params);
                     mLocalLog.log(String.format("new ephemeral candidate %s network ID:%d, "
                                                 + "meteredHint=%b",
                                         WifiNetworkSelector.toScanId(mScanResultCandidate),
diff --git a/service/java/com/android/server/wifi/ScoringParams.java b/service/java/com/android/server/wifi/ScoringParams.java
index f2a3182..a860542 100644
--- a/service/java/com/android/server/wifi/ScoringParams.java
+++ b/service/java/com/android/server/wifi/ScoringParams.java
@@ -93,6 +93,7 @@
         public int currentNetworkBonusPercent = 20;
         public int secureNetworkBonus = 40;
         public int lastSelectionMinutes = 480;
+        public int estimateRssiErrorMargin = 5;
         public static final int MIN_MINUTES = 1;
         public static final int MAX_MINUTES = Integer.MAX_VALUE / (60 * 1000);
 
@@ -281,6 +282,8 @@
                 R.integer.config_wifiFrameworkSecureNetworkBonus);
         mVal.lastSelectionMinutes = context.getResources().getInteger(
                 R.integer.config_wifiFrameworkLastSelectionMinutes);
+        mVal.estimateRssiErrorMargin = context.getResources().getInteger(
+                R.integer.config_wifiEstimateRssiErrorMarginDb);
         mVal.pps[ACTIVE_TRAFFIC] = context.getResources().getInteger(
                 R.integer.config_wifiFrameworkMinPacketPerSecondActiveTraffic);
         mVal.pps[HIGH_TRAFFIC] = context.getResources().getInteger(
@@ -406,6 +409,15 @@
     }
 
     /**
+     * Returns the estimate rssi error margin to account minor differences in the environment
+     * and the device's orientation.
+     *
+     */
+    public int getEstimateRssiErrorMargin() {
+        return mVal.estimateRssiErrorMargin;
+    }
+
+    /**
      */
     public int getThroughputBonusNumerator() {
         return mVal.throughputBonusNumerator;
diff --git a/service/java/com/android/server/wifi/SelfRecovery.java b/service/java/com/android/server/wifi/SelfRecovery.java
index b7eb723..39686ef 100644
--- a/service/java/com/android/server/wifi/SelfRecovery.java
+++ b/service/java/com/android/server/wifi/SelfRecovery.java
@@ -46,18 +46,24 @@
     public static final int REASON_LAST_RESORT_WATCHDOG = 0;
     public static final int REASON_WIFINATIVE_FAILURE = 1;
     public static final int REASON_STA_IFACE_DOWN = 2;
+    public static final int REASON_API_CALL = 3;
+    public static final int REASON_SUBSYSTEM_RESTART = 4;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"REASON_"}, value = {
             REASON_LAST_RESORT_WATCHDOG,
             REASON_WIFINATIVE_FAILURE,
-            REASON_STA_IFACE_DOWN})
+            REASON_STA_IFACE_DOWN,
+            REASON_API_CALL,
+            REASON_SUBSYSTEM_RESTART})
     public @interface RecoveryReason {}
 
     protected static final String[] REASON_STRINGS = {
             "Last Resort Watchdog",  // REASON_LAST_RESORT_WATCHDOG
             "WifiNative Failure",    // REASON_WIFINATIVE_FAILURE
-            "Sta Interface Down"     // REASON_STA_IFACE_DOWN
+            "Sta Interface Down",    // REASON_STA_IFACE_DOWN
+            "API call (e.g. user)",  // REASON_API_CALL
+            "Subsystem Restart"      // REASON_SUBSYSTEM_RESTART
     };
 
     private final Context mContext;
@@ -65,11 +71,44 @@
     private final Clock mClock;
     // Time since boot (in millis) that restart occurred
     private final LinkedList<Long> mPastRestartTimes;
-    public SelfRecovery(Context context, ActiveModeWarden activeModeWarden, Clock clock) {
+    private final WifiNative mWifiNative;
+    private int mSelfRecoveryReason;
+    private boolean mDidWeTriggerSelfRecovery;
+    private SubsystemRestartListenerInternal mSubsystemRestartListener;
+
+    private class SubsystemRestartListenerInternal
+            implements HalDeviceManager.SubsystemRestartListener{
+        @Override
+        public void onSubsystemRestart() {
+            String reasonString =  "";
+            if (!mDidWeTriggerSelfRecovery) {
+                // We did not trigger recovery, but looks like the firmware crashed?
+                mSelfRecoveryReason = REASON_SUBSYSTEM_RESTART;
+            }
+
+            if (mSelfRecoveryReason < REASON_STRINGS.length && mSelfRecoveryReason >= 0) {
+                reasonString = REASON_STRINGS[mSelfRecoveryReason];
+            }
+
+            Log.e(TAG, "Restarting wifi for reason: " + reasonString);
+            mActiveModeWarden.recoveryRestartWifi(mSelfRecoveryReason, reasonString,
+                    mSelfRecoveryReason != REASON_LAST_RESORT_WATCHDOG
+                     && mSelfRecoveryReason != REASON_API_CALL);
+
+            mDidWeTriggerSelfRecovery = false;
+        }
+    }
+
+    public SelfRecovery(Context context, ActiveModeWarden activeModeWarden,
+            Clock clock, WifiNative wifiNative) {
         mContext = context;
         mActiveModeWarden = activeModeWarden;
         mClock = clock;
         mPastRestartTimes = new LinkedList<>();
+        mWifiNative = wifiNative;
+        mSubsystemRestartListener = new SubsystemRestartListenerInternal();
+        mWifiNative.registerSubsystemRestartListener(mSubsystemRestartListener);
+        mDidWeTriggerSelfRecovery = false;
     }
 
     /**
@@ -86,7 +125,7 @@
      */
     public void trigger(@RecoveryReason int reason) {
         if (!(reason == REASON_LAST_RESORT_WATCHDOG || reason == REASON_WIFINATIVE_FAILURE
-                  || reason == REASON_STA_IFACE_DOWN)) {
+                  || reason == REASON_STA_IFACE_DOWN || reason == REASON_API_CALL)) {
             Log.e(TAG, "Invalid trigger reason. Ignoring...");
             return;
         }
@@ -114,7 +153,13 @@
             }
             mPastRestartTimes.add(mClock.getElapsedSinceBootMillis());
         }
-        mActiveModeWarden.recoveryRestartWifi(reason);
+
+        mSelfRecoveryReason = reason;
+        mDidWeTriggerSelfRecovery = true;
+        if (!mWifiNative.startSubsystemRestart()) {
+            // HAL call failed, fallback to internal flow.
+            mSubsystemRestartListener.onSubsystemRestart();
+        }
     }
 
     /**
diff --git a/service/java/com/android/server/wifi/SimRequiredNotifier.java b/service/java/com/android/server/wifi/SimRequiredNotifier.java
index fed7bd8..69fe4c3 100644
--- a/service/java/com/android/server/wifi/SimRequiredNotifier.java
+++ b/service/java/com/android/server/wifi/SimRequiredNotifier.java
@@ -16,24 +16,16 @@
 
 package com.android.server.wifi;
 
-import android.app.ActivityManager;
 import android.app.Notification;
-import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.graphics.drawable.Icon;
 import android.net.wifi.WifiConfiguration;
-import android.os.UserHandle;
 import android.provider.Settings;
-import android.util.Log;
 
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.wifi.resources.R;
 
-import java.util.List;
-
 /**
  * Helper class to generate SIM required notification
  *
@@ -43,13 +35,13 @@
     private static final String TAG = "SimRequiredNotifier";
     private final WifiContext mContext;
     private final FrameworkFacade mFrameworkFacade;
-    private final NotificationManager mNotificationManager;
+    private final WifiNotificationManager mNotificationManager;
 
-    public SimRequiredNotifier(WifiContext context, FrameworkFacade framework) {
+    public SimRequiredNotifier(WifiContext context, FrameworkFacade framework,
+            WifiNotificationManager wifiNotificationManager) {
         mContext = context;
         mFrameworkFacade = framework;
-        mNotificationManager =
-                mContext.getSystemService(NotificationManager.class);
+        mNotificationManager = wifiNotificationManager;
     }
 
     /**
@@ -69,24 +61,11 @@
      * Dismiss notification
      */
     public void dismissSimRequiredNotification() {
-        mNotificationManager.cancel(null, SystemMessage.NOTE_ID_WIFI_SIM_REQUIRED);
-    }
-
-    private String getSettingsPackageName() {
-        Intent intent = new Intent(Settings.ACTION_WIFI_SETTINGS);
-        List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentActivitiesAsUser(
-                intent, PackageManager.MATCH_SYSTEM_ONLY | PackageManager.MATCH_DEFAULT_ONLY,
-                UserHandle.of(ActivityManager.getCurrentUser()));
-        if (resolveInfos == null || resolveInfos.isEmpty()) {
-            Log.e(TAG, "Failed to resolve wifi settings activity");
-            return null;
-        }
-        // Pick the first one if there are more than 1 since the list is ordered from best to worst.
-        return resolveInfos.get(0).activityInfo.packageName;
+        mNotificationManager.cancel(SystemMessage.NOTE_ID_WIFI_SIM_REQUIRED);
     }
 
     private void showNotification(String ssid, String carrier) {
-        String settingsPackage = getSettingsPackageName();
+        String settingsPackage = mFrameworkFacade.getSettingsPackageName(mContext);
         if (settingsPackage == null) return;
         Intent intent = new Intent(Settings.ACTION_WIRELESS_SETTINGS)
                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
@@ -112,8 +91,8 @@
                 .setSmallIcon(Icon.createWithResource(mContext.getWifiOverlayApkPkgName(),
                         R.drawable.stat_notify_wifi_in_range))
                 .setContentIntent(mFrameworkFacade.getActivity(
-                        mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
-        mNotificationManager.notify(SystemMessage.NOTE_ID_WIFI_SIM_REQUIRED,
-                builder.build());
+                        mContext, 0, intent,
+                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE));
+        mNotificationManager.notify(SystemMessage.NOTE_ID_WIFI_SIM_REQUIRED, builder.build());
     }
 }
diff --git a/service/java/com/android/server/wifi/SoftApBackupRestore.java b/service/java/com/android/server/wifi/SoftApBackupRestore.java
index 441321c..59fbf46 100644
--- a/service/java/com/android/server/wifi/SoftApBackupRestore.java
+++ b/service/java/com/android/server/wifi/SoftApBackupRestore.java
@@ -23,7 +23,9 @@
 import android.net.wifi.WifiMigration;
 import android.util.BackupUtils;
 import android.util.Log;
+import android.util.SparseIntArray;
 
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.util.ApConfigUtil;
 import com.android.server.wifi.util.SettingsMigrationDataHolder;
 
@@ -50,7 +52,8 @@
     /**
      * Current backup data version.
      */
-    private static final int CURRENT_SAP_BACKUP_DATA_VERSION = 7;
+    private static final int CURRENT_SAP_BACKUP_DATA_VERSION = 8;
+    private static final int LAST_SAP_BACKUP_DATA_VERSION_IN_R = 7;
 
     private static final int ETHER_ADDR_LEN = 6; // Byte array size of MacAddress
 
@@ -78,8 +81,11 @@
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         try {
             DataOutputStream out = new DataOutputStream(baos);
-
-            out.writeInt(CURRENT_SAP_BACKUP_DATA_VERSION);
+            if (SdkLevel.isAtLeastS()) {
+                out.writeInt(CURRENT_SAP_BACKUP_DATA_VERSION);
+            } else {
+                out.writeInt(LAST_SAP_BACKUP_DATA_VERSION_IN_R);
+            }
             BackupUtils.writeString(out, config.getSsid());
             out.writeInt(config.getBand());
             out.writeInt(config.getChannel());
@@ -92,6 +98,19 @@
             writeMacAddressList(out, config.getBlockedClientList());
             writeMacAddressList(out, config.getAllowedClientList());
             out.writeBoolean(config.isAutoShutdownEnabled());
+            if (SdkLevel.isAtLeastS()) {
+                out.writeBoolean(config.isBridgedModeOpportunisticShutdownEnabled());
+                out.writeInt(config.getMacRandomizationSetting());
+                SparseIntArray channels = config.getChannels();
+                int numOfChannels = channels.size();
+                out.writeInt(numOfChannels);
+                for (int i = 0; i < numOfChannels; i++) {
+                    out.writeInt(channels.keyAt(i));
+                    out.writeInt(channels.valueAt(i));
+                }
+                out.writeBoolean(config.isIeee80211axEnabled());
+            }
+
         } catch (IOException io) {
             Log.e(TAG, "Invalid configuration received, IOException " + io);
             return new byte[0];
@@ -174,6 +193,17 @@
                     configBuilder.setAutoShutdownEnabled(migrationData.isSoftApTimeoutEnabled());
                 }
             }
+            if (version >= 8 && SdkLevel.isAtLeastS()) {
+                configBuilder.setBridgedModeOpportunisticShutdownEnabled(in.readBoolean());
+                configBuilder.setMacRandomizationSetting(in.readInt());
+                int numOfChannels = in.readInt();
+                SparseIntArray channels = new SparseIntArray(numOfChannels);
+                for (int i = 0; i < numOfChannels; i++) {
+                    channels.put(in.readInt(), in.readInt());
+                }
+                configBuilder.setChannels(channels);
+                configBuilder.setIeee80211axEnabled(in.readBoolean());
+            }
         } catch (IOException io) {
             Log.e(TAG, "Invalid backup data received, IOException: " + io);
             return null;
diff --git a/service/java/com/android/server/wifi/SoftApManager.java b/service/java/com/android/server/wifi/SoftApManager.java
index b321a78..aa9c7a9 100644
--- a/service/java/com/android/server/wifi/SoftApManager.java
+++ b/service/java/com/android/server/wifi/SoftApManager.java
@@ -24,6 +24,7 @@
 import static com.android.server.wifi.util.ApConfigUtil.SUCCESS;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Intent;
 import android.net.MacAddress;
 import android.net.wifi.ScanResult;
@@ -32,13 +33,14 @@
 import android.net.wifi.SoftApInfo;
 import android.net.wifi.WifiAnnotations;
 import android.net.wifi.WifiClient;
+import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
-import android.net.wifi.nl80211.NativeWifiClient;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.os.WorkSource;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -48,8 +50,11 @@
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import com.android.internal.util.WakeupMessage;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.WifiNative.InterfaceCallback;
 import com.android.server.wifi.WifiNative.SoftApListener;
+import com.android.server.wifi.coex.CoexManager;
+import com.android.server.wifi.coex.CoexManager.CoexListener;
 import com.android.server.wifi.util.ApConfigUtil;
 import com.android.wifi.resources.R;
 
@@ -57,11 +62,14 @@
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -74,20 +82,29 @@
     @VisibleForTesting
     public static final String SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG = TAG
             + " Soft AP Send Message Timeout";
+    @VisibleForTesting
+    public static final String SOFT_AP_SEND_MESSAGE_IDLE_IN_BRIDGED_MODE_TIMEOUT_TAG = TAG
+            + " Soft AP Send Message Bridged Mode Idle Timeout";
+
 
     private final WifiContext mContext;
     private final FrameworkFacade mFrameworkFacade;
     private final WifiNative mWifiNative;
+    // This will only be null if SdkLevel is not at least S
+    @Nullable private final CoexManager mCoexManager;
+    private final ClientModeImplMonitor mCmiMonitor;
+    private final ActiveModeWarden mActiveModeWarden;
+    private final SoftApNotifier mSoftApNotifier;
 
     @VisibleForTesting
-    SoftApNotifier mSoftApNotifier;
+    static final long SOFT_AP_PENDING_DISCONNECTION_CHECK_DELAY_MS = 1000;
 
-    private final String mCountryCode;
+    private String mCountryCode;
 
     private final SoftApStateMachine mStateMachine;
 
-    private final Listener mModeListener;
-    private final WifiManager.SoftApCallback mSoftApCallback;
+    private final Listener<SoftApManager> mModeListener;
+    private final WifiServiceImpl.SoftApCallbackInternal mSoftApCallback;
 
     private String mApInterfaceName;
     private boolean mIfaceIsUp;
@@ -96,33 +113,62 @@
     private final WifiApConfigStore mWifiApConfigStore;
 
     private final WifiMetrics mWifiMetrics;
+    private final long mId;
 
-    private boolean mIsRandomizeBssid;
+    private boolean mIsUnsetBssid;
+
+    private boolean mVerboseLoggingEnabled = false;
+
+    /**
+     * Original configuration, which is the passed configuration when init or
+     * the user-configured {@code WifiApConfigStore#getApConfiguration}tethering}
+     * settings when input is null.
+     *
+     * Use it when doing configuration update to know if the input configuration was changed.
+     * For others use case, it should use {@code mCurrentSoftApConfiguration}.
+     */
+    @NonNull
+    private final SoftApModeConfiguration mOriginalModeConfiguration;
+
+
+    /**
+     * Current Soft AP configuration which is used to start Soft AP.
+     * The configuration may be changed because
+     * 1. bssid is changed because MAC randomization
+     * 2. bands are changed because fallback to single AP mode mechanism.
+     */
+    @Nullable
+    private SoftApConfiguration mCurrentSoftApConfiguration;
 
     @NonNull
-    private SoftApModeConfiguration mApConfig;
-
-    @NonNull
-    private SoftApInfo mCurrentSoftApInfo = new SoftApInfo();
+    private Map<String, SoftApInfo> mCurrentSoftApInfoMap = new HashMap<>();
 
     @NonNull
     private SoftApCapability mCurrentSoftApCapability;
 
-    private List<WifiClient> mConnectedClients = new ArrayList<>();
+    private Map<String, List<WifiClient>> mConnectedClientWithApInfoMap = new HashMap<>();
+    @VisibleForTesting
+    Map<WifiClient, Integer> mPendingDisconnectClients = new HashMap<>();
+
     private boolean mTimeoutEnabled = false;
+    private boolean mBridgedModeOpportunisticsShutdownTimeoutEnabled = false;
 
     private final SarManager mSarManager;
 
     private String mStartTimestamp;
 
-    private long mDefaultShutDownTimeoutMills;
+    private long mDefaultShutdownTimeoutMillis;
+
+    private long mDefaultShutdownIdleInstanceInBridgedModeTimeoutMillis;
 
     private static final SimpleDateFormat FORMATTER = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
 
-    private BaseWifiDiagnostics mWifiDiagnostics;
+    private WifiDiagnostics mWifiDiagnostics;
 
-    private @Role int mRole = ROLE_UNSPECIFIED;
-    private @Role int mTargetRole = ROLE_UNSPECIFIED;
+    @Nullable
+    private SoftApRole mRole = null;
+    @Nullable
+    private WorkSource mRequestorWs = null;
 
     private boolean mEverReportMetricsForMaxClient = false;
 
@@ -132,86 +178,205 @@
     @NonNull
     private Set<MacAddress> mAllowedClientList = new HashSet<>();
 
+    @NonNull
+    private Set<Integer> mSafeChannelFrequencyList = new HashSet<>();
+
+    @VisibleForTesting
+    public WakeupMessage mSoftApTimeoutMessage;
+    @VisibleForTesting
+    public WakeupMessage mSoftApBridgedModeIdleInstanceTimeoutMessage;
+
+    // Internal flag which is used to avoid the timer re-schedule.
+    private boolean mIsBridgedModeIdleInstanceTimerActive = false;
+
     /**
      * Listener for soft AP events.
      */
     private final SoftApListener mSoftApListener = new SoftApListener() {
-
         @Override
         public void onFailure() {
             mStateMachine.sendMessage(SoftApStateMachine.CMD_FAILURE);
         }
 
         @Override
-        public void onConnectedClientsChanged(NativeWifiClient client, boolean isConnected) {
-            if (client != null) {
-                mStateMachine.sendMessage(SoftApStateMachine.CMD_ASSOCIATED_STATIONS_CHANGED,
-                        isConnected ? 1 : 0, 0, client);
-            } else {
-                Log.e(TAG, "onConnectedClientsChanged: Invalid type returned");
+        public void onInfoChanged(String apIfaceInstance, int frequency,
+                @WifiAnnotations.Bandwidth int bandwidth,
+                @WifiAnnotations.WifiStandard int generation,
+                MacAddress apIfaceInstanceMacAddress) {
+            SoftApInfo apInfo = new SoftApInfo();
+            apInfo.setFrequency(frequency);
+            apInfo.setBandwidth(bandwidth);
+            apInfo.setWifiStandard(generation);
+            if (apIfaceInstanceMacAddress != null) {
+                apInfo.setBssid(apIfaceInstanceMacAddress);
             }
+            apInfo.setApInstanceIdentifier(apIfaceInstance != null
+                    ? apIfaceInstance : mApInterfaceName);
+            mStateMachine.sendMessage(
+                    SoftApStateMachine.CMD_AP_INFO_CHANGED, 0, 0, apInfo);
         }
 
         @Override
-        public void onSoftApChannelSwitched(int frequency,
-                @WifiAnnotations.Bandwidth int bandwidth) {
-            mStateMachine.sendMessage(
-                    SoftApStateMachine.CMD_SOFT_AP_CHANNEL_SWITCHED, frequency, bandwidth);
+        public void onConnectedClientsChanged(String apIfaceInstance, MacAddress clientAddress,
+                boolean isConnected) {
+            if (clientAddress != null) {
+                WifiClient client = new WifiClient(clientAddress, apIfaceInstance != null
+                        ? apIfaceInstance : mApInterfaceName);
+                mStateMachine.sendMessage(SoftApStateMachine.CMD_ASSOCIATED_STATIONS_CHANGED,
+                        isConnected ? 1 : 0, 0, client);
+            } else {
+                Log.e(getTag(), "onConnectedClientsChanged: Invalid type returned");
+            }
         }
     };
 
-    public SoftApManager(@NonNull WifiContext context,
-                         @NonNull Looper looper,
-                         @NonNull FrameworkFacade framework,
-                         @NonNull WifiNative wifiNative,
-                         String countryCode,
-                         @NonNull Listener listener,
-                         @NonNull WifiManager.SoftApCallback callback,
-                         @NonNull WifiApConfigStore wifiApConfigStore,
-                         @NonNull SoftApModeConfiguration apConfig,
-                         @NonNull WifiMetrics wifiMetrics,
-                         @NonNull SarManager sarManager,
-                         @NonNull BaseWifiDiagnostics wifiDiagnostics) {
+    private final CoexListener mCoexListener = new CoexListener() {
+        @Override
+        public void onCoexUnsafeChannelsChanged() {
+            if (mCurrentSoftApConfiguration == null) {
+                return;
+            }
+            mStateMachine.sendMessage(SoftApStateMachine.CMD_SAFE_CHANNEL_FREQUENCY_CHANGED);
+        }
+    };
+
+    private void updateSafeChannelFrequencyList() {
+        if (!SdkLevel.isAtLeastS() || mCurrentSoftApConfiguration == null) {
+            return;
+        }
+        mSafeChannelFrequencyList.clear();
+        for (int configuredBand : mCurrentSoftApConfiguration.getBands()) {
+            for (int band : SoftApConfiguration.BAND_TYPES) {
+                if ((band & configuredBand) == 0) {
+                    continue;
+                }
+                for (int channel : mCurrentSoftApCapability.getSupportedChannelList(band)) {
+                    mSafeChannelFrequencyList.add(
+                            ApConfigUtil.convertChannelToFrequency(channel, band));
+                }
+            }
+        }
+        if ((mCoexManager.getCoexRestrictions() & WifiManager.COEX_RESTRICTION_SOFTAP) != 0) {
+            mSafeChannelFrequencyList.removeAll(
+                    ApConfigUtil.getUnsafeChannelFreqsFromCoex(mCoexManager));
+        }
+        Log.d(getTag(), "SafeChannelFrequencyList = " + mSafeChannelFrequencyList);
+    }
+
+    private void configureInternalConfiguration() {
+        if (mCurrentSoftApConfiguration == null) {
+            return;
+        }
+        mBlockedClientList = new HashSet<>(mCurrentSoftApConfiguration.getBlockedClientList());
+        mAllowedClientList = new HashSet<>(mCurrentSoftApConfiguration.getAllowedClientList());
+        mTimeoutEnabled = mCurrentSoftApConfiguration.isAutoShutdownEnabled();
+        mBridgedModeOpportunisticsShutdownTimeoutEnabled =
+                mCurrentSoftApConfiguration.isBridgedModeOpportunisticShutdownEnabledInternal();
+    }
+
+    private void updateChangeableConfiguration(SoftApConfiguration newConfig) {
+        if (mCurrentSoftApConfiguration == null || newConfig == null) {
+            return;
+        }
+        /**
+         * update configurations only which mentioned in WifiManager#setSoftApConfiguration
+         */
+        SoftApConfiguration.Builder newConfigurBuilder =
+                new SoftApConfiguration.Builder(mCurrentSoftApConfiguration)
+                .setAllowedClientList(newConfig.getAllowedClientList())
+                .setBlockedClientList(newConfig.getBlockedClientList())
+                .setClientControlByUserEnabled(newConfig.isClientControlByUserEnabled())
+                .setMaxNumberOfClients(newConfig.getMaxNumberOfClients())
+                .setShutdownTimeoutMillis(newConfig.getShutdownTimeoutMillis())
+                .setAutoShutdownEnabled(newConfig.isAutoShutdownEnabled());
+        if (SdkLevel.isAtLeastS()) {
+            newConfigurBuilder.setBridgedModeOpportunisticShutdownEnabled(
+                    newConfig.isBridgedModeOpportunisticShutdownEnabledInternal());
+        }
+        mCurrentSoftApConfiguration = newConfigurBuilder.build();
+        configureInternalConfiguration();
+    }
+
+    public SoftApManager(
+            @NonNull WifiContext context,
+            @NonNull Looper looper,
+            @NonNull FrameworkFacade framework,
+            @NonNull WifiNative wifiNative,
+            @NonNull CoexManager coexManager,
+            String countryCode,
+            @NonNull Listener<SoftApManager> listener,
+            @NonNull WifiServiceImpl.SoftApCallbackInternal callback,
+            @NonNull WifiApConfigStore wifiApConfigStore,
+            @NonNull SoftApModeConfiguration apConfig,
+            @NonNull WifiMetrics wifiMetrics,
+            @NonNull SarManager sarManager,
+            @NonNull WifiDiagnostics wifiDiagnostics,
+            @NonNull SoftApNotifier softApNotifier,
+            @NonNull ClientModeImplMonitor cmiMonitor,
+            @NonNull ActiveModeWarden activeModeWarden,
+            long id,
+            @NonNull WorkSource requestorWs,
+            @NonNull SoftApRole role,
+            boolean verboseLoggingEnabled) {
         mContext = context;
         mFrameworkFacade = framework;
-        mSoftApNotifier = new SoftApNotifier(mContext, mFrameworkFacade);
+        mSoftApNotifier = softApNotifier;
         mWifiNative = wifiNative;
+        mCoexManager = coexManager;
         mCountryCode = countryCode;
         mModeListener = listener;
         mSoftApCallback = callback;
         mWifiApConfigStore = wifiApConfigStore;
-        SoftApConfiguration softApConfig = apConfig.getSoftApConfiguration();
+        mCurrentSoftApConfiguration = apConfig.getSoftApConfiguration();
         mCurrentSoftApCapability = apConfig.getCapability();
         // null is a valid input and means we use the user-configured tethering settings.
-        if (softApConfig == null) {
-            softApConfig = mWifiApConfigStore.getApConfiguration();
+        if (mCurrentSoftApConfiguration == null) {
+            mCurrentSoftApConfiguration = mWifiApConfigStore.getApConfiguration();
             // may still be null if we fail to load the default config
         }
-        if (softApConfig != null) {
-            mIsRandomizeBssid = softApConfig.getBssid() == null;
-            softApConfig = mWifiApConfigStore.randomizeBssidIfUnset(mContext, softApConfig);
+        // Store mode configuration before update the configuration.
+        mOriginalModeConfiguration = new SoftApModeConfiguration(apConfig.getTargetMode(),
+                mCurrentSoftApConfiguration, mCurrentSoftApCapability);
+        if (mCurrentSoftApConfiguration != null) {
+            mIsUnsetBssid = mCurrentSoftApConfiguration.getBssid() == null;
+            if (mCurrentSoftApCapability.areFeaturesSupported(
+                    SoftApCapability.SOFTAP_FEATURE_MAC_ADDRESS_CUSTOMIZATION)) {
+                mCurrentSoftApConfiguration = mWifiApConfigStore.randomizeBssidIfUnset(
+                        mContext, mCurrentSoftApConfiguration);
+            }
         }
-        mApConfig = new SoftApModeConfiguration(apConfig.getTargetMode(),
-                softApConfig, mCurrentSoftApCapability);
         mWifiMetrics = wifiMetrics;
         mSarManager = sarManager;
         mWifiDiagnostics = wifiDiagnostics;
         mStateMachine = new SoftApStateMachine(looper);
-        if (softApConfig != null) {
-            mBlockedClientList = new HashSet<>(softApConfig.getBlockedClientList());
-            mAllowedClientList = new HashSet<>(softApConfig.getAllowedClientList());
-            mTimeoutEnabled = softApConfig.isAutoShutdownEnabled();
-        }
-        mDefaultShutDownTimeoutMills = mContext.getResources().getInteger(
+        configureInternalConfiguration();
+        mDefaultShutdownTimeoutMillis = mContext.getResources().getInteger(
                 R.integer.config_wifiFrameworkSoftApShutDownTimeoutMilliseconds);
+        mDefaultShutdownIdleInstanceInBridgedModeTimeoutMillis = mContext.getResources().getInteger(
+                R.integer
+                .config_wifiFrameworkSoftApShutDownIdleInstanceInBridgedModeTimeoutMillisecond);
+        mCmiMonitor = cmiMonitor;
+        mActiveModeWarden = activeModeWarden;
+        mCmiMonitor.registerListener(new ClientModeImplListener() {
+            @Override
+            public void onL2Connected(@NonNull ConcreteClientModeManager clientModeManager) {
+                SoftApManager.this.onL2Connected(clientModeManager);
+            }
+        });
+        updateSafeChannelFrequencyList();
+        mId = id;
+        mRole = role;
+        enableVerboseLogging(verboseLoggingEnabled);
+        mStateMachine.sendMessage(SoftApStateMachine.CMD_START, requestorWs);
     }
 
-    /**
-     * Start soft AP, as configured in the constructor.
-     */
     @Override
-    public void start() {
-        mStateMachine.sendMessage(SoftApStateMachine.CMD_START);
+    public long getId() {
+        return mId;
+    }
+
+    private String getTag() {
+        return TAG + "[" + (mApInterfaceName == null ? "unknown" : mApInterfaceName) + "]";
     }
 
     /**
@@ -219,30 +384,52 @@
      */
     @Override
     public void stop() {
-        Log.d(TAG, " currentstate: " + getCurrentStateName());
-        mTargetRole = ROLE_UNSPECIFIED;
+        Log.d(getTag(), " currentstate: " + getCurrentStateName());
         mStateMachine.sendMessage(SoftApStateMachine.CMD_STOP);
     }
 
-    @Override
-    public boolean isStopping() {
-        return mTargetRole == ROLE_UNSPECIFIED && mRole != ROLE_UNSPECIFIED;
+    private boolean isBridgedMode() {
+        return (SdkLevel.isAtLeastS() && mCurrentSoftApConfiguration != null
+                && mCurrentSoftApConfiguration.getBands().length > 1);
+    }
+
+    private long getShutdownTimeoutMillis() {
+        long timeout = mCurrentSoftApConfiguration.getShutdownTimeoutMillis();
+        return timeout > 0 ? timeout : mDefaultShutdownTimeoutMillis;
     }
 
     @Override
-    public @Role int getRole() {
+    @Nullable public SoftApRole getRole() {
         return mRole;
     }
 
     @Override
-    public void setRole(@Role int role) {
+    @Nullable public ClientRole getPreviousRole() {
+        return null;
+    }
+
+    @Override
+    public long getLastRoleChangeSinceBootMs() {
+        return 0;
+    }
+
+    /** Set the role of this SoftApManager */
+    public void setRole(SoftApRole role) {
         // softap does not allow in-place switching of roles.
-        Preconditions.checkState(mRole == ROLE_UNSPECIFIED);
-        Preconditions.checkState(SOFTAP_ROLES.contains(role));
-        mTargetRole = role;
+        Preconditions.checkState(mRole == null);
         mRole = role;
     }
 
+    @Override
+    public String getInterfaceName() {
+        return mApInterfaceName;
+    }
+
+    @Override
+    public WorkSource getRequestorWs() {
+        return mRequestorWs;
+    }
+
     /**
      * Update AP capability. Called when carrier config or device resouce config changed.
      *
@@ -263,29 +450,66 @@
     }
 
     /**
+     * Retrieve the {@link SoftApModeConfiguration} instance associated with this mode manager.
+     */
+    public SoftApModeConfiguration getSoftApModeConfiguration() {
+        return new SoftApModeConfiguration(mOriginalModeConfiguration.getTargetMode(),
+                mCurrentSoftApConfiguration, mCurrentSoftApCapability);
+    }
+
+    /**
      * Dump info about this softap manager.
      */
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("--Dump of SoftApManager--");
+        pw.println("Dump of SoftApManager id=" + mId);
 
         pw.println("current StateMachine mode: " + getCurrentStateName());
         pw.println("mRole: " + mRole);
         pw.println("mApInterfaceName: " + mApInterfaceName);
         pw.println("mIfaceIsUp: " + mIfaceIsUp);
         pw.println("mSoftApCountryCode: " + mCountryCode);
-        pw.println("mApConfig.targetMode: " + mApConfig.getTargetMode());
-        SoftApConfiguration softApConfig = mApConfig.getSoftApConfiguration();
-        pw.println("mApConfig.SoftApConfiguration.SSID: " + softApConfig.getSsid());
-        pw.println("mApConfig.SoftApConfiguration.mBand: " + softApConfig.getBand());
-        pw.println("mApConfig.SoftApConfiguration.hiddenSSID: " + softApConfig.isHiddenSsid());
-        pw.println("mConnectedClients.size(): " + mConnectedClients.size());
+        pw.println("mOriginalModeConfiguration.targetMode: "
+                + mOriginalModeConfiguration.getTargetMode());
+        pw.println("mCurrentSoftApConfiguration.SSID: " + mCurrentSoftApConfiguration.getSsid());
+        pw.println("mCurrentSoftApConfiguration.mBand: " + mCurrentSoftApConfiguration.getBand());
+        pw.println("mCurrentSoftApConfiguration.hiddenSSID: "
+                + mCurrentSoftApConfiguration.isHiddenSsid());
+        pw.println("getConnectedClientList().size(): " + getConnectedClientList().size());
         pw.println("mTimeoutEnabled: " + mTimeoutEnabled);
-        pw.println("mCurrentSoftApInfo " + mCurrentSoftApInfo);
+        pw.println("mBridgedModeOpportunisticsShutdownTimeoutEnabled: "
+                + mBridgedModeOpportunisticsShutdownTimeoutEnabled);
+        pw.println("mCurrentSoftApInfoMap " + mCurrentSoftApInfoMap);
         pw.println("mStartTimestamp: " + mStartTimestamp);
         mStateMachine.dump(fd, pw, args);
     }
 
+    @Override
+    public void enableVerboseLogging(boolean verbose) {
+        mVerboseLoggingEnabled = verbose;
+    }
+
+    @Override
+    public String toString() {
+        return "SoftApManager{id=" + getId()
+                + " iface=" + getInterfaceName()
+                + " role=" + getRole()
+                + "}";
+    }
+
+    /**
+     * A ClientModeImpl instance has been L2 connected.
+     *
+     * @param newPrimary the corresponding ConcreteClientModeManager instance for the ClientModeImpl
+     *                   that has been L2 connected.
+     */
+    private void onL2Connected(@NonNull ConcreteClientModeManager clientModeManager) {
+        Log.d(getTag(), "onL2Connected called");
+        mStateMachine.sendMessage(SoftApStateMachine.CMD_HANDLE_WIFI_CONNECTED,
+                clientModeManager.syncRequestConnectionInfo());
+    }
+
+
     private String getCurrentStateName() {
         IState currentState = mStateMachine.getCurrentState();
 
@@ -317,60 +541,70 @@
         }
 
         intent.putExtra(WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME, mApInterfaceName);
-        intent.putExtra(WifiManager.EXTRA_WIFI_AP_MODE, mApConfig.getTargetMode());
+        intent.putExtra(WifiManager.EXTRA_WIFI_AP_MODE, mOriginalModeConfiguration.getTargetMode());
         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
     private int setMacAddress() {
-        MacAddress mac = mApConfig.getSoftApConfiguration().getBssid();
+        MacAddress mac = mCurrentSoftApConfiguration.getBssid();
 
         if (mac == null) {
             // If no BSSID is explicitly requested, (re-)configure the factory MAC address. Some
             // drivers may not support setting the MAC at all, so fail soft in this case.
-            mac = mWifiNative.getFactoryMacAddress(mApInterfaceName);
-            if (mac == null) {
-                Log.e(TAG, "failed to get factory MAC address");
-                return ERROR_GENERIC;
+            if (!mWifiNative.resetApMacToFactoryMacAddress(mApInterfaceName)) {
+                Log.w(getTag(), "failed to reset to factory MAC address; "
+                        + "continuing with current MAC");
             }
-
-            if (!mWifiNative.setMacAddress(mApInterfaceName, mac)) {
-                Log.w(TAG, "failed to reset to factory MAC address; continuing with current MAC");
+        } else {
+            if (mWifiNative.isApSetMacAddressSupported(mApInterfaceName)) {
+                if (!mWifiNative.setApMacAddress(mApInterfaceName, mac)) {
+                    Log.e(getTag(), "failed to set explicitly requested MAC address");
+                    return ERROR_GENERIC;
+                }
+            } else if (!mIsUnsetBssid) {
+                // If hardware does not support MAC address setter,
+                // only report the error for non randomization.
+                return ERROR_UNSUPPORTED_CONFIGURATION;
             }
-            return SUCCESS;
         }
 
-
-        if (mWifiNative.isSetMacAddressSupported(mApInterfaceName)) {
-            if (!mWifiNative.setMacAddress(mApInterfaceName, mac)) {
-                Log.e(TAG, "failed to set explicitly requested MAC address");
-                return ERROR_GENERIC;
-            }
-        } else if (!mIsRandomizeBssid) {
-            // If hardware does not support MAC address setter,
-            // only report the error for non randomization.
-            return ERROR_UNSUPPORTED_CONFIGURATION;
-        }
         return SUCCESS;
     }
 
+    /**
+     * Dynamic update the country code when Soft AP enabled.
+     *
+     * @param countryCode 2 byte ASCII string. For ex: US, CA.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean updateCountryCode(@NonNull String countryCode) {
+        if (ApConfigUtil.isSoftApDynamicCountryCodeSupported(mContext)
+                && mCurrentSoftApCapability.areFeaturesSupported(
+                        SoftApCapability.SOFTAP_FEATURE_ACS_OFFLOAD)) {
+            mStateMachine.sendMessage(SoftApStateMachine.CMD_UPDATE_COUNTRY_CODE, countryCode);
+            return true;
+        }
+        return false;
+    }
+
     private int setCountryCode() {
-        int band = mApConfig.getSoftApConfiguration().getBand();
+        int band = mCurrentSoftApConfiguration.getBand();
         if (TextUtils.isEmpty(mCountryCode)) {
             if (band == SoftApConfiguration.BAND_5GHZ) {
                 // Country code is mandatory for 5GHz band.
-                Log.e(TAG, "Invalid country code, required for setting up soft ap in 5GHz");
+                Log.e(getTag(), "Invalid country code, required for setting up soft ap in 5GHz");
                 return ERROR_GENERIC;
             }
             // Absence of country code is not fatal for 2Ghz & Any band options.
             return SUCCESS;
         }
-
-        if (!mWifiNative.setCountryCodeHal(
+        if (!mWifiNative.setApCountryCode(
                 mApInterfaceName, mCountryCode.toUpperCase(Locale.ROOT))) {
             if (band == SoftApConfiguration.BAND_5GHZ) {
                 // Return an error if failed to set country code when AP is configured for
                 // 5GHz band.
-                Log.e(TAG, "Failed to set country code, required for setting up soft ap in 5GHz");
+                Log.e(getTag(), "Failed to set country code, "
+                        + "required for setting up soft ap in 5GHz");
                 return ERROR_GENERIC;
             }
             // Failure to set country code is not fatal for other band options.
@@ -384,13 +618,7 @@
      * @return integer result code
      */
     private int startSoftAp() {
-        SoftApConfiguration config = mApConfig.getSoftApConfiguration();
-        if (config == null || config.getSsid() == null) {
-            Log.e(TAG, "Unable to start soft AP without valid configuration");
-            return ERROR_GENERIC;
-        }
-
-        Log.d(TAG, "band " + config.getBand() + " iface "
+        Log.d(getTag(), "band " + mCurrentSoftApConfiguration.getBand() + " iface "
                 + mApInterfaceName + " country " + mCountryCode);
 
         int result = setMacAddress();
@@ -404,37 +632,42 @@
         }
 
         // Make a copy of configuration for updating AP band and channel.
-        SoftApConfiguration.Builder localConfigBuilder = new SoftApConfiguration.Builder(config);
+        SoftApConfiguration.Builder localConfigBuilder =
+                new SoftApConfiguration.Builder(mCurrentSoftApConfiguration);
 
         boolean acsEnabled = mCurrentSoftApCapability.areFeaturesSupported(
                 SoftApCapability.SOFTAP_FEATURE_ACS_OFFLOAD);
 
         result = ApConfigUtil.updateApChannelConfig(
-                mWifiNative, mContext.getResources(), mCountryCode, localConfigBuilder, config,
-                acsEnabled);
+                mWifiNative, mCoexManager, mContext.getResources(), mCountryCode,
+                localConfigBuilder, mCurrentSoftApConfiguration, acsEnabled);
         if (result != SUCCESS) {
-            Log.e(TAG, "Failed to update AP band and channel");
+            Log.e(getTag(), "Failed to update AP band and channel");
             return result;
         }
 
-        if (config.isHiddenSsid()) {
-            Log.d(TAG, "SoftAP is a hidden network");
+        if (mCurrentSoftApConfiguration.isHiddenSsid()) {
+            Log.d(getTag(), "SoftAP is a hidden network");
         }
 
-        if (!ApConfigUtil.checkSupportAllConfiguration(config, mCurrentSoftApCapability)) {
-            Log.d(TAG, "Unsupported Configuration detect! config = " + config);
+        if (!ApConfigUtil.checkSupportAllConfiguration(
+                mCurrentSoftApConfiguration, mCurrentSoftApCapability)) {
+            Log.d(getTag(), "Unsupported Configuration detect! config = "
+                    + mCurrentSoftApConfiguration);
             return ERROR_UNSUPPORTED_CONFIGURATION;
         }
 
         if (!mWifiNative.startSoftAp(mApInterfaceName,
-                  localConfigBuilder.build(), mSoftApListener)) {
-            Log.e(TAG, "Soft AP start failed");
+                  localConfigBuilder.build(),
+                  mOriginalModeConfiguration.getTargetMode() ==  WifiManager.IFACE_IP_MODE_TETHERED,
+                  mSoftApListener)) {
+            Log.e(getTag(), "Soft AP start failed");
             return ERROR_GENERIC;
         }
 
         mWifiDiagnostics.startLogging(mApInterfaceName);
         mStartTimestamp = FORMATTER.format(new Date(System.currentTimeMillis()));
-        Log.d(TAG, "Soft AP is started ");
+        Log.d(getTag(), "Soft AP is started ");
 
         return SUCCESS;
     }
@@ -444,7 +677,7 @@
      * This is usually done just before stopSoftAp().
      */
     private void disconnectAllClients() {
-        for (WifiClient client : mConnectedClients) {
+        for (WifiClient client : getConnectedClientList()) {
             mWifiNative.forceClientDisconnect(mApInterfaceName, client.getMacAddress(),
                     SAP_CLIENT_DISCONNECT_REASON_CODE_UNSPECIFIED);
         }
@@ -457,7 +690,26 @@
         disconnectAllClients();
         mWifiDiagnostics.stopLogging(mApInterfaceName);
         mWifiNative.teardownInterface(mApInterfaceName);
-        Log.d(TAG, "Soft AP is stopped");
+        Log.d(getTag(), "Soft AP is stopped");
+    }
+
+    private void addClientToPendingDisconnectionList(WifiClient client, int reason) {
+        Log.d(getTag(), "Fail to disconnect client: " + client.getMacAddress()
+                + ", add it into pending list");
+        mPendingDisconnectClients.put(client, reason);
+        mStateMachine.getHandler().removeMessages(
+                SoftApStateMachine.CMD_FORCE_DISCONNECT_PENDING_CLIENTS);
+        mStateMachine.sendMessageDelayed(
+                SoftApStateMachine.CMD_FORCE_DISCONNECT_PENDING_CLIENTS,
+                SOFT_AP_PENDING_DISCONNECTION_CHECK_DELAY_MS);
+    }
+
+    private List<WifiClient> getConnectedClientList() {
+        List<WifiClient> connectedClientList = new ArrayList<>();
+        for (List<WifiClient> it : mConnectedClientWithApInfoMap.values()) {
+            connectedClientList.addAll(it);
+        }
+        return connectedClientList;
     }
 
     private boolean checkSoftApClient(SoftApConfiguration config, WifiClient newClient) {
@@ -467,20 +719,26 @@
         }
 
         if (mBlockedClientList.contains(newClient.getMacAddress())) {
-            Log.d(TAG, "Force disconnect for client: " + newClient + "in blocked list");
-            mWifiNative.forceClientDisconnect(
+            Log.d(getTag(), "Force disconnect for client: " + newClient + "in blocked list");
+            if (!mWifiNative.forceClientDisconnect(
                     mApInterfaceName, newClient.getMacAddress(),
-                    WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER);
+                    WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER)) {
+                addClientToPendingDisconnectionList(newClient,
+                        WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER);
+            }
             return false;
         }
         if (config.isClientControlByUserEnabled()
                 && !mAllowedClientList.contains(newClient.getMacAddress())) {
             mSoftApCallback.onBlockedClientConnecting(newClient,
                     WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER);
-            Log.d(TAG, "Force disconnect for unauthorized client: " + newClient);
-            mWifiNative.forceClientDisconnect(
+            Log.d(getTag(), "Force disconnect for unauthorized client: " + newClient);
+            if (!mWifiNative.forceClientDisconnect(
                     mApInterfaceName, newClient.getMacAddress(),
-                    WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER);
+                    WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER)) {
+                addClientToPendingDisconnectionList(newClient,
+                        WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER);
+            }
             return false;
         }
         int maxConfig = mCurrentSoftApCapability.getMaxSupportedClients();
@@ -488,11 +746,14 @@
             maxConfig = Math.min(maxConfig, config.getMaxNumberOfClients());
         }
 
-        if (mConnectedClients.size() >= maxConfig) {
-            Log.i(TAG, "No more room for new client:" + newClient);
-            mWifiNative.forceClientDisconnect(
+        if (getConnectedClientList().size() >= maxConfig) {
+            Log.i(getTag(), "No more room for new client:" + newClient);
+            if (!mWifiNative.forceClientDisconnect(
                     mApInterfaceName, newClient.getMacAddress(),
-                    WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS);
+                    WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS)) {
+                addClientToPendingDisconnectionList(newClient,
+                        WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS);
+            }
             mSoftApCallback.onBlockedClientConnecting(newClient,
                     WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS);
             // Avoid report the max client blocked in the same settings.
@@ -515,9 +776,14 @@
         public static final int CMD_NO_ASSOCIATED_STATIONS_TIMEOUT = 5;
         public static final int CMD_INTERFACE_DESTROYED = 7;
         public static final int CMD_INTERFACE_DOWN = 8;
-        public static final int CMD_SOFT_AP_CHANNEL_SWITCHED = 9;
+        public static final int CMD_AP_INFO_CHANGED = 9;
         public static final int CMD_UPDATE_CAPABILITY = 10;
         public static final int CMD_UPDATE_CONFIG = 11;
+        public static final int CMD_FORCE_DISCONNECT_PENDING_CLIENTS = 12;
+        public static final int CMD_NO_ASSOCIATED_STATIONS_TIMEOUT_ON_ONE_INSTANCE = 13;
+        public static final int CMD_SAFE_CHANNEL_FREQUENCY_CHANGED = 14;
+        public static final int CMD_HANDLE_WIFI_CONNECTED = 15;
+        public static final int CMD_UPDATE_COUNTRY_CODE = 16;
 
         private final State mIdleState = new IdleState();
         private final State mStartedState = new StartedState();
@@ -548,8 +814,10 @@
         SoftApStateMachine(Looper looper) {
             super(TAG, looper);
 
+            // CHECKSTYLE:OFF IndentationCheck
             addState(mIdleState);
-            addState(mStartedState);
+                addState(mStartedState, mIdleState);
+            // CHECKSTYLE:ON IndentationCheck
 
             setInitialState(mIdleState);
             start();
@@ -564,25 +832,79 @@
             }
 
             @Override
+            public void exit() {
+                mModeListener.onStopped(SoftApManager.this);
+            }
+
+            @Override
             public boolean processMessage(Message message) {
                 switch (message.what) {
                     case CMD_STOP:
                         mStateMachine.quitNow();
                         break;
                     case CMD_START:
-                        mApInterfaceName = mWifiNative.setupInterfaceForSoftApMode(
-                                mWifiNativeInterfaceCallback);
-                        if (TextUtils.isEmpty(mApInterfaceName)) {
-                            Log.e(TAG, "setup failure when creating ap interface.");
+                        mRequestorWs = (WorkSource) message.obj;
+                        if (mCurrentSoftApConfiguration == null
+                                || mCurrentSoftApConfiguration.getSsid() == null) {
+                            Log.e(getTag(), "Unable to start soft AP without valid configuration");
                             updateApState(WifiManager.WIFI_AP_STATE_FAILED,
                                     WifiManager.WIFI_AP_STATE_DISABLED,
                                     WifiManager.SAP_START_FAILURE_GENERAL);
                             mWifiMetrics.incrementSoftApStartResult(
                                     false, WifiManager.SAP_START_FAILURE_GENERAL);
-                            mModeListener.onStartFailure();
+                            mModeListener.onStartFailure(SoftApManager.this);
                             break;
                         }
-                        mSoftApNotifier.dismissSoftApShutDownTimeoutExpiredNotification();
+                        if (isBridgedMode()) {
+                            boolean isFallbackToSingleAp = false;
+                            int newSingleApBand = 0;
+                            for (ClientModeManager cmm
+                                    : mActiveModeWarden.getClientModeManagers()) {
+                                WifiInfo wifiConnectedInfo = cmm.syncRequestConnectionInfo();
+                                int wifiFrequency = wifiConnectedInfo.getFrequency();
+                                if (wifiFrequency > 0
+                                        && !mSafeChannelFrequencyList.contains(
+                                        wifiFrequency)) {
+                                    Log.d(getTag(), "Wifi connected to unavailable freq: "
+                                            + wifiFrequency);
+                                    isFallbackToSingleAp = true;
+                                    break;
+                                }
+                            }
+                            for (int configuredBand : mCurrentSoftApConfiguration.getBands()) {
+                                int availableBand = ApConfigUtil.removeUnavailableBands(
+                                        mCurrentSoftApCapability,
+                                        configuredBand, mCoexManager);
+                                if (configuredBand != availableBand) {
+                                    isFallbackToSingleAp = true;
+                                }
+                                newSingleApBand |= availableBand;
+                            }
+                            if (isFallbackToSingleAp) {
+                                newSingleApBand = ApConfigUtil.append24GToBandIf24GSupported(
+                                        newSingleApBand, mContext);
+                                Log.i(getTag(), "Fallback to single AP mode with band "
+                                        + newSingleApBand);
+                                mCurrentSoftApConfiguration =
+                                        new SoftApConfiguration.Builder(mCurrentSoftApConfiguration)
+                                        .setBand(newSingleApBand)
+                                        .build();
+                            }
+                        }
+                        mApInterfaceName = mWifiNative.setupInterfaceForSoftApMode(
+                                mWifiNativeInterfaceCallback, mRequestorWs,
+                                mCurrentSoftApConfiguration.getBand(), isBridgedMode());
+                        if (TextUtils.isEmpty(mApInterfaceName)) {
+                            Log.e(getTag(), "setup failure when creating ap interface.");
+                            updateApState(WifiManager.WIFI_AP_STATE_FAILED,
+                                    WifiManager.WIFI_AP_STATE_DISABLED,
+                                    WifiManager.SAP_START_FAILURE_GENERAL);
+                            mWifiMetrics.incrementSoftApStartResult(
+                                    false, WifiManager.SAP_START_FAILURE_GENERAL);
+                            mModeListener.onStartFailure(SoftApManager.this);
+                            break;
+                        }
+                        mSoftApNotifier.dismissSoftApShutdownTimeoutExpiredNotification();
                         updateApState(WifiManager.WIFI_AP_STATE_ENABLING,
                                 WifiManager.WIFI_AP_STATE_DISABLED, 0);
                         int result = startSoftAp();
@@ -599,7 +921,7 @@
                                     failureReason);
                             stopSoftAp();
                             mWifiMetrics.incrementSoftApStartResult(false, failureReason);
-                            mModeListener.onStartFailure();
+                            mModeListener.onStartFailure(SoftApManager.this);
                             break;
                         }
                         transitionTo(mStartedState);
@@ -607,19 +929,24 @@
                     case CMD_UPDATE_CAPABILITY:
                         // Capability should only changed by carrier requirement. Only apply to
                         // Tether Mode
-                        if (mApConfig.getTargetMode() ==  WifiManager.IFACE_IP_MODE_TETHERED) {
+                        if (mOriginalModeConfiguration.getTargetMode()
+                                ==  WifiManager.IFACE_IP_MODE_TETHERED) {
                             SoftApCapability capability = (SoftApCapability) message.obj;
                             mCurrentSoftApCapability = new SoftApCapability(capability);
                         }
                         break;
                     case CMD_UPDATE_CONFIG:
                         SoftApConfiguration newConfig = (SoftApConfiguration) message.obj;
-                        Log.d(TAG, "Configuration changed to " + newConfig);
-                        mApConfig = new SoftApModeConfiguration(mApConfig.getTargetMode(),
-                                newConfig, mCurrentSoftApCapability);
-                        mBlockedClientList = new HashSet<>(newConfig.getBlockedClientList());
-                        mAllowedClientList = new HashSet<>(newConfig.getAllowedClientList());
-                        mTimeoutEnabled = newConfig.isAutoShutdownEnabled();
+                        Log.d(getTag(), "Configuration changed to " + newConfig);
+                        // Idle mode, update all configurations.
+                        mCurrentSoftApConfiguration = newConfig;
+                        configureInternalConfiguration();
+                        break;
+                    case CMD_UPDATE_COUNTRY_CODE:
+                        String countryCode = (String) message.obj;
+                        if (!TextUtils.isEmpty(countryCode)) {
+                            mCountryCode = countryCode;
+                        }
                         break;
                     default:
                         // Ignore all other commands.
@@ -631,26 +958,92 @@
         }
 
         private class StartedState extends State {
-            private WakeupMessage mSoftApTimeoutMessage;
-
-            private void scheduleTimeoutMessage() {
-                if (!mTimeoutEnabled || mConnectedClients.size() != 0) {
+            private void scheduleTimeoutMessages() {
+                // When SAP started, the mCurrentSoftApInfoMap is 0 because info does not update.
+                // Don't trigger bridged mode shutdown timeout when only one active instance
+                // In Dual AP, one instance may already be closed due to LTE coexistence or DFS
+                // restrictions or due to inactivity. i.e. mCurrentSoftApInfoMap.size() is 1)
+                final int connectedClients = getConnectedClientList().size();
+                if (isBridgedMode() && mCurrentSoftApInfoMap.size() != 1) {
+                    if (mBridgedModeOpportunisticsShutdownTimeoutEnabled
+                            && (connectedClients == 0 || getIdleInstances().size() != 0)) {
+                        if (!mIsBridgedModeIdleInstanceTimerActive) {
+                            mSoftApBridgedModeIdleInstanceTimeoutMessage.schedule(SystemClock
+                                    .elapsedRealtime()
+                                    + mDefaultShutdownIdleInstanceInBridgedModeTimeoutMillis);
+                            mIsBridgedModeIdleInstanceTimerActive = true;
+                            Log.d(getTag(), "Bridged mode instance opportunistic timeout message"
+                                    + " scheduled, delay = "
+                                    + mDefaultShutdownIdleInstanceInBridgedModeTimeoutMillis);
+                        }
+                    } else {
+                        cancelBridgedModeIdleInstanceTimeoutMessage();
+                    }
+                }
+                if (!mTimeoutEnabled || connectedClients != 0) {
                     cancelTimeoutMessage();
                     return;
                 }
-                long timeout = mApConfig.getSoftApConfiguration().getShutdownTimeoutMillis();
-                if (timeout == 0) {
-                    timeout =  mDefaultShutDownTimeoutMills;
-                }
+                long timeout = getShutdownTimeoutMillis();
                 mSoftApTimeoutMessage.schedule(SystemClock.elapsedRealtime()
                         + timeout);
-                Log.d(TAG, "Timeout message scheduled, delay = "
+                Log.d(getTag(), "Timeout message scheduled, delay = "
                         + timeout);
             }
 
+            private String getHighestFrequencyInstance(Set<String> candidateInstances) {
+                int currentHighestFrequencyOnAP = 0;
+                String highestFrequencyInstance = null;
+                for (String instance : candidateInstances) {
+                    SoftApInfo info = mCurrentSoftApInfoMap.get(instance);
+                    if (info == null) {
+                        Log.wtf(getTag(), "Invalid instance name, no way to get the frequency");
+                        return "";
+                    }
+                    int frequencyOnInstance = info.getFrequency();
+                    if (frequencyOnInstance > currentHighestFrequencyOnAP) {
+                        currentHighestFrequencyOnAP = frequencyOnInstance;
+                        highestFrequencyInstance = instance;
+                    }
+                }
+                return highestFrequencyInstance;
+            }
+
+            private void removeIfaceInstanceFromBridgedApIface(String instanceName) {
+                if (TextUtils.isEmpty(instanceName)) {
+                    return;
+                }
+                if (mCurrentSoftApInfoMap.containsKey(instanceName)) {
+                    Log.i(getTag(), "remove instance " + instanceName + "("
+                            + mCurrentSoftApInfoMap.get(instanceName).getFrequency()
+                            + ") from bridged iface " + mApInterfaceName);
+                    mWifiNative.removeIfaceInstanceFromBridgedApIface(mApInterfaceName,
+                            instanceName);
+                    // Remove the info and update it.
+                    updateSoftApInfo(mCurrentSoftApInfoMap.get(instanceName), true);
+                }
+            }
+
+            private Set<String> getIdleInstances() {
+                Set<String> idleInstances = new HashSet<String>();
+                for (String instance : mConnectedClientWithApInfoMap.keySet()) {
+                    if (mConnectedClientWithApInfoMap.getOrDefault(
+                            instance, Collections.emptyList()).size() == 0) {
+                        idleInstances.add(instance);
+                    }
+                }
+                return idleInstances;
+            }
+
             private void cancelTimeoutMessage() {
                 mSoftApTimeoutMessage.cancel();
-                Log.d(TAG, "Timeout message canceled");
+                Log.d(getTag(), "Timeout message canceled");
+            }
+
+            private void cancelBridgedModeIdleInstanceTimeoutMessage() {
+                mSoftApBridgedModeIdleInstanceTimeoutMessage.cancel();
+                mIsBridgedModeIdleInstanceTimerActive = false;
+                Log.d(getTag(), "Bridged mode idle instance timeout message canceled");
             }
 
             /**
@@ -665,24 +1058,28 @@
                 final int maxAllowedClientsByHardwareAndCarrier =
                         mCurrentSoftApCapability.getMaxSupportedClients();
                 final int userApConfigMaxClientCount =
-                        mApConfig.getSoftApConfiguration().getMaxNumberOfClients();
+                        mCurrentSoftApConfiguration.getMaxNumberOfClients();
                 int finalMaxClientCount = maxAllowedClientsByHardwareAndCarrier;
                 if (userApConfigMaxClientCount > 0) {
                     finalMaxClientCount = Math.min(userApConfigMaxClientCount,
                             maxAllowedClientsByHardwareAndCarrier);
                 }
-                int targetDisconnectClientNumber = mConnectedClients.size() - finalMaxClientCount;
+                List<WifiClient> currentClients = getConnectedClientList();
+                int targetDisconnectClientNumber = currentClients.size() - finalMaxClientCount;
                 List<WifiClient> allowedConnectedList = new ArrayList<>();
-                Iterator<WifiClient> iterator = mConnectedClients.iterator();
+                Iterator<WifiClient> iterator = currentClients.iterator();
                 while (iterator.hasNext()) {
                     WifiClient client = iterator.next();
                     if (mBlockedClientList.contains(client.getMacAddress())
-                              || (mApConfig.getSoftApConfiguration().isClientControlByUserEnabled()
+                              || (mCurrentSoftApConfiguration.isClientControlByUserEnabled()
                               && !mAllowedClientList.contains(client.getMacAddress()))) {
-                        Log.d(TAG, "Force disconnect for not allowed client: " + client);
-                        mWifiNative.forceClientDisconnect(
+                        Log.d(getTag(), "Force disconnect for not allowed client: " + client);
+                        if (!mWifiNative.forceClientDisconnect(
                                 mApInterfaceName, client.getMacAddress(),
-                                WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER);
+                                WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER)) {
+                            addClientToPendingDisconnectionList(client,
+                                    WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER);
+                        }
                         targetDisconnectClientNumber--;
                     } else {
                         allowedConnectedList.add(client);
@@ -694,11 +1091,14 @@
                     while (allowedClientIterator.hasNext()) {
                         if (targetDisconnectClientNumber == 0) break;
                         WifiClient allowedClient = allowedClientIterator.next();
-                        Log.d(TAG, "Force disconnect for client due to no more room: "
+                        Log.d(getTag(), "Force disconnect for client due to no more room: "
                                 + allowedClient);
-                        mWifiNative.forceClientDisconnect(
+                        if (!mWifiNative.forceClientDisconnect(
                                 mApInterfaceName, allowedClient.getMacAddress(),
-                                WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS);
+                                WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS)) {
+                            addClientToPendingDisconnectionList(allowedClient,
+                                    WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS);
+                        }
                         targetDisconnectClientNumber--;
                     }
                 }
@@ -714,60 +1114,123 @@
                     return;
                 }
 
-                int index = mConnectedClients.indexOf(client);
+                if (null != mPendingDisconnectClients.remove(client)) {
+                    Log.d(getTag(), "Remove client: " + client.getMacAddress()
+                            + "from pending disconnectionlist");
+                }
+
+                String apInstanceIdentifier = client.getApInstanceIdentifier();
+                List clientList = mConnectedClientWithApInfoMap.computeIfAbsent(
+                        apInstanceIdentifier, k -> new ArrayList<>());
+                int index = clientList.indexOf(client);
+
                 if ((index != -1) == isConnected) {
-                    Log.e(TAG, "Drop client connection event, client "
+                    Log.e(getTag(), "Drop client connection event, client "
                             + client + "isConnected: " + isConnected
                             + " , duplicate event or client is blocked");
                     return;
                 }
                 if (isConnected) {
-                    boolean isAllow = checkSoftApClient(
-                            mApConfig.getSoftApConfiguration(), client);
+                    boolean isAllow = checkSoftApClient(mCurrentSoftApConfiguration, client);
                     if (isAllow) {
-                        mConnectedClients.add(client);
+                        clientList.add(client);
                     } else {
                         return;
                     }
                 } else {
-                    mConnectedClients.remove(index);
+                    if (null == clientList.remove(index)) {
+                        Log.e(getTag(), "client doesn't exist in list, it should NOT happen");
+                    }
                 }
 
-                Log.d(TAG, "The connected wifi stations have changed with count: "
-                        + mConnectedClients.size() + ": " + mConnectedClients);
+                // Update clients list.
+                mConnectedClientWithApInfoMap.put(apInstanceIdentifier, clientList);
+                SoftApInfo currentInfoWithClientsChanged = mCurrentSoftApInfoMap
+                        .get(apInstanceIdentifier);
+                Log.d(getTag(), "The connected wifi stations have changed with count: "
+                        + clientList.size() + ": " + clientList + " on the AP which info is "
+                        + currentInfoWithClientsChanged);
 
                 if (mSoftApCallback != null) {
-                    mSoftApCallback.onConnectedClientsChanged(mConnectedClients);
+                    mSoftApCallback.onConnectedClientsOrInfoChanged(mCurrentSoftApInfoMap,
+                            mConnectedClientWithApInfoMap, isBridgedMode());
                 } else {
-                    Log.e(TAG,
-                            "SoftApCallback is null. Dropping ConnectedClientsChanged event."
-                    );
+                    Log.e(getTag(),
+                            "SoftApCallback is null. Dropping ConnectedClientsChanged event.");
                 }
 
                 mWifiMetrics.addSoftApNumAssociatedStationsChangedEvent(
-                        mConnectedClients.size(), mApConfig.getTargetMode());
+                        getConnectedClientList().size(),
+                        mConnectedClientWithApInfoMap.get(apInstanceIdentifier).size(),
+                        mOriginalModeConfiguration.getTargetMode(),
+                        mCurrentSoftApInfoMap.get(apInstanceIdentifier));
 
-                scheduleTimeoutMessage();
+                scheduleTimeoutMessages();
             }
 
-            private void setSoftApChannel(int freq, @WifiAnnotations.Bandwidth int apBandwidth) {
-                Log.d(TAG, "Channel switched. Frequency: " + freq
-                        + " Bandwidth: " + apBandwidth);
-
-                if (freq == mCurrentSoftApInfo.getFrequency()
-                        && apBandwidth == mCurrentSoftApInfo.getBandwidth()) {
-                    return; // no change
+            /**
+             * @param apInfo, the new SoftApInfo changed. Null used to clean up.
+             */
+            private void updateSoftApInfo(@Nullable SoftApInfo apInfo, boolean isRemoved) {
+                Log.d(getTag(), "SoftApInfo update " + apInfo + ", isRemoved: " + isRemoved);
+                if (apInfo == null) {
+                    // Clean up
+                    mCurrentSoftApInfoMap.clear();
+                    mConnectedClientWithApInfoMap.clear();
+                    mSoftApCallback.onConnectedClientsOrInfoChanged(mCurrentSoftApInfoMap,
+                            mConnectedClientWithApInfoMap, isBridgedMode());
+                    return;
+                }
+                String changedInstance = apInfo.getApInstanceIdentifier();
+                if (apInfo.equals(mCurrentSoftApInfoMap.get(changedInstance))) {
+                    if (isRemoved) {
+                        boolean isClientConnected =
+                                mConnectedClientWithApInfoMap.get(changedInstance).size() > 0;
+                        mCurrentSoftApInfoMap.remove(changedInstance);
+                        mConnectedClientWithApInfoMap.remove(changedInstance);
+                        mSoftApCallback.onConnectedClientsOrInfoChanged(mCurrentSoftApInfoMap,
+                                mConnectedClientWithApInfoMap, isBridgedMode());
+                        if (isClientConnected) {
+                            mWifiMetrics.addSoftApNumAssociatedStationsChangedEvent(
+                                    getConnectedClientList().size(), 0,
+                                    mOriginalModeConfiguration.getTargetMode(), apInfo);
+                        }
+                        if (isBridgedMode()) {
+                            mWifiMetrics.addSoftApInstanceDownEventInDualMode(
+                                    mOriginalModeConfiguration.getTargetMode(), apInfo);
+                        }
+                    }
+                    return;
                 }
 
-                mCurrentSoftApInfo.setFrequency(freq);
-                mCurrentSoftApInfo.setBandwidth(apBandwidth);
-                mSoftApCallback.onInfoChanged(mCurrentSoftApInfo);
+                // Make sure an empty client list is created when info updated
+                List clientList = mConnectedClientWithApInfoMap.computeIfAbsent(
+                        changedInstance, k -> new ArrayList<>());
+
+                if (clientList.size() != 0) {
+                    Log.e(getTag(), "The info: " + apInfo
+                            + " changed when client connected, it should NOT happen!!");
+                }
+
+                // Update the info when getting two infos in bridged mode.
+                // TODO: b/173999527. It may only one instance come up when starting bridged AP.
+                // Consider the handling with co-ex mechanism in bridged mode.
+                boolean waitForAnotherSoftApInfoInBridgedMode =
+                        isBridgedMode() && mCurrentSoftApInfoMap.size() == 0;
+
+                mCurrentSoftApInfoMap.put(changedInstance, new SoftApInfo(apInfo));
+                if (!waitForAnotherSoftApInfoInBridgedMode) {
+                    mSoftApCallback.onConnectedClientsOrInfoChanged(mCurrentSoftApInfoMap,
+                            mConnectedClientWithApInfoMap, isBridgedMode());
+                }
 
                 // ignore invalid freq and softap disable case for metrics
-                if (freq > 0 && apBandwidth != SoftApInfo.CHANNEL_WIDTH_INVALID) {
-                    mWifiMetrics.addSoftApChannelSwitchedEvent(mCurrentSoftApInfo.getFrequency(),
-                            mCurrentSoftApInfo.getBandwidth(), mApConfig.getTargetMode());
-                    updateUserBandPreferenceViolationMetricsIfNeeded();
+                if (apInfo.getFrequency() > 0
+                        && apInfo.getBandwidth() != SoftApInfo.CHANNEL_WIDTH_INVALID) {
+                    mWifiMetrics.addSoftApChannelSwitchedEvent(
+                            new ArrayList<>(mCurrentSoftApInfoMap.values()),
+                            mOriginalModeConfiguration.getTargetMode(), isBridgedMode());
+                    updateUserBandPreferenceViolationMetricsIfNeeded(apInfo);
                 }
             }
 
@@ -778,25 +1241,29 @@
 
                 mIfaceIsUp = isUp;
                 if (isUp) {
-                    Log.d(TAG, "SoftAp is ready for use");
+                    Log.d(getTag(), "SoftAp is ready for use");
                     updateApState(WifiManager.WIFI_AP_STATE_ENABLED,
                             WifiManager.WIFI_AP_STATE_ENABLING, 0);
-                    mModeListener.onStarted();
+                    mModeListener.onStarted(SoftApManager.this);
                     mWifiMetrics.incrementSoftApStartResult(true, 0);
+                    mCurrentSoftApInfoMap.clear();
+                    mConnectedClientWithApInfoMap.clear();
                     if (mSoftApCallback != null) {
-                        mSoftApCallback.onConnectedClientsChanged(mConnectedClients);
+                        mSoftApCallback.onConnectedClientsOrInfoChanged(mCurrentSoftApInfoMap,
+                                mConnectedClientWithApInfoMap, isBridgedMode());
                     }
                 } else {
                     // the interface was up, but goes down
                     sendMessage(CMD_INTERFACE_DOWN);
                 }
-                mWifiMetrics.addSoftApUpChangedEvent(isUp, mApConfig.getTargetMode(),
-                        mDefaultShutDownTimeoutMills);
+                mWifiMetrics.addSoftApUpChangedEvent(isUp,
+                        mOriginalModeConfiguration.getTargetMode(),
+                        mDefaultShutdownTimeoutMillis, isBridgedMode());
                 if (isUp) {
-                    mWifiMetrics.updateSoftApConfiguration(mApConfig.getSoftApConfiguration(),
-                            mApConfig.getTargetMode());
+                    mWifiMetrics.updateSoftApConfiguration(mCurrentSoftApConfiguration,
+                            mOriginalModeConfiguration.getTargetMode(), isBridgedMode());
                     mWifiMetrics.updateSoftApCapability(mCurrentSoftApCapability,
-                            mApConfig.getTargetMode());
+                            mOriginalModeConfiguration.getTargetMode(), isBridgedMode());
                 }
             }
 
@@ -811,12 +1278,18 @@
                         SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG,
                         SoftApStateMachine.CMD_NO_ASSOCIATED_STATIONS_TIMEOUT);
 
+                mSoftApBridgedModeIdleInstanceTimeoutMessage = new WakeupMessage(mContext, handler,
+                        SOFT_AP_SEND_MESSAGE_IDLE_IN_BRIDGED_MODE_TIMEOUT_TAG,
+                        SoftApStateMachine.CMD_NO_ASSOCIATED_STATIONS_TIMEOUT_ON_ONE_INSTANCE);
+                if (SdkLevel.isAtLeastS()) {
+                    mCoexManager.registerCoexListener(mCoexListener);
+                }
                 mSarManager.setSapWifiState(WifiManager.WIFI_AP_STATE_ENABLED);
-
-                Log.d(TAG, "Resetting connected clients on start");
-                mConnectedClients.clear();
+                Log.d(getTag(), "Resetting connected clients on start");
+                mConnectedClientWithApInfoMap.clear();
+                mPendingDisconnectClients.clear();
                 mEverReportMetricsForMaxClient = false;
-                scheduleTimeoutMessage();
+                scheduleTimeoutMessages();
             }
 
             @Override
@@ -824,51 +1297,64 @@
                 if (!mIfaceIsDestroyed) {
                     stopSoftAp();
                 }
-
-                Log.d(TAG, "Resetting num stations on stop");
-                if (mConnectedClients.size() != 0) {
-                    mConnectedClients.clear();
-                    if (mSoftApCallback != null) {
-                        mSoftApCallback.onConnectedClientsChanged(mConnectedClients);
-                    }
-                    mWifiMetrics.addSoftApNumAssociatedStationsChangedEvent(
-                            0, mApConfig.getTargetMode());
+                if (SdkLevel.isAtLeastS()) {
+                    mCoexManager.unregisterCoexListener(mCoexListener);
                 }
+                if (getConnectedClientList().size() != 0) {
+                    Log.d(getTag(), "Resetting num stations on stop");
+                    for (List<WifiClient> it : mConnectedClientWithApInfoMap.values()) {
+                        if (it.size() != 0) {
+                            mWifiMetrics.addSoftApNumAssociatedStationsChangedEvent(
+                                    0, 0, mOriginalModeConfiguration.getTargetMode(),
+                                    mCurrentSoftApInfoMap
+                                            .get(it.get(0).getApInstanceIdentifier()));
+                        }
+                    }
+                    mConnectedClientWithApInfoMap.clear();
+                    if (mSoftApCallback != null) {
+                        mSoftApCallback.onConnectedClientsOrInfoChanged(mCurrentSoftApInfoMap,
+                                mConnectedClientWithApInfoMap, isBridgedMode());
+                    }
+                }
+                mPendingDisconnectClients.clear();
                 cancelTimeoutMessage();
+                cancelBridgedModeIdleInstanceTimeoutMessage();
 
                 // Need this here since we are exiting |Started| state and won't handle any
                 // future CMD_INTERFACE_STATUS_CHANGED events after this point
-                mWifiMetrics.addSoftApUpChangedEvent(false, mApConfig.getTargetMode(),
-                        mDefaultShutDownTimeoutMills);
+                mWifiMetrics.addSoftApUpChangedEvent(false,
+                        mOriginalModeConfiguration.getTargetMode(),
+                        mDefaultShutdownTimeoutMillis, isBridgedMode());
                 updateApState(WifiManager.WIFI_AP_STATE_DISABLED,
                         WifiManager.WIFI_AP_STATE_DISABLING, 0);
 
                 mSarManager.setSapWifiState(WifiManager.WIFI_AP_STATE_DISABLED);
+
                 mApInterfaceName = null;
                 mIfaceIsUp = false;
                 mIfaceIsDestroyed = false;
-                mRole = ROLE_UNSPECIFIED;
-                mStateMachine.quitNow();
-                mModeListener.onStopped();
-                setSoftApChannel(0, SoftApInfo.CHANNEL_WIDTH_INVALID);
+                mRole = null;
+                updateSoftApInfo(null, false);
             }
 
-            private void updateUserBandPreferenceViolationMetricsIfNeeded() {
-                int band = mApConfig.getSoftApConfiguration().getBand();
+            private void updateUserBandPreferenceViolationMetricsIfNeeded(SoftApInfo apInfo) {
+                // The band preference violation only need to detect in single AP mode.
+                if (isBridgedMode()) return;
+                int band = mCurrentSoftApConfiguration.getBand();
                 boolean bandPreferenceViolated =
-                        (ScanResult.is24GHz(mCurrentSoftApInfo.getFrequency())
+                        (ScanResult.is24GHz(apInfo.getFrequency())
                             && !ApConfigUtil.containsBand(band,
                                     SoftApConfiguration.BAND_2GHZ))
-                        || (ScanResult.is5GHz(mCurrentSoftApInfo.getFrequency())
+                        || (ScanResult.is5GHz(apInfo.getFrequency())
                             && !ApConfigUtil.containsBand(band,
                                     SoftApConfiguration.BAND_5GHZ))
-                        || (ScanResult.is6GHz(mCurrentSoftApInfo.getFrequency())
+                        || (ScanResult.is6GHz(apInfo.getFrequency())
                             && !ApConfigUtil.containsBand(band,
                                     SoftApConfiguration.BAND_6GHZ));
 
                 if (bandPreferenceViolated) {
-                    Log.e(TAG, "Channel does not satisfy user band preference: "
-                            + mCurrentSoftApInfo.getFrequency());
+                    Log.e(getTag(), "Channel does not satisfy user band preference: "
+                            + apInfo.getFrequency());
                     mWifiMetrics.incrementNumSoftApUserBandPreferenceUnsatisfied();
                 }
             }
@@ -877,27 +1363,34 @@
             public boolean processMessage(Message message) {
                 switch (message.what) {
                     case CMD_ASSOCIATED_STATIONS_CHANGED:
-                        if (!(message.obj instanceof NativeWifiClient)) {
-                            Log.e(TAG, "Invalid type returned for"
+                        if (!(message.obj instanceof WifiClient)) {
+                            Log.e(getTag(), "Invalid type returned for"
                                     + " CMD_ASSOCIATED_STATIONS_CHANGED");
                             break;
                         }
-                        NativeWifiClient nativeClient = (NativeWifiClient) message.obj;
                         boolean isConnected = (message.arg1 == 1);
-                        if (nativeClient != null && nativeClient.getMacAddress() != null) {
-                            WifiClient client = new WifiClient(nativeClient.getMacAddress());
-                            Log.d(TAG, "CMD_ASSOCIATED_STATIONS_CHANGED, Client: "
-                                    + nativeClient.getMacAddress().toString() + " isConnected: "
-                                    + isConnected);
-                            updateConnectedClients(client, isConnected);
-                        }
+                        WifiClient client = (WifiClient) message.obj;
+                        Log.d(getTag(), "CMD_ASSOCIATED_STATIONS_CHANGED, Client: "
+                                + client.getMacAddress().toString() + " isConnected: "
+                                + isConnected);
+                        updateConnectedClients(client, isConnected);
                         break;
-                    case CMD_SOFT_AP_CHANNEL_SWITCHED:
-                        if (message.arg1 < 0) {
-                            Log.e(TAG, "Invalid ap channel frequency: " + message.arg1);
+                    case CMD_AP_INFO_CHANGED:
+                        if (!(message.obj instanceof SoftApInfo)) {
+                            Log.e(getTag(), "Invalid type returned for"
+                                    + " CMD_AP_INFO_CHANGED");
                             break;
                         }
-                        setSoftApChannel(message.arg1, message.arg2);
+                        SoftApInfo apInfo = (SoftApInfo) message.obj;
+                        if (apInfo.getFrequency() < 0) {
+                            Log.e(getTag(), "Invalid ap channel frequency: "
+                                    + apInfo.getFrequency());
+                            break;
+                        }
+                        // Update shutdown timeout
+                        apInfo.setAutoShutdownTimeoutMillis(mTimeoutEnabled
+                                ? getShutdownTimeoutMillis() : 0);
+                        updateSoftApInfo(apInfo, false);
                         break;
                     case CMD_INTERFACE_STATUS_CHANGED:
                         boolean isUp = message.arg1 == 1;
@@ -911,96 +1404,194 @@
                             updateApState(WifiManager.WIFI_AP_STATE_DISABLING,
                                     WifiManager.WIFI_AP_STATE_ENABLING, 0);
                         }
-                        transitionTo(mIdleState);
+                        quitNow();
                         break;
                     case CMD_START:
                         // Already started, ignore this command.
                         break;
                     case CMD_NO_ASSOCIATED_STATIONS_TIMEOUT:
                         if (!mTimeoutEnabled) {
-                            Log.wtf(TAG, "Timeout message received while timeout is disabled."
+                            Log.wtf(getTag(), "Timeout message received while timeout is disabled."
                                     + " Dropping.");
                             break;
                         }
-                        if (mConnectedClients.size() != 0) {
-                            Log.wtf(TAG, "Timeout message received but has clients. Dropping.");
+                        if (getConnectedClientList().size() != 0) {
+                            Log.wtf(getTag(), "Timeout message received but has clients. "
+                                    + "Dropping.");
                             break;
                         }
-                        mSoftApNotifier.showSoftApShutDownTimeoutExpiredNotification();
-                        Log.i(TAG, "Timeout message received. Stopping soft AP.");
+                        mSoftApNotifier.showSoftApShutdownTimeoutExpiredNotification();
+                        Log.i(getTag(), "Timeout message received. Stopping soft AP.");
                         updateApState(WifiManager.WIFI_AP_STATE_DISABLING,
                                 WifiManager.WIFI_AP_STATE_ENABLED, 0);
-                        transitionTo(mIdleState);
+                        quitNow();
+                        break;
+                    case CMD_NO_ASSOCIATED_STATIONS_TIMEOUT_ON_ONE_INSTANCE:
+                        if (!isBridgedMode() || mCurrentSoftApInfoMap.size() != 2) {
+                            Log.wtf(getTag(), "Ignore Bridged Mode Timeout message received"
+                                    + " in single AP state. Dropping");
+                            break;
+                        }
+                        if (!mBridgedModeOpportunisticsShutdownTimeoutEnabled) {
+                            Log.wtf(getTag(), "Bridged Mode Timeout message received"
+                                    + " while timeout is disabled. Dropping.");
+                            break;
+                        }
+                        Set<String> idleInstances = getIdleInstances();
+                        if (idleInstances.size() == 0) {
+                            break;
+                        }
+                        Log.d(getTag(), "Instance idle timout, the number of the idle instances is "
+                                + idleInstances.size());
+                        removeIfaceInstanceFromBridgedApIface(
+                                getHighestFrequencyInstance(idleInstances));
                         break;
                     case CMD_INTERFACE_DESTROYED:
-                        Log.d(TAG, "Interface was cleanly destroyed.");
+                        Log.d(getTag(), "Interface was cleanly destroyed.");
                         updateApState(WifiManager.WIFI_AP_STATE_DISABLING,
                                 WifiManager.WIFI_AP_STATE_ENABLED, 0);
                         mIfaceIsDestroyed = true;
-                        transitionTo(mIdleState);
+                        quitNow();
                         break;
                     case CMD_FAILURE:
-                        Log.w(TAG, "hostapd failure, stop and report failure");
+                        Log.w(getTag(), "hostapd failure, stop and report failure");
                         /* fall through */
                     case CMD_INTERFACE_DOWN:
-                        Log.w(TAG, "interface error, stop and report failure");
+                        Log.w(getTag(), "interface error, stop and report failure");
                         updateApState(WifiManager.WIFI_AP_STATE_FAILED,
                                 WifiManager.WIFI_AP_STATE_ENABLED,
                                 WifiManager.SAP_START_FAILURE_GENERAL);
                         updateApState(WifiManager.WIFI_AP_STATE_DISABLING,
                                 WifiManager.WIFI_AP_STATE_FAILED, 0);
-                        transitionTo(mIdleState);
+                        quitNow();
                         break;
                     case CMD_UPDATE_CAPABILITY:
                         // Capability should only changed by carrier requirement. Only apply to
                         // Tether Mode
-                        if (mApConfig.getTargetMode() ==  WifiManager.IFACE_IP_MODE_TETHERED) {
+                        if (mOriginalModeConfiguration.getTargetMode()
+                                ==  WifiManager.IFACE_IP_MODE_TETHERED) {
                             SoftApCapability capability = (SoftApCapability) message.obj;
                             mCurrentSoftApCapability = new SoftApCapability(capability);
                             mWifiMetrics.updateSoftApCapability(mCurrentSoftApCapability,
-                                    mApConfig.getTargetMode());
+                                    mOriginalModeConfiguration.getTargetMode(), isBridgedMode());
                             updateClientConnection();
                         }
                         break;
                     case CMD_UPDATE_CONFIG:
                         SoftApConfiguration newConfig = (SoftApConfiguration) message.obj;
-                        SoftApConfiguration currentConfig = mApConfig.getSoftApConfiguration();
-                        if (mIsRandomizeBssid) {
-                            // Current bssid is ramdon because unset. Set back to null..
-                            currentConfig = new SoftApConfiguration.Builder(currentConfig)
-                                    .setBssid(null)
-                                    .build();
-                        }
+                        SoftApConfiguration originalConfig =
+                                mOriginalModeConfiguration.getSoftApConfiguration();
                         if (!ApConfigUtil.checkConfigurationChangeNeedToRestart(
-                                currentConfig, newConfig)) {
-                            Log.d(TAG, "Configuration changed to " + newConfig);
-                            if (mApConfig.getSoftApConfiguration().getMaxNumberOfClients()
+                                originalConfig, newConfig)) {
+                            Log.d(getTag(), "Configuration changed to " + newConfig);
+                            if (mCurrentSoftApConfiguration.getMaxNumberOfClients()
                                     != newConfig.getMaxNumberOfClients()) {
-                                Log.d(TAG, "Max Client changed, reset to record the metrics");
+                                Log.d(getTag(), "Max Client changed, reset to record the metrics");
                                 mEverReportMetricsForMaxClient = false;
                             }
                             boolean needRescheduleTimer =
-                                    mApConfig.getSoftApConfiguration().getShutdownTimeoutMillis()
+                                    mCurrentSoftApConfiguration.getShutdownTimeoutMillis()
                                     != newConfig.getShutdownTimeoutMillis()
-                                    || mTimeoutEnabled != newConfig.isAutoShutdownEnabled();
-                            mBlockedClientList = new HashSet<>(newConfig.getBlockedClientList());
-                            mAllowedClientList = new HashSet<>(newConfig.getAllowedClientList());
-                            mTimeoutEnabled = newConfig.isAutoShutdownEnabled();
-                            mApConfig = new SoftApModeConfiguration(mApConfig.getTargetMode(),
-                                    newConfig, mCurrentSoftApCapability);
+                                    || mTimeoutEnabled != newConfig.isAutoShutdownEnabled()
+                                    || mBridgedModeOpportunisticsShutdownTimeoutEnabled
+                                    != newConfig
+                                    .isBridgedModeOpportunisticShutdownEnabledInternal();
+                            updateChangeableConfiguration(newConfig);
                             updateClientConnection();
                             if (needRescheduleTimer) {
                                 cancelTimeoutMessage();
-                                scheduleTimeoutMessage();
+                                cancelBridgedModeIdleInstanceTimeoutMessage();
+                                scheduleTimeoutMessages();
+                                // Update SoftApInfo
+                                for (SoftApInfo info : mCurrentSoftApInfoMap.values()) {
+                                    SoftApInfo newInfo = new SoftApInfo(info);
+                                    newInfo.setAutoShutdownTimeoutMillis(mTimeoutEnabled
+                                            ? getShutdownTimeoutMillis() : 0);
+                                    updateSoftApInfo(newInfo, false);
+                                }
                             }
                             mWifiMetrics.updateSoftApConfiguration(
-                                    mApConfig.getSoftApConfiguration(),
-                                    mApConfig.getTargetMode());
+                                    mCurrentSoftApConfiguration,
+                                    mOriginalModeConfiguration.getTargetMode(), isBridgedMode());
                         } else {
-                            Log.d(TAG, "Ignore the config: " + newConfig
+                            Log.d(getTag(), "Ignore the config: " + newConfig
                                     + " update since it requires restart");
                         }
                         break;
+                    case CMD_UPDATE_COUNTRY_CODE:
+                        String countryCode = (String) message.obj;
+                        if (!TextUtils.isEmpty(countryCode)
+                                && !TextUtils.equals(mCountryCode, countryCode)
+                                && mWifiNative.setApCountryCode(
+                                mApInterfaceName, countryCode.toUpperCase(Locale.ROOT))) {
+                            Log.i(getTag(), "Update country code when Soft AP enabled from "
+                                    + mCountryCode + " to " + countryCode);
+                            mCountryCode = countryCode;
+                        }
+                        break;
+                    case CMD_FORCE_DISCONNECT_PENDING_CLIENTS:
+                        if (mPendingDisconnectClients.size() != 0) {
+                            Log.d(getTag(), "Disconnect pending list is NOT empty");
+                            mPendingDisconnectClients.forEach((pendingClient, reason)->
+                                    mWifiNative.forceClientDisconnect(mApInterfaceName,
+                                    pendingClient.getMacAddress(), reason));
+                            sendMessageDelayed(
+                                    SoftApStateMachine.CMD_FORCE_DISCONNECT_PENDING_CLIENTS,
+                                    SOFT_AP_PENDING_DISCONNECTION_CHECK_DELAY_MS);
+                        }
+                        break;
+                    case CMD_SAFE_CHANNEL_FREQUENCY_CHANGED:
+                        updateSafeChannelFrequencyList();
+                        if (!isBridgedMode() || mCurrentSoftApInfoMap.size() != 2) {
+                            Log.d(getTag(), "Ignore safe channel changed in single AP state");
+                            break;
+                        }
+                        Set<String> unavailableInstances = new HashSet<>();
+                        for (SoftApInfo currentInfo : mCurrentSoftApInfoMap.values()) {
+                            int sapFreq = currentInfo.getFrequency();
+                            if (!mSafeChannelFrequencyList.contains(sapFreq)) {
+                                int sapBand = ApConfigUtil.convertFrequencyToBand(sapFreq);
+                                if (sapBand != ApConfigUtil.removeUnavailableBands(
+                                            mCurrentSoftApCapability,
+                                            sapBand, mCoexManager)) {
+                                    unavailableInstances.add(currentInfo.getApInstanceIdentifier());
+                                }
+                            }
+                        }
+                        removeIfaceInstanceFromBridgedApIface(
+                                getHighestFrequencyInstance(unavailableInstances));
+                        break;
+                    case CMD_HANDLE_WIFI_CONNECTED:
+                        if (!isBridgedMode() || mCurrentSoftApInfoMap.size() != 2) {
+                            Log.d(getTag(), "Ignore wifi connected in single AP state");
+                            break;
+                        }
+                        WifiInfo wifiInfo = (WifiInfo) message.obj;
+                        int wifiFreq = wifiInfo.getFrequency();
+                        String targetShutDownInstance = "";
+                        if (!mSafeChannelFrequencyList.contains(wifiFreq)) {
+                            Log.i(getTag(), "Wifi connected to freq:" + wifiFreq
+                                    + " which is unavailable for SAP");
+                            for (SoftApInfo sapInfo : mCurrentSoftApInfoMap.values()) {
+                                if (ApConfigUtil.convertFrequencyToBand(sapInfo.getFrequency())
+                                          == ApConfigUtil.convertFrequencyToBand(wifiFreq)) {
+                                    targetShutDownInstance = sapInfo.getApInstanceIdentifier();
+                                    Log.d(getTag(), "Remove the " + targetShutDownInstance
+                                            + " instance which is running on the same band as "
+                                            + "the wifi connection on an unsafe channel");
+                                    break;
+                                }
+                            }
+                            // Wifi may connect to different band as the SAP. For instances:
+                            // Wifi connect to 6Ghz but bridged AP is running on 2.4Ghz + 5Ghz.
+                            // In this case, targetShutDownInstance will be empty, shutdown the
+                            // highest frequency instance.
+                            removeIfaceInstanceFromBridgedApIface(
+                                    TextUtils.isEmpty(targetShutDownInstance)
+                                    ? getHighestFrequencyInstance(mCurrentSoftApInfoMap.keySet())
+                                    : targetShutDownInstance);
+                        }
+                        break;
                     default:
                         return NOT_HANDLED;
                 }
diff --git a/service/java/com/android/server/wifi/SoftApNotifier.java b/service/java/com/android/server/wifi/SoftApNotifier.java
index 45114a5..c75eedd 100644
--- a/service/java/com/android/server/wifi/SoftApNotifier.java
+++ b/service/java/com/android/server/wifi/SoftApNotifier.java
@@ -17,7 +17,6 @@
 package com.android.server.wifi;
 
 import android.app.Notification;
-import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.Intent;
 import android.graphics.drawable.Icon;
@@ -42,32 +41,32 @@
 
     private final WifiContext mContext;
     private final FrameworkFacade mFrameworkFacade;
-    private final NotificationManager mNotificationManager;
+    private final WifiNotificationManager mNotificationManager;
 
-    public SoftApNotifier(WifiContext context, FrameworkFacade framework) {
+    public SoftApNotifier(WifiContext context, FrameworkFacade framework,
+            WifiNotificationManager wifiNotificationManager) {
         mContext = context;
         mFrameworkFacade = framework;
-        mNotificationManager =
-                mContext.getSystemService(NotificationManager.class);
+        mNotificationManager = wifiNotificationManager;
     }
 
     /**
      * Show notification to notify user softap disable because auto shutdown timeout expired.
      */
-    public void showSoftApShutDownTimeoutExpiredNotification() {
+    public void showSoftApShutdownTimeoutExpiredNotification() {
         mNotificationManager.notify(NOTIFICATION_ID_SOFTAP_AUTO_DISABLED,
-                buildSoftApShutDownTimeoutExpiredNotification());
+                buildSoftApShutdownTimeoutExpiredNotification());
     }
 
     /**
      * Dismiss notification which used to notify user softap disable because auto shutdown
      * timeout expired.
      */
-    public void dismissSoftApShutDownTimeoutExpiredNotification() {
-        mNotificationManager.cancel(null, NOTIFICATION_ID_SOFTAP_AUTO_DISABLED);
+    public void dismissSoftApShutdownTimeoutExpiredNotification() {
+        mNotificationManager.cancel(NOTIFICATION_ID_SOFTAP_AUTO_DISABLED);
     }
 
-    private Notification buildSoftApShutDownTimeoutExpiredNotification() {
+    private Notification buildSoftApShutdownTimeoutExpiredNotification() {
         String title = mContext.getResources().getString(
                 R.string.wifi_softap_auto_shutdown_timeout_expired_title);
         String contentSummary = mContext.getResources().getString(
@@ -91,9 +90,10 @@
 
     private PendingIntent launchWifiTetherSettings() {
         Intent intent = new Intent(ACTION_HOTSPOT_PREFERENCES)
-                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                .setPackage(mFrameworkFacade.getSettingsPackageName(mContext));
         return mFrameworkFacade.getActivity(mContext, 0, intent,
-                PendingIntent.FLAG_UPDATE_CURRENT);
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
     }
 
 
diff --git a/service/java/com/android/server/wifi/SoftApStoreData.java b/service/java/com/android/server/wifi/SoftApStoreData.java
index ae9a40a..611bac7 100644
--- a/service/java/com/android/server/wifi/SoftApStoreData.java
+++ b/service/java/com/android/server/wifi/SoftApStoreData.java
@@ -23,7 +23,9 @@
 import android.net.wifi.WifiMigration;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.SparseIntArray;
 
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.util.ApConfigUtil;
 import com.android.server.wifi.util.SettingsMigrationDataHolder;
 import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
@@ -58,6 +60,13 @@
     private static final String XML_TAG_CLIENT_CONTROL_BY_USER = "ClientControlByUser";
     private static final String XML_TAG_BLOCKED_CLIENT_LIST = "BlockedClientList";
     private static final String XML_TAG_ALLOWED_CLIENT_LIST = "AllowedClientList";
+    private static final String XML_TAG_BRIDGED_MODE_OPPORTUNISTIC_SHUTDOWN_ENABLED =
+            "BridgedModeOpportunisticShutdownEnabled";
+    private static final String XML_TAG_MAC_RAMDOMIZATION_SETTING = "MacRandomizationSetting";
+    private static final String XML_TAG_BAND_CHANNEL_MAP = "BandChannelMap";
+    private static final String XML_TAG_80211_AX_ENABLED = "80211axEnabled";
+    private static final String XML_TAG_USER_CONFIGURATION = "UserConfiguration";
+
 
     private final Context mContext;
     private final SettingsMigrationDataHolder mSettingsMigrationDataHolder;
@@ -114,8 +123,11 @@
             if (softApConfig.getBssid() != null) {
                 XmlUtil.writeNextValue(out, XML_TAG_BSSID, softApConfig.getBssid().toString());
             }
-            XmlUtil.writeNextValue(out, XML_TAG_AP_BAND, softApConfig.getBand());
-            XmlUtil.writeNextValue(out, XML_TAG_CHANNEL, softApConfig.getChannel());
+            if (!SdkLevel.isAtLeastS()) {
+                // Band and channel change to store in Tag:BandChannelMap from S.
+                XmlUtil.writeNextValue(out, XML_TAG_AP_BAND, softApConfig.getBand());
+                XmlUtil.writeNextValue(out, XML_TAG_CHANNEL, softApConfig.getChannel());
+            }
             XmlUtil.writeNextValue(out, XML_TAG_HIDDEN_SSID, softApConfig.isHiddenSsid());
             XmlUtil.writeNextValue(out, XML_TAG_SECURITY_TYPE, softApConfig.getSecurityType());
             if (softApConfig.getSecurityType() != SoftApConfiguration.SECURITY_TYPE_OPEN) {
@@ -140,6 +152,21 @@
             XmlUtil.SoftApConfigurationXmlUtil.writeClientListToXml(out,
                     softApConfig.getAllowedClientList());
             XmlUtil.writeNextSectionEnd(out, XML_TAG_ALLOWED_CLIENT_LIST);
+            if (SdkLevel.isAtLeastS()) {
+                XmlUtil.writeNextValue(out, XML_TAG_BRIDGED_MODE_OPPORTUNISTIC_SHUTDOWN_ENABLED,
+                        softApConfig.isBridgedModeOpportunisticShutdownEnabled());
+                XmlUtil.writeNextValue(out, XML_TAG_MAC_RAMDOMIZATION_SETTING,
+                        softApConfig.getMacRandomizationSetting());
+
+                XmlUtil.writeNextSectionStart(out, XML_TAG_BAND_CHANNEL_MAP);
+                XmlUtil.SoftApConfigurationXmlUtil.writeChannelsToXml(out,
+                        softApConfig.getChannels());
+                XmlUtil.writeNextSectionEnd(out, XML_TAG_BAND_CHANNEL_MAP);
+                XmlUtil.writeNextValue(out, XML_TAG_80211_AX_ENABLED,
+                        softApConfig.isIeee80211axEnabled());
+                XmlUtil.writeNextValue(out, XML_TAG_USER_CONFIGURATION,
+                        softApConfig.isUserConfiguration());
+            }
         }
     }
 
@@ -162,6 +189,7 @@
         // 6GHz band. If the old encoding is found, a conversion is done.
         int channel = -1;
         int apBand = -1;
+        boolean hasBandChannelMap = false;
         List<MacAddress> blockedList = new ArrayList<>();
         List<MacAddress> allowedList = new ArrayList<>();
         boolean autoShutdownEnabledTagPresent = false;
@@ -220,6 +248,27 @@
                         case XML_TAG_CLIENT_CONTROL_BY_USER:
                             softApConfigBuilder.setClientControlByUserEnabled((boolean) value);
                             break;
+                        case XML_TAG_BRIDGED_MODE_OPPORTUNISTIC_SHUTDOWN_ENABLED:
+                            if (SdkLevel.isAtLeastS()) {
+                                softApConfigBuilder.setBridgedModeOpportunisticShutdownEnabled(
+                                        (boolean) value);
+                            }
+                            break;
+                        case XML_TAG_MAC_RAMDOMIZATION_SETTING:
+                            if (SdkLevel.isAtLeastS()) {
+                                softApConfigBuilder.setMacRandomizationSetting((int) value);
+                            }
+                            break;
+                        case XML_TAG_80211_AX_ENABLED:
+                            if (SdkLevel.isAtLeastS()) {
+                                softApConfigBuilder.setIeee80211axEnabled((boolean) value);
+                            }
+                            break;
+                        case XML_TAG_USER_CONFIGURATION:
+                            if (SdkLevel.isAtLeastS()) {
+                                softApConfigBuilder.setUserConfiguration((boolean) value);
+                            }
+                            break;
                         default:
                             Log.w(TAG, "Ignoring unknown value name " + valueName[0]);
                             break;
@@ -243,6 +292,14 @@
                                     in, outerTagDepth + 1);
                             if (parseredList != null) allowedList = new ArrayList<>(parseredList);
                             break;
+                        case XML_TAG_BAND_CHANNEL_MAP:
+                            if (SdkLevel.isAtLeastS()) {
+                                hasBandChannelMap = true;
+                                SparseIntArray channels = XmlUtil.SoftApConfigurationXmlUtil
+                                        .parseChannelsFromXml(in, outerTagDepth + 1);
+                                softApConfigBuilder.setChannels(channels);
+                            }
+                            break;
                         default:
                             Log.w(TAG, "Ignoring unknown tag found: " + tagName);
                             break;
@@ -251,11 +308,13 @@
             }
             softApConfigBuilder.setBlockedClientList(blockedList);
             softApConfigBuilder.setAllowedClientList(allowedList);
-            // Set channel and band
-            if (channel == 0) {
-                softApConfigBuilder.setBand(apBand);
-            } else {
-                softApConfigBuilder.setChannel(channel, apBand);
+            if (!hasBandChannelMap) {
+                // Set channel and band
+                if (channel == 0) {
+                    softApConfigBuilder.setBand(apBand);
+                } else {
+                    softApConfigBuilder.setChannel(channel, apBand);
+                }
             }
 
             // We should at-least have SSID restored from store.
diff --git a/service/java/com/android/server/wifi/StateChangeResult.java b/service/java/com/android/server/wifi/StateChangeResult.java
index f3d4bd6..e61db5d 100644
--- a/service/java/com/android/server/wifi/StateChangeResult.java
+++ b/service/java/com/android/server/wifi/StateChangeResult.java
@@ -16,35 +16,36 @@
 
 package com.android.server.wifi;
 
+import android.annotation.NonNull;
 import android.net.wifi.SupplicantState;
 import android.net.wifi.WifiSsid;
 
+import java.util.Objects;
+
 /**
  * Stores supplicant state change information passed from WifiMonitor to
  * a state machine. ClientModeImpl, SupplicantStateTracker and WpsStateMachine
  * are example state machines that handle it.
- * @hide
  */
 public class StateChangeResult {
-    StateChangeResult(int networkId, WifiSsid wifiSsid, String BSSID,
+    StateChangeResult(int networkId, @NonNull WifiSsid wifiSsid, @NonNull String bssid,
             SupplicantState state) {
         this.state = state;
-        this.wifiSsid= wifiSsid;
-        this.BSSID = BSSID;
+        this.wifiSsid = Objects.requireNonNull(wifiSsid);
+        this.bssid = Objects.requireNonNull(bssid);
         this.networkId = networkId;
     }
 
-    int networkId;
-    WifiSsid wifiSsid;
-    String BSSID;
-    SupplicantState state;
+    public final int networkId;
+    @NonNull public final WifiSsid wifiSsid;
+    @NonNull public final String bssid;
+    public final SupplicantState state;
 
     @Override
     public String toString() {
         StringBuffer sb = new StringBuffer();
-
-        sb.append(" SSID: ").append(wifiSsid.toString());
-        sb.append(" BSSID: ").append(BSSID);
+        sb.append(" ssid: ").append(wifiSsid);
+        sb.append(" bssid: ").append(bssid);
         sb.append(" nid: ").append(networkId);
         sb.append(" state: ").append(state);
         return sb.toString();
diff --git a/service/java/com/android/server/wifi/SupplicantStaIfaceCallbackImpl.java b/service/java/com/android/server/wifi/SupplicantStaIfaceCallbackImpl.java
index 0e2189b..4949972 100644
--- a/service/java/com/android/server/wifi/SupplicantStaIfaceCallbackImpl.java
+++ b/service/java/com/android/server/wifi/SupplicantStaIfaceCallbackImpl.java
@@ -21,6 +21,7 @@
 import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.ANQPNAIRealm;
 import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.ANQPRoamingConsortium;
 import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.ANQPVenueName;
+import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.ANQPVenueUrl;
 import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.HSConnCapability;
 import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.HSFriendlyName;
 import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.HSOSUProviders;
@@ -28,6 +29,7 @@
 
 import android.annotation.NonNull;
 import android.hardware.wifi.supplicant.V1_0.ISupplicantStaIfaceCallback;
+import android.net.wifi.SecurityParams;
 import android.net.wifi.SupplicantState;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
@@ -57,6 +59,7 @@
     private final WifiMonitor mWifiMonitor;
     // Used to help check for PSK password mismatch & EAP connection failure.
     private int mStateBeforeDisconnect = State.INACTIVE;
+    private String mCurrentSsid = null;
 
     SupplicantStaIfaceCallbackImpl(@NonNull SupplicantStaIfaceHal staIfaceHal,
             @NonNull String ifaceName,
@@ -144,14 +147,14 @@
     @Override
     public void onNetworkAdded(int id) {
         synchronized (mLock) {
-            mStaIfaceHal.logCallback("onNetworkAdded");
+            mStaIfaceHal.logCallback("onNetworkAdded id=" + id);
         }
     }
 
     @Override
     public void onNetworkRemoved(int id) {
         synchronized (mLock) {
-            mStaIfaceHal.logCallback("onNetworkRemoved");
+            mStaIfaceHal.logCallback("onNetworkRemoved id=" + id);
             // Reset state since network has been removed.
             mStateBeforeDisconnect = State.INACTIVE;
         }
@@ -174,10 +177,18 @@
                 // cache to track the state before the disconnect.
                 mStateBeforeDisconnect = newState;
             }
+
+            if (newState == State.ASSOCIATING || newState == State.ASSOCIATED
+                    || newState == State.COMPLETED) {
+                mStaIfaceHal.updateOnLinkedNetworkRoaming(mIfaceName, id);
+            }
+
             if (newState == State.COMPLETED) {
                 mWifiMonitor.broadcastNetworkConnectionEvent(
                         mIfaceName, mStaIfaceHal.getCurrentNetworkId(mIfaceName), filsHlpSent,
-                        bssidStr);
+                        wifiSsid, bssidStr);
+            } else if (newState == State.ASSOCIATING) {
+                mCurrentSsid = NativeUtil.encodeSsid(ssid);
             }
             mWifiMonitor.broadcastSupplicantStateChangeEvent(
                     mIfaceName, mStaIfaceHal.getCurrentNetworkId(mIfaceName), wifiSsid,
@@ -190,26 +201,35 @@
         onStateChanged(newState, bssid, id, ssid, false);
     }
 
+    public void onAnqpQueryDone(byte[/* 6 */] bssid,
+            ISupplicantStaIfaceCallback.AnqpData data,
+            ISupplicantStaIfaceCallback.Hs20AnqpData hs20Data,
+            android.hardware.wifi.supplicant.V1_4.ISupplicantStaIfaceCallback.AnqpData dataV14) {
+        Map<Constants.ANQPElementType, ANQPElement> elementsMap = new HashMap<>();
+        addAnqpElementToMap(elementsMap, ANQPVenueName, data.venueName);
+        addAnqpElementToMap(elementsMap, ANQPRoamingConsortium, data.roamingConsortium);
+        addAnqpElementToMap(
+                elementsMap, ANQPIPAddrAvailability, data.ipAddrTypeAvailability);
+        addAnqpElementToMap(elementsMap, ANQPNAIRealm, data.naiRealm);
+        addAnqpElementToMap(elementsMap, ANQP3GPPNetwork, data.anqp3gppCellularNetwork);
+        addAnqpElementToMap(elementsMap, ANQPDomName, data.domainName);
+        if (dataV14 != null) {
+            addAnqpElementToMap(elementsMap, ANQPVenueUrl, dataV14.venueUrl);
+        }
+        addAnqpElementToMap(elementsMap, HSFriendlyName, hs20Data.operatorFriendlyName);
+        addAnqpElementToMap(elementsMap, HSWANMetrics, hs20Data.wanMetrics);
+        addAnqpElementToMap(elementsMap, HSConnCapability, hs20Data.connectionCapability);
+        addAnqpElementToMap(elementsMap, HSOSUProviders, hs20Data.osuProvidersList);
+        mWifiMonitor.broadcastAnqpDoneEvent(
+                mIfaceName, new AnqpEvent(NativeUtil.macAddressToLong(bssid), elementsMap));
+    }
     @Override
     public void onAnqpQueryDone(byte[/* 6 */] bssid,
                                 ISupplicantStaIfaceCallback.AnqpData data,
                                 ISupplicantStaIfaceCallback.Hs20AnqpData hs20Data) {
         synchronized (mLock) {
             mStaIfaceHal.logCallback("onAnqpQueryDone");
-            Map<Constants.ANQPElementType, ANQPElement> elementsMap = new HashMap<>();
-            addAnqpElementToMap(elementsMap, ANQPVenueName, data.venueName);
-            addAnqpElementToMap(elementsMap, ANQPRoamingConsortium, data.roamingConsortium);
-            addAnqpElementToMap(
-                    elementsMap, ANQPIPAddrAvailability, data.ipAddrTypeAvailability);
-            addAnqpElementToMap(elementsMap, ANQPNAIRealm, data.naiRealm);
-            addAnqpElementToMap(elementsMap, ANQP3GPPNetwork, data.anqp3gppCellularNetwork);
-            addAnqpElementToMap(elementsMap, ANQPDomName, data.domainName);
-            addAnqpElementToMap(elementsMap, HSFriendlyName, hs20Data.operatorFriendlyName);
-            addAnqpElementToMap(elementsMap, HSWANMetrics, hs20Data.wanMetrics);
-            addAnqpElementToMap(elementsMap, HSConnCapability, hs20Data.connectionCapability);
-            addAnqpElementToMap(elementsMap, HSOSUProviders, hs20Data.osuProvidersList);
-            mWifiMonitor.broadcastAnqpDoneEvent(
-                    mIfaceName, new AnqpEvent(NativeUtil.macAddressToLong(bssid), elementsMap));
+            onAnqpQueryDone(bssid, data, hs20Data, null /* v1.4 element */);
         }
     }
 
@@ -231,7 +251,8 @@
             mStaIfaceHal.logCallback("onHs20SubscriptionRemediation");
             mWifiMonitor.broadcastWnmEvent(
                     mIfaceName,
-                    new WnmData(NativeUtil.macAddressToLong(bssid), url, osuMethod));
+                    WnmData.createRemediationEvent(NativeUtil.macAddressToLong(bssid), url,
+                            osuMethod));
         }
     }
 
@@ -242,7 +263,7 @@
             mStaIfaceHal.logCallback("onHs20DeauthImminentNotice");
             mWifiMonitor.broadcastWnmEvent(
                     mIfaceName,
-                    new WnmData(NativeUtil.macAddressToLong(bssid), url,
+                    WnmData.createDeauthImminentEvent(NativeUtil.macAddressToLong(bssid), url,
                             reasonCode == WnmData.ESS, reAuthDelayInSec));
         }
     }
@@ -271,48 +292,64 @@
                 }
             }
             mWifiMonitor.broadcastNetworkDisconnectionEvent(
-                    mIfaceName, locallyGenerated ? 1 : 0, reasonCode,
+                    mIfaceName, locallyGenerated, reasonCode, mCurrentSsid,
                     NativeUtil.macAddressFromByteArray(bssid));
         }
     }
 
+    private void handleAssocRejectEvent(AssocRejectEventInfo assocRejectInfo) {
+        boolean isWrongPwd = false;
+        WifiConfiguration curConfiguration =
+                mStaIfaceHal.getCurrentNetworkLocalConfig(mIfaceName);
+        if (curConfiguration != null) {
+            if (!assocRejectInfo.timedOut) {
+                Log.d(TAG, "flush PMK cache due to association rejection for config id "
+                        + curConfiguration.networkId + ".");
+                mStaIfaceHal.removePmkCacheEntry(curConfiguration.networkId);
+            }
+            // Special handling for WPA3-Personal networks. If the password is
+            // incorrect, the AP will send association rejection, with status code 1
+            // (unspecified failure). In SAE networks, the password authentication
+            // is not related to the 4-way handshake. In this case, we will send an
+            // authentication failure event up.
+            if (assocRejectInfo.statusCode == StatusCode.UNSPECIFIED_FAILURE) {
+                // Network Selection status is guaranteed to be initialized
+                SecurityParams params = curConfiguration.getNetworkSelectionStatus()
+                        .getCandidateSecurityParams();
+                if (params != null
+                        && params.getSecurityType() == WifiConfiguration.SECURITY_TYPE_SAE) {
+                    mStaIfaceHal.logCallback("SAE incorrect password");
+                    isWrongPwd = true;
+                }
+            } else if (assocRejectInfo.statusCode == StatusCode.CHALLENGE_FAIL
+                    && WifiConfigurationUtil.isConfigForWepNetwork(curConfiguration)) {
+                mStaIfaceHal.logCallback("WEP incorrect password");
+                isWrongPwd = true;
+            }
+        }
+
+        if (isWrongPwd) {
+            mWifiMonitor.broadcastAuthenticationFailureEvent(
+                    mIfaceName, WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD, -1);
+        }
+        mWifiMonitor.broadcastAssociationRejectionEvent(mIfaceName, assocRejectInfo);
+        mStateBeforeDisconnect = State.INACTIVE;
+    }
+
+    public void onAssociationRejected(android.hardware.wifi.supplicant.V1_4
+            .ISupplicantStaIfaceCallback.AssociationRejectionData assocRejectData) {
+        AssocRejectEventInfo assocRejectInfo = new AssocRejectEventInfo(assocRejectData);
+        handleAssocRejectEvent(assocRejectInfo);
+    }
+
     @Override
     public void onAssociationRejected(byte[/* 6 */] bssid, int statusCode, boolean timedOut) {
         synchronized (mLock) {
-            mStaIfaceHal.logCallback("onAssociationRejected");
-            boolean isWrongPwd = false;
-            WifiConfiguration curConfiguration =
-                    mStaIfaceHal.getCurrentNetworkLocalConfig(mIfaceName);
-            if (curConfiguration != null) {
-                if (!timedOut) {
-                    Log.d(TAG, "flush PMK cache due to association rejection for config id "
-                            + curConfiguration.networkId + ".");
-                    mStaIfaceHal.removePmkCacheEntry(curConfiguration.networkId);
-                }
-                // Special handling for WPA3-Personal networks. If the password is
-                // incorrect, the AP will send association rejection, with status code 1
-                // (unspecified failure). In SAE networks, the password authentication
-                // is not related to the 4-way handshake. In this case, we will send an
-                // authentication failure event up.
-                if (statusCode == StatusCode.UNSPECIFIED_FAILURE
-                        && WifiConfigurationUtil.isConfigForSaeNetwork(curConfiguration)) {
-                    mStaIfaceHal.logCallback("SAE incorrect password");
-                    isWrongPwd = true;
-                } else if (statusCode == StatusCode.CHALLENGE_FAIL
-                        && WifiConfigurationUtil.isConfigForWepNetwork(curConfiguration)) {
-                    mStaIfaceHal.logCallback("WEP incorrect password");
-                    isWrongPwd = true;
-                }
-            }
-
-            if (isWrongPwd) {
-                mWifiMonitor.broadcastAuthenticationFailureEvent(
-                        mIfaceName, WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD, -1);
-            }
-            mWifiMonitor
-                    .broadcastAssociationRejectionEvent(
-                            mIfaceName, statusCode, timedOut,
-                            NativeUtil.macAddressFromByteArray(bssid));
+            AssocRejectEventInfo assocRejectInfo = new AssocRejectEventInfo(
+                    mCurrentSsid,
+                    NativeUtil.macAddressFromByteArray(bssid),
+                    statusCode, timedOut);
+            handleAssocRejectEvent(assocRejectInfo);
         }
     }
 
@@ -322,6 +359,7 @@
             mStaIfaceHal.logCallback("onAuthenticationTimeout");
             mWifiMonitor.broadcastAuthenticationFailureEvent(
                     mIfaceName, WifiManager.ERROR_AUTH_FAILURE_TIMEOUT, -1);
+            mStateBeforeDisconnect = State.INACTIVE;
         }
     }
 
@@ -339,15 +377,21 @@
         }
     }
 
-    @Override
-    public void onEapFailure() {
+    public void onEapFailure(int errorCode) {
         synchronized (mLock) {
             mStaIfaceHal.logCallback("onEapFailure");
             mWifiMonitor.broadcastAuthenticationFailureEvent(
-                    mIfaceName, WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE, -1);
+                    mIfaceName, WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE, errorCode);
+            mStateBeforeDisconnect = State.INACTIVE;
         }
     }
 
+
+    @Override
+    public void onEapFailure() {
+        onEapFailure(-1);
+    }
+
     @Override
     public void onWpsEventSuccess() {
         mStaIfaceHal.logCallback("onWpsEventSuccess");
diff --git a/service/java/com/android/server/wifi/SupplicantStaIfaceCallbackV1_1Impl.java b/service/java/com/android/server/wifi/SupplicantStaIfaceCallbackV1_1Impl.java
index c4a3dda..e2be24c 100644
--- a/service/java/com/android/server/wifi/SupplicantStaIfaceCallbackV1_1Impl.java
+++ b/service/java/com/android/server/wifi/SupplicantStaIfaceCallbackV1_1Impl.java
@@ -121,11 +121,7 @@
 
     @Override
     public void onEapFailure_1_1(int code) {
-        synchronized (mLock) {
-            mStaIfaceHal.logCallback("onEapFailure_1_1");
-            mWifiMonitor.broadcastAuthenticationFailureEvent(
-                    mIfaceName, WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE, code);
-        }
+        mCallbackV10.onEapFailure(code);
     }
 
     @Override
diff --git a/service/java/com/android/server/wifi/SupplicantStaIfaceCallbackV1_2Impl.java b/service/java/com/android/server/wifi/SupplicantStaIfaceCallbackV1_2Impl.java
index 973e11a..f516489 100644
--- a/service/java/com/android/server/wifi/SupplicantStaIfaceCallbackV1_2Impl.java
+++ b/service/java/com/android/server/wifi/SupplicantStaIfaceCallbackV1_2Impl.java
@@ -179,11 +179,10 @@
         }
 
         // Set up key management: SAE or PSK
-        if (securityAkm == DppAkm.SAE || securityAkm == DppAkm.PSK_SAE) {
-            newWifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.SAE);
-            newWifiConfiguration.requirePmf = true;
-        } else if (securityAkm == DppAkm.PSK) {
-            newWifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+        if (securityAkm == DppAkm.SAE) {
+            newWifiConfiguration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
+        } else if (securityAkm == DppAkm.PSK_SAE || securityAkm == DppAkm.PSK) {
+            newWifiConfiguration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
         } else {
             // No other AKMs are currently supported
             onDppFailure(DppFailureCode.NOT_SUPPORTED);
@@ -193,9 +192,6 @@
         // Set up default values
         newWifiConfiguration.creatorName = mContext.getPackageManager()
                 .getNameForUid(Process.WIFI_UID);
-        newWifiConfiguration.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
-        newWifiConfiguration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
-        newWifiConfiguration.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
         newWifiConfiguration.status = WifiConfiguration.Status.ENABLED;
 
         mStaIfaceHal.getDppCallback().onSuccessConfigReceived(newWifiConfiguration);
diff --git a/service/java/com/android/server/wifi/SupplicantStaIfaceCallbackV1_3Impl.java b/service/java/com/android/server/wifi/SupplicantStaIfaceCallbackV1_3Impl.java
index 154fde4..fbf976d 100644
--- a/service/java/com/android/server/wifi/SupplicantStaIfaceCallbackV1_3Impl.java
+++ b/service/java/com/android/server/wifi/SupplicantStaIfaceCallbackV1_3Impl.java
@@ -118,8 +118,7 @@
 
     @Override
     public void onEapFailure_1_3(int code) {
-        mWifiMonitor.broadcastAuthenticationFailureEvent(
-                mIfaceName, WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE, code);
+        mCallbackV12.onEapFailure_1_1(code);
     }
 
     @Override
diff --git a/service/java/com/android/server/wifi/SupplicantStaIfaceCallbackV1_4Impl.java b/service/java/com/android/server/wifi/SupplicantStaIfaceCallbackV1_4Impl.java
new file mode 100644
index 0000000..6d62009
--- /dev/null
+++ b/service/java/com/android/server/wifi/SupplicantStaIfaceCallbackV1_4Impl.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.wifi;
+
+
+import android.annotation.NonNull;
+import android.hardware.wifi.supplicant.V1_4.ISupplicantStaIfaceCallback.AssociationRejectionData;
+import android.net.wifi.WifiManager;
+
+import com.android.server.wifi.hotspot2.WnmData;
+import com.android.server.wifi.util.NativeUtil;
+
+import java.util.ArrayList;
+
+abstract class SupplicantStaIfaceCallbackV1_4Impl extends
+        android.hardware.wifi.supplicant.V1_4.ISupplicantStaIfaceCallback.Stub {
+    private static final String TAG = SupplicantStaIfaceCallbackV1_4Impl.class.getSimpleName();
+    private final SupplicantStaIfaceHal mStaIfaceHal;
+    private final String mIfaceName;
+    private final WifiMonitor mWifiMonitor;
+    private final Object mLock;
+    private final SupplicantStaIfaceHal.SupplicantStaIfaceHalCallbackV1_3 mCallbackV13;
+    private final SupplicantStaIfaceHal.SupplicantStaIfaceHalCallback mCallbackV10;
+
+    SupplicantStaIfaceCallbackV1_4Impl(@NonNull SupplicantStaIfaceHal staIfaceHal,
+            @NonNull String ifaceName, @NonNull Object lock,
+            @NonNull WifiMonitor wifiMonitor) {
+        mStaIfaceHal = staIfaceHal;
+        mIfaceName = ifaceName;
+        mLock = lock;
+        mWifiMonitor = wifiMonitor;
+        // Create an older callback for function delegation,
+        // and it would cascadingly create older one.
+        mCallbackV13 = mStaIfaceHal.new SupplicantStaIfaceHalCallbackV1_3(mIfaceName);
+        mCallbackV10 = mStaIfaceHal.new SupplicantStaIfaceHalCallback(mIfaceName);
+    }
+
+    @Override
+    public void onNetworkAdded(int id) {
+        mCallbackV13.onNetworkAdded(id);
+    }
+
+    @Override
+    public void onNetworkRemoved(int id) {
+        mCallbackV13.onNetworkRemoved(id);
+    }
+
+    @Override
+    public void onStateChanged(int newState, byte[/* 6 */] bssid, int id,
+            ArrayList<Byte> ssid) {
+        mCallbackV13.onStateChanged(newState, bssid, id, ssid);
+    }
+
+    @Override
+    public void onAnqpQueryDone_1_4(byte[/* 6 */] bssid,
+            AnqpData data,
+            Hs20AnqpData hs20Data) {
+        synchronized (mLock) {
+            mStaIfaceHal.logCallback("onAnqpQueryDone_1_4");
+            mCallbackV10.onAnqpQueryDone(bssid, data.V1_0 /* v1.0 elemnt */, hs20Data,
+                    data /* v1.4 element */);
+        }
+    }
+
+    @Override
+    public void onAnqpQueryDone(byte[/* 6 */] bssid,
+            android.hardware.wifi.supplicant.V1_0.ISupplicantStaIfaceCallback.AnqpData data,
+            Hs20AnqpData hs20Data) {
+        mCallbackV13.onAnqpQueryDone(bssid, data, hs20Data);
+    }
+
+    @Override
+    public void onHs20IconQueryDone(byte[/* 6 */] bssid, String fileName,
+            ArrayList<Byte> data) {
+        mCallbackV13.onHs20IconQueryDone(bssid, fileName, data);
+    }
+
+    @Override
+    public void onHs20SubscriptionRemediation(byte[/* 6 */] bssid,
+            byte osuMethod, String url) {
+        mCallbackV13.onHs20SubscriptionRemediation(bssid, osuMethod, url);
+    }
+
+    @Override
+    public void onHs20DeauthImminentNotice(byte[/* 6 */] bssid, int reasonCode,
+            int reAuthDelayInSec, String url) {
+        mCallbackV13.onHs20DeauthImminentNotice(bssid, reasonCode, reAuthDelayInSec, url);
+    }
+
+    @Override
+    public void onDisconnected(byte[/* 6 */] bssid, boolean locallyGenerated,
+            int reasonCode) {
+        mCallbackV13.onDisconnected(bssid, locallyGenerated, reasonCode);
+    }
+
+    @Override
+    public void onAssociationRejected_1_4(AssociationRejectionData assocRejectData) {
+        synchronized (mLock) {
+            mStaIfaceHal.logCallback("onAssociationRejected_1_4");
+            mCallbackV10.onAssociationRejected(assocRejectData);
+        }
+    }
+
+    @Override
+    public void onAssociationRejected(byte[/* 6 */] bssid, int statusCode,
+            boolean timedOut) {
+        mCallbackV13.onAssociationRejected(bssid, statusCode, timedOut);
+    }
+
+    @Override
+    public void onAuthenticationTimeout(byte[/* 6 */] bssid) {
+        mCallbackV13.onAuthenticationTimeout(bssid);
+    }
+
+    @Override
+    public void onBssidChanged(byte reason, byte[/* 6 */] bssid) {
+        mCallbackV13.onBssidChanged(reason, bssid);
+    }
+
+    @Override
+    public void onEapFailure() {
+        mCallbackV13.onEapFailure();
+    }
+
+    @Override
+    public void onEapFailure_1_1(int code) {
+        mCallbackV13.onEapFailure_1_1(code);
+    }
+
+    @Override
+    public void onEapFailure_1_3(int code) {
+        mWifiMonitor.broadcastAuthenticationFailureEvent(
+                mIfaceName, WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE, code);
+    }
+
+    @Override
+    public void onWpsEventSuccess() {
+        mCallbackV13.onWpsEventSuccess();
+    }
+
+    @Override
+    public void onWpsEventFail(byte[/* 6 */] bssid, short configError, short errorInd) {
+        mCallbackV13.onWpsEventFail(bssid, configError, errorInd);
+    }
+
+    @Override
+    public void onWpsEventPbcOverlap() {
+        mCallbackV13.onWpsEventPbcOverlap();
+    }
+
+    @Override
+    public void onExtRadioWorkStart(int id) {
+        mCallbackV13.onExtRadioWorkStart(id);
+    }
+
+    @Override
+    public void onExtRadioWorkTimeout(int id) {
+        mCallbackV13.onExtRadioWorkTimeout(id);
+    }
+
+    @Override
+    public void onDppSuccessConfigReceived(ArrayList<Byte> ssid, String password,
+            byte[] psk, int securityAkm) {
+        mCallbackV13.onDppSuccessConfigReceived(
+                ssid, password, psk, securityAkm);
+    }
+
+    @Override
+    public void onDppSuccessConfigSent() {
+        mCallbackV13.onDppSuccessConfigSent();
+    }
+
+    @Override
+    public void onDppProgress(int code) {
+        mCallbackV13.onDppProgress(code);
+    }
+
+    @Override
+    public void onDppFailure(int code) {
+        mCallbackV13.onDppFailure(code);
+    }
+
+    @Override
+    public void onPmkCacheAdded(long expirationTimeInSec, ArrayList<Byte> serializedEntry) {
+        mCallbackV13.onPmkCacheAdded(expirationTimeInSec, serializedEntry);
+    }
+
+    @Override
+    public void onDppProgress_1_3(int code) {
+        mCallbackV13.onDppProgress_1_3(code);
+    }
+
+    @Override
+    public void onDppFailure_1_3(int code, String ssid, String channelList,
+            ArrayList<Short> bandList) {
+        mCallbackV13.onDppFailure_1_3(code, ssid, channelList, bandList);
+    }
+
+    @Override
+    public void onDppSuccess(int code) {
+        mCallbackV13.onDppSuccess(code);
+    }
+
+    @Override
+    public void onBssTmHandlingDone(BssTmData tmData) {
+        mCallbackV13.onBssTmHandlingDone(tmData);
+    }
+
+    @Override
+    public void onStateChanged_1_3(int newState, byte[/* 6 */] bssid, int id,
+            ArrayList<Byte> ssid, boolean filsHlpSent) {
+        mCallbackV13.onStateChanged_1_3(newState, bssid, id, ssid, filsHlpSent);
+    }
+
+    @Override
+    public void onHs20TermsAndConditionsAcceptanceRequestedNotification(byte[/* 6 */] bssid,
+            String url) {
+        synchronized (mLock) {
+            mStaIfaceHal.logCallback("onHs20TermsAndConditionsAcceptanceRequestedNotification");
+            mWifiMonitor.broadcastWnmEvent(mIfaceName,
+                    WnmData.createTermsAndConditionsAccetanceRequiredEvent(
+                            NativeUtil.macAddressToLong(bssid), url));
+        }
+    }
+
+    @Override
+    public void onNetworkNotFound(ArrayList<Byte> ssid) {
+        mStaIfaceHal.logCallback("onNetworkNotFoundNotification");
+        mWifiMonitor.broadcastNetworkNotFoundEvent(mIfaceName, NativeUtil.encodeSsid(ssid));
+    }
+}
diff --git a/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java b/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java
index a28bd72..ed0c0cc 100644
--- a/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java
+++ b/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java
@@ -15,13 +15,18 @@
  */
 package com.android.server.wifi;
 
+import static android.net.wifi.WifiManager.WIFI_FEATURE_DECORATED_IDENTITY;
 import static android.net.wifi.WifiManager.WIFI_FEATURE_DPP;
+import static android.net.wifi.WifiManager.WIFI_FEATURE_DPP_ENROLLEE_RESPONDER;
 import static android.net.wifi.WifiManager.WIFI_FEATURE_FILS_SHA256;
 import static android.net.wifi.WifiManager.WIFI_FEATURE_FILS_SHA384;
 import static android.net.wifi.WifiManager.WIFI_FEATURE_MBO;
 import static android.net.wifi.WifiManager.WIFI_FEATURE_OCE;
 import static android.net.wifi.WifiManager.WIFI_FEATURE_OWE;
+import static android.net.wifi.WifiManager.WIFI_FEATURE_PASSPOINT_TERMS_AND_CONDITIONS;
+import static android.net.wifi.WifiManager.WIFI_FEATURE_SAE_PK;
 import static android.net.wifi.WifiManager.WIFI_FEATURE_WAPI;
+import static android.net.wifi.WifiManager.WIFI_FEATURE_WFD_R2;
 import static android.net.wifi.WifiManager.WIFI_FEATURE_WPA3_SAE;
 import static android.net.wifi.WifiManager.WIFI_FEATURE_WPA3_SUITE_B;
 
@@ -41,6 +46,7 @@
 import android.hardware.wifi.supplicant.V1_3.ConnectionCapabilities;
 import android.hardware.wifi.supplicant.V1_3.WifiTechnology;
 import android.hardware.wifi.supplicant.V1_3.WpaDriverCapabilitiesMask;
+import android.hardware.wifi.supplicant.V1_4.LegacyMode;
 import android.hidl.manager.V1_0.IServiceManager;
 import android.hidl.manager.V1_0.IServiceNotification;
 import android.net.MacAddress;
@@ -52,8 +58,6 @@
 import android.os.RemoteException;
 import android.text.TextUtils;
 import android.util.Log;
-import android.util.MutableBoolean;
-import android.util.MutableInt;
 import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -67,6 +71,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Objects;
@@ -106,13 +111,15 @@
     // Supplicant HAL interface objects
     private IServiceManager mIServiceManager = null;
     private ISupplicant mISupplicant;
-    private HashMap<String, ISupplicantStaIface> mISupplicantStaIfaces = new HashMap<>();
-    private HashMap<String, ISupplicantStaIfaceCallback> mISupplicantStaIfaceCallbacks =
+    private Map<String, ISupplicantStaIface> mISupplicantStaIfaces = new HashMap<>();
+    private Map<String, ISupplicantStaIfaceCallback> mISupplicantStaIfaceCallbacks =
             new HashMap<>();
-    private HashMap<String, SupplicantStaNetworkHal> mCurrentNetworkRemoteHandles = new HashMap<>();
-    private HashMap<String, WifiConfiguration> mCurrentNetworkLocalConfigs = new HashMap<>();
+    private Map<String, SupplicantStaNetworkHal> mCurrentNetworkRemoteHandles = new HashMap<>();
+    private Map<String, WifiConfiguration> mCurrentNetworkLocalConfigs = new HashMap<>();
+    private Map<String, List<Pair<SupplicantStaNetworkHal, WifiConfiguration>>>
+            mLinkedNetworkLocalAndRemoteConfigs = new HashMap<>();
     @VisibleForTesting
-    HashMap<Integer, PmkCacheStoreData> mPmkCacheEntries = new HashMap<>();
+    Map<Integer, PmkCacheStoreData> mPmkCacheEntries = new HashMap<>();
     private SupplicantDeathEventHandler mDeathEventHandler;
     private ServiceManagerDeathRecipient mServiceManagerDeathRecipient;
     private SupplicantDeathRecipient mSupplicantDeathRecipient;
@@ -125,6 +132,7 @@
     private DppEventCallback mDppCallback = null;
     private final Clock mClock;
     private final WifiMetrics mWifiMetrics;
+    private final WifiGlobals mWifiGlobals;
 
     private final IServiceNotification mServiceNotificationCallback =
             new IServiceNotification.Stub() {
@@ -182,13 +190,15 @@
 
     public SupplicantStaIfaceHal(Context context, WifiMonitor monitor,
                                  FrameworkFacade frameworkFacade, Handler handler,
-                                 Clock clock, WifiMetrics wifiMetrics) {
+                                 Clock clock, WifiMetrics wifiMetrics,
+                                 WifiGlobals wifiGlobals) {
         mContext = context;
         mWifiMonitor = monitor;
         mFrameworkFacade = frameworkFacade;
         mEventHandler = handler;
         mClock = clock;
         mWifiMetrics = wifiMetrics;
+        mWifiGlobals = wifiGlobals;
 
         mServiceManagerDeathRecipient = new ServiceManagerDeathRecipient();
         mSupplicantDeathRecipient = new SupplicantDeathRecipient();
@@ -321,10 +331,29 @@
         }
     }
 
+    private boolean trySetupStaIfaceV1_4(@NonNull String ifaceName,
+            @NonNull ISupplicantStaIface iface)  throws RemoteException {
+        if (!isV1_4()) return false;
+
+        SupplicantStaIfaceHalCallbackV1_4 callbackV14 =
+                new SupplicantStaIfaceHalCallbackV1_4(ifaceName);
+        if (!registerCallbackV1_4(getStaIfaceMockableV1_4(iface), callbackV14)) {
+            throw new RemoteException("Init StaIface V1_4 failed.");
+        }
+        /* keep this in a store to avoid recycling by garbage collector. */
+        mISupplicantStaIfaceCallbacks.put(ifaceName, callbackV14);
+        return true;
+    }
+
     private boolean trySetupStaIfaceV1_3(@NonNull String ifaceName,
             @NonNull ISupplicantStaIface iface)  throws RemoteException {
         if (!isV1_3()) return false;
 
+        /* try newer version first. */
+        if (trySetupStaIfaceV1_4(ifaceName, iface)) {
+            logd("Newer HAL is found, skip V1_3 remaining init flow.");
+            return true;
+        }
         SupplicantStaIfaceHalCallbackV1_3 callbackV13 =
                 new SupplicantStaIfaceHalCallbackV1_3(ifaceName);
         if (!registerCallbackV1_3(getStaIfaceMockableV1_3(iface), callbackV13)) {
@@ -339,7 +368,7 @@
             @NonNull ISupplicantStaIface iface) throws RemoteException {
         if (!isV1_2()) return false;
 
-        /* try newer version fist. */
+        /* try newer version first. */
         if (trySetupStaIfaceV1_3(ifaceName, iface)) {
             logd("Newer HAL is found, skip V1_2 remaining init flow.");
             return true;
@@ -359,7 +388,7 @@
             @NonNull ISupplicantStaIface iface) throws RemoteException {
         if (!isV1_1()) return false;
 
-        /* try newer version fist. */
+        /* try newer version first. */
         if (trySetupStaIfaceV1_2(ifaceName, iface)) {
             logd("Newer HAL is found, skip V1_1 remaining init flow.");
             return true;
@@ -622,6 +651,7 @@
             mISupplicantStaIfaces.clear();
             mCurrentNetworkLocalConfigs.clear();
             mCurrentNetworkRemoteHandles.clear();
+            mLinkedNetworkLocalAndRemoteConfigs.clear();
         }
     }
 
@@ -631,9 +661,6 @@
                 Log.i(TAG, "Ignoring stale death recipient notification");
                 return;
             }
-            for (String ifaceName : mISupplicantStaIfaces.keySet()) {
-                mWifiMonitor.broadcastSupplicantDisconnectionEvent(ifaceName);
-            }
             clearState();
             if (mDeathEventHandler != null) {
                 mDeathEventHandler.onDeath();
@@ -695,14 +722,7 @@
                 return startDaemon_V1_1();
             } else {
                 Log.i(TAG, "Starting supplicant using init");
-                try {
-                    mFrameworkFacade.startSupplicant();
-                } catch (RuntimeException e) {
-                    // likely a "failed to set system property" runtime exception
-                    Log.e(TAG, "Failed to start supplicant using init", e);
-                    return false;
-                }
-                return true;
+                return mFrameworkFacade.startSupplicant();
             }
         }
     }
@@ -735,6 +755,7 @@
             linkToSupplicantDeath((cookie) -> {
                 Log.d(TAG, "ISupplicant died: cookie=" + cookie);
                 if (cookie != waitForDeathCookie) return;
+                supplicantServiceDiedHandler(mDeathRecipientCookie);
                 waitForDeathLatch.countDown();
             }, waitForDeathCookie);
 
@@ -832,6 +853,14 @@
         }
     }
 
+    protected android.hardware.wifi.supplicant.V1_4.ISupplicantStaIface
+            getStaIfaceMockableV1_4(ISupplicantIface iface) {
+        synchronized (mLock) {
+            return android.hardware.wifi.supplicant.V1_4.ISupplicantStaIface
+                    .asInterface(iface.asBinder());
+        }
+    }
+
     /**
      * Uses the IServiceManager to check if the device is running V1_1 of the HAL from the VINTF for
      * the device.
@@ -862,6 +891,16 @@
                 android.hardware.wifi.supplicant.V1_3.ISupplicant.kInterfaceName);
     }
 
+    /**
+     * Uses the IServiceManager to check if the device is running V1_4 of the HAL from the VINTF for
+     * the device.
+     * @return true if supported, false otherwise.
+     */
+    private boolean isV1_4() {
+        return checkHalVersionByInterfaceName(
+                android.hardware.wifi.supplicant.V1_4.ISupplicant.kInterfaceName);
+    }
+
     private boolean checkHalVersionByInterfaceName(String interfaceName) {
         if (interfaceName == null) {
             return false;
@@ -899,7 +938,7 @@
     }
 
     /**
-     * Helper method to look up the network config or the specified iface.
+     * Helper method to look up the network config for the specified iface.
      */
     protected WifiConfiguration getCurrentNetworkLocalConfig(@NonNull String ifaceName) {
         return mCurrentNetworkLocalConfigs.get(ifaceName);
@@ -932,7 +971,7 @@
                 Log.e(TAG, "Exception while saving config params: " + config, e);
             }
             if (!saveSuccess) {
-                loge("Failed to save variables for: " + config.getKey());
+                loge("Failed to save variables for: " + config.getProfileKey());
                 if (!removeAllNetworks(ifaceName)) {
                     loge("Failed to remove all networks on failure.");
                 }
@@ -955,7 +994,7 @@
      */
     public boolean connectToNetwork(@NonNull String ifaceName, @NonNull WifiConfiguration config) {
         synchronized (mLock) {
-            logd("connectToNetwork " + config.getKey());
+            logd("connectToNetwork " + config.getProfileKey());
             WifiConfiguration currentConfig = getCurrentNetworkLocalConfig(ifaceName);
             if (WifiConfigurationUtil.isSameNetwork(config, currentConfig)) {
                 String networkSelectionBSSID = config.getNetworkSelectionStatus()
@@ -977,6 +1016,7 @@
             } else {
                 mCurrentNetworkRemoteHandles.remove(ifaceName);
                 mCurrentNetworkLocalConfigs.remove(ifaceName);
+                mLinkedNetworkLocalAndRemoteConfigs.remove(ifaceName);
                 if (!removeAllNetworks(ifaceName)) {
                     loge("Failed to remove existing networks");
                     return false;
@@ -984,7 +1024,8 @@
                 Pair<SupplicantStaNetworkHal, WifiConfiguration> pair =
                         addNetworkAndSaveConfig(ifaceName, config);
                 if (pair == null) {
-                    loge("Failed to add/save network configuration: " + config.getKey());
+                    loge("Failed to add/save network configuration: " + config
+                            .getProfileKey());
                     return false;
                 }
                 mCurrentNetworkRemoteHandles.put(ifaceName, pair.first);
@@ -994,7 +1035,7 @@
                     checkSupplicantStaNetworkAndLogFailure(ifaceName, "connectToNetwork");
             if (networkHandle == null) {
                 loge("No valid remote network handle for network configuration: "
-                        + config.getKey());
+                        + config.getProfileKey());
                 return false;
             }
 
@@ -1004,12 +1045,12 @@
                     && pmkData.expirationTimeInSec > mClock.getElapsedSinceBootMillis() / 1000) {
                 logi("Set PMK cache for config id " + config.networkId);
                 if (networkHandle.setPmkCache(pmkData.data)) {
-                    mWifiMetrics.setConnectionPmkCache(true);
+                    mWifiMetrics.setConnectionPmkCache(ifaceName, true);
                 }
             }
 
             if (!networkHandle.select()) {
-                loge("Failed to select network configuration: " + config.getKey());
+                loge("Failed to select network configuration: " + config.getProfileKey());
                 return false;
             }
             return true;
@@ -1020,10 +1061,11 @@
      * Initiates roaming to the already configured network in wpa_supplicant. If the network
      * configuration provided does not match the already configured network, then this triggers
      * a new connection attempt (instead of roam).
-     * 1. First check if we're attempting to connect to the same network as we currently have
-     * configured.
-     * 2. Set the new bssid for the network in wpa_supplicant.
-     * 3. Trigger reassociate command to wpa_supplicant.
+     * 1. First check if we're attempting to connect to a linked network, and select the existing
+     *    supplicant network if there is one.
+     * 2.
+     * 3. Set the new bssid for the network in wpa_supplicant.
+     * 4. Trigger reassociate command to wpa_supplicant.
      *
      * @param ifaceName Name of the interface.
      * @param config WifiConfiguration parameters for the provided network.
@@ -1031,18 +1073,27 @@
      */
     public boolean roamToNetwork(@NonNull String ifaceName, WifiConfiguration config) {
         synchronized (mLock) {
+            if (updateOnLinkedNetworkRoaming(ifaceName, config.networkId)) {
+                SupplicantStaNetworkHal networkHandle = getCurrentNetworkRemoteHandle(ifaceName);
+                if (networkHandle == null) {
+                    loge("Roaming config matches a linked config, but a linked network handle was"
+                            + " not found.");
+                    return false;
+                }
+                return networkHandle.select();
+            }
             if (getCurrentNetworkId(ifaceName) != config.networkId) {
                 Log.w(TAG, "Cannot roam to a different network, initiate new connection. "
                         + "Current network ID: " + getCurrentNetworkId(ifaceName));
                 return connectToNetwork(ifaceName, config);
             }
             String bssid = config.getNetworkSelectionStatus().getNetworkSelectionBSSID();
-            logd("roamToNetwork" + config.getKey() + " (bssid " + bssid + ")");
+            logd("roamToNetwork" + config.getProfileKey() + " (bssid " + bssid + ")");
 
             SupplicantStaNetworkHal networkHandle =
                     checkSupplicantStaNetworkAndLogFailure(ifaceName, "roamToNetwork");
             if (networkHandle == null || !networkHandle.setBssid(bssid)) {
-                loge("Failed to set new bssid on network: " + config.getKey());
+                loge("Failed to set new bssid on network: " + config.getProfileKey());
                 return false;
             }
             if (!reassociate(ifaceName)) {
@@ -1105,11 +1156,26 @@
             // current network on receiving disconnection event from supplicant (b/32898136).
             mCurrentNetworkRemoteHandles.remove(ifaceName);
             mCurrentNetworkLocalConfigs.remove(ifaceName);
+            mLinkedNetworkLocalAndRemoteConfigs.remove(ifaceName);
             return true;
         }
     }
 
     /**
+     * Disable the current network in supplicant
+     *
+     * @param ifaceName Name of the interface.
+     */
+    public boolean disableCurrentNetwork(@NonNull String ifaceName) {
+        synchronized (mLock) {
+            SupplicantStaNetworkHal networkHandle =
+                    checkSupplicantStaNetworkAndLogFailure(ifaceName, "disableCurrentNetwork");
+            if (networkHandle == null) return false;
+            return networkHandle.disable();
+        }
+    }
+
+    /**
      * Set the currently configured network's bssid.
      *
      * @param ifaceName Name of the interface.
@@ -1326,7 +1392,7 @@
         synchronized (mLock) {
             SupplicantStaNetworkHal network =
                     new SupplicantStaNetworkHal(iSupplicantStaNetwork, ifaceName, mContext,
-                            mWifiMonitor);
+                            mWifiMonitor, mWifiGlobals, getAdvancedCapabilities(ifaceName));
             if (network != null) {
                 network.enableVerboseLogging(mVerboseLoggingEnabled);
             }
@@ -1430,6 +1496,24 @@
         }
     }
 
+    private boolean registerCallbackV1_4(
+            android.hardware.wifi.supplicant.V1_4.ISupplicantStaIface iface,
+            android.hardware.wifi.supplicant.V1_4.ISupplicantStaIfaceCallback callback) {
+        synchronized (mLock) {
+            String methodStr = "registerCallback_1_4";
+
+            if (iface == null) return false;
+            try {
+                android.hardware.wifi.supplicant.V1_4.SupplicantStatus status =
+                        iface.registerCallback_1_4(callback);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
     /**
      * @return a list of SupplicantNetworkID ints for all networks controlled by supplicant, returns
      * null if the call fails
@@ -1872,6 +1956,54 @@
     }
 
     /**
+     * Request Venue URL ANQP element from the specified AP |bssid|.
+     *
+     * @param ifaceName Name of the interface.
+     * @param bssid BSSID of the AP
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean initiateVenueUrlAnqpQuery(@NonNull String ifaceName, String bssid) {
+        synchronized (mLock) {
+            try {
+                return initiateVenueUrlAnqpQuery(
+                        ifaceName, NativeUtil.macAddressToByteArray(bssid));
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Illegal argument " + bssid, e);
+                return false;
+            }
+        }
+    }
+
+    /** See ISupplicantStaIface.hal for documentation */
+    private boolean initiateVenueUrlAnqpQuery(@NonNull String ifaceName, byte[/* 6 */] macAddress) {
+        synchronized (mLock) {
+            final String methodStr = "initiateVenueUrlAnqpQuery";
+            if (!isV1_4()) {
+                Log.e(TAG, "Method " + methodStr + " is not supported in existing HAL");
+                return false;
+            }
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            // Get a v1.4 supplicant STA Interface
+            android.hardware.wifi.supplicant.V1_4.ISupplicantStaIface staIfaceV14 =
+                    getStaIfaceMockableV1_4(iface);
+
+            if (staIfaceV14 == null) {
+                Log.e(TAG, methodStr
+                        + ": SupplicantStaIface is null, cannot initiate Venue URL ANQP request");
+                return false;
+            }
+            try {
+                android.hardware.wifi.supplicant.V1_4.SupplicantStatus status =
+                        staIfaceV14.initiateVenueUrlAnqpQuery(macAddress);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
      * Request the specified ANQP ICON from the specified AP |bssid|.
      *
      * @param ifaceName Name of the interface.
@@ -2464,7 +2596,8 @@
             int logLevel = turnOnVerbose
                     ? ISupplicant.DebugLevel.DEBUG
                     : ISupplicant.DebugLevel.INFO;
-            return setDebugParams(logLevel, false, false);
+            return setDebugParams(logLevel, false,
+                    turnOnVerbose && mWifiGlobals.getShowKeyVerboseLoggingModeEnabled());
         }
     }
 
@@ -2537,7 +2670,8 @@
         synchronized (mLock) {
             ISupplicantStaIface iface = getStaIface(ifaceName);
             if (iface == null) {
-                Log.e(TAG, "Can't call " + methodStr + ", ISupplicantStaIface is null");
+                Log.e(TAG, "Can't call " + methodStr + ", ISupplicantStaIface is null for iface="
+                        + ifaceName);
                 return null;
             }
             return iface;
@@ -2579,6 +2713,28 @@
     }
 
     /**
+     * Returns true if provided status code is SUCCESS, logs debug message and returns false
+     * otherwise
+     */
+    private boolean checkStatusAndLogFailure(
+            android.hardware.wifi.supplicant.V1_4.SupplicantStatus status,
+            final String methodStr) {
+        synchronized (mLock) {
+            if (status == null
+                    || status.code
+                    != android.hardware.wifi.supplicant.V1_4.SupplicantStatusCode.SUCCESS) {
+                Log.e(TAG, "ISupplicantStaIface." + methodStr + " failed: " + status);
+                return false;
+            } else {
+                if (mVerboseLoggingEnabled) {
+                    Log.d(TAG, "ISupplicantStaIface." + methodStr + " succeeded");
+                }
+                return true;
+            }
+        }
+    }
+
+    /**
      * Helper function to log callbacks.
      */
     protected void logCallback(final String methodStr) {
@@ -2673,6 +2829,12 @@
         }
     }
 
+    protected class SupplicantStaIfaceHalCallbackV1_4 extends SupplicantStaIfaceCallbackV1_4Impl {
+        SupplicantStaIfaceHalCallbackV1_4(@NonNull String ifaceName) {
+            super(SupplicantStaIfaceHal.this, ifaceName, mLock, mWifiMonitor);
+        }
+    }
+
     protected void addPmkCacheEntry(
             String ifaceName,
             int networkId, long expirationTimeInSec, ArrayList<Byte> serializedEntry) {
@@ -2749,7 +2911,7 @@
     }
 
     /**
-     * Returns a bitmask of advanced key management capabilities: WPA3 SAE/SUITE B and OWE
+     * Returns a bitmask of advanced capabilities: WPA3 SAE/SUITE B and OWE
      * Bitmask used is:
      * - WIFI_FEATURE_WPA3_SAE
      * - WIFI_FEATURE_WPA3_SUITE_B
@@ -2758,8 +2920,8 @@
      *  This is a v1.2+ HAL feature.
      *  On error, or if these features are not supported, 0 is returned.
      */
-    public long getAdvancedKeyMgmtCapabilities(@NonNull String ifaceName) {
-        final String methodStr = "getAdvancedKeyMgmtCapabilities";
+    public long getAdvancedCapabilities(@NonNull String ifaceName) {
+        final String methodStr = "getAdvancedCapabilities";
 
         long advancedCapabilities = 0;
         int keyMgmtCapabilities = getKeyMgmtCapabilities(ifaceName);
@@ -2798,6 +2960,21 @@
             if (mVerboseLoggingEnabled) {
                 Log.v(TAG, methodStr + ": DPP supported");
             }
+            if (isV1_4()) {
+                advancedCapabilities |= WIFI_FEATURE_DPP_ENROLLEE_RESPONDER;
+                if (mVerboseLoggingEnabled) {
+                    Log.v(TAG, methodStr + ": DPP ENROLLEE RESPONDER supported");
+                }
+            }
+        }
+
+        if (isV1_4()) {
+            advancedCapabilities |= WIFI_FEATURE_PASSPOINT_TERMS_AND_CONDITIONS
+                    | WIFI_FEATURE_DECORATED_IDENTITY;
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, methodStr + ": Passpoint T&C supported");
+                Log.v(TAG, methodStr + ": RFC 7542 decorated identity supported");
+            }
         }
 
         if ((keyMgmtCapabilities & android.hardware.wifi.supplicant.V1_3.ISupplicantStaNetwork
@@ -2835,7 +3012,7 @@
 
     private int getKeyMgmtCapabilities_1_3(@NonNull String ifaceName) {
         final String methodStr = "getKeyMgmtCapabilities_1_3";
-        MutableInt keyMgmtMask = new MutableInt(0);
+        Mutable<Integer> keyMgmtMask = new Mutable<>(0);
         ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
         if (iface == null) {
             return 0;
@@ -2868,8 +3045,8 @@
 
     private int getKeyMgmtCapabilities(@NonNull String ifaceName) {
         final String methodStr = "getKeyMgmtCapabilities";
-        MutableBoolean status = new MutableBoolean(false);
-        MutableInt keyMgmtMask = new MutableInt(0);
+        Mutable<Boolean> status = new Mutable<>(false);
+        Mutable<Integer> keyMgmtMask = new Mutable<>(0);
 
         if (isV1_3()) {
             keyMgmtMask.value = getKeyMgmtCapabilities_1_3(ifaceName);
@@ -2911,6 +3088,67 @@
         return keyMgmtMask.value;
     }
 
+    private Mutable<Integer> getWpaDriverCapabilities_1_4(@NonNull String ifaceName) {
+        final String methodStr = "getWpaDriverCapabilities_1_4";
+        Mutable<Integer> drvCapabilitiesMask = new Mutable<>(0);
+        ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+
+        if (null == iface) return drvCapabilitiesMask;
+
+        android.hardware.wifi.supplicant.V1_4.ISupplicantStaIface staIfaceV14 =
+                getStaIfaceMockableV1_4(iface);
+        if (null == staIfaceV14) {
+            Log.e(TAG, methodStr
+                    + ": SupplicantStaIface is null, cannot get wpa driver features");
+            return drvCapabilitiesMask;
+        }
+
+        try {
+            staIfaceV14.getWpaDriverCapabilities_1_4(
+                    (android.hardware.wifi.supplicant.V1_4.SupplicantStatus statusInternal,
+                            int drvCapabilities) -> {
+                        if (statusInternal.code
+                                == android.hardware.wifi.supplicant.V1_4
+                                .SupplicantStatusCode.SUCCESS) {
+                            drvCapabilitiesMask.value = drvCapabilities;
+                        }
+                        checkStatusAndLogFailure(statusInternal, methodStr);
+                    });
+        } catch (RemoteException e) {
+            handleRemoteException(e, methodStr);
+        }
+        return drvCapabilitiesMask;
+    }
+
+    private Mutable<Integer> getWpaDriverCapabilities_1_3(@NonNull String ifaceName) {
+        final String methodStr = "getWpaDriverCapabilities_1_3";
+        Mutable<Integer> drvCapabilitiesMask = new Mutable<>(0);
+        ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+
+        if (null == iface) return drvCapabilitiesMask;
+
+        android.hardware.wifi.supplicant.V1_3.ISupplicantStaIface staIfaceV13 =
+                getStaIfaceMockableV1_3(iface);
+        if (null == staIfaceV13) {
+            Log.e(TAG, methodStr
+                    + ": SupplicantStaIface is null, cannot get wpa driver features");
+            return drvCapabilitiesMask;
+        }
+
+        try {
+            staIfaceV13.getWpaDriverCapabilities(
+                    (SupplicantStatus statusInternal, int drvCapabilities) -> {
+                        if (statusInternal.code == SupplicantStatusCode.SUCCESS) {
+                            drvCapabilitiesMask.value = drvCapabilities;
+                        }
+                        checkStatusAndLogFailure(statusInternal, methodStr);
+                    });
+        } catch (RemoteException e) {
+            handleRemoteException(e, methodStr);
+        }
+        return drvCapabilitiesMask;
+    }
+
     /**
      * Get the driver supported features through supplicant.
      *
@@ -2919,34 +3157,13 @@
      */
     public long getWpaDriverFeatureSet(@NonNull String ifaceName) {
         final String methodStr = "getWpaDriverFeatureSet";
-        MutableInt drvCapabilitiesMask = new MutableInt(0);
+        Mutable<Integer> drvCapabilitiesMask = new Mutable<>(0);
         long featureSet = 0;
 
-        if (isV1_3()) {
-            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
-            if (iface == null) {
-                return 0;
-            }
-            // Get a v1.3 supplicant STA Interface
-            android.hardware.wifi.supplicant.V1_3.ISupplicantStaIface staIfaceV13 =
-                    getStaIfaceMockableV1_3(iface);
-            if (staIfaceV13 == null) {
-                Log.e(TAG, methodStr
-                        + ": SupplicantStaIface is null, cannot get wpa driver features");
-                return 0;
-            }
-
-            try {
-                staIfaceV13.getWpaDriverCapabilities(
-                        (SupplicantStatus statusInternal, int drvCapabilities) -> {
-                            if (statusInternal.code == SupplicantStatusCode.SUCCESS) {
-                                drvCapabilitiesMask.value = drvCapabilities;
-                            }
-                            checkStatusAndLogFailure(statusInternal, methodStr);
-                        });
-            } catch (RemoteException e) {
-                handleRemoteException(e, methodStr);
-            }
+        if (isV1_4()) {
+            drvCapabilitiesMask = getWpaDriverCapabilities_1_4(ifaceName);
+        } else if (isV1_3()) {
+            drvCapabilitiesMask = getWpaDriverCapabilities_1_3(ifaceName);
         } else {
             Log.i(TAG, "Method " + methodStr + " is not supported in existing HAL");
             return 0;
@@ -2966,11 +3183,27 @@
             }
         }
 
+        if ((drvCapabilitiesMask.value
+                & android.hardware.wifi.supplicant.V1_4.WpaDriverCapabilitiesMask.SAE_PK) != 0) {
+            featureSet |= WIFI_FEATURE_SAE_PK;
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, methodStr + ": SAE-PK supported");
+            }
+        }
+
+        if ((drvCapabilitiesMask.value
+                & android.hardware.wifi.supplicant.V1_4.WpaDriverCapabilitiesMask.WFD_R2) != 0) {
+            featureSet |= WIFI_FEATURE_WFD_R2;
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, methodStr + ": WFD-R2 supported");
+            }
+        }
+
         return featureSet;
     }
 
-    private @WifiStandard int getWifiStandardFromCap(ConnectionCapabilities capa) {
-        switch(capa.technology) {
+    private @WifiStandard int getWifiStandard(int technology) {
+        switch(technology) {
             case WifiTechnology.HE:
                 return ScanResult.WIFI_STANDARD_11AX;
             case WifiTechnology.VHT:
@@ -2984,8 +3217,8 @@
         }
     }
 
-    private int getChannelBandwidthFromCap(ConnectionCapabilities cap) {
-        switch(cap.channelBandwidth) {
+    private int getChannelBandwidth(int channelBandwidth) {
+        switch(channelBandwidth) {
             case WifiChannelWidthInMhz.WIDTH_20:
                 return ScanResult.CHANNEL_WIDTH_20MHZ;
             case WifiChannelWidthInMhz.WIDTH_40:
@@ -3011,42 +3244,87 @@
     public WifiNative.ConnectionCapabilities getConnectionCapabilities(@NonNull String ifaceName) {
         final String methodStr = "getConnectionCapabilities";
         WifiNative.ConnectionCapabilities capOut = new WifiNative.ConnectionCapabilities();
-        if (isV1_3()) {
-            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
-            if (iface == null) {
-                return capOut;
-            }
-
-            // Get a v1.3 supplicant STA Interface
-            android.hardware.wifi.supplicant.V1_3.ISupplicantStaIface staIfaceV13 =
-                    getStaIfaceMockableV1_3(iface);
-
-            if (staIfaceV13 == null) {
-                Log.e(TAG, methodStr
-                        + ": SupplicantStaIface is null, cannot get Connection Capabilities");
-                return capOut;
-            }
-
-            try {
-                staIfaceV13.getConnectionCapabilities(
-                        (SupplicantStatus statusInternal, ConnectionCapabilities cap) -> {
-                            if (statusInternal.code == SupplicantStatusCode.SUCCESS) {
-                                capOut.wifiStandard = getWifiStandardFromCap(cap);
-                                capOut.channelBandwidth = getChannelBandwidthFromCap(cap);
-                                capOut.maxNumberTxSpatialStreams = cap.maxNumberTxSpatialStreams;
-                                capOut.maxNumberRxSpatialStreams = cap.maxNumberRxSpatialStreams;
-                            }
-                            checkStatusAndLogFailure(statusInternal, methodStr);
-                        });
-            } catch (RemoteException e) {
-                handleRemoteException(e, methodStr);
-            }
+        ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+        if (iface == null) {
+            return capOut;
+        }
+        if (isV1_4()) {
+            return getConnectionCapabilities_1_4(iface);
+        } else if (isV1_3()) {
+            return getConnectionCapabilities_1_3(iface);
         } else {
             Log.e(TAG, "Method " + methodStr + " is not supported in existing HAL");
         }
         return capOut;
     }
 
+    private WifiNative.ConnectionCapabilities getConnectionCapabilities_1_3(
+            @NonNull ISupplicantStaIface iface) {
+        final String methodStr = "getConnectionCapabilities_1_3";
+        WifiNative.ConnectionCapabilities capOut = new WifiNative.ConnectionCapabilities();
+
+        // Get a v1.3 supplicant STA Interface
+        android.hardware.wifi.supplicant.V1_3.ISupplicantStaIface staIfaceV13 =
+                getStaIfaceMockableV1_3(iface);
+
+        if (staIfaceV13 == null) {
+            Log.e(TAG, methodStr
+                    + ": SupplicantStaIface is null, cannot get Connection Capabilities");
+            return capOut;
+        }
+
+        try {
+            staIfaceV13.getConnectionCapabilities(
+                    (SupplicantStatus statusInternal, ConnectionCapabilities cap) -> {
+                        if (statusInternal.code == SupplicantStatusCode.SUCCESS) {
+                            capOut.wifiStandard = getWifiStandard(cap.technology);
+                            capOut.channelBandwidth = getChannelBandwidth(
+                                    cap.channelBandwidth);
+                            capOut.maxNumberTxSpatialStreams = cap.maxNumberTxSpatialStreams;
+                            capOut.maxNumberRxSpatialStreams = cap.maxNumberRxSpatialStreams;
+                        }
+                        checkStatusAndLogFailure(statusInternal, methodStr);
+                    });
+        } catch (RemoteException e) {
+            handleRemoteException(e, methodStr);
+        }
+        return capOut;
+    }
+
+    private WifiNative.ConnectionCapabilities getConnectionCapabilities_1_4(
+            @NonNull ISupplicantStaIface iface) {
+        final String methodStr = "getConnectionCapabilities_1_4";
+        WifiNative.ConnectionCapabilities capOut = new WifiNative.ConnectionCapabilities();
+        // Get a v1.4 supplicant STA Interface
+        android.hardware.wifi.supplicant.V1_4.ISupplicantStaIface staIfaceV14 =
+                getStaIfaceMockableV1_4(iface);
+
+        if (staIfaceV14 == null) {
+            Log.e(TAG, methodStr
+                    + ": SupplicantStaIface is null, cannot get Connection Capabilities");
+            return capOut;
+        }
+
+        try {
+            staIfaceV14.getConnectionCapabilities_1_4(
+                    (android.hardware.wifi.supplicant.V1_4.SupplicantStatus statusInternal,
+                            android.hardware.wifi.supplicant.V1_4.ConnectionCapabilities cap)
+                            -> {
+                        if (statusInternal.code == SupplicantStatusCode.SUCCESS) {
+                            capOut.wifiStandard = getWifiStandard(cap.V1_3.technology);
+                            capOut.channelBandwidth = getChannelBandwidth(
+                                    cap.V1_3.channelBandwidth);
+                            capOut.is11bMode = (cap.legacyMode == LegacyMode.B_MODE);
+                            capOut.maxNumberTxSpatialStreams = cap.V1_3.maxNumberTxSpatialStreams;
+                            capOut.maxNumberRxSpatialStreams = cap.V1_3.maxNumberRxSpatialStreams;
+                        }
+                        checkStatusAndLogFailure(statusInternal, methodStr);
+                    });
+        } catch (RemoteException e) {
+            handleRemoteException(e, methodStr);
+        }
+        return capOut;
+    }
     /**
      * Adds a DPP peer URI to the URI list.
      *
@@ -3056,8 +3334,8 @@
      */
     public int addDppPeerUri(@NonNull String ifaceName, @NonNull String uri) {
         final String methodStr = "addDppPeerUri";
-        MutableBoolean status = new MutableBoolean(false);
-        MutableInt bootstrapId = new MutableInt(-1);
+        Mutable<Boolean> status = new Mutable<>(false);
+        Mutable<Integer> bootstrapId = new Mutable<>(-1);
 
         if (!isV1_2()) {
             Log.e(TAG, "Method " + methodStr + " is not supported in existing HAL");
@@ -3267,6 +3545,132 @@
     }
 
     /**
+     * Generate a DPP QR code based boot strap info
+     *
+     *  This is a v1.4+ HAL feature.
+     *  Returns DppResponderBootstrapInfo;
+     */
+    public WifiNative.DppBootstrapQrCodeInfo generateDppBootstrapInfoForResponder(
+            @NonNull String ifaceName, String macAddress, @NonNull String deviceInfo,
+            int dppCurve) {
+        final String methodStr = "generateDppBootstrapInfoForResponder";
+        Mutable<Boolean> status = new Mutable<>(false);
+        WifiNative.DppBootstrapQrCodeInfo bootstrapInfoOut =
+                new WifiNative.DppBootstrapQrCodeInfo();
+
+        ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+        if (iface == null) {
+            return bootstrapInfoOut;
+        }
+
+        // Get a v1.4 supplicant STA Interface
+        android.hardware.wifi.supplicant.V1_4.ISupplicantStaIface staIfaceV14 =
+                getStaIfaceMockableV1_4(iface);
+
+        if (staIfaceV14 == null) {
+            Log.e(TAG, methodStr + ": SupplicantStaIface V1.4 is null");
+            return bootstrapInfoOut;
+        }
+
+        try {
+            // Support for DPP Responder
+            // Requires HAL v1.4 or higher
+            staIfaceV14.generateDppBootstrapInfoForResponder(
+                    NativeUtil.macAddressToByteArray(macAddress), deviceInfo, dppCurve,
+                    (android.hardware.wifi.supplicant.V1_4.SupplicantStatus statusInternal,
+                            android.hardware.wifi.supplicant.V1_4
+                            .DppResponderBootstrapInfo info) -> {
+                        if (statusInternal.code == SupplicantStatusCode.SUCCESS) {
+                            bootstrapInfoOut.bootstrapId = info.bootstrapId;
+                            bootstrapInfoOut.listenChannel = info.listenChannel;
+                            bootstrapInfoOut.uri = info.uri;
+                        }
+                        checkStatusAndLogFailure(statusInternal, methodStr);
+                    });
+        } catch (RemoteException e) {
+            handleRemoteException(e, methodStr);
+            return bootstrapInfoOut;
+        }
+
+        return bootstrapInfoOut;
+    }
+
+    /**
+     * Starts DPP Enrollee-Responder request
+     *
+     *  This is a v1.4+ HAL feature.
+     *  Returns true when operation is successful
+     *  On error, or if these features are not supported, false is returned.
+     */
+    public boolean startDppEnrolleeResponder(@NonNull String ifaceName, int listenChannel) {
+        final String methodStr = "startDppEnrolleeResponder";
+
+        ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+        if (iface == null) {
+            return false;
+        }
+
+        // Get a v1.4 supplicant STA Interface
+        android.hardware.wifi.supplicant.V1_4.ISupplicantStaIface staIfaceV14 =
+                getStaIfaceMockableV1_4(iface);
+
+        if (staIfaceV14 == null) {
+            Log.e(TAG, methodStr + ": ISupplicantStaIface V1.4 is null");
+            return false;
+        }
+
+        try {
+            // Support for DPP Responder
+            // Requires HAL v1.4 or higher
+            android.hardware.wifi.supplicant.V1_4.SupplicantStatus status =
+                    staIfaceV14.startDppEnrolleeResponder(listenChannel);
+            return checkStatusAndLogFailure(status, methodStr);
+        } catch (RemoteException e) {
+            handleRemoteException(e, methodStr);
+        }
+
+        return false;
+    }
+
+    /**
+     * Stops/aborts DPP Responder request.
+     *
+     *  This is a v1.4+ HAL feature.
+     *  Returns true when operation is successful
+     *  On error, or if these features are not supported, false is returned.
+     */
+    public boolean stopDppResponder(@NonNull String ifaceName, int ownBootstrapId)  {
+        final String methodStr = "stopDppResponder";
+
+        ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+        if (iface == null) {
+            return false;
+        }
+
+        // Get a v1.4 supplicant STA Interface
+        android.hardware.wifi.supplicant.V1_4.ISupplicantStaIface staIfaceV14 =
+                getStaIfaceMockableV1_4(iface);
+
+        if (staIfaceV14 == null) {
+            Log.e(TAG, methodStr + ": ISupplicantStaIface V1.4 is null");
+            return false;
+        }
+
+        try {
+            // Support for DPP Responder
+            // Requires HAL v1.4 or higher
+            android.hardware.wifi.supplicant.V1_4.SupplicantStatus status =
+                    staIfaceV14.stopDppResponder(ownBootstrapId);
+            return checkStatusAndLogFailure(status, methodStr);
+        } catch (RemoteException e) {
+            handleRemoteException(e, methodStr);
+        }
+
+        return false;
+    }
+
+
+    /**
      * Register callbacks for DPP events.
      *
      * @param dppCallback DPP callback object.
@@ -3279,7 +3683,7 @@
         return mDppCallback;
     }
 
-   /**
+    /**
      * Set MBO cellular data availability.
      *
      * @param ifaceName Name of the interface.
@@ -3318,4 +3722,116 @@
         return false;
     }
 
+    /**
+     * Check if we've roamed to a linked network and make the linked network the current network
+     * if we have.
+     *
+     * @param ifaceName Name of the interface.
+     * @param newNetworkId network id of the network we've roamed to.
+     * @return true if we've roamed to a linked network, false if not.
+     */
+    public boolean updateOnLinkedNetworkRoaming(@NonNull String ifaceName, int newNetworkId) {
+        synchronized (mLock) {
+            SupplicantStaNetworkHal networkHal = getCurrentNetworkRemoteHandle(ifaceName);
+            List<Pair<SupplicantStaNetworkHal, WifiConfiguration>> linkedNetworkHandles =
+                    mLinkedNetworkLocalAndRemoteConfigs.get(ifaceName);
+            if (linkedNetworkHandles == null || networkHal == null
+                    || networkHal.getNetworkId() == newNetworkId) {
+                return false;
+            }
+            for (Pair<SupplicantStaNetworkHal, WifiConfiguration> pair : linkedNetworkHandles) {
+                if (pair.first.getNetworkId() != newNetworkId) {
+                    continue;
+                }
+                Log.i(TAG, "Roamed to linked network, make linked network as current network");
+                mCurrentNetworkRemoteHandles.put(ifaceName, pair.first);
+                mCurrentNetworkLocalConfigs.put(ifaceName, pair.second);
+                return true;
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Updates the linked networks for the current network and sends them to the supplicant.
+     *
+     * @param ifaceName Name of the interface.
+     * @param networkId network id of the network to link the configurations to.
+     * @param linkedConfigurations Map of config profile key to config for linking.
+     * @return true if networks were successfully linked, false otherwise.
+     */
+    public boolean updateLinkedNetworks(@NonNull String ifaceName, int networkId,
+                           Map<String, WifiConfiguration> linkedConfigurations) {
+        synchronized (mLock) {
+            WifiConfiguration currentConfig = getCurrentNetworkLocalConfig(ifaceName);
+            SupplicantStaNetworkHal currentHandle = getCurrentNetworkRemoteHandle(ifaceName);
+
+            if (currentConfig == null || currentHandle == null) {
+                Log.e(TAG, "current network not configured yet.");
+                return false;
+            }
+
+            if (networkId != currentConfig.networkId) {
+                Log.e(TAG, "current network id is not matching");
+                return false;
+            }
+
+            if (!removeAllNetworksExcept(ifaceName, networkId)) {
+                Log.e(TAG, "couldn't remove non-current supplicant networks");
+                return false;
+            }
+
+            mLinkedNetworkLocalAndRemoteConfigs.remove(ifaceName);
+
+            if (linkedConfigurations == null || linkedConfigurations.size() == 0) {
+                Log.i(TAG, "cleared linked networks");
+                return true;
+            }
+
+            List<Pair<SupplicantStaNetworkHal, WifiConfiguration>> linkedNetworkHandles =
+                    new ArrayList<>();
+            linkedNetworkHandles.add(new Pair(currentHandle, currentConfig));
+            for (String linkedNetwork : linkedConfigurations.keySet()) {
+                Log.i(TAG, "add linked network: " + linkedNetwork);
+                Pair<SupplicantStaNetworkHal, WifiConfiguration> pair =
+                        addNetworkAndSaveConfig(ifaceName, linkedConfigurations.get(linkedNetwork));
+                if (pair == null) {
+                    Log.e(TAG, "failed to add/save linked network: " + linkedNetwork);
+                    return false;
+                }
+                pair.first.enable(true);
+                linkedNetworkHandles.add(pair);
+            }
+
+            mLinkedNetworkLocalAndRemoteConfigs.put(ifaceName, linkedNetworkHandles);
+
+            return true;
+        }
+    }
+
+    /**
+     * Remove all networks except the supplied network ID from supplicant
+     *
+     * @param ifaceName Name of the interface
+     * @param networkId network id to keep
+     */
+    private boolean removeAllNetworksExcept(@NonNull String ifaceName, int networkId) {
+        synchronized (mLock) {
+            List<Integer> networks = listNetworks(ifaceName);
+            if (networks == null) {
+                Log.e(TAG, "removeAllNetworksExcept failed, got null networks");
+                return false;
+            }
+            for (int id : networks) {
+                if (networkId == id) {
+                    continue;
+                }
+                if (!removeNetwork(ifaceName, id)) {
+                    Log.e(TAG, "removeAllNetworksExcept failed to remove network: " + id);
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
 }
diff --git a/service/java/com/android/server/wifi/SupplicantStaNetworkCallbackImpl.java b/service/java/com/android/server/wifi/SupplicantStaNetworkCallbackImpl.java
new file mode 100644
index 0000000..68cb888
--- /dev/null
+++ b/service/java/com/android/server/wifi/SupplicantStaNetworkCallbackImpl.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.wifi;
+
+import android.annotation.NonNull;
+import android.hardware.wifi.supplicant.V1_0.ISupplicantStaNetworkCallback;
+
+import com.android.server.wifi.util.NativeUtil;
+
+class SupplicantStaNetworkCallbackImpl extends ISupplicantStaNetworkCallback.Stub {
+    private final SupplicantStaNetworkHal mNetworkHal;
+
+    /**
+     * Current configured network's framework network id.
+     */
+    private final int mFrameworkNetworkId;
+    /**
+     * Current configured network's ssid.
+     */
+    private final String mSsid;
+
+    private final String mIfaceName;
+
+    private final Object mLock;
+
+    private final WifiMonitor mWifiMonitor;
+
+    SupplicantStaNetworkCallbackImpl(
+            @NonNull SupplicantStaNetworkHal networkHal,
+            int frameworkNetworkId, String ssid,
+            @NonNull String ifaceName, @NonNull Object lock, @NonNull WifiMonitor wifiMonitor) {
+        mNetworkHal = networkHal;
+
+        mFrameworkNetworkId = frameworkNetworkId;
+        mSsid = ssid;
+
+        mIfaceName = ifaceName;
+        mLock = lock;
+        mWifiMonitor = wifiMonitor;
+    }
+
+    @Override
+    public void onNetworkEapSimGsmAuthRequest(
+            ISupplicantStaNetworkCallback.NetworkRequestEapSimGsmAuthParams params) {
+        synchronized (mLock) {
+            mNetworkHal.logCallback("onNetworkEapSimGsmAuthRequest");
+            String[] data = new String[params.rands.size()];
+            int i = 0;
+            for (byte[] rand : params.rands) {
+                data[i++] = NativeUtil.hexStringFromByteArray(rand);
+            }
+            mWifiMonitor.broadcastNetworkGsmAuthRequestEvent(
+                    mIfaceName, mFrameworkNetworkId, mSsid, data);
+        }
+    }
+
+    @Override
+    public void onNetworkEapSimUmtsAuthRequest(
+            ISupplicantStaNetworkCallback.NetworkRequestEapSimUmtsAuthParams params) {
+        synchronized (mLock) {
+            mNetworkHal.logCallback("onNetworkEapSimUmtsAuthRequest");
+            String randHex = NativeUtil.hexStringFromByteArray(params.rand);
+            String autnHex = NativeUtil.hexStringFromByteArray(params.autn);
+            String[] data = {randHex, autnHex};
+            mWifiMonitor.broadcastNetworkUmtsAuthRequestEvent(
+                    mIfaceName, mFrameworkNetworkId, mSsid, data);
+        }
+    }
+
+    @Override
+    public void onNetworkEapIdentityRequest() {
+        synchronized (mLock) {
+            mNetworkHal.logCallback("onNetworkEapIdentityRequest");
+            mWifiMonitor.broadcastNetworkIdentityRequestEvent(
+                    mIfaceName, mFrameworkNetworkId, mSsid);
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/SupplicantStaNetworkCallbackV1_4Impl.java b/service/java/com/android/server/wifi/SupplicantStaNetworkCallbackV1_4Impl.java
new file mode 100644
index 0000000..bbb08c4
--- /dev/null
+++ b/service/java/com/android/server/wifi/SupplicantStaNetworkCallbackV1_4Impl.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.wifi;
+
+import android.annotation.NonNull;
+import android.hardware.wifi.supplicant.V1_4.ISupplicantStaNetworkCallback;
+import android.hardware.wifi.supplicant.V1_4.ISupplicantStaNetworkCallback.TransitionDisableIndication;
+
+class SupplicantStaNetworkCallbackV1_4Impl extends ISupplicantStaNetworkCallback.Stub {
+    private final SupplicantStaNetworkHal mNetworkHal;
+
+    /**
+     * Current configured network's framework network id.
+     */
+    private final int mFrameworkNetworkId;
+    /**
+     * Current configured network's ssid.
+     */
+    private final String mSsid;
+
+    private final String mIfaceName;
+
+    private final WifiMonitor mWifiMonitor;
+
+    private final Object mLock;
+
+    private SupplicantStaNetworkHal.SupplicantStaNetworkHalCallback mCallbackV10;
+
+    SupplicantStaNetworkCallbackV1_4Impl(
+            @NonNull SupplicantStaNetworkHal networkHal,
+            int frameworkNetworkId, @NonNull String ssid,
+            @NonNull String ifaceName, @NonNull Object lock, @NonNull WifiMonitor wifiMonitor) {
+        mNetworkHal = networkHal;
+
+        mFrameworkNetworkId = frameworkNetworkId;
+        mSsid = ssid;
+
+        mIfaceName = ifaceName;
+        mLock = lock;
+        mWifiMonitor = wifiMonitor;
+
+        mCallbackV10 = mNetworkHal.new SupplicantStaNetworkHalCallback(
+                frameworkNetworkId, ssid);
+    }
+
+    @Override
+    public void onNetworkEapSimGsmAuthRequest(
+            ISupplicantStaNetworkCallback.NetworkRequestEapSimGsmAuthParams params) {
+        mCallbackV10.onNetworkEapSimGsmAuthRequest(params);
+    }
+
+    @Override
+    public void onNetworkEapSimUmtsAuthRequest(
+            ISupplicantStaNetworkCallback.NetworkRequestEapSimUmtsAuthParams params) {
+        mCallbackV10.onNetworkEapSimUmtsAuthRequest(params);
+    }
+
+    @Override
+    public void onNetworkEapIdentityRequest() {
+        mCallbackV10.onNetworkEapIdentityRequest();
+    }
+
+    @Override
+    public void onTransitionDisable(int indicationBits) {
+        synchronized (mLock) {
+            mNetworkHal.logCallback("onTransitionDisable");
+            int frameworkBits = 0;
+            if (0 != (indicationBits & TransitionDisableIndication.USE_WPA3_PERSONAL)) {
+                frameworkBits |= WifiMonitor.TDI_USE_WPA3_PERSONAL;
+            }
+            if (0 != (indicationBits & TransitionDisableIndication.USE_SAE_PK)) {
+                frameworkBits |= WifiMonitor.TDI_USE_SAE_PK;
+            }
+            if (0 != (indicationBits & TransitionDisableIndication.USE_WPA3_ENTERPRISE)) {
+                frameworkBits |= WifiMonitor.TDI_USE_WPA3_ENTERPRISE;
+            }
+            if (0 != (indicationBits & TransitionDisableIndication.USE_ENHANCED_OPEN)) {
+                frameworkBits |= WifiMonitor.TDI_USE_ENHANCED_OPEN;
+            }
+            if (0 == frameworkBits) return;
+
+            mWifiMonitor.broadcastTransitionDisableEvent(
+                    mIfaceName, mFrameworkNetworkId, frameworkBits);
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/SupplicantStaNetworkHal.java b/service/java/com/android/server/wifi/SupplicantStaNetworkHal.java
index 68623fb..717edbb 100644
--- a/service/java/com/android/server/wifi/SupplicantStaNetworkHal.java
+++ b/service/java/com/android/server/wifi/SupplicantStaNetworkHal.java
@@ -20,13 +20,14 @@
 import android.hardware.wifi.supplicant.V1_0.ISupplicantStaNetworkCallback;
 import android.hardware.wifi.supplicant.V1_0.SupplicantStatus;
 import android.hardware.wifi.supplicant.V1_0.SupplicantStatusCode;
+import android.net.wifi.SecurityParams;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiEnterpriseConfig.Ocsp;
+import android.net.wifi.WifiManager;
 import android.os.RemoteException;
 import android.text.TextUtils;
 import android.util.Log;
-import android.util.MutableBoolean;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.wifi.util.ArrayUtils;
@@ -92,6 +93,7 @@
     private final Context mContext;
     private final String mIfaceName;
     private final WifiMonitor mWifiMonitor;
+    private final WifiGlobals mWifiGlobals;
     private ISupplicantStaNetwork mISupplicantStaNetwork;
     private ISupplicantStaNetworkCallback mISupplicantStaNetworkCallback;
 
@@ -131,13 +133,17 @@
     private String mEapDomainSuffixMatch;
     private @Ocsp int mOcsp;
     private String mWapiCertSuite;
+    private long mAdvanceKeyMgmtFeatures;
 
     SupplicantStaNetworkHal(ISupplicantStaNetwork iSupplicantStaNetwork, String ifaceName,
-            Context context, WifiMonitor monitor) {
+            Context context, WifiMonitor monitor, WifiGlobals wifiGlobals,
+            long advanceKeyMgmtFeature) {
         mISupplicantStaNetwork = iSupplicantStaNetwork;
         mContext = context;
         mIfaceName = ifaceName;
         mWifiMonitor = monitor;
+        mWifiGlobals = wifiGlobals;
+        mAdvanceKeyMgmtFeatures = advanceKeyMgmtFeature;
     }
 
     /**
@@ -211,39 +217,22 @@
             /** allowedKeyManagement */
             if (getKeyMgmt()) {
                 BitSet keyMgmtMask = supplicantToWifiConfigurationKeyMgmtMask(mKeyMgmtMask);
-                config.allowedKeyManagement = removeFastTransitionFlags(keyMgmtMask);
-                config.allowedKeyManagement = removeSha256KeyMgmtFlags(config.allowedKeyManagement);
+                keyMgmtMask = removeFastTransitionFlags(keyMgmtMask);
+                keyMgmtMask = removeSha256KeyMgmtFlags(keyMgmtMask);
+                keyMgmtMask = removePskSaeUpgradableTypeFlags(keyMgmtMask);
+                config.setSecurityParams(keyMgmtMask);
+                config.enableFils(
+                        keyMgmtMask.get(WifiConfiguration.KeyMgmt.FILS_SHA256),
+                        keyMgmtMask.get(WifiConfiguration.KeyMgmt.FILS_SHA384));
             }
-            /** allowedProtocols */
-            if (getProto()) {
-                config.allowedProtocols =
-                        supplicantToWifiConfigurationProtoMask(mProtoMask);
-            }
-            /** allowedAuthAlgorithms */
-            if (getAuthAlg()) {
-                config.allowedAuthAlgorithms =
-                        supplicantToWifiConfigurationAuthAlgMask(mAuthAlgMask);
-            }
-            /** allowedGroupCiphers */
-            if (getGroupCipher()) {
-                config.allowedGroupCiphers =
-                        supplicantToWifiConfigurationGroupCipherMask(mGroupCipherMask);
-            }
-            /** allowedPairwiseCiphers */
-            if (getPairwiseCipher()) {
-                config.allowedPairwiseCiphers =
-                        supplicantToWifiConfigurationPairwiseCipherMask(mPairwiseCipherMask);
-            }
-            /** allowedPairwiseCiphers */
-            if (getGroupMgmtCipher()) {
-                config.allowedGroupManagementCiphers =
-                        supplicantToWifiConfigurationGroupMgmtCipherMask(mGroupMgmtCipherMask);
-            }
+
+            // supplicant only have one valid security type, it won't be a disbled params.
+            SecurityParams securityParams = config.getDefaultSecurityParams();
 
             /** PSK pass phrase */
             config.preSharedKey = null;
             if (getPskPassphrase() && !TextUtils.isEmpty(mPskPassphrase)) {
-                if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WAPI_PSK)) {
+                if (securityParams.isSecurityType(WifiConfiguration.SECURITY_TYPE_WAPI_PSK)) {
                     config.preSharedKey = mPskPassphrase;
                 } else {
                     config.preSharedKey = NativeUtil.addEnclosingQuotes(mPskPassphrase);
@@ -261,7 +250,7 @@
             }
 
             /** WAPI Cert Suite */
-            if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WAPI_CERT)) {
+            if (securityParams.isSecurityType(WifiConfiguration.SECURITY_TYPE_WAPI_CERT)) {
                 if (config.enterpriseConfig == null) {
                     return false;
                 }
@@ -309,17 +298,29 @@
                 return false;
             }
 
+            SecurityParams securityParams = config.getNetworkSelectionStatus()
+                    .getCandidateSecurityParams();
+            if (null == securityParams) {
+                Log.wtf(TAG, "No available security params.");
+                return false;
+            }
+            Log.d(TAG, "The target security params: " + securityParams);
+
             /** RequirePMF */
-            if (!setRequirePmf(config.requirePmf)) {
+            if (!setRequirePmf(securityParams.isRequirePmf())) {
                 Log.e(TAG, config.SSID + ": failed to set requirePMF: " + config.requirePmf);
                 return false;
             }
             /** Key Management Scheme */
-            if (config.allowedKeyManagement.cardinality() != 0) {
+            BitSet allowedKeyManagement = securityParams.getAllowedKeyManagement();
+            if (allowedKeyManagement.cardinality() != 0) {
                 // Add FT flags if supported.
-                BitSet keyMgmtMask = addFastTransitionFlags(config.allowedKeyManagement);
+                BitSet keyMgmtMask = addFastTransitionFlags(allowedKeyManagement);
                 // Add SHA256 key management flags.
                 keyMgmtMask = addSha256KeyMgmtFlags(keyMgmtMask);
+                // Add upgradable type key management flags for PSK/SAE.
+                keyMgmtMask = addPskSaeUpgradableTypeFlagsIfSupported(
+                        config, keyMgmtMask);
                 if (!setKeyMgmt(wifiConfigurationToSupplicantKeyMgmtMask(keyMgmtMask))) {
                     Log.e(TAG, "failed to set Key Management");
                     return false;
@@ -332,29 +333,33 @@
                 }
             }
             /** Security Protocol */
-            if (config.allowedProtocols.cardinality() != 0
-                    && !setProto(wifiConfigurationToSupplicantProtoMask(config.allowedProtocols))) {
+            BitSet allowedProtocols = securityParams.getAllowedProtocols();
+            if (allowedProtocols.cardinality() != 0
+                    && !setProto(wifiConfigurationToSupplicantProtoMask(allowedProtocols))) {
                 Log.e(TAG, "failed to set Security Protocol");
                 return false;
             }
             /** Auth Algorithm */
-            if (config.allowedAuthAlgorithms.cardinality() != 0
+            BitSet allowedAuthAlgorithms = securityParams.getAllowedAuthAlgorithms();
+            if (allowedAuthAlgorithms.cardinality() != 0
                     && !setAuthAlg(wifiConfigurationToSupplicantAuthAlgMask(
-                    config.allowedAuthAlgorithms))) {
+                    allowedAuthAlgorithms))) {
                 Log.e(TAG, "failed to set AuthAlgorithm");
                 return false;
             }
             /** Group Cipher */
-            if (config.allowedGroupCiphers.cardinality() != 0
+            BitSet allowedGroupCiphers = securityParams.getAllowedGroupCiphers();
+            if (allowedGroupCiphers.cardinality() != 0
                     && (!setGroupCipher(wifiConfigurationToSupplicantGroupCipherMask(
-                    config.allowedGroupCiphers)))) {
+                    allowedGroupCiphers)))) {
                 Log.e(TAG, "failed to set Group Cipher");
                 return false;
             }
             /** Pairwise Cipher*/
-            if (config.allowedPairwiseCiphers.cardinality() != 0
+            BitSet allowedPairwiseCiphers = securityParams.getAllowedPairwiseCiphers();
+            if (allowedPairwiseCiphers.cardinality() != 0
                     && !setPairwiseCipher(wifiConfigurationToSupplicantPairwiseCipherMask(
-                    config.allowedPairwiseCiphers))) {
+                    allowedPairwiseCiphers))) {
                 Log.e(TAG, "failed to set PairwiseCipher");
                 return false;
             }
@@ -362,13 +367,13 @@
             // For PSK, this can either be quoted ASCII passphrase or hex string for raw psk.
             // For SAE, password must be a quoted ASCII string
             if (config.preSharedKey != null) {
-                if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WAPI_PSK)) {
+                if (securityParams.isSecurityType(WifiConfiguration.SECURITY_TYPE_WAPI_PSK)) {
                     if (!setPskPassphrase(config.preSharedKey)) {
                         Log.e(TAG, "failed to set wapi psk passphrase");
                         return false;
                     }
                 } else if (config.preSharedKey.startsWith("\"")) {
-                    if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
+                    if (securityParams.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE)) {
                         /* WPA3 case, field is SAE Password */
                         if (!setSaePassword(
                                 NativeUtil.removeEnclosingQuotes(config.preSharedKey))) {
@@ -383,7 +388,7 @@
                         }
                     }
                 } else {
-                    if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
+                    if (securityParams.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE)) {
                         return false;
                     }
                     if (!setPsk(NativeUtil.hexStringToByteArray(config.preSharedKey))) {
@@ -418,7 +423,7 @@
             if (config.isPasspoint()) {
                 metadata.put(ID_STRING_KEY_FQDN, config.FQDN);
             }
-            metadata.put(ID_STRING_KEY_CONFIG_KEY, config.getKey());
+            metadata.put(ID_STRING_KEY_CONFIG_KEY, config.getProfileKey());
             metadata.put(ID_STRING_KEY_CREATOR_UID, Integer.toString(config.creatorUid));
             if (!setIdStr(createNetworkExtra(metadata))) {
                 Log.e(TAG, "failed to set id string");
@@ -430,6 +435,35 @@
                 Log.e(TAG, "failed to set update identifier");
                 return false;
             }
+            /** SAE configuration */
+            if (securityParams.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE)
+                    && getV1_4StaNetwork() != null) {
+                /**
+                 * Hash-to-Element preference.
+                 * For devices that don't support H2E, H2E mode will be permanently disabled.
+                 * Devices that support H2E will enable both legacy and H2E mode by default,
+                 * and will connect to SAE networks with H2E if possible, unless H2E only
+                 * mode is enabled, and then the device will not connect to SAE networks in
+                 * legacy mode.
+                 */
+                if (!mWifiGlobals.isWpa3SaeH2eSupported() && securityParams.isSaeH2eOnlyMode()) {
+                    Log.e(TAG, "This device does not support SAE H2E.");
+                    return false;
+                }
+                byte mode = mWifiGlobals.isWpa3SaeH2eSupported()
+                        ? android.hardware.wifi.supplicant.V1_4
+                                .ISupplicantStaNetwork.SaeH2eMode.H2E_OPTIONAL
+                        : android.hardware.wifi.supplicant.V1_4
+                                .ISupplicantStaNetwork.SaeH2eMode.DISABLED;
+                if (securityParams.isSaeH2eOnlyMode()) {
+                    mode = android.hardware.wifi.supplicant.V1_4
+                            .ISupplicantStaNetwork.SaeH2eMode.H2E_MANDATORY;
+                }
+                if (!setSaeH2eMode(mode)) {
+                    Log.e(TAG, "failed to set H2E preference.");
+                    return false;
+                }
+            }
             // Finish here if no EAP config to set
             if (config.enterpriseConfig != null
                     && config.enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE) {
@@ -449,16 +483,38 @@
             }
 
             // Now that the network is configured fully, start listening for callback events.
-            mISupplicantStaNetworkCallback =
-                    new SupplicantStaNetworkHalCallback(config.networkId, config.SSID);
-            if (!registerCallback(mISupplicantStaNetworkCallback)) {
-                Log.e(TAG, "Failed to register callback");
-                return false;
-            }
-            return true;
+            return tryRegisterCallback(config.networkId, config.SSID);
         }
     }
 
+    private boolean tryRegisterCallback_1_4(int networkId, String ssid) {
+        if (getV1_4StaNetwork() == null) return false;
+
+        SupplicantStaNetworkHalCallbackV1_4 callback =
+                new SupplicantStaNetworkHalCallbackV1_4(networkId, ssid);
+        if (!registerCallback_1_4(callback)) {
+            Log.e(TAG, "Failed to register V1.4 callback");
+            return false;
+        }
+        mISupplicantStaNetworkCallback = callback;
+        return true;
+    }
+
+    private boolean tryRegisterCallback(int networkId, String ssid) {
+        /* try newer version fist. */
+        if (getV1_4StaNetwork() != null) {
+            return tryRegisterCallback_1_4(networkId, ssid);
+        }
+
+        mISupplicantStaNetworkCallback =
+        new SupplicantStaNetworkHalCallback(networkId, ssid);
+        if (!registerCallback(mISupplicantStaNetworkCallback)) {
+            Log.e(TAG, "Failed to register callback");
+            return false;
+        }
+        return true;
+    }
+
     /**
      * Read network variables from wpa_supplicant into the provided WifiEnterpriseConfig object.
      *
@@ -561,34 +617,45 @@
      * @return true if succeeds, false otherwise.
      */
     private boolean saveSuiteBConfig(WifiConfiguration config) {
+        SecurityParams securityParams = config.getNetworkSelectionStatus()
+                .getCandidateSecurityParams();
+        if (null == securityParams) {
+            Log.wtf(TAG, "No available security params.");
+            return false;
+        }
+
         /** Group Cipher **/
-        if (config.allowedGroupCiphers.cardinality() != 0
+        BitSet allowedGroupCiphers = securityParams.getAllowedGroupCiphers();
+        if (allowedGroupCiphers.cardinality() != 0
                 && !setGroupCipher(wifiConfigurationToSupplicantGroupCipherMask(
-                config.allowedGroupCiphers))) {
+                allowedGroupCiphers))) {
             Log.e(TAG, "failed to set Group Cipher");
             return false;
         }
         /** Pairwise Cipher*/
-        if (config.allowedPairwiseCiphers.cardinality() != 0
+        BitSet allowedPairwiseCiphers = securityParams.getAllowedPairwiseCiphers();
+        if (allowedPairwiseCiphers.cardinality() != 0
                 && !setPairwiseCipher(wifiConfigurationToSupplicantPairwiseCipherMask(
-                config.allowedPairwiseCiphers))) {
+                allowedPairwiseCiphers))) {
             Log.e(TAG, "failed to set PairwiseCipher");
             return false;
         }
         /** GroupMgmt Cipher */
-        if (config.allowedGroupManagementCiphers.cardinality() != 0
+        BitSet allowedGroupManagementCiphers = securityParams.getAllowedGroupManagementCiphers();
+        if (allowedGroupManagementCiphers.cardinality() != 0
                 && !setGroupMgmtCipher(wifiConfigurationToSupplicantGroupMgmtCipherMask(
-                config.allowedGroupManagementCiphers))) {
+                allowedGroupManagementCiphers))) {
             Log.e(TAG, "failed to set GroupMgmtCipher");
             return false;
         }
 
-        if (config.allowedSuiteBCiphers.get(WifiConfiguration.SuiteBCipher.ECDHE_RSA)) {
+        BitSet allowedSuiteBCiphers = securityParams.getAllowedSuiteBCiphers();
+        if (allowedSuiteBCiphers.get(WifiConfiguration.SuiteBCipher.ECDHE_RSA)) {
             if (!enableTlsSuiteBEapPhase1Param(true)) {
                 Log.e(TAG, "failed to set TLSSuiteB");
                 return false;
             }
-        } else if (config.allowedSuiteBCiphers.get(WifiConfiguration.SuiteBCipher.ECDHE_ECDSA)) {
+        } else if (allowedSuiteBCiphers.get(WifiConfiguration.SuiteBCipher.ECDHE_ECDSA)) {
             if (!enableSuiteBEapOpenSslCiphers()) {
                 Log.e(TAG, "failed to set OpensslCipher");
                 return false;
@@ -630,10 +697,19 @@
             }
             /** EAP Anonymous Identity */
             eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.ANON_IDENTITY_KEY);
-            if (!TextUtils.isEmpty(eapParam)
-                    && !setEapAnonymousIdentity(NativeUtil.stringToByteArrayList(eapParam))) {
-                Log.e(TAG, ssid + ": failed to set eap anonymous identity: " + eapParam);
-                return false;
+            if (!TextUtils.isEmpty(eapParam)) {
+                if (null != getV1_4StaNetwork()) {
+                    String decoratedUsernamePrefix =
+                            eapConfig.getFieldValue(
+                                    WifiEnterpriseConfig.DECORATED_IDENTITY_PREFIX_KEY);
+                    if (!TextUtils.isEmpty(decoratedUsernamePrefix)) {
+                        eapParam = decoratedUsernamePrefix + eapParam;
+                    }
+                }
+                if (!setEapAnonymousIdentity(NativeUtil.stringToByteArrayList(eapParam))) {
+                    Log.e(TAG, ssid + ": failed to set eap anonymous identity: " + eapParam);
+                    return false;
+                }
             }
             /** EAP Password */
             eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.PASSWORD_KEY);
@@ -741,6 +817,12 @@
         }
     }
 
+    private android.hardware.wifi.supplicant.V1_4.ISupplicantStaNetwork getV1_4StaNetwork() {
+        synchronized (mLock) {
+            return getSupplicantStaNetworkForV1_4Mockable();
+        }
+    }
+
     /**
      * Maps WifiConfiguration Key Management BitSet to Supplicant HIDL bitmask int
      * TODO(b/32571829): Update mapping when fast transition keys are added
@@ -871,7 +953,7 @@
         return mask;
     }
 
-    private static int wifiConfigurationToSupplicantGroupCipherMask(BitSet groupCipherMask) {
+    private int wifiConfigurationToSupplicantGroupCipherMask(BitSet groupCipherMask) {
         int mask = 0;
         for (int bit = groupCipherMask.nextSetBit(0); bit != -1; bit =
                 groupCipherMask.nextSetBit(bit + 1)) {
@@ -892,12 +974,32 @@
                     mask |= ISupplicantStaNetwork.GroupCipherMask.GTK_NOT_USED;
                     break;
                 case WifiConfiguration.GroupCipher.GCMP_256:
+                    if (null == getV1_2StaNetwork()) {
+                        Log.d(TAG, "Ignore GCMP_256 cipher for the HAL older than 1.2.");
+                        break;
+                    }
+                    if (0 == (mAdvanceKeyMgmtFeatures & WifiManager.WIFI_FEATURE_WPA3_SUITE_B)) {
+                        Log.d(TAG, "Ignore unsupporting GCMP_256 cipher.");
+                        break;
+                    }
                     mask |= android.hardware.wifi.supplicant.V1_2.ISupplicantStaNetwork
                             .GroupCipherMask.GCMP_256;
                     break;
                 case WifiConfiguration.GroupCipher.SMS4:
-                    mask |= android.hardware.wifi.supplicant.V1_3.ISupplicantStaNetwork
+                    if (null != getV1_3StaNetwork()) {
+                        mask |= android.hardware.wifi.supplicant.V1_3.ISupplicantStaNetwork
                                 .GroupCipherMask.SMS4;
+                    } else {
+                        Log.d(TAG, "Ignore SMS4 cipher for the HAL older than 1.3.");
+                    }
+                    break;
+                case WifiConfiguration.GroupCipher.GCMP_128:
+                    if (null != getV1_4StaNetwork()) {
+                        mask |= android.hardware.wifi.supplicant.V1_4.ISupplicantStaNetwork
+                                .GroupCipherMask.GCMP_128;
+                    } else {
+                        Log.d(TAG, "Ignore GCMP_128 cipher for the HAL older than 1.4.");
+                    }
                     break;
                 default:
                     throw new IllegalArgumentException(
@@ -934,7 +1036,7 @@
         return mask;
     }
 
-    private static int wifiConfigurationToSupplicantPairwiseCipherMask(BitSet pairwiseCipherMask) {
+    private int wifiConfigurationToSupplicantPairwiseCipherMask(BitSet pairwiseCipherMask) {
         int mask = 0;
         for (int bit = pairwiseCipherMask.nextSetBit(0); bit != -1;
                 bit = pairwiseCipherMask.nextSetBit(bit + 1)) {
@@ -949,12 +1051,32 @@
                     mask |= ISupplicantStaNetwork.PairwiseCipherMask.CCMP;
                     break;
                 case WifiConfiguration.PairwiseCipher.GCMP_256:
+                    if (null == getV1_2StaNetwork()) {
+                        Log.d(TAG, "Ignore GCMP_256 cipher for the HAL older than 1.2.");
+                        break;
+                    }
+                    if (0 == (mAdvanceKeyMgmtFeatures & WifiManager.WIFI_FEATURE_WPA3_SUITE_B)) {
+                        Log.d(TAG, "Ignore unsupporting GCMP_256 cipher.");
+                        break;
+                    }
                     mask |= android.hardware.wifi.supplicant.V1_2.ISupplicantStaNetwork
                             .PairwiseCipherMask.GCMP_256;
                     break;
                 case WifiConfiguration.PairwiseCipher.SMS4:
-                    mask |= android.hardware.wifi.supplicant.V1_3.ISupplicantStaNetwork
-                            .PairwiseCipherMask.SMS4;
+                    if (null != getV1_3StaNetwork()) {
+                        mask |= android.hardware.wifi.supplicant.V1_3.ISupplicantStaNetwork
+                                .PairwiseCipherMask.SMS4;
+                    } else {
+                        Log.d(TAG, "Ignore SMS4 cipher for the HAL older than 1.3.");
+                    }
+                    break;
+                case WifiConfiguration.PairwiseCipher.GCMP_128:
+                    if (null != getV1_4StaNetwork()) {
+                        mask |= android.hardware.wifi.supplicant.V1_4.ISupplicantStaNetwork
+                                .PairwiseCipherMask.GCMP_128;
+                    } else {
+                        Log.d(TAG, "Ignore GCMP_128 cipher for the HAL older than 1.4.");
+                    }
                     break;
                 default:
                     throw new IllegalArgumentException(
@@ -1143,6 +1265,9 @@
         mask = supplicantMaskValueToWifiConfigurationBitSet(mask,
                 android.hardware.wifi.supplicant.V1_3.ISupplicantStaNetwork.GroupCipherMask
                         .SMS4, bitset, WifiConfiguration.GroupCipher.SMS4);
+        mask = supplicantMaskValueToWifiConfigurationBitSet(mask,
+                android.hardware.wifi.supplicant.V1_4.ISupplicantStaNetwork.GroupCipherMask
+                        .GCMP_128, bitset, WifiConfiguration.GroupCipher.GCMP_128);
         if (mask != 0) {
             throw new IllegalArgumentException(
                     "invalid group cipher mask from supplicant: " + mask);
@@ -1190,6 +1315,10 @@
                 android.hardware.wifi.supplicant.V1_3.ISupplicantStaNetwork.PairwiseCipherMask
                         .SMS4, bitset,
                 WifiConfiguration.PairwiseCipher.SMS4);
+        mask = supplicantMaskValueToWifiConfigurationBitSet(mask,
+                android.hardware.wifi.supplicant.V1_4.ISupplicantStaNetwork.PairwiseCipherMask
+                        .GCMP_128, bitset,
+                WifiConfiguration.PairwiseCipher.GCMP_128);
         if (mask != 0) {
             throw new IllegalArgumentException(
                     "invalid pairwise cipher mask from supplicant: " + mask);
@@ -1252,7 +1381,7 @@
             final String methodStr = "getId";
             if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
             try {
-                MutableBoolean statusOk = new MutableBoolean(false);
+                Mutable<Boolean> statusOk = new Mutable<>(false);
                 mISupplicantStaNetwork.getId((SupplicantStatus status, int idValue) -> {
                     statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
                     if (statusOk.value) {
@@ -1269,6 +1398,14 @@
         }
     }
 
+    /** get current network id */
+    public int getNetworkId() {
+        if (!getId()) {
+            return -1;
+        }
+        return mNetworkId;
+    }
+
     /** See ISupplicantStaNetwork.hal for documentation */
     private boolean registerCallback(ISupplicantStaNetworkCallback callback) {
         synchronized (mLock) {
@@ -1285,6 +1422,26 @@
     }
 
     /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean registerCallback_1_4(
+            android.hardware.wifi.supplicant.V1_4.ISupplicantStaNetworkCallback callback) {
+        synchronized (mLock) {
+            final String methodStr = "registerCallback_1_4";
+            android.hardware.wifi.supplicant.V1_4.ISupplicantStaNetwork
+                    iSupplicantStaNetworkV14 = getV1_4StaNetwork();
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            if (iSupplicantStaNetworkV14 == null) return false;
+            try {
+                android.hardware.wifi.supplicant.V1_4.SupplicantStatus status =
+                        iSupplicantStaNetworkV14.registerCallback_1_4(callback);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /** See ISupplicantStaNetwork.hal for documentation */
     private boolean setSsid(java.util.ArrayList<Byte> ssid) {
         synchronized (mLock) {
             final String methodStr = "setSsid";
@@ -1354,14 +1511,14 @@
             try {
                 SupplicantStatus status;
                 android.hardware.wifi.supplicant.V1_2.ISupplicantStaNetwork
-                        iSupplicantStaNetworkV12;
-                iSupplicantStaNetworkV12 = getV1_2StaNetwork();
-
-                if (getV1_3StaNetwork() != null) {
+                        iSupplicantStaNetworkV12 = getV1_2StaNetwork();
+                android.hardware.wifi.supplicant.V1_3.ISupplicantStaNetwork
+                        iSupplicantStaNetworkV13 = getV1_3StaNetwork();
+                if (null != iSupplicantStaNetworkV13) {
                     /* Support for new key management types:
                      * WAPI_PSK, WAPI_CERT
                      * Requires HAL v1.3 or higher */
-                    status = getV1_3StaNetwork().setKeyMgmt_1_3(keyMgmtMask);
+                    status = iSupplicantStaNetworkV13.setKeyMgmt_1_3(keyMgmtMask);
                 } else if (iSupplicantStaNetworkV12 != null) {
                     /* Support for new key management types;
                      * SAE, OWE, WPA_PSK_SHA256, WPA_EAP_SHA256
@@ -1384,12 +1541,14 @@
             final String methodStr = "setProto";
             if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
             try {
+                android.hardware.wifi.supplicant.V1_3.ISupplicantStaNetwork
+                        iSupplicantStaNetworkV13 = getV1_3StaNetwork();
                 SupplicantStatus status;
-                if (null != getV1_3StaNetwork()) {
+                if (null != iSupplicantStaNetworkV13) {
                     /* Support for new proto types: WAPI
                      * Requires HAL v1.3 or higher
                      */
-                    status = getV1_3StaNetwork().setProto_1_3(protoMask);
+                    status = iSupplicantStaNetworkV13.setProto_1_3(protoMask);
                 } else {
                     status = mISupplicantStaNetwork.setProto(protoMask);
                 }
@@ -1407,10 +1566,12 @@
             final String methodStr = "setAuthAlg";
             if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
             try {
+                android.hardware.wifi.supplicant.V1_3.ISupplicantStaNetwork
+                        iSupplicantStaNetworkV13 = getV1_3StaNetwork();
                 SupplicantStatus status;
-                if (null != getV1_3StaNetwork()) {
+                if (null != iSupplicantStaNetworkV13) {
                     /* Support for SAE Authentication algorithm requires HAL v1.3 or higher */
-                    status = getV1_3StaNetwork().setAuthAlg_1_3(authAlgMask);
+                    status = iSupplicantStaNetworkV13.setAuthAlg_1_3(authAlgMask);
                 } else {
                     status = mISupplicantStaNetwork.setAuthAlg(authAlgMask);
                 }
@@ -1423,6 +1584,24 @@
     }
 
     /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean setGroupCipher_1_4(int groupCipherMask) {
+        synchronized (mLock) {
+            final String methodStr = "setGroupCipher_1_4";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            android.hardware.wifi.supplicant.V1_4.ISupplicantStaNetwork
+                    iSupplicantStaNetworkV14 = getV1_4StaNetwork();
+            if (null == iSupplicantStaNetworkV14) return false;
+            try {
+                return checkStatusAndLogFailure(
+                        iSupplicantStaNetworkV14.setGroupCipher_1_4(groupCipherMask),
+                        methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
     private boolean setGroupCipher(int groupCipherMask) {
         synchronized (mLock) {
             final String methodStr = "setGroupCipher";
@@ -1430,20 +1609,22 @@
             try {
                 SupplicantStatus status;
                 android.hardware.wifi.supplicant.V1_2.ISupplicantStaNetwork
-                        iSupplicantStaNetworkV12;
-                iSupplicantStaNetworkV12 = getV1_2StaNetwork();
-                if (null != getV1_3StaNetwork()) {
+                        iSupplicantStaNetworkV12 = getV1_2StaNetwork();
+                android.hardware.wifi.supplicant.V1_3.ISupplicantStaNetwork
+                        iSupplicantStaNetworkV13 = getV1_3StaNetwork();
+                if (null != getV1_4StaNetwork()) {
+                    /* Support for new key group cipher types for GCMP_128
+                     * Requires HAL v1.4 or higher */
+                    return setGroupCipher_1_4(groupCipherMask);
+                } else if (null != iSupplicantStaNetworkV13) {
                     /* Support for new key group cipher types for SMS4
                      * Requires HAL v1.3 or higher */
-                    status = getV1_3StaNetwork().setGroupCipher_1_3(groupCipherMask);
+                    status = iSupplicantStaNetworkV13.setGroupCipher_1_3(groupCipherMask);
                 } else if (iSupplicantStaNetworkV12 != null) {
                     /* Support for new key group cipher types for SuiteB
                      * Requires HAL v1.2 or higher */
                     status = iSupplicantStaNetworkV12.setGroupCipher_1_2(groupCipherMask);
                 } else {
-                    // Clear GCMP_256 group cipher which is not supported before v1.2
-                    groupCipherMask &= ~android.hardware.wifi.supplicant.V1_2.ISupplicantStaNetwork
-                            .GroupCipherMask.GCMP_256;
                     status = mISupplicantStaNetwork.setGroupCipher(
                             groupCipherMask);
                 }
@@ -1510,6 +1691,25 @@
     }
 
     /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean setPairwiseCipher_1_4(int pairwiseCipherMask) {
+        synchronized (mLock) {
+            final String methodStr = "setPairwiseCipher_1_4";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            android.hardware.wifi.supplicant.V1_4.ISupplicantStaNetwork
+                    iSupplicantStaNetworkV14 = getV1_4StaNetwork();
+            if (null == iSupplicantStaNetworkV14) return false;
+            try {
+                return checkStatusAndLogFailure(
+                        iSupplicantStaNetworkV14.setPairwiseCipher_1_4(pairwiseCipherMask),
+                        methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /** See ISupplicantStaNetwork.hal for documentation */
     private boolean setPairwiseCipher(int pairwiseCipherMask) {
         synchronized (mLock) {
             final String methodStr = "setPairwiseCipher";
@@ -1517,20 +1717,22 @@
             try {
                 SupplicantStatus status;
                 android.hardware.wifi.supplicant.V1_2.ISupplicantStaNetwork
-                        iSupplicantStaNetworkV12;
-                iSupplicantStaNetworkV12 = getV1_2StaNetwork();
-                if (null != getV1_3StaNetwork()) {
+                        iSupplicantStaNetworkV12 = getV1_2StaNetwork();
+                android.hardware.wifi.supplicant.V1_3.ISupplicantStaNetwork
+                        iSupplicantStaNetworkV13 = getV1_3StaNetwork();
+                if (null != getV1_4StaNetwork()) {
+                    /* Support for new key pairwise cipher types for GCMP_128
+                     * Requires HAL v1.4 or higher */
+                    return setPairwiseCipher_1_4(pairwiseCipherMask);
+                } else if (null != iSupplicantStaNetworkV13) {
                     /* Support for new key pairwise cipher types for SMS4
                      * Requires HAL v1.3 or higher */
-                    status = getV1_3StaNetwork().setPairwiseCipher_1_3(pairwiseCipherMask);
+                    status = iSupplicantStaNetworkV13.setPairwiseCipher_1_3(pairwiseCipherMask);
                 } else if (iSupplicantStaNetworkV12 != null) {
                     /* Support for new key pairwise cipher types for SuiteB
                      * Requires HAL v1.2 or higher */
                     status = iSupplicantStaNetworkV12.setPairwiseCipher_1_2(pairwiseCipherMask);
                 } else {
-                    // Clear GCMP_256 pairwise cipher which is not supported before v1.2
-                    pairwiseCipherMask &= ~android.hardware.wifi.supplicant.V1_2
-                            .ISupplicantStaNetwork.PairwiseCipherMask.GCMP_256;
                     status =
                             mISupplicantStaNetwork.setPairwiseCipher(pairwiseCipherMask);
                 }
@@ -1667,9 +1869,11 @@
             final String methodStr = "setWapiCertSuite";
             if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
             try {
-                if (null != getV1_3StaNetwork()) {
+                android.hardware.wifi.supplicant.V1_3.ISupplicantStaNetwork
+                        iSupplicantStaNetworkV13 = getV1_3StaNetwork();
+                if (null != iSupplicantStaNetworkV13) {
                     /* Requires HAL v1.3 or higher */
-                    SupplicantStatus status = getV1_3StaNetwork().setWapiCertSuite(certSuite);
+                    SupplicantStatus status = iSupplicantStaNetworkV13.setWapiCertSuite(certSuite);
                     return checkStatusAndLogFailure(status, methodStr);
                 } else {
                     Log.e(TAG, "Cannot get ISupplicantStaNetwork V1.3");
@@ -1976,7 +2180,7 @@
             final String methodStr = "getSsid";
             if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
             try {
-                MutableBoolean statusOk = new MutableBoolean(false);
+                Mutable<Boolean> statusOk = new Mutable<>(false);
                 mISupplicantStaNetwork.getSsid((SupplicantStatus status,
                         java.util.ArrayList<Byte> ssidValue) -> {
                     statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
@@ -2000,7 +2204,7 @@
             final String methodStr = "getBssid";
             if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
             try {
-                MutableBoolean statusOk = new MutableBoolean(false);
+                Mutable<Boolean> statusOk = new Mutable<>(false);
                 mISupplicantStaNetwork.getBssid((SupplicantStatus status,
                         byte[/* 6 */] bssidValue) -> {
                     statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
@@ -2024,7 +2228,7 @@
             final String methodStr = "getScanSsid";
             if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
             try {
-                MutableBoolean statusOk = new MutableBoolean(false);
+                Mutable<Boolean> statusOk = new Mutable<>(false);
                 mISupplicantStaNetwork.getScanSsid((SupplicantStatus status,
                         boolean enabledValue) -> {
                     statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
@@ -2051,7 +2255,7 @@
                 return getKeyMgmt_1_3();
             } else {
                 try {
-                    MutableBoolean statusOk = new MutableBoolean(false);
+                    Mutable<Boolean> statusOk = new Mutable<>(false);
                     mISupplicantStaNetwork.getKeyMgmt((SupplicantStatus status,
                             int keyMgmtMaskValue) -> {
                         statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
@@ -2074,8 +2278,11 @@
         synchronized (mLock) {
             final String methodStr = "getKeyMgmt_1_3";
             try {
-                MutableBoolean statusOk = new MutableBoolean(false);
-                getV1_3StaNetwork().getKeyMgmt_1_3((SupplicantStatus status,
+                android.hardware.wifi.supplicant.V1_3.ISupplicantStaNetwork
+                        iSupplicantStaNetworkV13 = getV1_3StaNetwork();
+                if (null == iSupplicantStaNetworkV13) return false;
+                Mutable<Boolean> statusOk = new Mutable<>(false);
+                iSupplicantStaNetworkV13.getKeyMgmt_1_3((SupplicantStatus status,
                         int keyMgmtMaskValue) -> {
                     statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
                     if (statusOk.value) {
@@ -2101,7 +2308,7 @@
                 return getProto_1_3();
             } else {
                 try {
-                    MutableBoolean statusOk = new MutableBoolean(false);
+                    Mutable<Boolean> statusOk = new Mutable<>(false);
                     mISupplicantStaNetwork.getProto(
                             (SupplicantStatus status, int protoMaskValue) -> {
                             statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
@@ -2124,8 +2331,12 @@
         synchronized (mLock) {
             final String methodStr = "getProto_1_3";
             try {
-                MutableBoolean statusOk = new MutableBoolean(false);
-                getV1_3StaNetwork().getProto((SupplicantStatus status, int protoMaskValue) -> {
+                android.hardware.wifi.supplicant.V1_3.ISupplicantStaNetwork
+                        iSupplicantStaNetworkV13 = getV1_3StaNetwork();
+                if (null == iSupplicantStaNetworkV13) return false;
+                Mutable<Boolean> statusOk = new Mutable<>(false);
+                iSupplicantStaNetworkV13.getProto_1_3(
+                        (SupplicantStatus status, int protoMaskValue) -> {
                     statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
                     if (statusOk.value) {
                         this.mProtoMask = protoMaskValue;
@@ -2150,7 +2361,7 @@
                 return getAuthAlg_1_3();
             }
             try {
-                MutableBoolean statusOk = new MutableBoolean(false);
+                Mutable<Boolean> statusOk = new Mutable<>(false);
                 mISupplicantStaNetwork.getAuthAlg((SupplicantStatus status,
                         int authAlgMaskValue) -> {
                     statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
@@ -2171,8 +2382,11 @@
     private boolean getAuthAlg_1_3() {
         final String methodStr = "getAuthAlg_1_3";
         try {
-            MutableBoolean statusOk = new MutableBoolean(false);
-            getV1_3StaNetwork().getAuthAlg_1_3((SupplicantStatus status,
+            android.hardware.wifi.supplicant.V1_3.ISupplicantStaNetwork
+                    iSupplicantStaNetworkV13 = getV1_3StaNetwork();
+            if (null == iSupplicantStaNetworkV13) return false;
+            Mutable<Boolean> statusOk = new Mutable<>(false);
+            iSupplicantStaNetworkV13.getAuthAlg_1_3((SupplicantStatus status,
                     int authAlgMaskValue) -> {
                 statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
                 if (statusOk.value) {
@@ -2193,11 +2407,13 @@
         synchronized (mLock) {
             final String methodStr = "getGroupCipher";
             if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
-            if (getV1_3StaNetwork() != null) {
+            if (getV1_4StaNetwork() != null) {
+                return getGroupCipher_1_4();
+            } else if (getV1_3StaNetwork() != null) {
                 return getGroupCipher_1_3();
             } else {
                 try {
-                    MutableBoolean statusOk = new MutableBoolean(false);
+                    Mutable<Boolean> statusOk = new Mutable<>(false);
                     mISupplicantStaNetwork.getGroupCipher((SupplicantStatus status,
                             int groupCipherMaskValue) -> {
                         statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
@@ -2220,8 +2436,37 @@
         synchronized (mLock) {
             final String methodStr = "getGroupCipher_1_3";
             try {
-                MutableBoolean statusOk = new MutableBoolean(false);
-                getV1_3StaNetwork().getGroupCipher((SupplicantStatus status,
+                android.hardware.wifi.supplicant.V1_3.ISupplicantStaNetwork
+                        iSupplicantStaNetworkV13 = getV1_3StaNetwork();
+                if (null == iSupplicantStaNetworkV13) return false;
+                Mutable<Boolean> statusOk = new Mutable<>(false);
+                iSupplicantStaNetworkV13.getGroupCipher_1_3((SupplicantStatus status,
+                        int groupCipherMaskValue) -> {
+                    statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
+                    if (statusOk.value) {
+                        this.mGroupCipherMask = groupCipherMaskValue;
+                    } else {
+                        checkStatusAndLogFailure(status, methodStr);
+                    }
+                });
+                return statusOk.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    private boolean getGroupCipher_1_4() {
+        synchronized (mLock) {
+            final String methodStr = "getGroupCipher_1_4";
+            try {
+                android.hardware.wifi.supplicant.V1_4.ISupplicantStaNetwork
+                        iSupplicantStaNetworkV14 = getV1_4StaNetwork();
+                if (null == iSupplicantStaNetworkV14) return false;
+                Mutable<Boolean> statusOk = new Mutable<>(false);
+                iSupplicantStaNetworkV14.getGroupCipher_1_4((
+                        android.hardware.wifi.supplicant.V1_4.SupplicantStatus status,
                         int groupCipherMaskValue) -> {
                     statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
                     if (statusOk.value) {
@@ -2243,11 +2488,13 @@
         synchronized (mLock) {
             final String methodStr = "getPairwiseCipher";
             if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
-            if (getV1_3StaNetwork() != null) {
+            if (getV1_4StaNetwork() != null) {
+                return getPairwiseCipher_1_4();
+            } else if (getV1_3StaNetwork() != null) {
                 return getPairwiseCipher_1_3();
             } else {
                 try {
-                    MutableBoolean statusOk = new MutableBoolean(false);
+                    Mutable<Boolean> statusOk = new Mutable<>(false);
                     mISupplicantStaNetwork.getPairwiseCipher((SupplicantStatus status,
                             int pairwiseCipherMaskValue) -> {
                         statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
@@ -2270,8 +2517,37 @@
         synchronized (mLock) {
             final String methodStr = "getPairwiseCipher_1_3";
             try {
-                MutableBoolean statusOk = new MutableBoolean(false);
-                getV1_3StaNetwork().getPairwiseCipher((SupplicantStatus status,
+                android.hardware.wifi.supplicant.V1_3.ISupplicantStaNetwork
+                        iSupplicantStaNetworkV13 = getV1_3StaNetwork();
+                if (null == iSupplicantStaNetworkV13) return false;
+                Mutable<Boolean> statusOk = new Mutable<>(false);
+                iSupplicantStaNetworkV13.getPairwiseCipher_1_3((SupplicantStatus status,
+                        int pairwiseCipherMaskValue) -> {
+                    statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
+                    if (statusOk.value) {
+                        this.mPairwiseCipherMask = pairwiseCipherMaskValue;
+                    } else {
+                        checkStatusAndLogFailure(status, methodStr);
+                    }
+                });
+                return statusOk.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    private boolean getPairwiseCipher_1_4() {
+        synchronized (mLock) {
+            final String methodStr = "getPairwiseCipher_1_4";
+            try {
+                android.hardware.wifi.supplicant.V1_4.ISupplicantStaNetwork
+                        iSupplicantStaNetworkV14 = getV1_4StaNetwork();
+                if (null == iSupplicantStaNetworkV14) return false;
+                Mutable<Boolean> statusOk = new Mutable<>(false);
+                iSupplicantStaNetworkV14.getPairwiseCipher_1_4((
+                        android.hardware.wifi.supplicant.V1_4.SupplicantStatus status,
                         int pairwiseCipherMaskValue) -> {
                     statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
                     if (statusOk.value) {
@@ -2299,7 +2575,7 @@
             try {
                 iSupplicantStaNetworkV12 = getV1_2StaNetwork();
                 if (iSupplicantStaNetworkV12 != null) {
-                    MutableBoolean statusOk = new MutableBoolean(false);
+                    Mutable<Boolean> statusOk = new Mutable<>(false);
                     iSupplicantStaNetworkV12.getGroupMgmtCipher((SupplicantStatus status,
                             int groupMgmtCipherMaskValue) -> {
                         statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
@@ -2325,7 +2601,7 @@
             final String methodStr = "getPskPassphrase";
             if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
             try {
-                MutableBoolean statusOk = new MutableBoolean(false);
+                Mutable<Boolean> statusOk = new Mutable<>(false);
                 mISupplicantStaNetwork.getPskPassphrase((SupplicantStatus status,
                         String pskValue) -> {
                     statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
@@ -2354,7 +2630,7 @@
             try {
                 iSupplicantStaNetworkV12 = getV1_2StaNetwork();
                 if (iSupplicantStaNetworkV12 != null) {
-                    MutableBoolean statusOk = new MutableBoolean(false);
+                    Mutable<Boolean> statusOk = new Mutable<>(false);
                     iSupplicantStaNetworkV12.getSaePassword((SupplicantStatus status,
                             String saePassword) -> {
                         statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
@@ -2380,7 +2656,7 @@
             final String methodStr = "getPsk";
             if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
             try {
-                MutableBoolean statusOk = new MutableBoolean(false);
+                Mutable<Boolean> statusOk = new Mutable<>(false);
                 mISupplicantStaNetwork.getPsk((SupplicantStatus status, byte[] pskValue) -> {
                     statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
                     if (statusOk.value) {
@@ -2403,7 +2679,7 @@
             final String methodStr = "keyIdx";
             if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
             try {
-                MutableBoolean statusOk = new MutableBoolean(false);
+                Mutable<Boolean> statusOk = new Mutable<>(false);
                 mISupplicantStaNetwork.getWepKey(keyIdx, (SupplicantStatus status,
                         java.util.ArrayList<Byte> wepKeyValue) -> {
                     statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
@@ -2427,7 +2703,7 @@
             final String methodStr = "getWepTxKeyIdx";
             if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
             try {
-                MutableBoolean statusOk = new MutableBoolean(false);
+                Mutable<Boolean> statusOk = new Mutable<>(false);
                 mISupplicantStaNetwork.getWepTxKeyIdx((SupplicantStatus status,
                         int keyIdxValue) -> {
                     statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
@@ -2451,7 +2727,7 @@
             final String methodStr = "getRequirePmf";
             if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
             try {
-                MutableBoolean statusOk = new MutableBoolean(false);
+                Mutable<Boolean> statusOk = new Mutable<>(false);
                 mISupplicantStaNetwork.getRequirePmf((SupplicantStatus status,
                         boolean enabledValue) -> {
                     statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
@@ -2479,7 +2755,7 @@
                         iSupplicantStaNetworkV13;
                 iSupplicantStaNetworkV13 = getV1_3StaNetwork();
                 if (iSupplicantStaNetworkV13 != null) {
-                    MutableBoolean statusOk = new MutableBoolean(false);
+                    Mutable<Boolean> statusOk = new Mutable<>(false);
                     iSupplicantStaNetworkV13.getWapiCertSuite((SupplicantStatus status,
                             String suiteValue) -> {
                         statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
@@ -2507,7 +2783,7 @@
             final String methodStr = "getEapMethod";
             if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
             try {
-                MutableBoolean statusOk = new MutableBoolean(false);
+                Mutable<Boolean> statusOk = new Mutable<>(false);
                 mISupplicantStaNetwork.getEapMethod((SupplicantStatus status,
                         int methodValue) -> {
                     statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
@@ -2531,7 +2807,7 @@
             final String methodStr = "getEapPhase2Method";
             if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
             try {
-                MutableBoolean statusOk = new MutableBoolean(false);
+                Mutable<Boolean> statusOk = new Mutable<>(false);
                 mISupplicantStaNetwork.getEapPhase2Method((SupplicantStatus status,
                         int methodValue) -> {
                     statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
@@ -2555,7 +2831,7 @@
             final String methodStr = "getEapIdentity";
             if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
             try {
-                MutableBoolean statusOk = new MutableBoolean(false);
+                Mutable<Boolean> statusOk = new Mutable<>(false);
                 mISupplicantStaNetwork.getEapIdentity((SupplicantStatus status,
                         ArrayList<Byte> identityValue) -> {
                     statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
@@ -2579,7 +2855,7 @@
             final String methodStr = "getEapAnonymousIdentity";
             if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
             try {
-                MutableBoolean statusOk = new MutableBoolean(false);
+                Mutable<Boolean> statusOk = new Mutable<>(false);
                 mISupplicantStaNetwork.getEapAnonymousIdentity((SupplicantStatus status,
                         ArrayList<Byte> identityValue) -> {
                     statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
@@ -2618,7 +2894,7 @@
             final String methodStr = "getEapPassword";
             if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
             try {
-                MutableBoolean statusOk = new MutableBoolean(false);
+                Mutable<Boolean> statusOk = new Mutable<>(false);
                 mISupplicantStaNetwork.getEapPassword((SupplicantStatus status,
                         ArrayList<Byte> passwordValue) -> {
                     statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
@@ -2642,7 +2918,7 @@
             final String methodStr = "getEapCACert";
             if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
             try {
-                MutableBoolean statusOk = new MutableBoolean(false);
+                Mutable<Boolean> statusOk = new Mutable<>(false);
                 mISupplicantStaNetwork.getEapCACert((SupplicantStatus status, String pathValue) -> {
                     statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
                     if (statusOk.value) {
@@ -2665,7 +2941,7 @@
             final String methodStr = "getEapCAPath";
             if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
             try {
-                MutableBoolean statusOk = new MutableBoolean(false);
+                Mutable<Boolean> statusOk = new Mutable<>(false);
                 mISupplicantStaNetwork.getEapCAPath((SupplicantStatus status, String pathValue) -> {
                     statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
                     if (statusOk.value) {
@@ -2688,7 +2964,7 @@
             final String methodStr = "getEapClientCert";
             if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
             try {
-                MutableBoolean statusOk = new MutableBoolean(false);
+                Mutable<Boolean> statusOk = new Mutable<>(false);
                 mISupplicantStaNetwork.getEapClientCert((SupplicantStatus status,
                         String pathValue) -> {
                     statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
@@ -2712,7 +2988,7 @@
             final String methodStr = "getEapPrivateKeyId";
             if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
             try {
-                MutableBoolean statusOk = new MutableBoolean(false);
+                Mutable<Boolean> statusOk = new Mutable<>(false);
                 mISupplicantStaNetwork.getEapPrivateKeyId((SupplicantStatus status,
                         String idValue) -> {
                     statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
@@ -2736,7 +3012,7 @@
             final String methodStr = "getEapSubjectMatch";
             if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
             try {
-                MutableBoolean statusOk = new MutableBoolean(false);
+                Mutable<Boolean> statusOk = new Mutable<>(false);
                 mISupplicantStaNetwork.getEapSubjectMatch((SupplicantStatus status,
                         String matchValue) -> {
                     statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
@@ -2760,7 +3036,7 @@
             final String methodStr = "getEapAltSubjectMatch";
             if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
             try {
-                MutableBoolean statusOk = new MutableBoolean(false);
+                Mutable<Boolean> statusOk = new Mutable<>(false);
                 mISupplicantStaNetwork.getEapAltSubjectMatch((SupplicantStatus status,
                         String matchValue) -> {
                     statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
@@ -2784,7 +3060,7 @@
             final String methodStr = "getEapEngine";
             if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
             try {
-                MutableBoolean statusOk = new MutableBoolean(false);
+                Mutable<Boolean> statusOk = new Mutable<>(false);
                 mISupplicantStaNetwork.getEapEngine((SupplicantStatus status,
                         boolean enabledValue) -> {
                     statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
@@ -2808,7 +3084,7 @@
             final String methodStr = "getEapEngineID";
             if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
             try {
-                MutableBoolean statusOk = new MutableBoolean(false);
+                Mutable<Boolean> statusOk = new Mutable<>(false);
                 mISupplicantStaNetwork.getEapEngineID((SupplicantStatus status, String idValue) -> {
                     statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
                     if (statusOk.value) {
@@ -2831,7 +3107,7 @@
             final String methodStr = "getEapDomainSuffixMatch";
             if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
             try {
-                MutableBoolean statusOk = new MutableBoolean(false);
+                Mutable<Boolean> statusOk = new Mutable<>(false);
                 mISupplicantStaNetwork.getEapDomainSuffixMatch((SupplicantStatus status,
                         String matchValue) -> {
                     statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
@@ -2855,7 +3131,7 @@
             final String methodStr = "getIdStr";
             if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
             try {
-                MutableBoolean statusOk = new MutableBoolean(false);
+                Mutable<Boolean> statusOk = new Mutable<>(false);
                 mISupplicantStaNetwork.getIdStr((SupplicantStatus status, String idString) -> {
                     statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
                     if (statusOk.value) {
@@ -2873,7 +3149,7 @@
     }
 
     /** See ISupplicantStaNetwork.hal for documentation */
-    private boolean enable(boolean noConnect) {
+    public boolean enable(boolean noConnect) {
         synchronized (mLock) {
             final String methodStr = "enable";
             if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
@@ -2888,7 +3164,7 @@
     }
 
     /** See ISupplicantStaNetwork.hal for documentation */
-    private boolean disable() {
+    public boolean disable() {
         synchronized (mLock) {
             final String methodStr = "disable";
             if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
@@ -3158,6 +3434,19 @@
     }
 
     /**
+     * Method to mock out the V1_4 ISupplicantStaNetwork retrieval in unit tests.
+     *
+     * @return 1.4 ISupplicantStaNetwork object if the device is running the 1.4 supplicant hal
+     * service, null otherwise.
+     */
+    protected android.hardware.wifi.supplicant.V1_4.ISupplicantStaNetwork
+            getSupplicantStaNetworkForV1_4Mockable() {
+        if (mISupplicantStaNetwork == null) return null;
+        return android.hardware.wifi.supplicant.V1_4.ISupplicantStaNetwork.castFrom(
+                mISupplicantStaNetwork);
+    }
+
+    /**
      * Send eap identity response.
      *
      * @param identityStr          identity used for EAP-Identity
@@ -3263,7 +3552,7 @@
                         iSupplicantStaNetworkV13;
                 iSupplicantStaNetworkV13 = getV1_3StaNetwork();
                 if (iSupplicantStaNetworkV13 != null) {
-                    MutableBoolean statusOk = new MutableBoolean(false);
+                    Mutable<Boolean> statusOk = new Mutable<>(false);
                     iSupplicantStaNetworkV13.getOcsp((SupplicantStatus status,
                             int halOcspValue) -> {
                         statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
@@ -3329,6 +3618,30 @@
         }
     }
 
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean setSaeH2eMode(byte mode) {
+        synchronized (mLock) {
+            final String methodStr = "setSaeH2eMode";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+
+            try {
+                android.hardware.wifi.supplicant.V1_4.ISupplicantStaNetwork
+                        iSupplicantStaNetworkV14 = getV1_4StaNetwork();
+                if (iSupplicantStaNetworkV14 != null) {
+                    android.hardware.wifi.supplicant.V1_4.SupplicantStatus status =
+                            iSupplicantStaNetworkV14.setSaeH2eMode(mode);
+                    return checkStatusAndLogFailure(status, methodStr);
+                } else {
+                    Log.e(TAG, "Cannot get ISupplicantStaNetwork V1.4");
+                    return false;
+                }
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
     /**
      * Retrieve the NFC token for this network.
      *
@@ -3364,6 +3677,10 @@
         }
     }
 
+    private String getTag() {
+        return TAG + "[" + mIfaceName + "]";
+    }
+
     /**
      * Returns true if provided status code is SUCCESS, logs debug message and returns false
      * otherwise
@@ -3371,6 +3688,27 @@
     private boolean checkStatusAndLogFailure(SupplicantStatus status, final String methodStr) {
         synchronized (mLock) {
             if (status.code != SupplicantStatusCode.SUCCESS) {
+                Log.e(getTag(), "ISupplicantStaNetwork." + methodStr + " failed: " + status);
+                return false;
+            } else {
+                if (mVerboseLoggingEnabled) {
+                    Log.d(getTag(), "ISupplicantStaNetwork." + methodStr + " succeeded");
+                }
+                return true;
+            }
+        }
+    }
+
+    /**
+     * Returns true if provided status code is SUCCESS, logs debug message and returns false
+     * otherwise
+     */
+    private boolean checkStatusAndLogFailure(
+            android.hardware.wifi.supplicant.V1_4.SupplicantStatus status,
+            final String methodStr) {
+        synchronized (mLock) {
+            if (status.code
+                    != android.hardware.wifi.supplicant.V1_4.SupplicantStatusCode.SUCCESS) {
                 Log.e(TAG, "ISupplicantStaNetwork." + methodStr + " failed: " + status);
                 return false;
             } else {
@@ -3385,7 +3723,7 @@
     /**
      * Helper function to log callbacks.
      */
-    private void logCallback(final String methodStr) {
+    protected void logCallback(final String methodStr) {
         synchronized (mLock) {
             if (mVerboseLoggingEnabled) {
                 Log.d(TAG, "ISupplicantStaNetworkCallback." + methodStr + " received");
@@ -3482,6 +3820,40 @@
     }
 
     /**
+     * Adds both PSK and SAE AKM if auto-upgrade offload is supported.
+     */
+    private BitSet addPskSaeUpgradableTypeFlagsIfSupported(
+            WifiConfiguration config,
+            BitSet keyManagementFlags) {
+        synchronized (mLock) {
+            if (!config.isSecurityType(WifiConfiguration.SECURITY_TYPE_PSK)
+                    || !config.getSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK).isEnabled()
+                    || !mWifiGlobals.isWpa3SaeUpgradeOffloadEnabled()) {
+                return keyManagementFlags;
+            }
+
+            BitSet modifiedFlags = (BitSet) keyManagementFlags.clone();
+            modifiedFlags.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+            modifiedFlags.set(WifiConfiguration.KeyMgmt.SAE);
+            return modifiedFlags;
+        }
+    }
+
+    /**
+     * Removes SAE AKM when PSK and SAE AKM are both set, it only happens when
+     * auto-upgrade offload is supported.
+     */
+    private BitSet removePskSaeUpgradableTypeFlags(BitSet keyManagementFlags) {
+        if (!keyManagementFlags.get(WifiConfiguration.KeyMgmt.WPA_PSK)
+                || !keyManagementFlags.get(WifiConfiguration.KeyMgmt.SAE)) {
+            return keyManagementFlags;
+        }
+        BitSet modifiedFlags = (BitSet) keyManagementFlags.clone();
+        modifiedFlags.clear(WifiConfiguration.KeyMgmt.SAE);
+        return modifiedFlags;
+    }
+
+    /**
      * Creates the JSON encoded network extra using the map of string key, value pairs.
      */
     public static String createNetworkExtra(Map<String, String> values) {
@@ -3532,56 +3904,20 @@
         }
     }
 
-    private class SupplicantStaNetworkHalCallback extends ISupplicantStaNetworkCallback.Stub {
-        /**
-         * Current configured network's framework network id.
-         */
-        private final int mFramewokNetworkId;
-        /**
-         * Current configured network's ssid.
-         */
-        private final String mSsid;
-
-        SupplicantStaNetworkHalCallback(int framewokNetworkId, String ssid) {
-            mFramewokNetworkId = framewokNetworkId;
-            mSsid = ssid;
+    protected class SupplicantStaNetworkHalCallbackV1_4
+            extends SupplicantStaNetworkCallbackV1_4Impl {
+        SupplicantStaNetworkHalCallbackV1_4(int frameworkNetworkId, String ssid) {
+            super(SupplicantStaNetworkHal.this,
+                    frameworkNetworkId, ssid,
+                    mIfaceName, mLock, mWifiMonitor);
         }
+    }
 
-        @Override
-        public void onNetworkEapSimGsmAuthRequest(
-                ISupplicantStaNetworkCallback.NetworkRequestEapSimGsmAuthParams params) {
-            synchronized (mLock) {
-                logCallback("onNetworkEapSimGsmAuthRequest");
-                String[] data = new String[params.rands.size()];
-                int i = 0;
-                for (byte[] rand : params.rands) {
-                    data[i++] = NativeUtil.hexStringFromByteArray(rand);
-                }
-                mWifiMonitor.broadcastNetworkGsmAuthRequestEvent(
-                        mIfaceName, mFramewokNetworkId, mSsid, data);
-            }
-        }
-
-        @Override
-        public void onNetworkEapSimUmtsAuthRequest(
-                ISupplicantStaNetworkCallback.NetworkRequestEapSimUmtsAuthParams params) {
-            synchronized (mLock) {
-                logCallback("onNetworkEapSimUmtsAuthRequest");
-                String randHex = NativeUtil.hexStringFromByteArray(params.rand);
-                String autnHex = NativeUtil.hexStringFromByteArray(params.autn);
-                String[] data = {randHex, autnHex};
-                mWifiMonitor.broadcastNetworkUmtsAuthRequestEvent(
-                        mIfaceName, mFramewokNetworkId, mSsid, data);
-            }
-        }
-
-        @Override
-        public void onNetworkEapIdentityRequest() {
-            synchronized (mLock) {
-                logCallback("onNetworkEapIdentityRequest");
-                mWifiMonitor.broadcastNetworkIdentityRequestEvent(
-                        mIfaceName, mFramewokNetworkId, mSsid);
-            }
+    protected class SupplicantStaNetworkHalCallback extends SupplicantStaNetworkCallbackImpl {
+        SupplicantStaNetworkHalCallback(int frameworkNetworkId, String ssid) {
+            super(SupplicantStaNetworkHal.this,
+                    frameworkNetworkId, ssid,
+                    mIfaceName, mLock, mWifiMonitor);
         }
     }
 }
diff --git a/service/java/com/android/server/wifi/SupplicantStateTracker.java b/service/java/com/android/server/wifi/SupplicantStateTracker.java
index 19540d4..7a4c95a 100644
--- a/service/java/com/android/server/wifi/SupplicantStateTracker.java
+++ b/service/java/com/android/server/wifi/SupplicantStateTracker.java
@@ -16,13 +16,16 @@
 
 package com.android.server.wifi;
 
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_PRIMARY;
+
+import android.annotation.NonNull;
 import android.content.Context;
 import android.content.Intent;
 import android.net.wifi.SupplicantState;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
 import android.os.BatteryStatsManager;
-import android.os.Handler;
+import android.os.Looper;
 import android.os.Message;
 import android.os.Parcelable;
 import android.os.UserHandle;
@@ -39,14 +42,22 @@
  * that is based on these state changes:
  * - detect a failed WPA handshake that loops indefinitely
  * - authentication failure handling
+ * TODO(b/159944009): Need to rework this class to handle make before break transition on STA +
+ * STA devices (Apps will not see SUPPLICANT_STATE_CHANGED_ACTION when switching wifi networks)
  */
 public class SupplicantStateTracker extends StateMachine {
 
     private static final String TAG = "SupplicantStateTracker";
-    private static boolean DBG = false;
+
+    private final Context mContext;
     private final WifiConfigManager mWifiConfigManager;
-    private FrameworkFacade mFacade;
+    private final WifiMonitor mWifiMonitor;
     private final BatteryStatsManager mBatteryStatsManager;
+    private final String mInterfaceName;
+    private final ClientModeManager mClientModeManager;
+    private final ClientModeManagerBroadcastQueue mBroadcastQueue;
+
+    private boolean mVerboseLoggingEnabled = false;
     /* Indicates authentication failure in supplicant broadcast.
      * TODO: enhance auth failure reporting to include notification
      * for all type of failures: EAP, WPS & WPA networks */
@@ -60,66 +71,95 @@
      */
     private int mAuthFailureReason;
 
-    /* Maximum retries on a authentication failure notification */
-    private static final int MAX_RETRIES_ON_AUTHENTICATION_FAILURE = 2;
-
-    /* Maximum retries on assoc rejection events */
-    private static final int MAX_RETRIES_ON_ASSOCIATION_REJECT = 16;
-
-    private final Context mContext;
-
     private final State mUninitializedState = new UninitializedState();
     private final State mDefaultState = new DefaultState();
     private final State mInactiveState = new InactiveState();
     private final State mDisconnectState = new DisconnectedState();
     private final State mScanState = new ScanState();
-    private final State mConnectionActiveState = new ConnectionActiveState();
     private final State mHandshakeState = new HandshakeState();
     private final State mCompletedState = new CompletedState();
     private final State mDormantState = new DormantState();
 
-    void enableVerboseLogging(int verbose) {
-        if (verbose > 0) {
-            DBG = true;
-        } else {
-            DBG = false;
-        }
+    /** enable/disable verbose logging. */
+    public void enableVerboseLogging(boolean verbose) {
+        mVerboseLoggingEnabled = verbose;
     }
 
-    public String getSupplicantStateName() {
-        return getCurrentState().getName();
+    private String getTag() {
+        return TAG + "[" + (mInterfaceName == null ? "unknown" : mInterfaceName) + "]";
     }
 
-    public SupplicantStateTracker(Context c, WifiConfigManager wcs,
-            BatteryStatsManager batteryStatsManager, Handler t) {
-        super(TAG, t.getLooper());
-
-        mContext = c;
-        mWifiConfigManager = wcs;
+    public SupplicantStateTracker(
+            @NonNull Context context,
+            @NonNull WifiConfigManager wifiConfigManager,
+            @NonNull BatteryStatsManager batteryStatsManager,
+            @NonNull Looper looper,
+            @NonNull WifiMonitor wifiMonitor,
+            @NonNull String interfaceName,
+            @NonNull ClientModeManager clientModeManager,
+            @NonNull ClientModeManagerBroadcastQueue broadcastQueue) {
+        super(TAG, looper);
+        mContext = context;
+        mWifiConfigManager = wifiConfigManager;
         mBatteryStatsManager = batteryStatsManager;
-        // CHECKSTYLE:OFF IndentationCheck
-        addState(mDefaultState);
+        mWifiMonitor = wifiMonitor;
+        mInterfaceName = interfaceName;
+        mClientModeManager = clientModeManager;
+        mBroadcastQueue = broadcastQueue;
+
+        registerForWifiMonitorEvents();
+
+        addState(mDefaultState); {
+            // disconnected states
             addState(mUninitializedState, mDefaultState);
             addState(mInactiveState, mDefaultState);
             addState(mDisconnectState, mDefaultState);
-            addState(mConnectionActiveState, mDefaultState);
-                addState(mScanState, mConnectionActiveState);
-                addState(mHandshakeState, mConnectionActiveState);
-                addState(mCompletedState, mConnectionActiveState);
-                addState(mDormantState, mConnectionActiveState);
-        // CHECKSTYLE:ON IndentationCheck
+            // connected states
+            addState(mScanState, mDefaultState);
+            addState(mHandshakeState, mDefaultState);
+            addState(mCompletedState, mDefaultState);
+            addState(mDormantState, mDefaultState);
+        }
 
         setInitialState(mUninitializedState);
         setLogRecSize(50);
         setLogOnlyTransitions(true);
-        //start the state machine
+        // start the state machine
         start();
     }
 
+    private static final int[] WIFI_MONITOR_EVENTS = {
+            WifiMonitor.ASSOCIATION_REJECTION_EVENT,
+            WifiMonitor.AUTHENTICATION_FAILURE_EVENT,
+            WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT,
+    };
+
+    private void registerForWifiMonitorEvents() {
+        for (int event : WIFI_MONITOR_EVENTS) {
+            mWifiMonitor.registerHandler(mInterfaceName, event, getHandler());
+        }
+    }
+
+    private void deregisterForWifiMonitorEvents() {
+        for (int event : WIFI_MONITOR_EVENTS) {
+            mWifiMonitor.deregisterHandler(mInterfaceName, event, getHandler());
+        }
+    }
+
+    /**
+     * Called when the owner ClientModeImpl is stopped. No more actions shall be performed on this
+     * instance after it is stopped.
+     */
+    public void stop() {
+        deregisterForWifiMonitorEvents();
+
+        quitNow();
+    }
+
     private void handleNetworkConnectionFailure(int netId, int disableReason) {
-        if (DBG) {
-            Log.d(TAG, "handleNetworkConnectionFailure netId=" + Integer.toString(netId)
-                    + " reason " + Integer.toString(disableReason));
+        if (mVerboseLoggingEnabled) {
+            Log.d(getTag(), "handleNetworkConnectionFailure netId=" + netId
+                    + " reason " + disableReason);
         }
 
         /* update network status */
@@ -127,12 +167,14 @@
     }
 
     private void transitionOnSupplicantStateChange(StateChangeResult stateChangeResult) {
-        SupplicantState supState = (SupplicantState) stateChangeResult.state;
+        SupplicantState supState = stateChangeResult.state;
 
-        if (DBG) Log.d(TAG, "Supplicant state: " + supState.toString() + "\n");
+        if (mVerboseLoggingEnabled) {
+            Log.d(getTag(), "Supplicant state: " + supState.toString() + "\n");
+        }
 
         switch (supState) {
-           case DISCONNECTED:
+            case DISCONNECTED:
                 transitionTo(mDisconnectState);
                 break;
             case INTERFACE_DISABLED:
@@ -162,64 +204,66 @@
                 transitionTo(mUninitializedState);
                 break;
             default:
-                Log.e(TAG, "Unknown supplicant state " + supState);
+                Log.e(getTag(), "Unknown supplicant state " + supState);
                 break;
         }
     }
 
-    private void sendSupplicantStateChangedBroadcast(SupplicantState state, boolean failedAuth) {
-        sendSupplicantStateChangedBroadcast(state, failedAuth, WifiManager.ERROR_AUTH_FAILURE_NONE);
-    }
-
-    private void sendSupplicantStateChangedBroadcast(SupplicantState state, boolean failedAuth,
-            int reasonCode) {
-        int supplState;
+    private static int supplicantStateToBatteryStatsSupplicantState(SupplicantState state) {
         switch (state) {
             case DISCONNECTED:
-                supplState = BatteryStatsManager.WIFI_SUPPL_STATE_DISCONNECTED;
-                break;
+                return BatteryStatsManager.WIFI_SUPPL_STATE_DISCONNECTED;
             case INTERFACE_DISABLED:
-                supplState = BatteryStatsManager.WIFI_SUPPL_STATE_INTERFACE_DISABLED;
-                break;
+                return BatteryStatsManager.WIFI_SUPPL_STATE_INTERFACE_DISABLED;
             case INACTIVE:
-                supplState = BatteryStatsManager.WIFI_SUPPL_STATE_INACTIVE;
-                break;
+                return BatteryStatsManager.WIFI_SUPPL_STATE_INACTIVE;
             case SCANNING:
-                supplState = BatteryStatsManager.WIFI_SUPPL_STATE_SCANNING;
-                break;
+                return BatteryStatsManager.WIFI_SUPPL_STATE_SCANNING;
             case AUTHENTICATING:
-                supplState = BatteryStatsManager.WIFI_SUPPL_STATE_AUTHENTICATING;
-                break;
+                return BatteryStatsManager.WIFI_SUPPL_STATE_AUTHENTICATING;
             case ASSOCIATING:
-                supplState = BatteryStatsManager.WIFI_SUPPL_STATE_ASSOCIATING;
-                break;
+                return BatteryStatsManager.WIFI_SUPPL_STATE_ASSOCIATING;
             case ASSOCIATED:
-                supplState = BatteryStatsManager.WIFI_SUPPL_STATE_ASSOCIATED;
-                break;
+                return BatteryStatsManager.WIFI_SUPPL_STATE_ASSOCIATED;
             case FOUR_WAY_HANDSHAKE:
-                supplState = BatteryStatsManager.WIFI_SUPPL_STATE_FOUR_WAY_HANDSHAKE;
-                break;
+                return BatteryStatsManager.WIFI_SUPPL_STATE_FOUR_WAY_HANDSHAKE;
             case GROUP_HANDSHAKE:
-                supplState = BatteryStatsManager.WIFI_SUPPL_STATE_GROUP_HANDSHAKE;
-                break;
+                return BatteryStatsManager.WIFI_SUPPL_STATE_GROUP_HANDSHAKE;
             case COMPLETED:
-                supplState = BatteryStatsManager.WIFI_SUPPL_STATE_COMPLETED;
-                break;
+                return BatteryStatsManager.WIFI_SUPPL_STATE_COMPLETED;
             case DORMANT:
-                supplState = BatteryStatsManager.WIFI_SUPPL_STATE_DORMANT;
-                break;
+                return BatteryStatsManager.WIFI_SUPPL_STATE_DORMANT;
             case UNINITIALIZED:
-                supplState = BatteryStatsManager.WIFI_SUPPL_STATE_UNINITIALIZED;
-                break;
+                return BatteryStatsManager.WIFI_SUPPL_STATE_UNINITIALIZED;
             case INVALID:
-                supplState = BatteryStatsManager.WIFI_SUPPL_STATE_INVALID;
-                break;
+                return BatteryStatsManager.WIFI_SUPPL_STATE_INVALID;
             default:
                 Log.w(TAG, "Unknown supplicant state " + state);
-                supplState = BatteryStatsManager.WIFI_SUPPL_STATE_INVALID;
-                break;
+                return BatteryStatsManager.WIFI_SUPPL_STATE_INVALID;
         }
-        mBatteryStatsManager.reportWifiSupplicantStateChanged(supplState, failedAuth);
+    }
+
+    private void sendSupplicantStateChangedBroadcast(
+            SupplicantState state, boolean failedAuth, int reasonCode) {
+        int supplState = supplicantStateToBatteryStatsSupplicantState(state);
+        if (mClientModeManager.getRole() == ROLE_CLIENT_PRIMARY) {
+            mBatteryStatsManager.reportWifiSupplicantStateChanged(supplState, failedAuth);
+        }
+        String summary = "broadcast=SUPPLICANT_STATE_CHANGED_ACTION"
+                + " state=" + state
+                + " failedAuth=" + failedAuth
+                + " reasonCode=" + reasonCode;
+        if (mVerboseLoggingEnabled) Log.d(TAG, "Queuing " + summary);
+        mBroadcastQueue.queueOrSendBroadcast(
+                mClientModeManager,
+                () -> {
+                    if (mVerboseLoggingEnabled) Log.d(TAG, "Sending " + summary);
+                    sendSupplicantStateChangedBroadcast(mContext, state, failedAuth, reasonCode);
+                });
+    }
+
+    private static void sendSupplicantStateChangedBroadcast(
+            Context context, SupplicantState state, boolean failedAuth, int reasonCode) {
         Intent intent = new Intent(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
                 | Intent.FLAG_RECEIVER_REPLACE_PENDING);
@@ -232,7 +276,7 @@
                     WifiManager.EXTRA_SUPPLICANT_ERROR_REASON,
                     reasonCode);
         }
-        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+        context.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
     /********************************************************
@@ -241,12 +285,12 @@
 
     class DefaultState extends State {
         @Override
-         public void enter() {
-             if (DBG) Log.d(TAG, getName() + "\n");
-         }
+        public void enter() {
+            if (mVerboseLoggingEnabled) Log.d(getTag(), getName() + "\n");
+        }
         @Override
         public boolean processMessage(Message message) {
-            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            if (mVerboseLoggingEnabled) Log.d(getTag(), getName() + message.toString() + "\n");
             switch (message.what) {
                 case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
                     mAuthFailureInSupplicantBroadcast = true;
@@ -261,12 +305,9 @@
                     mAuthFailureReason = WifiManager.ERROR_AUTH_FAILURE_NONE;
                     transitionOnSupplicantStateChange(stateChangeResult);
                     break;
-                case ClientModeImpl.CMD_RESET_SUPPLICANT_STATE:
-                    transitionTo(mUninitializedState);
-                    break;
                 case WifiMonitor.ASSOCIATION_REJECTION_EVENT:
                 default:
-                    Log.e(TAG, "Ignoring " + message);
+                    Log.e(getTag(), "Ignoring " + message);
                     break;
             }
             return HANDLED;
@@ -283,42 +324,29 @@
      */
     class UninitializedState extends State {
         @Override
-         public void enter() {
-             if (DBG) Log.d(TAG, getName() + "\n");
-         }
+        public void enter() {
+            if (mVerboseLoggingEnabled) Log.d(getTag(), getName() + "\n");
+        }
     }
 
     class InactiveState extends State {
         @Override
-         public void enter() {
-             if (DBG) Log.d(TAG, getName() + "\n");
-         }
+        public void enter() {
+            if (mVerboseLoggingEnabled) Log.d(getTag(), getName() + "\n");
+        }
     }
 
     class DisconnectedState extends State {
         @Override
-         public void enter() {
-             if (DBG) Log.d(TAG, getName() + "\n");
-         }
+        public void enter() {
+            if (mVerboseLoggingEnabled) Log.d(getTag(), getName() + "\n");
+        }
     }
 
     class ScanState extends State {
         @Override
-         public void enter() {
-             if (DBG) Log.d(TAG, getName() + "\n");
-         }
-    }
-
-    /* Meta-state that processes supplicant disconnections and broadcasts this event. */
-    class ConnectionActiveState extends State {
-        @Override
-        public boolean processMessage(Message message) {
-            if (message.what == ClientModeImpl.CMD_RESET_SUPPLICANT_STATE) {
-                sendSupplicantStateChangedBroadcast(SupplicantState.DISCONNECTED, false);
-            }
-
-            /* Let parent states handle the state possible transition. */
-            return NOT_HANDLED;
+        public void enter() {
+            if (mVerboseLoggingEnabled) Log.d(getTag(), getName() + "\n");
         }
     }
 
@@ -332,14 +360,14 @@
         private int mLoopDetectCount;
 
         @Override
-         public void enter() {
-             if (DBG) Log.d(TAG, getName() + "\n");
-             mLoopDetectIndex = 0;
-             mLoopDetectCount = 0;
-         }
+        public void enter() {
+            if (mVerboseLoggingEnabled) Log.d(getTag(), getName() + "\n");
+            mLoopDetectIndex = 0;
+            mLoopDetectCount = 0;
+        }
         @Override
         public boolean processMessage(Message message) {
-            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            if (mVerboseLoggingEnabled) Log.d(getTag(), getName() + message.toString() + "\n");
             switch (message.what) {
                 case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
                     StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
@@ -349,8 +377,8 @@
                             mLoopDetectCount++;
                         }
                         if (mLoopDetectCount > MAX_SUPPLICANT_LOOP_ITERATIONS) {
-                            Log.d(TAG, "Supplicant loop detected, disabling network " +
-                                    stateChangeResult.networkId);
+                            Log.d(getTag(), "Supplicant loop detected, disabling network "
+                                    + stateChangeResult.networkId);
                             handleNetworkConnectionFailure(stateChangeResult.networkId,
                                     WifiConfiguration.NetworkSelectionStatus
                                             .DISABLED_AUTHENTICATION_FAILURE);
@@ -372,13 +400,13 @@
 
     class CompletedState extends State {
         @Override
-         public void enter() {
-             if (DBG) Log.d(TAG, getName() + "\n");
+        public void enter() {
+            if (mVerboseLoggingEnabled) Log.d(getTag(), getName() + "\n");
         }
         @Override
         public boolean processMessage(Message message) {
-            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
-            switch(message.what) {
+            if (mVerboseLoggingEnabled) Log.d(getTag(), getName() + message.toString() + "\n");
+            switch (message.what) {
                 case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
                     StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
                     SupplicantState state = stateChangeResult.state;
@@ -404,7 +432,7 @@
     class DormantState extends State {
         @Override
         public void enter() {
-            if (DBG) Log.d(TAG, getName() + "\n");
+            if (mVerboseLoggingEnabled) Log.d(getTag(), getName() + "\n");
         }
     }
 
diff --git a/service/java/com/android/server/wifi/ThroughputPredictor.java b/service/java/com/android/server/wifi/ThroughputPredictor.java
index 5cd8a5c..ff5d10d 100644
--- a/service/java/com/android/server/wifi/ThroughputPredictor.java
+++ b/service/java/com/android/server/wifi/ThroughputPredictor.java
@@ -100,6 +100,7 @@
     private static final int MAX_NUM_SPATIAL_STREAM_11N = 4;
     private static final int MAX_NUM_SPATIAL_STREAM_LEGACY = 1;
 
+    private static final int B_MODE_MAX_MBPS = 11;
     private final Context mContext;
 
     ThroughputPredictor(Context context) {
@@ -121,7 +122,8 @@
      * @return predicted maximum Tx throughput in Mbps
      */
     public int predictMaxTxThroughput(@NonNull WifiNative.ConnectionCapabilities capabilities) {
-        return predictThroughputInternal(capabilities.wifiStandard, capabilities.channelBandwidth,
+        return predictThroughputInternal(capabilities.wifiStandard, capabilities.is11bMode,
+                capabilities.channelBandwidth,
                 WifiInfo.MAX_RSSI, capabilities.maxNumberTxSpatialStreams, MIN_CHANNEL_UTILIZATION);
     }
 
@@ -131,7 +133,8 @@
      * @return predicted maximum Rx throughput in Mbps
      */
     public int predictMaxRxThroughput(@NonNull WifiNative.ConnectionCapabilities capabilities) {
-        return predictThroughputInternal(capabilities.wifiStandard, capabilities.channelBandwidth,
+        return predictThroughputInternal(capabilities.wifiStandard, capabilities.is11bMode,
+                capabilities.channelBandwidth,
                 WifiInfo.MAX_RSSI, capabilities.maxNumberRxSpatialStreams, MIN_CHANNEL_UTILIZATION);
     }
 
@@ -143,7 +146,8 @@
             int rssiDbm, int frequency, int channelUtilization) {
         int channelUtilizationFinal = getValidChannelUtilization(frequency,
                 INVALID, channelUtilization, false);
-        return predictThroughputInternal(capabilities.wifiStandard, capabilities.channelBandwidth,
+        return predictThroughputInternal(capabilities.wifiStandard, capabilities.is11bMode,
+                capabilities.channelBandwidth,
                 rssiDbm, capabilities.maxNumberTxSpatialStreams, channelUtilizationFinal);
     }
 
@@ -155,7 +159,8 @@
             int rssiDbm, int frequency, int channelUtilization) {
         int channelUtilizationFinal = getValidChannelUtilization(frequency,
                 INVALID, channelUtilization, false);
-        return predictThroughputInternal(capabilities.wifiStandard, capabilities.channelBandwidth,
+        return predictThroughputInternal(capabilities.wifiStandard, capabilities.is11bMode,
+                capabilities.channelBandwidth,
                 rssiDbm, capabilities.maxNumberRxSpatialStreams, channelUtilizationFinal);
     }
 
@@ -257,11 +262,11 @@
                 channelUtilizationLinkLayerStats,
                 isBluetoothConnected);
 
-        return predictThroughputInternal(wifiStandard, channelWidth, rssiDbm, maxNumSpatialStream,
-                channelUtilization);
+        return predictThroughputInternal(wifiStandard, false/* is11bMode */, channelWidth,
+                rssiDbm, maxNumSpatialStream, channelUtilization);
     }
 
-    private int predictThroughputInternal(@WifiStandard int wifiStandard,
+    private int predictThroughputInternal(@WifiStandard int wifiStandard, boolean is11bMode,
             int channelWidth, int rssiDbm, int maxNumSpatialStream,  int channelUtilization) {
 
         // channel bandwidth in MHz = 20MHz * (2 ^ channelWidthFactor);
@@ -276,6 +281,7 @@
         if (wifiStandard == ScanResult.WIFI_STANDARD_UNKNOWN) {
             return WifiInfo.LINK_SPEED_UNKNOWN;
         } else if (wifiStandard == ScanResult.WIFI_STANDARD_LEGACY) {
+            // For simplicity, use legacy OFDM parameters to predict 11b rate
             numTonePerSym = NUM_TONE_PER_SYM_LEGACY;
             channelWidthFactor = 0;
             maxNumSpatialStream = MAX_NUM_SPATIAL_STREAM_LEGACY;
@@ -344,6 +350,9 @@
 
         int throughputMbps = (phyRateMbps * airTimeFraction) / MAX_CHANNEL_UTILIZATION;
 
+        if (is11bMode) {
+            throughputMbps = Math.min(throughputMbps, B_MODE_MAX_MBPS);
+        }
         if (mVerboseLoggingEnabled) {
             StringBuilder sb = new StringBuilder();
             Log.d(TAG, sb.append(" BW: ").append(channelWidth)
diff --git a/service/java/com/android/server/wifi/ThroughputScorer.java b/service/java/com/android/server/wifi/ThroughputScorer.java
index ed0d0da..139ca72 100644
--- a/service/java/com/android/server/wifi/ThroughputScorer.java
+++ b/service/java/com/android/server/wifi/ThroughputScorer.java
@@ -52,8 +52,21 @@
     // config_wifi_framework_RSSI_SCORE_SLOPE
     public static final int RSSI_SCORE_SLOPE_IS_4 = 4;
 
+    /**
+     * Sample scoring buckets (assumes default overlay bucket sizes for metered, saved, etc):
+     * 0 -> 500: OEM private
+     * 500 -> 1000: OEM paid
+     * 1000 -> 1500: untrusted 3rd party
+     * 1500 -> 2000: untrusted carrier
+     * 2000 -> 2500: metered suggestions
+     * 2500 -> 3000: metered saved
+     * 3000 -> 3500: unmetered suggestions
+     * 3500 -> 4000: unmetered saved
+     */
     public static final int TRUSTED_AWARD = 1000;
     public static final int HALF_TRUSTED_AWARD = 1000 / 2;
+    public static final int NOT_OEM_PAID_AWARD = 500;
+    public static final int NOT_OEM_PRIVATE_AWARD = 500;
 
     private static final boolean USE_USER_CONNECT_CHOICE = true;
 
@@ -69,7 +82,7 @@
     /**
      * Calculates an individual candidate's score.
      */
-    private ScoredCandidate scoreCandidate(Candidate candidate) {
+    private ScoredCandidate scoreCandidate(Candidate candidate, boolean currentNetworkHasInternet) {
         int rssiSaturationThreshold = mScoringParams.getSufficientRssi(candidate.getFrequency());
         int rssi = Math.min(candidate.getScanRssi(), rssiSaturationThreshold);
         int rssiBaseScore = (rssi + RSSI_SCORE_OFFSET) * RSSI_SCORE_SLOPE_IS_4;
@@ -97,7 +110,6 @@
         int savedNetworkAward = candidate.isEphemeral() ? 0 : mScoringParams.getSavedNetworkBonus();
 
         int trustedAward = TRUSTED_AWARD;
-
         if (!candidate.isTrusted()) {
             savedNetworkAward = 0; // Saved networks are not untrusted, but clear anyway
             unmeteredAward = 0; // Ignore metered for untrusted networks
@@ -111,9 +123,31 @@
             }
         }
 
+        int notOemPaidAward = NOT_OEM_PAID_AWARD;
+        if (candidate.isOemPaid()) {
+            savedNetworkAward = 0; // Saved networks are not oem paid, but clear anyway
+            unmeteredAward = 0; // Ignore metered for oem paid networks
+            trustedAward = 0; // Ignore untrusted for oem paid networks.
+            notOemPaidAward = 0;
+        }
+
+        int notOemPrivateAward = NOT_OEM_PRIVATE_AWARD;
+        if (candidate.isOemPrivate()) {
+            savedNetworkAward = 0; // Saved networks are not oem paid, but clear anyway
+            unmeteredAward = 0; // Ignore metered for oem paid networks
+            trustedAward = 0; // Ignore untrusted for oem paid networks.
+            notOemPaidAward = 0;
+            notOemPrivateAward = 0;
+        }
+
         int score = rssiBaseScore + throughputBonusScore
                 + currentNetworkBoost + securityAward + unmeteredAward + savedNetworkAward
-                + trustedAward;
+                + trustedAward + notOemPaidAward + notOemPrivateAward;
+
+        // do not select a network that has no internet when the current network has internet.
+        if (currentNetworkHasInternet && !candidate.isCurrentNetwork() && unExpectedNoInternet) {
+            score = 0;
+        }
 
         if (candidate.getLastSelectionWeight() > 0.0) {
             // Put a recently-selected network in a tier above everything else,
@@ -129,6 +163,8 @@
                     + " unmeteredAward: " + unmeteredAward
                     + " savedNetworkAward: " + savedNetworkAward
                     + " trustedAward: " + trustedAward
+                    + " notOemPaidAward: " + notOemPaidAward
+                    + " notOemPrivateAward: " + notOemPrivateAward
                     + " final score: " + score);
         }
 
@@ -146,11 +182,21 @@
         return Math.min(throughputScoreRaw, mScoringParams.getThroughputBonusLimit());
     }
 
+    private boolean doesAnyCurrentNetworksHaveInternet(@NonNull Collection<Candidate> candidates) {
+        for (Candidate candidate : candidates) {
+            if (candidate.isCurrentNetwork() && !candidate.hasNoInternetAccess()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     @Override
     public ScoredCandidate scoreCandidates(@NonNull Collection<Candidate> candidates) {
         ScoredCandidate choice = ScoredCandidate.NONE;
+        boolean currentNetworkHasInternet = doesAnyCurrentNetworksHaveInternet(candidates);
         for (Candidate candidate : candidates) {
-            ScoredCandidate scoredCandidate = scoreCandidate(candidate);
+            ScoredCandidate scoredCandidate = scoreCandidate(candidate, currentNetworkHasInternet);
             if (scoredCandidate.value > choice.value) {
                 choice = scoredCandidate;
             }
diff --git a/service/java/com/android/server/wifi/VelocityBasedConnectedScore.java b/service/java/com/android/server/wifi/VelocityBasedConnectedScore.java
index b931689..38f1ee1 100644
--- a/service/java/com/android/server/wifi/VelocityBasedConnectedScore.java
+++ b/service/java/com/android/server/wifi/VelocityBasedConnectedScore.java
@@ -18,6 +18,7 @@
 
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiInfo;
+import android.util.Log;
 
 import com.android.server.wifi.util.KalmanFilter;
 import com.android.server.wifi.util.Matrix;
@@ -28,6 +29,7 @@
  */
 public class VelocityBasedConnectedScore extends ConnectedScore {
 
+    public static final String TAG = "WifiVelocityBasedConnectedScore";
     private final ScoringParams mScoringParams;
 
     private int mFrequency = ScanResult.BAND_5_GHZ_START_FREQ_MHZ;
@@ -80,20 +82,25 @@
     @Override
     public void updateUsingRssi(int rssi, long millis, double standardDeviation) {
         if (millis <= 0) return;
-        if (mLastMillis <= 0 || millis < mLastMillis || mFilter.mx == null) {
-            double initialVariance = 9.0 * standardDeviation * standardDeviation;
-            mFilter.mx = new Matrix(1, new double[]{rssi, 0.0});
-            mFilter.mP = new Matrix(2, new double[]{initialVariance, 0.0, 0.0, 0.0});
-        } else {
-            double dt = (millis - mLastMillis) * 0.001;
-            mFilter.mR.put(0, 0, standardDeviation * standardDeviation);
-            setDeltaTimeSeconds(dt);
-            mFilter.predict();
-            mFilter.update(new Matrix(1, new double[]{rssi}));
+        try {
+            if (mLastMillis <= 0 || millis < mLastMillis || mFilter.mx == null) {
+                double initialVariance = 9.0 * standardDeviation * standardDeviation;
+                mFilter.mx = new Matrix(1, new double[]{rssi, 0.0});
+                mFilter.mP = new Matrix(2, new double[]{initialVariance, 0.0, 0.0, 0.0});
+            } else {
+                double dt = (millis - mLastMillis) * 0.001;
+                mFilter.mR.put(0, 0, standardDeviation * standardDeviation);
+                setDeltaTimeSeconds(dt);
+                mFilter.predict();
+                mFilter.update(new Matrix(1, new double[]{rssi}));
+            }
+            mLastMillis = millis;
+            mFilteredRssi = mFilter.mx.get(0, 0);
+            mEstimatedRateOfRssiChange = mFilter.mx.get(1, 0);
+        } catch (RuntimeException e) {
+            Log.wtf(TAG, e);
+            reset();
         }
-        mLastMillis = millis;
-        mFilteredRssi = mFilter.mx.get(0, 0);
-        mEstimatedRateOfRssiChange = mFilter.mx.get(1, 0);
     }
 
     /**
@@ -116,7 +123,7 @@
     private double mEstimatedRateOfRssiChange;
 
     /**
-     * Returns the most recently computed extimate of the RSSI.
+     * Returns the most recently computed estimate of the RSSI.
      */
     public double getFilteredRssi() {
         return mFilteredRssi;
diff --git a/service/java/com/android/server/wifi/WakeupConfigStoreData.java b/service/java/com/android/server/wifi/WakeupConfigStoreData.java
index 074393b..6681202 100644
--- a/service/java/com/android/server/wifi/WakeupConfigStoreData.java
+++ b/service/java/com/android/server/wifi/WakeupConfigStoreData.java
@@ -17,6 +17,7 @@
 package com.android.server.wifi;
 
 import android.annotation.Nullable;
+import android.net.wifi.SecurityParams;
 import android.util.ArraySet;
 import android.util.Log;
 
@@ -45,6 +46,12 @@
     private static final String XML_TAG_NETWORK_SECTION = "Network";
     private static final String XML_TAG_SSID = "SSID";
     private static final String XML_TAG_SECURITY = "Security";
+    private static final String XML_TAG_SECURITY_PARAMS_LIST = "SecurityParamsList";
+    private static final String XML_TAG_SECURITY_PARAMS = "SecurityParams";
+    private static final String XML_TAG_SECURITY_TYPE = "SecurityType";
+    private static final String XML_TAG_SAE_IS_H2E_ONLY_MODE = "SaeIsH2eOnlyMode";
+    private static final String XML_TAG_SAE_IS_PK_ONLY_MODE = "SaeIsPkOnlyMode";
+    private static final String XML_TAG_IS_ADDED_BY_AUTO_UPGRADE = "IsAddedByAutoUpgrade";
 
     private final DataSource<Boolean> mIsActiveDataSource;
     private final DataSource<Boolean> mIsOnboardedDataSource;
@@ -125,6 +132,29 @@
         XmlUtil.writeNextSectionEnd(out, XML_TAG_FEATURE_STATE_SECTION);
     }
 
+    private void writeSecurityParamsList(
+            XmlSerializer out, ScanResultMatchInfo scanResultMatchInfo)
+            throws XmlPullParserException, IOException {
+        XmlUtil.writeNextSectionStart(out, XML_TAG_SECURITY_PARAMS_LIST);
+        for (SecurityParams params: scanResultMatchInfo.securityParamsList) {
+            XmlUtil.writeNextSectionStart(out, XML_TAG_SECURITY_PARAMS);
+            XmlUtil.writeNextValue(
+                    out, XML_TAG_SECURITY_TYPE,
+                    params.getSecurityType());
+            XmlUtil.writeNextValue(
+                    out, XML_TAG_SAE_IS_H2E_ONLY_MODE,
+                    params.isSaeH2eOnlyMode());
+            XmlUtil.writeNextValue(
+                    out, XML_TAG_SAE_IS_PK_ONLY_MODE,
+                    params.isSaePkOnlyMode());
+            XmlUtil.writeNextValue(
+                    out, XML_TAG_IS_ADDED_BY_AUTO_UPGRADE,
+                    params.isAddedByAutoUpgrade());
+            XmlUtil.writeNextSectionEnd(out, XML_TAG_SECURITY_PARAMS);
+        }
+        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECURITY_PARAMS_LIST);
+    }
+
     /**
      * Writes a {@link ScanResultMatchInfo} to an XML output stream.
      *
@@ -138,7 +168,7 @@
         XmlUtil.writeNextSectionStart(out, XML_TAG_NETWORK_SECTION);
 
         XmlUtil.writeNextValue(out, XML_TAG_SSID, scanResultMatchInfo.networkSsid);
-        XmlUtil.writeNextValue(out, XML_TAG_SECURITY, scanResultMatchInfo.networkType);
+        writeSecurityParamsList(out, scanResultMatchInfo);
 
         XmlUtil.writeNextSectionEnd(out, XML_TAG_NETWORK_SECTION);
     }
@@ -215,6 +245,59 @@
         mNotificationsDataSource.setData(notificationsShown);
     }
 
+    private SecurityParams parseSecurityParams(XmlPullParser in, int outerTagDepth)
+            throws IOException, XmlPullParserException {
+        SecurityParams params = null;
+        while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
+            String[] valueName = new String[1];
+            Object value = XmlUtil.readCurrentValue(in, valueName);
+            String tagName = valueName[0];
+            if (tagName == null) {
+                throw new XmlPullParserException("Missing value name");
+            }
+            switch (tagName) {
+                case XML_TAG_SECURITY_TYPE:
+                    params = SecurityParams.createSecurityParamsBySecurityType((int) value);
+                    break;
+                case XML_TAG_SAE_IS_H2E_ONLY_MODE:
+                    if (null == params) {
+                        throw new XmlPullParserException("Missing security type.");
+                    }
+                    params.enableSaeH2eOnlyMode((boolean) value);
+                    break;
+                case XML_TAG_SAE_IS_PK_ONLY_MODE:
+                    if (null == params) {
+                        throw new XmlPullParserException("Missing security type.");
+                    }
+                    params.enableSaePkOnlyMode((boolean) value);
+                    break;
+                case XML_TAG_IS_ADDED_BY_AUTO_UPGRADE:
+                    if (null == params) {
+                        throw new XmlPullParserException("Missing security type.");
+                    }
+                    params.setIsAddedByAutoUpgrade((boolean) value);
+                    break;
+            }
+        }
+        return params;
+    }
+
+    private void parseSecurityParamsList(
+            XmlPullParser in, int outerTagDepth, ScanResultMatchInfo scanResultMatchInfo)
+            throws IOException, XmlPullParserException {
+        while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
+            switch (in.getName()) {
+                case XML_TAG_SECURITY_PARAMS:
+                    SecurityParams params = parseSecurityParams(in, outerTagDepth + 1);
+                    if (null != params) {
+                        scanResultMatchInfo.securityParamsList.add(params);
+                    }
+                    break;
+            }
+        }
+
+    }
+
     /**
      * Parses a {@link ScanResultMatchInfo} from an XML input stream.
      *
@@ -228,6 +311,19 @@
             throws IOException, XmlPullParserException {
         ScanResultMatchInfo scanResultMatchInfo = new ScanResultMatchInfo();
         while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
+            if (in.getAttributeValue(null, "name") == null) {
+                String tagName = in.getName();
+                if (tagName == null) {
+                    throw new XmlPullParserException("Unexpected null tag found");
+                }
+                switch (tagName) {
+                    case XML_TAG_SECURITY_PARAMS_LIST:
+                        parseSecurityParamsList(in, outerTagDepth + 1, scanResultMatchInfo);
+                        break;
+                }
+                continue;
+            }
+
             String[] valueName = new String[1];
             Object value = XmlUtil.readCurrentValue(in, valueName);
             if (valueName[0] == null) {
@@ -238,7 +334,9 @@
                     scanResultMatchInfo.networkSsid = (String) value;
                     break;
                 case XML_TAG_SECURITY:
-                    scanResultMatchInfo.networkType = (int) value;
+                    // Migrate data from R to S.
+                    scanResultMatchInfo.securityParamsList.add(
+                            SecurityParams.createSecurityParamsBySecurityType((int) value));
                     break;
                 default:
                     Log.w(TAG, "Ignoring unknown tag under " + TAG + ": " + valueName[0]);
diff --git a/service/java/com/android/server/wifi/WakeupController.java b/service/java/com/android/server/wifi/WakeupController.java
index e40fc42..1939760 100644
--- a/service/java/com/android/server/wifi/WakeupController.java
+++ b/service/java/com/android/server/wifi/WakeupController.java
@@ -50,6 +50,8 @@
     private static final String TAG = "WakeupController";
 
     private static final boolean USE_PLATFORM_WIFI_WAKE = true;
+    private static final int INIT_WAKEUP_LOCK_SCAN_RESULT_VALID_DURATION_MS =
+            10 * 60 * 1000; // 10 minutes
 
     private final Context mContext;
     private final Handler mHandler;
@@ -64,6 +66,7 @@
     private final WakeupConfigStoreData mWakeupConfigStoreData;
     private final WifiWakeMetrics mWifiWakeMetrics;
     private final Clock mClock;
+    private final ActiveModeWarden mActiveModeWarden;
 
     private final WifiScanner.ScanListener mScanListener = new WifiScanner.ScanListener() {
         @Override
@@ -75,7 +78,7 @@
         public void onResults(WifiScanner.ScanData[] results) {
             // We treat any full band scans (with DFS or not) as "full".
             if (results.length == 1
-                    && WifiScanner.isFullBandScan(results[0].getBandScanned(), true)) {
+                    && WifiScanner.isFullBandScan(results[0].getScannedBandsInternal(), true)) {
                 handleScanResults(filterDfsScanResults(Arrays.asList(results[0].getResults())));
             }
         }
@@ -102,7 +105,10 @@
     /** Whether the WakeupController is currently active. */
     private boolean mIsActive = false;
 
-    /** The number of scans that have been handled by the controller since last {@link #reset()}. */
+    /**
+     *  The number of scans that have been handled by the controller since last
+     * {@link #onWifiEnabled()}.
+     */
     private int mNumScansHandled = 0;
 
     /** Whether Wifi verbose logging is enabled. */
@@ -134,7 +140,8 @@
             WifiWakeMetrics wifiWakeMetrics,
             WifiInjector wifiInjector,
             FrameworkFacade frameworkFacade,
-            Clock clock) {
+            Clock clock,
+            ActiveModeWarden activeModeWarden) {
         mContext = context;
         mHandler = handler;
         mWakeupLock = wakeupLock;
@@ -145,6 +152,7 @@
         mWifiWakeMetrics = wifiWakeMetrics;
         mFrameworkFacade = frameworkFacade;
         mWifiInjector = wifiInjector;
+        mActiveModeWarden = activeModeWarden;
         mContentObserver = new ContentObserver(mHandler) {
             @Override
             public void onChange(boolean selfChange) {
@@ -167,6 +175,14 @@
         mClock = clock;
         mLastDisconnectTimestampMillis = 0;
         mLastDisconnectInfo = null;
+
+        mActiveModeWarden.registerPrimaryClientModeManagerChangedCallback(
+                (prevPrimaryClientModeManager, newPrimaryClientModeManager) -> {
+                    // reset when the primary CMM changes
+                    if (newPrimaryClientModeManager != null) {
+                        onWifiEnabled();
+                    }
+                });
     }
 
     private void readWifiWakeupEnabledFromSettings() {
@@ -258,8 +274,9 @@
         if (isEnabledAndReady()) {
             mWakeupOnboarding.maybeShowNotification();
 
-            List<ScanResult> scanResults =
-                    filterDfsScanResults(mWifiInjector.getWifiScanner().getSingleScanResults());
+            List<ScanResult> scanResults = filterDfsScanResults(
+                    mWifiConfigManager.getMostRecentScanResultsForConfiguredNetworks(
+                            INIT_WAKEUP_LOCK_SCAN_RESULT_VALID_DURATION_MS));
             Set<ScanResultMatchInfo> matchInfos = toMatchInfos(scanResults);
             matchInfos.retainAll(getGoodSavedNetworksAndSuggestions());
 
@@ -299,9 +316,12 @@
         mWakeupOnboarding.onStop();
     }
 
-    /** Resets the WakeupController, setting {@link #mIsActive} to false. */
-    public void reset() {
-        Log.d(TAG, "reset()");
+    /**
+     * This is called at the end of a Wifi Wake session, after Wifi Wake successfully turned Wifi
+     * back on.
+     */
+    private void onWifiEnabled() {
+        Log.d(TAG, "onWifiEnabled()");
         mWifiWakeMetrics.recordResetEvent(mNumScansHandled);
         mNumScansHandled = 0;
         setActive(false);
@@ -337,10 +357,13 @@
 
         Set<ScanResultMatchInfo> goodNetworks = new HashSet<>(savedNetworks.size());
         for (WifiConfiguration config : savedNetworks) {
-            if (isWideAreaNetwork(config)
-                    || config.hasNoInternetAccess()
+            if (config.hasNoInternetAccess()
                     || config.noInternetAccessExpected
-                    || !config.getNetworkSelectionStatus().hasEverConnected()) {
+                    || !config.getNetworkSelectionStatus().hasEverConnected()
+                    || !config.allowAutojoin
+                    || config.getNetworkSelectionStatus().isNetworkPermanentlyDisabled()
+                    || (!config.getNetworkSelectionStatus().hasNeverDetectedCaptivePortal()
+                    && !config.validatedInternetAccess)) {
                 continue;
             }
             goodNetworks.add(ScanResultMatchInfo.fromWifiConfiguration(config));
@@ -356,11 +379,6 @@
         return goodNetworks;
     }
 
-    //TODO(b/69271702) implement WAN filtering
-    private static boolean isWideAreaNetwork(WifiConfiguration config) {
-        return false;
-    }
-
     /**
      * Handles incoming scan results.
      *
@@ -427,7 +445,9 @@
         if (USE_PLATFORM_WIFI_WAKE) {
             // TODO(b/72180295): ensure that there is no race condition with WifiServiceImpl here
             if (mWifiInjector.getWifiSettingsStore().handleWifiToggled(true /* wifiEnabled */)) {
-                mWifiInjector.getActiveModeWarden().wifiToggled();
+                mActiveModeWarden.wifiToggled(
+                        // Assumes user toggled it on from settings before.
+                        mFrameworkFacade.getSettingsWorkSource(mContext));
                 mWifiWakeMetrics.recordWakeupEvent(mNumScansHandled);
             }
         }
@@ -469,4 +489,8 @@
             mIsActive = data;
         }
     }
+
+    public void resetNotification() {
+        mWakeupOnboarding.onStop();
+    }
 }
diff --git a/service/java/com/android/server/wifi/WakeupNotificationFactory.java b/service/java/com/android/server/wifi/WakeupNotificationFactory.java
index 9eb69b7..0187009 100644
--- a/service/java/com/android/server/wifi/WakeupNotificationFactory.java
+++ b/service/java/com/android/server/wifi/WakeupNotificationFactory.java
@@ -41,13 +41,10 @@
     public static final int ONBOARD_ID = SystemMessage.NOTE_WIFI_WAKE_ONBOARD;
 
     private final WifiContext mContext;
-    private final WifiInjector mWifiInjector;
     private final FrameworkFacade mFrameworkFacade;
 
-    WakeupNotificationFactory(WifiContext context, WifiInjector wifiInjector,
-                              FrameworkFacade frameworkFacade) {
+    WakeupNotificationFactory(WifiContext context, FrameworkFacade frameworkFacade) {
         mContext = context;
-        mWifiInjector = wifiInjector;
         mFrameworkFacade = frameworkFacade;
     }
 
@@ -83,9 +80,8 @@
 
 
     private PendingIntent getPrivateBroadcast(String action) {
-        Intent intent = new Intent(action)
-                .setPackage(mWifiInjector.getWifiStackPackageName());
-        return mFrameworkFacade.getBroadcast(
-                mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+        Intent intent = new Intent(action).setPackage(mContext.getServiceWifiPackageName());
+        return mFrameworkFacade.getBroadcast(mContext, 0, intent,
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
     }
 }
diff --git a/service/java/com/android/server/wifi/WakeupOnboarding.java b/service/java/com/android/server/wifi/WakeupOnboarding.java
index 93540e0..c7fc717 100644
--- a/service/java/com/android/server/wifi/WakeupOnboarding.java
+++ b/service/java/com/android/server/wifi/WakeupOnboarding.java
@@ -20,7 +20,6 @@
 import static com.android.server.wifi.WakeupNotificationFactory.ACTION_OPEN_WIFI_PREFERENCES;
 import static com.android.server.wifi.WakeupNotificationFactory.ACTION_TURN_OFF_WIFI_WAKE;
 
-import android.app.NotificationManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -51,9 +50,9 @@
     static final long REQUIRED_NOTIFICATION_DELAY = DateUtils.DAY_IN_MILLIS;
     private static final long NOT_SHOWN_TIMESTAMP = -1;
 
-    private final Context mContext;
+    private final WifiContext mContext;
     private final WakeupNotificationFactory mWakeupNotificationFactory;
-    private NotificationManager mNotificationManager;
+    private final WifiNotificationManager mNotificationManager;
     private final Handler mHandler;
     private final WifiConfigManager mWifiConfigManager;
     private final IntentFilter mIntentFilter;
@@ -90,16 +89,18 @@
     };
 
     public WakeupOnboarding(
-            Context context,
+            WifiContext context,
             WifiConfigManager wifiConfigManager,
             Handler handler,
             FrameworkFacade frameworkFacade,
-            WakeupNotificationFactory wakeupNotificationFactory) {
+            WakeupNotificationFactory wakeupNotificationFactory,
+            WifiNotificationManager wifiNotificationManager) {
         mContext = context;
         mWifiConfigManager = wifiConfigManager;
         mHandler = handler;
         mFrameworkFacade = frameworkFacade;
         mWakeupNotificationFactory = wakeupNotificationFactory;
+        mNotificationManager = wifiNotificationManager;
 
         mIntentFilter = new IntentFilter();
         mIntentFilter.addAction(ACTION_TURN_OFF_WIFI_WAKE);
@@ -130,7 +131,7 @@
 
         mContext.registerReceiver(mBroadcastReceiver, mIntentFilter,
                 null /* broadcastPermission */, mHandler);
-        getNotificationManager().notify(WakeupNotificationFactory.ONBOARD_ID,
+        mNotificationManager.notify(WakeupNotificationFactory.ONBOARD_ID,
                 mWakeupNotificationFactory.createOnboardingNotification());
     }
 
@@ -171,7 +172,7 @@
         }
 
         mContext.unregisterReceiver(mBroadcastReceiver);
-        getNotificationManager().cancel(WakeupNotificationFactory.ONBOARD_ID);
+        mNotificationManager.cancel(WakeupNotificationFactory.ONBOARD_ID);
         mIsNotificationShowing = false;
     }
 
@@ -185,14 +186,6 @@
         mWifiConfigManager.saveToStore(false /* forceWrite */);
     }
 
-    private NotificationManager getNotificationManager() {
-        if (mNotificationManager == null) {
-            mNotificationManager = (NotificationManager)
-                    mContext.getSystemService(Context.NOTIFICATION_SERVICE);
-        }
-        return mNotificationManager;
-    }
-
     /** Returns the {@link WakeupConfigStoreData.DataSource} for the onboarded status. */
     public WakeupConfigStoreData.DataSource<Boolean> getIsOnboadedDataSource() {
         return new IsOnboardedDataSource();
diff --git a/service/java/com/android/server/wifi/WifiApConfigStore.java b/service/java/com/android/server/wifi/WifiApConfigStore.java
index 39dd2ff..4e756d4 100644
--- a/service/java/com/android/server/wifi/WifiApConfigStore.java
+++ b/service/java/com/android/server/wifi/WifiApConfigStore.java
@@ -21,19 +21,24 @@
 import android.content.IntentFilter;
 import android.net.MacAddress;
 import android.net.wifi.SoftApConfiguration;
+import android.net.wifi.SoftApConfiguration.BandType;
 import android.os.Handler;
 import android.os.Process;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.SparseIntArray;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.MacAddressUtils;
 import com.android.server.wifi.util.ApConfigUtil;
 import com.android.wifi.resources.R;
 
+import java.nio.charset.CharsetEncoder;
 import java.nio.charset.StandardCharsets;
 import java.security.SecureRandom;
 import java.util.ArrayList;
+import java.util.Objects;
 import java.util.Random;
 
 import javax.annotation.Nullable;
@@ -71,6 +76,9 @@
     private final WifiConfigManager mWifiConfigManager;
     private final ActiveModeWarden mActiveModeWarden;
     private boolean mHasNewDataToSerialize = false;
+    private boolean mForceApChannel = false;
+    private int mForcedApBand;
+    private int mForcedApChannel;
 
     /**
      * Module to interact with the wifi config store.
@@ -130,10 +138,19 @@
         }
         SoftApConfiguration sanitizedPersistentconfig =
                 sanitizePersistentApConfig(mPersistentWifiApConfig);
-        if (mPersistentWifiApConfig != sanitizedPersistentconfig) {
+        if (!Objects.equals(mPersistentWifiApConfig, sanitizedPersistentconfig)) {
             Log.d(TAG, "persisted config was converted, need to resave it");
             persistConfigAndTriggerBackupManagerProxy(sanitizedPersistentconfig);
         }
+        if (mForceApChannel) {
+            Log.d(TAG, "getApConfiguration: Band force to " + mForcedApBand
+                    + ", and channel force to " + mForcedApChannel);
+            return mForcedApChannel == 0
+                    ? new SoftApConfiguration.Builder(mPersistentWifiApConfig)
+                            .setBand(mForcedApBand).build()
+                    : new SoftApConfiguration.Builder(mPersistentWifiApConfig)
+                            .setChannel(mForcedApChannel, mForcedApBand).build();
+        }
         return mPersistentWifiApConfig;
     }
 
@@ -149,7 +166,27 @@
         } else {
             config = sanitizePersistentApConfig(config);
         }
-        persistConfigAndTriggerBackupManagerProxy(config);
+        persistConfigAndTriggerBackupManagerProxy(
+                new SoftApConfiguration.Builder(config).setUserConfiguration(true).build());
+    }
+
+    /**
+     * Returns SoftApConfiguration in which some parameters might be upgrade to supported default
+     * configuration.
+     */
+    public SoftApConfiguration upgradeSoftApConfiguration(@NonNull SoftApConfiguration config) {
+        SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(config);
+        if (SdkLevel.isAtLeastS() && ApConfigUtil.isBridgedModeSupported(mContext)
+                && config.getBands().length == 1) {
+            int[] dual_bands = new int[] {
+                    SoftApConfiguration.BAND_2GHZ,
+                    SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ};
+            if (SdkLevel.isAtLeastS()) {
+                configBuilder.setBands(dual_bands);
+            }
+            Log.i(TAG, "Device support bridged AP, upgrade band setting to bridged configuration");
+        }
+        return configBuilder.build();
     }
 
     /**
@@ -164,6 +201,10 @@
      *
      * SAE/SAE-Transition need hardware support, reset to secured WPA2 security type when device
      * doesn't support it.
+     *
+     * Check band(s) setting to make sure all of the band(s) are supported.
+     * - If previous bands configuration is bridged mode. Reset to 2.4G when device doesn't support
+     *   it.
      */
     public SoftApConfiguration resetToDefaultForUnsupportedConfig(
             @NonNull SoftApConfiguration config) {
@@ -199,29 +240,35 @@
                 && config.getChannel() != 0) {
             // The device might not support customize channel or forced channel might not
             // work in some countries. Need to reset it.
-            // Add 2.4G by default
-            configBuilder.setBand(config.getBand() | SoftApConfiguration.BAND_2GHZ);
+            configBuilder.setBand(ApConfigUtil.append24GToBandIf24GSupported(
+                    config.getBand(), mContext));
             Log.i(TAG, "Reset SAP channel configuration");
         }
 
-        int newBand = config.getBand();
-        if (!mContext.getResources().getBoolean(R.bool.config_wifi6ghzSupport)
-                && (newBand & SoftApConfiguration.BAND_6GHZ) != 0) {
-            newBand &= ~SoftApConfiguration.BAND_6GHZ;
-            Log.i(TAG, "Device doesn't support 6g, remove 6G band from band setting");
-        }
-
-        if (!mContext.getResources().getBoolean(R.bool.config_wifi5ghzSupport)
-                && (newBand & SoftApConfiguration.BAND_5GHZ) != 0) {
-            newBand &= ~SoftApConfiguration.BAND_5GHZ;
-            Log.i(TAG, "Device doesn't support 5g, remove 5G band from band setting");
-        }
-
-        if (newBand != config.getBand()) {
-            // Always added 2.4G by default when reset the band.
-            Log.i(TAG, "Reset band from " + config.getBand() + " to "
-                    + (newBand | SoftApConfiguration.BAND_2GHZ));
-            configBuilder.setBand(newBand | SoftApConfiguration.BAND_2GHZ);
+        if (SdkLevel.isAtLeastS() && config.getBands().length > 1) {
+            if (!ApConfigUtil.isBridgedModeSupported(mContext)
+                    || !isBandsSupported(config.getBands(), mContext)) {
+                int newSingleApBand = 0;
+                for (int targetBand : config.getBands()) {
+                    int availableBand = ApConfigUtil.removeUnsupportedBands(
+                            mContext, targetBand);
+                    newSingleApBand |= availableBand;
+                }
+                newSingleApBand = ApConfigUtil.append24GToBandIf24GSupported(
+                        newSingleApBand, mContext);
+                configBuilder.setBand(newSingleApBand);
+                Log.i(TAG, "An unsupported band setting for the bridged mode, force to "
+                        + newSingleApBand);
+            }
+        } else {
+            // Single band case, check and remove unsupported band.
+            int newBand = ApConfigUtil.removeUnsupportedBands(mContext, config.getBand());
+            if (newBand != config.getBand()) {
+                newBand = ApConfigUtil.append24GToBandIf24GSupported(newBand, mContext);
+                Log.i(TAG, "Reset band from " + config.getBand() + " to "
+                        + newBand);
+                configBuilder.setBand(newBand);
+            }
         }
 
         if (mContext.getResources().getBoolean(R.bool.config_wifiSoftapResetHiddenConfig)
@@ -237,23 +284,49 @@
             Log.i(TAG, "Reset SAP auto shutdown configuration");
         }
 
+        if (!ApConfigUtil.isApMacRandomizationSupported(mContext)) {
+            if (SdkLevel.isAtLeastS()) {
+                configBuilder.setMacRandomizationSetting(SoftApConfiguration.RANDOMIZATION_NONE);
+                Log.i(TAG, "Force set SAP MAC randomization to NONE when not supported");
+            }
+        }
+
         mWifiMetrics.noteSoftApConfigReset(config, configBuilder.build());
         return configBuilder.build();
     }
 
     private SoftApConfiguration sanitizePersistentApConfig(SoftApConfiguration config) {
-        SoftApConfiguration.Builder convertedConfigBuilder = null;
-
-        // some countries are unable to support 5GHz only operation, always allow for 2GHz when
-        // config doesn't force channel
-        if (config.getChannel() == 0 && (config.getBand() & SoftApConfiguration.BAND_2GHZ) == 0) {
-            Log.w(TAG, "Supplied ap config band without 2.4G, add allowing for 2.4GHz");
-            if (convertedConfigBuilder == null) {
-                convertedConfigBuilder = new SoftApConfiguration.Builder(config);
+        SoftApConfiguration.Builder convertedConfigBuilder =
+                new SoftApConfiguration.Builder(config);
+        int[] bands = config.getBands();
+        // The bands length should always 1 in R. Adding SdkLevel.isAtLeastS for lint check only.
+        if (bands.length > 1 && SdkLevel.isAtLeastS()) {
+            // Consider 2.4G instance may be shutdown, i.e. only left 5G instance. If the 5G
+            // configuration is 5G band only, it will cause that driver can't switch channel from
+            // 5G to 2.4G when coexistence happene. Always append 2.4G into band configuration to
+            // allow driver handle coexistence case after 2.4G instance shutdown.
+            SparseIntArray newChannels = new SparseIntArray();
+            for (int i = 0; i < bands.length; i++) {
+                int channel = config.getChannels().valueAt(i);
+                if (channel == 0 && (bands[i] & SoftApConfiguration.BAND_2GHZ) == 0
+                        && ApConfigUtil.isBandSupported(bands[i], mContext)) {
+                    newChannels.put(ApConfigUtil.append24GToBandIf24GSupported(bands[i], mContext),
+                            0);
+                } else {
+                    newChannels.put(bands[i], channel);
+                }
             }
-            convertedConfigBuilder.setBand(config.getBand() | SoftApConfiguration.BAND_2GHZ);
+            convertedConfigBuilder.setChannels(newChannels);
+        } else if (config.getChannel() == 0 && (bands[0] & SoftApConfiguration.BAND_2GHZ) == 0) {
+            // some countries are unable to support 5GHz only operation, always allow for 2GHz when
+            // config doesn't force channel
+            if (ApConfigUtil.isBandSupported(bands[0], mContext)) {
+                Log.i(TAG, "Supplied ap config band without 2.4G, add allowing for 2.4GHz");
+                convertedConfigBuilder.setBand(
+                        ApConfigUtil.append24GToBandIf24GSupported(bands[0], mContext));
+            }
         }
-        return convertedConfigBuilder == null ? config : convertedConfigBuilder.build();
+        return convertedConfigBuilder.build();
     }
 
     private void persistConfigAndTriggerBackupManagerProxy(SoftApConfiguration config) {
@@ -272,7 +345,7 @@
      */
     private SoftApConfiguration getDefaultApConfiguration() {
         SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder();
-        configBuilder.setBand(SoftApConfiguration.BAND_2GHZ);
+        configBuilder.setBand(generateDefaultBand(mContext));
         configBuilder.setSsid(mContext.getResources().getString(
                 R.string.wifi_tether_configure_ssid_default) + "_" + getRandomIntForDefaultSsid());
         if (ApConfigUtil.isWpa3SaeSupported(mContext)) {
@@ -282,6 +355,26 @@
             configBuilder.setPassphrase(generatePassword(),
                     SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
         }
+
+        // It is new overlay configuration, it should always false in R. Add SdkLevel.isAtLeastS for
+        // lint check
+        if (ApConfigUtil.isBridgedModeSupported(mContext)) {
+            if (SdkLevel.isAtLeastS()) {
+                int[] dual_bands = new int[] {
+                        SoftApConfiguration.BAND_2GHZ,
+                        SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ};
+                configBuilder.setBands(dual_bands);
+            }
+        }
+
+        // Update default MAC randomization setting to NONE when feature doesn't support it.
+        if (!ApConfigUtil.isApMacRandomizationSupported(mContext)) {
+            if (SdkLevel.isAtLeastS()) {
+                configBuilder.setMacRandomizationSetting(SoftApConfiguration.RANDOMIZATION_NONE);
+            }
+        }
+
+        configBuilder.setUserConfiguration(false);
         return configBuilder.build();
     }
 
@@ -300,7 +393,7 @@
      * Generate a temporary WPA2 based configuration for use by the local only hotspot.
      * This config is not persisted and will not be stored by the WifiApConfigStore.
      */
-    public static SoftApConfiguration generateLocalOnlyHotspotConfig(Context context, int apBand,
+    public SoftApConfiguration generateLocalOnlyHotspotConfig(Context context, int apBand,
             @Nullable SoftApConfiguration customConfig) {
         SoftApConfiguration.Builder configBuilder;
         if (customConfig != null) {
@@ -326,6 +419,16 @@
             }
         }
 
+        // Update default MAC randomization setting to NONE when feature doesn't support it or
+        // It was disabled in tethered mode.
+        if (!ApConfigUtil.isApMacRandomizationSupported(context) || (mPersistentWifiApConfig != null
+                && mPersistentWifiApConfig.getMacRandomizationSettingInternal()
+                == SoftApConfiguration.RANDOMIZATION_NONE)) {
+            if (SdkLevel.isAtLeastS()) {
+                configBuilder.setMacRandomizationSetting(SoftApConfiguration.RANDOMIZATION_NONE);
+            }
+        }
+
         return configBuilder.build();
     }
 
@@ -335,8 +438,12 @@
      */
     SoftApConfiguration randomizeBssidIfUnset(Context context, SoftApConfiguration config) {
         SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(config);
-        if (config.getBssid() == null && context.getResources().getBoolean(
-                R.bool.config_wifi_ap_mac_randomization_supported)) {
+        if (config.getBssid() == null && ApConfigUtil.isApMacRandomizationSupported(mContext)) {
+            if (config.getMacRandomizationSettingInternal()
+                    == SoftApConfiguration.RANDOMIZATION_NONE) {
+                return configBuilder.build();
+            }
+
             MacAddress macAddress = mMacAddressUtil.calculatePersistentMac(config.getSsid(),
                     mMacAddressUtil.obtainMacRandHashFunctionForSap(Process.WIFI_UID));
             if (macAddress == null) {
@@ -398,8 +505,8 @@
     /**
      * Validate a SoftApConfiguration is properly configured for use by SoftApManager.
      *
-     * This method checks the length of the SSID and validates security settings (if it
-     * requires a password, was one provided?).
+     * This method checks the length of the SSID and for consistency between security settings (if
+     * it requires a password, was one provided?).
      *
      * @param apConfig {@link SoftApConfiguration} to use for softap mode
      * @param isPrivileged indicate the caller can pass some fields check or not
@@ -407,7 +514,7 @@
      * otherwise.
      */
     static boolean validateApWifiConfiguration(@NonNull SoftApConfiguration apConfig,
-            boolean isPrivileged) {
+            boolean isPrivileged, Context context) {
         // first check the SSID
         if (!validateApConfigSsid(apConfig.getSsid())) {
             // failed SSID verificiation checks
@@ -445,6 +552,16 @@
                 Log.d(TAG, "softap network password must be set");
                 return false;
             }
+
+            if (context.getResources().getBoolean(
+                    R.bool.config_wifiSoftapPassphraseAsciiEncodableCheck)) {
+                final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder();
+                if (!asciiEncoder.canEncode(preSharedKey)) {
+                    Log.d(TAG, "passphrase not ASCII encodable");
+                    return false;
+                }
+            }
+
             if (authType != SoftApConfiguration.SECURITY_TYPE_WPA3_SAE
                     && !validateApConfigPreSharedKey(preSharedKey)) {
                 // failed preSharedKey checks for WPA2 and WPA3 SAE Transition mode.
@@ -456,6 +573,16 @@
             return false;
         }
 
+        if (SdkLevel.isAtLeastS()) {
+            if (!isBandsSupported(apConfig.getBands(), context)) {
+                return false;
+            }
+        } else {
+            if (!ApConfigUtil.isBandSupported(apConfig.getBand(), context)) {
+                return false;
+            }
+        }
+
         return true;
     }
 
@@ -472,4 +599,48 @@
         }
         return sb.toString();
     }
+
+    /**
+     * Generate default band base on supported band configuration.
+     *
+     * @param context The caller context used to get value from resource file.
+     * @return A band which will be used for a default band in default configuration.
+     */
+    public static @BandType int generateDefaultBand(Context context) {
+        for (int band : SoftApConfiguration.BAND_TYPES) {
+            if (ApConfigUtil.isBandSupported(band, context)) {
+                return band;
+            }
+        }
+        Log.e(TAG, "Invalid overlay configuration! No any band supported on SoftAp");
+        return SoftApConfiguration.BAND_2GHZ;
+    }
+
+    private static boolean isBandsSupported(@NonNull int[] apBands, Context context) {
+        for (int band : apBands) {
+            if (!ApConfigUtil.isBandSupported(band, context)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Enable force-soft-AP-channel mode which takes effect when soft AP starts next time
+     *
+     * @param forcedApBand The forced band.
+     * @param forcedApChannel The forced IEEE channel number or 0 when forced AP band only.
+     */
+    public void enableForceSoftApBandOrChannel(@BandType int forcedApBand, int forcedApChannel) {
+        mForceApChannel = true;
+        mForcedApChannel = forcedApChannel;
+        mForcedApBand = forcedApBand;
+    }
+
+    /**
+     * Disable force-soft-AP-channel mode which take effect when soft AP starts next time
+     */
+    public void disableForceSoftApBandOrChannel() {
+        mForceApChannel = false;
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiBackupDataV1Parser.java b/service/java/com/android/server/wifi/WifiBackupDataV1Parser.java
index 89ab9a9..b99d987 100644
--- a/service/java/com/android/server/wifi/WifiBackupDataV1Parser.java
+++ b/service/java/com/android/server/wifi/WifiBackupDataV1Parser.java
@@ -26,6 +26,7 @@
 import android.net.RouteInfo;
 import android.net.StaticIpConfiguration;
 import android.net.Uri;
+import android.net.wifi.SecurityParams;
 import android.net.wifi.WifiConfiguration;
 import android.util.Log;
 import android.util.Pair;
@@ -92,7 +93,7 @@
 
     private static final String TAG = "WifiBackupDataV1Parser";
 
-    private static final int HIGHEST_SUPPORTED_MINOR_VERSION = 2;
+    private static final int HIGHEST_SUPPORTED_MINOR_VERSION = 3;
 
     // List of tags supported for <WifiConfiguration> section in minor version 0
     private static final Set<String> WIFI_CONFIGURATION_MINOR_V0_SUPPORTED_TAGS =
@@ -126,8 +127,22 @@
                 add(WifiConfigurationXmlUtil.XML_TAG_IS_AUTO_JOIN);
             }};
 
-    // List of tags supported for <IpConfiguration> section in minor version 0 to 2
-    private static final Set<String> IP_CONFIGURATION_MINOR_V0_V1_V2_SUPPORTED_TAGS =
+    // List of tags supported for <WifiConfiguration> section in minor version 3
+    private static final Set<String> WIFI_CONFIGURATION_MINOR_V3_SUPPORTED_TAGS =
+            new HashSet<String>() {{
+                addAll(WIFI_CONFIGURATION_MINOR_V2_SUPPORTED_TAGS);
+                add(WifiConfigurationXmlUtil.XML_TAG_SECURITY_PARAMS_LIST);
+                add(WifiConfigurationXmlUtil.XML_TAG_SECURITY_PARAMS);
+                add(WifiConfigurationXmlUtil.XML_TAG_SECURITY_TYPE);
+                add(WifiConfigurationXmlUtil.XML_TAG_SAE_IS_H2E_ONLY_MODE);
+                add(WifiConfigurationXmlUtil.XML_TAG_SAE_IS_PK_ONLY_MODE);
+                add(WifiConfigurationXmlUtil.XML_TAG_IS_ADDED_BY_AUTO_UPGRADE);
+                add(WifiConfigurationXmlUtil.XML_TAG_DELETION_PRIORITY);
+                add(WifiConfigurationXmlUtil.XML_TAG_NUM_REBOOTS_SINCE_LAST_USE);
+            }};
+
+    // List of tags supported for <IpConfiguration> section in minor version 0 to 3
+    private static final Set<String> IP_CONFIGURATION_MINOR_V0_V1_V2_V3_SUPPORTED_TAGS =
             new HashSet<String>(Arrays.asList(new String[] {
                 IpConfigurationXmlUtil.XML_TAG_IP_ASSIGNMENT,
                 IpConfigurationXmlUtil.XML_TAG_LINK_ADDRESS,
@@ -291,11 +306,20 @@
 
         // Loop through and parse out all the elements from the stream within this section.
         while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
-            String[] valueName = new String[1];
-            Object value = XmlUtil.readCurrentValue(in, valueName);
-            String tagName = valueName[0];
-            if (tagName == null) {
-                throw new XmlPullParserException("Missing value name");
+            String tagName = null;
+            Object value = null;
+            if (in.getAttributeValue(null, "name") != null) {
+                String[] valueName = new String[1];
+                value = XmlUtil.readCurrentValue(in, valueName);
+                tagName = valueName[0];
+                if (tagName == null) {
+                    throw new XmlPullParserException("Missing value name");
+                }
+            } else {
+                tagName = in.getName();
+                if (tagName == null) {
+                    throw new XmlPullParserException("Unexpected null tag found");
+                }
             }
 
             // ignore the tags that are not supported up until the current minor version
@@ -359,10 +383,19 @@
                 case WifiConfigurationXmlUtil.XML_TAG_IS_AUTO_JOIN:
                     configuration.allowAutojoin = (boolean) value;
                     break;
+                case WifiConfigurationXmlUtil.XML_TAG_DELETION_PRIORITY:
+                    configuration.setDeletionPriority((int) value);
+                    break;
+                case WifiConfigurationXmlUtil.XML_TAG_NUM_REBOOTS_SINCE_LAST_USE:
+                    configuration.numRebootsSinceLastUse = (int) value;
+                    break;
+                case WifiConfigurationXmlUtil.XML_TAG_SECURITY_PARAMS_LIST:
+                    parseSecurityParamsListFromXml(in, outerTagDepth + 1, configuration);
+                    break;
                 default:
                     // should never happen, since other tags are filtered out earlier
                     throw new XmlPullParserException(
-                            "Unknown value name found: " + valueName[0]);
+                            "Unknown value name found: " + tagName);
             }
         }
         clearAnyKnownIssuesInParsedConfiguration(configuration);
@@ -385,6 +418,8 @@
                 return WIFI_CONFIGURATION_MINOR_V1_SUPPORTED_TAGS;
             case 2:
                 return WIFI_CONFIGURATION_MINOR_V2_SUPPORTED_TAGS;
+            case 3:
+                return WIFI_CONFIGURATION_MINOR_V3_SUPPORTED_TAGS;
             default:
                 Log.e(TAG, "Invalid minorVersion: " + minorVersion);
                 return Collections.<String>emptySet();
@@ -415,6 +450,63 @@
         }
     }
 
+    private static SecurityParams parseSecurityParamsFromXml(
+            XmlPullParser in, int outerTagDepth) throws XmlPullParserException, IOException {
+        SecurityParams params = null;
+        while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
+            String[] valueName = new String[1];
+            Object value = XmlUtil.readCurrentValue(in, valueName);
+            String tagName = valueName[0];
+            if (tagName == null) {
+                throw new XmlPullParserException("Missing value name");
+            }
+            switch (tagName) {
+                case WifiConfigurationXmlUtil.XML_TAG_SECURITY_TYPE:
+                    params = SecurityParams.createSecurityParamsBySecurityType((int) value);
+                    break;
+                case WifiConfigurationXmlUtil.XML_TAG_SAE_IS_H2E_ONLY_MODE:
+                    if (null == params) throw new XmlPullParserException("Missing security type.");
+                    params.enableSaeH2eOnlyMode((boolean) value);
+                    break;
+                case WifiConfigurationXmlUtil.XML_TAG_SAE_IS_PK_ONLY_MODE:
+                    if (null == params) throw new XmlPullParserException("Missing security type.");
+                    params.enableSaePkOnlyMode((boolean) value);
+                    break;
+                case WifiConfigurationXmlUtil.XML_TAG_IS_ADDED_BY_AUTO_UPGRADE:
+                    if (null == params) throw new XmlPullParserException("Missing security type.");
+                    params.setIsAddedByAutoUpgrade((boolean) value);
+                    break;
+            }
+        }
+        return params;
+    }
+
+    /**
+     * Populate security params list elements only if they were non-empty in the backup data.
+     *
+     * @throws XmlPullParserException if parsing errors occur.
+     */
+    private static void parseSecurityParamsListFromXml(
+            XmlPullParser in, int outerTagDepth,
+            WifiConfiguration configuration)
+            throws XmlPullParserException, IOException {
+
+        List<SecurityParams> paramsList = new ArrayList<>();
+        while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
+            switch (in.getName()) {
+                case WifiConfigurationXmlUtil.XML_TAG_SECURITY_PARAMS:
+                    SecurityParams params = parseSecurityParamsFromXml(in, outerTagDepth + 1);
+                    if (params != null) {
+                        paramsList.add(params);
+                    }
+                    break;
+            }
+        }
+        if (!paramsList.isEmpty()) {
+            configuration.setSecurityParams(paramsList);
+        }
+    }
+
     private static List<String> parseProxyExclusionListString(
             @Nullable String exclusionListString) {
         if (exclusionListString == null) {
@@ -612,7 +704,8 @@
             case 0:
             case 1:
             case 2:
-                return IP_CONFIGURATION_MINOR_V0_V1_V2_SUPPORTED_TAGS;
+            case 3:
+                return IP_CONFIGURATION_MINOR_V0_V1_V2_V3_SUPPORTED_TAGS;
             default:
                 Log.e(TAG, "Invalid minorVersion: " + minorVersion);
                 return Collections.<String>emptySet();
diff --git a/service/java/com/android/server/wifi/WifiBlocklistMonitor.java b/service/java/com/android/server/wifi/WifiBlocklistMonitor.java
new file mode 100644
index 0000000..b53f648
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiBlocklistMonitor.java
@@ -0,0 +1,1145 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.DISABLE_REASON_INFOS;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
+import android.net.wifi.WifiConfiguration.NetworkSelectionStatus.DisableReasonInfo;
+import android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NetworkSelectionDisableReason;
+import android.net.wifi.WifiManager;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.LocalLog;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.wifi.resources.R;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * This class manages the addition and removal of BSSIDs to the BSSID blocklist, which is used
+ * for firmware roaming and network selection.
+ */
+public class WifiBlocklistMonitor {
+    // A special type association rejection
+    public static final int REASON_AP_UNABLE_TO_HANDLE_NEW_STA = 0;
+    // No internet
+    public static final int REASON_NETWORK_VALIDATION_FAILURE = 1;
+    // Wrong password error
+    public static final int REASON_WRONG_PASSWORD = 2;
+    // Incorrect EAP credentials
+    public static final int REASON_EAP_FAILURE = 3;
+    // Other association rejection failures
+    public static final int REASON_ASSOCIATION_REJECTION = 4;
+    // Association timeout failures.
+    public static final int REASON_ASSOCIATION_TIMEOUT = 5;
+    // Other authentication failures
+    public static final int REASON_AUTHENTICATION_FAILURE = 6;
+    // DHCP failures
+    public static final int REASON_DHCP_FAILURE = 7;
+    // Abnormal disconnect error
+    public static final int REASON_ABNORMAL_DISCONNECT = 8;
+    // AP initiated disconnect for a given duration.
+    public static final int REASON_FRAMEWORK_DISCONNECT_MBO_OCE = 9;
+    // Avoid connecting to the failed AP when trying to reconnect on other available candidates.
+    public static final int REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT = 10;
+    // The connected scorer has disconnected this network.
+    public static final int REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE = 11;
+    // Non-local disconnection in the middle of connecting state
+    public static final int REASON_NONLOCAL_DISCONNECT_CONNECTING = 12;
+    // Constant being used to keep track of how many failure reasons there are.
+    public static final int NUMBER_REASON_CODES = 13;
+    public static final int INVALID_REASON = -1;
+
+    @IntDef(prefix = { "REASON_" }, value = {
+            REASON_AP_UNABLE_TO_HANDLE_NEW_STA,
+            REASON_NETWORK_VALIDATION_FAILURE,
+            REASON_WRONG_PASSWORD,
+            REASON_EAP_FAILURE,
+            REASON_ASSOCIATION_REJECTION,
+            REASON_ASSOCIATION_TIMEOUT,
+            REASON_AUTHENTICATION_FAILURE,
+            REASON_DHCP_FAILURE,
+            REASON_ABNORMAL_DISCONNECT,
+            REASON_FRAMEWORK_DISCONNECT_MBO_OCE,
+            REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT,
+            REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE,
+            REASON_NONLOCAL_DISCONNECT_CONNECTING
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface FailureReason {}
+
+    // To be filled with values from the overlay.
+    private static final int[] FAILURE_COUNT_DISABLE_THRESHOLD = new int[NUMBER_REASON_CODES];
+    private boolean mFailureCountDisableThresholdArrayInitialized = false;
+    private static final long ABNORMAL_DISCONNECT_RESET_TIME_MS = TimeUnit.HOURS.toMillis(3);
+    private static final int MIN_RSSI_DIFF_TO_UNBLOCK_BSSID = 5;
+    @VisibleForTesting
+    public static final int NUM_CONSECUTIVE_FAILURES_PER_NETWORK_EXP_BACKOFF = 5;
+    @VisibleForTesting
+    public static final long WIFI_CONFIG_MAX_DISABLE_DURATION_MILLIS = TimeUnit.HOURS.toMillis(18);
+    private static final String TAG = "WifiBlocklistMonitor";
+
+    private final Context mContext;
+    private final WifiLastResortWatchdog mWifiLastResortWatchdog;
+    private final WifiConnectivityHelper mConnectivityHelper;
+    private final Clock mClock;
+    private final LocalLog mLocalLog;
+    private final WifiScoreCard mWifiScoreCard;
+    private final ScoringParams mScoringParams;
+    private final WifiMetrics mWifiMetrics;
+    private final Map<Integer, BssidDisableReason> mBssidDisableReasons =
+            buildBssidDisableReasons();
+    private final SparseArray<DisableReasonInfo> mDisableReasonInfo;
+
+    // Map of bssid to BssidStatus
+    private Map<String, BssidStatus> mBssidStatusMap = new ArrayMap<>();
+    private Set<String> mDisabledSsids = new ArraySet<>();
+
+    // Internal logger to make sure imporatant logs do not get lost.
+    private BssidBlocklistMonitorLogger mBssidBlocklistMonitorLogger =
+            new BssidBlocklistMonitorLogger(60);
+
+    // Map of ssid to Allowlist SSIDs
+    private Map<String, List<String>> mSsidAllowlistMap = new ArrayMap<>();
+
+    /**
+     * Verbose logging flag. Toggled by developer options.
+     */
+    private boolean mVerboseLoggingEnabled = false;
+
+
+    private Map<Integer, BssidDisableReason> buildBssidDisableReasons() {
+        Map<Integer, BssidDisableReason> result = new ArrayMap<>();
+        result.put(REASON_AP_UNABLE_TO_HANDLE_NEW_STA, new BssidDisableReason(
+                "REASON_AP_UNABLE_TO_HANDLE_NEW_STA", false, false));
+        result.put(REASON_NETWORK_VALIDATION_FAILURE, new BssidDisableReason(
+                "REASON_NETWORK_VALIDATION_FAILURE", true, false));
+        result.put(REASON_WRONG_PASSWORD, new BssidDisableReason(
+                "REASON_WRONG_PASSWORD", false, true));
+        result.put(REASON_EAP_FAILURE, new BssidDisableReason(
+                "REASON_EAP_FAILURE", true, true));
+        result.put(REASON_ASSOCIATION_REJECTION, new BssidDisableReason(
+                "REASON_ASSOCIATION_REJECTION", true, true));
+        result.put(REASON_ASSOCIATION_TIMEOUT, new BssidDisableReason(
+                "REASON_ASSOCIATION_TIMEOUT", true, true));
+        result.put(REASON_AUTHENTICATION_FAILURE, new BssidDisableReason(
+                "REASON_AUTHENTICATION_FAILURE", true, true));
+        result.put(REASON_DHCP_FAILURE, new BssidDisableReason(
+                "REASON_DHCP_FAILURE", true, false));
+        result.put(REASON_ABNORMAL_DISCONNECT, new BssidDisableReason(
+                "REASON_ABNORMAL_DISCONNECT", true, false));
+        result.put(REASON_FRAMEWORK_DISCONNECT_MBO_OCE, new BssidDisableReason(
+                "REASON_FRAMEWORK_DISCONNECT_MBO_OCE", false, false));
+        result.put(REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT, new BssidDisableReason(
+                "REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT", false, false));
+        result.put(REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE, new BssidDisableReason(
+                "REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE", true, false));
+        // TODO: b/174166637, add the same reason code in SSID blocklist and mark ignoreIfOnlyBssid
+        // to true once it is covered in SSID blocklist.
+        result.put(REASON_NONLOCAL_DISCONNECT_CONNECTING, new BssidDisableReason(
+                "REASON_NONLOCAL_DISCONNECT_CONNECTING", true, false));
+        return result;
+    }
+
+    class BssidDisableReason {
+        public final String reasonString;
+        public final boolean isLowRssiSensitive;
+        public final boolean ignoreIfOnlyBssid;
+
+        BssidDisableReason(String reasonString, boolean isLowRssiSensitive,
+                boolean ignoreIfOnlyBssid) {
+            this.reasonString = reasonString;
+            this.isLowRssiSensitive = isLowRssiSensitive;
+            this.ignoreIfOnlyBssid = ignoreIfOnlyBssid;
+        }
+    }
+
+    /**
+     * Create a new instance of WifiBlocklistMonitor
+     */
+    WifiBlocklistMonitor(Context context, WifiConnectivityHelper connectivityHelper,
+            WifiLastResortWatchdog wifiLastResortWatchdog, Clock clock, LocalLog localLog,
+            WifiScoreCard wifiScoreCard, ScoringParams scoringParams, WifiMetrics wifiMetrics) {
+        mContext = context;
+        mConnectivityHelper = connectivityHelper;
+        mWifiLastResortWatchdog = wifiLastResortWatchdog;
+        mClock = clock;
+        mLocalLog = localLog;
+        mWifiScoreCard = wifiScoreCard;
+        mScoringParams = scoringParams;
+        mDisableReasonInfo = DISABLE_REASON_INFOS.clone();
+        mWifiMetrics = wifiMetrics;
+        loadCustomConfigsForDisableReasonInfos();
+    }
+
+    // A helper to log debugging information in the local log buffer, which can
+    // be retrieved in bugreport.
+    private void localLog(String log) {
+        mLocalLog.log(log);
+    }
+
+    /**
+     * calculates the blocklist duration based on the current failure streak with exponential
+     * backoff.
+     * @param failureStreak should be greater or equal to 0.
+     * @return duration to block the BSSID in milliseconds
+     */
+    private long getBlocklistDurationWithExponentialBackoff(int failureStreak,
+            int baseBlocklistDurationMs) {
+        failureStreak = Math.min(failureStreak, mContext.getResources().getInteger(
+                R.integer.config_wifiBssidBlocklistMonitorFailureStreakCap));
+        if (failureStreak < 1) {
+            return baseBlocklistDurationMs;
+        }
+        return (long) (Math.pow(2.0, (double) failureStreak) * baseBlocklistDurationMs);
+    }
+
+    /**
+     * Dump the local log buffer and other internal state of WifiBlocklistMonitor.
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("Dump of WifiBlocklistMonitor");
+        mLocalLog.dump(fd, pw, args);
+        pw.println("WifiBlocklistMonitor - Bssid blocklist begin ----");
+        mBssidStatusMap.values().stream().forEach(entry -> pw.println(entry));
+        pw.println("WifiBlocklistMonitor - Bssid blocklist end ----");
+        mBssidBlocklistMonitorLogger.dump(pw);
+    }
+
+    private void addToBlocklist(@NonNull BssidStatus entry, long durationMs,
+            @FailureReason int reason, int rssi) {
+        entry.setAsBlocked(durationMs, reason, rssi);
+        localLog(TAG + " addToBlocklist: bssid=" + entry.bssid + ", ssid=" + entry.ssid
+                + ", durationMs=" + durationMs + ", reason=" + getFailureReasonString(reason)
+                + ", rssi=" + rssi);
+    }
+
+    /**
+     * increments the number of failures for the given bssid and returns the number of failures so
+     * far.
+     * @return the BssidStatus for the BSSID
+     */
+    private @NonNull BssidStatus incrementFailureCountForBssid(
+            @NonNull String bssid, @NonNull String ssid, int reasonCode) {
+        BssidStatus status = getOrCreateBssidStatus(bssid, ssid);
+        status.incrementFailureCount(reasonCode);
+        return status;
+    }
+
+    /**
+     * Get the BssidStatus representing the BSSID or create a new one if it doesn't exist.
+     */
+    private @NonNull BssidStatus getOrCreateBssidStatus(@NonNull String bssid,
+            @NonNull String ssid) {
+        BssidStatus status = mBssidStatusMap.get(bssid);
+        if (status == null || !ssid.equals(status.ssid)) {
+            if (status != null) {
+                localLog("getOrCreateBssidStatus: BSSID=" + bssid + ", SSID changed from "
+                        + status.ssid + " to " + ssid);
+            }
+            status = new BssidStatus(bssid, ssid);
+            mBssidStatusMap.put(bssid, status);
+        }
+        return status;
+    }
+
+    private boolean isValidNetworkAndFailureReason(String bssid, String ssid,
+            @FailureReason int reasonCode) {
+        if (bssid == null || ssid == null || WifiManager.UNKNOWN_SSID.equals(ssid)
+                || bssid.equals(ClientModeImpl.SUPPLICANT_BSSID_ANY)
+                || reasonCode < 0 || reasonCode >= NUMBER_REASON_CODES) {
+            Log.e(TAG, "Invalid input: BSSID=" + bssid + ", SSID=" + ssid
+                    + ", reasonCode=" + reasonCode);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean shouldWaitForWatchdogToTriggerFirst(String bssid,
+            @FailureReason int reasonCode) {
+        boolean isWatchdogRelatedFailure = reasonCode == REASON_ASSOCIATION_REJECTION
+                || reasonCode == REASON_AUTHENTICATION_FAILURE
+                || reasonCode == REASON_DHCP_FAILURE;
+        return isWatchdogRelatedFailure && mWifiLastResortWatchdog.shouldIgnoreBssidUpdate(bssid);
+    }
+
+    /**
+     * Block any attempts to auto-connect to the BSSID for the specified duration.
+     * This is meant to be used by features that need wifi to avoid a BSSID for a certain duration,
+     * and thus will not increase the failure streak counters.
+     * @param bssid identifies the AP to block.
+     * @param ssid identifies the SSID the AP belongs to.
+     * @param durationMs duration in millis to block.
+     * @param blockReason reason for blocking the BSSID.
+     * @param rssi the latest RSSI observed.
+     */
+    public void blockBssidForDurationMs(@NonNull String bssid, @NonNull String ssid,
+            long durationMs, @FailureReason int blockReason, int rssi) {
+        if (durationMs <= 0 || !isValidNetworkAndFailureReason(bssid, ssid, blockReason)) {
+            Log.e(TAG, "Invalid input: BSSID=" + bssid + ", SSID=" + ssid
+                    + ", durationMs=" + durationMs + ", blockReason=" + blockReason
+                    + ", rssi=" + rssi);
+            return;
+        }
+        BssidStatus status = getOrCreateBssidStatus(bssid, ssid);
+        if (status.isInBlocklist
+                && status.blocklistEndTimeMs - mClock.getWallClockMillis() > durationMs) {
+            // Return because this BSSID is already being blocked for a longer time.
+            return;
+        }
+        addToBlocklist(status, durationMs, blockReason, rssi);
+    }
+
+    private String getFailureReasonString(@FailureReason int reasonCode) {
+        if (reasonCode == INVALID_REASON) {
+            return "INVALID_REASON";
+        }
+        BssidDisableReason disableReason = mBssidDisableReasons.get(reasonCode);
+        if (disableReason == null) {
+            return "REASON_UNKNOWN";
+        }
+        return disableReason.reasonString;
+    }
+
+    private int getFailureThresholdForReason(@FailureReason int reasonCode) {
+        if (mFailureCountDisableThresholdArrayInitialized) {
+            return FAILURE_COUNT_DISABLE_THRESHOLD[reasonCode];
+        }
+        FAILURE_COUNT_DISABLE_THRESHOLD[REASON_AP_UNABLE_TO_HANDLE_NEW_STA] =
+                mContext.getResources().getInteger(
+                        R.integer.config_wifiBssidBlocklistMonitorApUnableToHandleNewStaThreshold);
+        FAILURE_COUNT_DISABLE_THRESHOLD[REASON_NETWORK_VALIDATION_FAILURE] =
+                mContext.getResources().getInteger(R.integer
+                        .config_wifiBssidBlocklistMonitorNetworkValidationFailureThreshold);
+        FAILURE_COUNT_DISABLE_THRESHOLD[REASON_WRONG_PASSWORD] =
+                mContext.getResources().getInteger(
+                        R.integer.config_wifiBssidBlocklistMonitorWrongPasswordThreshold);
+        FAILURE_COUNT_DISABLE_THRESHOLD[REASON_EAP_FAILURE] =
+                mContext.getResources().getInteger(
+                        R.integer.config_wifiBssidBlocklistMonitorEapFailureThreshold);
+        FAILURE_COUNT_DISABLE_THRESHOLD[REASON_ASSOCIATION_REJECTION] =
+                mContext.getResources().getInteger(
+                        R.integer.config_wifiBssidBlocklistMonitorAssociationRejectionThreshold);
+        FAILURE_COUNT_DISABLE_THRESHOLD[REASON_ASSOCIATION_TIMEOUT] =
+                mContext.getResources().getInteger(
+                        R.integer.config_wifiBssidBlocklistMonitorAssociationTimeoutThreshold);
+        FAILURE_COUNT_DISABLE_THRESHOLD[REASON_AUTHENTICATION_FAILURE] =
+                mContext.getResources().getInteger(
+                        R.integer.config_wifiBssidBlocklistMonitorAuthenticationFailureThreshold);
+        FAILURE_COUNT_DISABLE_THRESHOLD[REASON_DHCP_FAILURE] =
+                mContext.getResources().getInteger(
+                        R.integer.config_wifiBssidBlocklistMonitorDhcpFailureThreshold);
+        FAILURE_COUNT_DISABLE_THRESHOLD[REASON_ABNORMAL_DISCONNECT] =
+                mContext.getResources().getInteger(
+                        R.integer.config_wifiBssidBlocklistMonitorAbnormalDisconnectThreshold);
+        FAILURE_COUNT_DISABLE_THRESHOLD[REASON_NONLOCAL_DISCONNECT_CONNECTING] =
+                mContext.getResources().getInteger(R.integer
+                        .config_wifiBssidBlocklistMonitorNonlocalDisconnectConnectingThreshold);
+        mFailureCountDisableThresholdArrayInitialized = true;
+        return FAILURE_COUNT_DISABLE_THRESHOLD[reasonCode];
+    }
+
+    private boolean handleBssidConnectionFailureInternal(String bssid, String ssid,
+            @FailureReason int reasonCode, int rssi) {
+        BssidStatus entry = incrementFailureCountForBssid(bssid, ssid, reasonCode);
+        int failureThreshold = getFailureThresholdForReason(reasonCode);
+        int currentStreak = mWifiScoreCard.getBssidBlocklistStreak(ssid, bssid, reasonCode);
+        if (currentStreak > 0 || entry.failureCount[reasonCode] >= failureThreshold) {
+            // To rule out potential device side issues, don't add to blocklist if
+            // WifiLastResortWatchdog is still not triggered
+            if (shouldWaitForWatchdogToTriggerFirst(bssid, reasonCode)) {
+                localLog("Ignoring failure to wait for watchdog to trigger first.");
+                return false;
+            }
+            int baseBlockDurationMs = getBaseBlockDurationForReason(reasonCode);
+            addToBlocklist(entry,
+                    getBlocklistDurationWithExponentialBackoff(currentStreak, baseBlockDurationMs),
+                    reasonCode, rssi);
+            mWifiScoreCard.incrementBssidBlocklistStreak(ssid, bssid, reasonCode);
+            return true;
+        }
+        return false;
+    }
+
+    private int getBaseBlockDurationForReason(int blockReason) {
+        switch (blockReason) {
+            case REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE:
+                return mContext.getResources().getInteger(R.integer
+                        .config_wifiBssidBlocklistMonitorConnectedScoreBaseBlockDurationMs);
+            default:
+                return mContext.getResources().getInteger(
+                        R.integer.config_wifiBssidBlocklistMonitorBaseBlockDurationMs);
+        }
+    }
+
+    /**
+     * Note a failure event on a bssid and perform appropriate actions.
+     * @return True if the blocklist has been modified.
+     */
+    public boolean handleBssidConnectionFailure(String bssid, String ssid,
+            @FailureReason int reasonCode, int rssi) {
+        if (!isValidNetworkAndFailureReason(bssid, ssid, reasonCode)) {
+            return false;
+        }
+        BssidDisableReason bssidDisableReason = mBssidDisableReasons.get(reasonCode);
+        if (bssidDisableReason == null) {
+            Log.e(TAG, "Bssid disable reason not found. ReasonCode=" + reasonCode);
+            return false;
+        }
+        if (bssidDisableReason.ignoreIfOnlyBssid && !mDisabledSsids.contains(ssid)
+                && mWifiLastResortWatchdog.isBssidOnlyApOfSsid(bssid)) {
+            localLog("Ignoring BSSID failure due to no other APs available. BSSID=" + bssid);
+            return false;
+        }
+        if (reasonCode == REASON_ABNORMAL_DISCONNECT) {
+            long connectionTime = mWifiScoreCard.getBssidConnectionTimestampMs(ssid, bssid);
+            // only count disconnects that happen shortly after a connection.
+            if (mClock.getWallClockMillis() - connectionTime
+                    > mContext.getResources().getInteger(
+                            R.integer.config_wifiBssidBlocklistAbnormalDisconnectTimeWindowMs)) {
+                return false;
+            }
+        }
+        return handleBssidConnectionFailureInternal(bssid, ssid, reasonCode, rssi);
+    }
+
+    /**
+     * To be called when a WifiConfiguration is either temporarily disabled or permanently disabled.
+     * @param ssid of the WifiConfiguration that is disabled.
+     */
+    public void handleWifiConfigurationDisabled(String ssid) {
+        if (ssid != null) {
+            mDisabledSsids.add(ssid);
+        }
+    }
+
+    /**
+     * Note a connection success event on a bssid and clear appropriate failure counters.
+     */
+    public void handleBssidConnectionSuccess(@NonNull String bssid, @NonNull String ssid) {
+        mDisabledSsids.remove(ssid);
+        /**
+         * First reset the blocklist streak.
+         * This needs to be done even if a BssidStatus is not found, since the BssidStatus may
+         * have been removed due to blocklist timeout.
+         */
+        mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_AP_UNABLE_TO_HANDLE_NEW_STA);
+        mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_WRONG_PASSWORD);
+        mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_EAP_FAILURE);
+        mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_ASSOCIATION_REJECTION);
+        mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_ASSOCIATION_TIMEOUT);
+        mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_AUTHENTICATION_FAILURE);
+        mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid,
+                REASON_NONLOCAL_DISCONNECT_CONNECTING);
+
+        long connectionTime = mClock.getWallClockMillis();
+        long prevConnectionTime = mWifiScoreCard.setBssidConnectionTimestampMs(
+                ssid, bssid, connectionTime);
+        if (connectionTime - prevConnectionTime > ABNORMAL_DISCONNECT_RESET_TIME_MS) {
+            mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_ABNORMAL_DISCONNECT);
+            mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid,
+                    REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE);
+        }
+
+        BssidStatus status = mBssidStatusMap.get(bssid);
+        if (status == null) {
+            return;
+        }
+        // Clear the L2 failure counters
+        status.failureCount[REASON_AP_UNABLE_TO_HANDLE_NEW_STA] = 0;
+        status.failureCount[REASON_WRONG_PASSWORD] = 0;
+        status.failureCount[REASON_EAP_FAILURE] = 0;
+        status.failureCount[REASON_ASSOCIATION_REJECTION] = 0;
+        status.failureCount[REASON_ASSOCIATION_TIMEOUT] = 0;
+        status.failureCount[REASON_AUTHENTICATION_FAILURE] = 0;
+        status.failureCount[REASON_NONLOCAL_DISCONNECT_CONNECTING] = 0;
+        if (connectionTime - prevConnectionTime > ABNORMAL_DISCONNECT_RESET_TIME_MS) {
+            status.failureCount[REASON_ABNORMAL_DISCONNECT] = 0;
+        }
+    }
+
+    /**
+     * Note a successful network validation on a BSSID and clear appropriate failure counters.
+     * And then remove the BSSID from blocklist.
+     */
+    public void handleNetworkValidationSuccess(@NonNull String bssid, @NonNull String ssid) {
+        mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_NETWORK_VALIDATION_FAILURE);
+        BssidStatus status = mBssidStatusMap.get(bssid);
+        if (status == null) {
+            return;
+        }
+        status.failureCount[REASON_NETWORK_VALIDATION_FAILURE] = 0;
+        /**
+         * Network validation may take more than 1 tries to succeed.
+         * remove the BSSID from blocklist to make sure we are not accidentally blocking good
+         * BSSIDs.
+         **/
+        if (status.isInBlocklist) {
+            mBssidBlocklistMonitorLogger.logBssidUnblocked(status, "Network validation success");
+            mBssidStatusMap.remove(bssid);
+        }
+    }
+
+    /**
+     * Note a successful DHCP provisioning and clear appropriate faliure counters.
+     */
+    public void handleDhcpProvisioningSuccess(@NonNull String bssid, @NonNull String ssid) {
+        mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_DHCP_FAILURE);
+        BssidStatus status = mBssidStatusMap.get(bssid);
+        if (status == null) {
+            return;
+        }
+        status.failureCount[REASON_DHCP_FAILURE] = 0;
+    }
+
+    /**
+     * Note the removal of a network from the Wifi stack's internal database and reset
+     * appropriate failure counters.
+     * @param ssid
+     */
+    public void handleNetworkRemoved(@NonNull String ssid) {
+        clearBssidBlocklistForSsid(ssid);
+        mWifiScoreCard.resetBssidBlocklistStreakForSsid(ssid);
+    }
+
+    /**
+     * Clears the blocklist for BSSIDs associated with the input SSID only.
+     * @param ssid
+     */
+    public void clearBssidBlocklistForSsid(@NonNull String ssid) {
+        int prevSize = mBssidStatusMap.size();
+        mBssidStatusMap.entrySet().removeIf(e -> {
+            BssidStatus status = e.getValue();
+            if (status.ssid == null) {
+                return false;
+            }
+            if (status.ssid.equals(ssid)) {
+                mBssidBlocklistMonitorLogger.logBssidUnblocked(
+                        status, "clearBssidBlocklistForSsid");
+                return true;
+            }
+            return false;
+        });
+        int diff = prevSize - mBssidStatusMap.size();
+        if (diff > 0) {
+            localLog(TAG + " clearBssidBlocklistForSsid: SSID=" + ssid
+                    + ", num BSSIDs cleared=" + diff);
+        }
+    }
+
+    /**
+     * Clears the BSSID blocklist and failure counters.
+     */
+    public void clearBssidBlocklist() {
+        if (mBssidStatusMap.size() > 0) {
+            int prevSize = mBssidStatusMap.size();
+            for (BssidStatus status : mBssidStatusMap.values()) {
+                mBssidBlocklistMonitorLogger.logBssidUnblocked(status, "clearBssidBlocklist");
+            }
+            mBssidStatusMap.clear();
+            localLog(TAG + " clearBssidBlocklist: num BSSIDs cleared="
+                    + (prevSize - mBssidStatusMap.size()));
+        }
+        mDisabledSsids.clear();
+    }
+
+    /**
+     * @param ssid
+     * @return the number of BSSIDs currently in the blocklist for the |ssid|.
+     */
+    public int updateAndGetNumBlockedBssidsForSsid(@NonNull String ssid) {
+        return (int) updateAndGetBssidBlocklistInternal()
+                .filter(entry -> ssid.equals(entry.ssid)).count();
+    }
+
+    private int getNumBlockedBssidsForSsids(@NonNull Set<String> ssids) {
+        if (ssids.isEmpty()) {
+            return 0;
+        }
+        return (int) mBssidStatusMap.values().stream()
+                .filter(entry -> entry.isInBlocklist && ssids.contains(entry.ssid))
+                .count();
+    }
+
+    /**
+     * Overloaded version of updateAndGetBssidBlocklist.
+     * Accepts a @Nullable String ssid as input, and updates the firmware roaming
+     * configuration if the blocklist for the input ssid has been changed.
+     * @param ssids set of ssids to update firmware roaming configuration for.
+     * @return Set of BSSIDs currently in the blocklist
+     */
+    public Set<String> updateAndGetBssidBlocklistForSsids(@NonNull Set<String> ssids) {
+        int numBefore = getNumBlockedBssidsForSsids(ssids);
+        Set<String> bssidBlocklist = updateAndGetBssidBlocklist();
+        if (getNumBlockedBssidsForSsids(ssids) != numBefore) {
+            updateFirmwareRoamingConfiguration(ssids);
+        }
+        return bssidBlocklist;
+    }
+
+    /**
+     * Gets the BSSIDs that are currently in the blocklist.
+     * @return Set of BSSIDs currently in the blocklist
+     */
+    public Set<String> updateAndGetBssidBlocklist() {
+        return updateAndGetBssidBlocklistInternal()
+                .map(entry -> entry.bssid)
+                .collect(Collectors.toSet());
+    }
+
+    /**
+     * Gets the list of block reasons for BSSIDs currently in the blocklist.
+     * @return The set of unique reasons for blocking BSSIDs with this SSID.
+     */
+    public Set<Integer> getFailureReasonsForSsid(@NonNull String ssid) {
+        if (ssid == null) {
+            return Collections.emptySet();
+        }
+        return mBssidStatusMap.values().stream()
+                .filter(entry -> entry.isInBlocklist && ssid.equals(entry.ssid))
+                .map(entry -> entry.blockReason)
+                .collect(Collectors.toSet());
+    }
+
+    /**
+     * Attempts to re-enable BSSIDs that likely experienced failures due to low RSSI.
+     * @param scanDetails
+     */
+    public void tryEnablingBlockedBssids(List<ScanDetail> scanDetails) {
+        if (scanDetails == null) {
+            return;
+        }
+        for (ScanDetail scanDetail : scanDetails) {
+            ScanResult scanResult = scanDetail.getScanResult();
+            if (scanResult == null) {
+                continue;
+            }
+            BssidStatus status = mBssidStatusMap.get(scanResult.BSSID);
+            if (status == null || !status.isInBlocklist
+                    || !isLowRssiSensitiveFailure(status.blockReason)) {
+                continue;
+            }
+            int sufficientRssi = mScoringParams.getSufficientRssi(scanResult.frequency);
+            if (status.lastRssi < sufficientRssi && scanResult.level >= sufficientRssi
+                    && scanResult.level - status.lastRssi >= MIN_RSSI_DIFF_TO_UNBLOCK_BSSID) {
+                mBssidBlocklistMonitorLogger.logBssidUnblocked(
+                        status, "rssi significantly improved");
+                mBssidStatusMap.remove(status.bssid);
+            }
+        }
+    }
+
+    private boolean isLowRssiSensitiveFailure(int blockReason) {
+        return mBssidDisableReasons.get(blockReason) == null ? false
+                : mBssidDisableReasons.get(blockReason).isLowRssiSensitive;
+    }
+
+    /**
+     * Removes expired BssidStatus entries and then return remaining entries in the blocklist.
+     * @return Stream of BssidStatus for BSSIDs that are in the blocklist.
+     */
+    private Stream<BssidStatus> updateAndGetBssidBlocklistInternal() {
+        Stream.Builder<BssidStatus> builder = Stream.builder();
+        long curTime = mClock.getWallClockMillis();
+        mBssidStatusMap.entrySet().removeIf(e -> {
+            BssidStatus status = e.getValue();
+            if (status.isInBlocklist) {
+                if (status.blocklistEndTimeMs < curTime) {
+                    mBssidBlocklistMonitorLogger.logBssidUnblocked(
+                            status, "updateAndGetBssidBlocklistInternal");
+                    return true;
+                }
+                builder.accept(status);
+            }
+            return false;
+        });
+        return builder.build();
+    }
+
+    /**
+     * Sends the BSSIDs belonging to the input SSID down to the firmware to prevent auto-roaming
+     * to those BSSIDs.
+     * @param ssids
+     */
+    public void updateFirmwareRoamingConfiguration(@NonNull Set<String> ssids) {
+        if (!mConnectivityHelper.isFirmwareRoamingSupported()) {
+            return;
+        }
+        ArrayList<String> bssidBlocklist = updateAndGetBssidBlocklistInternal()
+                .filter(entry -> ssids.contains(entry.ssid))
+                .sorted((o1, o2) -> (int) (o2.blocklistEndTimeMs - o1.blocklistEndTimeMs))
+                .map(entry -> entry.bssid)
+                .collect(Collectors.toCollection(ArrayList::new));
+        int fwMaxBlocklistSize = mConnectivityHelper.getMaxNumBlocklistBssid();
+        if (fwMaxBlocklistSize <= 0) {
+            Log.e(TAG, "Invalid max BSSID blocklist size:  " + fwMaxBlocklistSize);
+            return;
+        }
+        // Having the blocklist size exceeding firmware max limit is unlikely because we have
+        // already flitered based on SSID. But just in case this happens, we are prioritizing
+        // sending down BSSIDs blocked for the longest time.
+        if (bssidBlocklist.size() > fwMaxBlocklistSize) {
+            bssidBlocklist = new ArrayList<String>(bssidBlocklist.subList(0,
+                    fwMaxBlocklistSize));
+        }
+
+        // Collect all the allowed SSIDs
+        Set<String> allowedSsidSet = new HashSet<>();
+        for (String ssid : ssids) {
+            List<String> allowedSsidsForSsid = mSsidAllowlistMap.get(ssid);
+            if (allowedSsidsForSsid != null) {
+                allowedSsidSet.addAll(allowedSsidsForSsid);
+            }
+        }
+        ArrayList<String> ssidAllowlist = new ArrayList<>(allowedSsidSet);
+        int allowlistSize = ssidAllowlist.size();
+        int maxAllowlistSize = mConnectivityHelper.getMaxNumAllowlistSsid();
+        if (maxAllowlistSize <= 0) {
+            Log.wtf(TAG, "Invalid max SSID allowlist size:  " + maxAllowlistSize);
+            return;
+        }
+        if (allowlistSize > maxAllowlistSize) {
+            ssidAllowlist = new ArrayList<>(ssidAllowlist.subList(0, maxAllowlistSize));
+            localLog("Trim down SSID allowlist size from " + allowlistSize + " to "
+                    + ssidAllowlist.size());
+        }
+
+        // plumb down to HAL
+        String message = "set firmware roaming configurations. "
+                + "bssidBlocklist=";
+        if (bssidBlocklist.size() == 0) {
+            message += "<EMPTY>";
+        } else {
+            message += String.join(", ", bssidBlocklist);
+        }
+        if (!mConnectivityHelper.setFirmwareRoamingConfiguration(bssidBlocklist, ssidAllowlist)) {
+            Log.e(TAG, "Failed to " + message);
+            mBssidBlocklistMonitorLogger.log("Failed to " + message);
+        } else {
+            mBssidBlocklistMonitorLogger.log("Successfully " + message);
+        }
+    }
+
+    @VisibleForTesting
+    public int getBssidBlocklistMonitorLoggerSize() {
+        return mBssidBlocklistMonitorLogger.size();
+    }
+
+    private class BssidBlocklistMonitorLogger {
+        private LinkedList<String> mLogBuffer = new LinkedList<>();
+        private int mBufferSize;
+
+        BssidBlocklistMonitorLogger(int bufferSize) {
+            mBufferSize = bufferSize;
+        }
+
+        public void logBssidUnblocked(BssidStatus bssidStatus, String unblockReason) {
+            // only log history for Bssids that had been blocked.
+            if (bssidStatus == null || !bssidStatus.isInBlocklist) {
+                return;
+            }
+            StringBuilder sb = createStringBuilderWithLogTime();
+            sb.append(", Bssid unblocked, Reason=" + unblockReason);
+            sb.append(", Unblocked BssidStatus={" + bssidStatus.toString() + "}");
+            logInternal(sb.toString());
+        }
+
+        // cache a single line of log message in the rotating buffer
+        public void log(String message) {
+            if (message == null) {
+                return;
+            }
+            StringBuilder sb = createStringBuilderWithLogTime();
+            sb.append(" " + message);
+            logInternal(sb.toString());
+        }
+
+        private StringBuilder createStringBuilderWithLogTime() {
+            StringBuilder sb = new StringBuilder();
+            Calendar calendar = Calendar.getInstance();
+            calendar.setTimeInMillis(mClock.getWallClockMillis());
+            sb.append("logTimeMs=" + String.format("%tm-%td %tH:%tM:%tS.%tL", calendar, calendar,
+                    calendar, calendar, calendar, calendar));
+            return sb;
+        }
+
+        private void logInternal(String message) {
+            mLogBuffer.add(message);
+            if (mLogBuffer.size() > mBufferSize) {
+                mLogBuffer.removeFirst();
+            }
+        }
+
+        @VisibleForTesting
+        public int size() {
+            return mLogBuffer.size();
+        }
+
+        public void dump(PrintWriter pw) {
+            pw.println("WifiBlocklistMonitor - Bssid blocklist logs begin ----");
+            for (String line : mLogBuffer) {
+                pw.println(line);
+            }
+            pw.println("WifiBlocklistMonitor - Bssid blocklist logs end ----");
+        }
+    }
+
+    /**
+     * Helper class that counts the number of failures per BSSID.
+     */
+    private class BssidStatus {
+        public final String bssid;
+        public final String ssid;
+        public final int[] failureCount = new int[NUMBER_REASON_CODES];
+        public int blockReason = INVALID_REASON; // reason of blocking this BSSID
+        // The latest RSSI that's seen before this BSSID is added to blocklist.
+        public int lastRssi = 0;
+
+        // The following are used to flag how long this BSSID stays in the blocklist.
+        public boolean isInBlocklist;
+        public long blocklistEndTimeMs;
+        public long blocklistStartTimeMs;
+
+        BssidStatus(String bssid, String ssid) {
+            this.bssid = bssid;
+            this.ssid = ssid;
+        }
+
+        /**
+         * increments the failure count for the reasonCode by 1.
+         * @return the incremented failure count
+         */
+        public int incrementFailureCount(int reasonCode) {
+            return ++failureCount[reasonCode];
+        }
+
+        /**
+         * Set this BSSID as blocked for the specified duration.
+         * @param durationMs
+         * @param blockReason
+         * @param rssi
+         */
+        public void setAsBlocked(long durationMs, @FailureReason int blockReason, int rssi) {
+            isInBlocklist = true;
+            blocklistStartTimeMs = mClock.getWallClockMillis();
+            blocklistEndTimeMs = blocklistStartTimeMs + durationMs;
+            this.blockReason = blockReason;
+            lastRssi = rssi;
+            mWifiMetrics.incrementBssidBlocklistCount(blockReason);
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("BSSID=" + bssid);
+            sb.append(", SSID=" + ssid);
+            sb.append(", isInBlocklist=" + isInBlocklist);
+            if (isInBlocklist) {
+                sb.append(", blockReason=" + getFailureReasonString(blockReason));
+                sb.append(", lastRssi=" + lastRssi);
+                Calendar calendar = Calendar.getInstance();
+                calendar.setTimeInMillis(blocklistStartTimeMs);
+                sb.append(", blocklistStartTimeMs="
+                        + String.format("%tm-%td %tH:%tM:%tS.%tL", calendar, calendar,
+                        calendar, calendar, calendar, calendar));
+                calendar.setTimeInMillis(blocklistEndTimeMs);
+                sb.append(", blocklistEndTimeMs="
+                        + String.format("%tm-%td %tH:%tM:%tS.%tL", calendar, calendar,
+                        calendar, calendar, calendar, calendar));
+            }
+            return sb.toString();
+        }
+    }
+
+    /**
+     * Enable/disable verbose logging in WifiBlocklistMonitor.
+     */
+    public void enableVerboseLogging(boolean verbose) {
+        mVerboseLoggingEnabled = verbose;
+    }
+
+    /**
+     * Modify the internal copy of DisableReasonInfo with custom configurations defined in
+     * an overlay.
+     */
+    private void loadCustomConfigsForDisableReasonInfos() {
+        mDisableReasonInfo.put(NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION,
+                new DisableReasonInfo(
+                        // Note that there is a space at the end of this string. Cannot fix
+                        // since this string is persisted.
+                        "NETWORK_SELECTION_DISABLED_ASSOCIATION_REJECTION ",
+                        mContext.getResources().getInteger(R.integer
+                                .config_wifiDisableReasonAssociationRejectionThreshold),
+                        5 * 60 * 1000));
+
+        mDisableReasonInfo.put(NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE,
+                new DisableReasonInfo(
+                        "NETWORK_SELECTION_DISABLED_AUTHENTICATION_FAILURE",
+                        mContext.getResources().getInteger(R.integer
+                                .config_wifiDisableReasonAuthenticationFailureThreshold),
+                        5 * 60 * 1000));
+
+        mDisableReasonInfo.put(NetworkSelectionStatus.DISABLED_DHCP_FAILURE,
+                new DisableReasonInfo(
+                        "NETWORK_SELECTION_DISABLED_DHCP_FAILURE",
+                        mContext.getResources().getInteger(R.integer
+                                .config_wifiDisableReasonDhcpFailureThreshold),
+                        5 * 60 * 1000));
+
+        mDisableReasonInfo.put(NetworkSelectionStatus.DISABLED_NETWORK_NOT_FOUND,
+                new DisableReasonInfo(
+                        "NETWORK_SELECTION_DISABLED_NETWORK_NOT_FOUND",
+                        mContext.getResources().getInteger(R.integer
+                                .config_wifiDisableReasonNetworkNotFoundThreshold),
+                        5 * 60 * 1000));
+    }
+
+    /** Update DisableReasonInfo with carrier configurations defined in an overlay. **/
+    public void loadCarrierConfigsForDisableReasonInfos() {
+        int duration = mContext.getResources().getInteger(
+                R.integer.config_wifiDisableReasonAuthenticationFailureCarrierSpecificDurationMs);
+        DisableReasonInfo disableReasonInfo = new DisableReasonInfo(
+                "NETWORK_SELECTION_DISABLED_AUTHENTICATION_PRIVATE_EAP_ERROR",
+                mContext.getResources().getInteger(R.integer
+                        .config_wifiDisableReasonAuthenticationFailureCarrierSpecificThreshold),
+                duration);
+        mDisableReasonInfo.put(
+                NetworkSelectionStatus.DISABLED_AUTHENTICATION_PRIVATE_EAP_ERROR,
+                disableReasonInfo);
+    }
+
+    /**
+     * Returns true if the disable duration for this WifiConfiguration has passed. Returns false
+     * if the WifiConfiguration is either not disabled or is permanently disabled.
+     */
+    public boolean shouldEnableNetwork(WifiConfiguration config) {
+        NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
+        if (networkStatus.isNetworkTemporaryDisabled()) {
+            long timeDifferenceMs =
+                    mClock.getElapsedSinceBootMillis() - networkStatus.getDisableTime();
+            int disableReason = networkStatus.getNetworkSelectionDisableReason();
+            long disableTimeoutMs = (long) getNetworkSelectionDisableTimeoutMillis(disableReason);
+            int exponentialBackoffCount = mWifiScoreCard.lookupNetwork(config.SSID)
+                    .getRecentStats().getCount(WifiScoreCard.CNT_CONSECUTIVE_CONNECTION_FAILURE)
+                    - NUM_CONSECUTIVE_FAILURES_PER_NETWORK_EXP_BACKOFF;
+            for (int i = 0; i < exponentialBackoffCount; i++) {
+                disableTimeoutMs *= 2;
+                if (disableTimeoutMs > WIFI_CONFIG_MAX_DISABLE_DURATION_MILLIS) {
+                    disableTimeoutMs = WIFI_CONFIG_MAX_DISABLE_DURATION_MILLIS;
+                    break;
+                }
+            }
+            if (timeDifferenceMs >= disableTimeoutMs) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Update a network's status (both internal and public) according to the update reason and
+     * its current state. This method is expects to directly modify the internal WifiConfiguration
+     * that is stored by WifiConfigManager.
+     *
+     * @param config the internal WifiConfiguration to be updated.
+     * @param reason reason code for update.
+     * @return true if the input configuration has been updated, false otherwise.
+     */
+    public boolean updateNetworkSelectionStatus(WifiConfiguration config, int reason) {
+        if (reason < 0 || reason >= NetworkSelectionStatus.NETWORK_SELECTION_DISABLED_MAX) {
+            Log.e(TAG, "Invalid Network disable reason " + reason);
+            return false;
+        }
+        NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
+        if (reason != NetworkSelectionStatus.DISABLED_NONE) {
+            // Do not update SSID blocklist with information if this is the only
+            // SSID be observed. By ignoring it we will cause additional failures
+            // which will trigger Watchdog.
+            if (reason == NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION
+                    || reason == NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE
+                    || reason == NetworkSelectionStatus.DISABLED_DHCP_FAILURE) {
+                if (mWifiLastResortWatchdog.shouldIgnoreSsidUpdate()) {
+                    if (mVerboseLoggingEnabled) {
+                        Log.v(TAG, "Ignore update network selection status "
+                                + "since Watchdog trigger is activated");
+                    }
+                    return false;
+                }
+            }
+
+            networkStatus.incrementDisableReasonCounter(reason);
+            // For network disable reasons, we should only update the status if we cross the
+            // threshold.
+            int disableReasonCounter = networkStatus.getDisableReasonCounter(reason);
+            int disableReasonThreshold = getNetworkSelectionDisableThreshold(reason);
+            if (disableReasonCounter < disableReasonThreshold) {
+                if (mVerboseLoggingEnabled) {
+                    Log.v(TAG, "Disable counter for network " + config.getPrintableSsid()
+                            + " for reason "
+                            + NetworkSelectionStatus.getNetworkSelectionDisableReasonString(reason)
+                            + " is " + networkStatus.getDisableReasonCounter(reason)
+                            + " and threshold is " + disableReasonThreshold);
+                }
+                return true;
+            }
+        }
+        setNetworkSelectionStatus(config, reason);
+        return true;
+    }
+
+    /**
+     * Sets a network's status (both internal and public) according to the update reason and
+     * its current state.
+     *
+     * This updates the network's {@link WifiConfiguration#mNetworkSelectionStatus} field and the
+     * public {@link WifiConfiguration#status} field if the network is either enabled or
+     * permanently disabled.
+     *
+     * @param config network to be updated.
+     * @param reason reason code for update.
+     */
+    private void setNetworkSelectionStatus(WifiConfiguration config, int reason) {
+        NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
+        if (reason == NetworkSelectionStatus.DISABLED_NONE) {
+            setNetworkSelectionEnabled(config);
+        } else if (getNetworkSelectionDisableTimeoutMillis(reason)
+                != DisableReasonInfo.PERMANENT_DISABLE_TIMEOUT) {
+            setNetworkSelectionTemporarilyDisabled(config, reason);
+        } else {
+            setNetworkSelectionPermanentlyDisabled(config, reason);
+        }
+        localLog("setNetworkSelectionStatus: configKey=" + config.getProfileKey()
+                + " networkStatus=" + networkStatus.getNetworkStatusString() + " disableReason="
+                + networkStatus.getNetworkSelectionDisableReasonString());
+    }
+
+    /**
+     * Helper method to mark a network enabled for network selection.
+     */
+    private void setNetworkSelectionEnabled(WifiConfiguration config) {
+        NetworkSelectionStatus status = config.getNetworkSelectionStatus();
+        if (status.getNetworkSelectionStatus()
+                != NetworkSelectionStatus.NETWORK_SELECTION_ENABLED) {
+            localLog("setNetworkSelectionEnabled: configKey=" + config.getProfileKey()
+                    + " old networkStatus=" + status.getNetworkStatusString()
+                    + " disableReason=" + status.getNetworkSelectionDisableReasonString());
+        }
+        status.setNetworkSelectionStatus(
+                NetworkSelectionStatus.NETWORK_SELECTION_ENABLED);
+        status.setDisableTime(
+                NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
+        status.setNetworkSelectionDisableReason(NetworkSelectionStatus.DISABLED_NONE);
+
+        // Clear out all the disable reason counters.
+        status.clearDisableReasonCounter();
+        config.status = WifiConfiguration.Status.ENABLED;
+    }
+
+    /**
+     * Helper method to mark a network temporarily disabled for network selection.
+     */
+    private void setNetworkSelectionTemporarilyDisabled(
+            WifiConfiguration config, int disableReason) {
+        NetworkSelectionStatus status = config.getNetworkSelectionStatus();
+        status.setNetworkSelectionStatus(
+                NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED);
+        // Only need a valid time filled in for temporarily disabled networks.
+        status.setDisableTime(mClock.getElapsedSinceBootMillis());
+        status.setNetworkSelectionDisableReason(disableReason);
+        handleWifiConfigurationDisabled(config.SSID);
+        mWifiMetrics.incrementWificonfigurationBlocklistCount(disableReason);
+    }
+
+    /**
+     * Helper method to mark a network permanently disabled for network selection.
+     */
+    private void setNetworkSelectionPermanentlyDisabled(
+            WifiConfiguration config, int disableReason) {
+        NetworkSelectionStatus status = config.getNetworkSelectionStatus();
+        status.setNetworkSelectionStatus(
+                NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED);
+        status.setDisableTime(
+                NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
+        status.setNetworkSelectionDisableReason(disableReason);
+        handleWifiConfigurationDisabled(config.SSID);
+        config.status = WifiConfiguration.Status.DISABLED;
+        mWifiMetrics.incrementWificonfigurationBlocklistCount(disableReason);
+    }
+
+    /**
+     * Network Selection disable reason thresholds. These numbers are used to debounce network
+     * failures before we disable them.
+     *
+     * @param reason int reason code
+     * @return the disable threshold, or -1 if not found.
+     */
+    @VisibleForTesting
+    public int getNetworkSelectionDisableThreshold(@NetworkSelectionDisableReason int reason) {
+        DisableReasonInfo info = mDisableReasonInfo.get(reason);
+        if (info == null) {
+            Log.e(TAG, "Unrecognized network disable reason code for disable threshold: " + reason);
+            return -1;
+        } else {
+            return info.mDisableThreshold;
+        }
+    }
+
+    /**
+     * Network Selection disable timeout for each kind of error. After the timeout in milliseconds,
+     * enable the network again.
+     */
+    @VisibleForTesting
+    public int getNetworkSelectionDisableTimeoutMillis(@NetworkSelectionDisableReason int reason) {
+        DisableReasonInfo info = mDisableReasonInfo.get(reason);
+        if (info == null) {
+            Log.e(TAG, "Unrecognized network disable reason code for disable timeout: " + reason);
+            return -1;
+        } else {
+            return info.mDisableTimeoutMillis;
+        }
+    }
+
+    /**
+     * Sets the allowlist ssids for the given ssid
+     */
+    public void setAllowlistSsids(@NonNull String ssid, @NonNull List<String> ssidAllowlist) {
+        mSsidAllowlistMap.put(ssid, ssidAllowlist);
+    }
+}
diff --git a/service/java/com/android/server/wifi/WifiCandidates.java b/service/java/com/android/server/wifi/WifiCandidates.java
index 0513e5c..5f17369 100644
--- a/service/java/com/android/server/wifi/WifiCandidates.java
+++ b/service/java/com/android/server/wifi/WifiCandidates.java
@@ -21,12 +21,12 @@
 import android.content.Context;
 import android.net.MacAddress;
 import android.net.wifi.ScanResult;
+import android.net.wifi.SecurityParams;
 import android.net.wifi.WifiConfiguration;
 import android.util.ArrayMap;
 
 import com.android.internal.util.Preconditions;
 import com.android.server.wifi.proto.WifiScoreCardProto;
-import com.android.wifi.resources.R;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -91,6 +91,14 @@
          */
         boolean isTrusted();
         /**
+         * Returns true for a oem paid network.
+         */
+        boolean isOemPaid();
+        /**
+         * Returns true for a oem private network.
+         */
+        boolean isOemPrivate();
+        /**
          * Returns true if suggestion came from a carrier or privileged app.
          */
         boolean isCarrierOrPrivileged();
@@ -173,6 +181,8 @@
         private final boolean mPasspoint;
         private final boolean mEphemeral;
         private final boolean mTrusted;
+        private final boolean mOemPaid;
+        private final boolean mOemPrivate;
         private final boolean mCarrierOrPrivileged;
         private final int mPredictedThroughputMbps;
         private final int mEstimatedPercentInternetAvailability;
@@ -203,6 +213,8 @@
             this.mPasspoint = config.isPasspoint();
             this.mEphemeral = config.isEphemeral();
             this.mTrusted = config.trusted;
+            this.mOemPaid = config.oemPaid;
+            this.mOemPrivate = config.oemPrivate;
             this.mCarrierOrPrivileged = isCarrierOrPrivileged;
             this.mPredictedThroughputMbps = predictedThroughputMbps;
             this.mEstimatedPercentInternetAvailability = perBssid == null ? 50 :
@@ -240,6 +252,16 @@
         }
 
         @Override
+        public boolean isOemPaid() {
+            return mOemPaid;
+        }
+
+        @Override
+        public boolean isOemPrivate() {
+            return mOemPrivate;
+        }
+
+        @Override
         public boolean isCarrierOrPrivileged() {
             return mCarrierOrPrivileged;
         }
@@ -333,6 +355,8 @@
                     + (isCurrentNetwork() ? "current, " : "")
                     + (isEphemeral() ? "ephemeral" : "saved") + ", "
                     + (isTrusted() ? "trusted, " : "")
+                    + (isOemPaid() ? "oemPaid, " : "")
+                    + (isOemPrivate() ? "oemPrivate, " : "")
                     + (isCarrierOrPrivileged() ? "priv, " : "")
                     + (isMetered() ? "metered, " : "")
                     + (hasNoInternetAccess() ? "noInternet, " : "")
@@ -397,6 +421,7 @@
         public final ScanResultMatchInfo matchInfo; // Contains the SSID and security type
         public final MacAddress bssid;
         public final int networkId;                 // network configuration id
+        public final @WifiConfiguration.SecurityType int securityType;
 
         public Key(ScanResultMatchInfo matchInfo,
                    MacAddress bssid,
@@ -404,6 +429,18 @@
             this.matchInfo = matchInfo;
             this.bssid = bssid;
             this.networkId = networkId;
+            // If security type is not set, use the default security params.
+            this.securityType = matchInfo.getDefaultSecurityParams().getSecurityType();
+        }
+
+        public Key(ScanResultMatchInfo matchInfo,
+                   MacAddress bssid,
+                   int networkId,
+                   int securityType) {
+            this.matchInfo = matchInfo;
+            this.bssid = bssid;
+            this.networkId = networkId;
+            this.securityType = securityType;
         }
 
         @Override
@@ -412,12 +449,13 @@
             Key that = (Key) other;
             return (this.matchInfo.equals(that.matchInfo)
                     && this.bssid.equals(that.bssid)
-                    && this.networkId == that.networkId);
+                    && this.networkId == that.networkId
+                    && this.securityType == that.securityType);
         }
 
         @Override
         public int hashCode() {
-            return Objects.hash(matchInfo, bssid, networkId);
+            return Objects.hash(matchInfo, bssid, networkId, securityType);
         }
     }
 
@@ -468,9 +506,14 @@
     public @Nullable Key keyFromScanDetailAndConfig(ScanDetail scanDetail,
             WifiConfiguration config) {
         if (!validConfigAndScanDetail(config, scanDetail)) return null;
+
         ScanResult scanResult = scanDetail.getScanResult();
+        SecurityParams params = ScanResultMatchInfo.fromScanResult(scanResult)
+                .matchForNetworkSelection(ScanResultMatchInfo.fromWifiConfiguration(config));
+        if (null == params) return null;
         MacAddress bssid = MacAddress.fromString(scanResult.BSSID);
-        return new Key(ScanResultMatchInfo.fromScanResult(scanResult), bssid, config.networkId);
+        return new Key(ScanResultMatchInfo.fromScanResult(scanResult), bssid, config.networkId,
+                params.getSecurityType());
     }
 
     /**
@@ -497,7 +540,8 @@
                 key.matchInfo.networkSsid,
                 key.bssid.toString());
         perBssid.setSecurityType(
-                WifiScoreCardProto.SecurityType.forNumber(key.matchInfo.networkType));
+                WifiScoreCardProto.SecurityType.forNumber(
+                    key.matchInfo.getDefaultSecurityParams().getSecurityType()));
         perBssid.setNetworkConfigId(config.networkId);
         CandidateImpl candidate = new CandidateImpl(key, config, perBssid, nominatorId,
                 scanRssi,
@@ -534,8 +578,7 @@
         ScanResultMatchInfo key1 = ScanResultMatchInfo.fromScanResult(scanResult);
         if (!config.isPasspoint()) {
             ScanResultMatchInfo key2 = ScanResultMatchInfo.fromWifiConfiguration(config);
-            if (!key1.matchForNetworkSelection(key2, mContext.getResources()
-                    .getBoolean(R.bool.config_wifiSaeUpgradeEnabled))) {
+            if (!key1.equals(key2)) {
                 return failure(key1, key2);
             }
         }
diff --git a/service/java/com/android/server/wifi/WifiCarrierInfoManager.java b/service/java/com/android/server/wifi/WifiCarrierInfoManager.java
index d483047..ff0c4ce 100644
--- a/service/java/com/android/server/wifi/WifiCarrierInfoManager.java
+++ b/service/java/com/android/server/wifi/WifiCarrierInfoManager.java
@@ -16,40 +16,54 @@
 
 package com.android.server.wifi;
 
+import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.telephony.TelephonyManager.DATA_ENABLED_REASON_USER;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.app.AlertDialog;
 import android.app.Notification;
-import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
 import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.net.wifi.hotspot2.pps.Credential;
+import android.os.Build;
 import android.os.Handler;
 import android.os.PersistableBundle;
+import android.os.UserHandle;
 import android.telephony.CarrierConfigManager;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Base64;
 import android.util.Log;
 import android.util.Pair;
+import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.view.WindowManager;
 
+import androidx.annotation.RequiresApi;
+
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.messages.nano.SystemMessageProto;
+import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.modules.utils.HandlerExecutor;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.wifi.resources.R;
 
 import java.io.FileDescriptor;
@@ -96,6 +110,10 @@
     @VisibleForTesting
     public static final String NOTIFICATION_USER_DISMISSED_INTENT_ACTION =
             "com.android.server.wifi.action.CarrierNetwork.USER_DISMISSED";
+    /** Intent when user clicked on the notification. */
+    @VisibleForTesting
+    public static final String NOTIFICATION_USER_CLICKED_INTENT_ACTION =
+            "com.android.server.wifi.action.CarrierNetwork.USER_CLICKED";
     @VisibleForTesting
     public static final String EXTRA_CARRIER_NAME =
             "com.android.server.wifi.extra.CarrierNetwork.CARRIER_NAME";
@@ -137,6 +155,14 @@
     private static final int KC_LEN = 8;
 
     private static final Uri CONTENT_URI = Uri.parse("content://carrier_information/carrier");
+    /**
+     * Expiration timeout for user notification in milliseconds. (15 min)
+     */
+    private static final long NOTIFICATION_EXPIRY_MILLS = 15 * 60 * 1000;
+    /**
+     * Notification update delay in milliseconds. (10 min)
+     */
+    private static final long NOTIFICATION_UPDATE_DELAY_MILLS = 10 * 60 * 1000;
 
     private final WifiContext mContext;
     private final Handler mHandler;
@@ -144,8 +170,15 @@
     private final Resources mResources;
     private final TelephonyManager mTelephonyManager;
     private final SubscriptionManager mSubscriptionManager;
-    private final NotificationManager mNotificationManager;
+    private final WifiNotificationManager mNotificationManager;
     private final WifiMetrics mWifiMetrics;
+    private final Clock mClock;
+    /**
+     * Cached Map of <subscription ID, CarrierConfig PersistableBundle> since retrieving the
+     * PersistableBundle from CarrierConfigManager is somewhat expensive as it has hundreds of
+     * fields. This cache is cleared when the CarrierConfig changes to ensure data freshness.
+     */
+    private final SparseArray<PersistableBundle> mCachedCarrierConfigPerSubId = new SparseArray<>();
 
     /**
      * Intent filter for processing notification actions.
@@ -154,18 +187,103 @@
     private final FrameworkFacade mFrameworkFacade;
 
     private boolean mVerboseLogEnabled = false;
-    private SparseBooleanArray mImsiEncryptionRequired = new SparseBooleanArray();
     private SparseBooleanArray mImsiEncryptionInfoAvailable = new SparseBooleanArray();
-    private SparseBooleanArray mEapMethodPrefixEnable = new SparseBooleanArray();
     private final Map<Integer, Boolean> mImsiPrivacyProtectionExemptionMap = new HashMap<>();
-    private final List<OnUserApproveCarrierListener>
-            mOnUserApproveCarrierListeners =
+    private final Map<Integer, Boolean> mMergedCarrierNetworkOffloadMap = new HashMap<>();
+    private final Map<Integer, Boolean> mUnmergedCarrierNetworkOffloadMap = new HashMap<>();
+    private final List<OnUserApproveCarrierListener> mOnUserApproveCarrierListeners =
             new ArrayList<>();
+    private final SparseBooleanArray mUserDataEnabled = new SparseBooleanArray();
+    private final List<UserDataEnabledChangedListener>  mUserDataEnabledListenerList =
+            new ArrayList<>();
+    private final List<OnCarrierOffloadDisabledListener> mOnCarrierOffloadDisabledListeners =
+            new ArrayList<>();
+    private final SparseArray<SimInfo> mSubIdToSimInfoSparseArray = new SparseArray<>();
 
-    private boolean mUserApprovalUiActive = false;
-    private boolean mHasNewDataToSerialize = false;
+    private List<SubscriptionInfo> mActiveSubInfos = null;
+
+    private boolean mHasNewUserDataToSerialize = false;
+    private boolean mHasNewSharedDataToSerialize = false;
     private boolean mUserDataLoaded = false;
     private boolean mIsLastUserApprovalUiDialog = false;
+    private CarrierConfigManager mCarrierConfigManager;
+    /**
+     * The {@link Clock#getElapsedSinceBootMillis()} must be at least this value for us
+     * to update/show the notification.
+     */
+    private long mNotificationUpdateTime = 0L;
+
+    private static class SimInfo {
+        public final String imsi;
+        public final String mccMnc;
+        public final int carrierIdFromSimMccMnc;
+        public final int simCarrierId;
+
+        SimInfo(String imsi, String mccMnc, int carrierIdFromSimMccMnc, int simCarrierId) {
+            this.imsi = imsi;
+            this.mccMnc = mccMnc;
+            this.carrierIdFromSimMccMnc = carrierIdFromSimMccMnc;
+            this.simCarrierId = simCarrierId;
+        }
+
+        /**
+         * Get the carrier type of current SIM.
+         */
+        public int getCarrierType() {
+            if (carrierIdFromSimMccMnc == simCarrierId) {
+                return CARRIER_MNO_TYPE;
+            }
+            return CARRIER_MVNO_TYPE;
+        }
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder("SimInfo[ ")
+                    .append("IMSI=").append(imsi)
+                    .append(", MCCMNC=").append(mccMnc)
+                    .append(", carrierIdFromSimMccMnc=").append(carrierIdFromSimMccMnc)
+                    .append(", simCarrierId=").append(simCarrierId)
+                    .append(" ]");
+            return sb.toString();
+        }
+    }
+
+    /**
+     * Implement of {@link TelephonyCallback.DataEnabledListener}
+     */
+    @VisibleForTesting
+    @RequiresApi(Build.VERSION_CODES.S)
+    public final class UserDataEnabledChangedListener extends TelephonyCallback implements
+            TelephonyCallback.DataEnabledListener {
+        private final int mSubscriptionId;
+
+        public UserDataEnabledChangedListener(int subscriptionId) {
+            mSubscriptionId = subscriptionId;
+        }
+
+        @Override
+        public void onDataEnabledChanged(boolean enabled, int reason) {
+            if (reason == DATA_ENABLED_REASON_USER) {
+                Log.d(TAG, "Mobile data change by user to "
+                        + (enabled ? "enabled" : "disabled") + " for subId: " + mSubscriptionId);
+                mUserDataEnabled.put(mSubscriptionId, enabled);
+                if (!enabled) {
+                    for (OnCarrierOffloadDisabledListener listener :
+                            mOnCarrierOffloadDisabledListeners) {
+                        listener.onCarrierOffloadDisabled(mSubscriptionId, true);
+                    }
+                }
+            }
+        }
+
+        /**
+         * Unregister the listener from TelephonyManager,
+         */
+        public void unregisterListener() {
+            mTelephonyManager.createForSubscriptionId(mSubscriptionId)
+                    .unregisterTelephonyCallback(this);
+
+        }
+    }
 
     /**
      * Interface for other modules to listen to the user approve IMSI protection exemption.
@@ -179,6 +297,65 @@
     }
 
     /**
+     * Interface for other modules to listen to the carrier network offload disabled.
+     */
+    public interface OnCarrierOffloadDisabledListener {
+
+        /**
+         * Invoke when carrier offload on target subscriptionId is disabled.
+         */
+        void onCarrierOffloadDisabled(int subscriptionId, boolean merged);
+    }
+
+    /**
+     * Module to interact with the wifi config store.
+     */
+    private class WifiCarrierInfoStoreManagerDataSource implements
+            WifiCarrierInfoStoreManagerData.DataSource {
+
+        @Override
+        public Map<Integer, Boolean> toSerializeMergedCarrierNetworkOffloadMap() {
+            return mMergedCarrierNetworkOffloadMap;
+        }
+
+        @Override
+        public Map<Integer, Boolean> toSerializeUnmergedCarrierNetworkOffloadMap() {
+            return mUnmergedCarrierNetworkOffloadMap;
+        }
+
+        @Override
+        public void serializeComplete() {
+            mHasNewSharedDataToSerialize = false;
+        }
+
+
+        @Override
+        public void fromMergedCarrierNetworkOffloadMapDeserialized(
+                Map<Integer, Boolean> carrierOffloadMap) {
+            mMergedCarrierNetworkOffloadMap.clear();
+            mMergedCarrierNetworkOffloadMap.putAll(carrierOffloadMap);
+        }
+
+        @Override
+        public void fromUnmergedCarrierNetworkOffloadMapDeserialized(
+                Map<Integer, Boolean> subscriptionOffloadMap) {
+            mUnmergedCarrierNetworkOffloadMap.clear();
+            mUnmergedCarrierNetworkOffloadMap.putAll(subscriptionOffloadMap);
+        }
+
+        @Override
+        public void reset() {
+            mMergedCarrierNetworkOffloadMap.clear();
+            mUnmergedCarrierNetworkOffloadMap.clear();
+        }
+
+        @Override
+        public boolean hasNewDataToSerialize() {
+            return mHasNewSharedDataToSerialize;
+        }
+    }
+
+    /**
      * Module to interact with the wifi config store.
      */
     private class ImsiProtectionExemptionDataSource implements
@@ -186,8 +363,7 @@
         @Override
         public Map<Integer, Boolean> toSerialize() {
             // Clear the flag after writing to disk.
-            // TODO(b/115504887): Don't reset the flag on write failure.
-            mHasNewDataToSerialize = false;
+            mHasNewUserDataToSerialize = false;
             return mImsiPrivacyProtectionExemptionMap;
         }
 
@@ -206,7 +382,7 @@
 
         @Override
         public boolean hasNewDataToSerialize() {
-            return mHasNewDataToSerialize;
+            return mHasNewUserDataToSerialize;
         }
     }
 
@@ -223,14 +399,16 @@
 
                     switch (intent.getAction()) {
                         case NOTIFICATION_USER_ALLOWED_CARRIER_INTENT_ACTION:
-                            Log.i(TAG, "User clicked to allow carrier");
-                            sendImsiPrivacyConfirmationDialog(carrierName, carrierId);
-                            // Collapse the notification bar
-                            mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+                            handleUserAllowCarrierExemptionAction(carrierName, carrierId);
                             break;
                         case NOTIFICATION_USER_DISALLOWED_CARRIER_INTENT_ACTION:
                             handleUserDisallowCarrierExemptionAction(carrierName, carrierId);
                             break;
+                        case NOTIFICATION_USER_CLICKED_INTENT_ACTION:
+                            sendImsiPrivacyConfirmationDialog(carrierName, carrierId);
+                            // Collapse the notification bar
+                            mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+                            break;
                         case NOTIFICATION_USER_DISMISSED_INTENT_ACTION:
                             handleUserDismissAction();
                             return; // no need to cancel a dismissed notification, return.
@@ -239,13 +417,12 @@
                             return;
                     }
                     // Clear notification once the user interacts with it.
-                    mNotificationManager.cancel(SystemMessageProto
-                            .SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE);
+                    mNotificationManager.cancel(SystemMessage.NOTE_CARRIER_SUGGESTION_AVAILABLE);
                 }
             };
     private void handleUserDismissAction() {
         Log.i(TAG, "User dismissed the notification");
-        mUserApprovalUiActive = false;
+        mNotificationUpdateTime = 0;
         mWifiMetrics.addUserApprovalCarrierUiReaction(ACTION_USER_DISMISS,
                 mIsLastUserApprovalUiDialog);
     }
@@ -253,7 +430,7 @@
     private void handleUserAllowCarrierExemptionAction(String carrierName, int carrierId) {
         Log.i(TAG, "User clicked to allow carrier:" + carrierName);
         setHasUserApprovedImsiPrivacyExemptionForCarrier(true, carrierId);
-        mUserApprovalUiActive = false;
+        mNotificationUpdateTime = 0;
         mWifiMetrics.addUserApprovalCarrierUiReaction(ACTION_USER_ALLOWED_CARRIER,
                 mIsLastUserApprovalUiDialog);
 
@@ -262,16 +439,28 @@
     private void handleUserDisallowCarrierExemptionAction(String carrierName, int carrierId) {
         Log.i(TAG, "User clicked to disallow carrier:" + carrierName);
         setHasUserApprovedImsiPrivacyExemptionForCarrier(false, carrierId);
-        mUserApprovalUiActive = false;
+        mNotificationUpdateTime = 0;
         mWifiMetrics.addUserApprovalCarrierUiReaction(
                 ACTION_USER_DISALLOWED_CARRIER, mIsLastUserApprovalUiDialog);
     }
 
+    private class SubscriptionChangeListener extends
+            SubscriptionManager.OnSubscriptionsChangedListener {
+        @Override
+        public void onSubscriptionsChanged() {
+            mActiveSubInfos = mSubscriptionManager.getActiveSubscriptionInfoList();
+            mSubIdToSimInfoSparseArray.clear();
+            if (mVerboseLogEnabled) {
+                Log.v(TAG, "active subscription changes: " + mActiveSubInfos);
+            }
+        }
+    }
+
     /**
      * Gets the instance of WifiCarrierInfoManager.
      * @param telephonyManager Instance of {@link TelephonyManager}
      * @param subscriptionManager Instance of {@link SubscriptionManager}
-     * @param WifiInjector Instance of {@link WifiInjector}
+     * @param wifiInjector Instance of {@link WifiInjector}
      * @return The instance of WifiCarrierInfoManager
      */
     public WifiCarrierInfoManager(@NonNull TelephonyManager telephonyManager,
@@ -281,7 +470,8 @@
             @NonNull WifiContext context,
             @NonNull WifiConfigStore configStore,
             @NonNull Handler handler,
-            @NonNull WifiMetrics wifiMetrics) {
+            @NonNull WifiMetrics wifiMetrics,
+            @NonNull Clock clock) {
         mTelephonyManager = telephonyManager;
         mContext = context;
         mResources = mContext.getResources();
@@ -290,19 +480,24 @@
         mSubscriptionManager = subscriptionManager;
         mFrameworkFacade = frameworkFacade;
         mWifiMetrics = wifiMetrics;
-        mNotificationManager =
-                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+        mNotificationManager = mWifiInjector.getWifiNotificationManager();
+        mClock = clock;
         // Register broadcast receiver for UI interactions.
         mIntentFilter = new IntentFilter();
         mIntentFilter.addAction(NOTIFICATION_USER_DISMISSED_INTENT_ACTION);
         mIntentFilter.addAction(NOTIFICATION_USER_ALLOWED_CARRIER_INTENT_ACTION);
         mIntentFilter.addAction(NOTIFICATION_USER_DISALLOWED_CARRIER_INTENT_ACTION);
+        mIntentFilter.addAction(NOTIFICATION_USER_CLICKED_INTENT_ACTION);
 
-        mContext.registerReceiver(mBroadcastReceiver, mIntentFilter, null, handler);
-        configStore.registerStoreData(wifiInjector.makeImsiProtectionExemptionStoreData(
+        mContext.registerReceiver(mBroadcastReceiver, mIntentFilter, NETWORK_SETTINGS, handler);
+        configStore.registerStoreData(wifiInjector.makeWifiCarrierInfoStoreManagerData(
+                new WifiCarrierInfoStoreManagerDataSource()));
+        configStore.registerStoreData(wifiInjector.makeImsiPrivacyProtectionExemptionStoreData(
                 new ImsiProtectionExemptionDataSource()));
 
-        updateImsiEncryptionInfo(context);
+        mSubscriptionManager.addOnSubscriptionsChangedListener(new HandlerExecutor(mHandler),
+                new SubscriptionChangeListener());
+        onCarrierConfigChanged(context);
 
         // Monitor for carrier config changes.
         IntentFilter filter = new IntentFilter();
@@ -312,7 +507,7 @@
             public void onReceive(Context context, Intent intent) {
                 if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED
                         .equals(intent.getAction())) {
-                    updateImsiEncryptionInfo(context);
+                    mHandler.post(() -> onCarrierConfigChanged(context));
                 }
             }
         }, filter);
@@ -321,7 +516,7 @@
                 new ContentObserver(handler) {
                     @Override
                     public void onChange(boolean selfChange) {
-                        updateImsiEncryptionInfo(context);
+                        mHandler.post(() -> onCarrierConfigChanged(context));
                     }
                 });
     }
@@ -333,62 +528,158 @@
         mVerboseLogEnabled = verbose > 0;
     }
 
+    void onUnlockedUserSwitching(int currentUserId) {
+        // Retrieve list of broadcast receivers for this broadcast & send them directed
+        // broadcasts to wake them up (if they're in background).
+        final List<PackageInfo> provisioningPackageInfos =
+                mContext.getPackageManager().getPackagesHoldingPermissions(
+                        new String[] {android.Manifest.permission.NETWORK_CARRIER_PROVISIONING},
+                        PackageManager.MATCH_UNINSTALLED_PACKAGES);
+
+        vlogd("switched to current unlocked user. notify apps with"
+                + " NETWORK_CARRIER_PROVISIONING permission for user - " + currentUserId);
+
+        for (PackageInfo packageInfo : provisioningPackageInfos) {
+            Intent intentToSend = new Intent(WifiManager.ACTION_REFRESH_USER_PROVISIONING);
+            intentToSend.setPackage(packageInfo.packageName);
+            mContext.sendBroadcastAsUser(intentToSend, UserHandle.CURRENT,
+                    android.Manifest.permission.NETWORK_CARRIER_PROVISIONING);
+        }
+    }
+
+    private PersistableBundle getCarrierConfigForSubId(int subId) {
+        if (mCachedCarrierConfigPerSubId.contains(subId)) {
+            return mCachedCarrierConfigPerSubId.get(subId);
+        }
+        TelephonyManager specifiedTm = mTelephonyManager.createForSubscriptionId(subId);
+        if (specifiedTm.getSimApplicationState() != TelephonyManager.SIM_STATE_LOADED) {
+            return null;
+        }
+        if (mCarrierConfigManager == null) {
+            mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
+        }
+        if (mCarrierConfigManager == null) {
+            return null;
+        }
+        PersistableBundle carrierConfig = mCarrierConfigManager.getConfigForSubId(subId);
+        if (!CarrierConfigManager.isConfigForIdentifiedCarrier(carrierConfig)) {
+            return null;
+        }
+        mCachedCarrierConfigPerSubId.put(subId, carrierConfig);
+        return carrierConfig;
+    }
+
     /**
-     * Updates the IMSI encryption information.
+     * Checks whether MAC randomization should be disabled for the provided WifiConfiguration,
+     * based on an exception list in the CarrierConfigManager per subId.
+     * @param ssid the SSID of a WifiConfiguration, surrounded by double quotes.
+     * @param carrierId the ID associated with the network operator for this network suggestion.
+     * @param subId the best match subscriptionId for this network suggestion.
      */
-    private void updateImsiEncryptionInfo(Context context) {
-        CarrierConfigManager carrierConfigManager =
-                (CarrierConfigManager) context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        if (carrierConfigManager == null) {
-            return;
+    public boolean shouldDisableMacRandomization(String ssid, int carrierId, int subId) {
+        if (!SdkLevel.isAtLeastS()) {
+            return false;
+        }
+        if (carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) {
+            // only carrier networks are allowed to disable MAC randomization through this path.
+            return false;
+        }
+        PersistableBundle carrierConfig = getCarrierConfigForSubId(subId);
+        if (carrierConfig == null) {
+            return false;
+        }
+        String sanitizedSsid = WifiInfo.sanitizeSsid(ssid);
+        String[] macRandDisabledSsids = carrierConfig.getStringArray(CarrierConfigManager.Wifi
+                .KEY_SUGGESTION_SSID_LIST_WITH_MAC_RANDOMIZATION_DISABLED);
+        if (macRandDisabledSsids == null) {
+            return false;
+        }
+        for (String curSsid : macRandDisabledSsids) {
+            if (sanitizedSsid.equals(curSsid)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Checks whether merged carrier WiFi networks are permitted for the carrier based on a flag
+     * in the CarrierConfigManager.
+     *
+     * @param subId the best match subscriptionId for this network suggestion.
+     */
+    public boolean areMergedCarrierWifiNetworksAllowed(int subId) {
+        if (!SdkLevel.isAtLeastS()) {
+            return false;
+        }
+        PersistableBundle carrierConfig = getCarrierConfigForSubId(subId);
+        if (carrierConfig == null) {
+            return false;
         }
 
-        mImsiEncryptionRequired.clear();
+        return carrierConfig.getBoolean(
+                CarrierConfigManager.KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL, false);
+    }
+
+    /**
+     * Updates the IMSI encryption information and clears cached CarrierConfig data.
+     */
+    private void onCarrierConfigChanged(Context context) {
+        SparseArray<PersistableBundle> cachedCarrierConfigPerSubIdOld =
+                mCachedCarrierConfigPerSubId.clone();
+        mCachedCarrierConfigPerSubId.clear();
         mImsiEncryptionInfoAvailable.clear();
-        mEapMethodPrefixEnable.clear();
-        List<SubscriptionInfo> activeSubInfos =
-                mSubscriptionManager.getActiveSubscriptionInfoList();
-        if (activeSubInfos == null) {
+        if (mActiveSubInfos == null || mActiveSubInfos.isEmpty()) {
             return;
         }
-        for (SubscriptionInfo subInfo : activeSubInfos) {
+        for (SubscriptionInfo subInfo : mActiveSubInfos) {
             int subId = subInfo.getSubscriptionId();
-            PersistableBundle bundle = carrierConfigManager.getConfigForSubId(subId);
-            if (bundle != null) {
-                if ((bundle.getInt(CarrierConfigManager.IMSI_KEY_AVAILABILITY_INT)
-                                    & TelephonyManager.KEY_TYPE_WLAN) != 0) {
-                    vlogd("IMSI encryption is required for " + subId);
-                    mImsiEncryptionRequired.put(subId, true);
-                }
-                if (bundle.getBoolean(CarrierConfigManager.ENABLE_EAP_METHOD_PREFIX_BOOL)) {
-                    vlogd("EAP Prefix is required for " + subId);
-                    mEapMethodPrefixEnable.put(subId, true);
-                }
-            } else {
+            PersistableBundle bundle = getCarrierConfigForSubId(subId);
+            if (bundle == null) {
                 Log.e(TAG, "Carrier config is missing for: " + subId);
+            } else {
+                try {
+                    if (requiresImsiEncryption(subId)
+                            && mTelephonyManager.createForSubscriptionId(subId)
+                            .getCarrierInfoForImsiEncryption(TelephonyManager.KEY_TYPE_WLAN)
+                            != null) {
+                        vlogd("IMSI encryption info is available for " + subId);
+                        mImsiEncryptionInfoAvailable.put(subId, true);
+                    }
+                } catch (IllegalArgumentException e) {
+                    vlogd("IMSI encryption info is not available.");
+                }
+            }
+            PersistableBundle bundleOld = cachedCarrierConfigPerSubIdOld.get(subId);
+            if (bundleOld != null && bundleOld.getBoolean(CarrierConfigManager
+                    .KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL)
+                    && !areMergedCarrierWifiNetworksAllowed(subId)) {
+                vlogd("Allow carrier merged change from true to false");
+                for (OnCarrierOffloadDisabledListener listener :
+                        mOnCarrierOffloadDisabledListeners) {
+                    listener.onCarrierOffloadDisabled(subId, true);
+                }
             }
 
-            try {
-                if (mImsiEncryptionRequired.get(subId)
-                        && mTelephonyManager.createForSubscriptionId(subId)
-                        .getCarrierInfoForImsiEncryption(TelephonyManager.KEY_TYPE_WLAN) != null) {
-                    vlogd("IMSI encryption info is available for " + subId);
-                    mImsiEncryptionInfoAvailable.put(subId, true);
-                }
-            } catch (IllegalArgumentException e) {
-                vlogd("IMSI encryption info is not available.");
-            }
         }
     }
 
     /**
      * Check if the IMSI encryption is required for the SIM card.
+     * Note: should only be called when {@link #isSimReady(int)} is true, or the result may not be
+     * correct.
      *
      * @param subId The subscription ID of SIM card.
      * @return true if the IMSI encryption is required, otherwise false.
      */
     public boolean requiresImsiEncryption(int subId) {
-        return mImsiEncryptionRequired.get(subId);
+        PersistableBundle bundle = getCarrierConfigForSubId(subId);
+        if (bundle == null) {
+            Log.wtf(TAG, "requiresImsiEncryption is called when SIM is not ready!");
+            return false;
+        }
+        return (bundle.getInt(CarrierConfigManager.IMSI_KEY_AVAILABILITY_INT)
+                & TelephonyManager.KEY_TYPE_WLAN) != 0;
     }
 
     /**
@@ -408,6 +699,9 @@
      * @return the best match SubscriptionId
      */
     public int getBestMatchSubscriptionId(@NonNull WifiConfiguration config) {
+        if (config.subscriptionId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            return config.subscriptionId;
+        }
         if (config.isPasspoint()) {
             return getMatchingSubId(config.carrierId);
         } else {
@@ -422,14 +716,13 @@
      * @return the matched SubscriptionId
      */
     public int getMatchingSubId(int carrierId) {
-        List<SubscriptionInfo> subInfoList = mSubscriptionManager.getActiveSubscriptionInfoList();
-        if (subInfoList == null || subInfoList.isEmpty()) {
+        if (mActiveSubInfos == null || mActiveSubInfos.isEmpty()) {
             return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
         }
 
         int dataSubId = SubscriptionManager.getDefaultDataSubscriptionId();
         int matchSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-        for (SubscriptionInfo subInfo : subInfoList) {
+        for (SubscriptionInfo subInfo : mActiveSubInfos) {
             if (subInfo.getCarrierId() == carrierId) {
                 matchSubId = subInfo.getSubscriptionId();
                 if (matchSubId == dataSubId) {
@@ -453,7 +746,7 @@
             return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
         }
         int dataSubId = SubscriptionManager.getDefaultDataSubscriptionId();
-        if (isSimPresent(dataSubId)) {
+        if (isSimReady(dataSubId)) {
             vlogd("carrierId is not assigned, using the default data sub.");
             return dataSubId;
         }
@@ -462,30 +755,22 @@
     }
 
     /**
-     * Check if the specified SIM card is in the device.
+     * Check if the specified SIM card is ready for Wi-Fi connection on the device.
      *
      * @param subId subscription ID of SIM card in the device.
-     * @return true if the subId is active, otherwise false.
+     * @return true if the SIM is active and all info are available, otherwise false.
      */
-    public boolean isSimPresent(int subId) {
+    public boolean isSimReady(int subId) {
         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
             return false;
         }
-        List<SubscriptionInfo> subInfoList = mSubscriptionManager.getActiveSubscriptionInfoList();
-        if (subInfoList == null || subInfoList.isEmpty()) {
+        if (mActiveSubInfos == null || mActiveSubInfos.isEmpty()) {
             return false;
         }
-        return subInfoList.stream()
-                .anyMatch(info -> info.getSubscriptionId() == subId
-                        && isSimStateReady(info));
-    }
-
-    /**
-     * Check if SIM card for SubscriptionInfo is ready.
-     */
-    private boolean isSimStateReady(SubscriptionInfo info) {
-        int simSlotIndex = info.getSimSlotIndex();
-        return mTelephonyManager.getSimState(simSlotIndex) == TelephonyManager.SIM_STATE_READY;
+        if (getSimInfo(subId) == null || getCarrierConfigForSubId(subId) == null) {
+            return false;
+        }
+        return mActiveSubInfos.stream().anyMatch(info -> info.getSubscriptionId() == subId);
     }
 
     /**
@@ -501,20 +786,22 @@
             return null;
         }
 
-        TelephonyManager specifiedTm = mTelephonyManager.createForSubscriptionId(subId);
-        String imsi = specifiedTm.getSubscriberId();
-        String mccMnc = "";
-
-        if (specifiedTm.getSimState() == TelephonyManager.SIM_STATE_READY) {
-            mccMnc = specifiedTm.getSimOperator();
+        SimInfo simInfo = getSimInfo(subId);
+        if (simInfo == null) {
+            return null;
         }
 
-        String identity = buildIdentity(getSimMethodForConfig(config), imsi, mccMnc, false);
+        String identity = buildIdentity(getSimMethodForConfig(config), simInfo.imsi,
+                simInfo.mccMnc, false);
         if (identity == null) {
             Log.e(TAG, "Failed to build the identity");
             return null;
         }
 
+        if (!requiresImsiEncryption(subId)) {
+            return Pair.create(identity, "");
+        }
+        TelephonyManager specifiedTm = mTelephonyManager.createForSubscriptionId(subId);
         ImsiEncryptionInfo imsiEncryptionInfo;
         try {
             imsiEncryptionInfo = specifiedTm.getCarrierInfoForImsiEncryption(
@@ -548,26 +835,22 @@
      */
     public String getAnonymousIdentityWith3GppRealm(@NonNull WifiConfiguration config) {
         int subId = getBestMatchSubscriptionId(config);
-        TelephonyManager specifiedTm = mTelephonyManager.createForSubscriptionId(subId);
-        if (specifiedTm.getSimState() != TelephonyManager.SIM_STATE_READY) {
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
             return null;
         }
-        String mccMnc = specifiedTm.getSimOperator();
-        if (mccMnc == null || mccMnc.isEmpty()) {
+        SimInfo simInfo = getSimInfo(subId);
+        if (simInfo == null) {
+            return null;
+        }
+        Pair<String, String> mccMncPair = extractMccMnc(simInfo.imsi, simInfo.mccMnc);
+        if (mccMncPair == null) {
             return null;
         }
 
-        // Extract mcc & mnc from mccMnc
-        String mcc = mccMnc.substring(0, 3);
-        String mnc = mccMnc.substring(3);
-
-        if (mnc.length() == 2) {
-            mnc = "0" + mnc;
-        }
-
-        String realm = String.format(THREE_GPP_NAI_REALM_FORMAT, mnc, mcc);
+        String realm = String.format(THREE_GPP_NAI_REALM_FORMAT, mccMncPair.second,
+                mccMncPair.first);
         StringBuilder sb = new StringBuilder();
-        if (mEapMethodPrefixEnable.get(subId)) {
+        if (isEapMethodPrefixEnabled(subId)) {
             // Set the EAP method as a prefix
             String eapMethod = EAP_METHOD_PREFIX.get(config.enterpriseConfig.getEapMethod());
             if (!TextUtils.isEmpty(eapMethod)) {
@@ -669,7 +952,7 @@
      * @param isEncrypted Whether the imsi is encrypted or not.
      * @return the eap identity, built using either the encrypted or un-encrypted IMSI.
      */
-    private static String buildIdentity(int eapMethod, String imsi, String mccMnc,
+    private String buildIdentity(int eapMethod, String imsi, String mccMnc,
                                         boolean isEncrypted) {
         if (imsi == null || imsi.isEmpty()) {
             Log.e(TAG, "No IMSI or IMSI is null");
@@ -681,22 +964,10 @@
             return null;
         }
 
-        /* extract mcc & mnc from mccMnc */
-        String mcc;
-        String mnc;
-        if (mccMnc != null && !mccMnc.isEmpty()) {
-            mcc = mccMnc.substring(0, 3);
-            mnc = mccMnc.substring(3);
-            if (mnc.length() == 2) {
-                mnc = "0" + mnc;
-            }
-        } else {
-            // extract mcc & mnc from IMSI, assume mnc size is 3
-            mcc = imsi.substring(0, 3);
-            mnc = imsi.substring(3, 6);
-        }
+        Pair<String, String> mccMncPair = extractMccMnc(imsi, mccMnc);
 
-        String naiRealm = String.format(THREE_GPP_NAI_REALM_FORMAT, mnc, mcc);
+        String naiRealm = String.format(THREE_GPP_NAI_REALM_FORMAT, mccMncPair.second,
+                mccMncPair.first);
         return prefix + imsi + "@" + naiRealm;
     }
 
@@ -1095,30 +1366,6 @@
     }
 
     /**
-     * Get the carrier type of current SIM.
-     *
-     * @param subId the subscription ID of SIM card.
-     * @return carrier type of current active sim, {{@link #CARRIER_INVALID_TYPE}} if sim is not
-     * ready.
-     */
-    private int getCarrierType(int subId) {
-        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
-            return CARRIER_INVALID_TYPE;
-        }
-        TelephonyManager specifiedTm = mTelephonyManager.createForSubscriptionId(subId);
-
-        if (specifiedTm.getSimState() != TelephonyManager.SIM_STATE_READY) {
-            return CARRIER_INVALID_TYPE;
-        }
-
-        // If two APIs return the same carrier ID, then is considered as MNO, otherwise MVNO
-        if (specifiedTm.getCarrierIdFromSimMccMnc() == specifiedTm.getSimCarrierId()) {
-            return CARRIER_MNO_TYPE;
-        }
-        return CARRIER_MVNO_TYPE;
-    }
-
-    /**
      * Decorates a pseudonym with the NAI realm, in case it wasn't provided by the server
      *
      * @param config The instance of WifiConfiguration
@@ -1137,24 +1384,17 @@
         }
         int subId = getBestMatchSubscriptionId(config);
 
-        TelephonyManager specifiedTm = mTelephonyManager.createForSubscriptionId(subId);
-        if (specifiedTm.getSimState() != TelephonyManager.SIM_STATE_READY) {
+        SimInfo simInfo = getSimInfo(subId);
+        if (simInfo == null) {
             return null;
         }
-        String mccMnc = specifiedTm.getSimOperator();
-        if (mccMnc == null || mccMnc.isEmpty()) {
+        Pair<String, String> mccMncPair = extractMccMnc(simInfo.imsi, simInfo.mccMnc);
+        if (mccMncPair == null) {
             return null;
         }
 
-        // Extract mcc & mnc from mccMnc
-        String mcc = mccMnc.substring(0, 3);
-        String mnc = mccMnc.substring(3);
-
-        if (mnc.length() == 2) {
-            mnc = "0" + mnc;
-        }
-
-        String realm = String.format(THREE_GPP_NAI_REALM_FORMAT, mnc, mcc);
+        String realm = String.format(THREE_GPP_NAI_REALM_FORMAT, mccMncPair.second,
+                mccMncPair.first);
         return String.format("%s@%s", pseudonym, realm);
     }
 
@@ -1195,15 +1435,16 @@
             vlogd("IMSI is not available or not full");
             return false;
         }
-        List<SubscriptionInfo> infos = mSubscriptionManager.getActiveSubscriptionInfoList();
-        if (infos == null) {
+        if (mActiveSubInfos == null || mActiveSubInfos.isEmpty()) {
             return false;
         }
         // Find the active matching SIM card with the full IMSI from passpoint profile.
-        for (SubscriptionInfo subInfo : infos) {
-            String imsi = mTelephonyManager
-                    .createForSubscriptionId(subInfo.getSubscriptionId()).getSubscriberId();
-            if (imsiParameter.matchesImsi(imsi)) {
+        for (SubscriptionInfo subInfo : mActiveSubInfos) {
+            SimInfo simInfo = getSimInfo(subInfo.getSubscriptionId());
+            if (simInfo == null) {
+                continue;
+            }
+            if (imsiParameter.matchesImsi(simInfo.imsi)) {
                 config.setCarrierId(subInfo.getCarrierId());
                 return true;
             }
@@ -1213,20 +1454,25 @@
     }
 
     /**
-     * Get the IMSI and carrier ID of the SIM card which is matched with the given carrier ID.
+     * Get the IMSI and carrier ID of the SIM card which is matched with the given subscription ID.
      *
-     * @param carrierId The carrier ID see {@link TelephonyManager.getSimCarrierId}
+     * @param subId The subscription ID see {@link SubscriptionInfo#getSubscriptionId()}
      * @return null if there is no matching SIM card, otherwise the IMSI and carrier ID of the
      * matching SIM card
      */
-    public @Nullable String getMatchingImsi(int carrierId) {
-        int subId = getMatchingSubId(carrierId);
+    public @Nullable String getMatchingImsiBySubId(int subId) {
+        if (!isSimReady(subId)) {
+            return null;
+        }
         if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
             if (requiresImsiEncryption(subId) && !isImsiEncryptionInfoAvailable(subId)) {
                 vlogd("required IMSI encryption information is not available.");
                 return null;
             }
-            return mTelephonyManager.createForSubscriptionId(subId).getSubscriberId();
+            SimInfo simInfo = getSimInfo(subId);
+            if (simInfo != null) {
+                return simInfo.imsi;
+            }
         }
         vlogd("no active SIM card to match the carrier ID.");
         return null;
@@ -1246,8 +1492,7 @@
         if (imsiParameter == null) {
             return null;
         }
-        List<SubscriptionInfo> infos = mSubscriptionManager.getActiveSubscriptionInfoList();
-        if (infos == null) {
+        if (mActiveSubInfos == null) {
             return null;
         }
         int dataSubId = SubscriptionManager.getDefaultDataSubscriptionId();
@@ -1260,28 +1505,29 @@
 
         // Find the active matched SIM card with the priority order of Data MNO SIM,
         // Nondata MNO SIM, Data MVNO SIM, Nondata MVNO SIM.
-        for (SubscriptionInfo subInfo : infos) {
+        for (SubscriptionInfo subInfo : mActiveSubInfos) {
             int subId = subInfo.getSubscriptionId();
             if (requiresImsiEncryption(subId) && !isImsiEncryptionInfoAvailable(subId)) {
                 vlogd("required IMSI encryption information is not available.");
                 continue;
             }
-            TelephonyManager specifiedTm = mTelephonyManager.createForSubscriptionId(subId);
-            String operatorNumeric = specifiedTm.getSimOperator();
-            if (operatorNumeric != null && imsiParameter.matchesMccMnc(operatorNumeric)) {
-                String curImsi = specifiedTm.getSubscriberId();
-                if (TextUtils.isEmpty(curImsi)) {
+            SimInfo simInfo = getSimInfo(subId);
+            if (simInfo == null) {
+                continue;
+            }
+            if (simInfo.mccMnc != null && imsiParameter.matchesMccMnc(simInfo.mccMnc)) {
+                if (TextUtils.isEmpty(simInfo.imsi)) {
                     continue;
                 }
-                matchedPair = new Pair<>(curImsi, subInfo.getCarrierId());
+                matchedPair = new Pair<>(simInfo.imsi, subInfo.getCarrierId());
                 if (subId == dataSubId) {
                     matchedDataPair = matchedPair;
-                    if (getCarrierType(subId) == CARRIER_MNO_TYPE) {
+                    if (simInfo.getCarrierType() == CARRIER_MNO_TYPE) {
                         vlogd("MNO data is matched via IMSI.");
                         return matchedDataPair;
                     }
                 }
-                if (getCarrierType(subId) == CARRIER_MNO_TYPE) {
+                if (simInfo.getCarrierType() == CARRIER_MNO_TYPE) {
                     matchedMnoPair = matchedPair;
                 }
             }
@@ -1311,8 +1557,12 @@
     /** Dump state. */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println(TAG + ": ");
-        pw.println("mImsiEncryptionRequired=" + mImsiEncryptionRequired);
         pw.println("mImsiEncryptionInfoAvailable=" + mImsiEncryptionInfoAvailable);
+        pw.println("mImsiPrivacyProtectionExemptionMap=" + mImsiPrivacyProtectionExemptionMap);
+        pw.println("mMergedCarrierNetworkOffloadMap=" + mMergedCarrierNetworkOffloadMap);
+        pw.println("mSubIdToSimInfoSparseArray=" + mSubIdToSimInfoSparseArray);
+        pw.println("mActiveSubInfos=" + mActiveSubInfos);
+        pw.println("mCachedCarrierConfigPerSubId=" + mCachedCarrierConfigPerSubId);
     }
 
     /**
@@ -1324,12 +1574,11 @@
      *         by any available carrier, will return UNKNOWN_CARRIER_ID.
      */
     public int getCarrierIdForPackageWithCarrierPrivileges(String packageName) {
-        List<SubscriptionInfo> subInfoList = mSubscriptionManager.getActiveSubscriptionInfoList();
-        if (subInfoList == null || subInfoList.isEmpty()) {
+        if (mActiveSubInfos == null || mActiveSubInfos.isEmpty()) {
             if (mVerboseLogEnabled) Log.v(TAG, "No subs for carrier privilege check");
             return TelephonyManager.UNKNOWN_CARRIER_ID;
         }
-        for (SubscriptionInfo info : subInfoList) {
+        for (SubscriptionInfo info : mActiveSubInfos) {
             TelephonyManager specifiedTm =
                     mTelephonyManager.createForSubscriptionId(info.getSubscriptionId());
             if (specifiedTm.checkCarrierPrivilegesForPackage(packageName)
@@ -1345,7 +1594,7 @@
      * @param subId Subscription id
      * @return String of carrier name.
      */
-    public String getCarrierNameforSubId(int subId) {
+    public String getCarrierNameForSubId(int subId) {
         TelephonyManager specifiedTm =
                 mTelephonyManager.createForSubscriptionId(subId);
 
@@ -1364,7 +1613,7 @@
         if (config.carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) {
             return false;
         }
-        int subId = getMatchingSubId(config.carrierId);
+        int subId = getBestMatchSubscriptionId(config);
         return subId != SubscriptionManager.getDefaultDataSubscriptionId();
     }
 
@@ -1373,8 +1622,11 @@
      */
     public int getDefaultDataSimCarrierId() {
         int subId = SubscriptionManager.getDefaultDataSubscriptionId();
-        TelephonyManager specifiedTm = mTelephonyManager.createForSubscriptionId(subId);
-        return specifiedTm.getSimCarrierId();
+        SimInfo simInfo = getSimInfo(subId);
+        if (simInfo == null) {
+            return TelephonyManager.UNKNOWN_CARRIER_ID;
+        }
+        return simInfo.simCarrierId;
     }
 
     /**
@@ -1386,6 +1638,22 @@
     }
 
     /**
+     * Add a listener to monitor carrier offload disabled.
+     */
+    public void addOnCarrierOffloadDisabledListener(
+            OnCarrierOffloadDisabledListener listener) {
+        mOnCarrierOffloadDisabledListeners.add(listener);
+    }
+
+    /**
+     * remove a {@link OnCarrierOffloadDisabledListener}.
+     */
+    public void removeOnCarrierOffloadDisabledListener(
+            OnCarrierOffloadDisabledListener listener) {
+        mOnCarrierOffloadDisabledListeners.remove(listener);
+    }
+
+    /**
      * Clear the Imsi Privacy Exemption user approval info the target carrier.
      */
     public void clearImsiPrivacyExemptionForCarrier(int carrierId) {
@@ -1419,7 +1687,11 @@
     }
 
     private void sendImsiPrivacyNotification(int carrierId) {
-        String carrierName = getCarrierNameforSubId(getMatchingSubId(carrierId));
+        String carrierName = getCarrierNameForSubId(getMatchingSubId(carrierId));
+        if (carrierName == null) {
+            // If carrier name could not be retrieved, do not send notification.
+            return;
+        }
         Notification.Action userAllowAppNotificationAction =
                 new Notification.Action.Builder(null,
                         mResources.getText(R.string
@@ -1448,6 +1720,9 @@
                 .setStyle(new Notification.BigTextStyle()
                         .bigText(mResources.getString(
                                 R.string.wifi_suggestion_imsi_privacy_content)))
+                .setContentIntent(getPrivateBroadcast(NOTIFICATION_USER_CLICKED_INTENT_ACTION,
+                        Pair.create(EXTRA_CARRIER_NAME, carrierName),
+                        Pair.create(EXTRA_CARRIER_ID, carrierId)))
                 .setDeleteIntent(getPrivateBroadcast(NOTIFICATION_USER_DISMISSED_INTENT_ACTION,
                         Pair.create(EXTRA_CARRIER_NAME, carrierName),
                         Pair.create(EXTRA_CARRIER_ID, carrierId)))
@@ -1457,12 +1732,13 @@
                         mContext.getTheme()))
                 .addAction(userDisallowAppNotificationAction)
                 .addAction(userAllowAppNotificationAction)
+                .setTimeoutAfter(NOTIFICATION_EXPIRY_MILLS)
                 .build();
 
         // Post the notification.
-        mNotificationManager.notify(
-                SystemMessageProto.SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE, notification);
-        mUserApprovalUiActive = true;
+        mNotificationManager.notify(SystemMessage.NOTE_CARRIER_SUGGESTION_AVAILABLE, notification);
+        mNotificationUpdateTime = mClock.getElapsedSinceBootMillis()
+                + NOTIFICATION_UPDATE_DELAY_MILLS;
         mIsLastUserApprovalUiDialog = false;
     }
 
@@ -1495,7 +1771,6 @@
         dialog.getWindow().addSystemFlags(
                 WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
         dialog.show();
-        mUserApprovalUiActive = true;
         mIsLastUserApprovalUiDialog = true;
     }
 
@@ -1508,32 +1783,116 @@
         if (!mUserDataLoaded) {
             return;
         }
+        PersistableBundle bundle = getCarrierConfigForSubId(subId);
+        if (bundle == null) {
+            return;
+        }
         if (requiresImsiEncryption(subId)) {
             return;
         }
         if (mImsiPrivacyProtectionExemptionMap.containsKey(carrierId)) {
             return;
         }
-        if (mUserApprovalUiActive) {
-            return;
+        if (mNotificationUpdateTime > mClock.getElapsedSinceBootMillis()) {
+            return; // Active notification is still available, do not update.
         }
         Log.i(TAG, "Sending IMSI protection notification for " + carrierId);
         sendImsiPrivacyNotification(carrierId);
     }
 
+    /**
+     * Check if the target subscription has a matched carrier Id.
+     * @param subId Subscription Id for which this carrier network is valid.
+     * @param carrierId Carrier Id for this carrier network.
+     * @return true if matches, false otherwise.
+     */
+    public boolean isSubIdMatchingCarrierId(int subId, int carrierId) {
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            // If Subscription Id is not set, consider it matches. Best matching one will be used to
+            // connect.
+            return true;
+        }
+        if (mActiveSubInfos == null || mActiveSubInfos.isEmpty()) {
+            return false;
+        }
+        for (SubscriptionInfo info : mActiveSubInfos) {
+            if (info.getSubscriptionId() == subId) {
+                return info.getCarrierId() == carrierId;
+            }
+        }
+        return false;
+    }
+
     private PendingIntent getPrivateBroadcast(@NonNull String action,
             @NonNull Pair<String, String> extra1, @NonNull Pair<String, Integer> extra2) {
         Intent intent = new Intent(action)
-                .setPackage(mWifiInjector.getWifiStackPackageName())
+                .setPackage(mContext.getServiceWifiPackageName())
                 .putExtra(extra1.first, extra1.second)
                 .putExtra(extra2.first, extra2.second);
         return mFrameworkFacade.getBroadcast(mContext, 0, intent,
-                PendingIntent.FLAG_UPDATE_CURRENT);
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+    }
+
+    /**
+     * Set carrier network offload enabled/disabled.
+     * @param subscriptionId Subscription Id to change carrier offload
+     * @param merged True for carrier merged network, false otherwise.
+     * @param enabled True for enabled carrier offload, false otherwise.
+     */
+    public void setCarrierNetworkOffloadEnabled(int subscriptionId, boolean merged,
+            boolean enabled) {
+        if (merged) {
+            mMergedCarrierNetworkOffloadMap.put(subscriptionId, enabled);
+        } else {
+            mUnmergedCarrierNetworkOffloadMap.put(subscriptionId, enabled);
+        }
+        if (!enabled) {
+            for (OnCarrierOffloadDisabledListener listener : mOnCarrierOffloadDisabledListeners) {
+                listener.onCarrierOffloadDisabled(subscriptionId, merged);
+            }
+        }
+        saveToStore();
+    }
+
+    /**
+     * Check if carrier network offload is enabled/disabled.
+     * @param subId Subscription Id to check offload state.
+     * @param merged True for carrier merged network, false otherwise.
+     * @return True to indicate carrier offload is enabled, false otherwise.
+     */
+    public boolean isCarrierNetworkOffloadEnabled(int subId, boolean merged) {
+        if (merged) {
+            return mMergedCarrierNetworkOffloadMap.getOrDefault(subId, true)
+                    && isMobileDataEnabled(subId);
+        } else {
+            return mUnmergedCarrierNetworkOffloadMap.getOrDefault(subId, true);
+        }
+    }
+
+    private boolean isMobileDataEnabled(int subId) {
+        if (!SdkLevel.isAtLeastS()) {
+            // As the carrier offload enabled API and the merged carrier API (which is controlled by
+            // this toggle) were added in S. Don't check the mobile data toggle when Sdk is less
+            // than S.
+            return true;
+        }
+        if (mUserDataEnabled.indexOfKey(subId) >= 0) {
+            return mUserDataEnabled.get(subId);
+        }
+        TelephonyManager specifiedTm = mTelephonyManager.createForSubscriptionId(subId);
+        boolean enabled = specifiedTm.isDataEnabled();
+        mUserDataEnabled.put(subId, enabled);
+        UserDataEnabledChangedListener listener = new UserDataEnabledChangedListener(subId);
+        specifiedTm.registerTelephonyCallback(new HandlerExecutor(mHandler), listener);
+        mUserDataEnabledListenerList.add(listener);
+
+        return enabled;
     }
 
     private void saveToStore() {
         // Set the flag to let WifiConfigStore that we have new data to write.
-        mHasNewDataToSerialize = true;
+        mHasNewUserDataToSerialize = true;
+        mHasNewSharedDataToSerialize = true;
         if (!mWifiInjector.getWifiConfigManager().saveToStore(true)) {
             Log.w(TAG, "Failed to save to store");
         }
@@ -1544,6 +1903,76 @@
      */
     public void clear() {
         mImsiPrivacyProtectionExemptionMap.clear();
+        mMergedCarrierNetworkOffloadMap.clear();
+        mUnmergedCarrierNetworkOffloadMap.clear();
+        mUserDataEnabled.clear();
+        if (SdkLevel.isAtLeastS()) {
+            for (UserDataEnabledChangedListener listener : mUserDataEnabledListenerList) {
+                listener.unregisterListener();
+            }
+            mUserDataEnabledListenerList.clear();
+        }
+        resetNotification();
         saveToStore();
     }
+
+    public void resetNotification() {
+        mNotificationManager.cancel(SystemMessage.NOTE_CARRIER_SUGGESTION_AVAILABLE);
+        mNotificationUpdateTime = 0;
+    }
+
+    private SimInfo getSimInfo(int subId) {
+        SimInfo simInfo = mSubIdToSimInfoSparseArray.get(subId);
+        // If mccmnc is not available, try to get it again.
+        if (simInfo != null && simInfo.mccMnc != null && !simInfo.mccMnc.isEmpty()) {
+            return simInfo;
+        }
+        TelephonyManager specifiedTm = mTelephonyManager.createForSubscriptionId(subId);
+        if (specifiedTm.getSimApplicationState() != TelephonyManager.SIM_STATE_LOADED) {
+            return null;
+        }
+        String imsi = specifiedTm.getSubscriberId();
+        String mccMnc = specifiedTm.getSimOperator();
+        if (imsi == null || imsi.isEmpty()) {
+            Log.wtf(TAG, "Get invalid imsi when SIM is ready!");
+            return null;
+        }
+        int CarrierIdFromSimMccMnc = specifiedTm.getCarrierIdFromSimMccMnc();
+        int SimCarrierId = specifiedTm.getSimCarrierId();
+        simInfo = new SimInfo(imsi, mccMnc, CarrierIdFromSimMccMnc, SimCarrierId);
+        mSubIdToSimInfoSparseArray.put(subId, simInfo);
+        return simInfo;
+    }
+
+    /**
+     * Try to extract mcc and mnc from IMSI and MCCMNC
+     * @return a pair of string represent <mcc, mnc>. Pair.first is mcc, pair.second is mnc.
+     */
+    private Pair<String, String> extractMccMnc(String imsi, String mccMnc) {
+        String mcc;
+        String mnc;
+        if (mccMnc != null && !mccMnc.isEmpty() && mccMnc.length() >= 5) {
+            mcc = mccMnc.substring(0, 3);
+            mnc = mccMnc.substring(3);
+            if (mnc.length() == 2) {
+                mnc = "0" + mnc;
+            }
+        } else if (imsi != null && !imsi.isEmpty() && imsi.length() >= 6) {
+            // extract mcc & mnc from IMSI, assume mnc size is 3
+            mcc = imsi.substring(0, 3);
+            mnc = imsi.substring(3, 6);
+            vlogd("Guessing from IMSI, MCC=" + mcc + "; MNC=" + mnc);
+        } else {
+            return null;
+        }
+        return Pair.create(mcc, mnc);
+    }
+
+    private boolean isEapMethodPrefixEnabled(int subId) {
+        PersistableBundle bundle = getCarrierConfigForSubId(subId);
+        if (bundle == null) {
+            return false;
+        }
+        return bundle.getBoolean(CarrierConfigManager.ENABLE_EAP_METHOD_PREFIX_BOOL);
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiCarrierInfoStoreManagerData.java b/service/java/com/android/server/wifi/WifiCarrierInfoStoreManagerData.java
new file mode 100644
index 0000000..29e7a6a
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiCarrierInfoStoreManagerData.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.annotation.NonNull;
+import android.util.Log;
+
+import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
+import com.android.server.wifi.util.XmlUtil;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class performs serialization and parsing of XML data block that contain the map of carrier
+ * Wi-Fi offloading settings info.
+ */
+public class WifiCarrierInfoStoreManagerData implements WifiConfigStore.StoreData {
+    private static final String TAG = "WifiCarrierInfoStoreManagerData";
+    private static final String XML_TAG_SECTION_HEADER =
+            "WifiCarrierInfoStoreManagerDataStores";
+    private static final String XML_TAG_MERGED_CARRIER_NETWORK_OFFLOAD_MAP =
+            "MergedCarrierNetworkOffloadMap";
+    private static final String XML_TAG_UNMERGED_CARRIER_NETWORK_OFFLOAD_MAP =
+            "UnmergedCarrierNetworkOffloadMap";
+
+    /**
+     * Interface define the data source for the carrier IMSI protection exemption map store data.
+     */
+    public interface DataSource {
+
+        /**
+         * Retrieve the merged carrier network offload map from the data source to serialize to
+         * disk.
+         */
+        Map<Integer, Boolean> toSerializeMergedCarrierNetworkOffloadMap();
+
+        /**
+         * Retrieve the unmerged carrier network offload map from the data source to serialize to
+         * disk.
+         */
+        Map<Integer, Boolean> toSerializeUnmergedCarrierNetworkOffloadMap();
+
+        /**
+         * Should be called when serialize is completed.
+         */
+        void serializeComplete();
+
+
+        /**
+         * Set the merged carrier network offload map in the data source after deserialize them
+         * from disk.
+         */
+        void fromMergedCarrierNetworkOffloadMapDeserialized(
+                Map<Integer, Boolean> carrierOffloadMap);
+
+        /**
+         * Set the unmerged carrier network offload map in the data source after serializing them
+         * from disk.
+         */
+        void fromUnmergedCarrierNetworkOffloadMapDeserialized(
+                Map<Integer, Boolean> subscriptionOffloadMap);
+
+        /**
+         * Clear internal data structure in preparation for user switch or initial store read.
+         */
+        void reset();
+
+        /**
+         * Indicates whether there is new data to serialize.
+         */
+        boolean hasNewDataToSerialize();
+    }
+
+    private final DataSource mDataSource;
+
+    /**
+     * Set the data source fot store data.
+     */
+    public WifiCarrierInfoStoreManagerData(@NonNull DataSource dataSource) {
+        mDataSource = dataSource;
+    }
+
+    @Override
+    public void serializeData(XmlSerializer out, WifiConfigStoreEncryptionUtil encryptionUtil)
+            throws XmlPullParserException, IOException {
+        XmlUtil.writeNextValue(out, XML_TAG_MERGED_CARRIER_NETWORK_OFFLOAD_MAP,
+                integerMapToStringMap(mDataSource.toSerializeMergedCarrierNetworkOffloadMap()));
+        XmlUtil.writeNextValue(out, XML_TAG_UNMERGED_CARRIER_NETWORK_OFFLOAD_MAP,
+                integerMapToStringMap(mDataSource.toSerializeUnmergedCarrierNetworkOffloadMap()));
+        mDataSource.serializeComplete();
+    }
+
+    @Override
+    public void deserializeData(XmlPullParser in, int outerTagDepth, int version,
+            WifiConfigStoreEncryptionUtil encryptionUtil)
+            throws XmlPullParserException, IOException {
+        mDataSource.reset();
+        if (in != null) {
+            parseWifiCarrierInfoStoreMaps(in, outerTagDepth);
+        }
+    }
+
+    private void parseWifiCarrierInfoStoreMaps(XmlPullParser in,
+            int outerTagDepth)
+            throws XmlPullParserException, IOException {
+        while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
+            String[] valueName = new String[1];
+            Object value = XmlUtil.readCurrentValue(in, valueName);
+            if (valueName[0] == null) {
+                throw new XmlPullParserException("Missing value name");
+            }
+            switch (valueName[0]) {
+                case XML_TAG_MERGED_CARRIER_NETWORK_OFFLOAD_MAP:
+                    if (value instanceof Map) {
+                        mDataSource.fromMergedCarrierNetworkOffloadMapDeserialized(
+                                stringMapToIntegerMap((Map<String, Boolean>) value));
+                    }
+                    break;
+                case XML_TAG_UNMERGED_CARRIER_NETWORK_OFFLOAD_MAP:
+                    if (value instanceof Map) {
+                        mDataSource.fromUnmergedCarrierNetworkOffloadMapDeserialized(
+                                stringMapToIntegerMap((Map<String, Boolean>) value));
+                    }
+                    break;
+                default:
+                    Log.w(TAG, "Unknown tag under "
+                            + XML_TAG_SECTION_HEADER
+                            + ": " + valueName[0]);
+                    break;
+            }
+        }
+    }
+
+    private Map<String, Boolean> integerMapToStringMap(Map<Integer, Boolean> input) {
+        Map<String, Boolean> output = new HashMap<>();
+        if (input == null) {
+            return output;
+        }
+        for (Map.Entry<Integer, Boolean> entry : input.entrySet()) {
+            output.put(Integer.toString(entry.getKey()), entry.getValue());
+        }
+        return output;
+    }
+
+    private Map<Integer, Boolean> stringMapToIntegerMap(Map<String, Boolean> input) {
+        Map<Integer, Boolean> output = new HashMap<>();
+        if (input == null) {
+            return output;
+        }
+        for (Map.Entry<String, Boolean> entry : input.entrySet()) {
+            try {
+                output.put(Integer.valueOf(entry.getKey()), entry.getValue());
+            } catch (NumberFormatException e) {
+                Log.e(TAG, "Failed to Integer convert: " + entry.getKey());
+            }
+        }
+        return output;
+    }
+
+    @Override
+    public void resetData() {
+        mDataSource.reset();
+    }
+
+    @Override
+    public boolean hasNewDataToSerialize() {
+        return mDataSource.hasNewDataToSerialize();
+    }
+
+    @Override
+    public String getName() {
+        return XML_TAG_SECTION_HEADER;
+    }
+
+    @Override
+    public int getStoreFileId() {
+        // User general store.
+        return WifiConfigStore.STORE_FILE_SHARED_GENERAL;
+    }
+}
diff --git a/service/java/com/android/server/wifi/WifiChannelUtilization.java b/service/java/com/android/server/wifi/WifiChannelUtilization.java
index 5e8cde6..4ed0961 100644
--- a/service/java/com/android/server/wifi/WifiChannelUtilization.java
+++ b/service/java/com/android/server/wifi/WifiChannelUtilization.java
@@ -41,6 +41,7 @@
  * The cache is updated when a new stats arrives and it has been a long while since the last update.
  * To get more statistically sound channel utilization, for these devices which support
  * mobility state report, the cache update is stopped when the device stays in the stationary state.
+ * TODO(b/159052883): This may need to be reworked for STA + STA.
  */
 public class WifiChannelUtilization {
     private static final String TAG = "WifiChannelUtilization";
@@ -229,12 +230,14 @@
      * frequency or it reaches the end of cache.
      * @param freq Frequency of current channel
      * @param radioOnTimeMs The latest radioOnTime of current channel
-     * @return the found channelStat reference if search succeeds, or a dummy channelStats with time
-     * zero if channelStats is not found for the given frequency, or a dummy channelStats with the
-     * latest radioOnTimeMs if it reaches the end of cache.
+     * @return the found channelStat reference if search succeeds,
+     *             or a placeholder channelStats with time zero if channelStats is not found
+     *             for the given frequency,
+     *             or a placeholder channelStats with the latest radioOnTimeMs if it reaches
+     *             the end of cache.
      */
     private ChannelStats findChanStatsReference(int freq, int radioOnTimeMs) {
-        // A dummy channelStats with the latest radioOnTimeMs.
+        // A placeholder channelStats with the latest radioOnTimeMs.
         ChannelStats channelStatsCurrRadioOnTime = new ChannelStats();
         channelStatsCurrRadioOnTime.radioOnTimeMs = radioOnTimeMs;
         Iterator iterator = mChannelStatsMapCache.iterator();
@@ -243,7 +246,7 @@
             // If the freq can't be found in current channelStatsMap, stop search because it won't
             // appear in older ones either due to the fact that channelStatsMap are accumulated
             // in HW and thus a recent reading should have channels no less than old readings.
-            // Return a dummy channelStats with zero radioOnTimeMs
+            // Return a placeholder channelStats with zero radioOnTimeMs
             if (channelStatsMap == null || channelStatsMap.get(freq) == null) {
                 return new ChannelStats();
             }
diff --git a/service/java/com/android/server/wifi/WifiConfigManager.java b/service/java/com/android/server/wifi/WifiConfigManager.java
index f8c687e..1c87eb2 100644
--- a/service/java/com/android/server/wifi/WifiConfigManager.java
+++ b/service/java/com/android/server/wifi/WifiConfigManager.java
@@ -16,8 +16,6 @@
 
 package com.android.server.wifi;
 
-import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.DISABLE_REASON_INFOS;
-
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -31,19 +29,19 @@
 import android.net.ProxyInfo;
 import android.net.StaticIpConfiguration;
 import android.net.wifi.ScanResult;
+import android.net.wifi.SecurityParams;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
-import android.net.wifi.WifiConfiguration.NetworkSelectionStatus.DisableReasonInfo;
-import android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NetworkSelectionDisableReason;
 import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiScanner;
-import android.os.Handler;
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.LocalLog;
@@ -57,7 +55,6 @@
 import com.android.server.wifi.util.LruConnectionTracker;
 import com.android.server.wifi.util.MissingCounterTimerLockList;
 import com.android.server.wifi.util.WifiPermissionsUtil;
-import com.android.server.wifi.util.WifiPermissionsWrapper;
 import com.android.wifi.resources.R;
 
 import org.xmlpull.v1.XmlPullParserException;
@@ -66,7 +63,6 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.BitSet;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
@@ -75,6 +71,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * This class provides the APIs to manage configured Wi-Fi networks.
@@ -113,31 +110,49 @@
         /**
          * Invoked on network being added.
          */
-        void onNetworkAdded(@NonNull WifiConfiguration config);
+        default void onNetworkAdded(@NonNull WifiConfiguration config) { };
         /**
          * Invoked on network being enabled.
          */
-        void onNetworkEnabled(@NonNull WifiConfiguration config);
+        default void onNetworkEnabled(@NonNull WifiConfiguration config) { };
         /**
          * Invoked on network being permanently disabled.
          */
-        void onNetworkPermanentlyDisabled(@NonNull WifiConfiguration config, int disableReason);
+        default void onNetworkPermanentlyDisabled(@NonNull WifiConfiguration config,
+                int disableReason) { };
         /**
          * Invoked on network being removed.
          */
-        void onNetworkRemoved(@NonNull WifiConfiguration config);
+        default void onNetworkRemoved(@NonNull WifiConfiguration config) { };
         /**
          * Invoked on network being temporarily disabled.
          */
-        void onNetworkTemporarilyDisabled(@NonNull WifiConfiguration config, int disableReason);
+        default void onNetworkTemporarilyDisabled(@NonNull WifiConfiguration config,
+                int disableReason) { };
         /**
          * Invoked on network being updated.
          *
          * @param newConfig Updated WifiConfiguration object.
          * @param oldConfig Prev WifiConfiguration object.
          */
-        void onNetworkUpdated(
-                @NonNull WifiConfiguration newConfig, @NonNull WifiConfiguration oldConfig);
+        default void onNetworkUpdated(
+                @NonNull WifiConfiguration newConfig, @NonNull WifiConfiguration oldConfig) { };
+
+        /**
+         * Invoked when user connect choice is set.
+         * @param networks List of network profiles to set user connect choice.
+         * @param choiceKey Network key {@link WifiConfiguration#getProfileKey()}
+         *                  corresponding to the network which the user chose.
+         * @param rssi the signal strength of the user selected network
+         */
+        default void onConnectChoiceSet(@NonNull List<WifiConfiguration> networks,
+                String choiceKey, int rssi) { }
+
+        /**
+         * Invoked when user connect choice is removed.
+         * @param choiceKey The network profile key of the user connect choice that was removed.
+         */
+        default void onConnectChoiceRemoved(String choiceKey){ }
     }
     /**
      * Max size of scan details to cache in {@link #mScanDetailCaches}.
@@ -172,26 +187,22 @@
     private static final int SCAN_RESULT_MAXIMUM_AGE_MS = 40000;
 
     /**
-     * Maximum number of blocked BSSIDs per SSID used for calcualting the duration of temporarily
-     * disabling a network.
-     */
-    private static final int MAX_BLOCKED_BSSID_PER_NETWORK = 10;
-
-    /**
      * Enforce a minimum time to wait after the last disconnect to generate a new randomized MAC,
      * since IPv6 networks don't provide the DHCP lease duration.
      * 4 hours.
      */
     @VisibleForTesting
-    protected static final long AGGRESSIVE_MAC_WAIT_AFTER_DISCONNECT_MS = 4 * 60 * 60 * 1000;
+    protected static final long ENHANCED_MAC_WAIT_AFTER_DISCONNECT_MS = 4 * 60 * 60 * 1000;
     @VisibleForTesting
-    protected static final long AGGRESSIVE_MAC_REFRESH_MS_MIN = 30 * 60 * 1000; // 30 minutes
+    protected static final long ENHANCED_MAC_REFRESH_MS_MIN = 30 * 60 * 1000; // 30 minutes
     @VisibleForTesting
-    protected static final long AGGRESSIVE_MAC_REFRESH_MS_MAX = 24 * 60 * 60 * 1000; // 24 hours
+    protected static final long ENHANCED_MAC_REFRESH_MS_MAX = 24 * 60 * 60 * 1000; // 24 hours
 
     private static final MacAddress DEFAULT_MAC_ADDRESS =
             MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS);
 
+    private static final String VRRP_MAC_ADDRESS_PREFIX = "00:00:5E:00:01";
+
     /**
      * Expiration timeout for user disconnect network. (1 hour)
      */
@@ -203,6 +214,8 @@
     @VisibleForTesting
     protected static final String ENHANCED_MAC_RANDOMIZATION_FEATURE_FORCE_ENABLE_FLAG =
             "enhanced_mac_randomization_force_enabled";
+    private static final int NON_CARRIER_MERGED_NETWORKS_SCAN_CACHE_QUERY_DURATION_MS =
+            10 * 60 * 1000; // 10 minutes
 
     /**
      * General sorting algorithm of all networks for scanning purposes:
@@ -241,13 +254,15 @@
     private final WifiKeyStore mWifiKeyStore;
     private final WifiConfigStore mWifiConfigStore;
     private final WifiPermissionsUtil mWifiPermissionsUtil;
-    private final WifiPermissionsWrapper mWifiPermissionsWrapper;
-    private final WifiInjector mWifiInjector;
     private final MacAddressUtil mMacAddressUtil;
+    private final WifiMetrics mWifiMetrics;
+    private final WifiBlocklistMonitor mWifiBlocklistMonitor;
+    private final WifiLastResortWatchdog mWifiLastResortWatchdog;
     private final WifiCarrierInfoManager mWifiCarrierInfoManager;
     private final WifiScoreCard mWifiScoreCard;
     // Keep order of network connection.
     private final LruConnectionTracker mLruConnectionTracker;
+    private final BuildProperties mBuildProperties;
 
     /**
      * Local log used for debugging any WifiConfigManager issues.
@@ -269,6 +284,8 @@
      * Also when user manfully select to connect network will unblock that network.
      */
     private final MissingCounterTimerLockList<String> mUserTemporarilyDisabledList;
+    private final NonCarrierMergedNetworksStatusTracker mNonCarrierMergedNetworksStatusTracker;
+
 
     /**
      * Framework keeps a mapping from configKey to the randomized MAC address so that
@@ -280,7 +297,7 @@
     /**
      * Store the network update listeners.
      */
-    private final List<OnNetworkUpdateListener> mListeners;
+    private final Set<OnNetworkUpdateListener> mListeners;
 
     private final FrameworkFacade mFrameworkFacade;
     private final DeviceConfigFacade mDeviceConfigFacade;
@@ -334,18 +351,25 @@
      * Create new instance of WifiConfigManager.
      */
     WifiConfigManager(
-            Context context, Clock clock, UserManager userManager,
-            WifiCarrierInfoManager wifiCarrierInfoManager, WifiKeyStore wifiKeyStore,
+            Context context,
+            Clock clock,
+            UserManager userManager,
+            WifiCarrierInfoManager wifiCarrierInfoManager,
+            WifiKeyStore wifiKeyStore,
             WifiConfigStore wifiConfigStore,
             WifiPermissionsUtil wifiPermissionsUtil,
-            WifiPermissionsWrapper wifiPermissionsWrapper,
-            WifiInjector wifiInjector,
+            MacAddressUtil macAddressUtil,
+            WifiMetrics wifiMetrics,
+            WifiBlocklistMonitor wifiBlocklistMonitor,
+            WifiLastResortWatchdog wifiLastResortWatchdog,
             NetworkListSharedStoreData networkListSharedStoreData,
             NetworkListUserStoreData networkListUserStoreData,
             RandomizedMacStoreData randomizedMacStoreData,
-            FrameworkFacade frameworkFacade, Handler handler,
-            DeviceConfigFacade deviceConfigFacade, WifiScoreCard wifiScoreCard,
-            LruConnectionTracker lruConnectionTracker) {
+            FrameworkFacade frameworkFacade,
+            DeviceConfigFacade deviceConfigFacade,
+            WifiScoreCard wifiScoreCard,
+            LruConnectionTracker lruConnectionTracker,
+            BuildProperties buildProperties) {
         mContext = context;
         mClock = clock;
         mUserManager = userManager;
@@ -354,16 +378,18 @@
         mWifiKeyStore = wifiKeyStore;
         mWifiConfigStore = wifiConfigStore;
         mWifiPermissionsUtil = wifiPermissionsUtil;
-        mWifiPermissionsWrapper = wifiPermissionsWrapper;
-        mWifiInjector = wifiInjector;
+        mWifiMetrics = wifiMetrics;
+        mWifiBlocklistMonitor = wifiBlocklistMonitor;
+        mWifiLastResortWatchdog = wifiLastResortWatchdog;
         mWifiScoreCard = wifiScoreCard;
 
         mConfiguredNetworks = new ConfigurationMap(userManager);
         mScanDetailCaches = new HashMap<>(16, 0.75f);
         mUserTemporarilyDisabledList =
                 new MissingCounterTimerLockList<>(SCAN_RESULT_MISSING_COUNT_THRESHOLD, mClock);
+        mNonCarrierMergedNetworksStatusTracker = new NonCarrierMergedNetworksStatusTracker(mClock);
         mRandomizedMacAddressMapping = new HashMap<>();
-        mListeners = new ArrayList<>();
+        mListeners = new ArraySet<>();
 
         // Register store data for network list and deleted ephemeral SSIDs.
         mNetworkListSharedStoreData = networkListSharedStoreData;
@@ -378,71 +404,72 @@
 
         mLocalLog = new LocalLog(
                 context.getSystemService(ActivityManager.class).isLowRamDevice() ? 128 : 256);
-        mMacAddressUtil = mWifiInjector.getMacAddressUtil();
+        mMacAddressUtil = macAddressUtil;
         mLruConnectionTracker = lruConnectionTracker;
+        mBuildProperties = buildProperties;
     }
 
     /**
-     * Network Selection disable reason thresholds. These numbers are used to debounce network
-     * failures before we disable them.
-     *
-     * @param reason int reason code
-     * @return the disable threshold, or -1 if not found.
+     * Update the cellular data availability of the default data SIM.
      */
-    @VisibleForTesting
-    public static int getNetworkSelectionDisableThreshold(
-            @NetworkSelectionDisableReason int reason) {
-        DisableReasonInfo info = DISABLE_REASON_INFOS.get(reason);
-        if (info == null) {
-            Log.e(TAG, "Unrecognized network disable reason code for disable threshold: " + reason);
-            return -1;
-        } else {
-            return info.mDisableThreshold;
+    public void onCellularConnectivityChanged(@WifiDataStall.CellularDataStatusCode int status) {
+        localLog("onCellularConnectivityChanged:" + status);
+        if (status == WifiDataStall.CELLULAR_DATA_NOT_AVAILABLE) {
+            stopRestrictingAutoJoinToSubscriptionId();
         }
     }
 
     /**
-     * Network Selection disable timeout for each kind of error. After the timeout in milliseconds,
-     * enable the network again.
-     */
-    @VisibleForTesting
-    public static int getNetworkSelectionDisableTimeoutMillis(
-            @NetworkSelectionDisableReason int reason) {
-        DisableReasonInfo info = DISABLE_REASON_INFOS.get(reason);
-        if (info == null) {
-            Log.e(TAG, "Unrecognized network disable reason code for disable timeout: " + reason);
-            return -1;
-        } else {
-            return info.mDisableTimeoutMillis;
-        }
-    }
-
-    /**
-     * Determine if the framework should perform "aggressive" MAC randomization when connecting
+     * Determine if the framework should perform enhanced MAC randomization when connecting
      * to the SSID or FQDN in the input WifiConfiguration.
      * @param config
      * @return
      */
-    public boolean shouldUseAggressiveRandomization(WifiConfiguration config) {
+    public boolean shouldUseEnhancedRandomization(WifiConfiguration config) {
         if (!isMacRandomizationSupported()
-                || config.macRandomizationSetting != WifiConfiguration.RANDOMIZATION_PERSISTENT) {
+                || config.macRandomizationSetting == WifiConfiguration.RANDOMIZATION_NONE) {
             return false;
         }
+
+        // Use enhanced randomization if it's forced on by dev option
         if (mFrameworkFacade.getIntegerSetting(mContext,
                 ENHANCED_MAC_RANDOMIZATION_FEATURE_FORCE_ENABLE_FLAG, 0) == 1) {
             return true;
         }
+
+        // use enhanced or persistent randomization if configured to do so.
+        if (config.macRandomizationSetting == WifiConfiguration.RANDOMIZATION_NON_PERSISTENT) {
+            return true;
+        }
+        if (config.macRandomizationSetting == WifiConfiguration.RANDOMIZATION_PERSISTENT) {
+            return false;
+        }
+
+        // otherwise the wifi frameworks should decide automatically
         if (config.getIpConfiguration().getIpAssignment() == IpConfiguration.IpAssignment.STATIC) {
             return false;
         }
+        if (config.isOpenNetwork() && shouldEnableEnhancedRandomizationOnOpenNetwork(config)) {
+            return true;
+        }
         if (config.isPasspoint()) {
-            return isNetworkOptInForAggressiveRandomization(config.FQDN);
+            return isNetworkOptInForEnhancedRandomization(config.FQDN);
         } else {
-            return isNetworkOptInForAggressiveRandomization(config.SSID);
+            return isNetworkOptInForEnhancedRandomization(config.SSID);
         }
     }
 
-    private boolean isNetworkOptInForAggressiveRandomization(String ssidOrFqdn) {
+    private boolean shouldEnableEnhancedRandomizationOnOpenNetwork(WifiConfiguration config) {
+        if (!mDeviceConfigFacade.allowEnhancedMacRandomizationOnOpenSsids()
+                && !mContext.getResources().getBoolean(
+                        R.bool.config_wifiAllowEnhancedMacRandomizationOnOpenSsids)) {
+            return false;
+        }
+        return config.getNetworkSelectionStatus().hasEverConnected()
+                && config.getNetworkSelectionStatus().hasNeverDetectedCaptivePortal();
+    }
+
+    private boolean isNetworkOptInForEnhancedRandomization(String ssidOrFqdn) {
         Set<String> perDeviceSsidBlocklist = new ArraySet<>(mContext.getResources().getStringArray(
                 R.array.config_wifi_aggressive_randomization_ssid_blocklist));
         if (mDeviceConfigFacade.getAggressiveMacRandomizationSsidBlocklist().contains(ssidOrFqdn)
@@ -474,10 +501,11 @@
      * @param config the WifiConfiguration to obtain MAC address for.
      * @return persistent MAC address for this WifiConfiguration
      */
-    private MacAddress getPersistentMacAddress(WifiConfiguration config) {
+    @VisibleForTesting
+    public MacAddress getPersistentMacAddress(WifiConfiguration config) {
         // mRandomizedMacAddressMapping had been the location to save randomized MAC addresses.
         String persistentMacString = mRandomizedMacAddressMapping.get(
-                config.getKey());
+                config.getNetworkKey());
         // Use the MAC address stored in the storage if it exists and is valid. Otherwise
         // use the MAC address calculated from a hash function as the persistent MAC.
         if (persistentMacString != null) {
@@ -485,13 +513,13 @@
                 return MacAddress.fromString(persistentMacString);
             } catch (IllegalArgumentException e) {
                 Log.e(TAG, "Error creating randomized MAC address from stored value.");
-                mRandomizedMacAddressMapping.remove(config.getKey());
+                mRandomizedMacAddressMapping.remove(config.getNetworkKey());
             }
         }
-        MacAddress result = mMacAddressUtil.calculatePersistentMac(config.getKey(),
+        MacAddress result = mMacAddressUtil.calculatePersistentMac(config.getNetworkKey(),
                 mMacAddressUtil.obtainMacRandHashFunction(Process.WIFI_UID));
         if (result == null) {
-            result = mMacAddressUtil.calculatePersistentMac(config.getKey(),
+            result = mMacAddressUtil.calculatePersistentMac(config.getNetworkKey(),
                     mMacAddressUtil.obtainMacRandHashFunction(Process.WIFI_UID));
         }
         if (result == null) {
@@ -515,12 +543,17 @@
             return;
         }
         long expireDurationMs = (dhcpLeaseSeconds & 0xffffffffL) * 1000;
-        expireDurationMs = Math.max(AGGRESSIVE_MAC_REFRESH_MS_MIN, expireDurationMs);
-        expireDurationMs = Math.min(AGGRESSIVE_MAC_REFRESH_MS_MAX, expireDurationMs);
+        expireDurationMs = Math.max(ENHANCED_MAC_REFRESH_MS_MIN, expireDurationMs);
+        expireDurationMs = Math.min(ENHANCED_MAC_REFRESH_MS_MAX, expireDurationMs);
         internalConfig.randomizedMacExpirationTimeMs = mClock.getWallClockMillis()
                 + expireDurationMs;
     }
 
+    private void setRandomizedMacAddress(WifiConfiguration config, MacAddress mac) {
+        config.setRandomizedMacAddress(mac);
+        config.randomizedMacLastModifiedTimeMs = mClock.getWallClockMillis();
+    }
+
     /**
      * Obtain the persistent MAC address by first reading from an internal database. If non exists
      * then calculate the persistent MAC using HMAC-SHA256.
@@ -534,36 +567,37 @@
             return persistentMac;
         }
         WifiConfiguration internalConfig = getInternalConfiguredNetwork(config.networkId);
-        internalConfig.setRandomizedMacAddress(persistentMac);
+        setRandomizedMacAddress(internalConfig, persistentMac);
         return persistentMac;
     }
 
     /**
-     * This method is called before connecting to a network that has "aggressive randomization"
+     * This method is called before connecting to a network that has "enhanced randomization"
      * enabled, and will re-randomize the MAC address if needed.
      * @param config the WifiConfiguration to make the update
      * @return the updated MacAddress
      */
     private MacAddress updateRandomizedMacIfNeeded(WifiConfiguration config) {
         boolean shouldUpdateMac = config.randomizedMacExpirationTimeMs
-                < mClock.getWallClockMillis();
+                < mClock.getWallClockMillis() || mClock.getWallClockMillis()
+                - config.randomizedMacLastModifiedTimeMs >= ENHANCED_MAC_REFRESH_MS_MAX;
         if (!shouldUpdateMac) {
             return config.getRandomizedMacAddress();
         }
         WifiConfiguration internalConfig = getInternalConfiguredNetwork(config.networkId);
-        internalConfig.setRandomizedMacAddress(MacAddressUtils.createRandomUnicastAddress());
+        setRandomizedMacAddress(internalConfig, MacAddressUtils.createRandomUnicastAddress());
         return internalConfig.getRandomizedMacAddress();
     }
 
     /**
      * Returns the randomized MAC address that should be used for this WifiConfiguration.
      * This API may return a randomized MAC different from the persistent randomized MAC if
-     * the WifiConfiguration is configured for aggressive MAC randomization.
+     * the WifiConfiguration is configured for enhanced MAC randomization.
      * @param config
      * @return MacAddress
      */
     public MacAddress getRandomizedMacAndUpdateIfNeeded(WifiConfiguration config) {
-        MacAddress mac = shouldUseAggressiveRandomization(config)
+        MacAddress mac = shouldUseEnhancedRandomization(config)
                 ? updateRandomizedMacIfNeeded(config)
                 : setRandomizedMacToPersistentMac(config);
         return mac;
@@ -572,14 +606,11 @@
     /**
      * Enable/disable verbose logging in WifiConfigManager & its helper classes.
      */
-    public void enableVerboseLogging(int verbose) {
-        if (verbose > 0) {
-            mVerboseLoggingEnabled = true;
-        } else {
-            mVerboseLoggingEnabled = false;
-        }
+    public void enableVerboseLogging(boolean verbose) {
+        mVerboseLoggingEnabled = verbose;
         mWifiConfigStore.enableVerboseLogging(mVerboseLoggingEnabled);
         mWifiKeyStore.enableVerboseLogging(mVerboseLoggingEnabled);
+        mWifiBlocklistMonitor.enableVerboseLogging(mVerboseLoggingEnabled);
     }
 
     /**
@@ -612,7 +643,7 @@
      * @param configuration WifiConfiguration to hide the MAC address
      */
     private void maskRandomizedMacAddressInWifiConfiguration(WifiConfiguration configuration) {
-        configuration.setRandomizedMacAddress(DEFAULT_MAC_ADDRESS);
+        setRandomizedMacAddress(configuration, DEFAULT_MAC_ADDRESS);
     }
 
     /**
@@ -787,6 +818,40 @@
         return mConfiguredNetworks.valuesForCurrentUser();
     }
 
+    private WifiConfiguration getInternalConfiguredNetworkByUpgradableType(
+            WifiConfiguration config) {
+        WifiConfiguration internalConfig = null;
+        int securityType = config.getDefaultSecurityParams().getSecurityType();
+        WifiConfiguration possibleExistingConfig = new WifiConfiguration(config);
+        switch (securityType) {
+            case WifiConfiguration.SECURITY_TYPE_PSK:
+                possibleExistingConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
+                break;
+            case WifiConfiguration.SECURITY_TYPE_SAE:
+                possibleExistingConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
+                break;
+            case WifiConfiguration.SECURITY_TYPE_EAP:
+                possibleExistingConfig.setSecurityParams(
+                        WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
+                break;
+            case WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE:
+                possibleExistingConfig.setSecurityParams(
+                        WifiConfiguration.SECURITY_TYPE_EAP);
+                break;
+            case WifiConfiguration.SECURITY_TYPE_OPEN:
+                possibleExistingConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OWE);
+                break;
+            case WifiConfiguration.SECURITY_TYPE_OWE:
+                possibleExistingConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN);
+                break;
+            default:
+                return null;
+        }
+        internalConfig = mConfiguredNetworks.getByConfigKeyForCurrentUser(
+                possibleExistingConfig.getProfileKey());
+        return internalConfig;
+    }
+
     /**
      * Helper method to retrieve the internal WifiConfiguration object corresponding to the
      * provided configuration in our database.
@@ -798,10 +863,16 @@
         if (internalConfig != null) {
             return internalConfig;
         }
-        internalConfig = mConfiguredNetworks.getByConfigKeyForCurrentUser(config.getKey());
+        internalConfig = mConfiguredNetworks.getByConfigKeyForCurrentUser(
+                config.getProfileKey());
+        if (internalConfig != null) {
+            return internalConfig;
+        }
+        internalConfig = getInternalConfiguredNetworkByUpgradableType(config);
         if (internalConfig == null) {
             Log.e(TAG, "Cannot find network with networkId " + config.networkId
-                    + " or configKey " + config.getKey());
+                    + " or configKey " + config.getProfileKey()
+                    + " or upgradable security type check");
         }
         return internalConfig;
     }
@@ -886,7 +957,13 @@
             return true;
         }
 
-        final boolean isDeviceOwner = mWifiPermissionsUtil.isDeviceOwner(uid, packageName);
+        // TODO: ideally package should not be null here (and hence we wouldn't need the
+        // isDeviceOwner(uid) method), but it would require changing  many methods to pass the
+        // package name around (for example, all methods called by
+        // WifiServiceImpl.triggerConnectAndReturnStatus(netId, callingUid)
+        final boolean isDeviceOwner = packageName == null
+                ? mWifiPermissionsUtil.isDeviceOwner(uid)
+                : mWifiPermissionsUtil.isDeviceOwner(uid, packageName);
 
         // If |uid| corresponds to the device owner, allow all modifications.
         if (isDeviceOwner) {
@@ -901,7 +978,9 @@
         if (!isConfigEligibleForLockdown) {
             // App that created the network or settings app (i.e user) has permission to
             // modify the network.
-            return isCreator || mWifiPermissionsUtil.checkNetworkSettingsPermission(uid);
+            return isCreator
+                    || mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)
+                    || mWifiPermissionsUtil.checkNetworkSetupWizardPermission(uid);
         }
 
         final ContentResolver resolver = mContext.getContentResolver();
@@ -909,7 +988,43 @@
                 Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) != 0;
         return !isLockdownFeatureEnabled
                 // If not locked down, settings app (i.e user) has permission to modify the network.
-                && mWifiPermissionsUtil.checkNetworkSettingsPermission(uid);
+                && (mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)
+                || mWifiPermissionsUtil.checkNetworkSetupWizardPermission(uid));
+
+    }
+
+    private void mergeSecurityParamsListWithInternalWifiConfiguration(
+            WifiConfiguration internalConfig, WifiConfiguration externalConfig) {
+        // If not set, just copy over all list.
+        if (internalConfig.getSecurityParamsList().isEmpty()) {
+            internalConfig.setSecurityParams(externalConfig.getSecurityParamsList());
+            return;
+        }
+
+        WifiConfigurationUtil.addUpgradableSecurityTypeIfNecessary(externalConfig);
+
+        // An external caller is only allowed to set one type manually.
+        // As a result, only default type matters.
+        // There might be 3 cases:
+        // 1. Existing config with new upgradable type config,
+        //    ex. PSK/SAE config with SAE config.
+        // 2. Existing configuration with downgradable type config,
+        //    ex. SAE config with PSK config.
+        // 3. The new type is not a compatible type of existing config.
+        //    ex. Open config with PSK config.
+        //    This might happen when updating a config via network ID directly.
+        int oldType = internalConfig.getDefaultSecurityParams().getSecurityType();
+        int newType = externalConfig.getDefaultSecurityParams().getSecurityType();
+        if (oldType != newType) {
+            if (internalConfig.isSecurityType(newType)) {
+                internalConfig.setSecurityParamsIsAddedByAutoUpgrade(newType, false);
+            } else if (externalConfig.isSecurityType(oldType)) {
+                internalConfig.setSecurityParams(newType);
+                internalConfig.addSecurityParams(oldType);
+            } else {
+                internalConfig.setSecurityParams(externalConfig.getSecurityParamsList());
+            }
+        }
     }
 
     /**
@@ -937,7 +1052,6 @@
             internalConfig.BSSID = externalConfig.BSSID.toLowerCase();
         }
         internalConfig.hiddenSSID = externalConfig.hiddenSSID;
-        internalConfig.requirePmf = externalConfig.requirePmf;
 
         if (externalConfig.preSharedKey != null
                 && !externalConfig.preSharedKey.equals(PASSWORD_MASK)) {
@@ -969,37 +1083,7 @@
             internalConfig.roamingConsortiumIds = externalConfig.roamingConsortiumIds.clone();
         }
 
-        // Copy over all the auth/protocol/key mgmt parameters if set.
-        if (externalConfig.allowedAuthAlgorithms != null
-                && !externalConfig.allowedAuthAlgorithms.isEmpty()) {
-            internalConfig.allowedAuthAlgorithms =
-                    (BitSet) externalConfig.allowedAuthAlgorithms.clone();
-        }
-        if (externalConfig.allowedProtocols != null
-                && !externalConfig.allowedProtocols.isEmpty()) {
-            internalConfig.allowedProtocols = (BitSet) externalConfig.allowedProtocols.clone();
-        }
-        if (externalConfig.allowedKeyManagement != null
-                && !externalConfig.allowedKeyManagement.isEmpty()) {
-            internalConfig.allowedKeyManagement =
-                    (BitSet) externalConfig.allowedKeyManagement.clone();
-        }
-        if (externalConfig.allowedPairwiseCiphers != null
-                && !externalConfig.allowedPairwiseCiphers.isEmpty()) {
-            internalConfig.allowedPairwiseCiphers =
-                    (BitSet) externalConfig.allowedPairwiseCiphers.clone();
-        }
-        if (externalConfig.allowedGroupCiphers != null
-                && !externalConfig.allowedGroupCiphers.isEmpty()) {
-            internalConfig.allowedGroupCiphers =
-                    (BitSet) externalConfig.allowedGroupCiphers.clone();
-        }
-        if (externalConfig.allowedGroupManagementCiphers != null
-                && !externalConfig.allowedGroupManagementCiphers.isEmpty()) {
-            internalConfig.allowedGroupManagementCiphers =
-                    (BitSet) externalConfig.allowedGroupManagementCiphers.clone();
-        }
-        // allowedSuiteBCiphers is set internally according to the certificate type
+        mergeSecurityParamsListWithInternalWifiConfiguration(internalConfig, externalConfig);
 
         // Copy over the |IpConfiguration| parameters if set.
         if (externalConfig.getIpConfiguration() != null) {
@@ -1031,13 +1115,20 @@
         internalConfig.meteredHint = externalConfig.meteredHint;
         internalConfig.meteredOverride = externalConfig.meteredOverride;
 
-        // Copy trusted bit
         internalConfig.trusted = externalConfig.trusted;
+        internalConfig.oemPaid = externalConfig.oemPaid;
+        internalConfig.oemPrivate = externalConfig.oemPrivate;
+        internalConfig.carrierMerged = externalConfig.carrierMerged;
 
         // Copy over macRandomizationSetting
         internalConfig.macRandomizationSetting = externalConfig.macRandomizationSetting;
         internalConfig.carrierId = externalConfig.carrierId;
         internalConfig.isHomeProviderNetwork = externalConfig.isHomeProviderNetwork;
+        internalConfig.subscriptionId = externalConfig.subscriptionId;
+        internalConfig.getNetworkSelectionStatus()
+                .setConnectChoice(externalConfig.getNetworkSelectionStatus().getConnectChoice());
+        internalConfig.getNetworkSelectionStatus().setConnectChoiceRssi(
+                externalConfig.getNetworkSelectionStatus().getConnectChoiceRssi());
     }
 
     /**
@@ -1049,25 +1140,6 @@
      * @param configuration provided WifiConfiguration object.
      */
     private void setDefaultsInWifiConfiguration(WifiConfiguration configuration) {
-        configuration.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
-        configuration.allowedProtocols.set(WifiConfiguration.Protocol.WPA);
-
-        configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
-        configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
-
-        configuration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.GCMP_256);
-        configuration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
-        configuration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);
-
-        configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.GCMP_256);
-        configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
-        configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
-        configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40);
-        configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104);
-
-        configuration.allowedGroupManagementCiphers
-                .set(WifiConfiguration.GroupMgmtCipher.BIP_GMAC_256);
-
         configuration.setIpAssignment(IpConfiguration.IpAssignment.DHCP);
         configuration.setProxySettings(IpConfiguration.ProxySettings.NONE);
 
@@ -1096,13 +1168,15 @@
         // First set defaults in the new configuration created.
         setDefaultsInWifiConfiguration(newInternalConfig);
 
+        // Convert legacy fields to new security params
+        externalConfig.convertLegacyFieldsToSecurityParamsIfNeeded();
+
         // Copy over all the public elements from the provided configuration.
         mergeWithInternalWifiConfiguration(newInternalConfig, externalConfig);
 
         // Copy over the hidden configuration parameters. These are the only parameters used by
         // system apps to indicate some property about the network being added.
         // These are only copied over for network additions and ignored for network updates.
-        newInternalConfig.requirePmf = externalConfig.requirePmf;
         newInternalConfig.noInternetAccessExpected = externalConfig.noInternetAccessExpected;
         newInternalConfig.ephemeral = externalConfig.ephemeral;
         newInternalConfig.osu = externalConfig.osu;
@@ -1117,6 +1191,8 @@
         newInternalConfig.creatorUid = newInternalConfig.lastUpdateUid = uid;
         newInternalConfig.creatorName = newInternalConfig.lastUpdateName =
                 packageName != null ? packageName : mContext.getPackageManager().getNameForUid(uid);
+        newInternalConfig.lastUpdated = mClock.getWallClockMillis();
+        newInternalConfig.numRebootsSinceLastUse = 0;
         initRandomizedMacForInternalConfig(newInternalConfig);
         return newInternalConfig;
     }
@@ -1142,14 +1218,15 @@
         newInternalConfig.lastUpdateUid = uid;
         newInternalConfig.lastUpdateName =
                 packageName != null ? packageName : mContext.getPackageManager().getNameForUid(uid);
-
+        newInternalConfig.lastUpdated = mClock.getWallClockMillis();
+        newInternalConfig.numRebootsSinceLastUse = 0;
         return newInternalConfig;
     }
 
     private void logUserActionEvents(WifiConfiguration before, WifiConfiguration after) {
         // Logs changes in meteredOverride.
         if (before.meteredOverride != after.meteredOverride) {
-            mWifiInjector.getWifiMetrics().logUserActionEvent(
+            mWifiMetrics.logUserActionEvent(
                     WifiMetrics.convertMeteredOverrideEnumToUserActionEventType(
                             after.meteredOverride),
                     after.networkId);
@@ -1157,7 +1234,7 @@
 
         // Logs changes in macRandomizationSetting.
         if (before.macRandomizationSetting != after.macRandomizationSetting) {
-            mWifiInjector.getWifiMetrics().logUserActionEvent(
+            mWifiMetrics.logUserActionEvent(
                     after.macRandomizationSetting == WifiConfiguration.RANDOMIZATION_NONE
                             ? UserActionEvent.EVENT_CONFIGURE_MAC_RANDOMIZATION_OFF
                             : UserActionEvent.EVENT_CONFIGURE_MAC_RANDOMIZATION_ON,
@@ -1195,7 +1272,8 @@
             // Since the original config provided may have had an empty
             // {@link WifiConfiguration#allowedKeyMgmt} field, check again if we already have a
             // network with the the same configkey.
-            existingInternalConfig = getInternalConfiguredNetwork(newInternalConfig.getKey());
+            existingInternalConfig =
+                    getInternalConfiguredNetwork(newInternalConfig.getProfileKey());
         }
         // Existing network found. So, a network update.
         if (existingInternalConfig != null) {
@@ -1207,7 +1285,7 @@
             // Check for the app's permission before we let it update this network.
             if (!canModifyNetwork(existingInternalConfig, uid, packageName)) {
                 Log.e(TAG, "UID " + uid + " does not have permission to update configuration "
-                        + config.getKey());
+                        + config.getProfileKey());
                 return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
             }
             if (mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)
@@ -1219,11 +1297,15 @@
                             existingInternalConfig, config, uid, packageName);
         }
 
+        if (!WifiConfigurationUtil.addUpgradableSecurityTypeIfNecessary(newInternalConfig)) {
+            return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
+        }
+
         // Only add networks with proxy settings if the user has permission to
         if (WifiConfigurationUtil.hasProxyChanged(existingInternalConfig, newInternalConfig)
                 && !canModifyProxySettings(uid, packageName)) {
             Log.e(TAG, "UID " + uid + " does not have permission to modify proxy Settings "
-                    + config.getKey() + ". Must have NETWORK_SETTINGS,"
+                    + config.getProfileKey() + ". Must have NETWORK_SETTINGS,"
                     + " or be device or profile owner.");
             return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
         }
@@ -1231,9 +1313,10 @@
         if (WifiConfigurationUtil.hasMacRandomizationSettingsChanged(existingInternalConfig,
                 newInternalConfig) && !mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)
                 && !mWifiPermissionsUtil.checkNetworkSetupWizardPermission(uid)
-                && !(newInternalConfig.isPasspoint() && uid == newInternalConfig.creatorUid)) {
+                && !(newInternalConfig.isPasspoint() && uid == newInternalConfig.creatorUid)
+                && !config.fromWifiNetworkSuggestion) {
             Log.e(TAG, "UID " + uid + " does not have permission to modify MAC randomization "
-                    + "Settings " + config.getKey() + ". Must have "
+                    + "Settings " + config.getProfileKey() + ". Must have "
                     + "NETWORK_SETTINGS or NETWORK_SETUP_WIZARD or be the creator adding or "
                     + "updating a passpoint network.");
             return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
@@ -1267,11 +1350,21 @@
         // Add it to our internal map. This will replace any existing network configuration for
         // updates.
         try {
+            if (null != existingInternalConfig) {
+                mConfiguredNetworks.remove(existingInternalConfig.networkId);
+            }
             mConfiguredNetworks.put(newInternalConfig);
         } catch (IllegalArgumentException e) {
             Log.e(TAG, "Failed to add network to config map", e);
             return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
         }
+        if (removeExcessNetworks()) {
+            if (mConfiguredNetworks.getForAllUsers(newInternalConfig.networkId) == null) {
+                Log.e(TAG, "Cannot add network because number of configured networks is maxed.");
+                return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
+            }
+        }
+
         // Only re-enable network: 1. add or update user saved network; 2. add or update a user
         // saved passpoint network framework consider it is a new network.
         if (!newInternalConfig.fromWifiNetworkSuggestion
@@ -1282,14 +1375,16 @@
         // Stage the backup of the SettingsProvider package which backs this up.
         mBackupManagerProxy.notifyDataChanged();
 
-        NetworkUpdateResult result =
-                new NetworkUpdateResult(hasIpChanged, hasProxyChanged, hasCredentialChanged);
-        result.setIsNewNetwork(newNetwork);
-        result.setNetworkId(newInternalConfig.networkId);
+        NetworkUpdateResult result = new NetworkUpdateResult(
+                newInternalConfig.networkId,
+                hasIpChanged,
+                hasProxyChanged,
+                hasCredentialChanged,
+                newNetwork);
 
         localLog("addOrUpdateNetworkInternal: added/updated config."
                 + " netId=" + newInternalConfig.networkId
-                + " configKey=" + newInternalConfig.getKey()
+                + " configKey=" + newInternalConfig.getProfileKey()
                 + " uid=" + Integer.toString(newInternalConfig.creatorUid)
                 + " name=" + newInternalConfig.creatorName);
         return result;
@@ -1319,6 +1414,7 @@
             Log.e(TAG, "Cannot add/update network before store is read!");
             return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
         }
+        config.convertLegacyFieldsToSecurityParamsIfNeeded();
         WifiConfiguration existingConfig = getInternalConfiguredNetwork(config);
         if (!config.isEphemeral()) {
             // Removes the existing ephemeral network if it exists to add this configuration.
@@ -1373,6 +1469,76 @@
     }
 
     /**
+     * Increments the number of reboots since last use for each configuration.
+     *
+     * @see {@link WifiConfiguration#numRebootsSinceLastUse}
+     */
+    public void incrementNumRebootsSinceLastUse() {
+        getInternalConfiguredNetworks().forEach(config -> config.numRebootsSinceLastUse++);
+        saveToStore(false);
+    }
+
+    /**
+     * Removes excess networks in case the number of saved networks exceeds the max limit
+     * specified in config_wifiMaxNumWifiConfigurations.
+     *
+     * Configs are removed in ascending order of
+     *     1. Non-carrier networks before carrier networks
+     *     2. Non-connected networks before connected networks.
+     *     3. Deletion priority {@see WifiConfiguration#getDeletionPriority()}
+     *     4. Last use/creation/update time (lastUpdated/lastConnected or numRebootsSinceLastUse)
+     *     5. Open and OWE networks before networks with other security types.
+     *     6. Number of associations
+     *
+     * @return {@code true} if networks were removed, {@code false} otherwise.
+     */
+    private boolean removeExcessNetworks() {
+        final int maxNumConfigs = mContext.getResources().getInteger(
+                R.integer.config_wifiMaxNumWifiConfigurations);
+        if (maxNumConfigs < 0) {
+            // Max number of saved networks not specified.
+            return false;
+        }
+
+        List<WifiConfiguration> savedNetworks = getSavedNetworks(Process.WIFI_UID);
+        final int numExcessNetworks = savedNetworks.size() - maxNumConfigs;
+        if (numExcessNetworks <= 0) {
+            return false;
+        }
+
+        List<WifiConfiguration> configsToDelete = savedNetworks
+                .stream()
+                .sorted(Comparator.comparing((WifiConfiguration config) -> config.carrierId
+                        == TelephonyManager.UNKNOWN_CARRIER_ID)
+                        .thenComparing((WifiConfiguration config) -> config.status
+                                == WifiConfiguration.Status.CURRENT)
+                        .thenComparing((WifiConfiguration config) -> config.getDeletionPriority())
+                        .thenComparing((WifiConfiguration config) -> -config.numRebootsSinceLastUse)
+                        .thenComparing((WifiConfiguration config) ->
+                                Math.max(config.lastConnected, config.lastUpdated))
+                        .thenComparing((WifiConfiguration config) -> {
+                            try {
+                                int authType = config.getAuthType();
+                                return !(authType == WifiConfiguration.KeyMgmt.NONE
+                                        || authType == WifiConfiguration.KeyMgmt.OWE);
+                            } catch (IllegalStateException e) {
+                                // An invalid keymgmt configuration should be pruned first.
+                                return false;
+                            }
+                        })
+                        .thenComparing((WifiConfiguration config) -> config.numAssociation))
+                .limit(numExcessNetworks)
+                .collect(Collectors.toList());
+        for (WifiConfiguration config : configsToDelete) {
+            mConfiguredNetworks.remove(config.networkId);
+            localLog("removeExcessNetworks: removed config."
+                    + " netId=" + config.networkId
+                    + " configKey=" + config.getProfileKey());
+        }
+        return true;
+    }
+
+    /**
      * Removes the specified network configuration from our database.
      *
      * @param config provided WifiConfiguration object.
@@ -1390,16 +1556,21 @@
             mWifiKeyStore.removeKeys(config.enterpriseConfig);
         }
 
-        removeConnectChoiceFromAllNetworks(config.getKey());
+        // Do not remove the user choice when passpoint or suggestion networks are removed from
+        // WifiConfigManager. Will remove that when profile is deleted from PassointManager or
+        // WifiNetworkSuggestionsManager.
+        if (!config.isPasspoint() && !config.fromWifiNetworkSuggestion) {
+            removeConnectChoiceFromAllNetworks(config.getProfileKey());
+        }
         mConfiguredNetworks.remove(config.networkId);
         mScanDetailCaches.remove(config.networkId);
         // Stage the backup of the SettingsProvider package which backs this up.
         mBackupManagerProxy.notifyDataChanged();
-        mWifiInjector.getBssidBlocklistMonitor().handleNetworkRemoved(config.SSID);
+        mWifiBlocklistMonitor.handleNetworkRemoved(config.SSID);
 
         localLog("removeNetworkInternal: removed config."
                 + " netId=" + config.networkId
-                + " configKey=" + config.getKey()
+                + " configKey=" + config.getProfileKey()
                 + " uid=" + Integer.toString(uid)
                 + " name=" + mContext.getPackageManager().getNameForUid(uid));
         return true;
@@ -1423,7 +1594,7 @@
         }
         if (!canModifyNetwork(config, uid, packageName)) {
             Log.e(TAG, "UID " + uid + " does not have permission to delete configuration "
-                    + config.getKey());
+                    + config.getProfileKey());
             return false;
         }
         if (!removeNetworkInternal(config, uid)) {
@@ -1528,11 +1699,11 @@
                 mConfiguredNetworks.valuesForAllUsers().toArray(new WifiConfiguration[0]);
         for (WifiConfiguration config : copiedConfigs) {
             if (config.isPasspoint()) {
-                Log.d(TAG, "Removing passpoint network config " + config.getKey());
+                Log.d(TAG, "Removing passpoint network config " + config.getProfileKey());
                 removeNetwork(config.networkId, config.creatorUid, config.creatorName);
                 didRemove = true;
             } else if (config.ephemeral) {
-                Log.d(TAG, "Removing ephemeral network config " + config.getKey());
+                Log.d(TAG, "Removing ephemeral network config " + config.getProfileKey());
                 removeNetwork(config.networkId, config.creatorUid, config.creatorName);
                 didRemove = true;
             }
@@ -1541,16 +1712,16 @@
     }
 
     /**
-     * Removes the suggestion network configuration matched with {@code configKey} provided.
-     *
-     * @param configKey Config Key for the corresponding network suggestion.
+     * Removes the suggestion network configuration matched with WifiConfiguration provided.
+     * @param suggestion WifiConfiguration for suggestion which needs to remove
      * @return true if a network was removed, false otherwise.
      */
-    public boolean removeSuggestionConfiguredNetwork(@NonNull String configKey) {
-        WifiConfiguration config = getInternalConfiguredNetwork(configKey);
+    public boolean removeSuggestionConfiguredNetwork(@NonNull WifiConfiguration suggestion) {
+        WifiConfiguration config = getInternalConfiguredNetwork(
+                suggestion.getProfileKey());
         if (config != null && config.ephemeral && config.fromWifiNetworkSuggestion) {
-            Log.d(TAG, "Removing suggestion network config " + config.getKey());
-            return removeNetwork(config.networkId, config.creatorUid, config.creatorName);
+            Log.d(TAG, "Removing suggestion network config " + config.getProfileKey());
+            return removeNetwork(config.networkId, suggestion.creatorUid, suggestion.creatorName);
         }
         return false;
     }
@@ -1564,13 +1735,36 @@
     public boolean removePasspointConfiguredNetwork(@NonNull String configKey) {
         WifiConfiguration config = getInternalConfiguredNetwork(configKey);
         if (config != null && config.isPasspoint()) {
-            Log.d(TAG, "Removing passpoint network config " + config.getKey());
+            Log.d(TAG, "Removing passpoint network config " + config.getProfileKey());
             return removeNetwork(config.networkId, config.creatorUid, config.creatorName);
         }
         return false;
     }
 
     /**
+     * Removes all save networks configurations not created by the caller.
+     *
+     * @param callerUid the uid of the caller
+     * @return {@code true} if at least one network is removed.
+     */
+    public boolean removeNonCallerConfiguredNetwork(int callerUid) {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "removeNonCallerConfiguredNetwork caller = " + callerUid);
+        }
+        boolean didRemove = false;
+        WifiConfiguration[] copiedConfigs =
+                mConfiguredNetworks.valuesForAllUsers().toArray(new WifiConfiguration[0]);
+        for (WifiConfiguration config : copiedConfigs) {
+            if (config.creatorUid != callerUid) {
+                Log.d(TAG, "Removing non-caller network config " + config.getProfileKey());
+                removeNetwork(config.networkId, config.creatorUid, config.creatorName);
+                didRemove = true;
+            }
+        }
+        return didRemove;
+    }
+
+    /**
      * Check whether a network belong to a known list of networks that may not support randomized
      * MAC.
      * @param networkId
@@ -1579,71 +1773,11 @@
     public boolean isInFlakyRandomizationSsidHotlist(int networkId) {
         WifiConfiguration config = getConfiguredNetwork(networkId);
         return config != null
-                && config.macRandomizationSetting == WifiConfiguration.RANDOMIZATION_PERSISTENT
+                && config.macRandomizationSetting != WifiConfiguration.RANDOMIZATION_NONE
                 && mDeviceConfigFacade.getRandomizationFlakySsidHotlist().contains(config.SSID);
     }
 
     /**
-     * Helper method to mark a network enabled for network selection.
-     */
-    private void setNetworkSelectionEnabled(WifiConfiguration config) {
-        NetworkSelectionStatus status = config.getNetworkSelectionStatus();
-        if (status.getNetworkSelectionStatus()
-                != NetworkSelectionStatus.NETWORK_SELECTION_ENABLED) {
-            localLog("setNetworkSelectionEnabled: configKey=" + config.getKey()
-                    + " old networkStatus=" + status.getNetworkStatusString()
-                    + " disableReason=" + status.getNetworkSelectionDisableReasonString());
-        }
-        status.setNetworkSelectionStatus(
-                NetworkSelectionStatus.NETWORK_SELECTION_ENABLED);
-        status.setDisableTime(
-                NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
-        status.setNetworkSelectionDisableReason(NetworkSelectionStatus.DISABLED_NONE);
-
-        // Clear out all the disable reason counters.
-        status.clearDisableReasonCounter();
-        for (OnNetworkUpdateListener listener : mListeners) {
-            listener.onNetworkEnabled(
-                    createExternalWifiConfiguration(config, true, Process.WIFI_UID));
-        }
-    }
-
-    /**
-     * Helper method to mark a network temporarily disabled for network selection.
-     */
-    private void setNetworkSelectionTemporarilyDisabled(
-            WifiConfiguration config, int disableReason) {
-        NetworkSelectionStatus status = config.getNetworkSelectionStatus();
-        status.setNetworkSelectionStatus(
-                NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED);
-        // Only need a valid time filled in for temporarily disabled networks.
-        status.setDisableTime(mClock.getElapsedSinceBootMillis());
-        status.setNetworkSelectionDisableReason(disableReason);
-        for (OnNetworkUpdateListener listener : mListeners) {
-            listener.onNetworkTemporarilyDisabled(
-                    createExternalWifiConfiguration(config, true, Process.WIFI_UID), disableReason);
-        }
-    }
-
-    /**
-     * Helper method to mark a network permanently disabled for network selection.
-     */
-    private void setNetworkSelectionPermanentlyDisabled(
-            WifiConfiguration config, int disableReason) {
-        NetworkSelectionStatus status = config.getNetworkSelectionStatus();
-        status.setNetworkSelectionStatus(
-                NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED);
-        status.setDisableTime(
-                NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
-        status.setNetworkSelectionDisableReason(disableReason);
-        for (OnNetworkUpdateListener listener : mListeners) {
-            WifiConfiguration configForListener = new WifiConfiguration(config);
-            listener.onNetworkPermanentlyDisabled(
-                    createExternalWifiConfiguration(config, true, Process.WIFI_UID), disableReason);
-        }
-    }
-
-    /**
      * Helper method to set the publicly exposed status for the network and send out the network
      * status change broadcast.
      */
@@ -1653,86 +1787,6 @@
     }
 
     /**
-     * Sets a network's status (both internal and public) according to the update reason and
-     * its current state.
-     *
-     * This updates the network's {@link WifiConfiguration#mNetworkSelectionStatus} field and the
-     * public {@link WifiConfiguration#status} field if the network is either enabled or
-     * permanently disabled.
-     *
-     * @param config network to be updated.
-     * @param reason reason code for update.
-     * @return true if the input configuration has been updated, false otherwise.
-     */
-    private boolean setNetworkSelectionStatus(WifiConfiguration config, int reason) {
-        NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
-        if (reason < 0 || reason >= NetworkSelectionStatus.NETWORK_SELECTION_DISABLED_MAX) {
-            Log.e(TAG, "Invalid Network disable reason " + reason);
-            return false;
-        }
-        if (reason == NetworkSelectionStatus.DISABLED_NONE) {
-            setNetworkSelectionEnabled(config);
-            setNetworkStatus(config, WifiConfiguration.Status.ENABLED);
-        } else if (reason < NetworkSelectionStatus.PERMANENTLY_DISABLED_STARTING_INDEX) {
-            setNetworkSelectionTemporarilyDisabled(config, reason);
-        } else {
-            setNetworkSelectionPermanentlyDisabled(config, reason);
-            setNetworkStatus(config, WifiConfiguration.Status.DISABLED);
-        }
-        localLog("setNetworkSelectionStatus: configKey=" + config.getKey()
-                + " networkStatus=" + networkStatus.getNetworkStatusString() + " disableReason="
-                + networkStatus.getNetworkSelectionDisableReasonString());
-        saveToStore(false);
-        return true;
-    }
-
-    /**
-     * Update a network's status (both internal and public) according to the update reason and
-     * its current state.
-     *
-     * @param config network to be updated.
-     * @param reason reason code for update.
-     * @return true if the input configuration has been updated, false otherwise.
-     */
-    private boolean updateNetworkSelectionStatus(WifiConfiguration config, int reason) {
-        NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
-        if (reason != NetworkSelectionStatus.DISABLED_NONE) {
-
-            // Do not update SSID blocklist with information if this is the only
-            // SSID be observed. By ignoring it we will cause additional failures
-            // which will trigger Watchdog.
-            if (reason == NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION
-                    || reason == NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE
-                    || reason == NetworkSelectionStatus.DISABLED_DHCP_FAILURE) {
-                if (mWifiInjector.getWifiLastResortWatchdog().shouldIgnoreSsidUpdate()) {
-                    if (mVerboseLoggingEnabled) {
-                        Log.v(TAG, "Ignore update network selection status "
-                                    + "since Watchdog trigger is activated");
-                    }
-                    return false;
-                }
-            }
-
-            networkStatus.incrementDisableReasonCounter(reason);
-            // For network disable reasons, we should only update the status if we cross the
-            // threshold.
-            int disableReasonCounter = networkStatus.getDisableReasonCounter(reason);
-            int disableReasonThreshold = getNetworkSelectionDisableThreshold(reason);
-            if (disableReasonCounter < disableReasonThreshold) {
-                if (mVerboseLoggingEnabled) {
-                    Log.v(TAG, "Disable counter for network " + config.getPrintableSsid()
-                            + " for reason "
-                            + NetworkSelectionStatus.getNetworkSelectionDisableReasonString(reason)
-                            + " is " + networkStatus.getDisableReasonCounter(reason)
-                            + " and threshold is " + disableReasonThreshold);
-                }
-                return true;
-            }
-        }
-        return setNetworkSelectionStatus(config, reason);
-    }
-
-    /**
      * Update a network's status (both internal and public) according to the update reason and
      * its current state.
      *
@@ -1755,39 +1809,62 @@
         return updateNetworkSelectionStatus(config, reason);
     }
 
+    private boolean updateNetworkSelectionStatus(WifiConfiguration config, int reason) {
+        int prevNetworkSelectionStatus = config.getNetworkSelectionStatus()
+                .getNetworkSelectionStatus();
+        if (!mWifiBlocklistMonitor.updateNetworkSelectionStatus(config, reason)) {
+            return false;
+        }
+        int newNetworkSelectionStatus = config.getNetworkSelectionStatus()
+                .getNetworkSelectionStatus();
+        if (prevNetworkSelectionStatus != newNetworkSelectionStatus) {
+            sendNetworkSelectionStatusChangedUpdate(config, newNetworkSelectionStatus, reason);
+            sendConfiguredNetworkChangedBroadcast(WifiManager.CHANGE_REASON_CONFIG_CHANGE);
+        }
+        saveToStore(false);
+        return true;
+    }
+
+    private void sendNetworkSelectionStatusChangedUpdate(WifiConfiguration config,
+            int newNetworkSelectionStatus, int disableReason) {
+        switch (newNetworkSelectionStatus) {
+            case NetworkSelectionStatus.NETWORK_SELECTION_ENABLED:
+                for (OnNetworkUpdateListener listener : mListeners) {
+                    listener.onNetworkEnabled(
+                            createExternalWifiConfiguration(config, true, Process.WIFI_UID));
+                }
+                break;
+            case NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED:
+                for (OnNetworkUpdateListener listener : mListeners) {
+                    listener.onNetworkTemporarilyDisabled(
+                            createExternalWifiConfiguration(config, true, Process.WIFI_UID),
+                            disableReason);
+                }
+                break;
+            case NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED:
+                for (OnNetworkUpdateListener listener : mListeners) {
+                    WifiConfiguration configForListener = new WifiConfiguration(config);
+                    listener.onNetworkPermanentlyDisabled(
+                            createExternalWifiConfiguration(config, true, Process.WIFI_UID),
+                            disableReason);
+                }
+                break;
+            default:
+                // all cases covered
+        }
+    }
+
     /**
-     * Attempt to re-enable a network for network selection, if this network was either:
-     * a) Previously temporarily disabled, but its disable timeout has expired, or
-     * b) Previously disabled because of a user switch, but is now visible to the current
-     * user.
-     *
-     * @param config configuration for the network to be re-enabled for network selection. The
-     *               network corresponding to the config must be visible to the current user.
-     * @return true if the network identified by {@param config} was re-enabled for qualified
-     * network selection, false otherwise.
+     * Re-enable all temporary disabled configured networks.
      */
-    private boolean tryEnableNetwork(WifiConfiguration config) {
-        NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
-        if (networkStatus.isNetworkTemporaryDisabled()) {
-            long timeDifferenceMs =
-                    mClock.getElapsedSinceBootMillis() - networkStatus.getDisableTime();
-            int disableReason = networkStatus.getNetworkSelectionDisableReason();
-            int blockedBssids = Math.min(MAX_BLOCKED_BSSID_PER_NETWORK,
-                    mWifiInjector.getBssidBlocklistMonitor()
-                            .updateAndGetNumBlockedBssidsForSsid(config.SSID));
-            // if no BSSIDs are blocked then we should keep trying to connect to something
-            long disableTimeoutMs = 0;
-            if (blockedBssids > 0) {
-                double multiplier = Math.pow(2.0, blockedBssids - 1.0);
-                disableTimeoutMs = (long) (getNetworkSelectionDisableTimeoutMillis(disableReason)
-                        * multiplier);
-            }
-            if (timeDifferenceMs >= disableTimeoutMs) {
-                return updateNetworkSelectionStatus(
-                        config, NetworkSelectionStatus.DISABLED_NONE);
+    public void enableTemporaryDisabledNetworks() {
+        mWifiBlocklistMonitor.clearBssidBlocklist();
+        for (WifiConfiguration config : getInternalConfiguredNetworks()) {
+            if (config.getNetworkSelectionStatus().isNetworkTemporaryDisabled()) {
+                updateNetworkSelectionStatus(config,
+                        NetworkSelectionStatus.DISABLED_NONE);
             }
         }
-        return false;
     }
 
     /**
@@ -1805,7 +1882,10 @@
         if (config == null) {
             return false;
         }
-        return tryEnableNetwork(config);
+        if (mWifiBlocklistMonitor.shouldEnableNetwork(config)) {
+            return updateNetworkSelectionStatus(config, NetworkSelectionStatus.DISABLED_NONE);
+        }
+        return false;
     }
 
     /**
@@ -1838,7 +1918,7 @@
         }
         if (!canModifyNetwork(config, uid, packageName)) {
             Log.e(TAG, "UID " + uid + " does not have permission to update configuration "
-                    + config.getKey());
+                    + config.getProfileKey());
             return false;
         }
         if (!updateNetworkSelectionStatus(
@@ -1875,7 +1955,7 @@
         }
         if (!canModifyNetwork(config, uid, packageName)) {
             Log.e(TAG, "UID " + uid + " does not have permission to update configuration "
-                    + config.getKey());
+                    + config.getProfileKey());
             return false;
         }
         if (!updateNetworkSelectionStatus(
@@ -1907,8 +1987,8 @@
 
         config.allowAutojoin = choice;
         if (!choice) {
-            removeConnectChoiceFromAllNetworks(config.getKey());
-            clearNetworkConnectChoice(config.networkId);
+            removeConnectChoiceFromAllNetworks(config.getProfileKey());
+            clearConnectChoiceInternal(config);
         }
         sendConfiguredNetworkChangedBroadcast(WifiManager.CHANGE_REASON_CONFIG_CHANGE);
         if (!config.ephemeral) {
@@ -1924,7 +2004,7 @@
      * @param uid       uid of the app requesting the connection.
      * @return true if the network was found, false otherwise.
      */
-    public boolean updateLastConnectUid(int networkId, int uid) {
+    private boolean updateLastConnectUid(int networkId, int uid) {
         if (mVerboseLoggingEnabled) {
             Log.v(TAG, "Update network last connect UID for " + networkId);
         }
@@ -1951,9 +2031,12 @@
      * 5. Sets the status of network as |CURRENT|.
      *
      * @param networkId network ID corresponding to the network.
+     * @param shouldSetUserConnectChoice setup user connect choice on this network.
+     * @param rssi signal strength of the connected network.
      * @return true if the network was found, false otherwise.
      */
-    public boolean updateNetworkAfterConnect(int networkId) {
+    public boolean updateNetworkAfterConnect(int networkId, boolean shouldSetUserConnectChoice,
+            int rssi) {
         if (mVerboseLoggingEnabled) {
             Log.v(TAG, "Update network after connect for " + networkId);
         }
@@ -1966,7 +2049,11 @@
         if (!config.isPasspoint() && (config.fromWifiNetworkSuggestion || !config.ephemeral)) {
             mLruConnectionTracker.addNetwork(config);
         }
+        if (shouldSetUserConnectChoice) {
+            setUserConnectChoice(config.networkId, rssi);
+        }
         config.lastConnected = mClock.getWallClockMillis();
+        config.numRebootsSinceLastUse = 0;
         config.numAssociation++;
         config.getNetworkSelectionStatus().clearDisableReasonCounter();
         config.getNetworkSelectionStatus().setHasEverConnected(true);
@@ -1976,6 +2063,17 @@
     }
 
     /**
+     * Set captive portal to be detected for this network.
+     * @param networkId
+     */
+    public void noteCaptivePortalDetected(int networkId) {
+        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
+        if (config != null) {
+            config.getNetworkSelectionStatus().setHasNeverDetectedCaptivePortal(false);
+        }
+    }
+
+    /**
      * Updates a network configuration after disconnection from it.
      *
      * This method updates the following WifiConfiguration elements:
@@ -1995,7 +2093,7 @@
         }
         config.lastDisconnected = mClock.getWallClockMillis();
         config.randomizedMacExpirationTimeMs = Math.max(config.randomizedMacExpirationTimeMs,
-                config.lastDisconnected + AGGRESSIVE_MAC_WAIT_AFTER_DISCONNECT_MS);
+                config.lastDisconnected + ENHANCED_MAC_WAIT_AFTER_DISCONNECT_MS);
         // If the network hasn't been disabled, mark it back as
         // enabled after disconnection.
         if (config.status == WifiConfiguration.Status.CURRENT) {
@@ -2043,6 +2141,7 @@
         config.getNetworkSelectionStatus().setCandidate(null);
         config.getNetworkSelectionStatus().setCandidateScore(Integer.MIN_VALUE);
         config.getNetworkSelectionStatus().setSeenInLastQualifiedNetworkSelection(false);
+        config.getNetworkSelectionStatus().setCandidateSecurityParams(null);
         return true;
     }
 
@@ -2057,11 +2156,14 @@
      * @param networkId  network ID corresponding to the network.
      * @param scanResult Candidate ScanResult associated with this network.
      * @param score      Score assigned to the candidate.
+     * @param params     Security params for this candidate.
      * @return true if the network was found, false otherwise.
      */
-    public boolean setNetworkCandidateScanResult(int networkId, ScanResult scanResult, int score) {
+    public boolean setNetworkCandidateScanResult(int networkId, ScanResult scanResult, int score,
+            SecurityParams params) {
         if (mVerboseLoggingEnabled) {
-            Log.v(TAG, "Set network candidate scan result " + scanResult + " for " + networkId);
+            Log.v(TAG, "Set network candidate scan result " + scanResult + " for " + networkId
+                    + " with security params " + params);
         }
         WifiConfiguration config = getInternalConfiguredNetwork(networkId);
         if (config == null) {
@@ -2071,6 +2173,7 @@
         config.getNetworkSelectionStatus().setCandidate(scanResult);
         config.getNetworkSelectionStatus().setCandidateScore(score);
         config.getNetworkSelectionStatus().setSeenInLastQualifiedNetworkSelection(true);
+        config.getNetworkSelectionStatus().setCandidateSecurityParams(params);
         return true;
     }
 
@@ -2082,7 +2185,7 @@
      *
      * @param connectChoiceConfigKey ConfigKey corresponding to the network that is being removed.
      */
-    private void removeConnectChoiceFromAllNetworks(String connectChoiceConfigKey) {
+    public void removeConnectChoiceFromAllNetworks(String connectChoiceConfigKey) {
         if (mVerboseLoggingEnabled) {
             Log.v(TAG, "Removing connect choice from all networks " + connectChoiceConfigKey);
         }
@@ -2095,54 +2198,12 @@
             if (TextUtils.equals(connectChoice, connectChoiceConfigKey)) {
                 Log.d(TAG, "remove connect choice:" + connectChoice + " from " + config.SSID
                         + " : " + config.networkId);
-                clearNetworkConnectChoice(config.networkId);
+                clearConnectChoiceInternal(config);
             }
         }
-    }
-
-    /**
-     * Clear the {@link NetworkSelectionStatus#mConnectChoice} for the provided network.
-     *
-     * @param networkId network ID corresponding to the network.
-     * @return true if the network was found, false otherwise.
-     */
-    public boolean clearNetworkConnectChoice(int networkId) {
-        if (mVerboseLoggingEnabled) {
-            Log.v(TAG, "Clear network connect choice for " + networkId);
+        for (OnNetworkUpdateListener listener : mListeners) {
+            listener.onConnectChoiceRemoved(connectChoiceConfigKey);
         }
-        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
-        if (config == null) {
-            return false;
-        }
-        config.getNetworkSelectionStatus().setConnectChoice(null);
-        saveToStore(false);
-        return true;
-    }
-
-    /**
-     * Set the {@link NetworkSelectionStatus#mConnectChoice} for the provided network.
-     *
-     * This is invoked by Network Selector when the user overrides the currently connected network
-     * choice.
-     *
-     * @param networkId              network ID corresponding to the network.
-     * @param connectChoiceConfigKey ConfigKey corresponding to the network which was chosen over
-     *                               this network.
-     * @param timestamp              timestamp at which the choice was made.
-     * @return true if the network was found, false otherwise.
-     */
-    public boolean setNetworkConnectChoice(
-            int networkId, String connectChoiceConfigKey) {
-        if (mVerboseLoggingEnabled) {
-            Log.v(TAG, "Set network connect choice " + connectChoiceConfigKey + " for " + networkId);
-        }
-        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
-        if (config == null) {
-            return false;
-        }
-        config.getNetworkSelectionStatus().setConnectChoice(connectChoiceConfigKey);
-        saveToStore(false);
-        return true;
     }
 
     /**
@@ -2157,6 +2218,7 @@
             return false;
         }
         config.numNoInternetAccessReports++;
+        config.validatedInternetAccess = false;
         return true;
     }
 
@@ -2243,7 +2305,7 @@
         if (config == null) {
             return "";
         }
-        return config.getKey();
+        return config.getProfileKey();
     }
 
     /**
@@ -2313,10 +2375,6 @@
 
         // Add the scan detail to this network's scan detail cache.
         scanDetailCache.put(scanDetail);
-
-        // Since we added a scan result to this configuration, re-attempt linking.
-        // TODO: Do we really need to do this after every scan result?
-        attemptNetworkLinking(config);
     }
 
     /**
@@ -2325,16 +2383,24 @@
      * @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) {
+    public WifiConfiguration getSavedNetworkForScanDetail(ScanDetail scanDetail) {
         ScanResult scanResult = scanDetail.getScanResult();
         if (scanResult == null) {
             Log.e(TAG, "No scan result found in scan detail");
             return null;
         }
+        return getSavedNetworkForScanResult(scanResult);
+    }
+
+    /**
+     * Retrieves a configured network corresponding to the provided scan result if one exists.
+     *
+     * @param scanResult ScanResult instance to use for looking up the network.
+     * @return WifiConfiguration object representing the network corresponding to the scanResult,
+     * null if none exists.
+     */
+    public WifiConfiguration getSavedNetworkForScanResult(@NonNull ScanResult scanResult) {
         WifiConfiguration config = null;
         try {
             config = mConfiguredNetworks.getByScanResultForCurrentUser(scanResult);
@@ -2343,7 +2409,7 @@
         }
         if (config != null) {
             if (mVerboseLoggingEnabled) {
-                Log.v(TAG, "getSavedNetworkFromScanDetail Found " + config.getKey()
+                Log.v(TAG, "getSavedNetworkFromScanResult Found " + config.getProfileKey()
                         + " for " + scanResult.SSID + "[" + scanResult.capabilities + "]");
             }
         }
@@ -2355,11 +2421,9 @@
      * {@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);
+    public void updateScanDetailCacheFromScanDetailForSavedNetwork(ScanDetail scanDetail) {
+        WifiConfiguration network = getSavedNetworkForScanDetail(scanDetail);
         if (network == null) {
             return;
         }
@@ -2373,11 +2437,9 @@
      * @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);
+    public WifiConfiguration getSavedNetworkForScanDetailAndCache(ScanDetail scanDetail) {
+        WifiConfiguration network = getSavedNetworkForScanDetail(scanDetail);
         if (network == null) {
             return null;
         }
@@ -2426,7 +2488,7 @@
                     Log.v(TAG, "Updating scan detail cache freq=" + result.frequency
                             + " BSSID=" + result.BSSID
                             + " RSSI=" + result.level
-                            + " for " + config.getKey());
+                            + " for " + config.getProfileKey());
                 }
             }
         }
@@ -2464,45 +2526,56 @@
     private boolean shouldNetworksBeLinked(
             WifiConfiguration network1, WifiConfiguration network2,
             ScanDetailCache scanDetailCache1, ScanDetailCache scanDetailCache2) {
-        // TODO (b/30706406): Link networks only with same passwords if the
-        // |mOnlyLinkSameCredentialConfigurations| flag is set.
+        // Check if networks should not be linked due to credential mismatch
         if (mContext.getResources().getBoolean(
                 R.bool.config_wifi_only_link_same_credential_configurations)) {
             if (!TextUtils.equals(network1.preSharedKey, network2.preSharedKey)) {
-                if (mVerboseLoggingEnabled) {
-                    Log.v(TAG, "shouldNetworksBeLinked unlink due to password mismatch");
-                }
                 return false;
             }
         }
+
+        // Skip VRRP MAC addresses since they are likely to correspond to different networks even if
+        // they match.
+        if ((network1.defaultGwMacAddress != null && network1.defaultGwMacAddress
+                .regionMatches(true, 0, VRRP_MAC_ADDRESS_PREFIX, 0,
+                        VRRP_MAC_ADDRESS_PREFIX.length()))
+                || (network2.defaultGwMacAddress != null && network2.defaultGwMacAddress
+                .regionMatches(true, 0, VRRP_MAC_ADDRESS_PREFIX, 0,
+                        VRRP_MAC_ADDRESS_PREFIX.length()))) {
+            return false;
+        }
+
+        // Check if networks should be linked due to default gateway match
         if (network1.defaultGwMacAddress != null && network2.defaultGwMacAddress != null) {
             // If both default GW are known, link only if they are equal
-            if (network1.defaultGwMacAddress.equals(network2.defaultGwMacAddress)) {
+            if (network1.defaultGwMacAddress.equalsIgnoreCase(network2.defaultGwMacAddress)) {
                 if (mVerboseLoggingEnabled) {
                     Log.v(TAG, "shouldNetworksBeLinked link due to same gw " + network2.SSID
                             + " and " + network1.SSID + " GW " + network1.defaultGwMacAddress);
                 }
                 return true;
             }
-        } else {
-            // We do not know BOTH default gateways hence we will try to link
-            // hoping that WifiConfigurations are indeed behind the same gateway.
-            // once both WifiConfiguration have been tried and thus once both default gateways
-            // are known we will revisit the choice of linking them.
-            if (scanDetailCache1 != null && scanDetailCache2 != null) {
-                for (String abssid : scanDetailCache1.keySet()) {
-                    for (String bbssid : scanDetailCache2.keySet()) {
-                        if (abssid.regionMatches(
-                                true, 0, bbssid, 0, LINK_CONFIGURATION_BSSID_MATCH_LENGTH)) {
-                            // If first 16 ASCII characters of BSSID matches,
-                            // we assume this is a DBDC.
-                            if (mVerboseLoggingEnabled) {
-                                Log.v(TAG, "shouldNetworksBeLinked link due to DBDC BSSID match "
-                                        + network2.SSID + " and " + network1.SSID
-                                        + " bssida " + abssid + " bssidb " + bbssid);
-                            }
-                            return true;
+            return false;
+        }
+
+        // We do not know BOTH default gateways yet, but if the first 16 ASCII characters of BSSID
+        // match then we can assume this is a DBDC with the same gateway. Once both gateways become
+        // known, we will unlink the networks if it turns out the gateways are actually different.
+        if (!mContext.getResources().getBoolean(
+                R.bool.config_wifiAllowLinkingUnknownDefaultGatewayConfigurations)) {
+            return false;
+        }
+        if (scanDetailCache1 != null && scanDetailCache2 != null) {
+            for (String abssid : scanDetailCache1.keySet()) {
+                for (String bbssid : scanDetailCache2.keySet()) {
+                    if (abssid.regionMatches(
+                            true, 0, bbssid, 0, LINK_CONFIGURATION_BSSID_MATCH_LENGTH)) {
+                        if (mVerboseLoggingEnabled) {
+                            Log.v(TAG, "shouldNetworksBeLinked link due to DBDC BSSID match "
+                                    + network2.SSID + " and " + network1.SSID
+                                    + " bssida " + abssid + " bssidb " + bbssid);
                         }
+                        return true;
                     }
                 }
             }
@@ -2518,8 +2591,8 @@
      */
     private void linkNetworks(WifiConfiguration network1, WifiConfiguration network2) {
         if (mVerboseLoggingEnabled) {
-            Log.v(TAG, "linkNetworks will link " + network2.getKey()
-                    + " and " + network1.getKey());
+            Log.v(TAG, "linkNetworks will link " + network2.getProfileKey()
+                    + " and " + network1.getProfileKey());
         }
         if (network2.linkedConfigurations == null) {
             network2.linkedConfigurations = new HashMap<>();
@@ -2529,8 +2602,8 @@
         }
         // TODO (b/30638473): This needs to become a set instead of map, but it will need
         // public interface changes and need some migration of existing store data.
-        network2.linkedConfigurations.put(network1.getKey(), 1);
-        network1.linkedConfigurations.put(network2.getKey(), 1);
+        network2.linkedConfigurations.put(network1.getProfileKey(), 1);
+        network1.linkedConfigurations.put(network2.getProfileKey(), 1);
     }
 
     /**
@@ -2541,20 +2614,20 @@
      */
     private void unlinkNetworks(WifiConfiguration network1, WifiConfiguration network2) {
         if (network2.linkedConfigurations != null
-                && (network2.linkedConfigurations.get(network1.getKey()) != null)) {
+                && (network2.linkedConfigurations.get(network1.getProfileKey()) != null)) {
             if (mVerboseLoggingEnabled) {
-                Log.v(TAG, "unlinkNetworks un-link " + network1.getKey()
-                        + " from " + network2.getKey());
+                Log.v(TAG, "unlinkNetworks un-link " + network1.getProfileKey()
+                        + " from " + network2.getProfileKey());
             }
-            network2.linkedConfigurations.remove(network1.getKey());
+            network2.linkedConfigurations.remove(network1.getProfileKey());
         }
         if (network1.linkedConfigurations != null
-                && (network1.linkedConfigurations.get(network2.getKey()) != null)) {
+                && (network1.linkedConfigurations.get(network2.getProfileKey()) != null)) {
             if (mVerboseLoggingEnabled) {
-                Log.v(TAG, "unlinkNetworks un-link " + network2.getKey()
-                        + " from " + network1.getKey());
+                Log.v(TAG, "unlinkNetworks un-link " + network2.getProfileKey()
+                        + " from " + network1.getProfileKey());
             }
-            network1.linkedConfigurations.remove(network2.getKey());
+            network1.linkedConfigurations.remove(network2.getProfileKey());
         }
     }
 
@@ -2567,7 +2640,7 @@
      */
     private void attemptNetworkLinking(WifiConfiguration config) {
         // Only link WPA_PSK config.
-        if (!config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
+        if (!config.isSecurityType(WifiConfiguration.SECURITY_TYPE_PSK)) {
             return;
         }
         ScanDetailCache scanDetailCache = getScanDetailCacheForNetwork(config.networkId);
@@ -2577,15 +2650,18 @@
             return;
         }
         for (WifiConfiguration linkConfig : getInternalConfiguredNetworks()) {
-            if (linkConfig.getKey().equals(config.getKey())) {
+            if (linkConfig.getProfileKey().equals(config.getProfileKey())) {
                 continue;
             }
             if (linkConfig.ephemeral) {
                 continue;
             }
+            if (!linkConfig.getNetworkSelectionStatus().isNetworkEnabled()) {
+                continue;
+            }
             // Network Selector will be allowed to dynamically jump from a linked configuration
             // to another, hence only link configurations that have WPA_PSK security type.
-            if (!linkConfig.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
+            if (!linkConfig.isSecurityType(WifiConfiguration.SECURITY_TYPE_PSK)) {
                 continue;
             }
             ScanDetailCache linkScanDetailCache =
@@ -2646,13 +2722,26 @@
     }
 
     /**
+     * Check if the provided network should be disabled because it's a non-carrier-merged network.
+     * @param config WifiConfiguration
+     * @return true if the network is a non-carrier-merged network and it should be disabled,
+     * otherwise false.
+     */
+    public boolean isNonCarrierMergedNetworkTemporarilyDisabled(
+            @NonNull WifiConfiguration config) {
+        return mNonCarrierMergedNetworksStatusTracker.isNetworkDisabled(config);
+    }
+
+    /**
      * User temporarily disable a network and will be block to auto-join when network is still
      * nearby.
      *
      * The network will be re-enabled when:
      * a) User select to connect the network.
      * b) The network is not in range for {@link #USER_DISCONNECT_NETWORK_BLOCK_EXPIRY_MS}
-     * c) Toggle wifi off, reset network settings or device reboot.
+     * c) The maximum disable duration configured by
+     * config_wifiAllNonCarrierMergedWifiMaxDisableDurationMinutes has passed.
+     * d) Toggle wifi off, reset network settings or device reboot.
      *
      * @param network Input can be SSID or FQDN. And caller must ensure that the SSID passed thru
      *                this API matched the WifiConfiguration.SSID rules, and thus be surrounded by
@@ -2660,10 +2749,62 @@
      *        uid     UID of the calling process.
      */
     public void userTemporarilyDisabledNetwork(String network, int uid) {
-        mUserTemporarilyDisabledList.add(network, USER_DISCONNECT_NETWORK_BLOCK_EXPIRY_MS);
+        int maxDisableDurationMinutes = mContext.getResources().getInteger(R.integer
+                .config_wifiAllNonCarrierMergedWifiMaxDisableDurationMinutes);
+        mUserTemporarilyDisabledList.add(network, USER_DISCONNECT_NETWORK_BLOCK_EXPIRY_MS,
+                maxDisableDurationMinutes * 60 * 1000);
         Log.d(TAG, "Temporarily disable network: " + network + " uid=" + uid + " num="
-                + mUserTemporarilyDisabledList.size());
+                + mUserTemporarilyDisabledList.size() + ", maxDisableDurationMinutes:"
+                + maxDisableDurationMinutes);
         removeUserChoiceFromDisabledNetwork(network, uid);
+        saveToStore(false);
+    }
+
+    /**
+     * Temporarily disable visible and configured networks except for carrier merged networks for
+     * the given subscriptionId.
+     * @param subscriptionId
+     */
+    public void startRestrictingAutoJoinToSubscriptionId(int subscriptionId) {
+        int minDisableDurationMinutes = mContext.getResources().getInteger(R.integer
+                .config_wifiAllNonCarrierMergedWifiMinDisableDurationMinutes);
+        int maxDisableDurationMinutes = mContext.getResources().getInteger(R.integer
+                .config_wifiAllNonCarrierMergedWifiMaxDisableDurationMinutes);
+        localLog("startRestrictingAutoJoinToSubscriptionId: " + subscriptionId
+                + " minDisableDurationMinutes:" + minDisableDurationMinutes
+                + " maxDisableDurationMinutes:" + maxDisableDurationMinutes);
+        long maxDisableDurationMs = maxDisableDurationMinutes * 60 * 1000;
+        // do a clear to make sure we start at a clean state.
+        mNonCarrierMergedNetworksStatusTracker.clear();
+        mNonCarrierMergedNetworksStatusTracker.disableAllNonCarrierMergedNetworks(subscriptionId,
+                minDisableDurationMinutes * 60 * 1000,
+                maxDisableDurationMs);
+        for (WifiConfiguration config : getInternalConfiguredNetworks()) {
+            ScanDetailCache scanDetailCache = getScanDetailCacheForNetwork(config.networkId);
+            if (scanDetailCache == null) {
+                continue;
+            }
+            ScanResult scanResult = scanDetailCache.getMostRecentScanResult();
+            if (scanResult == null) {
+                continue;
+            }
+            if (mClock.getWallClockMillis() - scanResult.seen
+                    < NON_CARRIER_MERGED_NETWORKS_SCAN_CACHE_QUERY_DURATION_MS) {
+                // do not disable if this is a carrier-merged-network with the given subscriptionId
+                if (config.carrierMerged && config.subscriptionId == subscriptionId) {
+                    continue;
+                }
+                mNonCarrierMergedNetworksStatusTracker.temporarilyDisableNetwork(config,
+                        USER_DISCONNECT_NETWORK_BLOCK_EXPIRY_MS, maxDisableDurationMs);
+            }
+        }
+    }
+
+    /**
+     * Resets the effects of startTemporarilyDisablngAllNonCarrierMergedWifi.
+     */
+    public void stopRestrictingAutoJoinToSubscriptionId() {
+        mNonCarrierMergedNetworksStatusTracker.clear();
     }
 
     /**
@@ -2674,6 +2815,7 @@
      */
     public void updateUserDisabledList(List<String> networks) {
         mUserTemporarilyDisabledList.update(new HashSet<>(networks));
+        mNonCarrierMergedNetworksStatusTracker.update(new HashSet<>(networks));
     }
 
     private void removeUserChoiceFromDisabledNetwork(
@@ -2681,10 +2823,10 @@
         for (WifiConfiguration config : getInternalConfiguredNetworks()) {
             if (TextUtils.equals(config.SSID, network) || TextUtils.equals(config.FQDN, network)) {
                 if (mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)) {
-                    mWifiInjector.getWifiMetrics().logUserActionEvent(
+                    mWifiMetrics.logUserActionEvent(
                             UserActionEvent.EVENT_DISCONNECT_WIFI, config.networkId);
                 }
-                removeConnectChoiceFromAllNetworks(config.getKey());
+                removeConnectChoiceFromAllNetworks(config.getProfileKey());
             }
         }
     }
@@ -2692,22 +2834,24 @@
     /**
      * User enabled network manually, maybe trigger by user select to connect network.
      * @param networkId enabled network id.
+     * @return true if the operation succeeded, false otherwise.
      */
-    public void userEnabledNetwork(int networkId) {
+    public boolean userEnabledNetwork(int networkId) {
         WifiConfiguration configuration = getInternalConfiguredNetwork(networkId);
         if (configuration == null) {
-            return;
+            return false;
         }
-        String network;
+        final String network;
         if (configuration.isPasspoint()) {
             network = configuration.FQDN;
         } else {
             network = configuration.SSID;
         }
         mUserTemporarilyDisabledList.remove(network);
-        mWifiInjector.getBssidBlocklistMonitor().clearBssidBlocklistForSsid(configuration.SSID);
+        mWifiBlocklistMonitor.clearBssidBlocklistForSsid(configuration.SSID);
         Log.d(TAG, "Enable disabled network: " + network + " num="
                 + mUserTemporarilyDisabledList.size());
+        return true;
     }
 
     /**
@@ -2753,6 +2897,23 @@
     }
 
     /**
+     * Clear all ephemeral carrier networks, make the subscriptionId update during the next network
+     * selection.
+     */
+    public void removeEphemeralCarrierNetworks() {
+        if (mVerboseLoggingEnabled) localLog("removeEphemeralCarrierNetwork");
+        WifiConfiguration[] copiedConfigs =
+                mConfiguredNetworks.valuesForAllUsers().toArray(new WifiConfiguration[0]);
+        for (WifiConfiguration config : copiedConfigs) {
+            if (!config.ephemeral
+                    || config.subscriptionId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                continue;
+            }
+            removeNetwork(config.networkId, config.creatorUid, config.creatorName);
+        }
+    }
+
+    /**
      * Helper method to perform the following operations during user switch/unlock:
      * - Remove private networks of the old user.
      * - Load from the new user store file.
@@ -2782,7 +2943,7 @@
      * - Write the store files to move any user specific private networks from shared store to user
      *   store.
      *
-     * Need to be called when {@link com.android.server.SystemService#onSwitchUser(int)} is invoked.
+     * Need to be called when {@link com.android.server.SystemService#onUserSwitching} is invoked.
      *
      * @param userId The identifier of the new foreground user, after the switch.
      * @return List of network ID's of all the private networks of the old user which will be
@@ -2816,6 +2977,8 @@
 
         if (mUserManager.isUserUnlockingOrUnlocked(UserHandle.of(mCurrentUserId))) {
             handleUserUnlockOrSwitch(mCurrentUserId);
+            // only handle the switching of unlocked users in {@link WifiCarrierInfoManager}.
+            mWifiCarrierInfoManager.onUnlockedUserSwitching(mCurrentUserId);
         } else {
             // Cannot read data from new user's CE store file before they log-in.
             mPendingUnlockStoreRead = true;
@@ -2828,7 +2991,7 @@
      * Handles the unlock of foreground user. This maybe needed to read the store file if the user's
      * CE storage is not visible when {@link #handleUserSwitch(int)} is invoked.
      *
-     * Need to be called when {@link com.android.server.SystemService#onUnlockUser(int)} is invoked.
+     * Need to be called when {@link com.android.server.SystemService#onUserUnlocking} is invoked.
      *
      * @param userId The identifier of the user that unlocked.
      */
@@ -2854,7 +3017,7 @@
      * Handles the stop of foreground user. This is needed to write the store file to flush
      * out any pending data before the user's CE store storage is unavailable.
      *
-     * Need to be called when {@link com.android.server.SystemService#onStopUser(int)} is invoked.
+     * Need to be called when {@link com.android.server.SystemService#onUserStopping} is invoked.
      *
      * @param userId The identifier of the user that stopped.
      */
@@ -2880,6 +3043,7 @@
         localLog("clearInternalData: Clearing all internal data");
         mConfiguredNetworks.clear();
         mUserTemporarilyDisabledList.clear();
+        mNonCarrierMergedNetworksStatusTracker.clear();
         mRandomizedMacAddressMapping.clear();
         mScanDetailCaches.clear();
         clearLastSelectedNetwork();
@@ -2906,7 +3070,7 @@
                 removedNetworkIds.add(config.networkId);
                 localLog("clearInternalUserData: removed config."
                         + " netId=" + config.networkId
-                        + " configKey=" + config.getKey());
+                        + " configKey=" + config.getProfileKey());
                 mConfiguredNetworks.remove(config.networkId);
                 for (OnNetworkUpdateListener listener : mListeners) {
                     listener.onNetworkRemoved(
@@ -2918,6 +3082,7 @@
             sendConfiguredNetworkChangedBroadcast(WifiManager.CHANGE_REASON_REMOVED);
         }
         mUserTemporarilyDisabledList.clear();
+        mNonCarrierMergedNetworksStatusTracker.clear();
         mScanDetailCaches.clear();
         clearLastSelectedNetwork();
         return removedNetworkIds;
@@ -2933,9 +3098,25 @@
             List<WifiConfiguration> configurations,
             Map<String, String> macAddressMapping) {
         for (WifiConfiguration configuration : configurations) {
+            if (!WifiConfigurationUtil.validate(
+                    configuration, WifiConfigurationUtil.VALIDATE_FOR_ADD)) {
+                Log.e(TAG, "Skipping malformed network from shared store: " + configuration);
+                continue;
+            }
+
+            WifiConfiguration existingConfiguration = getInternalConfiguredNetwork(configuration);
+            if (null != existingConfiguration) {
+                Log.d(TAG, "Merging network from shared store "
+                        + configuration.getProfileKey());
+                mergeWithInternalWifiConfiguration(existingConfiguration, configuration);
+                continue;
+            }
+
             configuration.networkId = mNextNetworkId++;
+            WifiConfigurationUtil.addUpgradableSecurityTypeIfNecessary(configuration);
             if (mVerboseLoggingEnabled) {
-                Log.v(TAG, "Adding network from shared store " + configuration.getKey());
+                Log.v(TAG, "Adding network from shared store "
+                        + configuration.getProfileKey());
             }
             try {
                 mConfiguredNetworks.put(configuration);
@@ -2954,9 +3135,25 @@
      */
     private void loadInternalDataFromUserStore(List<WifiConfiguration> configurations) {
         for (WifiConfiguration configuration : configurations) {
+            if (!WifiConfigurationUtil.validate(
+                    configuration, WifiConfigurationUtil.VALIDATE_FOR_ADD)) {
+                Log.e(TAG, "Skipping malformed network from user store: " + configuration);
+                continue;
+            }
+
+            WifiConfiguration existingConfiguration = getInternalConfiguredNetwork(configuration);
+            if (null != existingConfiguration) {
+                Log.d(TAG, "Merging network from user store "
+                        + configuration.getProfileKey());
+                mergeWithInternalWifiConfiguration(existingConfiguration, configuration);
+                continue;
+            }
+
             configuration.networkId = mNextNetworkId++;
+            WifiConfigurationUtil.addUpgradableSecurityTypeIfNecessary(configuration);
             if (mVerboseLoggingEnabled) {
-                Log.v(TAG, "Adding network from user store " + configuration.getKey());
+                Log.v(TAG, "Adding network from user store "
+                        + configuration.getProfileKey());
             }
             try {
                 mConfiguredNetworks.put(configuration);
@@ -2972,15 +3169,15 @@
 
     /**
      * Initializes the randomized MAC address for an internal WifiConfiguration depending on
-     * whether it should use aggressive randomization.
+     * whether it should use enhanced randomization.
      * @param config
      */
     private void initRandomizedMacForInternalConfig(WifiConfiguration internalConfig) {
-        MacAddress randomizedMac = shouldUseAggressiveRandomization(internalConfig)
+        MacAddress randomizedMac = shouldUseEnhancedRandomization(internalConfig)
                 ? MacAddressUtils.createRandomUnicastAddress()
                 : getPersistentMacAddress(internalConfig);
         if (randomizedMac != null) {
-            internalConfig.setRandomizedMacAddress(randomizedMac);
+            setRandomizedMacAddress(internalConfig, randomizedMac);
         }
     }
 
@@ -3031,6 +3228,24 @@
     }
 
     /**
+     * Helper method to handle any config store errors on user builds vs other debuggable builds.
+     */
+    private boolean handleConfigStoreFailure(boolean onlyUserStore) {
+        // On eng/userdebug builds, return failure to leave the device in a debuggable state.
+        if (!mBuildProperties.isUserBuild()) return false;
+
+        // On user builds, ignore the failure and let the user create new networks.
+        Log.w(TAG, "Ignoring config store errors on user build");
+        if (!onlyUserStore) {
+            loadInternalData(Collections.emptyList(), Collections.emptyList(),
+                    Collections.emptyMap());
+        } else {
+            loadInternalDataFromUserStore(Collections.emptyList());
+        }
+        return true;
+    }
+
+    /**
      * Read the config store and load the in-memory lists from the store data retrieved and sends
      * out the networks changed broadcast.
      *
@@ -3060,10 +3275,10 @@
             mWifiConfigStore.read();
         } catch (IOException | IllegalStateException e) {
             Log.wtf(TAG, "Reading from new store failed. All saved networks are lost!", e);
-            return false;
+            return handleConfigStoreFailure(false);
         } catch (XmlPullParserException e) {
             Log.wtf(TAG, "XML deserialization of store failed. All saved networks are lost!", e);
-            return false;
+            return handleConfigStoreFailure(false);
         }
         loadInternalData(mNetworkListSharedStoreData.getConfigurations(),
                 mNetworkListUserStoreData.getConfigurations(),
@@ -3095,11 +3310,11 @@
             mWifiConfigStore.switchUserStoresAndRead(userStoreFiles);
         } catch (IOException | IllegalStateException e) {
             Log.wtf(TAG, "Reading from new store failed. All saved private networks are lost!", e);
-            return false;
+            return handleConfigStoreFailure(true);
         } catch (XmlPullParserException e) {
             Log.wtf(TAG, "XML deserialization of store failed. All saved private networks are "
                     + "lost!", e);
-            return false;
+            return handleConfigStoreFailure(true);
         }
         loadInternalDataFromUserStore(mNetworkListUserStoreData.getConfigurations());
         return true;
@@ -3212,6 +3427,7 @@
                 + mContext.getResources().getBoolean(R.bool.config_wifiPnoRecencySortingEnabled));
         mWifiConfigStore.dump(fd, pw, args);
         mWifiCarrierInfoManager.dump(fd, pw, args);
+        mNonCarrierMergedNetworksStatusTracker.dump(fd, pw, args);
     }
 
     /**
@@ -3249,6 +3465,13 @@
     }
 
     /**
+     * Remove the network update event listener
+     */
+    public void removeOnNetworkUpdateListener(OnNetworkUpdateListener listener) {
+        mListeners.remove(listener);
+    }
+
+    /**
      * Set extra failure reason for given config. Used to surface extra failure details to the UI
      * @param netId The network ID of the config to set the extra failure reason for
      * @param reason the WifiConfiguration.ExtraFailureReason failure code representing the most
@@ -3259,7 +3482,12 @@
         if (config == null) {
             return;
         }
-        config.recentFailure.setAssociationStatus(reason);
+        mWifiMetrics.incrementRecentFailureAssociationStatusCount(reason);
+        int previousReason = config.recentFailure.getAssociationStatus();
+        config.recentFailure.setAssociationStatus(reason, mClock.getElapsedSinceBootMillis());
+        if (previousReason != reason) {
+            sendConfiguredNetworkChangedBroadcast(WifiManager.CHANGE_REASON_CONFIG_CHANGE);
+        }
     }
 
     /**
@@ -3274,6 +3502,23 @@
     }
 
     /**
+     * Clear all recent failure reasons that have timed out.
+     */
+    public void cleanupExpiredRecentFailureReasons() {
+        long timeoutDuration = mContext.getResources().getInteger(
+                R.integer.config_wifiRecentFailureReasonExpirationMinutes) * 60 * 1000;
+        for (WifiConfiguration config : getInternalConfiguredNetworks()) {
+            if (config.recentFailure.getAssociationStatus()
+                    != WifiConfiguration.RECENT_FAILURE_NONE
+                    && mClock.getElapsedSinceBootMillis()
+                    >= config.recentFailure.getLastUpdateTimeSinceBootMillis() + timeoutDuration) {
+                config.recentFailure.clear();
+                sendConfiguredNetworkChangedBroadcast(WifiManager.CHANGE_REASON_CONFIG_CHANGE);
+            }
+        }
+    }
+
+    /**
      * Find the highest RSSI among all valid scanDetails in current network's scanDetail cache.
      * If scanDetail is too old, it is not considered to be valid.
      * @param netId The network ID of the config to find scan RSSI
@@ -3300,4 +3545,233 @@
     public Comparator<WifiConfiguration> getScanListComparator() {
         return mScanListComparator;
     }
+
+    /**
+     * This API is called when a connection successfully completes on an existing network
+     * selected by the user. It is not called after the first connection of a newly added network.
+     * Following actions will be triggered:
+     * 1. If this network is disabled, we need re-enable it again.
+     * 2. This network is favored over all the other networks visible in latest network
+     * selection procedure.
+     *
+     * @param netId ID for the network chosen by the user
+     * @param rssi the signal strength of the user selected network
+     * @return true -- There is change made to connection choice of any saved network.
+     * false -- There is no change made to connection choice of any saved network.
+     */
+    private boolean setUserConnectChoice(int netId, int rssi) {
+        localLog("userSelectNetwork: network ID=" + netId);
+        WifiConfiguration selected = getInternalConfiguredNetwork(netId);
+
+        if (selected == null || selected.getProfileKey() == null) {
+            localLog("userSelectNetwork: Invalid configuration with nid=" + netId);
+            return false;
+        }
+
+        // Enable the network if it is disabled.
+        if (!selected.getNetworkSelectionStatus().isNetworkEnabled()) {
+            updateNetworkSelectionStatus(selected,
+                    WifiConfiguration.NetworkSelectionStatus.DISABLED_NONE);
+        }
+        boolean changed = setLegacyUserConnectChoice(selected, rssi);
+        return changed;
+    }
+
+    /**
+     * This maintains the legacy user connect choice state in the config store
+     */
+    private boolean setLegacyUserConnectChoice(@NonNull final WifiConfiguration selected,
+            int rssi) {
+        boolean change = false;
+        Collection<WifiConfiguration> configuredNetworks = getInternalConfiguredNetworks();
+        ArrayList<WifiConfiguration> networksInRange = new ArrayList<>();
+        String key = selected.getProfileKey();
+        for (WifiConfiguration network : configuredNetworks) {
+            WifiConfiguration.NetworkSelectionStatus status = network.getNetworkSelectionStatus();
+            if (network.networkId == selected.networkId) {
+                if (status.getConnectChoice() != null) {
+                    localLog("Remove user selection preference of " + status.getConnectChoice()
+                            + " from " + network.SSID + " : " + network.networkId);
+                    clearConnectChoiceInternal(network);
+                    change = true;
+                }
+                continue;
+            }
+
+            if (status.getSeenInLastQualifiedNetworkSelection()) {
+                setConnectChoiceInternal(network, key, rssi);
+                change = true;
+                networksInRange.add(network);
+            }
+        }
+
+        for (OnNetworkUpdateListener listener : mListeners) {
+            listener.onConnectChoiceSet(networksInRange, key, rssi);
+        }
+        return change;
+    }
+
+    private void clearConnectChoiceInternal(WifiConfiguration config) {
+        config.getNetworkSelectionStatus().setConnectChoice(null);
+        config.getNetworkSelectionStatus().setConnectChoiceRssi(0);
+    }
+
+    private void setConnectChoiceInternal(WifiConfiguration config, String key, int rssi) {
+        config.getNetworkSelectionStatus().setConnectChoice(key);
+        config.getNetworkSelectionStatus().setConnectChoiceRssi(rssi);
+        localLog("Add connect choice key: " + key + " rssi: " + rssi + " to "
+                + WifiNetworkSelector.toNetworkString(config));
+    }
+
+    /** Update WifiConfigManager before connecting to a network. */
+    public void updateBeforeConnect(int networkId, int callingUid) {
+        userEnabledNetwork(networkId);
+        if (!enableNetwork(networkId, true, callingUid, null)
+                || !updateLastConnectUid(networkId, callingUid)) {
+            Log.i(TAG, "connect Allowing uid " + callingUid
+                    + " with insufficient permissions to connect=" + networkId);
+        }
+    }
+
+    /** See {@link WifiManager#save(WifiConfiguration, WifiManager.ActionListener)} */
+    public NetworkUpdateResult updateBeforeSaveNetwork(WifiConfiguration config, int callingUid) {
+        NetworkUpdateResult result = addOrUpdateNetwork(config, callingUid);
+        if (!result.isSuccess()) {
+            Log.e(TAG, "saveNetwork adding/updating config=" + config + " failed");
+            return result;
+        }
+        if (!enableNetwork(result.getNetworkId(), false, callingUid, null)) {
+            Log.e(TAG, "saveNetwork enabling config=" + config + " failed");
+            return NetworkUpdateResult.makeFailed();
+        }
+        return result;
+    }
+
+    /**
+     * Gets the most recent scan result that is newer than maxAgeMillis for each configured network.
+     * @param maxAgeMillis scan results older than this parameter will get filtered out.
+     */
+    public @NonNull List<ScanResult> getMostRecentScanResultsForConfiguredNetworks(
+            int maxAgeMillis) {
+        List<ScanResult> results = new ArrayList<>();
+        long timeNowMs = mClock.getWallClockMillis();
+        for (WifiConfiguration config : getInternalConfiguredNetworks()) {
+            ScanDetailCache scanDetailCache = getScanDetailCacheForNetwork(config.networkId);
+            if (scanDetailCache == null) {
+                continue;
+            }
+            ScanResult scanResult = scanDetailCache.getMostRecentScanResult();
+            if (scanResult == null) {
+                continue;
+            }
+            if (timeNowMs - scanResult.seen < maxAgeMillis) {
+                results.add(scanResult);
+            }
+        }
+        return results;
+    }
+
+    /**
+     * Update the configuration according to transition disable indications.
+     *
+     * @param networkId network ID corresponding to the network.
+     * @param indicationBit transition disable indication bits.
+     * @return true if the network was found, false otherwise.
+     */
+    public boolean updateNetworkTransitionDisable(
+            int networkId,
+            @WifiMonitor.TransitionDisableIndication int indicationBit) {
+        localLog("updateNetworkTransitionDisable: network ID=" + networkId
+                + " indication: " + indicationBit);
+        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
+        if (config == null) {
+            Log.e(TAG, "Cannot find network for " + networkId);
+            return false;
+        }
+        if (0 != (indicationBit & WifiMonitor.TDI_USE_WPA3_PERSONAL)
+                && config.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE)) {
+            config.setSecurityParamsEnabled(WifiConfiguration.SECURITY_TYPE_PSK, false);
+        }
+        if (0 != (indicationBit & WifiMonitor.TDI_USE_SAE_PK)) {
+            config.enableSaePkOnlyMode(true);
+        }
+        if (0 != (indicationBit & WifiMonitor.TDI_USE_WPA3_ENTERPRISE)
+                && config.isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE)) {
+            config.setSecurityParamsEnabled(WifiConfiguration.SECURITY_TYPE_EAP, false);
+        }
+        if (0 != (indicationBit & WifiMonitor.TDI_USE_ENHANCED_OPEN)
+                && config.isSecurityType(WifiConfiguration.SECURITY_TYPE_OWE)) {
+            config.setSecurityParamsEnabled(WifiConfiguration.SECURITY_TYPE_OPEN, false);
+        }
+        return true;
+    }
+
+    /**
+     * Retrieves the configured network corresponding to the provided configKey
+     * without any masking.
+     *
+     * WARNING: Don't use this to pass network configurations except in the wifi stack, when
+     * there is a need for passwords and randomized MAC address.
+     *
+     * @param configKey configKey of the requested network.
+     * @return Copy of WifiConfiguration object if found, null otherwise.
+     */
+    private WifiConfiguration getConfiguredNetworkWithoutMasking(String configKey) {
+        WifiConfiguration config = getInternalConfiguredNetwork(configKey);
+        if (config == null) {
+            return null;
+        }
+        return new WifiConfiguration(config);
+    }
+
+    /**
+     * This method links the config of the provided network id to every linkable saved network.
+     *
+     * @param networkId networkId corresponding to the network to be potentially linked.
+     */
+    public void updateLinkedNetworks(int networkId) {
+        WifiConfiguration internalConfig = getInternalConfiguredNetwork(networkId);
+        if (internalConfig == null) {
+            return;
+        }
+        internalConfig.linkedConfigurations = new HashMap<>();
+        attemptNetworkLinking(internalConfig);
+    }
+
+    /**
+     * This method returns a map containing each config key and unmasked WifiConfiguration of every
+     * network linked to the provided network id.
+     * @param networkId networkId to get the linked configs of.
+     * @return HashMap of config key to unmasked WifiConfiguration
+     */
+    public Map<String, WifiConfiguration> getLinkedNetworksWithoutMasking(int networkId) {
+        WifiConfiguration internalConfig = getInternalConfiguredNetwork(networkId);
+        if (internalConfig == null) {
+            return null;
+        }
+
+        Map<String, WifiConfiguration> linkedNetworks = new HashMap<>();
+        Map<String, Integer> linkedConfigurations = internalConfig.linkedConfigurations;
+        if (linkedConfigurations == null) {
+            return null;
+        }
+        for (String configKey : linkedConfigurations.keySet()) {
+            linkedNetworks.put(configKey, getConfiguredNetworkWithoutMasking(configKey));
+        }
+        return linkedNetworks;
+    }
+
+    /**
+     * This method updates FILS AKMs to the internal network.
+     *
+     * @param networkId networkId corresponding to the network to be updated.
+     */
+    public void updateFilsAkms(int networkId,
+            boolean isFilsSha256Supported, boolean isFilsSha384Supported) {
+        WifiConfiguration internalConfig = getInternalConfiguredNetwork(networkId);
+        if (internalConfig == null) {
+            return;
+        }
+        internalConfig.enableFils(isFilsSha256Supported, isFilsSha384Supported);
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiConfigStore.java b/service/java/com/android/server/wifi/WifiConfigStore.java
index 2853bba..34b148e 100644
--- a/service/java/com/android/server/wifi/WifiConfigStore.java
+++ b/service/java/com/android/server/wifi/WifiConfigStore.java
@@ -61,6 +61,7 @@
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -758,7 +759,7 @@
             // There can only be 1 store data matching the tag, O indicates a previous StoreData
             // module that no longer exists (ignore this XML section).
             StoreData storeData = storeDataList.stream()
-                    .filter(s -> s.getName().equals(headerName[0]))
+                    .filter(s -> s.getSectionsToParse().contains(headerName[0]))
                     .findAny()
                     .orElse(null);
             if (storeData == null) {
@@ -766,8 +767,8 @@
                         + storeDataList);
                 continue;
             }
-            storeData.deserializeData(in, rootTagDepth + 1, version,
-                    storeFile.getEncryptionUtil());
+            storeData.deserializeDataForSection(in, rootTagDepth + 1, version,
+                    storeFile.getEncryptionUtil(), headerName[0]);
             storeDatasInvoked.add(storeData);
         }
         // Inform all the other registered store data clients that there is nothing in the store
@@ -813,6 +814,7 @@
         pw.println("Dump of WifiConfigStore");
         pw.println("WifiConfigStore - Store File Begin ----");
         Stream.of(mSharedStores, mUserStores)
+                .filter(Objects::nonNull)
                 .flatMap(List::stream)
                 .forEach((storeFile) -> {
                     pw.print("Name: " + storeFile.mFileName);
@@ -990,6 +992,16 @@
                 throws XmlPullParserException, IOException;
 
         /**
+         * By default we will call the default deserializeData function. If some module needs to
+         * parse data with non-default structure(for migration purposes), then override this method.
+         */
+        default void deserializeDataForSection(@Nullable XmlPullParser in, int outerTagDepth,
+                @Version int version, @Nullable WifiConfigStoreEncryptionUtil encryptionUtil,
+                @NonNull String sectionName) throws XmlPullParserException, IOException {
+            deserializeData(in, outerTagDepth, version, encryptionUtil);
+        }
+
+        /**
          * Reset configuration data.
          */
         void resetData();
@@ -1010,6 +1022,16 @@
         String getName();
 
         /**
+         * By default, we parse the section the module writes. If some module needs to parse other
+         * sections (for migration purposes), then override this method.
+         * @return a set of section headers
+         */
+        default HashSet<String> getSectionsToParse() {
+            //
+            return new HashSet<String>() {{ add(getName()); }};
+        }
+
+        /**
          * File Id where this data needs to be written to.
          * This should be one of {@link #STORE_FILE_SHARED_GENERAL},
          * {@link #STORE_FILE_USER_GENERAL} or
diff --git a/service/java/com/android/server/wifi/WifiConfigurationUtil.java b/service/java/com/android/server/wifi/WifiConfigurationUtil.java
index 55a8c00..5e0db72 100644
--- a/service/java/com/android/server/wifi/WifiConfigurationUtil.java
+++ b/service/java/com/android/server/wifi/WifiConfigurationUtil.java
@@ -16,15 +16,22 @@
 
 package com.android.server.wifi;
 
+import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_EAP;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE;
 import static android.net.wifi.WifiManager.ALL_ZEROS_MAC_ADDRESS;
 
 import static com.android.server.wifi.util.NativeUtil.addEnclosingQuotes;
 
+import android.annotation.SuppressLint;
 import android.net.IpConfiguration;
 import android.net.MacAddress;
 import android.net.StaticIpConfiguration;
+import android.net.wifi.SecurityParams;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
 import android.net.wifi.WifiNetworkSpecifier;
 import android.net.wifi.WifiScanner;
 import android.os.PatternMatcher;
@@ -33,13 +40,16 @@
 import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.util.NativeUtil;
 
 import java.nio.charset.StandardCharsets;
 import java.security.cert.X509Certificate;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.BitSet;
 import java.util.Comparator;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -63,6 +73,8 @@
     private static final int SAE_ASCII_MIN_LEN = 1 + ENCLOSING_QUOTES_LEN;
     private static final int PSK_SAE_ASCII_MAX_LEN = 63 + ENCLOSING_QUOTES_LEN;
     private static final int PSK_SAE_HEX_LEN = 64;
+    private static final int WEP104_KEY_BYTES_LEN = 13;
+    private static final int WEP40_KEY_BYTES_LEN = 5;
 
     @VisibleForTesting
     public static final String PASSWORD_MASK = "*";
@@ -72,6 +84,9 @@
     private static final Pair<MacAddress, MacAddress> MATCH_ALL_BSSID_PATTERN =
             new Pair<>(ALL_ZEROS_MAC_ADDRESS, ALL_ZEROS_MAC_ADDRESS);
 
+    private static final int NETWORK_ID_SECURITY_MASK = 0xff;
+    private static final int NETWORK_ID_SECURITY_OFFSET = 23;
+
     /**
      * Checks if the provided |wepKeys| array contains any non-null value;
      */
@@ -88,58 +103,64 @@
      * Helper method to check if the provided |config| corresponds to a PSK network or not.
      */
     public static boolean isConfigForPskNetwork(WifiConfiguration config) {
-        return config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK);
+        return config.isSecurityType(WifiConfiguration.SECURITY_TYPE_PSK);
     }
 
     /**
      * Helper method to check if the provided |config| corresponds to a WAPI PSK network or not.
      */
     public static boolean isConfigForWapiPskNetwork(WifiConfiguration config) {
-        return config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WAPI_PSK);
+        return config.isSecurityType(WifiConfiguration.SECURITY_TYPE_WAPI_PSK);
     }
 
     /**
      * Helper method to check if the provided |config| corresponds to a WAPI CERT network or not.
      */
     public static boolean isConfigForWapiCertNetwork(WifiConfiguration config) {
-        return config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WAPI_CERT);
+        return config.isSecurityType(WifiConfiguration.SECURITY_TYPE_WAPI_CERT);
     }
 
     /**
      * Helper method to check if the provided |config| corresponds to an SAE network or not.
      */
     public static boolean isConfigForSaeNetwork(WifiConfiguration config) {
-        return config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE);
+        return config.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE);
     }
 
     /**
      * Helper method to check if the provided |config| corresponds to an OWE network or not.
      */
     public static boolean isConfigForOweNetwork(WifiConfiguration config) {
-        return config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE);
+        return config.isSecurityType(WifiConfiguration.SECURITY_TYPE_OWE);
     }
 
     /**
      * Helper method to check if the provided |config| corresponds to a EAP network or not.
      */
     public static boolean isConfigForEapNetwork(WifiConfiguration config) {
-        return (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP)
-                || config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X));
+        return config.isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP);
+    }
+
+    /**
+     * Helper method to check if the provided |config| corresponds to
+     * a WPA3 Enterprise network or not.
+     */
+    public static boolean isConfigForWpa3EnterpriseNetwork(WifiConfiguration config) {
+        return config.isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
     }
 
     /**
      * Helper method to check if the provided |config| corresponds to a EAP Suite-B network or not.
      */
-    public static boolean isConfigForEapSuiteBNetwork(WifiConfiguration config) {
-        return config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SUITE_B_192);
+    public static boolean isConfigForWpa3Enterprise192BitNetwork(WifiConfiguration config) {
+        return config.isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT);
     }
 
     /**
      * Helper method to check if the provided |config| corresponds to a WEP network or not.
      */
     public static boolean isConfigForWepNetwork(WifiConfiguration config) {
-        return (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)
-                && hasAnyValidWepKey(config.wepKeys));
+        return config.isSecurityType(WifiConfiguration.SECURITY_TYPE_WEP);
     }
 
     /**
@@ -148,8 +169,9 @@
      */
     public static boolean isConfigForOpenNetwork(WifiConfiguration config) {
         return (!(isConfigForWepNetwork(config) || isConfigForPskNetwork(config)
+                || isConfigForWapiPskNetwork(config) || isConfigForWapiCertNetwork(config)
                 || isConfigForEapNetwork(config) || isConfigForSaeNetwork(config)
-                || isConfigForEapSuiteBNetwork(config)));
+                || isConfigForWpa3Enterprise192BitNetwork(config)));
     }
 
     /**
@@ -203,7 +225,7 @@
     public static boolean hasMacRandomizationSettingsChanged(WifiConfiguration existingConfig,
             WifiConfiguration newConfig) {
         if (existingConfig == null) {
-            return newConfig.macRandomizationSetting != WifiConfiguration.RANDOMIZATION_PERSISTENT;
+            return newConfig.macRandomizationSetting != WifiConfiguration.RANDOMIZATION_AUTO;
         }
         return newConfig.macRandomizationSetting != existingConfig.macRandomizationSetting;
     }
@@ -262,6 +284,10 @@
                     existingEnterpriseConfig.getAltSubjectMatch())) {
                 return true;
             }
+            if (!TextUtils.equals(newEnterpriseConfig.getWapiCertSuite(),
+                    existingEnterpriseConfig.getWapiCertSuite())) {
+                return true;
+            }
             if (newEnterpriseConfig.getOcsp() != existingEnterpriseConfig.getOcsp()) {
                 return true;
             }
@@ -311,6 +337,9 @@
                 newConfig.allowedSuiteBCiphers)) {
             return true;
         }
+        if (!existingConfig.getSecurityParamsList().equals(newConfig.getSecurityParamsList())) {
+            return true;
+        }
         if (!Objects.equals(existingConfig.preSharedKey, newConfig.preSharedKey)) {
             return true;
         }
@@ -400,6 +429,8 @@
             Log.e(TAG, "validateBssid failed: empty string");
             return false;
         }
+        // Allow reset of bssid with "any".
+        if (bssid.equals(ClientModeImpl.SUPPLICANT_BSSID_ANY)) return true;
         MacAddress bssidMacAddress;
         try {
             bssidMacAddress = MacAddress.fromString(bssid);
@@ -470,6 +501,55 @@
         return true;
     }
 
+    private static boolean validateWepKeys(String[] wepKeys, int wepTxKeyIndex, boolean isAdd) {
+        if (isAdd) {
+            if (wepKeys == null) {
+                Log.e(TAG, "validateWepKeys: null string");
+                return false;
+            }
+        } else {
+            if (wepKeys == null) {
+                // This is an update, so the psk can be null if that is not being changed.
+                return true;
+            } else {
+                boolean allMaskedKeys = true;
+                for (int i = 0; i < wepKeys.length; i++) {
+                    if (wepKeys[i] != null && !TextUtils.equals(wepKeys[i], PASSWORD_MASK)) {
+                        allMaskedKeys = false;
+                    }
+                }
+                if (allMaskedKeys) {
+                    // This is an update, so the app might have returned back the masked password,
+                    // let it thru. WifiConfigManager will handle it.
+                    return true;
+                }
+            }
+        }
+        for (int i = 0; i < wepKeys.length; i++) {
+            if (wepKeys[i] != null) {
+                try {
+                    ArrayList<Byte> wepKeyBytes =
+                            NativeUtil.hexOrQuotedStringToBytes(wepKeys[i]);
+                    if (wepKeyBytes.size() != WEP40_KEY_BYTES_LEN
+                            && wepKeyBytes.size() != WEP104_KEY_BYTES_LEN) {
+                        Log.e(TAG, "validateWepKeys: invalid wep key length "
+                                + wepKeys[i].length() + " at index " + i);
+                        return false;
+                    }
+                } catch (IllegalArgumentException e) {
+                    Log.e(TAG, "validateWepKeys: invalid wep key at index " + i, e);
+                    return false;
+                }
+            }
+        }
+        if (wepTxKeyIndex >= wepKeys.length) {
+            Log.e(TAG, "validateWepKeys: invalid wep tx key index " + wepTxKeyIndex
+                    + " wepKeys len: " + wepKeys.length);
+            return false;
+        }
+        return true;
+    }
+
     private static boolean validateBitSet(BitSet bitSet, int validValuesLength) {
         if (bitSet == null) return false;
         BitSet clonedBitset = (BitSet) bitSet.clone();
@@ -531,8 +611,11 @@
                 Log.e(TAG, "validateKeyMgmt failed: not PSK or 8021X");
                 return false;
             }
+            // SUITE-B keymgmt must be WPA_EAP + IEEE8021X + SUITE_B_192.
             if (keyMgmnt.cardinality() == 3
-                    && !keyMgmnt.get(WifiConfiguration.KeyMgmt.SUITE_B_192)) {
+                    && !(keyMgmnt.get(WifiConfiguration.KeyMgmt.WPA_EAP)
+                            && keyMgmnt.get(WifiConfiguration.KeyMgmt.IEEE8021X)
+                            && keyMgmnt.get(WifiConfiguration.KeyMgmt.SUITE_B_192))) {
                 Log.e(TAG, "validateKeyMgmt failed: not SUITE_B_192");
                 return false;
             }
@@ -599,39 +682,28 @@
         if (!validateKeyMgmt(config.allowedKeyManagement)) {
             return false;
         }
-        if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)
+        if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_WEP)
+                && config.wepKeys != null
+                && !validateWepKeys(config.wepKeys, config.wepTxKeyIndex, isAdd)) {
+            return false;
+        }
+        if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_PSK)
                 && !validatePassword(config.preSharedKey, isAdd, false)) {
             return false;
         }
-        if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
-            // PMF mandatory for OWE networks
-            if (!config.requirePmf) {
-                Log.e(TAG, "PMF must be enabled for OWE networks");
-                return false;
-            }
+        if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE)
+                && !validatePassword(config.preSharedKey, isAdd, true)) {
+            return false;
         }
-        if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
-            // PMF mandatory for WPA3-Personal networks
-            if (!config.requirePmf) {
-                Log.e(TAG, "PMF must be enabled for SAE networks");
-                return false;
-            }
-            if (!validatePassword(config.preSharedKey, isAdd, true)) {
-                return false;
-            }
+
+        if (!validateEnterpriseConfig(config, isAdd)) {
+            return false;
         }
-        if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SUITE_B_192)) {
-            // PMF mandatory for WPA3-Enterprise networks
-            if (!config.requirePmf) {
-                Log.e(TAG, "PMF must be enabled for Suite-B 192-bit networks");
-                return false;
-            }
-        }
+
         // b/153435438: Added to deal with badly formed WifiConfiguration from apps.
         if (config.preSharedKey != null && !config.needsPreSharedKey()) {
             Log.e(TAG, "preSharedKey set with an invalid KeyMgmt, resetting KeyMgmt to WPA_PSK");
-            config.allowedKeyManagement.clear();
-            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+            config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
         }
         if (!validateIpConfiguration(config.getIpConfiguration())) {
             return false;
@@ -655,7 +727,6 @@
                     + baseAddress);
             return false;
         }
-        // TBD: Can we do any more checks?
         return true;
     }
 
@@ -697,6 +768,12 @@
         return false;
     }
 
+    // TODO: b/177434707 calls inside same module are safe
+    @SuppressLint("NewApi")
+    private static int getBand(WifiNetworkSpecifier s) {
+        return s.getBand();
+    }
+
     /**
      * Validate the configuration received from an external application inside
      * {@link WifiNetworkSpecifier}.
@@ -704,15 +781,16 @@
      * This method checks for the following parameters:
      * 1. {@link WifiNetworkSpecifier#ssidPatternMatcher}
      * 2. {@link WifiNetworkSpecifier#bssidPatternMatcher}
-     * 3. {@link WifiConfiguration#SSID}
-     * 4. {@link WifiConfiguration#BSSID}
-     * 5. {@link WifiConfiguration#preSharedKey}
-     * 6. {@link WifiConfiguration#allowedKeyManagement}
-     * 7. {@link WifiConfiguration#allowedProtocols}
-     * 8. {@link WifiConfiguration#allowedAuthAlgorithms}
-     * 9. {@link WifiConfiguration#allowedGroupCiphers}
-     * 10. {@link WifiConfiguration#allowedPairwiseCiphers}
-     * 11. {@link WifiConfiguration#getIpConfiguration()}
+     * 3. {@link WifiNetworkSpecifier#getBand()}
+     * 4. {@link WifiConfiguration#SSID}
+     * 5. {@link WifiConfiguration#BSSID}
+     * 6. {@link WifiConfiguration#preSharedKey}
+     * 7. {@link WifiConfiguration#allowedKeyManagement}
+     * 8. {@link WifiConfiguration#allowedProtocols}
+     * 9. {@link WifiConfiguration#allowedAuthAlgorithms}
+     * 10. {@link WifiConfiguration#allowedGroupCiphers}
+     * 11. {@link WifiConfiguration#allowedPairwiseCiphers}
+     * 12. {@link WifiConfiguration#getIpConfiguration()}
      *
      * @param specifier Instance of {@link WifiNetworkSpecifier}.
      * @return true if the parameters are valid, false otherwise.
@@ -730,6 +808,9 @@
             Log.e(TAG, "validateNetworkSpecifier failed : match-all specifier");
             return false;
         }
+        if (!WifiNetworkSpecifier.validateBand(getBand(specifier))) {
+            return false;
+        }
         WifiConfiguration config = specifier.wifiConfiguration;
         if (specifier.ssidPatternMatcher.getType() == PatternMatcher.PATTERN_LITERAL) {
             // For literal SSID matches, the value should satisfy SSID requirements.
@@ -760,30 +841,13 @@
         if (!validateKeyMgmt(config.allowedKeyManagement)) {
             return false;
         }
-        if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)
+        if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_PSK)
                 && !validatePassword(config.preSharedKey, true, false)) {
             return false;
         }
-        if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
-            // PMF mandatory for OWE networks
-            if (!config.requirePmf) {
-                return false;
-            }
-        }
-        if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
-            // PMF mandatory for WPA3-Personal networks
-            if (!config.requirePmf) {
-                return false;
-            }
-            if (!validatePassword(config.preSharedKey, true, true)) {
-                return false;
-            }
-        }
-        if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SUITE_B_192)) {
-            // PMF mandatory for WPA3-Enterprise networks
-            if (!config.requirePmf) {
-                return false;
-            }
+        if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE)
+                && !validatePassword(config.preSharedKey, true, true)) {
+            return false;
         }
         // TBD: Validate some enterprise params as well in the future here.
         return true;
@@ -812,6 +876,10 @@
         if (!Objects.equals(config.SSID, config1.SSID)) {
             return false;
         }
+        if (!Objects.equals(config.getNetworkSelectionStatus().getCandidateSecurityParams(),
+                config1.getNetworkSelectionStatus().getCandidateSecurityParams())) {
+            return false;
+        }
         if (WifiConfigurationUtil.hasCredentialChanged(config, config1)) {
             return false;
         }
@@ -833,10 +901,9 @@
         }
         pnoNetwork.flags |= WifiScanner.PnoSettings.PnoNetwork.FLAG_A_BAND;
         pnoNetwork.flags |= WifiScanner.PnoSettings.PnoNetwork.FLAG_G_BAND;
-        if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
+        if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_PSK)) {
             pnoNetwork.authBitField |= WifiScanner.PnoSettings.PnoNetwork.AUTH_CODE_PSK;
-        } else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP)
-                || config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X)) {
+        } else if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP)) {
             pnoNetwork.authBitField |= WifiScanner.PnoSettings.PnoNetwork.AUTH_CODE_EAPOL;
         } else {
             pnoNetwork.authBitField |= WifiScanner.PnoSettings.PnoNetwork.AUTH_CODE_OPEN;
@@ -844,6 +911,97 @@
         return pnoNetwork;
     }
 
+    private static void addOpenUpgradableSecurityTypeIfNecessary(WifiConfiguration config) {
+        if (!config.isSecurityType(WifiConfiguration.SECURITY_TYPE_OPEN)) return;
+        if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_OWE)) return;
+
+        Log.d(TAG, "Add upgradable OWE configuration.");
+        SecurityParams oweParams = SecurityParams.createSecurityParamsBySecurityType(
+                WifiConfiguration.SECURITY_TYPE_OWE);
+        oweParams.setIsAddedByAutoUpgrade(true);
+        config.addSecurityParams(oweParams);
+    }
+
+    private static void addPskUpgradableSecurityTypeIfNecessary(WifiConfiguration config) {
+        if (!config.isSecurityType(WifiConfiguration.SECURITY_TYPE_PSK)) return;
+        if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE)) return;
+
+        Log.d(TAG, "Add upgradable SAE configuration.");
+        SecurityParams saeParams = SecurityParams.createSecurityParamsBySecurityType(
+                WifiConfiguration.SECURITY_TYPE_SAE);
+        saeParams.setIsAddedByAutoUpgrade(true);
+        config.addSecurityParams(saeParams);
+    }
+
+    private static void addEapUpgradableSecurityTypeIfNecessary(WifiConfiguration config) {
+        if (!config.isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP)) return;
+        if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE)) return;
+
+        Log.d(TAG, "Add upgradable Enterprise configuration.");
+        SecurityParams wpa3EnterpriseParams = SecurityParams.createSecurityParamsBySecurityType(
+                WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
+        wpa3EnterpriseParams.setIsAddedByAutoUpgrade(true);
+        config.addSecurityParams(wpa3EnterpriseParams);
+    }
+
+    /**
+     * Add upgradable securit type to the given wifi configuration.
+     *
+     * @param config the wifi configuration to be checked.
+     */
+    public static boolean addUpgradableSecurityTypeIfNecessary(WifiConfiguration config) {
+        try {
+            addOpenUpgradableSecurityTypeIfNecessary(config);
+            addPskUpgradableSecurityTypeIfNecessary(config);
+            addEapUpgradableSecurityTypeIfNecessary(config);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Failed to add upgradable security type");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * For a upgradable type which is added by the auto-upgrade mechenism, it is only
+     * matched when corresponding auto-upgrade features are enabled.
+     */
+    private static boolean shouldOmitAutoUpgradeParams(SecurityParams params) {
+        if (!params.isAddedByAutoUpgrade()) return false;
+
+        WifiGlobals wifiGlobals = WifiInjector.getInstance().getWifiGlobals();
+
+        if (params.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE)) {
+            return !wifiGlobals.isWpa3SaeUpgradeEnabled();
+        }
+        if (params.isSecurityType(WifiConfiguration.SECURITY_TYPE_OWE)) {
+            return !wifiGlobals.isOweUpgradeEnabled();
+        }
+        return false;
+    }
+
+    private static boolean isSecurityParamsSupported(SecurityParams params) {
+        final long wifiFeatures = WifiInjector.getInstance()
+                .getActiveModeWarden().getPrimaryClientModeManager()
+                .getSupportedFeatures();
+        switch (params.getSecurityType()) {
+            case WifiConfiguration.SECURITY_TYPE_SAE:
+                return 0 != (wifiFeatures & WifiManager.WIFI_FEATURE_WPA3_SAE);
+            case WifiConfiguration.SECURITY_TYPE_OWE:
+                return 0 != (wifiFeatures & WifiManager.WIFI_FEATURE_OWE);
+        }
+        return true;
+    }
+
+    /**
+     * Check the security params is valid or not.
+     * @param params the requesting security params.
+     * @return true if it's valid; otherwise false.
+     */
+    public static boolean isSecurityParamsValid(SecurityParams params) {
+        if (!params.isEnabled()) return false;
+        if (!isSecurityParamsSupported(params)) return false;
+        return true;
+    }
 
     /**
      * General WifiConfiguration list sorting algorithm:
@@ -891,4 +1049,155 @@
             }
         }
     }
+
+    /**
+     * Convert multi-type configurations to a list of configurations with a single security type,
+     * where a configuration with multiple security configurations will be converted to multiple
+     * Wi-Fi configurations with a single security type..
+     *
+     * @param configs the list of multi-type configurations.
+     * @return a list of Wi-Fi configurations with a single security type,
+     *         that may contain multiple configurations with the same network ID.
+     */
+    public static List<WifiConfiguration> convertMultiTypeConfigsToLegacyConfigs(
+            List<WifiConfiguration> configs) {
+        List<WifiConfiguration> legacyConfigs = new ArrayList<>();
+        for (WifiConfiguration config : configs) {
+            boolean wpa2EnterpriseAdded = false;
+            WifiConfiguration wpa3EnterpriseConfig = null;
+            for (SecurityParams params: config.getSecurityParamsList()) {
+                if (!params.isEnabled()) continue;
+                if (shouldOmitAutoUpgradeParams(params)) continue;
+                WifiConfiguration legacyConfig = new WifiConfiguration(config);
+                legacyConfig.setSecurityParams(params);
+                legacyConfig.networkId = addSecurityTypeToNetworkId(
+                        legacyConfig.networkId,
+                        params.getSecurityType());
+                int securityType = params.getSecurityType();
+                if (securityType == SECURITY_TYPE_EAP) {
+                    wpa2EnterpriseAdded = true;
+                } else if (securityType == SECURITY_TYPE_EAP_WPA3_ENTERPRISE) {
+                    wpa3EnterpriseConfig = legacyConfig;
+                    continue;
+                }
+                legacyConfigs.add(legacyConfig);
+            }
+            if (wpa3EnterpriseConfig != null && (SdkLevel.isAtLeastS() || !wpa2EnterpriseAdded)) {
+                // R Wifi settings maps WPA3-Enterprise to the same security type as
+                // WPA2-Enterprise, which causes a DuplicateKeyException. For R, we should only
+                // return the WPA2-Enterprise config if we have both.
+                legacyConfigs.add(wpa3EnterpriseConfig);
+            }
+        }
+        return legacyConfigs;
+    }
+
+    /**
+     * Converts WifiInfo.SecurityType to WifiConfiguration.SecurityType
+     */
+    public static
+            @WifiConfiguration.SecurityType int convertWifiInfoSecurityTypeToWifiConfiguration(
+                    @WifiInfo.SecurityType int securityType) {
+        switch (securityType) {
+            case WifiInfo.SECURITY_TYPE_OPEN:
+                return WifiConfiguration.SECURITY_TYPE_OPEN;
+            case WifiInfo.SECURITY_TYPE_WEP:
+                return WifiConfiguration.SECURITY_TYPE_WEP;
+            case WifiInfo.SECURITY_TYPE_PSK:
+                return WifiConfiguration.SECURITY_TYPE_PSK;
+            case WifiInfo.SECURITY_TYPE_EAP:
+                return WifiConfiguration.SECURITY_TYPE_EAP;
+            case WifiInfo.SECURITY_TYPE_SAE:
+                return WifiConfiguration.SECURITY_TYPE_SAE;
+            case WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT:
+                return WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT;
+            case WifiInfo.SECURITY_TYPE_OWE:
+                return WifiConfiguration.SECURITY_TYPE_OWE;
+            case WifiInfo.SECURITY_TYPE_WAPI_PSK:
+                return WifiConfiguration.SECURITY_TYPE_WAPI_PSK;
+            case WifiInfo.SECURITY_TYPE_WAPI_CERT:
+                return WifiConfiguration.SECURITY_TYPE_WAPI_CERT;
+            case WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE:
+                return WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE;
+            case WifiInfo.SECURITY_TYPE_OSEN:
+                return WifiConfiguration.SECURITY_TYPE_OSEN;
+            case WifiInfo.SECURITY_TYPE_PASSPOINT_R1_R2:
+                return WifiConfiguration.SECURITY_TYPE_PASSPOINT_R1_R2;
+            case WifiInfo.SECURITY_TYPE_PASSPOINT_R3:
+                return WifiConfiguration.SECURITY_TYPE_PASSPOINT_R3;
+            default:
+                return -1;
+        }
+    }
+
+    /**
+     * Adds a WifiConfiguration.SecurityType value to a network ID to differentiate the network IDs
+     * of legacy single-type configurations derived from the same multi-type configuration.
+     *
+     * This method only works for SDK levels less than S, since those callers may expect a unique
+     * network ID for each single-type configuration. For SDK level S and above, this method returns
+     * the network ID as-is.
+     *
+     * @param netId network id to add the security type to
+     * @param securityType WifiConfiguration security type to encode
+     * @return network id with security type encoded in it
+     */
+    public static int addSecurityTypeToNetworkId(
+            int netId, @WifiConfiguration.SecurityType int securityType) {
+        if (netId == INVALID_NETWORK_ID || SdkLevel.isAtLeastS()) {
+            return netId;
+        }
+        return removeSecurityTypeFromNetworkId(netId)
+                | ((securityType & NETWORK_ID_SECURITY_MASK) << NETWORK_ID_SECURITY_OFFSET);
+    }
+
+    /**
+     * Removes the security type value of a network ID to have it match with internal network IDs.
+     *
+     * This method only works for SDK levels less than S. It should be used on network ID passed in
+     * from external callers, since those callers are be exposed to network IDs with an embedded
+     * security type value. For SDK levels S and above, this method returns the network ID as-is.
+     *
+     * @param netId network id to remove the security type from
+     * @return network id with the security type removed
+     */
+    public static int removeSecurityTypeFromNetworkId(int netId) {
+        if (netId == INVALID_NETWORK_ID || SdkLevel.isAtLeastS()) {
+            return netId;
+        }
+        return netId & ~(NETWORK_ID_SECURITY_MASK << NETWORK_ID_SECURITY_OFFSET);
+    }
+
+    private static boolean validateEnterpriseConfig(WifiConfiguration config, boolean isAdd) {
+        if ((config.isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP)
+                || config.isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE))
+                && !config.isEnterprise()) {
+            return false;
+        }
+        if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT)
+                && (!config.isEnterprise()
+                || config.enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.TLS)) {
+            return false;
+        }
+        if (config.isEnterprise()) {
+            if (config.enterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.PEAP
+                    || config.enterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.TTLS) {
+
+                int phase2Method = config.enterpriseConfig.getPhase2Method();
+                if (phase2Method == WifiEnterpriseConfig.Phase2.MSCHAP
+                        || phase2Method == WifiEnterpriseConfig.Phase2.MSCHAPV2
+                        || phase2Method == WifiEnterpriseConfig.Phase2.PAP
+                        || phase2Method == WifiEnterpriseConfig.Phase2.GTC) {
+                    // Check the password on add only. When updating, the password may not be
+                    // available and it appears as "(Unchanged)" in Settings
+                    if ((isAdd && TextUtils.isEmpty(config.enterpriseConfig.getPassword()))
+                            || TextUtils.isEmpty(config.enterpriseConfig.getIdentity())) {
+                        Log.e(TAG, "Enterprise network without an identity or a password set");
+                        return false;
+                    }
+                }
+            }
+        }
+        return true;
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiConnectivityHelper.java b/service/java/com/android/server/wifi/WifiConnectivityHelper.java
index 4fb0d5d..4634c6d 100644
--- a/service/java/com/android/server/wifi/WifiConnectivityHelper.java
+++ b/service/java/com/android/server/wifi/WifiConnectivityHelper.java
@@ -35,13 +35,13 @@
     private static final String TAG = "WifiConnectivityHelper";
     @VisibleForTesting
     public static int INVALID_LIST_SIZE = -1;
-    private final WifiNative mWifiNative;
+    private final WifiInjector mWifiInjector;
     private boolean mFirmwareRoamingSupported = false;
-    private int mMaxNumBlacklistBssid = INVALID_LIST_SIZE;
-    private int mMaxNumWhitelistSsid = INVALID_LIST_SIZE;
+    private int mMaxNumBlocklistBssid = INVALID_LIST_SIZE;
+    private int mMaxNumAllowlistSsid = INVALID_LIST_SIZE;
 
-    WifiConnectivityHelper(WifiNative wifiNative) {
-        mWifiNative = wifiNative;
+    WifiConnectivityHelper(WifiInjector wifiInjector) {
+        mWifiInjector = wifiInjector;
     }
 
     /**
@@ -56,11 +56,12 @@
      */
     public boolean getFirmwareRoamingInfo() {
         mFirmwareRoamingSupported = false;
-        mMaxNumBlacklistBssid = INVALID_LIST_SIZE;
-        mMaxNumWhitelistSsid = INVALID_LIST_SIZE;
+        mMaxNumBlocklistBssid = INVALID_LIST_SIZE;
+        mMaxNumAllowlistSsid = INVALID_LIST_SIZE;
 
-        long fwFeatureSet =
-                mWifiNative.getSupportedFeatureSet(mWifiNative.getClientInterfaceName());
+        ClientModeManager primaryManager =
+                mWifiInjector.getActiveModeWarden().getPrimaryClientModeManager();
+        long fwFeatureSet = primaryManager.getSupportedFeatures();
         Log.d(TAG, "Firmware supported feature set: " + Long.toHexString(fwFeatureSet));
 
         if ((fwFeatureSet & WIFI_FEATURE_CONTROL_ROAMING) == 0) {
@@ -68,19 +69,19 @@
             return true;
         }
 
-        WifiNative.RoamingCapabilities roamingCap = new WifiNative.RoamingCapabilities();
-        if (mWifiNative.getRoamingCapabilities(mWifiNative.getClientInterfaceName(), roamingCap)) {
+        WifiNative.RoamingCapabilities roamingCap = primaryManager.getRoamingCapabilities();
+        if (roamingCap != null) {
             if (roamingCap.maxBlocklistSize < 0 || roamingCap.maxAllowlistSize < 0) {
-                Log.e(TAG, "Invalid firmware roaming capabilities: max num blacklist bssid="
-                        + roamingCap.maxBlocklistSize + " max num whitelist ssid="
+                Log.e(TAG, "Invalid firmware roaming capabilities: max num blocklist bssid="
+                        + roamingCap.maxBlocklistSize + " max num allowlist ssid="
                         + roamingCap.maxAllowlistSize);
             } else {
                 mFirmwareRoamingSupported = true;
-                mMaxNumBlacklistBssid = roamingCap.maxBlocklistSize;
-                mMaxNumWhitelistSsid = roamingCap.maxAllowlistSize;
-                Log.d(TAG, "Firmware roaming supported with capabilities: max num blacklist bssid="
-                        + mMaxNumBlacklistBssid + " max num whitelist ssid="
-                        + mMaxNumWhitelistSsid);
+                mMaxNumBlocklistBssid = roamingCap.maxBlocklistSize;
+                mMaxNumAllowlistSsid = roamingCap.maxAllowlistSize;
+                Log.d(TAG, "Firmware roaming supported with capabilities: max num blocklist bssid="
+                        + mMaxNumBlocklistBssid + " max num allowlist ssid="
+                        + mMaxNumAllowlistSsid);
                 return true;
             }
         } else {
@@ -98,31 +99,31 @@
     }
 
     /**
-     * Get the maximum size of BSSID blacklist firmware supports.
+     * Get the maximum size of BSSID blocklist firmware supports.
      *
      * @return INVALID_LIST_SIZE if firmware roaming is not supported, or
-     * maximum size of the BSSID blacklist firmware supports.
+     * maximum size of the BSSID blocklist firmware supports.
      */
-    public int getMaxNumBlacklistBssid() {
+    public int getMaxNumBlocklistBssid() {
         if (mFirmwareRoamingSupported) {
-            return mMaxNumBlacklistBssid;
+            return mMaxNumBlocklistBssid;
         } else {
-            Log.e(TAG, "getMaxNumBlacklistBssid: Firmware roaming is not supported");
+            Log.e(TAG, "getMaxNumBlocklistBssid: Firmware roaming is not supported");
             return INVALID_LIST_SIZE;
         }
     }
 
     /**
-     * Get the maximum size of SSID whitelist firmware supports.
+     * Get the maximum size of SSID allowlist firmware supports.
      *
      * @return INVALID_LIST_SIZE if firmware roaming is not supported, or
-     * maximum size of the SSID whitelist firmware supports.
+     * maximum size of the SSID allowlist firmware supports.
      */
-    public int getMaxNumWhitelistSsid() {
+    public int getMaxNumAllowlistSsid() {
         if (mFirmwareRoamingSupported) {
-            return mMaxNumWhitelistSsid;
+            return mMaxNumAllowlistSsid;
         } else {
-            Log.e(TAG, "getMaxNumWhitelistSsid: Firmware roaming is not supported");
+            Log.e(TAG, "getMaxNumAllowlistSsid: Firmware roaming is not supported");
             return INVALID_LIST_SIZE;
         }
     }
@@ -130,36 +131,37 @@
     /**
      * Write firmware roaming configuration to firmware.
      *
-     * @param blacklistBssids BSSIDs to be blacklisted
-     * @param whitelistSsids  SSIDs to be whitelisted
+     * @param blocklistBssids BSSIDs to be blocklisted
+     * @param allowlistSsids  SSIDs to be allowlisted
      * @return true if succeeded, false otherwise.
      */
-    public boolean setFirmwareRoamingConfiguration(ArrayList<String> blacklistBssids,
-            ArrayList<String> whitelistSsids) {
+    public boolean setFirmwareRoamingConfiguration(ArrayList<String> blocklistBssids,
+            ArrayList<String> allowlistSsids) {
         if (!mFirmwareRoamingSupported) {
             Log.e(TAG, "Firmware roaming is not supported");
             return false;
         }
 
-        if (blacklistBssids == null || whitelistSsids == null) {
+        if (blocklistBssids == null || allowlistSsids == null) {
             Log.e(TAG, "Invalid firmware roaming configuration settings");
             return false;
         }
 
-        int blacklistSize = blacklistBssids.size();
-        int whitelistSize = whitelistSsids.size();
+        int blocklistSize = blocklistBssids.size();
+        int allowlistSize = allowlistSsids.size();
 
-        if (blacklistSize > mMaxNumBlacklistBssid || whitelistSize > mMaxNumWhitelistSsid) {
-            Log.e(TAG, "Invalid BSSID blacklist size " + blacklistSize + " SSID whitelist size "
-                    + whitelistSize + ". Max blacklist size: " + mMaxNumBlacklistBssid
-                    + ", max whitelist size: " + mMaxNumWhitelistSsid);
+        if (blocklistSize > mMaxNumBlocklistBssid || allowlistSize > mMaxNumAllowlistSsid) {
+            Log.e(TAG, "Invalid BSSID blocklist size " + blocklistSize + " SSID allowlist size "
+                    + allowlistSize + ". Max blocklist size: " + mMaxNumBlocklistBssid
+                    + ", max allowlist size: " + mMaxNumAllowlistSsid);
             return false;
         }
 
         WifiNative.RoamingConfig roamConfig = new WifiNative.RoamingConfig();
-        roamConfig.blocklistBssids = blacklistBssids;
-        roamConfig.allowlistSsids = whitelistSsids;
+        roamConfig.blocklistBssids = blocklistBssids;
+        roamConfig.allowlistSsids = allowlistSsids;
 
-        return mWifiNative.configureRoaming(mWifiNative.getClientInterfaceName(), roamConfig);
+        return mWifiInjector.getActiveModeWarden()
+                .getPrimaryClientModeManager().configureRoaming(roamConfig);
     }
 }
diff --git a/service/java/com/android/server/wifi/WifiConnectivityManager.java b/service/java/com/android/server/wifi/WifiConnectivityManager.java
index 3a6661f..99796c7 100644
--- a/service/java/com/android/server/wifi/WifiConnectivityManager.java
+++ b/service/java/com/android/server/wifi/WifiConnectivityManager.java
@@ -16,15 +16,22 @@
 
 package com.android.server.wifi;
 
-import static com.android.internal.util.Preconditions.checkNotNull;
+import static android.net.wifi.WifiConfiguration.RANDOMIZATION_NONE;
+
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_PRIMARY;
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SECONDARY_LONG_LIVED;
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SECONDARY_TRANSIENT;
 import static com.android.server.wifi.ClientModeImpl.WIFI_WORK_SOURCE;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AlarmManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.net.MacAddress;
 import android.net.wifi.ScanResult;
-import android.net.wifi.SupplicantState;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
@@ -35,6 +42,7 @@
 import android.net.wifi.WifiScanner.ScanSettings;
 import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.os.Handler;
+import android.os.PowerManager;
 import android.os.Process;
 import android.os.WorkSource;
 import android.util.ArrayMap;
@@ -44,21 +52,24 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.modules.utils.HandlerExecutor;
+import com.android.modules.utils.build.SdkLevel;
+import com.android.server.wifi.hotspot2.PasspointManager;
 import com.android.server.wifi.util.ScanResultUtil;
 import com.android.wifi.resources.R;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * This class manages all the connectivity related scanning activities.
@@ -89,20 +100,12 @@
     // PNO scan interval in milli-seconds. This is the scan
     // performed when screen is off and connected.
     private static final int CONNECTED_PNO_SCAN_INTERVAL_MS = 160 * 1000; // 160 seconds
-    // When a network is found by PNO scan but gets rejected by Wifi Network Selector due
-    // to its low RSSI value, scan will be reschduled in an exponential back off manner.
-    private static final int LOW_RSSI_NETWORK_RETRY_START_DELAY_MS = 20 * 1000; // 20 seconds
-    private static final int LOW_RSSI_NETWORK_RETRY_MAX_DELAY_MS = 80 * 1000; // 80 seconds
     // Maximum number of retries when starting a scan failed
     @VisibleForTesting
     public static final int MAX_SCAN_RESTART_ALLOWED = 5;
     // Number of milli-seconds to delay before retry starting
     // a previously failed scan
     private static final int RESTART_SCAN_DELAY_MS = 2 * 1000; // 2 seconds
-    // When in disconnected mode, a watchdog timer will be fired
-    // every WATCHDOG_INTERVAL_MS to start a single scan. This is
-    // to prevent caveat from things like PNO scan.
-    private static final int WATCHDOG_INTERVAL_MS = 20 * 60 * 1000; // 20 minutes
     // Restricted channel list age out value.
     private static final long CHANNEL_LIST_AGE_MS = 60 * 60 * 1000; // 1 hour
     // This is the time interval for the connection attempt rate calculation. Connection attempt
@@ -113,7 +116,8 @@
     private static final int TEMP_BSSID_BLOCK_DURATION = 10 * 1000; // 10 seconds
     // Maximum age of frequencies last seen to be included in pno scans. (30 days)
     private static final long MAX_PNO_SCAN_FREQUENCY_AGE_MS = (long) 1000 * 3600 * 24 * 30;
-    // ClientModeImpl has a bunch of states. From the
+    private static final int POWER_SAVE_SCAN_INTERVAL_MULTIPLIER = 2;
+    // ClientModeManager has a bunch of states. From the
     // WifiConnectivityManager's perspective it only cares
     // if it is in Connected state, Disconnected state or in
     // transition between these two states.
@@ -124,21 +128,19 @@
 
     // Initial scan state, used to manage performing partial scans in initial scans
     // Initial scans are the first scan after enabling Wifi or turning on screen when disconnected
-    private static final int INITIAL_SCAN_STATE_START = 0;
-    private static final int INITIAL_SCAN_STATE_AWAITING_RESPONSE = 1;
-    private static final int INITIAL_SCAN_STATE_COMPLETE = 2;
+    @VisibleForTesting
+    public static final int INITIAL_SCAN_STATE_START = 0;
+    public static final int INITIAL_SCAN_STATE_AWAITING_RESPONSE = 1;
+    public static final int INITIAL_SCAN_STATE_COMPLETE = 2;
 
     // Log tag for this class
     private static final String TAG = "WifiConnectivityManager";
     private static final String ALL_SINGLE_SCAN_LISTENER = "AllSingleScanListener";
     private static final String PNO_SCAN_LISTENER = "PnoScanListener";
 
-    private final Context mContext;
-    private final ClientModeImpl mStateMachine;
-    private final WifiInjector mWifiInjector;
+    private final WifiContext mContext;
     private final WifiConfigManager mConfigManager;
     private final WifiNetworkSuggestionsManager mWifiNetworkSuggestionsManager;
-    private final WifiInfo mWifiInfo;
     private final WifiConnectivityHelper mConnectivityHelper;
     private final WifiNetworkSelector mNetworkSelector;
     private final WifiLastResortWatchdog mWifiLastResortWatchdog;
@@ -149,11 +151,21 @@
     private final Clock mClock;
     private final ScoringParams mScoringParams;
     private final LocalLog mLocalLog;
-    private final LinkedList<Long> mConnectionAttemptTimeStamps;
-    private final BssidBlocklistMonitor mBssidBlocklistMonitor;
-    private WifiScanner mScanner;
-    private WifiScoreCard mWifiScoreCard;
+    private final WifiGlobals mWifiGlobals;
+    /**
+     * Keeps connection attempts within the last {@link #MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS}
+     * milliseconds.
+     */
+    private final LinkedList<Long> mConnectionAttemptTimeStamps = new LinkedList<>();
+    private final WifiBlocklistMonitor mWifiBlocklistMonitor;
+    private final PasspointManager mPasspointManager;
+    private final WifiScoreCard mWifiScoreCard;
+    private final WifiChannelUtilization mWifiChannelUtilization;
+    private final PowerManager mPowerManager;
+    private final DeviceConfigFacade mDeviceConfigFacade;
+    private final ActiveModeWarden mActiveModeWarden;
 
+    private WifiScanner mScanner;
     private boolean mDbg = false;
     private boolean mVerboseLoggingEnabled = false;
     private boolean mWifiEnabled = false;
@@ -164,12 +176,15 @@
     private int mInitialScanState = INITIAL_SCAN_STATE_COMPLETE;
     private boolean mAutoJoinEnabledExternal = true; // enabled by default
     private boolean mUntrustedConnectionAllowed = false;
+    private boolean mOemPaidConnectionAllowed = false;
+    private boolean mOemPrivateConnectionAllowed = false;
+    private WorkSource mOemPaidConnectionRequestorWs = null;
+    private WorkSource mOemPrivateConnectionRequestorWs = null;
     private boolean mTrustedConnectionAllowed = false;
     private boolean mSpecificNetworkRequestInProgress = false;
     private int mScanRestartCount = 0;
     private int mSingleScanRestartCount = 0;
     private int mTotalConnectivityAttemptsRateLimited = 0;
-    private String mLastConnectionAttemptBssid = null;
     private long mLastPeriodicSingleScanTimeStamp = RESET_TIME_STAMP;
     private long mLastNetworkSelectionTimeStamp = RESET_TIME_STAMP;
     private boolean mPnoScanStarted = false;
@@ -199,7 +214,6 @@
     private int[] mCurrentSingleScanScheduleSec;
 
     private int mCurrentSingleScanScheduleIndex;
-    private WifiChannelUtilization mWifiChannelUtilization;
     // Cached WifiCandidates used in high mobility state to avoid connecting to APs that are
     // moving relative to the user.
     private CachedWifiCandidates mCachedWifiCandidates = null;
@@ -244,8 +258,8 @@
         }
     }
 
-    // As a watchdog mechanism, a single scan will be scheduled every WATCHDOG_INTERVAL_MS
-    // if it is in the WIFI_STATE_DISCONNECTED state.
+    // As a watchdog mechanism, a single scan will be scheduled every
+    // config_wifiPnoWatchdogIntervalMinutes if it is in the WIFI_STATE_DISCONNECTED state.
     private final AlarmManager.OnAlarmListener mWatchdogListener =
             new AlarmManager.OnAlarmListener() {
                 public void onAlarm() {
@@ -290,37 +304,101 @@
             };
 
     /**
+     * Interface for callback from handling scan results.
+     */
+    private interface HandleScanResultsListener {
+        /**
+         * @param wasCandidateSelected true - if a candidate is selected by WifiNetworkSelector
+         *                             false - if no candidate is selected by WifiNetworkSelector
+         */
+        void onHandled(boolean wasCandidateSelected);
+    }
+
+    /**
+     * Helper method to consolidate handling of scan results when no candidate is selected.
+     */
+    private void handleScanResultsWithNoCandidate(
+            @NonNull HandleScanResultsListener handleScanResultsListener) {
+        if (mWifiState == WIFI_STATE_DISCONNECTED) {
+            mOpenNetworkNotifier.handleScanResults(
+                    mNetworkSelector.getFilteredScanDetailsForOpenUnsavedNetworks());
+        }
+        mWifiMetrics.noteFirstNetworkSelectionAfterBoot(false);
+        handleScanResultsListener.onHandled(false);
+    }
+
+    /**
+     * Helper method to consolidate handling of scan results when a candidate is selected.
+     */
+    private void handleScanResultsWithCandidate(
+            @NonNull HandleScanResultsListener handleScanResultsListener) {
+        mWifiMetrics.noteFirstNetworkSelectionAfterBoot(true);
+        handleScanResultsListener.onHandled(true);
+    }
+
+    /**
      * Handles 'onResult' callbacks for the Periodic, Single & Pno ScanListener.
      * Executes selection of potential network candidates, initiation of connection attempt to that
      * network.
-     *
-     * @return true - if a candidate is selected by WifiNetworkSelector
-     *         false - if no candidate is selected by WifiNetworkSelector
      */
-    private boolean handleScanResults(List<ScanDetail> scanDetails, String listenerName,
-            boolean isFullScan) {
-        mWifiChannelUtilization.refreshChannelStatsAndChannelUtilization(
-                mStateMachine.getWifiLinkLayerStats(), WifiChannelUtilization.UNKNOWN_FREQ);
-
-        updateUserDisabledList(scanDetails);
-
-        // Check if any blocklisted BSSIDs can be freed.
-        mBssidBlocklistMonitor.tryEnablingBlockedBssids(scanDetails);
-        Set<String> bssidBlocklist = mBssidBlocklistMonitor.updateAndGetBssidBlocklistForSsid(
-                mWifiInfo.getSSID());
-
-        if (mStateMachine.isSupplicantTransientState()) {
-            localLog(listenerName
-                    + " onResults: No network selection because supplicantTransientState is "
-                    + mStateMachine.isSupplicantTransientState());
-            return false;
+    private void handleScanResults(@NonNull List<ScanDetail> scanDetails,
+            @NonNull String listenerName,
+            boolean isFullScan,
+            @NonNull HandleScanResultsListener handleScanResultsListener) {
+        List<WifiNetworkSelector.ClientModeManagerState> cmmStates = new ArrayList<>();
+        Set<String> connectedSsids = new HashSet<>();
+        boolean hasExistingSecondaryCmm = false;
+        for (ClientModeManager clientModeManager :
+                mActiveModeWarden.getInternetConnectivityClientModeManagers()) {
+            if (clientModeManager.getRole() == ROLE_CLIENT_SECONDARY_LONG_LIVED) {
+                hasExistingSecondaryCmm = true;
+            }
+            mWifiChannelUtilization.refreshChannelStatsAndChannelUtilization(
+                    clientModeManager.getWifiLinkLayerStats(),
+                    WifiChannelUtilization.UNKNOWN_FREQ);
+            WifiInfo wifiInfo = clientModeManager.syncRequestConnectionInfo();
+            if (clientModeManager.isConnected()) {
+                connectedSsids.add(wifiInfo.getSSID());
+            }
+            cmmStates.add(new WifiNetworkSelector.ClientModeManagerState(clientModeManager));
         }
+        // We don't have any existing secondary CMM, but are we allowed to create a secondary CMM
+        // and do we have a request for OEM_PAID/OEM_PRIVATE request? If yes, we need to perform
+        // network selection to check if we have any potential candidate for the secondary CMM
+        // creation.
+        if (!hasExistingSecondaryCmm
+                && (mOemPaidConnectionAllowed || mOemPrivateConnectionAllowed)) {
+            // prefer OEM PAID requestor if it exists.
+            WorkSource oemPaidOrOemPrivateRequestorWs =
+                    mOemPaidConnectionRequestorWs != null
+                            ? mOemPaidConnectionRequestorWs
+                            : mOemPrivateConnectionRequestorWs;
+            if (oemPaidOrOemPrivateRequestorWs == null) {
+                Log.e(TAG, "Both mOemPaidConnectionRequestorWs & mOemPrivateConnectionRequestorWs "
+                        + "are null!");
+            }
+            if (oemPaidOrOemPrivateRequestorWs != null
+                    && mActiveModeWarden.canRequestMoreClientModeManagersInRole(
+                            oemPaidOrOemPrivateRequestorWs,
+                            ROLE_CLIENT_SECONDARY_LONG_LIVED)) {
+                // Add a placeholder CMM state to ensure network selection is performed for a
+                // potential second STA creation.
+                cmmStates.add(new WifiNetworkSelector.ClientModeManagerState());
+            }
+        }
+        // Check if any blocklisted BSSIDs can be freed.
+        mWifiBlocklistMonitor.tryEnablingBlockedBssids(scanDetails);
+        Set<String> bssidBlocklist = mWifiBlocklistMonitor.updateAndGetBssidBlocklistForSsids(
+                connectedSsids);
+        updateUserDisabledList(scanDetails);
+        // Clear expired recent failure statuses
+        mConfigManager.cleanupExpiredRecentFailureReasons();
 
         localLog(listenerName + " onResults: start network selection");
 
         List<WifiCandidates.Candidate> candidates = mNetworkSelector.getCandidatesFromScan(
-                scanDetails, bssidBlocklist, mWifiInfo, mStateMachine.isConnected(),
-                mStateMachine.isDisconnected(), mUntrustedConnectionAllowed);
+                scanDetails, bssidBlocklist, cmmStates, mUntrustedConnectionAllowed,
+                mOemPaidConnectionAllowed, mOemPrivateConnectionAllowed);
         mLatestCandidates = candidates;
         mLatestCandidatesTimestampMs = mClock.getElapsedSinceBootMillis();
 
@@ -330,21 +408,149 @@
             candidates = filterCandidatesHighMovement(candidates, listenerName, isFullScan);
         }
 
-        WifiConfiguration candidate = mNetworkSelector.selectNetwork(candidates);
         mLastNetworkSelectionTimeStamp = mClock.getElapsedSinceBootMillis();
         mWifiLastResortWatchdog.updateAvailableNetworks(
                 mNetworkSelector.getConnectableScanDetails());
         mWifiMetrics.countScanResults(scanDetails);
+        // No candidates, return early.
+        if (candidates == null || candidates.size() == 0) {
+            localLog(listenerName + ":  No candidates");
+            handleScanResultsWithNoCandidate(handleScanResultsListener);
+            return;
+        }
+        // We have an oem paid/private network request and device supports STA + STA, check if there
+        // are oem paid/private suggestions.
+        if ((mOemPaidConnectionAllowed || mOemPrivateConnectionAllowed)
+                && mActiveModeWarden.isStaStaConcurrencySupportedForRestrictedConnections()) {
+            // Split the candidates based on whether they are oem paid/oem private or not.
+            Map<Boolean, List<WifiCandidates.Candidate>> candidatesPartitioned =
+                    candidates.stream()
+                            .collect(Collectors.groupingBy(c -> c.isOemPaid() || c.isOemPrivate()));
+            List<WifiCandidates.Candidate> primaryCmmCandidates =
+                    candidatesPartitioned.getOrDefault(false, Collections.emptyList());
+            List<WifiCandidates.Candidate> secondaryCmmCandidates =
+                    candidatesPartitioned.getOrDefault(true, Collections.emptyList());
+            // Some oem paid/private suggestions found, use secondary cmm flow.
+            if (!secondaryCmmCandidates.isEmpty()) {
+                handleCandidatesFromScanResultsUsingSecondaryCmmIfAvailable(
+                        listenerName, primaryCmmCandidates, secondaryCmmCandidates,
+                        handleScanResultsListener);
+                return;
+            }
+            // intentional fallthrough: No oem paid/private suggestions, fallback to legacy flow.
+        }
+        handleCandidatesFromScanResultsForPrimaryCmmUsingMbbIfAvailable(
+                listenerName, candidates, handleScanResultsListener);
+    }
+
+    /**
+     * Executes selection of best network for 2 concurrent STA's from the candidates provided,
+     * initiation of connection attempt to a network on both the STA's (if found).
+     */
+    private void handleCandidatesFromScanResultsUsingSecondaryCmmIfAvailable(
+            @NonNull String listenerName,
+            @NonNull List<WifiCandidates.Candidate> primaryCmmCandidates,
+            @NonNull List<WifiCandidates.Candidate> secondaryCmmCandidates,
+            @NonNull HandleScanResultsListener handleScanResultsListener) {
+        // Perform network selection among secondary candidates.
+        WifiConfiguration secondaryCmmCandidate =
+                mNetworkSelector.selectNetwork(secondaryCmmCandidates);
+        // No oem paid/private selected, fallback to legacy flow (should never happen!).
+        if (secondaryCmmCandidate == null
+                || secondaryCmmCandidate.getNetworkSelectionStatus().getCandidate() == null) {
+            localLog(listenerName + ": No secondary candidate");
+            handleCandidatesFromScanResultsForPrimaryCmmUsingMbbIfAvailable(
+                    listenerName,
+                    Stream.concat(primaryCmmCandidates.stream(), secondaryCmmCandidates.stream())
+                            .collect(Collectors.toList()),
+                    handleScanResultsListener);
+            return;
+        }
+        String secondaryCmmCandidateBssid =
+                secondaryCmmCandidate.getNetworkSelectionStatus().getCandidate().BSSID;
+        WorkSource secondaryRequestorWs = null;
+        // OEM_PAID takes precedence over OEM_PRIVATE, so attribute to OEM_PAID requesting app.
+        if (secondaryCmmCandidate.oemPaid
+                && mActiveModeWarden.canRequestMoreClientModeManagersInRole(
+                mOemPaidConnectionRequestorWs, ROLE_CLIENT_SECONDARY_LONG_LIVED)) {
+            secondaryRequestorWs = mOemPaidConnectionRequestorWs;
+        } else if (secondaryCmmCandidate.oemPrivate
+                && mActiveModeWarden.canRequestMoreClientModeManagersInRole(
+                mOemPrivateConnectionRequestorWs, ROLE_CLIENT_SECONDARY_LONG_LIVED)) {
+            secondaryRequestorWs = mOemPrivateConnectionRequestorWs;
+        }
+        // Secondary STA not available, fallback to legacy flow.
+        if (secondaryRequestorWs == null) {
+            localLog(listenerName + ": No secondary STA available");
+            handleCandidatesFromScanResultsForPrimaryCmmUsingMbbIfAvailable(
+                    listenerName,
+                    Stream.concat(primaryCmmCandidates.stream(), secondaryCmmCandidates.stream())
+                            .collect(Collectors.toList()),
+                    handleScanResultsListener);
+            return;
+        }
+        WifiConfiguration primaryCmmCandidate =
+                mNetworkSelector.selectNetwork(primaryCmmCandidates);
+        // Request for a new client mode manager to spin up concurrent connection
+        mActiveModeWarden.requestSecondaryLongLivedClientModeManager(
+                (cm) -> {
+                    if (cm == null) {
+                        localLog(listenerName + ": Secondary client mode manager request returned "
+                                + "null, aborting (wifi off?)");
+                        handleScanResultsWithNoCandidate(handleScanResultsListener);
+                        return;
+                    }
+                    // We did not end up getting the secondary client mode manager for some reason
+                    // after we checked above! Fallback to legacy flow.
+                    if (cm.getRole() == ROLE_CLIENT_PRIMARY) {
+                        localLog(listenerName + ": Secondary client mode manager request returned"
+                                + " primary, falling back to single client mode manager flow.");
+                        handleCandidatesFromScanResultsForPrimaryCmmUsingMbbIfAvailable(
+                                listenerName,
+                                Stream.concat(primaryCmmCandidates.stream(),
+                                        secondaryCmmCandidates.stream())
+                                        .collect(Collectors.toList()),
+                                handleScanResultsListener);
+                        return;
+                    }
+                    // Don't use make before break for these connection requests.
+
+                    // If we also selected a primary candidate trigger connection.
+                    if (primaryCmmCandidate != null) {
+                        localLog(listenerName + ":  WNS candidate(primary)-"
+                                + primaryCmmCandidate.SSID);
+                        connectToNetworkUsingCmmWithoutMbb(
+                                getPrimaryClientModeManager(), primaryCmmCandidate);
+                    }
+
+                    localLog(listenerName + ":  WNS candidate(secondary)-"
+                            + secondaryCmmCandidate.SSID);
+                    // Secndary candidate cannot be null (otherwise we would have switched to legacy
+                    // flow above)
+                    connectToNetworkUsingCmmWithoutMbb(cm, secondaryCmmCandidate);
+
+                    handleScanResultsWithCandidate(handleScanResultsListener);
+                }, secondaryRequestorWs,
+                secondaryCmmCandidate.SSID,
+                mConnectivityHelper.isFirmwareRoamingSupported()
+                        ? null : secondaryCmmCandidateBssid);
+    }
+
+    /**
+     * Executes selection of best network from the candidates provided, initiation of connection
+     * attempt to that network.
+     */
+    private void handleCandidatesFromScanResultsForPrimaryCmmUsingMbbIfAvailable(
+            @NonNull String listenerName, @NonNull List<WifiCandidates.Candidate> candidates,
+            @NonNull HandleScanResultsListener handleScanResultsListener) {
+        WifiConfiguration candidate = mNetworkSelector.selectNetwork(candidates);
         if (candidate != null) {
             localLog(listenerName + ":  WNS candidate-" + candidate.SSID);
-            connectToNetwork(candidate);
-            return true;
+            connectToNetworkForPrimaryCmmUsingMbbIfAvailable(candidate);
+            handleScanResultsWithCandidate(handleScanResultsListener);
         } else {
-            if (mWifiState == WIFI_STATE_DISCONNECTED) {
-                mOpenNetworkNotifier.handleScanResults(
-                        mNetworkSelector.getFilteredScanDetailsForOpenUnsavedNetworks());
-            }
-            return false;
+            localLog(listenerName + ":  No candidate");
+            handleScanResultsWithNoCandidate(handleScanResultsListener);
         }
     }
 
@@ -413,19 +619,12 @@
             passpointAp.add(scanDetail.getScanResult());
         }
         if (!passpointAp.isEmpty()) {
-            results.addAll(new ArrayList<>(mWifiInjector.getPasspointManager()
-                    .getAllMatchingPasspointProfilesForScanResults(passpointAp).keySet()));
+            results.addAll(mPasspointManager
+                    .getAllMatchingPasspointProfilesForScanResults(passpointAp).keySet());
         }
         mConfigManager.updateUserDisabledList(results);
     }
 
-    /**
-     * Set whether bluetooth is in the connected state
-     */
-    public void setBluetoothConnected(boolean isBluetoothConnected) {
-        mNetworkSelector.setBluetoothConnected(isBluetoothConnected);
-    }
-
     private class CachedWifiCandidates {
         public final long timeSinceBootMs;
         public final Map<WifiCandidates.Key, Integer> candidateRssiMap;
@@ -487,7 +686,7 @@
             boolean isFullBandScanResults = false;
             if (results != null && results.length > 0) {
                 isFullBandScanResults =
-                        WifiScanner.isFullBandScan(results[0].getBandScanned(), true);
+                        WifiScanner.isFullBandScan(results[0].getScannedBandsInternal(), true);
             }
             // Full band scan results only.
             if (mWaitForFullBandScanResults) {
@@ -499,54 +698,59 @@
                     mWaitForFullBandScanResults = false;
                 }
             }
+
+            // Create a new list to avoid looping call trigger concurrent exception.
+            List<ScanDetail> scanDetailList = new ArrayList<>(mScanDetails);
+            clearScanDetails();
+
             if (results != null && results.length > 0) {
-                mWifiMetrics.incrementAvailableNetworksHistograms(mScanDetails,
+                mWifiMetrics.incrementAvailableNetworksHistograms(scanDetailList,
                         isFullBandScanResults);
             }
             if (mNumScanResultsIgnoredDueToSingleRadioChain > 0) {
                 Log.i(TAG, "Number of scan results ignored due to single radio chain scan: "
                         + mNumScanResultsIgnoredDueToSingleRadioChain);
             }
-            boolean wasConnectAttempted = handleScanResults(mScanDetails,
-                    ALL_SINGLE_SCAN_LISTENER, isFullBandScanResults);
-            clearScanDetails();
+            handleScanResults(scanDetailList,
+                    ALL_SINGLE_SCAN_LISTENER, isFullBandScanResults,
+                    wasCandidateSelected -> {
+                        // Update metrics to see if a single scan detected a valid network
+                        // while PNO scan didn't.
+                        // Note: We don't update the background scan metrics any more as it is
+                        //       not in use.
+                        if (mPnoScanStarted) {
+                            if (wasCandidateSelected) {
+                                mWifiMetrics.incrementNumConnectivityWatchdogPnoBad();
+                            } else {
+                                mWifiMetrics.incrementNumConnectivityWatchdogPnoGood();
+                            }
+                        }
 
-            // Update metrics to see if a single scan detected a valid network
-            // while PNO scan didn't.
-            // Note: We don't update the background scan metrics any more as it is
-            //       not in use.
-            if (mPnoScanStarted) {
-                if (wasConnectAttempted) {
-                    mWifiMetrics.incrementNumConnectivityWatchdogPnoBad();
-                } else {
-                    mWifiMetrics.incrementNumConnectivityWatchdogPnoGood();
-                }
-            }
+                        // Check if we are in the middle of initial partial scan
+                        if (mInitialScanState == INITIAL_SCAN_STATE_AWAITING_RESPONSE) {
+                            // Done with initial scan
+                            setInitialScanState(INITIAL_SCAN_STATE_COMPLETE);
 
-            // Check if we are in the middle of initial partial scan
-            if (mInitialScanState == INITIAL_SCAN_STATE_AWAITING_RESPONSE) {
-                // Done with initial scan
-                setInitialScanState(INITIAL_SCAN_STATE_COMPLETE);
-
-                if (wasConnectAttempted) {
-                    Log.i(TAG, "Connection attempted with the reduced initial scans");
-                    schedulePeriodicScanTimer(
-                            getScheduledSingleScanIntervalMs(mCurrentSingleScanScheduleIndex));
-                    mWifiMetrics.reportInitialPartialScan(mInitialPartialScanChannelCount, true);
-                    mInitialPartialScanChannelCount = 0;
-                } else {
-                    Log.i(TAG, "Connection was not attempted, issuing a full scan");
-                    startConnectivityScan(SCAN_IMMEDIATELY);
-                    mFailedInitialPartialScan = true;
-                }
-            } else if (mInitialScanState == INITIAL_SCAN_STATE_COMPLETE) {
-                if (mFailedInitialPartialScan && wasConnectAttempted) {
-                    // Initial scan failed, but following full scan succeeded
-                    mWifiMetrics.reportInitialPartialScan(mInitialPartialScanChannelCount, false);
-                }
-                mFailedInitialPartialScan = false;
-                mInitialPartialScanChannelCount = 0;
-            }
+                            if (wasCandidateSelected) {
+                                Log.i(TAG, "Connection attempted with the reduced initial scans");
+                                mWifiMetrics.reportInitialPartialScan(
+                                        mInitialPartialScanChannelCount, true);
+                                mInitialPartialScanChannelCount = 0;
+                            } else {
+                                Log.i(TAG, "Connection was not attempted, issuing a full scan");
+                                startConnectivityScan(SCAN_IMMEDIATELY);
+                                mFailedInitialPartialScan = true;
+                            }
+                        } else if (mInitialScanState == INITIAL_SCAN_STATE_COMPLETE) {
+                            if (mFailedInitialPartialScan && wasCandidateSelected) {
+                                // Initial scan failed, but following full scan succeeded
+                                mWifiMetrics.reportInitialPartialScan(
+                                        mInitialPartialScanChannelCount, false);
+                            }
+                            mFailedInitialPartialScan = false;
+                            mInitialPartialScanChannelCount = 0;
+                        }
+                    });
         }
 
         @Override
@@ -630,8 +834,13 @@
     // A PNO scan is initiated when screen is off.
     private class PnoScanListener implements WifiScanner.PnoScanListener {
         private List<ScanDetail> mScanDetails = new ArrayList<ScanDetail>();
-        private int mLowRssiNetworkRetryDelay =
-                LOW_RSSI_NETWORK_RETRY_START_DELAY_MS;
+        private int mLowRssiNetworkRetryDelayMs;
+
+        private void limitLowRssiNetworkRetryDelay() {
+            mLowRssiNetworkRetryDelayMs = Math.min(mLowRssiNetworkRetryDelayMs,
+                    mContext.getResources().getInteger(R.integer
+                            .config_wifiPnoScanLowRssiNetworkRetryMaxDelaySec) * 1000);
+        }
 
         public void clearScanDetails() {
             mScanDetails.clear();
@@ -640,12 +849,13 @@
         // Reset to the start value when either a non-PNO scan is started or
         // WifiNetworkSelector selects a candidate from the PNO scan results.
         public void resetLowRssiNetworkRetryDelay() {
-            mLowRssiNetworkRetryDelay = LOW_RSSI_NETWORK_RETRY_START_DELAY_MS;
+            mLowRssiNetworkRetryDelayMs = mContext.getResources().getInteger(R.integer
+                    .config_wifiPnoScanLowRssiNetworkRetryStartDelaySec) * 1000;
         }
 
         @VisibleForTesting
         public int getLowRssiNetworkRetryDelay() {
-            return mLowRssiNetworkRetryDelay;
+            return mLowRssiNetworkRetryDelayMs;
         }
 
         @Override
@@ -694,23 +904,29 @@
                 mScanDetails.add(ScanResultUtil.toScanDetail(result));
             }
 
-            boolean wasConnectAttempted;
-            wasConnectAttempted = handleScanResults(mScanDetails, PNO_SCAN_LISTENER, false);
+            // Create a new list to avoid looping call trigger concurrent exception.
+            List<ScanDetail> scanDetailList = new ArrayList<>(mScanDetails);
             clearScanDetails();
             mScanRestartCount = 0;
 
-            if (!wasConnectAttempted) {
-                // The scan results were rejected by WifiNetworkSelector due to low RSSI values
-                if (mLowRssiNetworkRetryDelay > LOW_RSSI_NETWORK_RETRY_MAX_DELAY_MS) {
-                    mLowRssiNetworkRetryDelay = LOW_RSSI_NETWORK_RETRY_MAX_DELAY_MS;
-                }
-                scheduleDelayedConnectivityScan(mLowRssiNetworkRetryDelay);
+            handleScanResults(scanDetailList, PNO_SCAN_LISTENER, false,
+                    wasCandidateSelected -> {
+                        if (!wasCandidateSelected) {
+                            // The scan results were rejected by WifiNetworkSelector due to low
+                            // RSSI values
+                            // Lazy initialization
+                            if (mLowRssiNetworkRetryDelayMs == 0) {
+                                resetLowRssiNetworkRetryDelay();
+                            }
+                            scheduleDelayedConnectivityScan(mLowRssiNetworkRetryDelayMs);
 
-                // Set up the delay value for next retry.
-                mLowRssiNetworkRetryDelay *= 2;
-            } else {
-                resetLowRssiNetworkRetryDelay();
-            }
+                            // Set up the delay value for next retry.
+                            mLowRssiNetworkRetryDelayMs *= 2;
+                            limitLowRssiNetworkRetryDelay();
+                        } else {
+                            resetLowRssiNetworkRetryDelay();
+                        }
+                    });
         }
     }
 
@@ -734,8 +950,6 @@
         public void onNetworkUpdated(WifiConfiguration newConfig, WifiConfiguration oldConfig) {
             triggerScanOnNetworkChanges();
         }
-        @Override
-        public void onNetworkTemporarilyDisabled(WifiConfiguration config, int disableReason) { }
 
         @Override
         public void onNetworkPermanentlyDisabled(WifiConfiguration config, int disableReason) {
@@ -756,44 +970,112 @@
         }
     }
 
+    private class ModeChangeCallback implements ActiveModeWarden.ModeChangeCallback {
+        @Override
+        public void onActiveModeManagerAdded(@NonNull ActiveModeManager activeModeManager) {
+            update();
+        }
+
+        @Override
+        public void onActiveModeManagerRemoved(@NonNull ActiveModeManager activeModeManager) {
+            update();
+        }
+
+        @Override
+        public void onActiveModeManagerRoleChanged(@NonNull ActiveModeManager activeModeManager) {
+            update();
+        }
+
+        private void update() {
+            List<ClientModeManager> primaryManagers =
+                    mActiveModeWarden.getInternetConnectivityClientModeManagers();
+            setWifiEnabled(!primaryManagers.isEmpty());
+        }
+    }
+
     /**
      * WifiConnectivityManager constructor
      */
-    WifiConnectivityManager(Context context, ScoringParams scoringParams,
-            ClientModeImpl stateMachine,
-            WifiInjector injector, WifiConfigManager configManager,
-            WifiNetworkSuggestionsManager wifiNetworkSuggestionsManager, WifiInfo wifiInfo,
-            WifiNetworkSelector networkSelector, WifiConnectivityHelper connectivityHelper,
-            WifiLastResortWatchdog wifiLastResortWatchdog, OpenNetworkNotifier openNetworkNotifier,
-            WifiMetrics wifiMetrics, Handler handler,
-            Clock clock, LocalLog localLog, WifiScoreCard scoreCard) {
+    WifiConnectivityManager(
+            WifiContext context,
+            ScoringParams scoringParams,
+            WifiConfigManager configManager,
+            WifiNetworkSuggestionsManager wifiNetworkSuggestionsManager,
+            WifiNetworkSelector networkSelector,
+            WifiConnectivityHelper connectivityHelper,
+            WifiLastResortWatchdog wifiLastResortWatchdog,
+            OpenNetworkNotifier openNetworkNotifier,
+            WifiMetrics wifiMetrics,
+            Handler handler,
+            Clock clock,
+            LocalLog localLog,
+            WifiScoreCard scoreCard,
+            WifiBlocklistMonitor wifiBlocklistMonitor,
+            WifiChannelUtilization wifiChannelUtilization,
+            PasspointManager passpointManager,
+            DeviceConfigFacade deviceConfigFacade,
+            ActiveModeWarden activeModeWarden,
+            WifiGlobals wifiGlobals) {
         mContext = context;
-        mStateMachine = stateMachine;
-        mWifiInjector = injector;
+        mScoringParams = scoringParams;
         mConfigManager = configManager;
         mWifiNetworkSuggestionsManager = wifiNetworkSuggestionsManager;
-        mWifiInfo = wifiInfo;
         mNetworkSelector = networkSelector;
         mConnectivityHelper = connectivityHelper;
-        mLocalLog = localLog;
         mWifiLastResortWatchdog = wifiLastResortWatchdog;
         mOpenNetworkNotifier = openNetworkNotifier;
         mWifiMetrics = wifiMetrics;
-        mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
         mEventHandler = handler;
         mClock = clock;
-        mScoringParams = scoringParams;
-        mConnectionAttemptTimeStamps = new LinkedList<>();
+        mLocalLog = localLog;
+        mWifiScoreCard = scoreCard;
+        mWifiBlocklistMonitor = wifiBlocklistMonitor;
+        mWifiChannelUtilization = wifiChannelUtilization;
+        mPasspointManager = passpointManager;
+        mDeviceConfigFacade = deviceConfigFacade;
+        mActiveModeWarden = activeModeWarden;
+        mWifiGlobals = wifiGlobals;
+
+        mAlarmManager = context.getSystemService(AlarmManager.class);
+        mPowerManager = mContext.getSystemService(PowerManager.class);
+
+        // Listen for screen state change events.
+        // TODO: We should probably add a shared broadcast receiver in the wifi stack which
+        // can used by various modules to listen to common system events. Creating multiple
+        // broadcast receivers in each class within the wifi stack is *somewhat* expensive.
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_SCREEN_ON);
+        filter.addAction(Intent.ACTION_SCREEN_OFF);
+        mContext.registerReceiver(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        String action = intent.getAction();
+                        if (action.equals(Intent.ACTION_SCREEN_ON)) {
+                            handleScreenStateChanged(true);
+                        } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
+                            handleScreenStateChanged(false);
+                        }
+                    }
+                }, filter, null, mEventHandler);
+        handleScreenStateChanged(mPowerManager.isInteractive());
 
         // Listen to WifiConfigManager network update events
         mConfigManager.addOnNetworkUpdateListener(new OnNetworkUpdateListener());
         // Listen to WifiNetworkSuggestionsManager suggestion update events
         mWifiNetworkSuggestionsManager.addOnSuggestionUpdateListener(
                 new OnSuggestionUpdateListener());
-        mBssidBlocklistMonitor = mWifiInjector.getBssidBlocklistMonitor();
-        mWifiChannelUtilization = mWifiInjector.getWifiChannelUtilizationScan();
-        mNetworkSelector.setWifiChannelUtilization(mWifiChannelUtilization);
-        mWifiScoreCard = scoreCard;
+        mActiveModeWarden.registerModeChangeCallback(new ModeChangeCallback());
+    }
+
+    @NonNull
+    private WifiInfo getPrimaryWifiInfo() {
+        return getPrimaryClientModeManager().syncRequestConnectionInfo();
+    }
+
+    private ClientModeManager getPrimaryClientModeManager() {
+        // There should only be 1 primary client mode manager at any point of time.
+        return mActiveModeWarden.getPrimaryClientModeManager();
     }
 
     /** Initialize single scanning schedules, and validate them */
@@ -835,7 +1117,7 @@
      * should be skipped or not. This attempts to rate limit the rate of connections to
      * prevent us from flapping between networks and draining battery rapidly.
      */
-    private boolean shouldSkipConnectionAttempt(Long timeMillis) {
+    private boolean shouldSkipConnectionAttempt(long timeMillis) {
         Iterator<Long> attemptIter = mConnectionAttemptTimeStamps.iterator();
         // First evict old entries from the queue.
         while (attemptIter.hasNext()) {
@@ -855,7 +1137,8 @@
     /**
      * Add the current connection attempt timestamp to our queue of connection attempts.
      */
-    private void noteConnectionAttempt(Long timeMillis) {
+    private void noteConnectionAttempt(long timeMillis) {
+        localLog("noteConnectionAttempt: timeMillis=" + timeMillis);
         mConnectionAttemptTimeStamps.addLast(timeMillis);
     }
 
@@ -867,89 +1150,314 @@
         mConnectionAttemptTimeStamps.clear();
     }
 
+    private static <T> T coalesce(T a, T  b) {
+        return a != null ? a : b;
+    }
+
+    private boolean isClientModeManagerConnectedOrConnectingToCandidate(
+            ClientModeManager clientModeManager, WifiConfiguration candidate) {
+        int targetNetworkId = candidate.networkId;
+        WifiConfiguration connectedOrConnectingWifiConfiguration = coalesce(
+                clientModeManager.getConnectingWifiConfiguration(),
+                clientModeManager.getConnectedWifiConfiguration());
+        boolean connectingOrConnectedToTarget =
+                connectedOrConnectingWifiConfiguration != null
+                        && (targetNetworkId == connectedOrConnectingWifiConfiguration.networkId
+                        || (mContext.getResources().getBoolean(
+                                R.bool.config_wifiEnableLinkedNetworkRoaming)
+                        && connectedOrConnectingWifiConfiguration.isLinked(candidate)));
+
+        // Is Firmware roaming control is supported?
+        //   - Yes, framework does nothing, firmware will roam if necessary.
+        //   - No, framework initiates roaming.
+        if (mConnectivityHelper.isFirmwareRoamingSupported()) {
+            // just check for networkID.
+            return connectingOrConnectedToTarget;
+        }
+
+        // check for networkID and BSSID.
+        String connectedOrConnectingBssid = coalesce(
+                clientModeManager.getConnectingBssid(),
+                clientModeManager.getConnectedBssid());
+        ScanResult scanResultCandidate =
+                candidate.getNetworkSelectionStatus().getCandidate();
+        String targetBssid = scanResultCandidate.BSSID;
+        return connectingOrConnectedToTarget
+                && Objects.equals(targetBssid, connectedOrConnectingBssid);
+    }
+
+    /**
+     * Trigger network connection for primary client mode manager using make before break.
+     *
+     * Note: This may trigger make before break on a secondary STA if available which will
+     * eventually become primary after validation or torn down if it does not become primary.
+     */
+    private void connectToNetworkForPrimaryCmmUsingMbbIfAvailable(
+            @NonNull WifiConfiguration candidate) {
+        ClientModeManager primaryManager = mActiveModeWarden.getPrimaryClientModeManager();
+        connectToNetworkUsingCmm(
+                primaryManager, candidate,
+                new ConnectHandler() {
+                    @Override
+                    public void triggerConnectWhenDisconnected(
+                            WifiConfiguration targetNetwork,
+                            String targetBssid) {
+                        triggerConnectToNetworkUsingCmm(primaryManager, targetNetwork, targetBssid);
+                        // since using primary manager to connect, stop any existing managers in the
+                        // secondary transient role since they are no longer needed.
+                        mActiveModeWarden.stopAllClientModeManagersInRole(
+                                ROLE_CLIENT_SECONDARY_TRANSIENT);
+                    }
+
+                    @Override
+                    public void triggerConnectWhenConnected(
+                            WifiConfiguration currentNetwork,
+                            WifiConfiguration targetNetwork,
+                            String targetBssid) {
+                        mWifiMetrics.incrementWifiToWifiSwitchTriggerCount();
+                        // If both the current & target networks have MAC randomization disabled,
+                        // we cannot use MBB because then both ifaces would need to use the exact
+                        // same MAC address (the "designated" factory MAC for the device), which is
+                        // illegal. Fallback to single STA behavior.
+
+                        // TODO(b/172086124): Possibly move this logic to
+                        // ActiveModeWarden.handleAdditionalClientModeManagerRequest() to
+                        // ensure that all fallback logic in 1 central place (all the necessary
+                        // info is already included in the secondary STA creation request).
+                        if (currentNetwork.macRandomizationSetting == RANDOMIZATION_NONE
+                                && targetNetwork.macRandomizationSetting == RANDOMIZATION_NONE) {
+                            triggerConnectToNetworkUsingCmm(
+                                    primaryManager, targetNetwork, targetBssid);
+                            // since using primary manager to connect, stop any existing managers in
+                            // the secondary transient role since they are no longer needed.
+                            mActiveModeWarden.stopAllClientModeManagersInRole(
+                                    ROLE_CLIENT_SECONDARY_TRANSIENT);
+                            return;
+                        }
+                        // Else, use MBB if available.
+                        triggerConnectToNetworkUsingMbbIfAvailable(targetNetwork, targetBssid);
+                    }
+
+                    @Override
+                    public void triggerRoamWhenConnected(
+                            WifiConfiguration currentNetwork,
+                            WifiConfiguration targetNetwork,
+                            String targetBssid) {
+                        triggerRoamToNetworkUsingCmm(
+                                primaryManager, targetNetwork, targetBssid);
+                        // since using primary manager to connect, stop any existing managers in the
+                        // secondary transient role since they are no longer needed.
+                        mActiveModeWarden.stopAllClientModeManagersInRole(
+                                ROLE_CLIENT_SECONDARY_TRANSIENT);
+                    }
+                });
+
+    }
+
+    /**
+     * Trigger network connection for provided client mode manager without using make before break.
+     */
+    private void connectToNetworkUsingCmmWithoutMbb(
+            @NonNull ClientModeManager clientModeManager, @NonNull WifiConfiguration candidate) {
+        connectToNetworkUsingCmm(clientModeManager, candidate,
+                new ConnectHandler() {
+                    @Override
+                    public void triggerConnectWhenDisconnected(
+                            WifiConfiguration targetNetwork,
+                            String targetBssid) {
+                        triggerConnectToNetworkUsingCmm(
+                                clientModeManager, targetNetwork, targetBssid);
+                    }
+
+                    @Override
+                    public void triggerConnectWhenConnected(
+                            WifiConfiguration currentNetwork,
+                            WifiConfiguration targetNetwork,
+                            String targetBssid) {
+                        triggerConnectToNetworkUsingCmm(
+                                clientModeManager, targetNetwork, targetBssid);
+                    }
+
+                    @Override
+                    public void triggerRoamWhenConnected(
+                            WifiConfiguration currentNetwork,
+                            WifiConfiguration targetNetwork,
+                            String targetBssid) {
+                        triggerRoamToNetworkUsingCmm(
+                                clientModeManager, targetNetwork, targetBssid);
+                    }
+                });
+    }
+
+    /**
+     * Interface to use for trigger connection in various scenarios.
+     */
+    private interface ConnectHandler {
+        /**
+         * Invoked to trigger connection to a network when disconnected.
+         */
+        void triggerConnectWhenDisconnected(
+                @NonNull WifiConfiguration targetNetwork, @NonNull String targetBssid);
+        /**
+         * Invoked to trigger connection to a network when connected to a different network.
+         */
+        void triggerConnectWhenConnected(
+                @NonNull WifiConfiguration currentNetwork, @NonNull WifiConfiguration targetNetwork,
+                @NonNull String targetBssid);
+        /**
+         * Invoked to trigger roam to a specific bssid network when connected to a network.
+         */
+        void triggerRoamWhenConnected(
+                @NonNull WifiConfiguration currentNetwork, @NonNull WifiConfiguration targetNetwork,
+                @NonNull String targetBssid);
+    }
+
+    private String getAssociationId(@Nullable WifiConfiguration config, @Nullable String bssid) {
+        return config == null ? "Disconnected" : config.SSID + " : " + bssid;
+    }
+
     /**
      * Attempt to connect to a network candidate.
      *
-     * Based on the currently connected network, this menthod determines whether we should
+     * Based on the currently connected network, this method determines whether we should
      * connect or roam to the network candidate recommended by WifiNetworkSelector.
      */
-    private void connectToNetwork(WifiConfiguration candidate) {
-        ScanResult scanResultCandidate = candidate.getNetworkSelectionStatus().getCandidate();
-        if (scanResultCandidate == null) {
-            localLog("connectToNetwork: bad candidate - "  + candidate
-                    + " scanResult: " + scanResultCandidate);
+    private void connectToNetworkUsingCmm(@NonNull ClientModeManager clientModeManager,
+            @NonNull WifiConfiguration targetNetwork,
+            @NonNull ConnectHandler connectHandler) {
+        if (targetNetwork.getNetworkSelectionStatus().getCandidate() == null) {
+            localLog("connectToNetwork(" + clientModeManager + "): bad candidate - "
+                    + targetNetwork + " scanResult is null!");
+            return;
+        }
+        String targetBssid = targetNetwork.getNetworkSelectionStatus().getCandidate().BSSID;
+        String targetAssociationId = getAssociationId(targetNetwork, targetBssid);
+
+        if (isClientModeManagerConnectedOrConnectingToCandidate(clientModeManager, targetNetwork)) {
+            localLog("connectToNetwork(" + clientModeManager + "): either already connected or is "
+                    + "connecting to " + targetAssociationId);
             return;
         }
 
-        String targetBssid = scanResultCandidate.BSSID;
-        String targetAssociationId = candidate.SSID + " : " + targetBssid;
-
-        // Check if we are already connected or in the process of connecting to the target
-        // BSSID. mWifiInfo.mBSSID tracks the currently connected BSSID. This is checked just
-        // in case the firmware automatically roamed to a BSSID different from what
-        // WifiNetworkSelector selected.
-        if (targetBssid != null
-                && (targetBssid.equals(mLastConnectionAttemptBssid)
-                    || targetBssid.equals(mWifiInfo.getBSSID()))
-                && SupplicantState.isConnecting(mWifiInfo.getSupplicantState())) {
-            localLog("connectToNetwork: Either already connected "
-                    + "or is connecting to " + targetAssociationId);
+        if (targetNetwork.BSSID != null
+                && !targetNetwork.BSSID.equals(ClientModeImpl.SUPPLICANT_BSSID_ANY)
+                && !targetNetwork.BSSID.equals(targetBssid)) {
+            localLog("connectToNetwork(" + clientModeManager + "): target BSSID " + targetBssid
+                    + " does not match the config specified BSSID " + targetNetwork.BSSID
+                    + ". Drop it!");
             return;
         }
 
-        if (candidate.BSSID != null
-                && !candidate.BSSID.equals(ClientModeImpl.SUPPLICANT_BSSID_ANY)
-                && !candidate.BSSID.equals(targetBssid)) {
-            localLog("connecToNetwork: target BSSID " + targetBssid + " does not match the "
-                    + "config specified BSSID " + candidate.BSSID + ". Drop it!");
+        WifiConfiguration currentNetwork = coalesce(
+                clientModeManager.getConnectedWifiConfiguration(),
+                clientModeManager.getConnectingWifiConfiguration());
+        String currentBssid = coalesce(
+                clientModeManager.getConnectedBssid(), clientModeManager.getConnectingBssid());
+        String currentAssociationId = getAssociationId(currentNetwork, currentBssid);
+
+        // Already on desired network id, we need to trigger roam since the device does not
+        // support firmware roaming (already checked in
+        // isClientModeManagerConnectedOrConnectingToCandidate()).
+        if (currentNetwork != null
+                && (currentNetwork.networkId == targetNetwork.networkId
+                || (mContext.getResources().getBoolean(R.bool.config_wifiEnableLinkedNetworkRoaming)
+                && currentNetwork.isLinked(targetNetwork)))) {
+            localLog("connectToNetwork(" + clientModeManager + "): Roam to " + targetAssociationId
+                    + " from " + currentAssociationId);
+            connectHandler.triggerRoamWhenConnected(currentNetwork, targetNetwork, targetBssid);
             return;
         }
 
+        // Need to connect to a different network id
+        // Framework specifies the connection target BSSID if firmware doesn't support
+        // {@link android.net.wifi.WifiManager#WIFI_FEATURE_CONTROL_ROAMING} or the
+        // candidate configuration contains a specified BSSID.
+        if (mConnectivityHelper.isFirmwareRoamingSupported()
+                && (targetNetwork.BSSID == null
+                || targetNetwork.BSSID.equals(ClientModeImpl.SUPPLICANT_BSSID_ANY))) {
+            targetBssid = ClientModeImpl.SUPPLICANT_BSSID_ANY;
+        }
+        localLog("connectToNetwork(" + clientModeManager + "): Connect to "
+                + getAssociationId(targetNetwork, targetBssid) + " from "
+                + currentAssociationId);
+        if (currentNetwork == null) {
+            connectHandler.triggerConnectWhenDisconnected(targetNetwork, targetBssid);
+            return;
+        }
+        connectHandler.triggerConnectWhenConnected(currentNetwork, targetNetwork, targetBssid);
+    }
+
+    private boolean shouldConnect() {
         long elapsedTimeMillis = mClock.getElapsedSinceBootMillis();
         if (!mScreenOn && shouldSkipConnectionAttempt(elapsedTimeMillis)) {
             localLog("connectToNetwork: Too many connection attempts. Skipping this attempt!");
             mTotalConnectivityAttemptsRateLimited++;
-            return;
+            return false;
         }
         noteConnectionAttempt(elapsedTimeMillis);
+        return true;
+    }
 
-        mLastConnectionAttemptBssid = targetBssid;
-
-        WifiConfiguration currentConnectedNetwork = mConfigManager
-                .getConfiguredNetwork(mWifiInfo.getNetworkId());
-        String currentAssociationId = (currentConnectedNetwork == null) ? "Disconnected" :
-                (mWifiInfo.getSSID() + " : " + mWifiInfo.getBSSID());
-
-        if (currentConnectedNetwork != null
-                && (currentConnectedNetwork.networkId == candidate.networkId
-                //TODO(b/36788683): re-enable linked configuration check
-                /* || currentConnectedNetwork.isLinked(candidate) */)) {
-            // Framework initiates roaming only if firmware doesn't support
-            // {@link android.net.wifi.WifiManager#WIFI_FEATURE_CONTROL_ROAMING}.
-            if (mConnectivityHelper.isFirmwareRoamingSupported()) {
-                // Keep this logging here for now to validate the firmware roaming behavior.
-                localLog("connectToNetwork: Roaming candidate - " + targetAssociationId + "."
-                        + " The actual roaming target is up to the firmware.");
-            } else {
-                localLog("connectToNetwork: Roaming to " + targetAssociationId + " from "
-                        + currentAssociationId);
-                mStateMachine.startRoamToNetwork(candidate.networkId, scanResultCandidate);
-            }
-        } else {
-            // Framework specifies the connection target BSSID if firmware doesn't support
-            // {@link android.net.wifi.WifiManager#WIFI_FEATURE_CONTROL_ROAMING} or the
-            // candidate configuration contains a specified BSSID.
-            if (mConnectivityHelper.isFirmwareRoamingSupported() && (candidate.BSSID == null
-                      || candidate.BSSID.equals(ClientModeImpl.SUPPLICANT_BSSID_ANY))) {
-                targetBssid = ClientModeImpl.SUPPLICANT_BSSID_ANY;
-                localLog("connectToNetwork: Connect to " + candidate.SSID + ":" + targetBssid
-                        + " from " + currentAssociationId);
-            } else {
-                localLog("connectToNetwork: Connect to " + targetAssociationId + " from "
-                        + currentAssociationId);
-            }
-            mStateMachine.startConnectToNetwork(candidate.networkId, Process.WIFI_UID, targetBssid);
+    /**
+     * Trigger roaming to a new bssid while being connected to a different bssid in same network.
+     */
+    private void triggerRoamToNetworkUsingCmm(
+            @NonNull ClientModeManager clientModeManager,
+            @NonNull WifiConfiguration targetNetwork,
+            @NonNull String targetBssid) {
+        if (!shouldConnect()) {
+            return;
         }
+        clientModeManager.startRoamToNetwork(targetNetwork.networkId, targetBssid);
+    }
+
+    /**
+     * Trigger connection to a new wifi network while being disconnected.
+     */
+    private void triggerConnectToNetworkUsingCmm(
+            @NonNull ClientModeManager clientModeManager,
+            @NonNull WifiConfiguration targetNetwork, @NonNull String targetBssid) {
+        if (!shouldConnect()) {
+            return;
+        }
+        clientModeManager.startConnectToNetwork(
+                targetNetwork.networkId, Process.WIFI_UID, targetBssid);
+    }
+
+    /**
+     * Trigger connection to a new wifi network while being connected to another network.
+     * Depending on device configuration, this uses
+     *  - MBB make before break (Dual STA), or
+     *  - BBM break before make (Single STA)
+     */
+    private void triggerConnectToNetworkUsingMbbIfAvailable(
+            @NonNull WifiConfiguration targetNetwork, @NonNull String targetBssid) {
+        // Request a ClientModeManager from ActiveModeWarden to connect with - may be an existing
+        // CMM or a newly created one (potentially switching networks using Make-Before-Break)
+        mActiveModeWarden.requestSecondaryTransientClientModeManager(
+                (@Nullable ClientModeManager clientModeManager) -> {
+                    localLog("connectToNetwork: received requested ClientModeManager "
+                            + clientModeManager);
+                    if (clientModeManager == null) {
+                        localLog("connectToNetwork: Wifi has been toggled off, aborting");
+                        return;
+                    }
+                    // we don't know which ClientModeManager will be allocated to us. Thus, double
+                    // check if we're already connected before connecting.
+                    if (isClientModeManagerConnectedOrConnectingToCandidate(
+                            clientModeManager, targetNetwork)) {
+                        localLog("connectToNetwork: already connected or connecting to candidate="
+                                + targetNetwork + " on " + clientModeManager);
+                        return;
+                    }
+                    if (clientModeManager.getRole() == ROLE_CLIENT_SECONDARY_TRANSIENT) {
+                        mWifiMetrics.incrementMakeBeforeBreakTriggerCount();
+                    }
+                    triggerConnectToNetworkUsingCmm(clientModeManager, targetNetwork, targetBssid);
+                },
+                ActiveModeWarden.INTERNAL_REQUESTOR_WS,
+                targetNetwork.SSID,
+                mConnectivityHelper.isFirmwareRoamingSupported() ? null : targetBssid);
     }
 
     // Helper for selecting the band for connectivity scan
@@ -959,6 +1467,12 @@
 
     private int getScanBand(boolean isFullBandScan) {
         if (isFullBandScan) {
+            if (SdkLevel.isAtLeastS()) {
+                if (mContext.getResources().getBoolean(R.bool.config_wifiEnable6ghzPscScanning)) {
+                    return WifiScanner.WIFI_BAND_24_5_WITH_DFS_6_GHZ;
+                }
+                return WifiScanner.WIFI_BAND_BOTH_WITH_DFS;
+            }
             return WifiScanner.WIFI_BAND_ALL;
         } else {
             // Use channel list instead.
@@ -973,7 +1487,7 @@
     private boolean setScanChannels(ScanSettings settings) {
         Set<Integer> freqs;
 
-        WifiConfiguration config = mStateMachine.getCurrentWifiConfiguration();
+        WifiConfiguration config = getPrimaryClientModeManager().getConnectedWifiConfiguration();
         if (config == null) {
             long ageInMillis = 1000 * 60 * mContext.getResources().getInteger(
                     R.integer.config_wifiInitialPartialScanChannelCacheAgeMins);
@@ -1032,9 +1546,10 @@
         final int maxNumActiveChannelsForPartialScans = mContext.getResources().getInteger(
                 R.integer.config_wifi_framework_associated_partial_scan_max_num_active_channels);
         Set<Integer> channelSet = new HashSet<>();
+        WifiInfo wifiInfo = getPrimaryWifiInfo();
         // First add the currently connected network channel.
-        if (mWifiInfo.getFrequency() > 0) {
-            channelSet.add(mWifiInfo.getFrequency());
+        if (wifiInfo.getFrequency() > 0) {
+            channelSet.add(wifiInfo.getFrequency());
         }
         // Then get channels for the network.
         addChannelFromWifiScoreCard(channelSet, config, maxNumActiveChannelsForPartialScans,
@@ -1122,9 +1637,10 @@
                 <= 1000 * mContext.getResources().getInteger(
                 R.integer.config_wifiConnectedHighRssiScanMinimumWindowSizeSec));
 
+        WifiInfo wifiInfo = getPrimaryWifiInfo();
         boolean isGoodLinkAndAcceptableInternetAndShortTimeSinceLastNetworkSelection =
-                mNetworkSelector.hasSufficientLinkQuality(mWifiInfo)
-                && mNetworkSelector.hasInternetOrExpectNoInternet(mWifiInfo)
+                mNetworkSelector.hasSufficientLinkQuality(wifiInfo)
+                && mNetworkSelector.hasInternetOrExpectNoInternet(wifiInfo)
                 && isShortTimeSinceLastNetworkSelection;
         // Check it is one of following conditions to skip scan (with firmware roaming)
         // or do partial scan only (without firmware roaming).
@@ -1133,9 +1649,9 @@
         //    and it is a short time since last network selection
         // 3) There is active stream such that scan will be likely disruptive
         if (mWifiState == WIFI_STATE_CONNECTED
-                && (mNetworkSelector.isNetworkSufficient(mWifiInfo)
+                && (mNetworkSelector.isNetworkSufficient(wifiInfo)
                 || isGoodLinkAndAcceptableInternetAndShortTimeSinceLastNetworkSelection
-                || mNetworkSelector.hasActiveStream(mWifiInfo))) {
+                || mNetworkSelector.hasActiveStream(wifiInfo))) {
             // If only partial scan is proposed and firmware roaming control is supported,
             // we will not issue any scan because firmware roaming will take care of
             // intra-SSID roam.
@@ -1161,11 +1677,9 @@
                     setInitialScanState(INITIAL_SCAN_STATE_AWAITING_RESPONSE);
                     mWifiMetrics.incrementInitialPartialScanCount();
                 }
-                // No scheduling for another scan (until we get the results)
-                return;
+            } else {
+                startSingleScan(isFullBandScan, WIFI_WORK_SOURCE);
             }
-
-            startSingleScan(isFullBandScan, WIFI_WORK_SOURCE);
             schedulePeriodicScanTimer(
                     getScheduledSingleScanIntervalMs(mCurrentSingleScanScheduleIndex));
 
@@ -1191,11 +1705,19 @@
             if (index >= mCurrentSingleScanScheduleSec.length) {
                 index = mCurrentSingleScanScheduleSec.length - 1;
             }
-
-            return mCurrentSingleScanScheduleSec[index] * 1000;
+            return getScanIntervalWithPowerSaveMultiplier(
+                    mCurrentSingleScanScheduleSec[index] * 1000);
         }
     }
 
+    private int getScanIntervalWithPowerSaveMultiplier(int interval) {
+        if (!mDeviceConfigFacade.isWifiBatterySaverEnabled()) {
+            return interval;
+        }
+        return mPowerManager.isPowerSaveMode()
+                ? POWER_SAVE_SCAN_INTERVAL_MULTIPLIER * interval : interval;
+    }
+
     // Set the single scanning schedule
     private void setSingleScanningSchedule(int[] scheduleSec) {
         synchronized (mLock) {
@@ -1212,6 +1734,9 @@
 
     // Update the single scanning schedule if needed, and return true if update occurs
     private boolean updateSingleScanningSchedule() {
+        if (!mWifiEnabled || !mAutoJoinEnabled) {
+            return false;
+        }
         if (mWifiState != WIFI_STATE_CONNECTED) {
             // No need to update the scanning schedule
             return false;
@@ -1238,6 +1763,11 @@
         mInitialScanState = state;
     }
 
+    @VisibleForTesting
+    public int getInitialScanState() {
+        return mInitialScanState;
+    }
+
     // Reset the last periodic single scan time stamp so that the next periodic single
     // scan can start immediately.
     private void resetLastPeriodicSingleScanTimeStamp() {
@@ -1270,6 +1800,16 @@
         }
         settings.type = WifiScanner.SCAN_TYPE_HIGH_ACCURACY; // always do high accuracy scans.
         settings.band = getScanBand(isFullBandScan);
+        // Only enable RNR for full scans since we already have a known channel list for
+        // partial scan. We do not want to enable RNR for partial scan since it could end up
+        // wasting time scanning for 6Ghz APs that the device doesn't have credential to.
+        if (SdkLevel.isAtLeastS()) {
+            settings.setRnrSetting(isFullBandScan ? WifiScanner.WIFI_RNR_ENABLED
+                    : WifiScanner.WIFI_RNR_NOT_NEEDED);
+            settings.set6GhzPscOnlyEnabled(isFullBandScan
+                    ? mContext.getResources().getBoolean(R.bool.config_wifiEnable6ghzPscScanning)
+                    : false);
+        }
         settings.reportEvents = WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT
                             | WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
         settings.numBssidsPerScan = 0;
@@ -1317,11 +1857,11 @@
             case WifiManager.DEVICE_MOBILITY_STATE_UNKNOWN:
             case WifiManager.DEVICE_MOBILITY_STATE_LOW_MVMT:
             case WifiManager.DEVICE_MOBILITY_STATE_HIGH_MVMT:
-                return mContext.getResources()
-                        .getInteger(R.integer.config_wifiMovingPnoScanIntervalMillis);
+                return getScanIntervalWithPowerSaveMultiplier(mContext.getResources()
+                        .getInteger(R.integer.config_wifiMovingPnoScanIntervalMillis));
             case WifiManager.DEVICE_MOBILITY_STATE_STATIONARY:
-                return mContext.getResources()
-                        .getInteger(R.integer.config_wifiStationaryPnoScanIntervalMillis);
+                return getScanIntervalWithPowerSaveMultiplier(mContext.getResources()
+                        .getInteger(R.integer.config_wifiStationaryPnoScanIntervalMillis));
             default:
                 return -1;
         }
@@ -1396,8 +1936,6 @@
         scanSettings.numBssidsPerScan = 0;
         scanSettings.periodInMs = deviceMobilityStateToPnoScanIntervalMs(mDeviceMobilityState);
 
-        mPnoScanListener.clearScanDetails();
-
         mScanner.startDisconnectedPnoScan(
                 scanSettings, pnoSettings, new HandlerExecutor(mEventHandler), mPnoScanListener);
         mPnoScanStarted = true;
@@ -1444,8 +1982,6 @@
             addChannelFromWifiScoreCard(channelList, config, 0,
                     MAX_PNO_SCAN_FREQUENCY_AGE_MS);
             pnoNetwork.frequencies = channelList.stream().mapToInt(Integer::intValue).toArray();
-            localLog("retrievePnoNetworkList " + pnoNetwork.ssid + ":"
-                    + Arrays.toString(pnoNetwork.frequencies));
         }
         return pnoList;
     }
@@ -1464,7 +2000,8 @@
         localLog("scheduleWatchdogTimer");
 
         mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                            mClock.getElapsedSinceBootMillis() + WATCHDOG_INTERVAL_MS,
+                            mClock.getElapsedSinceBootMillis() + mContext.getResources().getInteger(
+                                    R.integer.config_wifiPnoWatchdogIntervalMs),
                             WATCHDOG_TIMER_TAG,
                             mWatchdogListener, mEventHandler);
         mWatchdogScanTimerSet = true;
@@ -1541,14 +2078,13 @@
                 + " wifiState=" + stateToString(mWifiState)
                 + " scanImmediately=" + scanImmediately
                 + " wifiEnabled=" + mWifiEnabled
-                + " wifiConnectivityManagerEnabled="
-                + mAutoJoinEnabled);
+                + " mAutoJoinEnabled=" + mAutoJoinEnabled);
 
         if (!mWifiEnabled || !mAutoJoinEnabled) {
             return;
         }
 
-        // Always stop outstanding connecivity scan if there is any
+        // Always stop outstanding connectivity scan if there is any
         stopConnectivityScan();
 
         // Don't start a connectivity scan while Wifi is in the transition
@@ -1565,7 +2101,6 @@
                 startDisconnectedPnoScan();
             }
         }
-
     }
 
     // Stop connectivity scan if there is any.
@@ -1580,7 +2115,7 @@
     /**
      * Handler for screen state (on/off) changes
      */
-    public void handleScreenStateChanged(boolean screenOn) {
+    private void handleScreenStateChanged(boolean screenOn) {
         localLog("handleScreenStateChanged: screenOn=" + screenOn);
 
         mScreenOn = screenOn;
@@ -1618,9 +2153,10 @@
      * 2. The device is connected to that network.
      */
     private boolean useSingleSavedNetworkSchedule() {
-        WifiConfiguration currentNetwork = mStateMachine.getCurrentWifiConfiguration();
+        WifiConfiguration currentNetwork =
+                getPrimaryClientModeManager().getConnectedWifiConfiguration();
         if (currentNetwork == null) {
-            localLog("Current network is missing, may caused by remove network and disconnecting ");
+            localLog("Current network is missing, may caused by remove network and disconnecting");
             return false;
         }
         List<WifiConfiguration> savedNetworks =
@@ -1631,7 +2167,7 @@
         }
 
         List<PasspointConfiguration> passpointNetworks =
-                mWifiInjector.getPasspointManager().getProviderConfigs(Process.WIFI_UID, true);
+                mPasspointManager.getProviderConfigs(Process.WIFI_UID, true);
         // If we have multiple networks (saved + passpoint), then no need to proceed
         if (passpointNetworks.size() + savedNetworks.size() > 1) {
             return false;
@@ -1661,7 +2197,7 @@
 
         // If we have a single suggestion network, and we are connected to it, return true.
         WifiNetworkSuggestion network = suggestionsNetworks.iterator().next();
-        String suggestionKey = network.getWifiConfiguration().getKey();
+        String suggestionKey = network.getWifiConfiguration().getProfileKey();
         WifiConfiguration config = mConfigManager.getConfiguredNetwork(suggestionKey);
         return (config != null && config.networkId == currentNetworkId);
     }
@@ -1684,7 +2220,15 @@
     /**
      * Handler for WiFi state (connected/disconnected) changes
      */
-    public void handleConnectionStateChanged(int state) {
+    public void handleConnectionStateChanged(
+            ConcreteClientModeManager clientModeManager, int state) {
+        List<ClientModeManager> internetConnectivityCmms =
+                mActiveModeWarden.getInternetConnectivityClientModeManagers();
+        if (!(internetConnectivityCmms.contains(clientModeManager))) {
+            Log.w(TAG, "Ignoring call from non primary Mode Manager " + clientModeManager,
+                    new Throwable());
+            return;
+        }
         localLog("handleConnectionStateChanged: state=" + stateToString(state));
 
         if (mConnectedSingleScanScheduleSec == null) {
@@ -1707,7 +2251,6 @@
         // Reset BSSID of last connection attempt and kick off
         // the watchdog timer if entering disconnected state.
         if (mWifiState == WIFI_STATE_DISCONNECTED) {
-            mLastConnectionAttemptBssid = null;
             scheduleWatchdogTimer();
             // Switch to the disconnected scanning schedule
             setSingleScanningSchedule(mDisconnectedSingleScanScheduleSec);
@@ -1735,12 +2278,20 @@
      * @param bssid the failed network.
      * @param ssid identifies the failed network.
      */
-    public void handleConnectionAttemptEnded(int failureCode, @NonNull String bssid,
-            @NonNull String ssid) {
+    public void handleConnectionAttemptEnded(@NonNull ActiveModeManager activeModeManager,
+            int failureCode, @NonNull String bssid, @NonNull String ssid) {
+        List<ClientModeManager> internetConnectivityCmms =
+                mActiveModeWarden.getInternetConnectivityClientModeManagers();
+        if (!(internetConnectivityCmms.contains(activeModeManager))) {
+            Log.w(TAG, "Ignoring call from non primary Mode Manager " + activeModeManager,
+                    new Throwable());
+            return;
+        }
+        WifiInfo wifiInfo = getPrimaryWifiInfo();
         if (failureCode == WifiMetrics.ConnectionEvent.FAILURE_NONE) {
-            String ssidUnquoted = (mWifiInfo.getWifiSsid() == null)
+            String ssidUnquoted = (wifiInfo.getWifiSsid() == null)
                     ? null
-                    : mWifiInfo.getWifiSsid().toString();
+                    : wifiInfo.getWifiSsid().toString();
             mOpenNetworkNotifier.handleWifiConnected(ssidUnquoted);
         } else {
             mOpenNetworkNotifier.handleConnectionFailure();
@@ -1759,7 +2310,17 @@
             MacAddress macAddress = MacAddress.fromString(bssid);
             int prevNumCandidates = mLatestCandidates.size();
             mLatestCandidates = mLatestCandidates.stream()
-                    .filter(candidate -> !macAddress.equals(candidate.getKey().bssid))
+                    .filter(candidate -> {
+                        // filter out the candidate with the BSSID that just failed
+                        if (macAddress.equals(candidate.getKey().bssid)) {
+                            return false;
+                        }
+                        // filter out candidates that are disabled.
+                        WifiConfiguration config =
+                                mConfigManager.getConfiguredNetwork(candidate.getNetworkConfigId());
+                        return config != null
+                                && config.getNetworkSelectionStatus().isNetworkEnabled();
+                    })
                     .collect(Collectors.toList());
             if (prevNumCandidates == mLatestCandidates.size()) {
                 return;
@@ -1769,16 +2330,15 @@
                 localLog("Automatic retry on the next best WNS candidate-" + candidate.SSID);
                 // Make sure that the failed BSSID is blocked for at least TEMP_BSSID_BLOCK_DURATION
                 // to prevent the supplicant from trying it again.
-                mBssidBlocklistMonitor.blockBssidForDurationMs(bssid, ssid,
+                mWifiBlocklistMonitor.blockBssidForDurationMs(bssid, ssid,
                         TEMP_BSSID_BLOCK_DURATION,
-                        BssidBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT, 0);
-                connectToNetwork(candidate);
+                        WifiBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT, 0);
+                connectToNetworkForPrimaryCmmUsingMbbIfAvailable(candidate);
             }
         } catch (IllegalArgumentException e) {
             localLog("retryConnectionOnLatestCandidates: failed to create MacAddress from bssid="
                     + bssid);
             mLatestCandidates = null;
-            return;
         }
     }
 
@@ -1788,7 +2348,8 @@
         // if auto-join was disabled externally, don't re-enable for any triggers.
         // External triggers to disable always trumps any internal state.
         setAutoJoinEnabled(mAutoJoinEnabledExternal
-                && (mUntrustedConnectionAllowed || mTrustedConnectionAllowed)
+                && (mUntrustedConnectionAllowed || mOemPaidConnectionAllowed
+                || mOemPrivateConnectionAllowed || mTrustedConnectionAllowed)
                 && !mSpecificNetworkRequestInProgress);
         startConnectivityScan(SCAN_IMMEDIATELY);
     }
@@ -1805,7 +2366,6 @@
         }
     }
 
-
     /**
      * Triggered when {@link UntrustedWifiNetworkFactory} has a pending ephemeral network request.
      */
@@ -1819,24 +2379,43 @@
     }
 
     /**
-     * Triggered when {@link WifiNetworkFactory} is processing a specific network request.
+     * Triggered when {@link OemPaidWifiNetworkFactory} has a pending network request.
      */
-    public void setSpecificNetworkRequestInProgress(boolean inProgress) {
-        localLog("setsetSpecificNetworkRequestInProgress : inProgress=" + inProgress);
+    public void setOemPaidConnectionAllowed(boolean allowed, WorkSource requestorWs) {
+        localLog("setOemPaidConnectionAllowed: allowed=" + allowed + ", requestorWs="
+                + requestorWs);
 
-        if (mSpecificNetworkRequestInProgress != inProgress) {
-            mSpecificNetworkRequestInProgress = inProgress;
+        if (mOemPaidConnectionAllowed != allowed) {
+            mOemPaidConnectionAllowed = allowed;
+            mOemPaidConnectionRequestorWs = requestorWs;
             checkAllStatesAndEnableAutoJoin();
         }
     }
 
     /**
-     * Handler when user specifies a particular network to connect to
+     * Triggered when {@link OemPrivateWifiNetworkFactory} has a pending network request.
      */
-    public void setUserConnectChoice(int netId) {
-        localLog("setUserConnectChoice: netId=" + netId);
+    public void setOemPrivateConnectionAllowed(boolean allowed, WorkSource requestorWs) {
+        localLog("setOemPrivateConnectionAllowed: allowed=" + allowed + ", requestorWs="
+                + requestorWs);
 
-        mNetworkSelector.setUserConnectChoice(netId);
+        if (mOemPrivateConnectionAllowed != allowed) {
+            mOemPrivateConnectionAllowed = allowed;
+            mOemPrivateConnectionRequestorWs = requestorWs;
+            checkAllStatesAndEnableAutoJoin();
+        }
+    }
+
+    /**
+     * Triggered when {@link WifiNetworkFactory} is processing a specific network request.
+     */
+    public void setSpecificNetworkRequestInProgress(boolean inProgress) {
+        localLog("setSpecificNetworkRequestInProgress : inProgress=" + inProgress);
+
+        if (mSpecificNetworkRequestInProgress != inProgress) {
+            mSpecificNetworkRequestInProgress = inProgress;
+            checkAllStatesAndEnableAutoJoin();
+        }
     }
 
     /**
@@ -1850,14 +2429,14 @@
         localLog("prepareForForcedConnection: SSID=" + config.SSID);
 
         clearConnectionAttemptTimeStamps();
-        mBssidBlocklistMonitor.clearBssidBlocklistForSsid(config.SSID);
+        mWifiBlocklistMonitor.clearBssidBlocklistForSsid(config.SSID);
     }
 
     /**
      * Handler for on-demand connectivity scan
      */
     public void forceConnectivityScan(WorkSource workSource) {
-        if (!mWifiEnabled) return;
+        if (!mWifiEnabled || !mRunning) return;
         localLog("forceConnectivityScan in request of " + workSource);
 
         clearConnectionAttemptTimeStamps();
@@ -1871,8 +2450,8 @@
      */
     private void retrieveWifiScanner() {
         if (mScanner != null) return;
-        mScanner = mWifiInjector.getWifiScanner();
-        checkNotNull(mScanner);
+        mScanner = Objects.requireNonNull(mContext.getSystemService(WifiScanner.class),
+                "Got a null instance of WifiScanner!");
         // Register for all single scan results
         mScanner.registerScanListener(new HandlerExecutor(mEventHandler), mAllSingleScanListener);
     }
@@ -1884,8 +2463,8 @@
         if (mRunning) return;
         retrieveWifiScanner();
         mConnectivityHelper.getFirmwareRoamingInfo();
-        mBssidBlocklistMonitor.clearBssidBlocklist();
-        mWifiChannelUtilization.init(mStateMachine.getWifiLinkLayerStats());
+        mWifiChannelUtilization.init(getPrimaryClientModeManager().getWifiLinkLayerStats());
+        clearConnectionAttemptTimeStamps(); // clear connection attempts.
 
         if (mContext.getResources().getBoolean(R.bool.config_wifiEnablePartialInitialScan)) {
             setInitialScanState(INITIAL_SCAN_STATE_START);
@@ -1906,7 +2485,6 @@
         cancelWatchdogScan();
         resetLastPeriodicSingleScanTimeStamp();
         mOpenNetworkNotifier.clearPendingNotification(true /* resetRepeatDelay */);
-        mLastConnectionAttemptBssid = null;
         mWaitForFullBandScanResults = false;
         mLatestCandidates = null;
         mLatestCandidatesTimestampMs = 0;
@@ -1932,12 +2510,21 @@
     /**
      * Inform WiFi is enabled for connection or not
      */
-    public void setWifiEnabled(boolean enable) {
+    private void setWifiEnabled(boolean enable) {
+        if (mWifiEnabled == enable) return;
+
         localLog("Set WiFi " + (enable ? "enabled" : "disabled"));
 
-        if (mWifiEnabled && !enable) {
+        if (!enable) {
             mNetworkSelector.resetOnDisable();
-            mBssidBlocklistMonitor.clearBssidBlocklist();
+            mConfigManager.enableTemporaryDisabledNetworks();
+            mConfigManager.stopRestrictingAutoJoinToSubscriptionId();
+            mConfigManager.clearUserTemporarilyDisabledList();
+            mConfigManager.removeAllEphemeralOrPasspointConfiguredNetworks();
+            // Flush ANQP cache if configured to do so
+            if (mWifiGlobals.flushAnqpCacheOnWifiToggleOffEvent()) {
+                mPasspointManager.clearAnqpRequestsAndFlushCache();
+            }
         }
         mWifiEnabled = enable;
         updateRunningState();
@@ -1982,6 +2569,6 @@
         mLocalLog.dump(fd, pw, args);
         pw.println("WifiConnectivityManager - Log End ----");
         mOpenNetworkNotifier.dump(fd, pw, args);
-        mBssidBlocklistMonitor.dump(fd, pw, args);
+        mWifiBlocklistMonitor.dump(fd, pw, args);
     }
 }
diff --git a/service/java/com/android/server/wifi/WifiContext.java b/service/java/com/android/server/wifi/WifiContext.java
index bbcb9d8..ca06301 100644
--- a/service/java/com/android/server/wifi/WifiContext.java
+++ b/service/java/com/android/server/wifi/WifiContext.java
@@ -40,7 +40,9 @@
     /** Intent action that is used to identify ServiceWifiResources.apk */
     private static final String ACTION_RESOURCES_APK =
             "com.android.server.wifi.intent.action.SERVICE_WIFI_RESOURCES_APK";
-    private static final String WIFI_OVERLAY_JAVA_PKG_NAME = "com.android.wifi.resources";
+
+    /** Since service-wifi runs within system_server, its package name is "android". */
+    private static final String SERVICE_WIFI_PACKAGE_NAME = "android";
 
     private String mWifiOverlayApkPkgName;
 
@@ -53,17 +55,7 @@
         super(contextBase);
     }
 
-    /**
-     * Get the Java package name of the resources in ServiceWifiResources.apk
-     *
-     * i.e. the package name of the Wifi Resources R class:
-     * {@code import com.android.wifi.resources.R;}, which is "com.android.wifi.resources"
-     */
-    public String getWifiOverlayJavaPkgName() {
-        return WIFI_OVERLAY_JAVA_PKG_NAME;
-    }
-
-    /** Get the Android application package name of ServiceWifiResources.apk */
+    /** Get the package name of ServiceWifiResources.apk */
     public String getWifiOverlayApkPkgName() {
         if (mWifiOverlayApkPkgName != null) {
             return mWifiOverlayApkPkgName;
@@ -149,4 +141,9 @@
         }
         return mWifiThemeFromApk;
     }
+
+    /** Get the package name that service-wifi runs under. */
+    public String getServiceWifiPackageName() {
+        return SERVICE_WIFI_PACKAGE_NAME;
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiCountryCode.java b/service/java/com/android/server/wifi/WifiCountryCode.java
index 3d05571..0398200 100644
--- a/service/java/com/android/server/wifi/WifiCountryCode.java
+++ b/service/java/com/android/server/wifi/WifiCountryCode.java
@@ -16,23 +16,30 @@
 
 package com.android.server.wifi;
 
-import android.content.BroadcastReceiver;
+import static com.android.server.wifi.WifiSettingsConfigStore.WIFI_DEFAULT_COUNTRY_CODE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Handler;
+import android.os.SystemProperties;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.wifi.resources.R;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.Date;
+import java.util.List;
 import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * Provide functions for making changes to WiFi country code.
@@ -42,97 +49,244 @@
  */
 public class WifiCountryCode {
     private static final String TAG = "WifiCountryCode";
+    private static final String BOOT_DEFAULT_WIFI_COUNTRY_CODE = "ro.boot.wificountrycode";
     private final Context mContext;
     private final TelephonyManager mTelephonyManager;
+    private final ActiveModeWarden mActiveModeWarden;
     private final WifiNative mWifiNative;
+    private final WifiSettingsConfigStore mSettingsConfigStore;
+    private List<ChangeListener> mListeners = new ArrayList<>();
     private boolean DBG = false;
-    private boolean mReady = false;
+    /**
+     * Map of active ClientModeManager instance to whether it is ready for country code change.
+     *
+     * - When a new ClientModeManager instance is created, it is added to this map and starts out
+     * ready for any country code changes (value = true).
+     * - When the ClientModeManager instance starts a connection attempt, it is marked not ready for
+     * country code changes (value = false).
+     * - When the ClientModeManager instance ends the connection, it is again marked ready for
+     * country code changes (value = true).
+     * - When the ClientModeManager instance is destroyed, it is removed from this map.
+     */
+    private final Map<ActiveModeManager, Boolean> mAmmToReadyForChangeMap =
+            new ArrayMap<>();
     private static final SimpleDateFormat FORMATTER = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
 
-    private String mDefaultCountryCode = null;
     private String mTelephonyCountryCode = null;
+    private String mOverrideCountryCode = null;
     private String mDriverCountryCode = null;
+    private String mReceivedDriverCountryCode = null;
     private String mTelephonyCountryTimestamp = null;
     private String mDriverCountryTimestamp = null;
     private String mReadyTimestamp = null;
-    private boolean mForceCountryCode = false;
+
+    private class ModeChangeCallbackInternal implements ActiveModeWarden.ModeChangeCallback {
+        @Override
+        public void onActiveModeManagerAdded(@NonNull ActiveModeManager activeModeManager) {
+            if (activeModeManager.getRole() instanceof ActiveModeManager.ClientRole
+                    || activeModeManager instanceof SoftApManager) {
+                // Add this CMM for tracking. Interface is up and HAL is initialized at this point.
+                // If this device runs the 1.5 HAL version, use the IWifiChip.setCountryCode()
+                // to set the country code.
+                mAmmToReadyForChangeMap.put(activeModeManager, true);
+                evaluateAllCmmStateAndApplyIfAllReady();
+            }
+        }
+
+        @Override
+        public void onActiveModeManagerRemoved(@NonNull ActiveModeManager activeModeManager) {
+            if (mAmmToReadyForChangeMap.remove(activeModeManager) != null) {
+                // Remove this CMM from tracking.
+                evaluateAllCmmStateAndApplyIfAllReady();
+            }
+        }
+
+        @Override
+        public void onActiveModeManagerRoleChanged(@NonNull ActiveModeManager activeModeManager) {
+            if (activeModeManager.getRole() == ActiveModeManager.ROLE_CLIENT_PRIMARY) {
+                // Set this CMM ready for change. This is needed to handle the transition from
+                // ROLE_CLIENT_SCAN_ONLY to ROLE_CLIENT_PRIMARY on devices running older HAL
+                // versions (since the IWifiChip.setCountryCode() was only added in the 1.5 HAL
+                // version, before that we need to wait till supplicant is up for country code
+                // change.
+                mAmmToReadyForChangeMap.put(activeModeManager, true);
+                evaluateAllCmmStateAndApplyIfAllReady();
+            }
+        }
+    }
+
+    private class ClientModeListenerInternal implements ClientModeImplListener {
+        @Override
+        public void onConnectionStart(@NonNull ConcreteClientModeManager clientModeManager) {
+            if (mAmmToReadyForChangeMap.get(clientModeManager) == null) {
+                Log.wtf(TAG, "Connection start received from unknown client mode manager");
+            }
+            // connection start. CMM not ready for country code change.
+            mAmmToReadyForChangeMap.put(clientModeManager, false);
+            evaluateAllCmmStateAndApplyIfAllReady();
+        }
+
+        @Override
+        public void onConnectionEnd(@NonNull ConcreteClientModeManager clientModeManager) {
+            if (mAmmToReadyForChangeMap.get(clientModeManager) == null) {
+                Log.wtf(TAG, "Connection end received from unknown client mode manager");
+            }
+            // connection end. CMM ready for country code change.
+            mAmmToReadyForChangeMap.put(clientModeManager, true);
+            evaluateAllCmmStateAndApplyIfAllReady();
+        }
+
+    }
+
+    private class CountryChangeListenerInternal implements ChangeListener {
+        @Override
+        public void onDriverCountryCodeChanged(String country) {
+            if (TextUtils.equals(country, mReceivedDriverCountryCode)) {
+                return;
+            }
+            Log.i(TAG, "Receive onDriverCountryCodeChanged " + country);
+            mReceivedDriverCountryCode = country;
+            updateDriverCountryCodeAndNotifyListener(country);
+        }
+
+        @Override
+        public void onSetCountryCodeSucceeded(String country) {
+            Log.i(TAG, "Receive onSetCountryCodeSucceeded " + country);
+            // The driver country code updated, don't need to trigger again.
+            if (TextUtils.equals(country, mReceivedDriverCountryCode)) {
+                return;
+            }
+            updateDriverCountryCodeAndNotifyListener(country);
+        }
+    }
 
     public WifiCountryCode(
             Context context,
-            Handler handler,
+            ActiveModeWarden activeModeWarden,
+            ClientModeImplMonitor clientModeImplMonitor,
             WifiNative wifiNative,
-            String oemDefaultCountryCode) {
+            @NonNull WifiSettingsConfigStore settingsConfigStore) {
         mContext = context;
         mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+        mActiveModeWarden = activeModeWarden;
         mWifiNative = wifiNative;
+        mSettingsConfigStore = settingsConfigStore;
 
-        if (!TextUtils.isEmpty(oemDefaultCountryCode)) {
-            mDefaultCountryCode = oemDefaultCountryCode.toUpperCase(Locale.US);
+        mActiveModeWarden.registerModeChangeCallback(new ModeChangeCallbackInternal());
+        clientModeImplMonitor.registerListener(new ClientModeListenerInternal());
+        mWifiNative.registerCountryCodeEventListener(new CountryChangeListenerInternal());
+
+        Log.d(TAG, "Default country code from system property "
+                + BOOT_DEFAULT_WIFI_COUNTRY_CODE + " is " + getOemDefaultCountryCode());
+    }
+
+    /**
+     * Default country code stored in system property
+     * @return Country code if available, null otherwise.
+     */
+    public static String getOemDefaultCountryCode() {
+        String country = SystemProperties.get(BOOT_DEFAULT_WIFI_COUNTRY_CODE);
+        return WifiCountryCode.isValid(country) ? country.toUpperCase(Locale.US) : null;
+    }
+
+    /**
+     * Is this a valid country code
+     * @param countryCode A 2-Character alphanumeric country code.
+     * @return true if the countryCode is valid, false otherwise.
+     */
+    public static boolean isValid(String countryCode) {
+        return countryCode != null && countryCode.length() == 2
+                && countryCode.chars().allMatch(Character::isLetterOrDigit);
+    }
+
+    /**
+     * The class for country code related change listener
+     */
+    public interface ChangeListener {
+        /**
+         * Called when receiving country code changed from driver.
+         */
+        void onDriverCountryCodeChanged(String countryCode);
+
+        /**
+         * Called when country code set to native layer successful, framework sends event to
+         * force country code changed.
+         *
+         * Reason: The country code change listener from wificond rely on driver supported
+         * NL80211_CMD_REG_CHANGE/NL80211_CMD_WIPHY_REG_CHANGE. Trigger update country code
+         * to listener here for non-supported platform.
+         */
+        default void onSetCountryCodeSucceeded(String country) {}
+    }
+
+    /**
+     * Register Country code changed listener.
+     */
+    public void registerListener(@NonNull ChangeListener listener) {
+        mListeners.add(listener);
+        if (mDriverCountryCode != null) {
+            listener.onDriverCountryCodeChanged(mDriverCountryCode);
         }
-        context.registerReceiver(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                String countryCode = intent.getStringExtra(TelephonyManager.EXTRA_NETWORK_COUNTRY);
-                Log.d(TAG, "Country code changed");
-                setCountryCodeAndUpdate(countryCode);
-            }}, new IntentFilter(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED), null, handler);
-
-        Log.d(TAG, "mDefaultCountryCode " + mDefaultCountryCode);
     }
 
     /**
      * Enable verbose logging for WifiCountryCode.
      */
-    public void enableVerboseLogging(int verbose) {
-        if (verbose > 0) {
-            DBG = true;
-        } else {
-            DBG = false;
-        }
+    public void enableVerboseLogging(boolean verbose) {
+        DBG = verbose;
     }
 
     private void initializeTelephonyCountryCodeIfNeeded() {
         // If we don't have telephony country code set yet, poll it.
         if (mTelephonyCountryCode == null) {
             Log.d(TAG, "Reading country code from telephony");
-            setCountryCode(mTelephonyManager.getNetworkCountryIso());
+            setTelephonyCountryCode(mTelephonyManager.getNetworkCountryIso());
         }
     }
 
     /**
-     * Change the state to indicates if wpa_supplicant is ready to handle country code changing
-     * request or not.
-     * We call native code to request country code changes only when wpa_supplicant is
-     * started but not yet L2 connected.
+     * We call native code to request country code changes only if all {@link ClientModeManager}
+     * instances are ready for country code change. Country code is a chip level configuration and
+     * results in all the connections on the chip being disrupted.
+     *
+     * @return true if there are active CMM's and all are ready for country code change.
      */
-    public synchronized void setReadyForChange(boolean ready) {
-        mReady = ready;
-        mReadyTimestamp = FORMATTER.format(new Date(System.currentTimeMillis()));
-        // We are ready to set country code now.
-        // We need to post pending country code request.
-        if (mReady) {
+    private boolean isReady() {
+        return !mAmmToReadyForChangeMap.isEmpty()
+                && mAmmToReadyForChangeMap.values().stream().allMatch(r -> r);
+    }
+
+    /**
+     * Check all active CMM instances and apply country code change if ready.
+     */
+    private void evaluateAllCmmStateAndApplyIfAllReady() {
+        Log.d(TAG, "evaluateAllCmmStateAndApplyIfAllReady: " + mAmmToReadyForChangeMap);
+        if (isReady()) {
+            mReadyTimestamp = FORMATTER.format(new Date(System.currentTimeMillis()));
+            // We are ready to set country code now.
+            // We need to post pending country code request.
+            initializeTelephonyCountryCodeIfNeeded();
             updateCountryCode();
         }
     }
 
     /**
-     * Enable force-country-code mode
-     * This is for forcing a country using cmd wifi from adb shell
+     * This call will override any existing country code.
      * This is for test purpose only and we should disallow any update from
-     * telephony in this mode
-     * @param countryCode The forced two-letter country code
+     * telephony in this mode.
+     * @param countryCode A 2-Character alphanumeric country code.
      */
-    synchronized void enableForceCountryCode(String countryCode) {
+    public synchronized void setOverrideCountryCode(String countryCode) {
         if (TextUtils.isEmpty(countryCode)) {
-            Log.d(TAG, "Fail to force country code because the received country code is empty");
+            Log.d(TAG, "Fail to override country code because"
+                    + "the received country code is empty");
             return;
         }
-        mForceCountryCode = true;
-        mTelephonyCountryCode = countryCode.toUpperCase(Locale.US);
+        mOverrideCountryCode = countryCode.toUpperCase(Locale.US);
 
         // If wpa_supplicant is ready we set the country code now, otherwise it will be
         // set once wpa_supplicant is ready.
-        if (mReady) {
+        if (isReady()) {
             updateCountryCode();
         } else {
             Log.d(TAG, "skip update supplicant not ready yet");
@@ -140,26 +294,21 @@
     }
 
     /**
-     * Disable force-country-code mode
+     * This is for clearing the country code previously set through #setOverrideCountryCode() method
      */
-    synchronized void disableForceCountryCode() {
-        mForceCountryCode = false;
-        mTelephonyCountryCode = null;
+    public synchronized void clearOverrideCountryCode() {
+        mOverrideCountryCode = null;
 
         // If wpa_supplicant is ready we set the country code now, otherwise it will be
         // set once wpa_supplicant is ready.
-        if (mReady) {
+        if (isReady()) {
             updateCountryCode();
         } else {
             Log.d(TAG, "skip update supplicant not ready yet");
         }
     }
 
-    private boolean setCountryCode(String countryCode) {
-        if (mForceCountryCode) {
-            Log.d(TAG, "Telephony Country code ignored due to force-country-code mode");
-            return false;
-        }
+    private void setTelephonyCountryCode(String countryCode) {
         Log.d(TAG, "Set telephony country code to: " + countryCode);
         mTelephonyCountryTimestamp = FORMATTER.format(new Date(System.currentTimeMillis()));
 
@@ -173,7 +322,6 @@
         } else {
             mTelephonyCountryCode = countryCode.toUpperCase(Locale.US);
         }
-        return true;
     }
 
     /**
@@ -183,11 +331,15 @@
      * otherwise we think it is from other applications.
      * @return Returns true if the country code passed in is acceptable.
      */
-    private boolean setCountryCodeAndUpdate(String countryCode) {
-        if (!setCountryCode(countryCode)) return false;
+    public boolean setTelephonyCountryCodeAndUpdate(String countryCode) {
+        setTelephonyCountryCode(countryCode);
+        if (mOverrideCountryCode != null) {
+            Log.d(TAG, "Skip Telephony Country code update due to override country code set");
+            return false;
+        }
         // If wpa_supplicant is ready we set the country code now, otherwise it will be
         // set once wpa_supplicant is ready.
-        if (mReady) {
+        if (isReady()) {
             updateCountryCode();
         } else {
             Log.d(TAG, "skip update supplicant not ready yet");
@@ -201,7 +353,7 @@
      *
      * @return Returns the local copy of the Country Code that was sent to the driver upon
      * setReadyForChange(true).
-     * If wpa_supplicant was never started, this may be null even if a SIM reported a valid
+     * If wpa_supplicant was never started, this may be null even if Telephony reported a valid
      * country code.
      * Returns null if no Country Code was sent to driver.
      */
@@ -211,30 +363,59 @@
     }
 
     /**
-     * Method to return the currently reported Country Code from the SIM or phone default setting.
+     * Method to return the currently reported Country Code resolved from various sources:
+     * e.g. default country code, cellular network country code, country code override, etc.
      *
-     * @return The currently reported Country Code from the SIM. If there is no Country Code
-     * reported from SIM, a phone default Country Code will be returned.
-     * Returns null when there is no Country Code available.
+     * @return The current Wifi Country Code resolved from various sources. Returns null when there
+     * is no Country Code available.
      */
+    @Nullable
     public synchronized String getCountryCode() {
+        initializeTelephonyCountryCodeIfNeeded();
         return pickCountryCode();
     }
 
     /**
+     * set default country code
+     * @param countryCode A 2-Character alphanumeric country code.
+     */
+    public synchronized void setDefaultCountryCode(String countryCode) {
+        if (TextUtils.isEmpty(countryCode)) {
+            Log.d(TAG, "Fail to set default country code because the country code is empty");
+            return;
+        }
+
+        mSettingsConfigStore.put(WIFI_DEFAULT_COUNTRY_CODE,
+                countryCode.toUpperCase(Locale.US));
+        Log.i(TAG, "Default country code updated in config store: " + countryCode);
+
+        // If wpa_supplicant is ready we set the country code now, otherwise it will be
+        // set once wpa_supplicant is ready.
+        if (isReady()) {
+            updateCountryCode();
+        } else {
+            Log.d(TAG, "skip update supplicant not ready yet");
+        }
+    }
+
+    /**
      * Method to dump the current state of this WifiCounrtyCode object.
      */
     public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("mRevertCountryCodeOnCellularLoss: "
                 + mContext.getResources().getBoolean(
                         R.bool.config_wifi_revert_country_code_on_cellular_loss));
-        pw.println("mDefaultCountryCode: " + mDefaultCountryCode);
+        pw.println("DefaultCountryCode(system property): " + getOemDefaultCountryCode());
+        pw.println("DefaultCountryCode(config store): "
+                + mSettingsConfigStore.get(WIFI_DEFAULT_COUNTRY_CODE));
         pw.println("mDriverCountryCode: " + mDriverCountryCode);
         pw.println("mTelephonyCountryCode: " + mTelephonyCountryCode);
         pw.println("mTelephonyCountryTimestamp: " + mTelephonyCountryTimestamp);
+        pw.println("mOverrideCountryCode: " + mOverrideCountryCode);
         pw.println("mDriverCountryTimestamp: " + mDriverCountryTimestamp);
         pw.println("mReadyTimestamp: " + mReadyTimestamp);
-        pw.println("mReady: " + mReady);
+        pw.println("isReady: " + isReady());
+        pw.println("mAmmToReadyForChangeMap: " + mAmmToReadyForChangeMap);
     }
 
     private void updateCountryCode() {
@@ -244,7 +425,7 @@
         // We do not check if the country code equals the current one.
         // There are two reasons:
         // 1. Wpa supplicant may silently modify the country code.
-        // 2. If Wifi restarted therefoere wpa_supplicant also restarted,
+        // 2. If Wifi restarted therefore wpa_supplicant also restarted,
         // the country code counld be reset to '00' by wpa_supplicant.
         if (country != null) {
             setCountryCodeNative(country);
@@ -255,28 +436,65 @@
     }
 
     private String pickCountryCode() {
-
-        initializeTelephonyCountryCodeIfNeeded();
-
+        if (mOverrideCountryCode != null) {
+            return mOverrideCountryCode;
+        }
         if (mTelephonyCountryCode != null) {
             return mTelephonyCountryCode;
         }
-        if (mDefaultCountryCode != null) {
-            return mDefaultCountryCode;
-        }
-        // If there is no candidate country code we will return null.
-        return null;
+        return mSettingsConfigStore.get(WIFI_DEFAULT_COUNTRY_CODE);
     }
 
     private boolean setCountryCodeNative(String country) {
-        mDriverCountryTimestamp = FORMATTER.format(new Date(System.currentTimeMillis()));
-        if (mWifiNative.setCountryCode(mWifiNative.getClientInterfaceName(), country)) {
-            Log.d(TAG, "Succeeded to set country code to: " + country);
-            mDriverCountryCode = country;
-            return true;
+        Set<ActiveModeManager> amms = mAmmToReadyForChangeMap.keySet();
+        boolean isConcreteClientModeManagerUpdated = false;
+        boolean anyAmmConfigured = false;
+        for (ActiveModeManager am : amms) {
+            if (!isConcreteClientModeManagerUpdated && am instanceof ConcreteClientModeManager) {
+                // Set the country code using one of the active mode managers. Since
+                // country code is a chip level global setting, it can be set as long
+                // as there is at least one active interface to communicate to Wifi chip
+                ConcreteClientModeManager cm = (ConcreteClientModeManager) am;
+                if (!cm.setCountryCode(country)) {
+                    Log.d(TAG, "Failed to set country code (ConcreteClientModeManager) to "
+                            + country);
+                } else {
+                    isConcreteClientModeManagerUpdated = true;
+                    anyAmmConfigured = true;
+                    // Start from S, frameworks support country code callback from wificond,
+                    // move "notify the lister" to CountryChangeListenerInternal.
+                    if (!SdkLevel.isAtLeastS()) {
+                        updateDriverCountryCodeAndNotifyListener(country);
+                    }
+                }
+            } else if (am instanceof SoftApManager) {
+                // The API:updateCountryCode in SoftApManager is asynchronous, it requires a new
+                // callback support in S to trigger "updateDriverCountryCodeAndNotifyListener" for
+                // the new S API: SoftApCapability#getSupportedChannelList(band).
+                // It requires:
+                // 1. a new overlay configuration which is introduced from S.
+                // 2. wificond support in S for S API: SoftApCapability#getSupportedChannelList
+                // Any case if device supported to set country code in R,
+                // the new S API: SoftApCapability#getSupportedChannelList(band) still doesn't work
+                // normally in R build when wifi disabled.
+                SoftApManager sm = (SoftApManager) am;
+                if (!sm.updateCountryCode(country)) {
+                    Log.d(TAG, "Can't set country code (SoftApManager) to "
+                            + country + " (Device doesn't support it)");
+                } else {
+                    anyAmmConfigured = true;
+                }
+            }
         }
-        Log.d(TAG, "Failed to set country code to: " + country);
-        return false;
+        return anyAmmConfigured;
+    }
+
+    private void updateDriverCountryCodeAndNotifyListener(String country) {
+        mDriverCountryTimestamp = FORMATTER.format(new Date(System.currentTimeMillis()));
+        mDriverCountryCode = country;
+        for (ChangeListener listener : mListeners) {
+            listener.onDriverCountryCodeChanged(country);
+        }
     }
 }
 
diff --git a/service/java/com/android/server/wifi/WifiDataStall.java b/service/java/com/android/server/wifi/WifiDataStall.java
index fd75032..bb9b9bb 100644
--- a/service/java/com/android/server/wifi/WifiDataStall.java
+++ b/service/java/com/android/server/wifi/WifiDataStall.java
@@ -18,22 +18,28 @@
 
 import static com.android.server.wifi.util.InformationElementUtil.BssLoad.CHANNEL_UTILIZATION_SCALE;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
-import android.net.wifi.ScanResult;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager.DeviceMobilityState;
 import android.os.Handler;
 import android.telephony.PhoneStateListener;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
 import com.android.modules.utils.HandlerExecutor;
-import com.android.server.wifi.proto.WifiStatsLog;
+import com.android.server.wifi.ActiveModeWarden.PrimaryClientModeManagerChangedCallback;
+import com.android.server.wifi.WifiNative.ConnectionCapabilities;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.WifiIsUnusableEvent;
-import com.android.server.wifi.scanner.KnownBandsChannelHelper;
 import com.android.server.wifi.util.InformationElementUtil.BssLoad;
 import com.android.wifi.resources.R;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Looks for Wifi data stalls
  */
@@ -62,7 +68,8 @@
     private final WifiChannelUtilization mWifiChannelUtilization;
     private TelephonyManager mTelephonyManager;
     private final ThroughputPredictor mThroughputPredictor;
-    private WifiNative.ConnectionCapabilities mConnectionCapabilities;
+    private final ActiveModeWarden mActiveModeWarden;
+    private final ClientModeImplMonitor mClientModeImplMonitor;
 
     private int mLastFrequency = -1;
     private String mLastBssid;
@@ -79,9 +86,72 @@
     private int mTxTputKbps = INVALID_THROUGHPUT;
     private int mRxTputKbps = INVALID_THROUGHPUT;
 
+    /** @hide */
+    @IntDef(prefix = { "CELLULAR_DATA_" }, value = {
+            CELLULAR_DATA_UNKNOWN,
+            CELLULAR_DATA_AVAILABLE,
+            CELLULAR_DATA_NOT_AVAILABLE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CellularDataStatusCode {}
+    public static final int CELLULAR_DATA_UNKNOWN = 0;
+    public static final int CELLULAR_DATA_AVAILABLE = 1;
+    public static final int CELLULAR_DATA_NOT_AVAILABLE = 2;
+
+    private class ModeChangeCallback implements ActiveModeWarden.ModeChangeCallback {
+        @Override
+        public void onActiveModeManagerAdded(@NonNull ActiveModeManager activeModeManager) {
+            update();
+        }
+
+        @Override
+        public void onActiveModeManagerRemoved(@NonNull ActiveModeManager activeModeManager) {
+            update();
+        }
+
+        @Override
+        public void onActiveModeManagerRoleChanged(@NonNull ActiveModeManager activeModeManager) {
+            update();
+        }
+
+        private void update() {
+            // Register/Unregister phone listener on wifi on/off.
+            if (mActiveModeWarden.getPrimaryClientModeManagerNullable() != null) {
+                enablePhoneStateListener();
+            } else {
+                disablePhoneStateListener();
+            }
+        }
+    }
+
+    private class PrimaryModeChangeCallback implements PrimaryClientModeManagerChangedCallback {
+        @Override
+        public void onChange(
+                @Nullable ConcreteClientModeManager prevPrimaryClientModeManager,
+                @Nullable ConcreteClientModeManager newPrimaryClientModeManager) {
+            // This is needed to reset state on an MBB switch or wifi toggle.
+            if (prevPrimaryClientModeManager != null) {
+                reset();
+            }
+            if (newPrimaryClientModeManager != null) {
+                init();
+            }
+        }
+    }
+
+    private class ClientModeImplListenerInternal implements ClientModeImplListener {
+        @Override
+        public void onConnectionEnd(@NonNull ConcreteClientModeManager clientModeManager) {
+            if (clientModeManager.getRole() == ActiveModeManager.ROLE_CLIENT_PRIMARY) {
+                reset();
+            }
+        }
+    }
+
     public WifiDataStall(FrameworkFacade facade, WifiMetrics wifiMetrics, Context context,
             DeviceConfigFacade deviceConfigFacade, WifiChannelUtilization wifiChannelUtilization,
-            Clock clock, Handler handler, ThroughputPredictor throughputPredictor) {
+            Clock clock, Handler handler, ThroughputPredictor throughputPredictor,
+            ActiveModeWarden activeModeWarden, ClientModeImplMonitor clientModeImplMonitor) {
         mFacade = facade;
         mDeviceConfigFacade = deviceConfigFacade;
         mWifiMetrics = wifiMetrics;
@@ -90,26 +160,33 @@
         mWifiChannelUtilization = wifiChannelUtilization;
         mWifiChannelUtilization.setCacheUpdateIntervalMs(LLSTATS_CACHE_UPDATE_INTERVAL_MIN_MS);
         mThroughputPredictor = throughputPredictor;
+        mActiveModeWarden = activeModeWarden;
+        mClientModeImplMonitor = clientModeImplMonitor;
         mPhoneStateListener = new PhoneStateListener(new HandlerExecutor(handler)) {
             @Override
             public void onDataConnectionStateChanged(int state, int networkType) {
-                if (state == TelephonyManager.DATA_CONNECTED) {
-                    mIsCellularDataAvailable = true;
-                } else if (state == TelephonyManager.DATA_DISCONNECTED) {
-                    mIsCellularDataAvailable = false;
-                } else {
+                if (state != TelephonyManager.DATA_CONNECTED
+                        && state != TelephonyManager.DATA_DISCONNECTED) {
                     Log.e(TAG, "onDataConnectionStateChanged unexpected State: " + state);
                     return;
                 }
+                mIsCellularDataAvailable = state == TelephonyManager.DATA_CONNECTED;
+                mActiveModeWarden.getPrimaryClientModeManager()
+                        .onCellularConnectivityChanged(mIsCellularDataAvailable
+                                ? CELLULAR_DATA_AVAILABLE : CELLULAR_DATA_NOT_AVAILABLE);
                 logd("Cellular Data: " + mIsCellularDataAvailable);
             }
         };
+        mActiveModeWarden.registerPrimaryClientModeManagerChangedCallback(
+                new PrimaryModeChangeCallback());
+        mActiveModeWarden.registerModeChangeCallback(new ModeChangeCallback());
+        mClientModeImplMonitor.registerListener(new ClientModeImplListenerInternal());
     }
 
     /**
      * initialization after wifi is enabled
      */
-    public void init() {
+    private void init() {
         mWifiChannelUtilization.init(null);
         reset();
     }
@@ -117,7 +194,7 @@
     /**
      * Reset internal variables
      */
-    public void reset() {
+    private void reset() {
         mLastTxBytes = 0;
         mLastRxBytes = 0;
         mLastFrequency = -1;
@@ -130,20 +207,34 @@
         mRxTputKbps = INVALID_THROUGHPUT;
     }
 
-    /**
-     * Set ConnectionCapabilities after each association and roaming
-     */
-    public void setConnectionCapabilities(WifiNative.ConnectionCapabilities capabilities) {
-        mConnectionCapabilities = capabilities;
-    }
-    /**
-     * Enable phone state listener
-     */
-    public void enablePhoneStateListener() {
+    private void createTelephonyManagerForDefaultDataSubIfNeeded() {
         if (mTelephonyManager == null) {
             mTelephonyManager = (TelephonyManager) mContext
                     .getSystemService(Context.TELEPHONY_SERVICE);
         }
+        int defaultSubscriptionId = SubscriptionManager.getDefaultDataSubscriptionId();
+        if (defaultSubscriptionId != SubscriptionManager.INVALID_SUBSCRIPTION_ID
+                && defaultSubscriptionId != mTelephonyManager.getSubscriptionId()) {
+            mTelephonyManager = mTelephonyManager.createForSubscriptionId(
+                    SubscriptionManager.getDefaultDataSubscriptionId());
+        }
+    }
+
+    /**
+     * Reset the PhoneStateListener to listen on the default data SIM.
+     */
+    public void resetPhoneStateListener() {
+        disablePhoneStateListener();
+        mActiveModeWarden.getPrimaryClientModeManager()
+                .onCellularConnectivityChanged(CELLULAR_DATA_UNKNOWN);
+        enablePhoneStateListener();
+    }
+
+    /**
+     * Enable phone state listener
+     */
+    private void enablePhoneStateListener() {
+        createTelephonyManagerForDefaultDataSubIfNeeded();
         if (mTelephonyManager != null && !mPhoneStateListenerEnabled) {
             mPhoneStateListenerEnabled = true;
             mTelephonyManager.listen(mPhoneStateListener,
@@ -154,7 +245,7 @@
     /**
      * Disable phone state listener
      */
-    public void disablePhoneStateListener() {
+    private void disablePhoneStateListener() {
         if (mTelephonyManager != null && mPhoneStateListenerEnabled) {
             mPhoneStateListenerEnabled = false;
             mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
@@ -218,25 +309,32 @@
     /**
      * Update data stall detection, check throughput sufficiency and report wifi health stat
      * with the latest link layer stats
+     * @param connectionCapabilities Connection capabilities.
      * @param oldStats second most recent WifiLinkLayerStats
      * @param newStats most recent WifiLinkLayerStats
      * @param wifiInfo WifiInfo for current connection
      * @return trigger type of WifiIsUnusableEvent
+     *
+     * Note: This is only collected for primary STA currently because RSSI polling is disabled for
+     * non-primary STAs.
      */
-    public int checkDataStallAndThroughputSufficiency(WifiLinkLayerStats oldStats,
-            WifiLinkLayerStats newStats, WifiInfo wifiInfo) {
+    public int checkDataStallAndThroughputSufficiency(
+            @NonNull String ifaceName,
+            @NonNull ConnectionCapabilities connectionCapabilities,
+            @Nullable WifiLinkLayerStats oldStats,
+            @Nullable WifiLinkLayerStats newStats,
+            @NonNull WifiInfo wifiInfo) {
         int currFrequency = wifiInfo.getFrequency();
         mWifiChannelUtilization.refreshChannelStatsAndChannelUtilization(newStats, currFrequency);
         int ccaLevel = mWifiChannelUtilization.getUtilizationRatio(currFrequency);
         mWifiMetrics.incrementChannelUtilizationCount(ccaLevel, currFrequency);
-
         if (oldStats == null || newStats == null) {
             // First poll after new association
             // Update throughput with prediction
-            if (wifiInfo.getRssi() != WifiInfo.INVALID_RSSI && mConnectionCapabilities != null) {
-                mTxTputKbps = mThroughputPredictor.predictTxThroughput(mConnectionCapabilities,
+            if (wifiInfo.getRssi() != WifiInfo.INVALID_RSSI && connectionCapabilities != null) {
+                mTxTputKbps = mThroughputPredictor.predictTxThroughput(connectionCapabilities,
                         wifiInfo.getRssi(), currFrequency, ccaLevel) * 1000;
-                mRxTputKbps = mThroughputPredictor.predictRxThroughput(mConnectionCapabilities,
+                mRxTputKbps = mThroughputPredictor.predictRxThroughput(connectionCapabilities,
                         wifiInfo.getRssi(), currFrequency, ccaLevel) * 1000;
             }
             mIsThroughputSufficient = true;
@@ -336,8 +434,6 @@
         if (timeDeltaLastTwoPollsMs > 0 && timeDeltaLastTwoPollsMs <= maxTimeDeltaMs) {
             mWifiMetrics.incrementConnectionDuration(timeDeltaLastTwoPollsMs,
                     mIsThroughputSufficient, mIsCellularDataAvailable);
-            reportWifiHealthStat(currFrequency, timeDeltaLastTwoPollsMs, mIsThroughputSufficient,
-                    mIsCellularDataAvailable);
         }
 
         boolean possibleDataStallTx = isTxTputLow
@@ -349,13 +445,14 @@
         boolean dataStallTx = isTxTrafficHigh ? possibleDataStallTx : mDataStallTx;
         boolean dataStallRx = isRxTrafficHigh ? possibleDataStallRx : mDataStallRx;
 
-        return detectConsecutiveTwoDataStalls(timeDeltaLastTwoPollsMs, dataStallTx, dataStallRx);
+        return detectConsecutiveTwoDataStalls(ifaceName, timeDeltaLastTwoPollsMs, dataStallTx,
+                dataStallRx);
     }
 
     // Data stall event is triggered if there are consecutive Tx and/or Rx data stalls
     // 1st data stall should be preceded by no data stall
     // Reset mDataStallStartTimeMs to -1 if currently there is no Tx or Rx data stall
-    private int detectConsecutiveTwoDataStalls(int timeDeltaLastTwoPollsMs,
+    private int detectConsecutiveTwoDataStalls(String ifaceName, int timeDeltaLastTwoPollsMs,
             boolean dataStallTx, boolean dataStallRx) {
         if (timeDeltaLastTwoPollsMs >= MAX_MS_DELTA_FOR_DATA_STALL) {
             return WifiIsUnusableEvent.TYPE_UNKNOWN;
@@ -368,7 +465,8 @@
                 mDataStallStartTimeMs = mClock.getElapsedSinceBootMillis();
                 if (mDeviceConfigFacade.getDataStallDurationMs() == 0) {
                     mDataStallStartTimeMs = -1;
-                    int result = calculateUsabilityEventType(mDataStallTx, mDataStallRx);
+                    int result = calculateUsabilityEventType(ifaceName, mDataStallTx,
+                            mDataStallRx);
                     mDataStallRx = false;
                     mDataStallTx = false;
                     return result;
@@ -378,7 +476,8 @@
                 if (elapsedTime >= mDeviceConfigFacade.getDataStallDurationMs()) {
                     mDataStallStartTimeMs = -1;
                     if (elapsedTime <= VALIDITY_PERIOD_OF_DATA_STALL_START_MS) {
-                        int result = calculateUsabilityEventType(mDataStallTx, mDataStallRx);
+                        int result = calculateUsabilityEventType(ifaceName, mDataStallTx,
+                                mDataStallRx);
                         mDataStallRx = false;
                         mDataStallTx = false;
                         return result;
@@ -409,7 +508,8 @@
         }
         return (int) (txRetriesDelta * 100 / txAttempts);
     }
-    private int calculateUsabilityEventType(boolean dataStallTx, boolean dataStallRx) {
+    private int calculateUsabilityEventType(String ifaceName, boolean dataStallTx,
+            boolean dataStallRx) {
         int result = WifiIsUnusableEvent.TYPE_UNKNOWN;
         if (dataStallTx && dataStallRx) {
             result = WifiIsUnusableEvent.TYPE_DATA_STALL_BOTH;
@@ -418,7 +518,7 @@
         } else if (dataStallRx) {
             result = WifiIsUnusableEvent.TYPE_DATA_STALL_TX_WITHOUT_RX;
         }
-        mWifiMetrics.logWifiIsUnusableEvent(result);
+        mWifiMetrics.logWifiIsUnusableEvent(ifaceName, result);
         return result;
     }
 
@@ -493,43 +593,6 @@
         return  possibleFalseInsufficient ? lastIsTputSufficient : isTputSufficient;
     }
 
-    /**
-     * Report the latest Wifi connection health to statsd
-     */
-    private void reportWifiHealthStat(int frequency, int timeDeltaLastTwoPollsMs,
-            boolean isThroughputSufficient,
-            boolean isCellularDataAvailable) {
-        int band = getBand(frequency);
-        WifiStatsLog.write(WifiStatsLog.WIFI_HEALTH_STAT_REPORTED, timeDeltaLastTwoPollsMs,
-                isThroughputSufficient,  isCellularDataAvailable, band);
-    }
-
-    private int getBand(int frequency) {
-        int band;
-        if (ScanResult.is24GHz(frequency)) {
-            band = WifiStatsLog.WIFI_HEALTH_STAT_REPORTED__BAND__BAND_2G;
-        } else if (ScanResult.is5GHz(frequency)) {
-            if (frequency <= KnownBandsChannelHelper.BAND_5_GHZ_LOW_END_FREQ) {
-                band = WifiStatsLog.WIFI_HEALTH_STAT_REPORTED__BAND__BAND_5G_LOW;
-            } else if (frequency <= KnownBandsChannelHelper.BAND_5_GHZ_MID_END_FREQ) {
-                band = WifiStatsLog.WIFI_HEALTH_STAT_REPORTED__BAND__BAND_5G_MIDDLE;
-            } else {
-                band = WifiStatsLog.WIFI_HEALTH_STAT_REPORTED__BAND__BAND_5G_HIGH;
-            }
-        } else if (ScanResult.is6GHz(frequency)) {
-            if (frequency <= KnownBandsChannelHelper.BAND_6_GHZ_LOW_END_FREQ) {
-                band = WifiStatsLog.WIFI_HEALTH_STAT_REPORTED__BAND__BAND_6G_LOW;
-            } else if (frequency <= KnownBandsChannelHelper.BAND_6_GHZ_MID_END_FREQ) {
-                band = WifiStatsLog.WIFI_HEALTH_STAT_REPORTED__BAND__BAND_6G_MIDDLE;
-            } else {
-                band = WifiStatsLog.WIFI_HEALTH_STAT_REPORTED__BAND__BAND_6G_HIGH;
-            }
-        } else {
-            band = WifiStatsLog.WIFI_HEALTH_STAT_REPORTED__BAND__UNKNOWN;
-        }
-        return band;
-    }
-
     private void logd(String string) {
         if (mVerboseLoggingEnabled) {
             Log.d(TAG, string);
diff --git a/service/java/com/android/server/wifi/WifiDiagnostics.java b/service/java/com/android/server/wifi/WifiDiagnostics.java
index 3804562..7a0977c 100644
--- a/service/java/com/android/server/wifi/WifiDiagnostics.java
+++ b/service/java/com/android/server/wifi/WifiDiagnostics.java
@@ -17,9 +17,12 @@
 package com.android.server.wifi;
 
 import android.annotation.NonNull;
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.os.BugreportManager;
 import android.os.BugreportParams;
+import android.os.Handler;
+import android.os.Looper;
 import android.util.ArraySet;
 import android.util.Base64;
 import android.util.Log;
@@ -40,7 +43,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Calendar;
-import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
@@ -52,17 +54,24 @@
 /**
  * Tracks various logs for framework.
  */
-class WifiDiagnostics extends BaseWifiDiagnostics {
+public class WifiDiagnostics {
     /**
      * Thread-safety:
-     * 1) All non-private methods are |synchronized|.
-     * 2) Callbacks into WifiDiagnostics use non-private (and hence, synchronized) methods. See, e.g,
-     *    onRingBufferData(), onWifiAlert().
+     * 1) Most non-private methods are |synchronized| with the exception of
+     *      {@link #captureBugReportData(int)} and {@link #triggerBugReportDataCapture(int)}, and
+     *      a few others. See those methods' documentation.
+     * 2) Callbacks into WifiDiagnostics use non-private (and hence, synchronized) methods.
+     *      See, e.g, onRingBufferData(), onWifiAlert().
      */
 
     private static final String TAG = "WifiDiags";
     private static final boolean DBG = false;
 
+    public static final byte CONNECTION_EVENT_STARTED = 0;
+    public static final byte CONNECTION_EVENT_SUCCEEDED = 1;
+    public static final byte CONNECTION_EVENT_FAILED = 2;
+    public static final byte CONNECTION_EVENT_TIMEOUT = 3;
+
     /** log level flags; keep these consistent with wifi_logger.h */
 
     /** No logs whatsoever */
@@ -122,29 +131,36 @@
     @VisibleForTesting public static final String DRIVER_DUMP_SECTION_HEADER =
             "Driver state dump";
 
-    private int mLogLevel = VERBOSE_NO_LOG;
-    private boolean mIsLoggingEventHandlerRegistered;
-    private WifiNative.RingBufferStatus[] mRingBuffers;
-    private WifiNative.RingBufferStatus mPerPacketRingBuffer;
+    private final WifiNative mWifiNative;
     private final Context mContext;
     private final BuildProperties mBuildProperties;
     private final WifiLog mLog;
     private final LastMileLogger mLastMileLogger;
     private final Runtime mJavaRuntime;
     private final WifiMetrics mWifiMetrics;
+    private final WifiInjector mWifiInjector;
+    private final Clock mClock;
+    private final Handler mWorkerThreadHandler;
+
+    private int mLogLevel = VERBOSE_NO_LOG;
+    private boolean mIsLoggingEventHandlerRegistered;
+    private WifiNative.RingBufferStatus[] mRingBuffers;
+    private WifiNative.RingBufferStatus mPerPacketRingBuffer;
+    private String mFirmwareVersion;
+    private String mDriverVersion;
+    private int mSupportedFeatureSet;
     private int mMaxRingBufferSizeBytes;
-    private WifiInjector mWifiInjector;
-    private Clock mClock;
 
     /** Interfaces started logging */
     private final Set<String> mActiveInterfaces = new ArraySet<>();
+    private String mLatestIfaceLogged = "";
 
-    public WifiDiagnostics(Context context, WifiInjector wifiInjector,
-                           WifiNative wifiNative, BuildProperties buildProperties,
-                           LastMileLogger lastMileLogger, Clock clock) {
-        super(wifiNative);
-
+    public WifiDiagnostics(
+            Context context, WifiInjector wifiInjector,
+            WifiNative wifiNative, BuildProperties buildProperties,
+            LastMileLogger lastMileLogger, Clock clock, Looper workerLooper) {
         mContext = context;
+        mWifiNative = wifiNative;
         mBuildProperties = buildProperties;
         mIsLoggingEventHandlerRegistered = false;
         mLog = wifiInjector.makeLog(TAG);
@@ -153,6 +169,7 @@
         mWifiMetrics = wifiInjector.getWifiMetrics();
         mWifiInjector = wifiInjector;
         mClock = clock;
+        mWorkerThreadHandler = new Handler(workerLooper);
     }
 
     /**
@@ -162,7 +179,6 @@
      *
      * @param ifaceName the interface requesting to start logging.
      */
-    @Override
     public synchronized void startLogging(@NonNull String ifaceName) {
         if (mActiveInterfaces.contains(ifaceName)) {
             Log.w(TAG, "Interface: " + ifaceName + " had already started logging");
@@ -181,12 +197,12 @@
         }
 
         mActiveInterfaces.add(ifaceName);
+        mLatestIfaceLogged = ifaceName;
 
         Log.d(TAG, "startLogging() iface list is " + mActiveInterfaces
                 + " after adding " + ifaceName);
     }
 
-    @Override
     public synchronized void startPacketLog() {
         if (mPerPacketRingBuffer != null) {
             startLoggingRingBuffer(mPerPacketRingBuffer);
@@ -195,7 +211,6 @@
         }
     }
 
-    @Override
     public synchronized void stopPacketLog() {
         if (mPerPacketRingBuffer != null) {
             stopLoggingRingBuffer(mPerPacketRingBuffer);
@@ -211,7 +226,6 @@
      *
      * @param ifaceName the interface requesting to stop logging.
      */
-    @Override
     public synchronized void stopLogging(@NonNull String ifaceName) {
         if (!mActiveInterfaces.contains(ifaceName)) {
             Log.w(TAG, "ifaceName: " + ifaceName + " is not in the start log user list");
@@ -242,37 +256,75 @@
         }
     }
 
-    @Override
-    public synchronized void reportConnectionEvent(byte event) {
-        mLastMileLogger.reportConnectionEvent(event);
+    /**
+     * Inform the diagnostics module of a connection event.
+     * @param event The type of connection event (see CONNECTION_EVENT_* constants)
+     */
+    public synchronized void reportConnectionEvent(byte event,
+            ClientModeManager clientModeManager) {
+        mLastMileLogger.reportConnectionEvent(clientModeManager.getInterfaceName(), event);
         if (event == CONNECTION_EVENT_FAILED || event == CONNECTION_EVENT_TIMEOUT) {
-            mPacketFatesForLastFailure = fetchPacketFates();
+            mPacketFatesForLastFailure = new PacketFates(clientModeManager);
         }
     }
 
-    @Override
-    public synchronized void captureBugReportData(int reason) {
-        BugReport report = captureBugreport(reason, isVerboseLoggingEnabled());
-        mLastBugReports.addLast(report);
-        flushDump(reason);
-    }
-
-    @Override
-    public synchronized void captureAlertData(int errorCode, byte[] alertData) {
-        BugReport report = captureBugreport(errorCode, isVerboseLoggingEnabled());
-        report.alertData = alertData;
-        mLastAlerts.addLast(report);
-        /* Flush HAL ring buffer when detecting data stall */
-        if (Arrays.stream(mContext.getResources().getIntArray(
-                R.array.config_wifi_fatal_firmware_alert_error_code_list))
-                .boxed().collect(Collectors.toList()).contains(errorCode)) {
-            flushDump(REPORT_REASON_FATAL_FW_ALERT);
+    /**
+     * Synchronously capture bug report data.
+     *
+     * Note: this method is not marked as synchronized, but it is synchronized internally.
+     * getLogcat*() methods are very slow, so do not synchronize these calls (they are thread safe,
+     * do not need to be synchronized).
+     */
+    public void captureBugReportData(int reason) {
+        final boolean verbose;
+        synchronized (this) {
+            verbose = isVerboseLoggingEnabled();
+        }
+        BugReport report = captureBugreport(reason, verbose);
+        synchronized (this) {
+            mLastBugReports.addLast(report);
+            flushDump(reason);
         }
     }
 
-    @Override
+    /**
+     * Asynchronously capture bug report data.
+     *
+     * Not synchronized because no work is performed on the calling thread.
+     */
+    public void triggerBugReportDataCapture(int reason) {
+        mWorkerThreadHandler.post(() -> {
+            captureBugReportData(reason);
+        });
+    }
+
+    private void triggerAlertDataCapture(int errorCode, byte[] alertData) {
+        mWorkerThreadHandler.post(() -> {
+            final boolean verbose;
+            synchronized (this) {
+                verbose = isVerboseLoggingEnabled();
+            }
+            // This is very slow, don't put this inside `synchronized(this)`!
+            BugReport report = captureBugreport(errorCode, verbose);
+            synchronized (this) {
+                report.alertData = alertData;
+                mLastAlerts.addLast(report);
+
+                /* Flush HAL ring buffer when detecting data stall */
+                if (Arrays.stream(mContext.getResources().getIntArray(
+                        R.array.config_wifi_fatal_firmware_alert_error_code_list))
+                        .boxed().collect(Collectors.toList()).contains(errorCode)) {
+                    flushDump(REPORT_REASON_FATAL_FW_ALERT);
+                }
+            }
+        });
+    }
+
     public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        super.dump(pw);
+        pw.println("Chipset information :-----------------------------------------------");
+        pw.println("FW Version is: " + mFirmwareVersion);
+        pw.println("Driver Version is: " + mDriverVersion);
+        pw.println("Supported Feature set: " + mSupportedFeatureSet);
 
         for (int i = 0; i < mLastAlerts.size(); i++) {
             pw.println("--------------------------------------------------------------------");
@@ -301,7 +353,8 @@
      * Initiates a system-level bug report if there is no bug report taken recently.
      * This is done in a non-blocking fashion.
      */
-    @Override
+    // TODO(b/193460475): BugReportManager changes from SystemApi to PublicApi, not a new API
+    @SuppressLint("NewApi")
     public void takeBugReport(String bugTitle, String bugDetail) {
         if (mBuildProperties.isUserBuild()
                 || !mContext.getResources().getBoolean(
@@ -315,6 +368,7 @@
             return;
         }
         mLastBugReportTime = currentTime;
+        mWifiMetrics.logBugReport();
         BugreportManager bugreportManager = mContext.getSystemService(BugreportManager.class);
         BugreportParams params = new BugreportParams(BugreportParams.BUGREPORT_MODE_FULL);
         try {
@@ -421,7 +475,7 @@
         }
     }
 
-    class LimitedCircularArray<E> {
+    static class LimitedCircularArray<E> {
         private ArrayList<E> mArrayList;
         private int mMax;
         LimitedCircularArray(int max) {
@@ -471,8 +525,11 @@
     }
 
     synchronized void onWifiAlert(int errorCode, @NonNull byte[] buffer) {
-        captureAlertData(errorCode, buffer);
-        mWifiMetrics.logFirmwareAlert(errorCode);
+        triggerAlertDataCapture(errorCode, buffer);
+        // TODO b/166309727 This currently assumes that the firmware alert comes from latest
+        // interface that started logging, as the callback does not tell us which interface
+        // caused the alert.
+        mWifiMetrics.logFirmwareAlert(mLatestIfaceLogged, errorCode);
         mWifiInjector.getWifiScoreCard().noteFirmwareAlert(errorCode);
     }
 
@@ -481,7 +538,6 @@
      *
      * @param verbose - with the obvious interpretation
      */
-    @Override
     public synchronized void enableVerboseLogging(boolean verboseEnabled) {
         final int ringBufferByteLimitSmall = mContext.getResources().getInteger(
                 R.integer.config_wifi_logger_ring_buffer_default_size_limit_kb) * 1024;
@@ -507,7 +563,6 @@
     }
 
     private void clearVerboseLogs() {
-
         for (int i = 0; i < mLastAlerts.size(); i++) {
             mLastAlerts.get(i).clearVerboseLogs();
         }
@@ -640,19 +695,22 @@
         report.systemTimeMs = System.currentTimeMillis();
         report.kernelTimeNanos = System.nanoTime();
 
-        if (mRingBuffers != null) {
-            for (WifiNative.RingBufferStatus buffer : mRingBuffers) {
-                /* this will push data in mRingBuffers */
-                mWifiNative.getRingBufferData(buffer.name);
-                ByteArrayRingBuffer data = mRingBufferData.get(buffer.name);
-                byte[][] buffers = new byte[data.getNumBuffers()][];
-                for (int i = 0; i < data.getNumBuffers(); i++) {
-                    buffers[i] = data.getBuffer(i).clone();
+        synchronized (this) {
+            if (mRingBuffers != null) {
+                for (WifiNative.RingBufferStatus buffer : mRingBuffers) {
+                    /* this will push data in mRingBuffers */
+                    mWifiNative.getRingBufferData(buffer.name);
+                    ByteArrayRingBuffer data = mRingBufferData.get(buffer.name);
+                    byte[][] buffers = new byte[data.getNumBuffers()][];
+                    for (int i = 0; i < data.getNumBuffers(); i++) {
+                        buffers[i] = data.getBuffer(i).clone();
+                    }
+                    report.ringBuffers.put(buffer.name, buffers);
                 }
-                report.ringBuffers.put(buffer.name, buffers);
             }
         }
 
+        // getLogcat*() is very slow, do not put them inside `synchronize(this)`!
         report.logcatLines = getLogcatSystem(127);
         report.kernelLogLines = getLogcatKernel(127);
 
@@ -664,12 +722,12 @@
     }
 
     @VisibleForTesting
-    LimitedCircularArray<BugReport> getBugReports() {
+    synchronized LimitedCircularArray<BugReport> getBugReports() {
         return mLastBugReports;
     }
 
     @VisibleForTesting
-    LimitedCircularArray<BugReport> getAlertReports() {
+    synchronized LimitedCircularArray<BugReport> getAlertReports() {
         return mLastAlerts;
     }
 
@@ -727,6 +785,7 @@
         }
     }
 
+    /** This method is thread safe */
     private ArrayList<String> getLogcat(String logcatSections, int maxLines) {
         ArrayList<String> lines = new ArrayList<>(maxLines);
         try {
@@ -741,71 +800,69 @@
             mLog.dump("Exception while capturing logcat: %").c(e.toString()).flush();
         }
         return lines;
-
     }
 
+    /** This method is thread safe */
     private ArrayList<String> getLogcatSystem(int maxLines) {
         return getLogcat("main,system,crash", maxLines);
     }
 
+    /** This method is thread safe */
     private ArrayList<String> getLogcatKernel(int maxLines) {
         return getLogcat("kernel", maxLines);
     }
 
     /** Packet fate reporting */
-    private ArrayList<WifiNative.FateReport> mPacketFatesForLastFailure;
+    private PacketFates mPacketFatesForLastFailure;
 
-    private ArrayList<WifiNative.FateReport> fetchPacketFates() {
-        ArrayList<WifiNative.FateReport> mergedFates = new ArrayList<WifiNative.FateReport>();
-        WifiNative.TxFateReport[] txFates =
-                new WifiNative.TxFateReport[WifiLoggerHal.MAX_FATE_LOG_LEN];
-        if (mWifiNative.getTxPktFates(mWifiNative.getClientInterfaceName(), txFates)) {
-            for (int i = 0; i < txFates.length && txFates[i] != null; i++) {
-                mergedFates.add(txFates[i]);
-            }
+    static class PacketFates {
+        public final String clientModeManagerInfo;
+        @NonNull public final List<WifiNative.FateReport> mergedFates;
+
+        PacketFates(ClientModeManager clientModeManager) {
+            clientModeManagerInfo = clientModeManager.toString();
+            mergedFates = new ArrayList<WifiNative.FateReport>();
+            mergedFates.addAll(clientModeManager.getTxPktFates());
+            mergedFates.addAll(clientModeManager.getRxPktFates());
+            mergedFates.sort(Comparator.comparing(fateReport -> fateReport.mDriverTimestampUSec));
         }
+    }
 
-        WifiNative.RxFateReport[] rxFates =
-                new WifiNative.RxFateReport[WifiLoggerHal.MAX_FATE_LOG_LEN];
-        if (mWifiNative.getRxPktFates(mWifiNative.getClientInterfaceName(), rxFates)) {
-            for (int i = 0; i < rxFates.length && rxFates[i] != null; i++) {
-                mergedFates.add(rxFates[i]);
-            }
+    private @NonNull List<PacketFates> fetchPacketFatesForAllClientIfaces() {
+        List<ClientModeManager> clientModeManagers =
+                mWifiInjector.getActiveModeWarden().getClientModeManagers();
+        List<PacketFates> packetFates = new ArrayList<>();
+        for (ClientModeManager cm : clientModeManagers) {
+            packetFates.add(new PacketFates(cm));
         }
-
-        Collections.sort(mergedFates, new Comparator<WifiNative.FateReport>() {
-            @Override
-            public int compare(WifiNative.FateReport lhs, WifiNative.FateReport rhs) {
-                return Long.compare(lhs.mDriverTimestampUSec, rhs.mDriverTimestampUSec);
-            }
-        });
-
-        return mergedFates;
+        return packetFates;
     }
 
     private void dumpPacketFates(PrintWriter pw) {
         dumpPacketFatesInternal(pw, "Last failed connection fates", mPacketFatesForLastFailure,
                 isVerboseLoggingEnabled());
-        dumpPacketFatesInternal(pw, "Latest fates", fetchPacketFates(), isVerboseLoggingEnabled());
+        for (PacketFates fates : fetchPacketFatesForAllClientIfaces()) {
+            dumpPacketFatesInternal(pw, "Latest fates", fates, isVerboseLoggingEnabled());
+        }
     }
 
     private static void dumpPacketFatesInternal(PrintWriter pw, String description,
-            ArrayList<WifiNative.FateReport> fates, boolean verbose) {
-        if (fates == null) {
+            PacketFates packetFates, boolean verbose) {
+        if (packetFates == null) {
             pw.format("No fates fetched for \"%s\"\n", description);
             return;
         }
-
-        if (fates.size() == 0) {
+        if (packetFates.mergedFates.size() == 0) {
             pw.format("HAL provided zero fates for \"%s\"\n", description);
             return;
         }
 
         pw.format("--------------------- %s ----------------------\n", description);
+        pw.format("ClientModeManagerInfo=%s ---------------\n", packetFates.clientModeManagerInfo);
 
         StringBuilder verboseOutput = new StringBuilder();
         pw.print(WifiNative.FateReport.getTableHeader());
-        for (WifiNative.FateReport fate : fates) {
+        for (WifiNative.FateReport fate : packetFates.mergedFates) {
             pw.print(fate.toTableRowString());
             if (verbose) {
                 // Important: only print Personally Identifiable Information (PII) if verbose
@@ -828,7 +885,6 @@
      *
      * @param ifaceName Name of the interface.
      */
-    @Override
     public void startPktFateMonitoring(@NonNull String ifaceName) {
         if (!mWifiNative.startPktFateMonitoring(ifaceName)) {
             mLog.wC("Failed to start packet fate monitoring");
diff --git a/service/java/com/android/server/wifi/WifiGlobals.java b/service/java/com/android/server/wifi/WifiGlobals.java
new file mode 100644
index 0000000..b1c699e
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiGlobals.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.content.Context;
+
+import com.android.wifi.resources.R;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.annotation.concurrent.ThreadSafe;
+
+
+/** Global wifi service in-memory state that is not persisted. */
+@ThreadSafe
+public class WifiGlobals {
+
+    /**
+     * Maximum allowable interval in milliseconds between polling for RSSI and linkspeed
+     * information. This is also used as the polling interval for WifiTrafficPoller, which updates
+     * its data activity on every CMD_RSSI_POLL.
+     */
+    private static final int MAXIMUM_POLL_RSSI_INTERVAL_MSECS = 6000;
+
+    private final Context mContext;
+
+    private final AtomicInteger mPollRssiIntervalMillis = new AtomicInteger(-1);
+    private final AtomicBoolean mIpReachabilityDisconnectEnabled = new AtomicBoolean(true);
+    private final AtomicBoolean mIsBluetoothConnected = new AtomicBoolean(false);
+
+    // This is read from the overlay, cache it after boot up.
+    private final boolean mIsWpa3SaeUpgradeEnabled;
+    // This is read from the overlay, cache it after boot up.
+    private final boolean mIsWpa3SaeUpgradeOffloadEnabled;
+    // This is read from the overlay, cache it after boot up.
+    private final boolean mIsOweUpgradeEnabled;
+    // This is read from the overlay, cache it after boot up.
+    private final boolean mFlushAnqpCacheOnWifiToggleOffEvent;
+    // This is read from the overlay, cache it after boot up.
+    private final boolean mIsWpa3SaeH2eSupported;
+    // This is read from the overlay, cache it after boot up.
+    private final String mP2pDeviceNamePrefix;
+    // This is read from the overlay, cache it after boot up.
+    private final int mP2pDeviceNamePostfixNumDigits;
+    // This is read from the overlay, cache it after boot up.
+    private final int mClientModeImplNumLogRecs;
+
+    // This is set by WifiManager#setVerboseLoggingEnabled(int).
+    private boolean mIsShowKeyVerboseLoggingModeEnabled = false;
+
+    public WifiGlobals(Context context) {
+        mContext = context;
+
+        mIsWpa3SaeUpgradeEnabled = mContext.getResources()
+                .getBoolean(R.bool.config_wifiSaeUpgradeEnabled);
+        mIsWpa3SaeUpgradeOffloadEnabled = mContext.getResources()
+                .getBoolean(R.bool.config_wifiSaeUpgradeOffloadEnabled);
+        mIsOweUpgradeEnabled = mContext.getResources()
+                .getBoolean(R.bool.config_wifiOweUpgradeEnabled);
+        mFlushAnqpCacheOnWifiToggleOffEvent = mContext.getResources()
+                .getBoolean(R.bool.config_wifiFlushAnqpCacheOnWifiToggleOffEvent);
+        mIsWpa3SaeH2eSupported = mContext.getResources()
+                .getBoolean(R.bool.config_wifiSaeH2eSupported);
+        mP2pDeviceNamePrefix = mContext.getResources()
+                .getString(R.string.config_wifiP2pDeviceNamePrefix);
+        mP2pDeviceNamePostfixNumDigits = mContext.getResources()
+                .getInteger(R.integer.config_wifiP2pDeviceNamePostfixNumDigits);
+        mClientModeImplNumLogRecs = mContext.getResources()
+                .getInteger(R.integer.config_wifiClientModeImplNumLogRecs);
+    }
+
+    /** Get the interval between RSSI polls, in milliseconds. */
+    public int getPollRssiIntervalMillis() {
+        int interval = mPollRssiIntervalMillis.get();
+        if (interval > 0) {
+            return interval;
+        } else {
+            return Math.min(
+                    mContext.getResources()
+                            .getInteger(R.integer.config_wifiPollRssiIntervalMilliseconds),
+                    MAXIMUM_POLL_RSSI_INTERVAL_MSECS);
+        }
+    }
+
+    /** Set the interval between RSSI polls, in milliseconds. */
+    public void setPollRssiIntervalMillis(int newPollIntervalMillis) {
+        mPollRssiIntervalMillis.set(newPollIntervalMillis);
+    }
+
+    /** Returns whether CMD_IP_REACHABILITY_LOST events should trigger disconnects. */
+    public boolean getIpReachabilityDisconnectEnabled() {
+        return mIpReachabilityDisconnectEnabled.get();
+    }
+
+    /** Sets whether CMD_IP_REACHABILITY_LOST events should trigger disconnects. */
+    public void setIpReachabilityDisconnectEnabled(boolean enabled) {
+        mIpReachabilityDisconnectEnabled.set(enabled);
+    }
+
+    /** Set whether bluetooth is enabled. */
+    public void setBluetoothEnabled(boolean isEnabled) {
+        // If BT was connected and then turned off, there is no CONNECTION_STATE_CHANGE message.
+        // So set mIsBluetoothConnected to false if we get a bluetooth disable while connected.
+        // But otherwise, Bluetooth being turned on doesn't mean that we're connected.
+        if (!isEnabled) {
+            mIsBluetoothConnected.set(false);
+        }
+    }
+
+    /** Set whether bluetooth is connected. */
+    public void setBluetoothConnected(boolean isConnected) {
+        mIsBluetoothConnected.set(isConnected);
+    }
+
+    /** Get whether bluetooth is connected */
+    public boolean isBluetoothConnected() {
+        return mIsBluetoothConnected.get();
+    }
+
+    /**
+     * Helper method to check if Connected MAC Randomization is supported - onDown events are
+     * skipped if this feature is enabled (b/72459123).
+     *
+     * @return boolean true if Connected MAC randomization is supported, false otherwise
+     */
+    public boolean isConnectedMacRandomizationEnabled() {
+        return mContext.getResources().getBoolean(
+                R.bool.config_wifi_connected_mac_randomization_supported);
+    }
+
+    /**
+     * Help method to check if WPA3 SAE auto-upgrade is enabled.
+     *
+     * @return boolean true if auto-upgrade is enabled, false otherwise.
+     */
+    public boolean isWpa3SaeUpgradeEnabled() {
+        return mIsWpa3SaeUpgradeEnabled;
+    }
+
+    /**
+     * Help method to check if WPA3 SAE auto-upgrade offload is enabled.
+     *
+     * @return boolean true if auto-upgrade offload is enabled, false otherwise.
+     */
+    public boolean isWpa3SaeUpgradeOffloadEnabled() {
+        return mIsWpa3SaeUpgradeOffloadEnabled;
+    }
+
+    /**
+     * Help method to check if OWE auto-upgrade is enabled.
+     *
+     * @return boolean true if auto-upgrade is enabled, false otherwise.
+     */
+    public boolean isOweUpgradeEnabled() {
+        return mIsOweUpgradeEnabled;
+    }
+
+    /**
+     * Help method to check if the setting to flush ANQP cache when Wi-Fi is toggled off.
+     *
+     * @return boolean true to flush ANQP cache on Wi-Fi toggle off event, false otherwise.
+     */
+    public boolean flushAnqpCacheOnWifiToggleOffEvent() {
+        return mFlushAnqpCacheOnWifiToggleOffEvent;
+    }
+
+    /*
+     * Help method to check if WPA3 SAE Hash-to-Element is supported on this device.
+     *
+     * @return boolean true if supported;otherwise false.
+     */
+    public boolean isWpa3SaeH2eSupported() {
+        return mIsWpa3SaeH2eSupported;
+    }
+
+    /** Set if show key verbose logging mode is enabled. */
+    public void setShowKeyVerboseLoggingModeEnabled(boolean enable) {
+        mIsShowKeyVerboseLoggingModeEnabled = enable;
+    }
+
+    /** Check if show key verbose logging mode is enabled. */
+    public boolean getShowKeyVerboseLoggingModeEnabled() {
+        return mIsShowKeyVerboseLoggingModeEnabled;
+    }
+
+    /** Get the prefix of the default wifi p2p device name. */
+    public String getWifiP2pDeviceNamePrefix() {
+        return mP2pDeviceNamePrefix;
+    }
+
+    /** Get the number of the default wifi p2p device name postfix digit. */
+    public int getWifiP2pDeviceNamePostfixNumDigits() {
+        return mP2pDeviceNamePostfixNumDigits;
+    }
+
+    /** Get the number of log records to maintain. */
+    public int getClientModeImplNumLogRecs() {
+        return mClientModeImplNumLogRecs;
+    }
+
+    /** Dump method for debugging */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("Dump of WifiGlobals");
+        pw.println("mPollRssiIntervalMillis=" + mPollRssiIntervalMillis.get());
+        pw.println("mIpReachabilityDisconnectEnabled=" + mIpReachabilityDisconnectEnabled.get());
+        pw.println("mIsBluetoothConnected=" + mIsBluetoothConnected.get());
+        pw.println("mIsWpa3SaeUpgradeEnabled=" + mIsWpa3SaeUpgradeEnabled);
+        pw.println("mIsWpa3SaeUpgradeOffloadEnabled=" + mIsWpa3SaeUpgradeOffloadEnabled);
+        pw.println("mIsOweUpgradeEnabled=" + mIsOweUpgradeEnabled);
+        pw.println("mFlushAnqpCacheOnWifiToggleOffEvent=" + mFlushAnqpCacheOnWifiToggleOffEvent);
+        pw.println("mIsWpa3SaeH2eSupported=" + mIsWpa3SaeH2eSupported);
+        pw.println("mP2pDeviceNamePrefix=" + mP2pDeviceNamePrefix);
+        pw.println("mP2pDeviceNamePostfixNumDigits=" + mP2pDeviceNamePostfixNumDigits);
+        pw.println("mClientModeImplNumLogRecs=" + mClientModeImplNumLogRecs);
+    }
+}
diff --git a/service/java/com/android/server/wifi/WifiHealthMonitor.java b/service/java/com/android/server/wifi/WifiHealthMonitor.java
index b21ea51..011099a 100644
--- a/service/java/com/android/server/wifi/WifiHealthMonitor.java
+++ b/service/java/com/android/server/wifi/WifiHealthMonitor.java
@@ -99,6 +99,8 @@
     static final int HEALTH_MONITOR_COUNT_TX_SPEED_MIN_MBPS = 54;
     // Minimum Tx packet per seconds for disconnection stats collection
     static final int HEALTH_MONITOR_MIN_TX_PACKET_PER_SEC = 4;
+    private static final long MAX_TIME_SINCE_LAST_CONNECTION_MS = 48 * 3600_000;
+
 
     private final Context mContext;
     private final WifiConfigManager mWifiConfigManager;
@@ -109,6 +111,7 @@
     private final WifiNative mWifiNative;
     private final WifiInjector mWifiInjector;
     private final DeviceConfigFacade mDeviceConfigFacade;
+    private final ActiveModeWarden mActiveModeWarden;
     private WifiScanner mScanner;
     private MemoryStore mMemoryStore;
     private boolean mWifiEnabled;
@@ -125,9 +128,31 @@
     private boolean mHasNewDataForWifiMetrics = false;
     private int mDeviceMobilityState = WifiManager.DEVICE_MOBILITY_STATE_UNKNOWN;
 
+    private class ModeChangeCallback implements ActiveModeWarden.ModeChangeCallback {
+        @Override
+        public void onActiveModeManagerAdded(@NonNull ActiveModeManager activeModeManager) {
+            update();
+        }
+
+        @Override
+        public void onActiveModeManagerRemoved(@NonNull ActiveModeManager activeModeManager) {
+            update();
+        }
+
+        @Override
+        public void onActiveModeManagerRoleChanged(@NonNull ActiveModeManager activeModeManager) {
+            update();
+        }
+
+        private void update() {
+            setWifiEnabled(mActiveModeWarden.getPrimaryClientModeManagerNullable() != null);
+        }
+    }
+
     WifiHealthMonitor(Context context, WifiInjector wifiInjector, Clock clock,
             WifiConfigManager wifiConfigManager, WifiScoreCard wifiScoreCard, Handler handler,
-            WifiNative wifiNative, String l2KeySeed, DeviceConfigFacade deviceConfigFacade) {
+            WifiNative wifiNative, String l2KeySeed, DeviceConfigFacade deviceConfigFacade,
+            ActiveModeWarden activeModeWarden) {
         mContext = context;
         mWifiInjector = wifiInjector;
         mClock = clock;
@@ -137,9 +162,10 @@
         mHandler = handler;
         mWifiNative = wifiNative;
         mDeviceConfigFacade = deviceConfigFacade;
-        mWifiEnabled = false;
+        mActiveModeWarden = activeModeWarden;
         mWifiSystemInfoStats = new WifiSystemInfoStats(l2KeySeed);
         mWifiConfigManager.addOnNetworkUpdateListener(new OnNetworkUpdateListener());
+        mActiveModeWarden.registerModeChangeCallback(new ModeChangeCallback());
     }
 
     /**
@@ -186,7 +212,8 @@
      * During the off->on transition, retrieve scanner.
      * During the on->off transition, issue MemoryStore write to save data.
      */
-    public void setWifiEnabled(boolean enable) {
+    private void setWifiEnabled(boolean enable) {
+        if (mWifiEnabled == enable) return;
         mWifiEnabled = enable;
         logd("Set WiFi " + (enable ? "enabled" : "disabled"));
         if (enable) {
@@ -309,10 +336,13 @@
         List<WifiConfiguration> configuredNetworks = mWifiConfigManager.getConfiguredNetworks();
         for (WifiConfiguration network : configuredNetworks) {
             if (isInvalidConfiguredNetwork(network)) continue;
+            boolean isRecentlyConnected = (mClock.getWallClockMillis() - network.lastConnected)
+                    < MAX_TIME_SINCE_LAST_CONNECTION_MS;
             PerNetwork perNetwork = mWifiScoreCard.lookupNetwork(network.SSID);
             int cntName = WifiScoreCard.CNT_CONNECTION_ATTEMPT;
             if (perNetwork.getStatsCurrBuild().getCount(cntName) > 0
-                    || perNetwork.getRecentStats().getCount(cntName) > 0) {
+                    || perNetwork.getRecentStats().getCount(cntName) > 0
+                    || isRecentlyConnected) {
                 pw.println(mWifiScoreCard.lookupNetwork(network.SSID));
             }
         }
@@ -464,6 +494,8 @@
         stats.cntAssocTimeout = failureStats.getCount(REASON_ASSOC_TIMEOUT);
         stats.cntAuthFailure = failureStats.getCount(REASON_AUTH_FAILURE);
         stats.cntConnectionFailure = failureStats.getCount(REASON_CONNECTION_FAILURE);
+        stats.cntDisconnectionNonlocalConnecting =
+                failureStats.getCount(REASON_CONNECTION_FAILURE_DISCONNECTION);
         stats.cntDisconnectionNonlocal =
                 failureStats.getCount(REASON_DISCONNECTION_NONLOCAL);
         stats.cntShortConnectionNonlocal =
@@ -582,14 +614,16 @@
     public static final int REASON_CONNECTION_FAILURE = 3;
     public static final int REASON_DISCONNECTION_NONLOCAL = 4;
     public static final int REASON_SHORT_CONNECTION_NONLOCAL = 5;
-    public static final int NUMBER_FAILURE_REASON_CODE = 6;
+    public static final int REASON_CONNECTION_FAILURE_DISCONNECTION = 6;
+    public static final int NUMBER_FAILURE_REASON_CODE = 7;
     public static final String[] FAILURE_REASON_NAME = {
             "association rejection failure",
             "association timeout failure",
             "authentication failure",
             "connection failure",
             "disconnection",
-            "short connection"
+            "short connection",
+            "connection failure disconnection",
     };
     @IntDef(prefix = { "REASON_" }, value = {
         REASON_NO_FAILURE,
@@ -598,7 +632,8 @@
         REASON_AUTH_FAILURE,
         REASON_CONNECTION_FAILURE,
         REASON_DISCONNECTION_NONLOCAL,
-        REASON_SHORT_CONNECTION_NONLOCAL
+        REASON_SHORT_CONNECTION_NONLOCAL,
+        REASON_CONNECTION_FAILURE_DISCONNECTION
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface FailureReasonCode {}
@@ -1007,14 +1042,6 @@
         }
 
         @Override
-        public void onNetworkEnabled(WifiConfiguration config) {
-        }
-
-        @Override
-        public void onNetworkPermanentlyDisabled(WifiConfiguration config, int disableReason) {
-        }
-
-        @Override
         public void onNetworkRemoved(WifiConfiguration config) {
             if (config == null || (config.fromWifiNetworkSuggestion && !config.isPasspoint())) {
                 // If a suggestion non-passpoint network is removed from wifiConfigManager do not
@@ -1023,14 +1050,6 @@
             }
             mWifiScoreCard.removeNetwork(config.SSID);
         }
-
-        @Override
-        public void onNetworkTemporarilyDisabled(WifiConfiguration config, int disableReason) {
-        }
-
-        @Override
-        public void onNetworkUpdated(WifiConfiguration newConfig, WifiConfiguration oldConfig) {
-        }
     }
 
     /**
@@ -1064,7 +1083,7 @@
                 return;
             }
 
-            if (WifiScanner.isFullBandScan(results[0].getBandScanned(), true)) {
+            if (WifiScanner.isFullBandScan(results[0].getScannedBandsInternal(), true)) {
                 handleScanResults(mScanDetails);
             }
             clearScanDetails();
diff --git a/service/java/com/android/server/wifi/WifiInjector.java b/service/java/com/android/server/wifi/WifiInjector.java
index f49bc95..1ca8fe5 100644
--- a/service/java/com/android/server/wifi/WifiInjector.java
+++ b/service/java/com/android/server/wifi/WifiInjector.java
@@ -17,16 +17,19 @@
 package com.android.server.wifi;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.AppOpsManager;
-import android.app.NotificationManager;
 import android.content.Context;
 import android.net.IpMemoryStore;
+import android.net.LinkProperties;
+import android.net.MatchAllNetworkSpecifier;
+import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
 import android.net.NetworkKey;
+import android.net.NetworkProvider;
 import android.net.NetworkScoreManager;
-import android.net.wifi.WifiManager;
 import android.net.wifi.WifiScanner;
 import android.net.wifi.nl80211.WifiNl80211Manager;
 import android.os.BatteryStatsManager;
@@ -34,17 +37,21 @@
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Process;
-import android.os.SystemProperties;
 import android.os.UserManager;
+import android.os.WorkSource;
 import android.provider.Settings.Secure;
 import android.security.keystore.AndroidKeyStoreProvider;
+import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.LocalLog;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.modules.utils.HandlerExecutor;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.aware.WifiAwareMetrics;
+import com.android.server.wifi.coex.CoexManager;
 import com.android.server.wifi.hotspot2.PasspointManager;
 import com.android.server.wifi.hotspot2.PasspointNetworkNominateHelper;
 import com.android.server.wifi.hotspot2.PasspointObjectFactory;
@@ -53,11 +60,14 @@
 import com.android.server.wifi.p2p.WifiP2pMonitor;
 import com.android.server.wifi.p2p.WifiP2pNative;
 import com.android.server.wifi.rtt.RttMetrics;
+import com.android.server.wifi.util.LastCallerInfoManager;
 import com.android.server.wifi.util.LruConnectionTracker;
 import com.android.server.wifi.util.NetdWrapper;
 import com.android.server.wifi.util.SettingsMigrationDataHolder;
 import com.android.server.wifi.util.WifiPermissionsUtil;
 import com.android.server.wifi.util.WifiPermissionsWrapper;
+import com.android.server.wifi.util.WorkSourceHelper;
+import com.android.wifi.resources.R;
 
 import java.security.KeyStore;
 import java.security.KeyStoreException;
@@ -80,17 +90,55 @@
      */
     private static final int MAX_RECENTLY_CONNECTED_NETWORK = 100;
 
+    private static NetworkCapabilities.Builder makeBaseNetworkCapatibilitiesFilterBuilder() {
+        NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder()
+                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
+                .setLinkUpstreamBandwidthKbps(1024 * 1024)
+                .setLinkDownstreamBandwidthKbps(1024 * 1024)
+                .setNetworkSpecifier(new MatchAllNetworkSpecifier());
+        if (SdkLevel.isAtLeastS()) {
+            builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
+        }
+        return builder;
+    }
+
+    @VisibleForTesting
+    public static final NetworkCapabilities REGULAR_NETWORK_CAPABILITIES_FILTER =
+            makeBaseNetworkCapatibilitiesFilterBuilder()
+                    .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                    .build();
+
+    private static NetworkCapabilities makeOemNetworkCapatibilitiesFilter() {
+        NetworkCapabilities.Builder builder =
+                makeBaseNetworkCapatibilitiesFilterBuilder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID);
+        if (SdkLevel.isAtLeastS()) {
+            // OEM_PRIVATE capability was only added in Android S.
+            builder.addCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE);
+        }
+        return builder.build();
+    }
+
+    private static final NetworkCapabilities OEM_NETWORK_CAPABILITIES_FILTER =
+            makeOemNetworkCapatibilitiesFilter();
+
+
     static WifiInjector sWifiInjector = null;
 
     private final WifiContext mContext;
     private final BatteryStatsManager mBatteryStats;
-    private final FrameworkFacade mFrameworkFacade;
+    private final FrameworkFacade mFrameworkFacade = new FrameworkFacade();
     private final DeviceConfigFacade mDeviceConfigFacade;
     private final UserManager mUserManager;
-    private final HandlerThread mAsyncChannelHandlerThread;
     private final HandlerThread mWifiHandlerThread;
     private final HandlerThread mWifiP2pServiceHandlerThread;
     private final HandlerThread mPasspointProvisionerHandlerThread;
+    private final HandlerThread mWifiDiagnosticsHandlerThread;
     private final WifiTrafficPoller mWifiTrafficPoller;
     private final WifiCountryCode mCountryCode;
     private final BackupManagerProxy mBackupManagerProxy = new BackupManagerProxy();
@@ -104,19 +152,20 @@
     private final HostapdHal mHostapdHal;
     private final WifiVendorHal mWifiVendorHal;
     private final ScoringParams mScoringParams;
-    private final ClientModeImpl mClientModeImpl;
     private final ActiveModeWarden mActiveModeWarden;
     private final WifiSettingsStore mSettingsStore;
-    private OpenNetworkNotifier mOpenNetworkNotifier;
+    private final OpenNetworkNotifier mOpenNetworkNotifier;
     private final WifiLockManager mLockManager;
     private final WifiNl80211Manager mWifiCondManager;
     private final Clock mClock = new Clock();
     private final WifiMetrics mWifiMetrics;
     private final WifiP2pMetrics mWifiP2pMetrics;
-    private WifiLastResortWatchdog mWifiLastResortWatchdog;
+    private final WifiLastResortWatchdog mWifiLastResortWatchdog;
     private final PropertyService mPropertyService = new SystemPropertyService();
     private final BuildProperties mBuildProperties = new SystemBuildProperties();
     private final WifiBackupRestore mWifiBackupRestore;
+    // This will only be null if SdkLevel is not at least S
+    @Nullable private final CoexManager mCoexManager;
     private final SoftApBackupRestore mSoftApBackupRestore;
     private final WifiMulticastLockManager mWifiMulticastLockManager;
     private final WifiConfigStore mWifiConfigStore;
@@ -130,19 +179,20 @@
     private final ScoredNetworkNominator mScoredNetworkNominator;
     private final WifiNetworkScoreCache mWifiNetworkScoreCache;
     private final NetworkScoreManager mNetworkScoreManager;
+    private final ClientModeManagerBroadcastQueue mBroadcastQueue;
     private WifiScanner mWifiScanner;
     private final WifiPermissionsWrapper mWifiPermissionsWrapper;
     private final WifiPermissionsUtil mWifiPermissionsUtil;
     private final PasspointManager mPasspointManager;
     private HandlerThread mWifiAwareHandlerThread;
     private HandlerThread mRttHandlerThread;
-    private HalDeviceManager mHalDeviceManager;
+    private final HalDeviceManager mHalDeviceManager;
     private final WifiStateTracker mWifiStateTracker;
     private final SelfRecovery mSelfRecovery;
     private final WakeupController mWakeupController;
     private final ScanRequestProxy mScanRequestProxy;
     private final SarManager mSarManager;
-    private final BaseWifiDiagnostics mWifiDiagnostics;
+    private final WifiDiagnostics mWifiDiagnostics;
     private final WifiDataStall mWifiDataStall;
     private final WifiScoreCard mWifiScoreCard;
     private final WifiNetworkSuggestionsManager mWifiNetworkSuggestionsManager;
@@ -151,12 +201,11 @@
     private final LinkProbeManager mLinkProbeManager;
     private IpMemoryStore mIpMemoryStore;
     private final WifiThreadRunner mWifiThreadRunner;
-    private BssidBlocklistMonitor mBssidBlocklistMonitor;
-    private final MacAddressUtil mMacAddressUtil;
+    private final WifiBlocklistMonitor mWifiBlocklistMonitor;
+    private final MacAddressUtil mMacAddressUtil = new MacAddressUtil();
     private final MboOceController mMboOceController;
     private final WifiCarrierInfoManager mWifiCarrierInfoManager;
-    private WifiChannelUtilization mWifiChannelUtilizationScan;
-    private WifiChannelUtilization mWifiChannelUtilizationConnected;
+    private final WifiChannelUtilization mWifiChannelUtilizationScan;
     private final KeyStore mKeyStore;
     private final ConnectionFailureNotificationBuilder mConnectionFailureNotificationBuilder;
     private final ThroughputPredictor mThroughputPredictor;
@@ -167,6 +216,23 @@
             mWifiScanAlwaysAvailableSettingsCompatibility;
     private final SettingsMigrationDataHolder mSettingsMigrationDataHolder;
     private final LruConnectionTracker mLruConnectionTracker;
+    private final WifiConnectivityManager mWifiConnectivityManager;
+    private final ConnectHelper mConnectHelper;
+    private final ConnectionFailureNotifier mConnectionFailureNotifier;
+    private final WifiNetworkFactory mWifiNetworkFactory;
+    private final UntrustedWifiNetworkFactory mUntrustedWifiNetworkFactory;
+    private final OemWifiNetworkFactory mOemWifiNetworkFactory;
+    private final WifiP2pConnection mWifiP2pConnection;
+    private final WifiGlobals mWifiGlobals;
+    private final SimRequiredNotifier mSimRequiredNotifier;
+    private final DefaultClientModeManager mDefaultClientModeManager;
+    private final AdaptiveConnectivityEnabledSettingObserver
+            mAdaptiveConnectivityEnabledSettingObserver;
+    private final MakeBeforeBreakManager mMakeBeforeBreakManager;
+    private final ClientModeImplMonitor mCmiMonitor = new ClientModeImplMonitor();
+    private final ExternalScoreUpdateObserverProxy mExternalScoreUpdateObserverProxy;
+    private final WifiNotificationManager mWifiNotificationManager;
+    private final LastCallerInfoManager mLastCallerInfoManager;
 
     public WifiInjector(WifiContext context) {
         if (context == null) {
@@ -182,20 +248,21 @@
         sWifiInjector = this;
 
         // Now create and start handler threads
-        mAsyncChannelHandlerThread = new HandlerThread("AsyncChannelHandlerThread");
-        mAsyncChannelHandlerThread.start();
         mWifiHandlerThread = new HandlerThread("WifiHandlerThread");
         mWifiHandlerThread.start();
         Looper wifiLooper = mWifiHandlerThread.getLooper();
         Handler wifiHandler = new Handler(wifiLooper);
+        mWifiDiagnosticsHandlerThread = new HandlerThread("WifiDiagnostics");
+        mWifiDiagnosticsHandlerThread.start();
 
-        mFrameworkFacade = new FrameworkFacade();
-        mMacAddressUtil = new MacAddressUtil();
         mContext = context;
+        mWifiNotificationManager = new WifiNotificationManager(mContext);
+        mWifiGlobals = new WifiGlobals(mContext);
         mScoringParams = new ScoringParams(mContext);
+        mWifiChannelUtilizationScan = new WifiChannelUtilization(mClock, mContext);
         mSettingsMigrationDataHolder = new SettingsMigrationDataHolder(mContext);
         mConnectionFailureNotificationBuilder = new ConnectionFailureNotificationBuilder(
-                mContext, getWifiStackPackageName(), mFrameworkFacade);
+                mContext, mFrameworkFacade);
         mBatteryStats = context.getSystemService(BatteryStatsManager.class);
         mWifiPermissionsWrapper = new WifiPermissionsWrapper(mContext);
         mNetworkScoreManager = mContext.getSystemService(NetworkScoreManager.class);
@@ -219,33 +286,43 @@
         RttMetrics rttMetrics = new RttMetrics(mClock);
         mWifiP2pMetrics = new WifiP2pMetrics(mClock);
         mDppMetrics = new DppMetrics();
+        mWifiMonitor = new WifiMonitor();
         mWifiMetrics = new WifiMetrics(mContext, mFrameworkFacade, mClock, wifiLooper,
                 awareMetrics, rttMetrics, new WifiPowerMetrics(mBatteryStats), mWifiP2pMetrics,
-                mDppMetrics);
+                mDppMetrics, mWifiMonitor);
         mDeviceConfigFacade = new DeviceConfigFacade(mContext, wifiHandler, mWifiMetrics);
+        mAdaptiveConnectivityEnabledSettingObserver =
+                new AdaptiveConnectivityEnabledSettingObserver(wifiHandler, mWifiMetrics,
+                        mFrameworkFacade, mContext);
         // Modules interacting with Native.
-        mWifiMonitor = new WifiMonitor(this);
-        mHalDeviceManager = new HalDeviceManager(mClock, wifiHandler);
-        mWifiVendorHal = new WifiVendorHal(mHalDeviceManager, wifiHandler);
+        mHalDeviceManager = new HalDeviceManager(mClock, this, wifiHandler);
+        mWifiVendorHal = new WifiVendorHal(mContext, mHalDeviceManager, wifiHandler, mWifiGlobals);
         mSupplicantStaIfaceHal = new SupplicantStaIfaceHal(
-                mContext, mWifiMonitor, mFrameworkFacade, wifiHandler, mClock, mWifiMetrics);
+                mContext, mWifiMonitor, mFrameworkFacade, wifiHandler, mClock, mWifiMetrics,
+                mWifiGlobals);
         mHostapdHal = new HostapdHal(mContext, wifiHandler);
         mWifiCondManager = (WifiNl80211Manager) mContext.getSystemService(
                 Context.WIFI_NL80211_SERVICE);
         mWifiNative = new WifiNative(
                 mWifiVendorHal, mSupplicantStaIfaceHal, mHostapdHal, mWifiCondManager,
                 mWifiMonitor, mPropertyService, mWifiMetrics,
-                wifiHandler, new Random(), this);
-        mWifiP2pMonitor = new WifiP2pMonitor(this);
+                wifiHandler, new Random(), mBuildProperties, this);
+        mWifiP2pMonitor = new WifiP2pMonitor();
         mSupplicantP2pIfaceHal = new SupplicantP2pIfaceHal(mWifiP2pMonitor);
-        mWifiP2pNative = new WifiP2pNative(this,
-                mWifiVendorHal, mSupplicantP2pIfaceHal, mHalDeviceManager,
-                mPropertyService);
+        mWifiP2pNative = new WifiP2pNative(mWifiCondManager, mWifiNative,
+                mWifiVendorHal, mSupplicantP2pIfaceHal, mHalDeviceManager, mPropertyService);
+        SubscriptionManager subscriptionManager =
+                mContext.getSystemService(SubscriptionManager.class);
+        if (SdkLevel.isAtLeastS()) {
+            mCoexManager = new CoexManager(mContext, mWifiNative, makeTelephonyManager(),
+                    subscriptionManager, mContext.getSystemService(CarrierConfigManager.class),
+                    wifiHandler);
+        } else {
+            mCoexManager = null;
+        }
 
         // Now get instances of all the objects that depend on the HandlerThreads
-        mWifiTrafficPoller = new WifiTrafficPoller(wifiHandler);
-        mCountryCode = new WifiCountryCode(mContext, wifiHandler, mWifiNative,
-                SystemProperties.get(BOOT_DEFAULT_WIFI_COUNTRY_CODE));
+        mWifiTrafficPoller = new WifiTrafficPoller(mContext);
         // WifiConfigManager/Store objects and their dependencies.
         KeyStore keyStore = null;
         try {
@@ -254,42 +331,66 @@
             Log.wtf(TAG, "Failed to load keystore", e);
         }
         mKeyStore = keyStore;
-        mWifiKeyStore = new WifiKeyStore(mKeyStore);
+        mWifiKeyStore = new WifiKeyStore(mContext, mKeyStore, mFrameworkFacade);
         // New config store
         mWifiConfigStore = new WifiConfigStore(mContext, wifiHandler, mClock, mWifiMetrics,
                 WifiConfigStore.createSharedFiles(mFrameworkFacade.isNiapModeOn(mContext)));
-        SubscriptionManager subscriptionManager =
-                mContext.getSystemService(SubscriptionManager.class);
         mWifiCarrierInfoManager = new WifiCarrierInfoManager(makeTelephonyManager(),
                 subscriptionManager, this, mFrameworkFacade, mContext,
-                mWifiConfigStore, wifiHandler, mWifiMetrics);
+                mWifiConfigStore, wifiHandler, mWifiMetrics, mClock);
         String l2KeySeed = Secure.getString(mContext.getContentResolver(), Secure.ANDROID_ID);
-        mWifiScoreCard = new WifiScoreCard(mClock, l2KeySeed, mDeviceConfigFacade);
+        mWifiScoreCard = new WifiScoreCard(mClock, l2KeySeed, mDeviceConfigFacade,
+                mFrameworkFacade, mContext);
         mWifiMetrics.setWifiScoreCard(mWifiScoreCard);
         mLruConnectionTracker = new LruConnectionTracker(MAX_RECENTLY_CONNECTED_NETWORK,
                 mContext);
+        mWifiConnectivityHelper = new WifiConnectivityHelper(this);
+        int maxLinesLowRam = mContext.getResources().getInteger(
+                R.integer.config_wifiConnectivityLocalLogMaxLinesLowRam);
+        int maxLinesHighRam = mContext.getResources().getInteger(
+                R.integer.config_wifiConnectivityLocalLogMaxLinesHighRam);
+        mConnectivityLocalLog = new LocalLog(
+                mContext.getSystemService(ActivityManager.class).isLowRamDevice() ? maxLinesLowRam
+                        : maxLinesHighRam);
+        mWifiDiagnostics = new WifiDiagnostics(
+                mContext, this, mWifiNative, mBuildProperties,
+                new LastMileLogger(this), mClock, mWifiDiagnosticsHandlerThread.getLooper());
+        mWifiLastResortWatchdog = new WifiLastResortWatchdog(this, mContext, mClock,
+                mWifiMetrics, mWifiDiagnostics, wifiLooper,
+                mDeviceConfigFacade, mWifiThreadRunner, mWifiMonitor);
+        mWifiBlocklistMonitor = new WifiBlocklistMonitor(mContext, mWifiConnectivityHelper,
+                mWifiLastResortWatchdog, mClock, new LocalLog(
+                mContext.getSystemService(ActivityManager.class).isLowRamDevice() ? 128 : 256),
+                mWifiScoreCard, mScoringParams, mWifiMetrics);
+        mWifiMetrics.setWifiBlocklistMonitor(mWifiBlocklistMonitor);
         // Config Manager
         mWifiConfigManager = new WifiConfigManager(mContext, mClock,
                 mUserManager, mWifiCarrierInfoManager,
                 mWifiKeyStore, mWifiConfigStore, mWifiPermissionsUtil,
-                mWifiPermissionsWrapper, this,
+                mMacAddressUtil, mWifiMetrics, mWifiBlocklistMonitor, mWifiLastResortWatchdog,
                 new NetworkListSharedStoreData(mContext),
                 new NetworkListUserStoreData(mContext),
-                new RandomizedMacStoreData(), mFrameworkFacade, wifiHandler, mDeviceConfigFacade,
-                mWifiScoreCard, mLruConnectionTracker);
+                new RandomizedMacStoreData(), mFrameworkFacade, mDeviceConfigFacade,
+                mWifiScoreCard, mLruConnectionTracker, mBuildProperties);
         mSettingsConfigStore = new WifiSettingsConfigStore(context, wifiHandler,
                 mSettingsMigrationDataHolder, mWifiConfigManager, mWifiConfigStore);
         mSettingsStore = new WifiSettingsStore(mContext, mSettingsConfigStore);
         mWifiMetrics.setWifiConfigManager(mWifiConfigManager);
+        mWifiMetrics.setWifiSettingsStore(mSettingsStore);
 
-        mWifiConnectivityHelper = new WifiConnectivityHelper(mWifiNative);
-        mConnectivityLocalLog = new LocalLog(
-                mContext.getSystemService(ActivityManager.class).isLowRamDevice() ? 256 : 512);
         mWifiMetrics.setScoringParams(mScoringParams);
         mThroughputPredictor = new ThroughputPredictor(mContext);
+        mScanRequestProxy = new ScanRequestProxy(mContext,
+                mContext.getSystemService(AppOpsManager.class),
+                mContext.getSystemService(ActivityManager.class),
+                this, mWifiConfigManager,
+                mWifiPermissionsUtil, mWifiMetrics, mClock, wifiHandler, mSettingsConfigStore);
+        mSarManager = new SarManager(mContext, makeTelephonyManager(), wifiLooper,
+                mWifiNative);
         mWifiNetworkSelector = new WifiNetworkSelector(mContext, mWifiScoreCard, mScoringParams,
-                mWifiConfigManager, mClock, mConnectivityLocalLog, mWifiMetrics, mWifiNative,
-                mThroughputPredictor);
+                mWifiConfigManager, mClock, mConnectivityLocalLog, mWifiMetrics, this,
+                mThroughputPredictor, mWifiChannelUtilizationScan, mWifiGlobals,
+                mScanRequestProxy);
         CompatibilityScorer compatibilityScorer = new CompatibilityScorer(mScoringParams);
         mWifiNetworkSelector.registerCandidateScorer(compatibilityScorer);
         ScoreCardBasedScorer scoreCardBasedScorer = new ScoreCardBasedScorer(mScoringParams);
@@ -301,7 +402,8 @@
         mWifiMetrics.setWifiNetworkSelector(mWifiNetworkSelector);
         mWifiNetworkSuggestionsManager = new WifiNetworkSuggestionsManager(mContext, wifiHandler,
                 this, mWifiPermissionsUtil, mWifiConfigManager, mWifiConfigStore, mWifiMetrics,
-                mWifiCarrierInfoManager, mWifiKeyStore, mLruConnectionTracker);
+                mWifiCarrierInfoManager, mWifiKeyStore, mLruConnectionTracker,
+                mClock);
         mPasspointManager = new PasspointManager(mContext, this,
                 wifiHandler, mWifiNative, mWifiKeyStore, mClock, new PasspointObjectFactory(),
                 mWifiConfigManager, mWifiConfigStore, mWifiMetrics, mWifiCarrierInfoManager,
@@ -313,47 +415,80 @@
                 mWifiConfigManager, nominateHelper, mConnectivityLocalLog, mWifiCarrierInfoManager,
                 mWifiPermissionsUtil, mWifiNetworkSuggestionsManager);
         mNetworkSuggestionNominator = new NetworkSuggestionNominator(mWifiNetworkSuggestionsManager,
-                mWifiConfigManager, nominateHelper, mConnectivityLocalLog, mWifiCarrierInfoManager);
+                mWifiConfigManager, nominateHelper, mConnectivityLocalLog, mWifiCarrierInfoManager,
+                mWifiMetrics);
         mScoredNetworkNominator = new ScoredNetworkNominator(mContext, wifiHandler,
                 mFrameworkFacade, mNetworkScoreManager, mContext.getPackageManager(),
                 mWifiConfigManager, mConnectivityLocalLog,
                 mWifiNetworkScoreCache, mWifiPermissionsUtil);
 
         mWifiMetrics.setPasspointManager(mPasspointManager);
-        mScanRequestProxy = new ScanRequestProxy(mContext,
-                (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE),
-                (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE),
-                this, mWifiConfigManager,
-                mWifiPermissionsUtil, mWifiMetrics, mClock, wifiHandler, mSettingsConfigStore);
-        mSarManager = new SarManager(mContext, makeTelephonyManager(), wifiLooper,
-                mWifiNative);
-        mWifiDiagnostics = new WifiDiagnostics(
-                mContext, this, mWifiNative, mBuildProperties,
-                new LastMileLogger(this), mClock);
-        mWifiChannelUtilizationConnected = new WifiChannelUtilization(mClock, mContext);
-        mWifiDataStall = new WifiDataStall(mFrameworkFacade, mWifiMetrics, mContext,
-                mDeviceConfigFacade, mWifiChannelUtilizationConnected, mClock, wifiHandler,
-                mThroughputPredictor);
-        mWifiMetrics.setWifiDataStall(mWifiDataStall);
+        WifiChannelUtilization wifiChannelUtilizationConnected =
+                new WifiChannelUtilization(mClock, mContext);
+        mWifiMetrics.setWifiChannelUtilization(wifiChannelUtilizationConnected);
         mLinkProbeManager = new LinkProbeManager(mClock, mWifiNative, mWifiMetrics,
                 mFrameworkFacade, wifiHandler, mContext);
-        SupplicantStateTracker supplicantStateTracker = new SupplicantStateTracker(
-                mContext, mWifiConfigManager, mBatteryStats, wifiHandler);
-        mMboOceController = new MboOceController(makeTelephonyManager(), mWifiNative);
-        mWifiHealthMonitor = new WifiHealthMonitor(mContext, this, mClock, mWifiConfigManager,
-                mWifiScoreCard, wifiHandler, mWifiNative, l2KeySeed, mDeviceConfigFacade);
-        mWifiMetrics.setWifiHealthMonitor(mWifiHealthMonitor);
-        mClientModeImpl = new ClientModeImpl(mContext, mFrameworkFacade,
-                wifiLooper, mUserManager,
-                this, mBackupManagerProxy, mCountryCode, mWifiNative,
-                new WrongPasswordNotifier(mContext, mFrameworkFacade),
-                mSarManager, mWifiTrafficPoller, mLinkProbeManager, mBatteryStats,
-                supplicantStateTracker, mMboOceController, mWifiCarrierInfoManager,
-                new EapFailureNotifier(mContext, mFrameworkFacade, mWifiCarrierInfoManager),
-                new SimRequiredNotifier(mContext, mFrameworkFacade));
+        mDefaultClientModeManager = new DefaultClientModeManager();
+        mExternalScoreUpdateObserverProxy =
+                new ExternalScoreUpdateObserverProxy(mWifiThreadRunner);
+        mDppManager = new DppManager(wifiHandler, mWifiNative,
+                mWifiConfigManager, mContext, mDppMetrics, mScanRequestProxy, mWifiPermissionsUtil);
         mActiveModeWarden = new ActiveModeWarden(this, wifiLooper,
-                mWifiNative, new DefaultModeManager(mContext), mBatteryStats, mWifiDiagnostics,
-                mContext, mClientModeImpl, mSettingsStore, mFrameworkFacade, mWifiPermissionsUtil);
+                mWifiNative, mDefaultClientModeManager, mBatteryStats, mWifiDiagnostics,
+                mContext, mSettingsStore, mFrameworkFacade, mWifiPermissionsUtil, mWifiMetrics,
+                mExternalScoreUpdateObserverProxy, mDppManager);
+        mWifiMetrics.setActiveModeWarden(mActiveModeWarden);
+        mWifiHealthMonitor = new WifiHealthMonitor(mContext, this, mClock, mWifiConfigManager,
+            mWifiScoreCard, wifiHandler, mWifiNative, l2KeySeed, mDeviceConfigFacade,
+            mActiveModeWarden);
+        mWifiDataStall = new WifiDataStall(mFrameworkFacade, mWifiMetrics, mContext,
+                mDeviceConfigFacade, wifiChannelUtilizationConnected, mClock, wifiHandler,
+                mThroughputPredictor, mActiveModeWarden, mCmiMonitor);
+        mWifiMetrics.setWifiDataStall(mWifiDataStall);
+        mWifiMetrics.setWifiHealthMonitor(mWifiHealthMonitor);
+        mWifiP2pConnection = new WifiP2pConnection(mContext, wifiLooper, mActiveModeWarden);
+        mConnectHelper = new ConnectHelper(mActiveModeWarden, mWifiConfigManager);
+        mBroadcastQueue = new ClientModeManagerBroadcastQueue(mActiveModeWarden, mContext);
+        mMakeBeforeBreakManager = new MakeBeforeBreakManager(mActiveModeWarden, mFrameworkFacade,
+                mContext, mCmiMonitor, mBroadcastQueue, mWifiMetrics);
+        mOpenNetworkNotifier = new OpenNetworkNotifier(mContext,
+                wifiLooper, mFrameworkFacade, mClock, mWifiMetrics,
+                mWifiConfigManager, mWifiConfigStore, mConnectHelper,
+                new ConnectToNetworkNotificationBuilder(mContext, mFrameworkFacade),
+                mMakeBeforeBreakManager, mWifiNotificationManager);
+        mWifiConnectivityManager = new WifiConnectivityManager(
+                mContext, mScoringParams, mWifiConfigManager,
+                mWifiNetworkSuggestionsManager, mWifiNetworkSelector,
+                mWifiConnectivityHelper, mWifiLastResortWatchdog, mOpenNetworkNotifier,
+                mWifiMetrics, wifiHandler,
+                mClock, mConnectivityLocalLog, mWifiScoreCard, mWifiBlocklistMonitor,
+                mWifiChannelUtilizationScan, mPasspointManager, mDeviceConfigFacade,
+                mActiveModeWarden, mWifiGlobals);
+        mMboOceController = new MboOceController(makeTelephonyManager(), mActiveModeWarden);
+        mCountryCode = new WifiCountryCode(mContext, mActiveModeWarden,
+                mCmiMonitor, mWifiNative, mSettingsConfigStore);
+        mConnectionFailureNotifier = new ConnectionFailureNotifier(
+                mContext, mFrameworkFacade, mWifiConfigManager,
+                mWifiConnectivityManager, wifiHandler,
+                mWifiNotificationManager, mConnectionFailureNotificationBuilder);
+        mWifiNetworkFactory = new WifiNetworkFactory(
+                wifiLooper, mContext, REGULAR_NETWORK_CAPABILITIES_FILTER,
+                (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE),
+                (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE),
+                (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE),
+                mClock, this, mWifiConnectivityManager, mWifiConfigManager,
+                mWifiConfigStore, mWifiPermissionsUtil, mWifiMetrics, mActiveModeWarden,
+                mConnectHelper, mCmiMonitor);
+        // We can't filter untrusted network in the capabilities filter because a trusted
+        // network would still satisfy a request that accepts untrusted ones.
+        // We need a second network factory for untrusted network requests because we need a
+        // different score filter for these requests.
+        mUntrustedWifiNetworkFactory = new UntrustedWifiNetworkFactory(
+                wifiLooper, mContext, REGULAR_NETWORK_CAPABILITIES_FILTER,
+                mWifiConnectivityManager);
+        mOemWifiNetworkFactory = new OemWifiNetworkFactory(
+                wifiLooper, mContext, OEM_NETWORK_CAPABILITIES_FILTER,
+                mWifiConnectivityManager);
         mWifiScanAlwaysAvailableSettingsCompatibility =
                 new WifiScanAlwaysAvailableSettingsCompatibility(mContext, wifiHandler,
                         mSettingsStore, mActiveModeWarden, mFrameworkFacade);
@@ -361,35 +496,34 @@
                 mContext, this, wifiHandler, mBackupManagerProxy,
                 mWifiConfigStore, mWifiConfigManager, mActiveModeWarden, mWifiMetrics);
         WakeupNotificationFactory wakeupNotificationFactory =
-                new WakeupNotificationFactory(mContext, this, mFrameworkFacade);
+                new WakeupNotificationFactory(mContext, mFrameworkFacade);
         WakeupOnboarding wakeupOnboarding = new WakeupOnboarding(mContext, mWifiConfigManager,
-                wifiHandler, mFrameworkFacade, wakeupNotificationFactory);
+                wifiHandler, mFrameworkFacade, wakeupNotificationFactory, mWifiNotificationManager);
         mWakeupController = new WakeupController(mContext, wifiHandler,
                 new WakeupLock(mWifiConfigManager, mWifiMetrics.getWakeupMetrics(), mClock),
                 new WakeupEvaluator(mScoringParams), wakeupOnboarding, mWifiConfigManager,
                 mWifiConfigStore, mWifiNetworkSuggestionsManager, mWifiMetrics.getWakeupMetrics(),
-                this, mFrameworkFacade, mClock);
-        mLockManager = new WifiLockManager(mContext, mBatteryStats,
-                mClientModeImpl, mFrameworkFacade, wifiHandler, mWifiNative, mClock, mWifiMetrics);
-        mSelfRecovery = new SelfRecovery(mContext, mActiveModeWarden, mClock);
-        mWifiMulticastLockManager = new WifiMulticastLockManager(
-                mClientModeImpl.getMcastLockManagerFilterController(), mBatteryStats);
-        mDppManager = new DppManager(wifiHandler, mWifiNative,
-                mWifiConfigManager, mContext, mDppMetrics, mScanRequestProxy);
+                this, mFrameworkFacade, mClock, mActiveModeWarden);
+        mLockManager = new WifiLockManager(mContext, mBatteryStats, mActiveModeWarden,
+                mFrameworkFacade, wifiHandler, mClock, mWifiMetrics);
+        mSelfRecovery = new SelfRecovery(mContext, mActiveModeWarden, mClock, mWifiNative);
+        mWifiMulticastLockManager = new WifiMulticastLockManager(mActiveModeWarden, mBatteryStats);
 
         // Register the various network Nominators with the network selector.
         mWifiNetworkSelector.registerNetworkNominator(mSavedNetworkNominator);
         mWifiNetworkSelector.registerNetworkNominator(mNetworkSuggestionNominator);
         mWifiNetworkSelector.registerNetworkNominator(mScoredNetworkNominator);
 
-        mClientModeImpl.start();
+        mSimRequiredNotifier = new SimRequiredNotifier(mContext, mFrameworkFacade,
+                mWifiNotificationManager);
+        mLastCallerInfoManager = new LastCallerInfoManager();
     }
 
     /**
-     *  Obtain an instance of the WifiInjector class.
+     * Obtain an instance of the WifiInjector class.
      *
-     *  This is the generic method to get an instance of the class. The first instance should be
-     *  retrieved using the getInstanceWithContext method.
+     * This is the generic method to get an instance of the class. The first instance should be
+     * retrieved using the getInstanceWithContext method.
      */
     public static WifiInjector getInstance() {
         if (sWifiInjector == null) {
@@ -413,6 +547,31 @@
         LogcatLog.enableVerboseLogging(verbose);
         mDppManager.enableVerboseLogging(verbose);
         mWifiCarrierInfoManager.enableVerboseLogging(verbose);
+
+        boolean verboseBool = verbose > 0;
+        Log.i(TAG, "enableVerboseLogging(" + verbose + "): " + verboseBool);
+        mCountryCode.enableVerboseLogging(verboseBool);
+        mWifiDiagnostics.enableVerboseLogging(verboseBool);
+        mWifiMonitor.enableVerboseLogging(verboseBool);
+        mWifiNative.enableVerboseLogging(verboseBool);
+        mWifiConfigManager.enableVerboseLogging(verboseBool);
+        mPasspointManager.enableVerboseLogging(verboseBool);
+        mWifiNetworkFactory.enableVerboseLogging(verboseBool);
+        mLinkProbeManager.enableVerboseLogging(verboseBool);
+        mMboOceController.enableVerboseLogging(verboseBool);
+        mWifiScoreCard.enableVerboseLogging(verboseBool);
+        mWifiHealthMonitor.enableVerboseLogging(verboseBool);
+        mThroughputPredictor.enableVerboseLogging(verboseBool);
+        mWifiDataStall.enableVerboseLogging(verboseBool);
+        mWifiConnectivityManager.enableVerboseLogging(verboseBool);
+        mWifiNetworkSelector.enableVerboseLogging(verboseBool);
+        mMakeBeforeBreakManager.setVerboseLoggingEnabled(verboseBool);
+        mBroadcastQueue.setVerboseLoggingEnabled(verboseBool);
+        if (SdkLevel.isAtLeastS()) {
+            mCoexManager.enableVerboseLogging(verboseBool);
+        }
+        mWifiPermissionsWrapper.enableVerboseLogging(verboseBool);
+        mWifiPermissionsUtil.enableVerboseLogging(verboseBool);
     }
 
     public UserManager getUserManager() {
@@ -439,10 +598,6 @@
         return mFrameworkFacade;
     }
 
-    public HandlerThread getAsyncChannelHandlerThread() {
-        return mAsyncChannelHandlerThread;
-    }
-
     public HandlerThread getWifiP2pServiceHandlerThread() {
         return mWifiP2pServiceHandlerThread;
     }
@@ -471,10 +626,6 @@
         return mSarManager;
     }
 
-    public ClientModeImpl getClientModeImpl() {
-        return mClientModeImpl;
-    }
-
     public ActiveModeWarden getActiveModeWarden() {
         return mActiveModeWarden;
     }
@@ -495,14 +646,6 @@
         return mClock;
     }
 
-    public PropertyService getPropertyService() {
-        return mPropertyService;
-    }
-
-    public BuildProperties getBuildProperties() {
-        return mBuildProperties;
-    }
-
     public WifiBackupRestore getWifiBackupRestore() {
         return mWifiBackupRestore;
     }
@@ -536,32 +679,87 @@
     }
 
     public TelephonyManager makeTelephonyManager() {
-        return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        return mContext.getSystemService(TelephonyManager.class);
     }
 
     public WifiCarrierInfoManager getWifiCarrierInfoManager() {
         return mWifiCarrierInfoManager;
     }
 
-    public WifiStateTracker getWifiStateTracker() {
-        return mWifiStateTracker;
-    }
-
     public DppManager getDppManager() {
         return mDppManager;
     }
 
     /**
      * Create a SoftApManager.
+     *
      * @param config SoftApModeConfiguration object holding the config and mode
      * @return an instance of SoftApManager
      */
-    public SoftApManager makeSoftApManager(@NonNull ActiveModeManager.Listener listener,
-                                           @NonNull WifiManager.SoftApCallback callback,
-                                           @NonNull SoftApModeConfiguration config) {
+    public SoftApManager makeSoftApManager(
+            @NonNull ActiveModeManager.Listener<SoftApManager> listener,
+            @NonNull WifiServiceImpl.SoftApCallbackInternal callback,
+            @NonNull SoftApModeConfiguration config,
+            @NonNull WorkSource requestorWs,
+            @NonNull ActiveModeManager.SoftApRole role,
+            boolean verboseLoggingEnabled) {
         return new SoftApManager(mContext, mWifiHandlerThread.getLooper(),
-                mFrameworkFacade, mWifiNative, mCountryCode.getCountryCode(), listener, callback,
-                mWifiApConfigStore, config, mWifiMetrics, mSarManager, mWifiDiagnostics);
+                mFrameworkFacade, mWifiNative, mCoexManager, mCountryCode.getCountryCode(),
+                listener, callback, mWifiApConfigStore, config, mWifiMetrics, mSarManager,
+                mWifiDiagnostics, new SoftApNotifier(mContext, mFrameworkFacade,
+                mWifiNotificationManager), mCmiMonitor, mActiveModeWarden,
+                mClock.getElapsedSinceBootMillis(), requestorWs, role, verboseLoggingEnabled);
+    }
+
+    /**
+     * Create a ClientModeImpl
+     * @param ifaceName interface name for the ClientModeImpl
+     * @param clientModeManager ClientModeManager that will own the ClientModeImpl
+     */
+    public ClientModeImpl makeClientModeImpl(
+            @NonNull String ifaceName,
+            @NonNull ConcreteClientModeManager clientModeManager,
+            boolean verboseLoggingEnabled) {
+        ExtendedWifiInfo wifiInfo = new ExtendedWifiInfo(mWifiGlobals, ifaceName);
+        SupplicantStateTracker supplicantStateTracker = new SupplicantStateTracker(
+                mContext, mWifiConfigManager, mBatteryStats, mWifiHandlerThread.getLooper(),
+                mWifiMonitor, ifaceName, clientModeManager, mBroadcastQueue);
+        supplicantStateTracker.enableVerboseLogging(verboseLoggingEnabled);
+        return new ClientModeImpl(mContext, mWifiMetrics, mClock,
+                mWifiScoreCard, mWifiStateTracker, mWifiPermissionsUtil, mWifiConfigManager,
+                mPasspointManager, mWifiMonitor, mWifiDiagnostics,
+                mWifiDataStall, mScoringParams, mWifiThreadRunner,
+                mWifiNetworkSuggestionsManager, mWifiHealthMonitor, mThroughputPredictor,
+                mDeviceConfigFacade, mScanRequestProxy, wifiInfo, mWifiConnectivityManager,
+                mWifiBlocklistMonitor, mConnectionFailureNotifier,
+                REGULAR_NETWORK_CAPABILITIES_FILTER, mWifiNetworkFactory,
+                mUntrustedWifiNetworkFactory, mOemWifiNetworkFactory,
+                mWifiLastResortWatchdog, mWakeupController,
+                mLockManager, mFrameworkFacade, mWifiHandlerThread.getLooper(),
+                mWifiNative, new WrongPasswordNotifier(mContext, mFrameworkFacade,
+                mWifiNotificationManager),
+                mWifiTrafficPoller, mLinkProbeManager, mClock.getElapsedSinceBootMillis(),
+                mBatteryStats, supplicantStateTracker, mMboOceController, mWifiCarrierInfoManager,
+                new EapFailureNotifier(mContext, mFrameworkFacade, mWifiCarrierInfoManager,
+                        mWifiNotificationManager),
+                mSimRequiredNotifier,
+                new WifiScoreReport(mScoringParams, mClock, mWifiMetrics, wifiInfo,
+                        mWifiNative, mWifiBlocklistMonitor, mWifiThreadRunner, mWifiScoreCard,
+                        mDeviceConfigFacade, mContext, mAdaptiveConnectivityEnabledSettingObserver,
+                        ifaceName, mExternalScoreUpdateObserverProxy, mSettingsStore),
+                mWifiP2pConnection, mWifiGlobals, ifaceName, clientModeManager,
+                mCmiMonitor, mBroadcastQueue, mWifiNetworkSelector, makeTelephonyManager(),
+                this, mSettingsConfigStore, verboseLoggingEnabled);
+    }
+
+    public WifiNetworkAgent makeWifiNetworkAgent(
+            @NonNull NetworkCapabilities nc,
+            @NonNull LinkProperties linkProperties,
+            @NonNull NetworkAgentConfig naConfig,
+            @Nullable NetworkProvider provider,
+            @NonNull WifiNetworkAgent.Callback callback) {
+        return new WifiNetworkAgent(mContext, mWifiHandlerThread.getLooper(),
+                nc, linkProperties, naConfig, provider, callback);
     }
 
     /**
@@ -570,24 +768,32 @@
      * @param listener listener for ClientModeManager state changes
      * @return a new instance of ClientModeManager
      */
-    public ClientModeManager makeClientModeManager(ClientModeManager.Listener listener) {
-        return new ClientModeManager(mContext, mWifiHandlerThread.getLooper(), mClock,
-                mWifiNative, listener, mWifiMetrics, mSarManager, mWakeupController,
-                mClientModeImpl);
+    public ConcreteClientModeManager makeClientModeManager(
+            @NonNull ClientModeManager.Listener<ConcreteClientModeManager> listener,
+            @NonNull WorkSource requestorWs,
+            @NonNull ActiveModeManager.ClientRole role,
+            boolean verboseLoggingEnabled) {
+        return new ConcreteClientModeManager(
+                mContext, mWifiHandlerThread.getLooper(), mClock,
+                mWifiNative, listener, mWifiMetrics, mWakeupController,
+                this, mSelfRecovery, mWifiGlobals, mDefaultClientModeManager,
+                mClock.getElapsedSinceBootMillis(), requestorWs, role, mBroadcastQueue,
+                verboseLoggingEnabled);
+    }
+
+    public ScanOnlyModeImpl makeScanOnlyModeImpl(@NonNull String ifaceName) {
+        return new ScanOnlyModeImpl(mClock.getElapsedSinceBootMillis(), mWifiNative, ifaceName);
     }
 
     /**
      * Create a WifiLog instance.
+     *
      * @param tag module name to include in all log messages
      */
     public WifiLog makeLog(String tag) {
         return new LogcatLog(tag);
     }
 
-    public BaseWifiDiagnostics getWifiDiagnostics() {
-        return mWifiDiagnostics;
-    }
-
     /**
      * Obtain an instance of WifiScanner.
      * If it was not already created, then obtain an instance.  Note, this must be done lazily since
@@ -601,61 +807,6 @@
     }
 
     /**
-     * Construct a new instance of WifiConnectivityManager & its dependencies.
-     *
-     * Create and return a new WifiConnectivityManager.
-     * @param clientModeImpl Instance of client mode impl.
-     * TODO(b/116233964): Remove cyclic dependency between WifiConnectivityManager & ClientModeImpl.
-     */
-    public WifiConnectivityManager makeWifiConnectivityManager(ClientModeImpl clientModeImpl) {
-        mOpenNetworkNotifier = new OpenNetworkNotifier(mContext,
-                mWifiHandlerThread.getLooper(), mFrameworkFacade, mClock, mWifiMetrics,
-                mWifiConfigManager, mWifiConfigStore, clientModeImpl,
-                new ConnectToNetworkNotificationBuilder(mContext, this, mFrameworkFacade));
-        mWifiLastResortWatchdog = new WifiLastResortWatchdog(this, mContext, mClock,
-                mWifiMetrics, clientModeImpl, mWifiHandlerThread.getLooper(), mDeviceConfigFacade,
-                mWifiThreadRunner);
-        mBssidBlocklistMonitor = new BssidBlocklistMonitor(mContext, mWifiConnectivityHelper,
-                mWifiLastResortWatchdog, mClock, mConnectivityLocalLog, mWifiScoreCard,
-                mScoringParams);
-        mWifiMetrics.setBssidBlocklistMonitor(mBssidBlocklistMonitor);
-        mWifiChannelUtilizationScan = new WifiChannelUtilization(mClock, mContext);
-        return new WifiConnectivityManager(mContext, getScoringParams(),
-                clientModeImpl, this,
-                mWifiConfigManager, mWifiNetworkSuggestionsManager, clientModeImpl.getWifiInfo(),
-                mWifiNetworkSelector, mWifiConnectivityHelper,
-                mWifiLastResortWatchdog, mOpenNetworkNotifier,
-                mWifiMetrics, new Handler(mWifiHandlerThread.getLooper()),
-                mClock, mConnectivityLocalLog, mWifiScoreCard);
-    }
-
-    /**
-     * Construct a new instance of ConnectionFailureNotifier.
-     * @param wifiConnectivityManager
-     * @return the created instance
-     */
-    public ConnectionFailureNotifier makeConnectionFailureNotifier(
-            WifiConnectivityManager wifiConnectivityManager) {
-        return new ConnectionFailureNotifier(mContext, this, mFrameworkFacade, mWifiConfigManager,
-                wifiConnectivityManager, new Handler(mWifiHandlerThread.getLooper()));
-    }
-
-    /**
-     * Construct a new instance of {@link WifiNetworkFactory}.
-     * TODO(b/116233964): Remove cyclic dependency between WifiConnectivityManager & ClientModeImpl.
-     */
-    public WifiNetworkFactory makeWifiNetworkFactory(
-            NetworkCapabilities nc, WifiConnectivityManager wifiConnectivityManager) {
-        return new WifiNetworkFactory(
-                mWifiHandlerThread.getLooper(), mContext, nc,
-                (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE),
-                (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE),
-                (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE),
-                mClock, this, wifiConnectivityManager, mWifiConfigManager,
-                mWifiConfigStore, mWifiPermissionsUtil, mWifiMetrics);
-    }
-
-    /**
      * Construct an instance of {@link NetworkRequestStoreData}.
      */
     public NetworkRequestStoreData makeNetworkRequestStoreData(
@@ -664,16 +815,6 @@
     }
 
     /**
-     * Construct a new instance of {@link UntrustedWifiNetworkFactory}.
-     * TODO(b/116233964): Remove cyclic dependency between WifiConnectivityManager & ClientModeImpl.
-     */
-    public UntrustedWifiNetworkFactory makeUntrustedWifiNetworkFactory(
-            NetworkCapabilities nc, WifiConnectivityManager wifiConnectivityManager) {
-        return new UntrustedWifiNetworkFactory(
-                mWifiHandlerThread.getLooper(), mContext, nc, wifiConnectivityManager);
-    }
-
-    /**
      * Construct an instance of {@link NetworkSuggestionStoreData}.
      */
     public NetworkSuggestionStoreData makeNetworkSuggestionStoreData(
@@ -682,9 +823,17 @@
     }
 
     /**
-     *
+     * Construct an instance of {@link WifiCarrierInfoStoreManagerData}
      */
-    public ImsiPrivacyProtectionExemptionStoreData makeImsiProtectionExemptionStoreData(
+    public WifiCarrierInfoStoreManagerData makeWifiCarrierInfoStoreManagerData(
+            WifiCarrierInfoStoreManagerData.DataSource dataSource) {
+        return new WifiCarrierInfoStoreManagerData(dataSource);
+    }
+
+    /**
+     * Construct an instance of {@link ImsiPrivacyProtectionExemptionStoreData}
+     */
+    public ImsiPrivacyProtectionExemptionStoreData makeImsiPrivacyProtectionExemptionStoreData(
             ImsiPrivacyProtectionExemptionStoreData.DataSource dataSource) {
         return new ImsiPrivacyProtectionExemptionStoreData(dataSource);
     }
@@ -735,14 +884,6 @@
         return mMacAddressUtil;
     }
 
-    public NotificationManager getNotificationManager() {
-        return (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
-    }
-
-    public ConnectionFailureNotificationBuilder getConnectionFailureNotificationBuilder() {
-        return mConnectionFailureNotificationBuilder;
-    }
-
     /**
      * Returns a single instance of HalDeviceManager for injection.
      */
@@ -762,6 +903,14 @@
         return mWifiP2pNative;
     }
 
+    /**
+     * Returns a single instance of CoexManager for injection.
+     * This will be null if SdkLevel is not at least S.
+     */
+    @Nullable public CoexManager getCoexManager() {
+        return mCoexManager;
+    }
+
     public WifiP2pMonitor getWifiP2pMonitor() {
         return mWifiP2pMonitor;
     }
@@ -793,26 +942,18 @@
         return mIpMemoryStore;
     }
 
-    public BssidBlocklistMonitor getBssidBlocklistMonitor() {
-        return mBssidBlocklistMonitor;
+    public WifiBlocklistMonitor getWifiBlocklistMonitor() {
+        return mWifiBlocklistMonitor;
     }
 
     public HostapdHal getHostapdHal() {
         return mHostapdHal;
     }
 
-    public String getWifiStackPackageName() {
-       return mContext.getPackageName();
-    }
-
     public WifiThreadRunner getWifiThreadRunner() {
         return mWifiThreadRunner;
     }
 
-    public WifiChannelUtilization getWifiChannelUtilizationScan() {
-        return mWifiChannelUtilizationScan;
-    }
-
     public WifiNetworkScoreCache getWifiNetworkScoreCache() {
         return mWifiNetworkScoreCache;
     }
@@ -832,10 +973,6 @@
         return mWifiHealthMonitor;
     }
 
-    public ThroughputPredictor getThroughputPredictor() {
-        return mThroughputPredictor;
-    }
-
     public WifiSettingsConfigStore getSettingsConfigStore() {
         return mSettingsConfigStore;
     }
@@ -848,4 +985,81 @@
     public DeviceConfigFacade getDeviceConfigFacade() {
         return mDeviceConfigFacade;
     }
+
+    public WifiConnectivityManager getWifiConnectivityManager() {
+        return mWifiConnectivityManager;
+    }
+
+    public ConnectHelper getConnectHelper() {
+        return mConnectHelper;
+    }
+
+    public WifiNetworkFactory getWifiNetworkFactory() {
+        return mWifiNetworkFactory;
+    }
+
+    public UntrustedWifiNetworkFactory getUntrustedWifiNetworkFactory() {
+        return mUntrustedWifiNetworkFactory;
+    }
+
+    public OemWifiNetworkFactory getOemWifiNetworkFactory() {
+        return mOemWifiNetworkFactory;
+    }
+
+    public WifiDiagnostics getWifiDiagnostics() {
+        return mWifiDiagnostics;
+    }
+
+    public WifiP2pConnection getWifiP2pConnection() {
+        return mWifiP2pConnection;
+    }
+
+    public WifiGlobals getWifiGlobals() {
+        return mWifiGlobals;
+    }
+
+    public SimRequiredNotifier getSimRequiredNotifier() {
+        return mSimRequiredNotifier;
+    }
+
+    /**
+     * Useful for mocking {@link WorkSourceHelper} instance in {@link HalDeviceManager} unit tests.
+     */
+    public WorkSourceHelper makeWsHelper(@NonNull WorkSource ws) {
+        return new WorkSourceHelper(ws, mWifiPermissionsUtil,
+                mContext.getSystemService(ActivityManager.class), mContext.getPackageManager());
+    }
+
+    public AdaptiveConnectivityEnabledSettingObserver
+            getAdaptiveConnectivityEnabledSettingObserver() {
+        return mAdaptiveConnectivityEnabledSettingObserver;
+    }
+
+    public MakeBeforeBreakManager getMakeBeforeBreakManager() {
+        return mMakeBeforeBreakManager;
+    }
+
+    public OpenNetworkNotifier getOpenNetworkNotifier() {
+        return mOpenNetworkNotifier;
+    }
+
+    public WifiNotificationManager getWifiNotificationManager() {
+        return mWifiNotificationManager;
+    }
+
+    public LastCallerInfoManager getLastCallerInfoManager() {
+        return mLastCallerInfoManager;
+    }
+
+    public BuildProperties getBuildProperties() {
+        return mBuildProperties;
+    }
+
+    public DefaultClientModeManager getDefaultClientModeManager() {
+        return mDefaultClientModeManager;
+    }
+
+    public LinkProbeManager getLinkProbeManager() {
+        return mLinkProbeManager;
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiKeyStore.java b/service/java/com/android/server/wifi/WifiKeyStore.java
index c248d22..71559d2 100644
--- a/service/java/com/android/server/wifi/WifiKeyStore.java
+++ b/service/java/com/android/server/wifi/WifiKeyStore.java
@@ -17,14 +17,17 @@
 package com.android.server.wifi;
 
 import android.annotation.Nullable;
+import android.content.Context;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiEnterpriseConfig;
+import android.os.UserHandle;
 import android.security.KeyChain;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
 
 import com.android.internal.util.Preconditions;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.util.ArrayUtils;
 
 import java.security.Key;
@@ -33,6 +36,9 @@
 import java.security.Principal;
 import java.security.cert.Certificate;
 import java.security.cert.X509Certificate;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.ECParameterSpec;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -49,12 +55,16 @@
     private boolean mVerboseLoggingEnabled = false;
 
     @Nullable private final KeyStore mKeyStore;
+    private final Context mContext;
+    private final FrameworkFacade mFrameworkFacade;
 
-    WifiKeyStore(@Nullable KeyStore keyStore) {
+    WifiKeyStore(Context context, @Nullable KeyStore keyStore, FrameworkFacade frameworkFacade) {
         mKeyStore = keyStore;
         if (mKeyStore == null) {
             Log.e(TAG, "Unable to retrieve keystore, all key operations will fail");
         }
+        mContext = context;
+        mFrameworkFacade = frameworkFacade;
     }
 
     /**
@@ -101,7 +111,8 @@
         }
         X509Certificate[] caCertificates = config.getCaCertificates();
         Set<String> oldCaCertificatesToRemove = new ArraySet<>();
-        if (existingConfig != null && existingConfig.getCaCertificateAliases() != null) {
+        if (existingConfig != null && existingConfig.getCaCertificateAliases() != null
+                && existingConfig.isAppInstalledCaCert()) {
             oldCaCertificatesToRemove.addAll(
                     Arrays.asList(existingConfig.getCaCertificateAliases()));
         }
@@ -125,8 +136,10 @@
         }
         // If alias changed, remove the old one.
         if (!alias.equals(existingAlias)) {
-            // Remove old private keys.
-            removeEntryFromKeyStore(existingAlias);
+            if (existingConfig != null && existingConfig.isAppInstalledDeviceKeyAndCert()) {
+                // Remove old private keys.
+                removeEntryFromKeyStore(existingAlias);
+            }
         }
         // Remove any old CA certs.
         for (String oldAlias : oldCaCertificatesToRemove) {
@@ -260,6 +273,23 @@
             existingEnterpriseConfig = existingConfig.enterpriseConfig;
             existingKeyId = existingConfig.getKeyIdForCredentials(existingConfig);
         }
+
+        if (SdkLevel.isAtLeastS()) {
+            // If client key is in KeyChain, convert KeyChain alias into a grant string that can be
+            // used by the supplicant like a normal alias.
+            final String keyChainAlias = enterpriseConfig.getClientKeyPairAliasInternal();
+            if (keyChainAlias != null) {
+                final String grantString = mFrameworkFacade.getWifiKeyGrantAsUser(
+                        mContext, UserHandle.getUserHandleForUid(config.creatorUid), keyChainAlias);
+                if (grantString == null) {
+                    // The key is not granted to Wifi uid or the alias is invalid.
+                    Log.e(TAG, "Unable to get key grant");
+                    return false;
+                }
+                enterpriseConfig.setClientCertificateAlias(grantString);
+            }
+        }
+
         if (!needsKeyStore(enterpriseConfig)) {
             return true;
         }
@@ -276,7 +306,7 @@
 
         // For WPA3-Enterprise 192-bit networks, set the SuiteBCipher field based on the
         // CA certificate type. Suite-B requires SHA384, reject other certs.
-        if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SUITE_B_192)) {
+        if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT)) {
             // Read the CA certificates, and initialize
             String[] caAliases = config.enterpriseConfig.getCaCertificateAliases();
 
@@ -331,8 +361,9 @@
             }
 
             if (clientCertType == caCertType) {
-                config.allowedSuiteBCiphers.clear();
-                config.allowedSuiteBCiphers.set(clientCertType);
+                config.enableSuiteBCiphers(
+                        clientCertType == WifiConfiguration.SuiteBCipher.ECDHE_ECDSA,
+                        clientCertType == WifiConfiguration.SuiteBCipher.ECDHE_RSA);
             } else {
                 Log.e(TAG, "Client certificate for Suite-B is incompatible with the CA "
                         + "certificate");
@@ -358,6 +389,7 @@
                 Log.d(TAG, "Checking cert " + p.getName());
             }
         }
+        int bitLength = 0;
 
         // Wi-Fi alliance requires the use of both ECDSA secp384r1 and RSA 3072 certificates
         // in WPA3-Enterprise 192-bit security networks, which are also known as Suite-B-192
@@ -369,18 +401,56 @@
         // we are supporting both types here.
         if (sigAlgOid.equals("1.2.840.113549.1.1.12")) {
             // sha384WithRSAEncryption
-            if (mVerboseLoggingEnabled) {
-                Log.d(TAG, "Found Suite-B RSA certificate");
+            if (x509Certificate.getPublicKey() instanceof RSAPublicKey) {
+                final RSAPublicKey rsaPublicKey = (RSAPublicKey) x509Certificate.getPublicKey();
+                if (rsaPublicKey.getModulus() != null) {
+                    bitLength = rsaPublicKey.getModulus().bitLength();
+                    if (bitLength >= 3072) {
+                        if (mVerboseLoggingEnabled) {
+                            Log.d(TAG, "Found Suite-B RSA certificate");
+                        }
+                        return WifiConfiguration.SuiteBCipher.ECDHE_RSA;
+                    }
+                }
             }
-            return WifiConfiguration.SuiteBCipher.ECDHE_RSA;
         } else if (sigAlgOid.equals("1.2.840.10045.4.3.3")) {
             // ecdsa-with-SHA384
-            if (mVerboseLoggingEnabled) {
-                Log.d(TAG, "Found Suite-B ECDSA certificate");
+            if (x509Certificate.getPublicKey() instanceof ECPublicKey) {
+                final ECPublicKey ecPublicKey = (ECPublicKey) x509Certificate.getPublicKey();
+                final ECParameterSpec ecParameterSpec = ecPublicKey.getParams();
+                if (ecParameterSpec != null && ecParameterSpec.getOrder() != null) {
+                    bitLength = ecParameterSpec.getOrder().bitLength();
+                    if (bitLength >= 384) {
+                        if (mVerboseLoggingEnabled) {
+                            Log.d(TAG, "Found Suite-B ECDSA certificate");
+                        }
+                        return WifiConfiguration.SuiteBCipher.ECDHE_ECDSA;
+                    }
+                }
             }
-            return WifiConfiguration.SuiteBCipher.ECDHE_ECDSA;
         }
-        Log.e(TAG, "Invalid certificate type for Suite-B: " + sigAlgOid);
+        Log.e(TAG, "Invalid certificate type for Suite-B: " + sigAlgOid + " or insufficient"
+                + " bit length: " + bitLength);
         return -1;
     }
+
+    /**
+     * Requests a grant from KeyChain and populates client certificate alias with it.
+     *
+     * @return true if no problems encountered.
+     */
+    public boolean validateKeyChainAlias(String alias, int uid) {
+        if (TextUtils.isEmpty(alias)) {
+            Log.e(TAG, "Alias cannot be empty");
+            return false;
+        }
+
+        if (!SdkLevel.isAtLeastS()) {
+            Log.w(TAG, "Attempt to use a KeyChain key on pre-S device");
+            return false;
+        }
+
+        return mFrameworkFacade.hasWifiKeyGrantAsUser(
+                mContext, UserHandle.getUserHandleForUid(uid), alias);
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiLastResortWatchdog.java b/service/java/com/android/server/wifi/WifiLastResortWatchdog.java
index 9846747..46b3e50 100644
--- a/service/java/com/android/server/wifi/WifiLastResortWatchdog.java
+++ b/service/java/com/android/server/wifi/WifiLastResortWatchdog.java
@@ -16,15 +16,18 @@
 
 package com.android.server.wifi;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.text.TextUtils;
 import android.util.LocalLog;
 import android.util.Log;
+import android.util.LruCache;
 import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -44,6 +47,8 @@
  * Essentially this class automates a user toggling 'Airplane Mode' when WiFi "won't work".
  * IF each available saved network has failed connecting more times than the FAILURE_THRESHOLD
  * THEN Watchdog will restart Supplicant, wifi driver and return ClientModeImpl to InitialState.
+ * TODO(b/159944009): May need to rework this class to handle make before break transition on STA +
+ * STA devices.
  */
 public class WifiLastResortWatchdog {
     private static final String TAG = "WifiLastResortWatchdog";
@@ -95,49 +100,59 @@
             new HashMap<>();
 
     /* List of failure BSSID */
-    private Set<String> mBssidFailureList = new HashSet<>();
+    private final Set<String> mBssidFailureList = new HashSet<>();
 
-    // Tracks: if ClientModeImpl is in ConnectedState
-    private boolean mWifiIsConnected = false;
     // Is Watchdog allowed to trigger now? Set to false after triggering. Set to true after
     // successfully connecting or a new network (SSID) becomes available to connect to.
     private boolean mWatchdogAllowedToTrigger = true;
     private long mTimeLastTrigger = 0;
     private String mSsidLastTrigger = null;
-
-    private WifiInjector mWifiInjector;
-    private WifiMetrics mWifiMetrics;
-    private ClientModeImpl mClientModeImpl;
-    private Looper mClientModeImplLooper;
     private double mBugReportProbability = PROB_TAKE_BUGREPORT_DEFAULT;
-    private Clock mClock;
-    private Context mContext;
-    private DeviceConfigFacade mDeviceConfigFacade;
     // If any connection failure happened after watchdog triggering restart then assume watchdog
     // did not fix the problem
     private boolean mWatchdogFixedWifi = true;
-    private long mLastStartConnectTime = 0;
+    /**
+     * int key: networkId
+     * long value: last time we started connecting to this network, in milliseconds since boot
+     *
+     * Limit size to 10 to prevent it from growing without bounds.
+     */
+    private final LruCache<Integer, Long> mNetworkIdToLastStartConnectTimeMillisSinceBoot =
+            new LruCache<>(10);
+    private Boolean mWatchdogFeatureEnabled = null;
+
+    private final WifiInjector mWifiInjector;
+    private final WifiMetrics mWifiMetrics;
+    private final WifiDiagnostics mWifiDiagnostics;
+    private final Clock mClock;
+    private final Context mContext;
+    private final DeviceConfigFacade mDeviceConfigFacade;
     private final Handler mHandler;
     private final WifiThreadRunner mWifiThreadRunner;
-
-    private Boolean mWatchdogFeatureEnabled = null;
+    private final WifiMonitor mWifiMonitor;
 
     /**
      * Local log used for debugging any WifiLastResortWatchdog issues.
      */
     private final LocalLog mLocalLog = new LocalLog(100);
 
-    WifiLastResortWatchdog(WifiInjector wifiInjector, Context context, Clock clock,
-            WifiMetrics wifiMetrics, ClientModeImpl clientModeImpl, Looper clientModeImplLooper,
-            DeviceConfigFacade deviceConfigFacade, WifiThreadRunner wifiThreadRunner) {
+    WifiLastResortWatchdog(
+            WifiInjector wifiInjector,
+            Context context, Clock clock,
+            WifiMetrics wifiMetrics,
+            WifiDiagnostics wifiDiagnostics,
+            Looper clientModeImplLooper,
+            DeviceConfigFacade deviceConfigFacade,
+            WifiThreadRunner wifiThreadRunner,
+            WifiMonitor wifiMonitor) {
         mWifiInjector = wifiInjector;
         mClock = clock;
         mWifiMetrics = wifiMetrics;
-        mClientModeImpl = clientModeImpl;
-        mClientModeImplLooper = clientModeImplLooper;
+        mWifiDiagnostics = wifiDiagnostics;
         mContext = context;
         mDeviceConfigFacade = deviceConfigFacade;
         mWifiThreadRunner = wifiThreadRunner;
+        mWifiMonitor = wifiMonitor;
         mHandler = new Handler(clientModeImplLooper) {
             public void handleMessage(Message msg) {
                 processMessage(msg);
@@ -145,30 +160,54 @@
         };
     }
 
-    /**
-     * Returns handler for L2 events from supplicant.
-     * @return Handler
-     */
-    public Handler getHandler() {
-        return mHandler;
+    private static final int[] WIFI_MONITOR_EVENTS = {
+            WifiMonitor.NETWORK_CONNECTION_EVENT
+    };
+
+    public void registerForWifiMonitorEvents(String ifaceName) {
+        for (int event : WIFI_MONITOR_EVENTS) {
+            mWifiMonitor.registerHandler(ifaceName, event, mHandler);
+        }
+    }
+
+    public void deregisterForWifiMonitorEvents(String ifaceName) {
+        for (int event : WIFI_MONITOR_EVENTS) {
+            mWifiMonitor.deregisterHandler(ifaceName, event, mHandler);
+        }
+    }
+
+    @NonNull
+    private WifiInfo getPrimaryWifiInfo() {
+        // This is retrieved lazily since there is a non-trivial circular dependency between
+        // ActiveModeWarden & WifiLastResortWatchdog.
+        ActiveModeWarden activeModeWarden = mWifiInjector.getActiveModeWarden();
+        if (activeModeWarden == null) return new WifiInfo();
+        // Cannot be null.
+        ClientModeManager primaryCmm = activeModeWarden.getPrimaryClientModeManager();
+        return primaryCmm.syncRequestConnectionInfo();
     }
 
     /**
      * Refreshes when the last CMD_START_CONNECT is triggered.
      */
-    public void noteStartConnectTime() {
-        mHandler.post(() -> {
-            mLastStartConnectTime = mClock.getElapsedSinceBootMillis();
-        });
+    public void noteStartConnectTime(int networkId) {
+        mHandler.post(() ->
+                mNetworkIdToLastStartConnectTimeMillisSinceBoot.put(
+                        networkId, mClock.getElapsedSinceBootMillis()));
     }
 
     private void processMessage(Message msg) {
         switch (msg.what) {
-            case WifiMonitor.NETWORK_CONNECTION_EVENT:
+            case WifiMonitor.NETWORK_CONNECTION_EVENT: {
+                NetworkConnectionEventInfo connectionInfo = (NetworkConnectionEventInfo) msg.obj;
+                int networkId = connectionInfo.networkId;
                 // Trigger bugreport for successful connections that take abnormally long
+                Long lastStartConnectTimeNullable =
+                        mNetworkIdToLastStartConnectTimeMillisSinceBoot.get(networkId);
                 if (mDeviceConfigFacade.isAbnormalConnectionBugreportEnabled()
-                        && mLastStartConnectTime > 0) {
-                    long durationMs = mClock.getElapsedSinceBootMillis() - mLastStartConnectTime;
+                        && lastStartConnectTimeNullable != null) {
+                    long durationMs =
+                            mClock.getElapsedSinceBootMillis() - lastStartConnectTimeNullable;
                     long abnormalConnectionDurationMs =
                             mDeviceConfigFacade.getAbnormalConnectionDurationMs();
                     if (durationMs > abnormalConnectionDurationMs) {
@@ -178,13 +217,14 @@
                                 + "Actually took " + durationMs + " milliseconds.";
                         logv("Triggering bug report for abnormal connection time.");
                         mWifiThreadRunner.post(() ->
-                                mClientModeImpl.takeBugReport(bugTitle, bugDetail));
+                                mWifiDiagnostics.takeBugReport(bugTitle, bugDetail));
                     }
                 }
                 // Should reset last connection time after each connection regardless if bugreport
                 // is enabled or not.
-                mLastStartConnectTime = 0;
+                mNetworkIdToLastStartConnectTimeMillisSinceBoot.remove(networkId);
                 break;
+            }
             default:
                 return;
         }
@@ -289,9 +329,11 @@
      * exceeded a failure threshold for all available networks, and executes the last resort restart
      * @param bssid of the network that has failed connection, can be "any"
      * @param reason Message id from ClientModeImpl for this failure
+     * @param isConnected whether the ClientModeImpl is currently connected
      * @return true if watchdog triggers, returned for test visibility
      */
-    public boolean noteConnectionFailureAndTriggerIfNeeded(String ssid, String bssid, int reason) {
+    public boolean noteConnectionFailureAndTriggerIfNeeded(String ssid, String bssid, int reason,
+            boolean isConnected) {
         if (mVerboseLoggingEnabled) {
             Log.v(TAG, "noteConnectionFailureAndTriggerIfNeeded: [" + ssid + ", " + bssid + ", "
                     + reason + "]");
@@ -306,7 +348,7 @@
             mWatchdogFixedWifi = false;
         }
         // Have we met conditions to trigger the Watchdog Wifi restart?
-        boolean isRestartNeeded = checkTriggerCondition();
+        boolean isRestartNeeded = checkTriggerCondition(isConnected);
         if (mVerboseLoggingEnabled) {
             Log.v(TAG, "isRestartNeeded = " + isRestartNeeded);
         }
@@ -340,15 +382,15 @@
     public void connectedStateTransition(boolean isEntering) {
         logv("connectedStateTransition: isEntering = " + isEntering);
 
-        mWifiIsConnected = isEntering;
         if (!isEntering) {
             return;
         }
+        WifiInfo wifiInfo = getPrimaryWifiInfo();
         if (!mWatchdogAllowedToTrigger && mWatchdogFixedWifi
                 && getWifiWatchdogFeature()
                 && checkIfAtleastOneNetworkHasEverConnected()
-                && checkIfConnectedBackToSameSsid()
-                && checkIfConnectedBssidHasEverFailed()) {
+                && checkIfConnectedBackToSameSsid(wifiInfo)
+                && checkIfConnectedBssidHasEverFailed(wifiInfo)) {
             takeBugReportWithCurrentProbability("Wifi fixed after restart");
             // WiFi has connected after a Watchdog trigger, without any new networks becoming
             // available, log a Watchdog success in wifi metrics
@@ -366,16 +408,16 @@
      * Helper function to check if device connected to BSSID
      * which is in BSSID failure list after watchdog trigger.
      */
-    private boolean checkIfConnectedBssidHasEverFailed() {
-        return mBssidFailureList.contains(mClientModeImpl.getWifiInfo().getBSSID());
+    private boolean checkIfConnectedBssidHasEverFailed(@NonNull WifiInfo wifiInfo) {
+        return mBssidFailureList.contains(wifiInfo.getBSSID());
     }
 
     /**
      * Helper function to check if device connect back to same
      * SSID after watchdog trigger
      */
-    private boolean checkIfConnectedBackToSameSsid() {
-        if (TextUtils.equals(mSsidLastTrigger, mClientModeImpl.getWifiInfo().getSSID())) {
+    private boolean checkIfConnectedBackToSameSsid(@NonNull WifiInfo wifiInfo) {
+        if (TextUtils.equals(mSsidLastTrigger, wifiInfo.getSSID())) {
             return true;
         }
         localLog("checkIfConnectedBackToSameSsid: different SSID be connected");
@@ -390,9 +432,7 @@
         if (mBugReportProbability <= Math.random()) {
             return;
         }
-        (new Handler(mClientModeImplLooper)).post(() -> {
-            mClientModeImpl.takeBugReport(BUGREPORT_TITLE, bugDetail);
-        });
+        mHandler.post(() -> mWifiDiagnostics.takeBugReport(BUGREPORT_TITLE, bugDetail));
     }
 
     /**
@@ -488,7 +528,7 @@
      * @param bssid BSSID of the access point
      * @return true if only BSSID for its corresponding SSID be observed
      */
-    private boolean isBssidOnlyApOfSsid(String bssid) {
+    public boolean isBssidOnlyApOfSsid(String bssid) {
         AvailableNetworkFailureCount availableNetworkFailureCount =
                 mRecentAvailableNetworks.get(bssid);
         if (availableNetworkFailureCount == null) {
@@ -519,11 +559,11 @@
      * of them, and have previously connected to at-least one of the available networks
      * @return is the trigger condition true
      */
-    private boolean checkTriggerCondition() {
+    private boolean checkTriggerCondition(boolean isConnected) {
         if (mVerboseLoggingEnabled) Log.v(TAG, "checkTriggerCondition.");
         // Don't check Watchdog trigger if wifi is in a connected state
         // (This should not occur, but we want to protect against any race conditions)
-        if (mWifiIsConnected) return false;
+        if (isConnected) return false;
         // Don't check Watchdog trigger if trigger is not enabled
         if (!mWatchdogAllowedToTrigger) return false;
 
@@ -612,6 +652,7 @@
     /**
      * Gets the buffer of recently available networks
      */
+    @VisibleForTesting
     Map<String, AvailableNetworkFailureCount> getRecentAvailableNetworks() {
         return mRecentAvailableNetworks;
     }
@@ -632,11 +673,11 @@
     /**
      * Prints all networks & counts within mRecentAvailableNetworks to string
      */
+    @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
         sb.append("mWatchdogFeatureEnabled: ").append(getWifiWatchdogFeature());
         sb.append("\nmWatchdogAllowedToTrigger: ").append(mWatchdogAllowedToTrigger);
-        sb.append("\nmWifiIsConnected: ").append(mWifiIsConnected);
         sb.append("\nmRecentAvailableNetworks: ").append(mRecentAvailableNetworks.size());
         for (Map.Entry<String, AvailableNetworkFailureCount> entry
                 : mRecentAvailableNetworks.entrySet()) {
@@ -658,7 +699,8 @@
      * @param bssid bssid to check the failures for
      * @return true if sum of failure count is over FAILURE_THRESHOLD
      */
-    public boolean isOverFailureThreshold(String bssid) {
+    @VisibleForTesting
+    boolean isOverFailureThreshold(String bssid) {
         return (getFailureCount(bssid, FAILURE_CODE_ASSOCIATION)
                 + getFailureCount(bssid, FAILURE_CODE_AUTHENTICATION)
                 + getFailureCount(bssid, FAILURE_CODE_DHCP)) >= FAILURE_THRESHOLD;
@@ -669,7 +711,8 @@
      * BSSID and returns the SSID count
      * @param reason failure reason to get count for
      */
-    public int getFailureCount(String bssid, int reason) {
+    @VisibleForTesting
+    int getFailureCount(String bssid, int reason) {
         AvailableNetworkFailureCount availableNetworkFailureCount =
                 mRecentAvailableNetworks.get(bssid);
         if (availableNetworkFailureCount == null) {
@@ -715,16 +758,13 @@
         return mWatchdogFeatureEnabled;
     }
 
-    protected void enableVerboseLogging(int verbose) {
-        if (verbose > 0) {
-            mVerboseLoggingEnabled = true;
-        } else {
-            mVerboseLoggingEnabled = false;
-        }
+    /** Enable/disable verbose logging. */
+    public void enableVerboseLogging(int verbose) {
+        mVerboseLoggingEnabled = verbose > 0;
     }
 
     @VisibleForTesting
-    protected void setBugReportProbability(double newProbability) {
+    void setBugReportProbability(double newProbability) {
         mBugReportProbability = newProbability;
     }
 
diff --git a/service/java/com/android/server/wifi/WifiLinkLayerStats.java b/service/java/com/android/server/wifi/WifiLinkLayerStats.java
index 898b131..ec00894 100644
--- a/service/java/com/android/server/wifi/WifiLinkLayerStats.java
+++ b/service/java/com/android/server/wifi/WifiLinkLayerStats.java
@@ -30,6 +30,7 @@
 public class WifiLinkLayerStats {
     public static final String V1_0 = "V1_0";
     public static final String V1_3 = "V1_3";
+    public static final String V1_5 = "V1_5";
 
     /** The version of hal StaLinkLayerStats **/
     public String version;
@@ -40,7 +41,7 @@
     /** RSSI of management frames */
     public int rssi_mgmt;
 
-    /* Packet counters */
+    /* Packet counters and contention time stats */
 
     /** WME Best Effort Access Category received mpdu */
     public long rxmpdu_be;
@@ -50,6 +51,17 @@
     public long lostmpdu_be;
     /** WME Best Effort Access Category number of transmission retries */
     public long retries_be;
+    /** WME Best Effort Access Category data packet min contention time in microseconds */
+    public long contentionTimeMinBeInUsec;
+    /** WME Best Effort Access Category data packet max contention time in microseconds */
+    public long contentionTimeMaxBeInUsec;
+    /** WME Best Effort Access Category data packet average contention time in microseconds */
+    public long contentionTimeAvgBeInUsec;
+    /**
+     * WME Best Effort Access Category number of data packets used for deriving the min, the max,
+     * and the average contention time
+     */
+    public long contentionNumSamplesBe;
 
     /** WME Background Access Category received mpdu */
     public long rxmpdu_bk;
@@ -59,6 +71,17 @@
     public long lostmpdu_bk;
     /** WME Background Access Category number of transmission retries */
     public long retries_bk;
+    /** WME Background Access Category data packet min contention time in microseconds */
+    public long contentionTimeMinBkInUsec;
+    /** WME Background Access Category data packet max contention time in microseconds */
+    public long contentionTimeMaxBkInUsec;
+    /** WME Background Access Category data packet average contention time in microseconds */
+    public long contentionTimeAvgBkInUsec;
+    /**
+     * WME Background Access Category number of data packets used for deriving the min, the max,
+     * and the average contention time
+     */
+    public long contentionNumSamplesBk;
 
     /** WME Video Access Category received mpdu */
     public long rxmpdu_vi;
@@ -68,6 +91,17 @@
     public long lostmpdu_vi;
     /** WME Video Access Category number of transmission retries */
     public long retries_vi;
+    /** WME Video Access Category data packet min contention time in microseconds */
+    public long contentionTimeMinViInUsec;
+    /** WME Video Access Category data packet max contention time in microseconds */
+    public long contentionTimeMaxViInUsec;
+    /** WME Video Access Category data packet average contention time in microseconds */
+    public long contentionTimeAvgViInUsec;
+    /**
+     * WME Video Access Category number of data packets used for deriving the min, the max, and
+     * the average contention time
+     */
+    public long contentionNumSamplesVi;
 
     /** WME Voice Access Category received mpdu */
     public long rxmpdu_vo;
@@ -77,6 +111,17 @@
     public long lostmpdu_vo;
     /** WME Voice Access Category number of transmission retries */
     public long retries_vo;
+    /** WME Voice Access Category data packet min contention time in microseconds */
+    public long contentionTimeMinVoInUsec;
+    /** WME Voice Access Category data packet max contention time in microseconds */
+    public long contentionTimeMaxVoInUsec;
+    /** WME Voice Access Category data packet average contention time in microseconds */
+    public long contentionTimeAvgVoInUsec;
+    /**
+     * WME Voice Access Category number of data packets used for deriving the min, the max, and
+     * the average contention time
+     */
+    public long contentionNumSamplesVo;
 
     /**
      * Cumulative milliseconds when radio is awake
@@ -87,7 +132,7 @@
      */
     public int tx_time;
     /**
-     * Cumulative milliseconds per level of active transmission
+     * Cumulative milliseconds per radio transmit power level of active transmission
      */
     public int[] tx_time_per_level;
     /**
@@ -101,23 +146,23 @@
     /**
      * Cumulative milliseconds when radio is awake due to nan scan
      */
-    public int on_time_nan_scan = -1;
+    public int on_time_nan_scan;
     /**
      * Cumulative milliseconds when radio is awake due to background scan
      */
-    public int on_time_background_scan = -1;
+    public int on_time_background_scan;
     /**
      * Cumulative milliseconds when radio is awake due to roam scan
      */
-    public int on_time_roam_scan = -1;
+    public int on_time_roam_scan;
     /**
      * Cumulative milliseconds when radio is awake due to pno scan
      */
-    public int on_time_pno_scan = -1;
+    public int on_time_pno_scan;
     /**
      * Cumulative milliseconds when radio is awake due to hotspot 2.0 scan amd GAS exchange
      */
-    public int on_time_hs20_scan = -1;
+    public int on_time_hs20_scan;
     /**
      * channel stats
      */
@@ -141,10 +186,149 @@
     public final SparseArray<ChannelStats> channelStatsMap = new SparseArray<>();
 
     /**
+     * numRadios - Number of radios used for coalescing above radio stats.
+     */
+    public int numRadios;
+
+    /**
      * TimeStamp - absolute milliseconds from boot when these stats were sampled.
      */
     public long timeStampInMs;
 
+    /**
+     * Duty cycle of the iface.
+     * if this iface is being served using time slicing on a radio with one or more ifaces
+     * (i.e MCC), then the duty cycle assigned to this iface in %.
+     * If not using time slicing (i.e SCC or DBS), set to 100.
+     */
+    public short timeSliceDutyCycleInPercent = -1;
+
+    /**
+     * Per rate information and statistics.
+     */
+    public static class RateStat {
+        /**
+         * Preamble information. 0: OFDM, 1:CCK, 2:HT 3:VHT 4:HE 5..7 reserved.
+         */
+        public int preamble;
+        /**
+         * Number of spatial streams. 0:1x1, 1:2x2, 3:3x3, 4:4x4.
+         */
+        public int nss;
+        /**
+         * Bandwidth information. 0:20MHz, 1:40Mhz, 2:80Mhz, 3:160Mhz.
+         */
+        public int bw;
+        /**
+         * MCS index. OFDM/CCK rate code would be as per IEEE std in the units of 0.5Mbps.
+         * HT/VHT/HE: it would be MCS index.
+         */
+        public int rateMcsIdx;
+        /**
+         * Bitrate in units of 100 Kbps.
+         */
+        public int bitRateInKbps;
+        /**
+         * Number of successfully transmitted data packets (ACK received).
+         */
+        public int txMpdu;
+        /**
+         * Number of received data packets.
+         */
+        public int rxMpdu;
+        /**
+         * Number of data packet losses (no ACK).
+         */
+        public int mpduLost;
+        /**
+         * Number of data packet retries.
+         */
+        public int retries;
+    }
+
+    /**
+     * Per peer statistics.
+     */
+    public static class PeerInfo {
+        /**
+         * Station count.
+         */
+        public short staCount;
+        /**
+         * Channel utilization.
+         */
+        public short chanUtil;
+        /**
+         * Per rate statistics.
+         */
+        public RateStat[] rateStats;
+    }
+
+    /**
+     * Peer statistics.
+     */
+    public PeerInfo[] peerInfo;
+
+    /**
+     * Radio stats
+     */
+    public static class RadioStat {
+        /**
+         * Radio identifier
+         */
+        public int radio_id;
+        /**
+         * Cumulative milliseconds when radio is awake from the last radio chip reset
+         */
+        public int on_time;
+        /**
+         * Cumulative milliseconds of active transmission from the last radio chip reset
+         */
+        public int tx_time;
+        /**
+         * Cumulative milliseconds of active receive from the last radio chip reset
+         */
+        public int rx_time;
+        /**
+         * Cumulative milliseconds when radio is awake due to scan from the last radio chip reset
+         */
+        public int on_time_scan;
+        /**
+         * Cumulative milliseconds when radio is awake due to nan scan from the last radio chip
+         * reset
+         */
+        public int on_time_nan_scan;
+        /**
+         * Cumulative milliseconds when radio is awake due to background scan from the last radio
+         * chip reset
+         */
+        public int on_time_background_scan;
+        /**
+         * Cumulative milliseconds when radio is awake due to roam scan from the last radio chip
+         * reset
+         */
+        public int on_time_roam_scan;
+        /**
+         * Cumulative milliseconds when radio is awake due to pno scan from the last radio chip
+         * reset
+         */
+        public int on_time_pno_scan;
+        /**
+         * Cumulative milliseconds when radio is awake due to hotspot 2.0 scan amd GAS exchange
+         * from the last radio chip reset
+         */
+        public int on_time_hs20_scan;
+        /**
+         * Channel stats list
+         */
+        public final SparseArray<ChannelStats> channelStatsMap = new SparseArray<>();
+    }
+
+    /**
+     * Radio stats of all the radios.
+     */
+    public RadioStat[] radioStats;
+
     @Override
     public String toString() {
         StringBuilder sbuf = new StringBuilder();
@@ -156,20 +340,53 @@
         sbuf.append(" BE : ").append(" rx=").append(Long.toString(this.rxmpdu_be))
                 .append(" tx=").append(Long.toString(this.txmpdu_be))
                 .append(" lost=").append(Long.toString(this.lostmpdu_be))
-                .append(" retries=").append(Long.toString(this.retries_be)).append('\n');
+                .append(" retries=").append(Long.toString(this.retries_be)).append('\n')
+                .append(" contention_time_min")
+                .append(Long.toString(this.contentionTimeMinBeInUsec))
+                .append(" contention_time_max")
+                .append(Long.toString(this.contentionTimeMaxBeInUsec)).append('\n')
+                .append(" contention_time_avg")
+                .append(Long.toString(this.contentionTimeAvgBeInUsec))
+                .append(" contention_num_samples")
+                .append(Long.toString(this.contentionNumSamplesBe)).append('\n');
         sbuf.append(" BK : ").append(" rx=").append(Long.toString(this.rxmpdu_bk))
                 .append(" tx=").append(Long.toString(this.txmpdu_bk))
                 .append(" lost=").append(Long.toString(this.lostmpdu_bk))
-                .append(" retries=").append(Long.toString(this.retries_bk)).append('\n');
+                .append(" retries=").append(Long.toString(this.retries_bk)).append('\n')
+                .append(" contention_time_min")
+                .append(Long.toString(this.contentionTimeMinBkInUsec))
+                .append(" contention_time_max")
+                .append(Long.toString(this.contentionTimeMaxBkInUsec)).append('\n')
+                .append(" contention_time_avg")
+                .append(Long.toString(this.contentionTimeAvgBkInUsec))
+                .append(" contention_num_samples")
+                .append(Long.toString(this.contentionNumSamplesBk)).append('\n');
         sbuf.append(" VI : ").append(" rx=").append(Long.toString(this.rxmpdu_vi))
                 .append(" tx=").append(Long.toString(this.txmpdu_vi))
                 .append(" lost=").append(Long.toString(this.lostmpdu_vi))
-                .append(" retries=").append(Long.toString(this.retries_vi)).append('\n');
+                .append(" retries=").append(Long.toString(this.retries_vi)).append('\n')
+                .append(" contention_time_min")
+                .append(Long.toString(this.contentionTimeMinViInUsec))
+                .append(" contention_time_max")
+                .append(Long.toString(this.contentionTimeMaxViInUsec)).append('\n')
+                .append(" contention_time_avg")
+                .append(Long.toString(this.contentionTimeAvgViInUsec))
+                .append(" contention_num_samples")
+                .append(Long.toString(this.contentionNumSamplesVi)).append('\n');
         sbuf.append(" VO : ").append(" rx=").append(Long.toString(this.rxmpdu_vo))
                 .append(" tx=").append(Long.toString(this.txmpdu_vo))
                 .append(" lost=").append(Long.toString(this.lostmpdu_vo))
-                .append(" retries=").append(Long.toString(this.retries_vo)).append('\n');
-        sbuf.append(" on_time : ").append(Integer.toString(this.on_time))
+                .append(" retries=").append(Long.toString(this.retries_vo)).append('\n')
+                .append(" contention_time_min")
+                .append(Long.toString(this.contentionTimeMinVoInUsec))
+                .append(" contention_time_max")
+                .append(Long.toString(this.contentionTimeMaxVoInUsec)).append('\n')
+                .append(" contention_time_avg")
+                .append(Long.toString(this.contentionTimeAvgVoInUsec))
+                .append(" contention_num_samples")
+                .append(Long.toString(this.contentionNumSamplesVo)).append('\n');
+        sbuf.append(" numRadios=" + numRadios)
+                .append(" on_time= ").append(Integer.toString(this.on_time))
                 .append(" tx_time=").append(Integer.toString(this.tx_time))
                 .append(" rx_time=").append(Integer.toString(this.rx_time))
                 .append(" scan_time=").append(Integer.toString(this.on_time_scan)).append('\n')
@@ -192,7 +409,56 @@
                     .append(" radioOnTimeMs=").append(channelStatsEntry.radioOnTimeMs)
                     .append(" ccaBusyTimeMs=").append(channelStatsEntry.ccaBusyTimeMs).append('\n');
         }
+        int numRadios = this.radioStats == null ? 0 : this.radioStats.length;
+        sbuf.append(" Individual radio stats: numRadios=").append(numRadios).append('\n');
+        for (int i = 0; i < numRadios; i++) {
+            RadioStat radio = this.radioStats[i];
+            sbuf.append(" radio_id=" + radio.radio_id)
+                    .append(" on_time=").append(Integer.toString(radio.on_time))
+                    .append(" tx_time=").append(Integer.toString(radio.tx_time))
+                    .append(" rx_time=").append(Integer.toString(radio.rx_time))
+                    .append(" scan_time=").append(Integer.toString(radio.on_time_scan)).append('\n')
+                    .append(" nan_scan_time=")
+                    .append(Integer.toString(radio.on_time_nan_scan)).append('\n')
+                    .append(" g_scan_time=")
+                    .append(Integer.toString(radio.on_time_background_scan)).append('\n')
+                    .append(" roam_scan_time=")
+                    .append(Integer.toString(radio.on_time_roam_scan)).append('\n')
+                    .append(" pno_scan_time=")
+                    .append(Integer.toString(radio.on_time_pno_scan)).append('\n')
+                    .append(" hs2.0_scan_time=")
+                    .append(Integer.toString(radio.on_time_hs20_scan)).append('\n');
+            int numRadioChanStats = radio.channelStatsMap.size();
+            sbuf.append(" Number of channel stats=").append(numRadioChanStats).append('\n');
+            for (int j = 0; j < numRadioChanStats; ++j) {
+                ChannelStats channelStatsEntry = radio.channelStatsMap.valueAt(j);
+                sbuf.append(" Frequency=").append(channelStatsEntry.frequency)
+                        .append(" radioOnTimeMs=").append(channelStatsEntry.radioOnTimeMs)
+                        .append(" ccaBusyTimeMs=").append(channelStatsEntry.ccaBusyTimeMs)
+                        .append('\n');
+            }
+        }
         sbuf.append(" ts=" + timeStampInMs);
+        int numPeers = this.peerInfo == null ? 0 : this.peerInfo.length;
+        sbuf.append(" Number of peers=").append(numPeers).append('\n');
+        for (int i = 0; i < numPeers; i++) {
+            PeerInfo peer = this.peerInfo[i];
+            sbuf.append(" staCount=").append(peer.staCount)
+                    .append(" chanUtil=").append(peer.chanUtil).append('\n');
+            int numRateStats = peer.rateStats == null ? 0 : peer.rateStats.length;
+            for (int j = 0; j < numRateStats; j++) {
+                RateStat rateStat = peer.rateStats[j];
+                sbuf.append(" preamble=").append(rateStat.preamble)
+                        .append(" nss=").append(rateStat.nss)
+                        .append(" bw=").append(rateStat.bw)
+                        .append(" rateMcsIdx=").append(rateStat.rateMcsIdx)
+                        .append(" bitRateInKbps=").append(rateStat.bitRateInKbps).append('\n')
+                        .append(" txMpdu=").append(rateStat.txMpdu)
+                        .append(" rxMpdu=").append(rateStat.rxMpdu)
+                        .append(" mpduLost=").append(rateStat.mpduLost)
+                        .append(" retries=").append(rateStat.retries).append('\n');
+            }
+        }
         return sbuf.toString();
     }
 
diff --git a/service/java/com/android/server/wifi/WifiLockManager.java b/service/java/com/android/server/wifi/WifiLockManager.java
index 7cbeea0..f0d693a 100644
--- a/service/java/com/android/server/wifi/WifiLockManager.java
+++ b/service/java/com/android/server/wifi/WifiLockManager.java
@@ -16,13 +16,21 @@
 
 package com.android.server.wifi;
 
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_PRIMARY;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.net.wifi.WifiManager;
 import android.os.BatteryStatsManager;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.WorkSource;
 import android.os.WorkSource.WorkChain;
@@ -30,6 +38,7 @@
 import android.util.Pair;
 import android.util.SparseArray;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.wifi.proto.WifiStatsLog;
 import com.android.server.wifi.util.WorkSourceUtil;
 
@@ -59,17 +68,18 @@
     private final Context mContext;
     private final BatteryStatsManager mBatteryStats;
     private final FrameworkFacade mFrameworkFacade;
-    private final ClientModeImpl mClientModeImpl;
+    private final ActiveModeWarden mActiveModeWarden;
     private final ActivityManager mActivityManager;
     private final Handler mHandler;
     private final WifiMetrics mWifiMetrics;
-    private final WifiNative mWifiNative;
 
     private final List<WifiLock> mWifiLocks = new ArrayList<>();
     // map UIDs to their corresponding records (for low-latency locks)
     private final SparseArray<UidRec> mLowLatencyUidWatchList = new SparseArray<>();
-    private int mCurrentOpMode;
+    /** the current op mode of the primary ClientModeManager */
+    private int mCurrentOpMode = WifiManager.WIFI_MODE_NO_LOCKS_HELD;
     private boolean mScreenOn = false;
+    /** whether Wifi is connected on the primary ClientModeManager */
     private boolean mWifiConnected = false;
 
     // For shell command support
@@ -84,21 +94,39 @@
     private long mCurrentSessionStartTimeMs;
 
     WifiLockManager(Context context, BatteryStatsManager batteryStats,
-            ClientModeImpl clientModeImpl, FrameworkFacade frameworkFacade, Handler handler,
-            WifiNative wifiNative, Clock clock, WifiMetrics wifiMetrics) {
+            ActiveModeWarden activeModeWarden, FrameworkFacade frameworkFacade,
+            Handler handler, Clock clock, WifiMetrics wifiMetrics) {
         mContext = context;
         mBatteryStats = batteryStats;
-        mClientModeImpl = clientModeImpl;
+        mActiveModeWarden = activeModeWarden;
         mFrameworkFacade = frameworkFacade;
         mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
-        mCurrentOpMode = WifiManager.WIFI_MODE_NO_LOCKS_HELD;
-        mWifiNative = wifiNative;
         mHandler = handler;
         mClock = clock;
         mWifiMetrics = wifiMetrics;
 
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_SCREEN_ON);
+        filter.addAction(Intent.ACTION_SCREEN_OFF);
+        context.registerReceiver(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        String action = intent.getAction();
+                        if (action.equals(Intent.ACTION_SCREEN_ON)) {
+                            handleScreenStateChanged(true);
+                        } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
+                            handleScreenStateChanged(false);
+                        }
+                    }
+                }, filter, null, mHandler);
+        handleScreenStateChanged(context.getSystemService(PowerManager.class).isInteractive());
+
         // Register for UID fg/bg transitions
         registerUidImportanceTransitions();
+
+        mActiveModeWarden.registerPrimaryClientModeManagerChangedCallback(
+                new PrimaryClientModeManagerChangedCallback());
     }
 
     // Check for conditions to activate high-perf lock
@@ -210,7 +238,8 @@
      *
      * @return int representing the currently held (highest power consumption) lock.
      */
-    public synchronized int getStrongestLockMode() {
+    @VisibleForTesting
+    synchronized int getStrongestLockMode() {
         // If Wifi Client is not connected, then all locks are not effective
         if (!mWifiConnected) {
             return WifiManager.WIFI_MODE_NO_LOCKS_HELD;
@@ -334,7 +363,7 @@
     /**
      * Handler for screen state (on/off) changes
      */
-    public void handleScreenStateChanged(boolean screenOn) {
+    private void handleScreenStateChanged(boolean screenOn) {
         if (mVerboseLoggingEnabled) {
             Log.d(TAG, "handleScreenStateChanged: screenOn = " + screenOn);
         }
@@ -352,7 +381,13 @@
     /**
      * Handler for Wifi Client mode state changes
      */
-    public void updateWifiClientConnected(boolean isConnected) {
+    public void updateWifiClientConnected(
+            ClientModeManager clientModeManager, boolean isConnected) {
+        // ignore if not primary
+        if (clientModeManager.getRole() != ROLE_CLIENT_PRIMARY) {
+            return;
+        }
+
         if (mWifiConnected == isConnected) {
             // No need to take action
             return;
@@ -374,7 +409,7 @@
         updateOpMode();
     }
 
-    private void setBlameHiPerfLocks(boolean shouldBlame) {
+    private synchronized void setBlameHiPerfLocks(boolean shouldBlame) {
         for (WifiLock lock : mWifiLocks) {
             if (lock.mMode == WifiManager.WIFI_MODE_FULL_HIGH_PERF) {
                 setBlameHiPerfWs(lock.getWorkSource(), shouldBlame);
@@ -563,22 +598,15 @@
         return true;
     }
 
-    private synchronized boolean updateOpMode() {
-        final int newLockMode = getStrongestLockMode();
-
-        if (newLockMode == mCurrentOpMode) {
-            // No action is needed
-            return true;
-        }
-
-        if (mVerboseLoggingEnabled) {
-            Log.d(TAG, "Current opMode: " + mCurrentOpMode + " New LockMode: " + newLockMode);
-        }
-
-        // Otherwise, we need to change current mode, first reset it to normal
+    /**
+     * Reset the given ClientModeManager's power save/low latency mode to the default.
+     * The method calls needed to reset is the reverse of the method calls used to set.
+     * @return true if the operation succeeded, false otherwise
+     */
+    private boolean resetCurrentMode(@NonNull ClientModeManager clientModeManager) {
         switch (mCurrentOpMode) {
             case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
-                if (!mClientModeImpl.setPowerSave(true)) {
+                if (!clientModeManager.setPowerSave(true)) {
                     Log.e(TAG, "Failed to reset the OpMode from hi-perf to Normal");
                     return false;
                 }
@@ -587,7 +615,7 @@
                 break;
 
             case WifiManager.WIFI_MODE_FULL_LOW_LATENCY:
-                if (!setLowLatencyMode(false)) {
+                if (!setLowLatencyMode(clientModeManager, false)) {
                     Log.e(TAG, "Failed to reset the OpMode from low-latency to Normal");
                     return false;
                 }
@@ -601,13 +629,19 @@
                 break;
         }
 
-        // Set the current mode, before we attempt to set the new mode
+        // reset the current mode
         mCurrentOpMode = WifiManager.WIFI_MODE_NO_LOCKS_HELD;
+        return true;
+    }
 
-        // Now switch to the new opMode
+    /**
+     * Set the new lock mode on the given ClientModeManager
+     * @return true if the operation succeeded, false otherwise
+     */
+    private boolean setNewMode(@NonNull ClientModeManager clientModeManager, int newLockMode) {
         switch (newLockMode) {
             case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
-                if (!mClientModeImpl.setPowerSave(false)) {
+                if (!clientModeManager.setPowerSave(false)) {
                     Log.e(TAG, "Failed to set the OpMode to hi-perf");
                     return false;
                 }
@@ -615,7 +649,7 @@
                 break;
 
             case WifiManager.WIFI_MODE_FULL_LOW_LATENCY:
-                if (!setLowLatencyMode(true)) {
+                if (!setLowLatencyMode(clientModeManager, true)) {
                     Log.e(TAG, "Failed to set the OpMode to low-latency");
                     return false;
                 }
@@ -627,7 +661,7 @@
                 break;
 
             default:
-                // Invalid mode, don't change currentOpMode , and exit with error
+                // Invalid mode, don't change currentOpMode, and exit with error
                 Log.e(TAG, "Invalid new opMode: " + newLockMode);
                 return false;
         }
@@ -637,27 +671,72 @@
         return true;
     }
 
-    private int getLowLatencyModeSupport() {
-        if (mLatencyModeSupport == LOW_LATENCY_SUPPORT_UNDEFINED) {
-            String ifaceName = mWifiNative.getClientInterfaceName();
-            if (ifaceName == null) {
-                return LOW_LATENCY_SUPPORT_UNDEFINED;
-            }
+    private synchronized boolean updateOpMode() {
+        final int newLockMode = getStrongestLockMode();
 
-            long supportedFeatures = mWifiNative.getSupportedFeatureSet(ifaceName);
-            if (supportedFeatures != 0) {
-                if ((supportedFeatures & WifiManager.WIFI_FEATURE_LOW_LATENCY) != 0) {
-                    mLatencyModeSupport = LOW_LATENCY_SUPPORTED;
-                } else {
-                    mLatencyModeSupport = LOW_LATENCY_NOT_SUPPORTED;
-                }
-            }
+        if (newLockMode == mCurrentOpMode) {
+            // No action is needed
+            return true;
         }
 
+        if (mVerboseLoggingEnabled) {
+            Log.d(TAG, "Current opMode: " + mCurrentOpMode
+                    + " New LockMode: " + newLockMode);
+        }
+
+        ClientModeManager primaryManager = mActiveModeWarden.getPrimaryClientModeManager();
+
+        // Otherwise, we need to change current mode, first reset it to normal
+        if (!resetCurrentMode(primaryManager)) {
+            return false;
+        }
+
+        // Now switch to the new opMode
+        return setNewMode(primaryManager, newLockMode);
+    }
+
+    private class PrimaryClientModeManagerChangedCallback
+            implements ActiveModeWarden.PrimaryClientModeManagerChangedCallback {
+
+        @Override
+        public void onChange(
+                @Nullable ConcreteClientModeManager prevPrimaryClientModeManager,
+                @Nullable ConcreteClientModeManager newPrimaryClientModeManager) {
+            // reset wifi lock on previous primary
+            if (prevPrimaryClientModeManager != null) {
+                resetCurrentMode(prevPrimaryClientModeManager);
+            }
+            // set wifi lock on new primary
+            if (newPrimaryClientModeManager != null) {
+                mWifiConnected = newPrimaryClientModeManager.isConnected();
+                setNewMode(newPrimaryClientModeManager, getStrongestLockMode());
+            } else {
+                mWifiConnected = false;
+            }
+        }
+    }
+
+    /** Returns the cached low latency mode support value, or tries to fetch it if not yet known. */
+    private int getLowLatencyModeSupport() {
+        if (mLatencyModeSupport != LOW_LATENCY_SUPPORT_UNDEFINED) {
+            return mLatencyModeSupport;
+        }
+
+        long supportedFeatures =
+                mActiveModeWarden.getPrimaryClientModeManager().getSupportedFeatures();
+        if (supportedFeatures == 0L) {
+            return LOW_LATENCY_SUPPORT_UNDEFINED;
+        }
+
+        if ((supportedFeatures & WifiManager.WIFI_FEATURE_LOW_LATENCY) != 0) {
+            mLatencyModeSupport = LOW_LATENCY_SUPPORTED;
+        } else {
+            mLatencyModeSupport = LOW_LATENCY_NOT_SUPPORTED;
+        }
         return mLatencyModeSupport;
     }
 
-    private boolean setLowLatencyMode(boolean enabled) {
+    private boolean setLowLatencyMode(ClientModeManager clientModeManager, boolean enabled) {
         int lowLatencySupport = getLowLatencyModeSupport();
 
         if (lowLatencySupport == LOW_LATENCY_SUPPORT_UNDEFINED) {
@@ -666,20 +745,20 @@
         }
 
         if (lowLatencySupport == LOW_LATENCY_SUPPORTED) {
-            if (!mClientModeImpl.setLowLatencyMode(enabled)) {
+            if (!clientModeManager.setLowLatencyMode(enabled)) {
                 Log.e(TAG, "Failed to set low latency mode");
                 return false;
             }
 
-            if (!mClientModeImpl.setPowerSave(!enabled)) {
+            if (!clientModeManager.setPowerSave(!enabled)) {
                 Log.e(TAG, "Failed to set power save mode");
                 // Revert the low latency mode
-                mClientModeImpl.setLowLatencyMode(!enabled);
+                clientModeManager.setLowLatencyMode(!enabled);
                 return false;
             }
         } else if (lowLatencySupport == LOW_LATENCY_NOT_SUPPORTED) {
             // Only set power save mode
-            if (!mClientModeImpl.setPowerSave(!enabled)) {
+            if (!clientModeManager.setPowerSave(!enabled)) {
                 Log.e(TAG, "Failed to set power save mode");
                 return false;
             }
@@ -762,7 +841,7 @@
         }
     }
 
-    protected void dump(PrintWriter pw) {
+    protected synchronized void dump(PrintWriter pw) {
         pw.println("Locks acquired: "
                 + mFullHighPerfLocksAcquired + " full high perf, "
                 + mFullLowLatencyLocksAcquired + " full low latency");
diff --git a/service/java/com/android/server/wifi/WifiMetrics.java b/service/java/com/android/server/wifi/WifiMetrics.java
index 2141389..3445b76 100644
--- a/service/java/com/android/server/wifi/WifiMetrics.java
+++ b/service/java/com/android/server/wifi/WifiMetrics.java
@@ -20,13 +20,23 @@
 
 import static java.lang.StrictMath.toIntExact;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.hardware.wifi.supplicant.V1_0.ISupplicantStaIfaceCallback;
 import android.net.wifi.EAPConstants;
 import android.net.wifi.IOnWifiUsabilityStatsListener;
 import android.net.wifi.ScanResult;
 import android.net.wifi.SoftApCapability;
 import android.net.wifi.SoftApConfiguration;
+import android.net.wifi.SoftApInfo;
 import android.net.wifi.SupplicantState;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
@@ -37,20 +47,26 @@
 import android.net.wifi.WifiUsabilityStatsEntry.ProbeStatus;
 import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.net.wifi.hotspot2.ProvisioningCallback;
+import android.net.wifi.hotspot2.ProvisioningCallback.OsuFailure;
 import android.net.wifi.nl80211.WifiNl80211Manager;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.PowerManager;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.SystemProperties;
+import android.os.WorkSource;
+import android.provider.Settings;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Base64;
 import android.util.Log;
 import android.util.Pair;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -65,8 +81,11 @@
 import com.android.server.wifi.proto.WifiStatsLog;
 import com.android.server.wifi.proto.nano.WifiMetricsProto;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.ConnectToNetworkNotificationAndActionCount;
+import com.android.server.wifi.proto.nano.WifiMetricsProto.ContentionTimeStats;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.DeviceMobilityStatePnoScanStats;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.ExperimentValues;
+import com.android.server.wifi.proto.nano.WifiMetricsProto.FirstConnectAfterBootStats;
+import com.android.server.wifi.proto.nano.WifiMetricsProto.FirstConnectAfterBootStats.Attempt;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.HealthMonitorMetrics;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.InitPartialScanStats;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.LinkProbeStats;
@@ -80,6 +99,8 @@
 import com.android.server.wifi.proto.nano.WifiMetricsProto.PasspointProvisionStats;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.PasspointProvisionStats.ProvisionFailureCount;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.PnoScanMetrics;
+import com.android.server.wifi.proto.nano.WifiMetricsProto.RadioStats;
+import com.android.server.wifi.proto.nano.WifiMetricsProto.RateStats;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.SoftApConnectedClientsEvent;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.StaEvent;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.StaEvent.ConfigInfo;
@@ -94,12 +115,12 @@
 import com.android.server.wifi.proto.nano.WifiMetricsProto.WifiNetworkSuggestionApiLog;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.WifiNetworkSuggestionApiLog.SuggestionAppCount;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.WifiStatus;
+import com.android.server.wifi.proto.nano.WifiMetricsProto.WifiToWifiSwitchStats;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.WifiToggleStats;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.WifiUsabilityStats;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.WifiUsabilityStatsEntry;
 import com.android.server.wifi.rtt.RttMetrics;
 import com.android.server.wifi.scanner.KnownBandsChannelHelper;
-import com.android.server.wifi.util.ExternalCallbackTracker;
 import com.android.server.wifi.util.InformationElementUtil;
 import com.android.server.wifi.util.IntCounter;
 import com.android.server.wifi.util.IntHistogram;
@@ -114,10 +135,13 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.time.Duration;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.BitSet;
 import java.util.Calendar;
+import java.util.Deque;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedList;
@@ -137,6 +161,7 @@
 public class WifiMetrics {
     private static final String TAG = "WifiMetrics";
     private static final boolean DBG = false;
+
     /**
      * Clamp the RSSI poll counts to values between [MIN,MAX]_RSSI_POLL
      */
@@ -183,6 +208,8 @@
     public static final int MAX_WIFI_USABILITY_STATS_PER_TYPE_TO_UPLOAD = 2;
     public static final int NUM_WIFI_USABILITY_STATS_ENTRIES_PER_WIFI_GOOD = 100;
     public static final int MIN_WIFI_GOOD_USABILITY_STATS_PERIOD_MS = 1000 * 3600; // 1 hour
+    public static final int PASSPOINT_DEAUTH_IMMINENT_SCOPE_ESS = 0;
+    public static final int PASSPOINT_DEAUTH_IMMINENT_SCOPE_BSS = 1;
     // Histogram for WifiConfigStore IO duration times. Indicates the following 5 buckets (in ms):
     //   < 50
     //   [50, 100)
@@ -197,6 +224,12 @@
     // Maximum time that a score breaching low event stays valid.
     public static final int VALIDITY_PERIOD_OF_SCORE_BREACH_LOW_MS = 90 * 1000; // 1.5 minutes
 
+    private static final int WIFI_RECONNECT_DURATION_SHORT_MILLIS = 10 * 1000;
+    private static final int WIFI_RECONNECT_DURATION_MEDIUM_MILLIS = 60 * 1000;
+    // Number of WME Access Categories
+    private static final int NUM_WME_ACCESS_CATEGORIES = 4;
+    private static final int MBB_LINGERING_DURATION_MAX_SECONDS = 30;
+
     private Clock mClock;
     private boolean mScreenOn;
     private int mWifiState;
@@ -204,11 +237,13 @@
     private RttMetrics mRttMetrics;
     private final PnoScanMetrics mPnoScanMetrics = new PnoScanMetrics();
     private final WifiLinkLayerUsageStats mWifiLinkLayerUsageStats = new WifiLinkLayerUsageStats();
+    /** Mapping of radio id values to RadioStats objects. */
+    private final SparseArray<RadioStats> mRadioStats = new SparseArray<>();
     private final ExperimentValues mExperimentValues = new ExperimentValues();
     private Handler mHandler;
     private ScoringParams mScoringParams;
     private WifiConfigManager mWifiConfigManager;
-    private BssidBlocklistMonitor mBssidBlocklistMonitor;
+    private WifiBlocklistMonitor mWifiBlocklistMonitor;
     private WifiNetworkSelector mWifiNetworkSelector;
     private PasspointManager mPasspointManager;
     private Context mContext;
@@ -217,6 +252,8 @@
     private WifiLinkLayerStats mLastLinkLayerStats;
     private WifiHealthMonitor mWifiHealthMonitor;
     private WifiScoreCard mWifiScoreCard;
+    private SessionData mPreviousSession;
+    private SessionData mCurrentSession;
     private String mLastBssid;
     private int mLastFrequency = -1;
     private int mSeqNumInsideFramework = 0;
@@ -242,6 +279,12 @@
     private int mLastPollFreq = -1;
     private int mLastScore = -1;
     private boolean mAdaptiveConnectivityEnabled = true;
+    private ScanMetrics mScanMetrics;
+    private WifiChannelUtilization mWifiChannelUtilization;
+    private WifiSettingsStore mWifiSettingsStore;
+    private IntCounter mPasspointDeauthImminentScope = new IntCounter();
+    private IntCounter mRecentFailureAssociationStatus = new IntCounter();
+    private boolean mFirstConnectionAfterBoot = true;
 
     /**
      * Metrics are stored within an instance of the WifiLog proto during runtime,
@@ -253,11 +296,11 @@
     /**
      * Session information that gets logged for every Wifi connection attempt.
      */
-    private final List<ConnectionEvent> mConnectionEventList = new ArrayList<>();
+    private final Deque<ConnectionEvent> mConnectionEventList = new ArrayDeque<>();
     /**
-     * The latest started (but un-ended) connection attempt
+     * The latest started (but un-ended) connection attempt per interface.
      */
-    private ConnectionEvent mCurrentConnectionEvent;
+    private final Map<String, ConnectionEvent> mCurrentConnectionEventPerIface = new ArrayMap<>();
     /**
      * Count of number of times each scan return code, indexed by WifiLog.ScanReturnCode
      */
@@ -290,6 +333,8 @@
     private final IntCounter mRxLinkSpeedCount6gMid = new IntCounter();
     private final IntCounter mRxLinkSpeedCount6gHigh = new IntCounter();
 
+    private final IntCounter mMakeBeforeBreakLingeringDurationSeconds = new IntCounter();
+
     /** RSSI of the scan result for the last connection event*/
     private int mScanResultRssi = 0;
     /** Boot-relative timestamp when the last candidate scanresult was received, used to calculate
@@ -385,7 +430,7 @@
     private final LinkedList<WifiUsabilityStats> mWifiUsabilityStatsListGood = new LinkedList<>();
     private int mWifiUsabilityStatsCounter = 0;
     private final Random mRand = new Random();
-    private final ExternalCallbackTracker<IOnWifiUsabilityStatsListener> mOnWifiUsabilityListeners;
+    private final RemoteCallbackList<IOnWifiUsabilityStatsListener> mOnWifiUsabilityListeners;
 
     private final SparseArray<DeviceMobilityStatePnoScanStats> mMobilityStatePnoStatsMap =
             new SparseArray<>();
@@ -411,6 +456,10 @@
     /** DPP */
     private final DppMetrics mDppMetrics;
 
+    private final WifiMonitor mWifiMonitor;
+    private ActiveModeWarden mActiveModeWarden;
+    private final Map<String, ActiveModeManager.ClientRole> mIfaceToRoleMap = new ArrayMap<>();
+
     /** WifiConfigStore read duration histogram. */
     private SparseIntArray mWifiConfigStoreReadDurationHistogram = new SparseIntArray();
 
@@ -425,6 +474,20 @@
     private final IntHistogram mWifiNetworkRequestApiMatchSizeHistogram =
             new IntHistogram(NETWORK_REQUEST_API_MATCH_SIZE_HISTOGRAM_BUCKETS);
 
+    private static final int[] NETWORK_REQUEST_API_DURATION_SEC_BUCKETS =
+            {0, toIntExact(Duration.ofMinutes(3).getSeconds()),
+                    toIntExact(Duration.ofMinutes(10).getSeconds()),
+                    toIntExact(Duration.ofMinutes(30).getSeconds()),
+                    toIntExact(Duration.ofHours(1).getSeconds()),
+                    toIntExact(Duration.ofHours(6).getSeconds())};
+    private final IntHistogram mWifiNetworkRequestApiConnectionDurationSecOnPrimaryIfaceHistogram =
+            new IntHistogram(NETWORK_REQUEST_API_DURATION_SEC_BUCKETS);
+    private final IntHistogram
+            mWifiNetworkRequestApiConnectionDurationSecOnSecondaryIfaceHistogram =
+            new IntHistogram(NETWORK_REQUEST_API_DURATION_SEC_BUCKETS);
+    private final IntHistogram mWifiNetworkRequestApiConcurrentConnectionDurationSecHistogram =
+            new IntHistogram(NETWORK_REQUEST_API_DURATION_SEC_BUCKETS);
+
     private final WifiNetworkSuggestionApiLog mWifiNetworkSuggestionApiLog =
             new WifiNetworkSuggestionApiLog();
     private static final int[] NETWORK_SUGGESTION_API_LIST_SIZE_HISTOGRAM_BUCKETS =
@@ -436,6 +499,9 @@
             new ArrayList<>();
     private final List<UserReaction> mUserApprovalCarrierUiReactionList =
             new ArrayList<>();
+    private final SparseBooleanArray mWifiNetworkSuggestionPriorityGroups =
+            new SparseBooleanArray();
+    private final Set<String> mWifiNetworkSuggestionCoexistSavedNetworks = new ArraySet<>();
 
     private final WifiLockStats mWifiLockStats = new WifiLockStats();
     private static final int[] WIFI_LOCK_SESSION_DURATION_HISTOGRAM_BUCKETS =
@@ -517,6 +583,13 @@
     private final CarrierWifiMetrics mCarrierWifiMetrics =
             new CarrierWifiMetrics();
 
+    @Nullable
+    private FirstConnectAfterBootStats mFirstConnectAfterBootStats =
+            new FirstConnectAfterBootStats();
+    private boolean mIsFirstConnectionAttemptComplete = false;
+
+    private final WifiToWifiSwitchStats mWifiToWifiSwitchStats = new WifiToWifiSwitchStats();
+
     @VisibleForTesting
     static class NetworkSelectionExperimentResults {
         public static final int MAX_CHOICES = 10;
@@ -535,11 +608,24 @@
         }
     }
 
-    class RouterFingerPrint {
-        private WifiMetricsProto.RouterFingerPrint mRouterFingerPrintProto;
-        RouterFingerPrint() {
-            mRouterFingerPrintProto = new WifiMetricsProto.RouterFingerPrint();
+    private static class SessionData {
+        private String mSsid;
+        private long mSessionStartTimeMillis;
+        private long mSessionEndTimeMillis;
+        private int mBand;
+        private int mAuthType;
+
+        SessionData(String ssid, long sessionStartTimeMillis, int band, int authType) {
+            mSsid = ssid;
+            mSessionStartTimeMillis = sessionStartTimeMillis;
+            mBand = band;
+            mAuthType = authType;
         }
+    }
+
+    class RouterFingerPrint {
+        private final WifiMetricsProto.RouterFingerPrint mRouterFingerPrintProto =
+                new WifiMetricsProto.RouterFingerPrint();
 
         public String toString() {
             StringBuilder sb = new StringBuilder();
@@ -562,55 +648,6 @@
             }
             return sb.toString();
         }
-        public void updateFromWifiConfiguration(WifiConfiguration config) {
-            synchronized (mLock) {
-                if (config != null) {
-                    // Is this a hidden network
-                    mRouterFingerPrintProto.hidden = config.hiddenSSID;
-                    // Config may not have a valid dtimInterval set yet, in which case dtim will be zero
-                    // (These are only populated from beacon frame scan results, which are returned as
-                    // scan results from the chip far less frequently than Probe-responses)
-                    if (config.dtimInterval > 0) {
-                        mRouterFingerPrintProto.dtim = config.dtimInterval;
-                    }
-                    mCurrentConnectionEvent.mConfigSsid = config.SSID;
-                    // Get AuthType information from config (We do this again from ScanResult after
-                    // associating with BSSID)
-                    if (config.allowedKeyManagement != null
-                            && config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)) {
-                        mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto
-                                .authentication = WifiMetricsProto.RouterFingerPrint.AUTH_OPEN;
-                    } else if (config.isEnterprise()) {
-                        mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto
-                                .authentication = WifiMetricsProto.RouterFingerPrint.AUTH_ENTERPRISE;
-                    } else {
-                        mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto
-                                .authentication = WifiMetricsProto.RouterFingerPrint.AUTH_PERSONAL;
-                    }
-                    mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto
-                            .passpoint = config.isPasspoint();
-                    // If there's a ScanResult candidate associated with this config already, get it and
-                    // log (more accurate) metrics from it
-                    ScanResult candidate = config.getNetworkSelectionStatus().getCandidate();
-                    if (candidate != null) {
-                        updateMetricsFromScanResult(candidate);
-                    }
-                    if (mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto
-                            .authentication == WifiMetricsProto.RouterFingerPrint.AUTH_ENTERPRISE
-                            && config.enterpriseConfig != null) {
-                        int eapMethod = config.enterpriseConfig.getEapMethod();
-                        mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto
-                                .eapMethod = getEapMethodProto(eapMethod);
-                        int phase2Method = config.enterpriseConfig.getPhase2Method();
-                        mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto
-                                .authPhase2Method = getAuthPhase2MethodProto(phase2Method);
-                        int ocspType = config.enterpriseConfig.getOcsp();
-                        mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto
-                                .ocspType = getOcspTypeProto(ocspType);
-                    }
-                }
-            }
-        }
 
         public void setPmkCache(boolean isEnabled) {
             synchronized (mLock) {
@@ -691,6 +728,8 @@
         public IntCounter networkSelectionFilteredBssidCount = new IntCounter();
         public int numHighMovementConnectionSkipped = 0;
         public int numHighMovementConnectionStarted = 0;
+        private final IntCounter mBlockedBssidPerReasonCount = new IntCounter();
+        private final IntCounter mBlockedConfigurationPerReasonCount = new IntCounter();
 
         public WifiMetricsProto.BssidBlocklistStats toProto() {
             WifiMetricsProto.BssidBlocklistStats proto = new WifiMetricsProto.BssidBlocklistStats();
@@ -699,18 +738,35 @@
                     R.bool.config_wifiHighMovementNetworkSelectionOptimizationEnabled);
             proto.numHighMovementConnectionSkipped = numHighMovementConnectionSkipped;
             proto.numHighMovementConnectionStarted = numHighMovementConnectionStarted;
+            proto.bssidBlocklistPerReasonCount = mBlockedBssidPerReasonCount.toProto();
+            proto.wifiConfigBlocklistPerReasonCount = mBlockedConfigurationPerReasonCount.toProto();
             return proto;
         }
 
+        public void incrementBssidBlocklistCount(int blockReason) {
+            mBlockedBssidPerReasonCount.increment(blockReason);
+        }
+
+        public void incrementWificonfigurationBlocklistCount(int blockReason) {
+            mBlockedConfigurationPerReasonCount.increment(blockReason);
+        }
+
         @Override
         public String toString() {
             StringBuilder sb = new StringBuilder();
             sb.append("networkSelectionFilteredBssidCount=" + networkSelectionFilteredBssidCount);
+            sb.append("\nmBlockedBssidPerReasonCount=" + mBlockedBssidPerReasonCount);
+            sb.append("\nmBlockedConfigurationPerReasonCount="
+                    + mBlockedConfigurationPerReasonCount);
+
             sb.append(", highMovementMultipleScansFeatureEnabled="
                     + mContext.getResources().getBoolean(
                             R.bool.config_wifiHighMovementNetworkSelectionOptimizationEnabled));
             sb.append(", numHighMovementConnectionSkipped=" + numHighMovementConnectionSkipped);
             sb.append(", numHighMovementConnectionStarted=" + numHighMovementConnectionStarted);
+            sb.append(", mBlockedBssidPerReasonCount=" + mBlockedBssidPerReasonCount);
+            sb.append(", mBlockedConfigurationPerReasonCount="
+                    + mBlockedConfigurationPerReasonCount);
             return sb.toString();
         }
     }
@@ -719,12 +775,15 @@
         private int mConnectionDurationCellularDataOffMs;
         private int mConnectionDurationSufficientThroughputMs;
         private int mConnectionDurationInSufficientThroughputMs;
+        private int mConnectionDurationInSufficientThroughputDefaultWifiMs;
 
         public WifiMetricsProto.ConnectionDurationStats toProto() {
             WifiMetricsProto.ConnectionDurationStats proto =
                     new WifiMetricsProto.ConnectionDurationStats();
             proto.totalTimeSufficientThroughputMs = mConnectionDurationSufficientThroughputMs;
             proto.totalTimeInsufficientThroughputMs = mConnectionDurationInSufficientThroughputMs;
+            proto.totalTimeInsufficientThroughputDefaultWifiMs =
+                    mConnectionDurationInSufficientThroughputDefaultWifiMs;
             proto.totalTimeCellularDataOffMs = mConnectionDurationCellularDataOffMs;
             return proto;
         }
@@ -732,9 +791,11 @@
             mConnectionDurationCellularDataOffMs = 0;
             mConnectionDurationSufficientThroughputMs = 0;
             mConnectionDurationInSufficientThroughputMs = 0;
+            mConnectionDurationInSufficientThroughputDefaultWifiMs = 0;
         }
         public void incrementDurationCount(int timeDeltaLastTwoPollsMs,
-                boolean isThroughputSufficient, boolean isCellularDataAvailable) {
+                boolean isThroughputSufficient, boolean isCellularDataAvailable,
+                boolean isDefaultOnWifi) {
             if (!isCellularDataAvailable) {
                 mConnectionDurationCellularDataOffMs += timeDeltaLastTwoPollsMs;
             } else {
@@ -742,6 +803,10 @@
                     mConnectionDurationSufficientThroughputMs += timeDeltaLastTwoPollsMs;
                 } else {
                     mConnectionDurationInSufficientThroughputMs += timeDeltaLastTwoPollsMs;
+                    if (isDefaultOnWifi) {
+                        mConnectionDurationInSufficientThroughputDefaultWifiMs +=
+                                timeDeltaLastTwoPollsMs;
+                    }
                 }
             }
         }
@@ -752,6 +817,8 @@
                     .append(mConnectionDurationSufficientThroughputMs)
                     .append(", connectionDurationInSufficientThroughputMs=")
                     .append(mConnectionDurationInSufficientThroughputMs)
+                    .append(", connectionDurationInSufficientThroughputDefaultWifiMs=")
+                    .append(mConnectionDurationInSufficientThroughputDefaultWifiMs)
                     .append(", connectionDurationCellularDataOffMs=")
                     .append(mConnectionDurationCellularDataOffMs);
             return sb.toString();
@@ -863,7 +930,7 @@
                     networkInfo.isPasspoint = config.isPasspoint();
                     mUserActionEvent.targetNetworkInfo = networkInfo;
                     mUserActionEvent.networkDisableReason = convertToNetworkDisableReason(
-                            config, mBssidBlocklistMonitor.getFailureReasonsForSsid(config.SSID));
+                            config, mWifiBlocklistMonitor.getFailureReasonsForSsid(config.SSID));
                 }
             }
         }
@@ -914,6 +981,9 @@
                 case UserActionEvent.EVENT_ADD_OR_UPDATE_NETWORK:
                     eventType = "EVENT_ADD_OR_UPDATE_NETWORK";
                     break;
+                case UserActionEvent.EVENT_RESTART_WIFI_SUB_SYSTEM:
+                    eventType = "EVENT_RESTART_WIFI_SUB_SYSTEM";
+                    break;
             }
             sb.append(" eventType=").append(eventType);
             sb.append(" startTimeMillis=").append(mUserActionEvent.startTimeMillis);
@@ -955,7 +1025,7 @@
      * Log event, tracking the start time, end time and result of a wireless connection attempt.
      */
     class ConnectionEvent {
-        WifiMetricsProto.ConnectionEvent mConnectionEvent;
+        final WifiMetricsProto.ConnectionEvent mConnectionEvent;
         //<TODO> Move these constants into a wifi.proto Enum, and create a new Failure Type field
         //covering more than just l2 failures. see b/27652362
         /**
@@ -987,19 +1057,20 @@
         public static final int FAILURE_DHCP = 10;
         // ASSOCIATION_TIMED_OUT
         public static final int FAILURE_ASSOCIATION_TIMED_OUT = 11;
+        // NETWORK_NOT_FOUND
+        public static final int FAILURE_NETWORK_NOT_FOUND = 12;
 
         RouterFingerPrint mRouterFingerPrint;
-        private long mRealStartTime;
-        private long mRealEndTime;
         private String mConfigSsid;
         private String mConfigBssid;
         private int mWifiState;
         private boolean mScreenOn;
+        private int mAuthType;
+        private int mTrigger;
+        private boolean mHasEverConnected;
 
         private ConnectionEvent() {
             mConnectionEvent = new WifiMetricsProto.ConnectionEvent();
-            mRealEndTime = 0;
-            mRealStartTime = 0;
             mRouterFingerPrint = new RouterFingerPrint();
             mConnectionEvent.routerFingerprint = mRouterFingerPrint.mRouterFingerPrintProto;
             mConfigSsid = "<NULL>";
@@ -1079,6 +1150,9 @@
                     case FAILURE_ASSOCIATION_TIMED_OUT:
                         sb.append("ASSOCIATION_TIMED_OUT");
                         break;
+                    case FAILURE_NETWORK_NOT_FOUND:
+                        sb.append("FAILURE_NETWORK_NOT_FOUND");
+                        break;
                     default:
                         sb.append("UNKNOWN");
                         break;
@@ -1178,6 +1252,9 @@
                     case WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_EAP_FAILURE:
                         sb.append("AUTH_FAILURE_EAP_FAILURE");
                         break;
+                    case WifiMetricsProto.ConnectionEvent.DISCONNECTION_NON_LOCAL:
+                        sb.append("DISCONNECTION_NON_LOCAL");
+                        break;
                     default:
                         sb.append("FAILURE_REASON_UNKNOWN");
                         break;
@@ -1224,8 +1301,65 @@
                 sb.append(", numConsecutiveConnectionFailure="
                         + mConnectionEvent.numConsecutiveConnectionFailure);
                 sb.append(", isOsuProvisioned=" + mConnectionEvent.isOsuProvisioned);
+                sb.append(" interfaceName=").append(mConnectionEvent.interfaceName);
+                sb.append(" interfaceRole=").append(
+                        clientRoleEnumToString(mConnectionEvent.interfaceRole));
+                sb.append(", isFirstConnectionAfterBoot="
+                        + mConnectionEvent.isFirstConnectionAfterBoot);
+                return sb.toString();
             }
-            return sb.toString();
+        }
+
+        private void updateFromWifiConfiguration(WifiConfiguration config) {
+            synchronized (mLock) {
+                if (config != null) {
+                    // Is this a hidden network
+                    mRouterFingerPrint.mRouterFingerPrintProto.hidden = config.hiddenSSID;
+                    // Config may not have a valid dtimInterval set yet, in which case dtim will be
+                    // zero (These are only populated from beacon frame scan results, which are
+                    // returned as scan results from the chip far less frequently than
+                    // Probe-responses)
+                    if (config.dtimInterval > 0) {
+                        mRouterFingerPrint.mRouterFingerPrintProto.dtim = config.dtimInterval;
+                    }
+
+                    mConfigSsid = config.SSID;
+                    // Get AuthType information from config (We do this again from ScanResult after
+                    // associating with BSSID)
+                    if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_OPEN)) {
+                        mRouterFingerPrint.mRouterFingerPrintProto.authentication =
+                                WifiMetricsProto.RouterFingerPrint.AUTH_OPEN;
+                    } else if (config.isEnterprise()) {
+                        mRouterFingerPrint.mRouterFingerPrintProto.authentication =
+                                WifiMetricsProto.RouterFingerPrint.AUTH_ENTERPRISE;
+                    } else {
+                        mRouterFingerPrint.mRouterFingerPrintProto.authentication =
+                                WifiMetricsProto.RouterFingerPrint.AUTH_PERSONAL;
+                    }
+                    mRouterFingerPrint.mRouterFingerPrintProto.passpoint = config.isPasspoint();
+                    mRouterFingerPrint.mRouterFingerPrintProto.isPasspointHomeProvider =
+                            config.isHomeProviderNetwork;
+                    // If there's a ScanResult candidate associated with this config already, get it
+                    // and log (more accurate) metrics from it
+                    ScanResult candidate = config.getNetworkSelectionStatus().getCandidate();
+                    if (candidate != null) {
+                        updateMetricsFromScanResult(this, candidate);
+                    }
+                    if (mRouterFingerPrint.mRouterFingerPrintProto.authentication
+                            == WifiMetricsProto.RouterFingerPrint.AUTH_ENTERPRISE
+                            && config.enterpriseConfig != null) {
+                        int eapMethod = config.enterpriseConfig.getEapMethod();
+                        mRouterFingerPrint.mRouterFingerPrintProto.eapMethod =
+                                getEapMethodProto(eapMethod);
+                        int phase2Method = config.enterpriseConfig.getPhase2Method();
+                        mRouterFingerPrint.mRouterFingerPrintProto.authPhase2Method =
+                                getAuthPhase2MethodProto(phase2Method);
+                        int ocspType = config.enterpriseConfig.getOcsp();
+                        mRouterFingerPrint.mRouterFingerPrintProto.ocspType =
+                                getOcspTypeProto(ocspType);
+                    }
+                }
+            }
         }
     }
 
@@ -1345,12 +1479,10 @@
     public WifiMetrics(Context context, FrameworkFacade facade, Clock clock, Looper looper,
             WifiAwareMetrics awareMetrics, RttMetrics rttMetrics,
             WifiPowerMetrics wifiPowerMetrics, WifiP2pMetrics wifiP2pMetrics,
-            DppMetrics dppMetrics) {
+            DppMetrics dppMetrics, WifiMonitor wifiMonitor) {
         mContext = context;
         mFacade = facade;
         mClock = clock;
-        mCurrentConnectionEvent = null;
-        mScreenOn = true;
         mWifiState = WifiMetricsProto.WifiLog.WIFI_DISABLED;
         mRecordStartTimeSec = mClock.getElapsedSinceBootMillis() / 1000;
         mWifiAwareMetrics = awareMetrics;
@@ -1358,6 +1490,7 @@
         mWifiPowerMetrics = wifiPowerMetrics;
         mWifiP2pMetrics = wifiP2pMetrics;
         mDppMetrics = dppMetrics;
+        mWifiMonitor = wifiMonitor;
         mHandler = new Handler(looper) {
             public void handleMessage(Message msg) {
                 synchronized (mLock) {
@@ -1372,8 +1505,26 @@
         unknownStateStats.numTimesEnteredState++;
         mCurrentDeviceMobilityStateStartMs = mClock.getElapsedSinceBootMillis();
         mCurrentDeviceMobilityStatePnoScanStartMs = -1;
-        mOnWifiUsabilityListeners =
-                new ExternalCallbackTracker<IOnWifiUsabilityStatsListener>(mHandler);
+        mOnWifiUsabilityListeners = new RemoteCallbackList<>();
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_SCREEN_ON);
+        filter.addAction(Intent.ACTION_SCREEN_OFF);
+        context.registerReceiver(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        String action = intent.getAction();
+                        if (action.equals(Intent.ACTION_SCREEN_ON)) {
+                            setScreenState(true);
+                        } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
+                            setScreenState(false);
+                        }
+                    }
+                }, filter, null, mHandler);
+        setScreenState(context.getSystemService(PowerManager.class).isInteractive());
+
+        mScanMetrics = new ScanMetrics(context, clock);
     }
 
     /** Sets internal ScoringParams member */
@@ -1401,9 +1552,9 @@
         mWifiDataStall = wifiDataStall;
     }
 
-    /** Sets internal BssidBlocklistMonitor member */
-    public void setBssidBlocklistMonitor(BssidBlocklistMonitor bssidBlocklistMonitor) {
-        mBssidBlocklistMonitor = bssidBlocklistMonitor;
+    /** Sets internal WifiBlocklistMonitor member */
+    public void setWifiBlocklistMonitor(WifiBlocklistMonitor wifiBlocklistMonitor) {
+        mWifiBlocklistMonitor = wifiBlocklistMonitor;
     }
 
     /** Sets internal WifiHealthMonitor member */
@@ -1416,11 +1567,76 @@
         mWifiScoreCard = wifiScoreCard;
     }
 
+    /** Sets internal WifiChannelUtilization member */
+    public void setWifiChannelUtilization(WifiChannelUtilization wifiChannelUtilization) {
+        mWifiChannelUtilization = wifiChannelUtilization;
+    }
+
+    /** Sets internal WifiSettingsStore member */
+    public void setWifiSettingsStore(WifiSettingsStore wifiSettingsStore) {
+        mWifiSettingsStore = wifiSettingsStore;
+    }
+
+    /** Sets internal ActiveModeWarden member */
+    public void setActiveModeWarden(ActiveModeWarden activeModeWarden) {
+        mActiveModeWarden = activeModeWarden;
+        mActiveModeWarden.registerModeChangeCallback(new ModeChangeCallback());
+    }
+
+    /**
+     * Implements callbacks that set the internal ifaceName to ClientRole mapping.
+     */
+    @VisibleForTesting
+    private class ModeChangeCallback implements ActiveModeWarden.ModeChangeCallback {
+        @Override
+        public void onActiveModeManagerAdded(@NonNull ActiveModeManager activeModeManager) {
+            if (!(activeModeManager instanceof ConcreteClientModeManager)) {
+                return;
+            }
+            synchronized (mLock) {
+                ConcreteClientModeManager clientModeManager =
+                        (ConcreteClientModeManager) activeModeManager;
+                mIfaceToRoleMap.put(clientModeManager.getInterfaceName(),
+                        clientModeManager.getRole());
+            }
+        }
+
+        @Override
+        public void onActiveModeManagerRemoved(@NonNull ActiveModeManager activeModeManager) {
+            if (!(activeModeManager instanceof ConcreteClientModeManager)) {
+                return;
+            }
+            synchronized (mLock) {
+                ConcreteClientModeManager clientModeManager =
+                        (ConcreteClientModeManager) activeModeManager;
+                mIfaceToRoleMap.remove(clientModeManager.getInterfaceName());
+            }
+        }
+
+        @Override
+        public void onActiveModeManagerRoleChanged(@NonNull ActiveModeManager activeModeManager) {
+            if (!(activeModeManager instanceof ConcreteClientModeManager)) {
+                return;
+            }
+            synchronized (mLock) {
+                ConcreteClientModeManager clientModeManager =
+                        (ConcreteClientModeManager) activeModeManager;
+                mIfaceToRoleMap.put(clientModeManager.getInterfaceName(),
+                        clientModeManager.getRole());
+            }
+        }
+    }
+
     /**
      * Increment cumulative counters for link layer stats.
      * @param newStats
      */
-    public void incrementWifiLinkLayerUsageStats(WifiLinkLayerStats newStats) {
+    public void incrementWifiLinkLayerUsageStats(String ifaceName, WifiLinkLayerStats newStats) {
+        // This is only collected for primary STA currently because RSSI polling is disabled for
+        // non-primary STAs.
+        if (!isPrimary(ifaceName)) {
+            return;
+        }
         if (newStats == null) {
             return;
         }
@@ -1451,9 +1667,54 @@
                 (newStats.on_time_pno_scan - mLastLinkLayerStats.on_time_pno_scan);
         mWifiLinkLayerUsageStats.radioHs20ScanTimeMs +=
                 (newStats.on_time_hs20_scan - mLastLinkLayerStats.on_time_hs20_scan);
+        incrementPerRadioUsageStats(mLastLinkLayerStats, newStats);
+
         mLastLinkLayerStats = newStats;
     }
 
+    /**
+     * Increment individual radio stats usage
+     */
+    private void incrementPerRadioUsageStats(WifiLinkLayerStats oldStats,
+            WifiLinkLayerStats newStats) {
+        if (newStats.radioStats != null && newStats.radioStats.length > 0
+                && oldStats.radioStats != null && oldStats.radioStats.length > 0
+                && newStats.radioStats.length == oldStats.radioStats.length) {
+            int numRadios = newStats.radioStats.length;
+            for (int i = 0; i < numRadios; i++) {
+                WifiLinkLayerStats.RadioStat newRadio = newStats.radioStats[i];
+                WifiLinkLayerStats.RadioStat oldRadio = oldStats.radioStats[i];
+                if (newRadio.radio_id != oldRadio.radio_id) {
+                    continue;
+                }
+                RadioStats radioStats = mRadioStats.get(newRadio.radio_id);
+                if (radioStats == null) {
+                    radioStats = new RadioStats();
+                    radioStats.radioId = newRadio.radio_id;
+                    mRadioStats.put(newRadio.radio_id, radioStats);
+                }
+                radioStats.totalRadioOnTimeMs
+                        += newRadio.on_time - oldRadio.on_time;
+                radioStats.totalRadioTxTimeMs
+                        += newRadio.tx_time - oldRadio.tx_time;
+                radioStats.totalRadioRxTimeMs
+                        += newRadio.rx_time - oldRadio.rx_time;
+                radioStats.totalScanTimeMs
+                        += newRadio.on_time_scan - oldRadio.on_time_scan;
+                radioStats.totalNanScanTimeMs
+                        += newRadio.on_time_nan_scan - oldRadio.on_time_nan_scan;
+                radioStats.totalBackgroundScanTimeMs
+                        += newRadio.on_time_background_scan - oldRadio.on_time_background_scan;
+                radioStats.totalRoamScanTimeMs
+                        += newRadio.on_time_roam_scan - oldRadio.on_time_roam_scan;
+                radioStats.totalPnoScanTimeMs
+                        += newRadio.on_time_pno_scan - oldRadio.on_time_pno_scan;
+                radioStats.totalHotspot2ScanTimeMs
+                        += newRadio.on_time_hs20_scan - oldRadio.on_time_hs20_scan;
+            }
+        }
+    }
+
     private boolean newLinkLayerStatsIsValid(WifiLinkLayerStats oldStats,
             WifiLinkLayerStats newStats) {
         if (newStats.on_time < oldStats.on_time
@@ -1503,64 +1764,84 @@
      * failure code.
      * Gathers and sets the RouterFingerPrint data as well
      *
+     * @param ifaceName interface name for this connection event
      * @param config WifiConfiguration of the config used for the current connection attempt
      * @param roamType Roam type that caused connection attempt, see WifiMetricsProto.WifiLog.ROAM_X
      * @return The duration in ms since the last unfinished connection attempt,
      * or 0 if there is no unfinished connection
      */
     public int startConnectionEvent(
-            WifiConfiguration config, String targetBSSID, int roamType) {
+            String ifaceName, WifiConfiguration config, String targetBSSID, int roamType) {
         synchronized (mLock) {
             int overlapWithLastConnectionMs = 0;
-            if (mCurrentConnectionEvent != null) {
+            ConnectionEvent currentConnectionEvent = mCurrentConnectionEventPerIface.get(ifaceName);
+            if (currentConnectionEvent != null) {
                 overlapWithLastConnectionMs = (int) (mClock.getElapsedSinceBootMillis()
-                        - mCurrentConnectionEvent.mRealStartTime);
-                //Is this new Connection Event the same as the current one
-                if (mCurrentConnectionEvent.mConfigSsid != null
-                        && mCurrentConnectionEvent.mConfigBssid != null
+                        - currentConnectionEvent.mConnectionEvent.startTimeSinceBootMillis);
+                // Is this new Connection Event the same as the current one
+                if (currentConnectionEvent.mConfigSsid != null
+                        && currentConnectionEvent.mConfigBssid != null
                         && config != null
-                        && mCurrentConnectionEvent.mConfigSsid.equals(config.SSID)
-                        && (mCurrentConnectionEvent.mConfigBssid.equals("any")
-                        || mCurrentConnectionEvent.mConfigBssid.equals(targetBSSID))) {
-                    mCurrentConnectionEvent.mConfigBssid = targetBSSID;
+                        && currentConnectionEvent.mConfigSsid.equals(config.SSID)
+                        && (currentConnectionEvent.mConfigBssid.equals("any")
+                        || currentConnectionEvent.mConfigBssid.equals(targetBSSID))) {
+                    currentConnectionEvent.mConfigBssid = targetBSSID;
                     // End Connection Event due to new connection attempt to the same network
-                    endConnectionEvent(ConnectionEvent.FAILURE_REDUNDANT_CONNECTION_ATTEMPT,
+                    endConnectionEvent(ifaceName,
+                            ConnectionEvent.FAILURE_REDUNDANT_CONNECTION_ATTEMPT,
                             WifiMetricsProto.ConnectionEvent.HLF_NONE,
-                            WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
+                            WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, 0);
                 } else {
                     // End Connection Event due to new connection attempt to different network
-                    endConnectionEvent(ConnectionEvent.FAILURE_NEW_CONNECTION_ATTEMPT,
+                    endConnectionEvent(ifaceName,
+                            ConnectionEvent.FAILURE_NEW_CONNECTION_ATTEMPT,
                             WifiMetricsProto.ConnectionEvent.HLF_NONE,
-                            WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
+                            WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, 0);
                 }
             }
-            //If past maximum connection events, start removing the oldest
+            // If past maximum connection events, start removing the oldest
             while(mConnectionEventList.size() >= MAX_CONNECTION_EVENTS) {
-                mConnectionEventList.remove(0);
+                mConnectionEventList.removeFirst();
             }
-            mCurrentConnectionEvent = new ConnectionEvent();
-            mCurrentConnectionEvent.mConnectionEvent.startTimeMillis =
+            currentConnectionEvent = new ConnectionEvent();
+            mCurrentConnectionEventPerIface.put(ifaceName, currentConnectionEvent);
+            currentConnectionEvent.mConnectionEvent.interfaceName = ifaceName;
+            currentConnectionEvent.mConnectionEvent.interfaceRole = convertIfaceToEnum(ifaceName);
+            currentConnectionEvent.mConnectionEvent.startTimeMillis =
                     mClock.getWallClockMillis();
-            mCurrentConnectionEvent.mConfigBssid = targetBSSID;
-            mCurrentConnectionEvent.mConnectionEvent.roamType = roamType;
-            mCurrentConnectionEvent.mConnectionEvent.networkSelectorExperimentId =
+            currentConnectionEvent.mConnectionEvent.startTimeSinceBootMillis =
+                    mClock.getElapsedSinceBootMillis();
+            currentConnectionEvent.mConfigBssid = targetBSSID;
+            currentConnectionEvent.mConnectionEvent.roamType = roamType;
+            currentConnectionEvent.mConnectionEvent.networkSelectorExperimentId =
                     mNetworkSelectorExperimentId;
-            mCurrentConnectionEvent.mRouterFingerPrint.updateFromWifiConfiguration(config);
-            mCurrentConnectionEvent.mConfigBssid = "any";
-            mCurrentConnectionEvent.mRealStartTime = mClock.getElapsedSinceBootMillis();
-            mCurrentConnectionEvent.mWifiState = mWifiState;
-            mCurrentConnectionEvent.mScreenOn = mScreenOn;
-            mConnectionEventList.add(mCurrentConnectionEvent);
+            currentConnectionEvent.updateFromWifiConfiguration(config);
+            currentConnectionEvent.mConfigBssid = "any";
+            currentConnectionEvent.mWifiState = mWifiState;
+            currentConnectionEvent.mScreenOn = mScreenOn;
+            currentConnectionEvent.mConnectionEvent.isFirstConnectionAfterBoot =
+                    mFirstConnectionAfterBoot;
+            mFirstConnectionAfterBoot = false;
+            mConnectionEventList.add(currentConnectionEvent);
             mScanResultRssiTimestampMillis = -1;
             if (config != null) {
-                mCurrentConnectionEvent.mConnectionEvent.useRandomizedMac =
+                try {
+                    currentConnectionEvent.mAuthType = config.getAuthType();
+                } catch (IllegalStateException e) {
+                    currentConnectionEvent.mAuthType = 0;
+                }
+                currentConnectionEvent.mHasEverConnected =
+                        config.getNetworkSelectionStatus().hasEverConnected();
+                currentConnectionEvent.mConnectionEvent.useRandomizedMac =
                         config.macRandomizationSetting
-                        == WifiConfiguration.RANDOMIZATION_PERSISTENT;
-                mCurrentConnectionEvent.mConnectionEvent.useAggressiveMac =
-                        mWifiConfigManager.shouldUseAggressiveRandomization(config);
-                mCurrentConnectionEvent.mConnectionEvent.connectionNominator =
+                        != WifiConfiguration.RANDOMIZATION_NONE;
+                currentConnectionEvent.mConnectionEvent.useAggressiveMac =
+                        mWifiConfigManager.shouldUseEnhancedRandomization(config);
+                currentConnectionEvent.mConnectionEvent.connectionNominator =
                         mNetworkIdToNominatorId.get(config.networkId,
                                 WifiMetricsProto.ConnectionEvent.NOMINATOR_UNKNOWN);
+                currentConnectionEvent.mConnectionEvent.isCarrierMerged = config.carrierMerged;
+
                 ScanResult candidate = config.getNetworkSelectionStatus().getCandidate();
                 if (candidate != null) {
                     // Cache the RSSI of the candidate, as the connection event level is updated
@@ -1569,101 +1850,110 @@
                     mScanResultRssi = candidate.level;
                     mScanResultRssiTimestampMillis = mClock.getElapsedSinceBootMillis();
                 }
-                mCurrentConnectionEvent.mConnectionEvent.numBssidInBlocklist =
-                        mBssidBlocklistMonitor.updateAndGetNumBlockedBssidsForSsid(config.SSID);
-                mCurrentConnectionEvent.mConnectionEvent.networkType =
+                currentConnectionEvent.mConnectionEvent.numBssidInBlocklist =
+                        mWifiBlocklistMonitor.updateAndGetNumBlockedBssidsForSsid(config.SSID);
+                currentConnectionEvent.mConnectionEvent.networkType =
                         WifiMetricsProto.ConnectionEvent.TYPE_UNKNOWN;
-                mCurrentConnectionEvent.mConnectionEvent.isOsuProvisioned = false;
+                currentConnectionEvent.mConnectionEvent.isOsuProvisioned = false;
                 if (config.isPasspoint()) {
-                    mCurrentConnectionEvent.mConnectionEvent.networkType =
+                    currentConnectionEvent.mConnectionEvent.networkType =
                             WifiMetricsProto.ConnectionEvent.TYPE_PASSPOINT;
-                    mCurrentConnectionEvent.mConnectionEvent.isOsuProvisioned =
+                    currentConnectionEvent.mConnectionEvent.isOsuProvisioned =
                             !TextUtils.isEmpty(config.updateIdentifier);
                 } else if (WifiConfigurationUtil.isConfigForSaeNetwork(config)) {
-                    mCurrentConnectionEvent.mConnectionEvent.networkType =
+                    currentConnectionEvent.mConnectionEvent.networkType =
                             WifiMetricsProto.ConnectionEvent.TYPE_WPA3;
                 } else if (WifiConfigurationUtil.isConfigForWapiPskNetwork(config)) {
-                    mCurrentConnectionEvent.mConnectionEvent.networkType =
+                    currentConnectionEvent.mConnectionEvent.networkType =
                             WifiMetricsProto.ConnectionEvent.TYPE_WAPI;
                 } else if (WifiConfigurationUtil.isConfigForWapiCertNetwork(config)) {
-                    mCurrentConnectionEvent.mConnectionEvent.networkType =
+                    currentConnectionEvent.mConnectionEvent.networkType =
                             WifiMetricsProto.ConnectionEvent.TYPE_WAPI;
                 } else if (WifiConfigurationUtil.isConfigForPskNetwork(config)) {
-                    mCurrentConnectionEvent.mConnectionEvent.networkType =
+                    currentConnectionEvent.mConnectionEvent.networkType =
                             WifiMetricsProto.ConnectionEvent.TYPE_WPA2;
                 } else if (WifiConfigurationUtil.isConfigForEapNetwork(config)) {
-                    mCurrentConnectionEvent.mConnectionEvent.networkType =
+                    currentConnectionEvent.mConnectionEvent.networkType =
                             WifiMetricsProto.ConnectionEvent.TYPE_EAP;
                 } else if (WifiConfigurationUtil.isConfigForOweNetwork(config)) {
-                    mCurrentConnectionEvent.mConnectionEvent.networkType =
+                    currentConnectionEvent.mConnectionEvent.networkType =
                             WifiMetricsProto.ConnectionEvent.TYPE_OWE;
                 } else if (WifiConfigurationUtil.isConfigForOpenNetwork(config)) {
-                    mCurrentConnectionEvent.mConnectionEvent.networkType =
+                    currentConnectionEvent.mConnectionEvent.networkType =
                             WifiMetricsProto.ConnectionEvent.TYPE_OPEN;
                 }
 
                 if (!config.fromWifiNetworkSuggestion) {
-                    mCurrentConnectionEvent.mConnectionEvent.networkCreator =
+                    currentConnectionEvent.mConnectionEvent.networkCreator =
                             WifiMetricsProto.ConnectionEvent.CREATOR_USER;
                 } else if (config.carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) {
-                    mCurrentConnectionEvent.mConnectionEvent.networkCreator =
+                    currentConnectionEvent.mConnectionEvent.networkCreator =
                             WifiMetricsProto.ConnectionEvent.CREATOR_CARRIER;
                 } else {
-                    mCurrentConnectionEvent.mConnectionEvent.networkCreator =
+                    currentConnectionEvent.mConnectionEvent.networkCreator =
                             WifiMetricsProto.ConnectionEvent.CREATOR_UNKNOWN;
                 }
 
-                mCurrentConnectionEvent.mConnectionEvent.screenOn = mScreenOn;
-                if (mCurrentConnectionEvent.mConfigSsid != null) {
+                currentConnectionEvent.mConnectionEvent.screenOn = mScreenOn;
+                if (currentConnectionEvent.mConfigSsid != null) {
                     WifiScoreCard.NetworkConnectionStats recentStats = mWifiScoreCard.lookupNetwork(
-                            mCurrentConnectionEvent.mConfigSsid).getRecentStats();
-                    mCurrentConnectionEvent.mConnectionEvent.numConsecutiveConnectionFailure =
+                            currentConnectionEvent.mConfigSsid).getRecentStats();
+                    currentConnectionEvent.mConnectionEvent.numConsecutiveConnectionFailure =
                             recentStats.getCount(WifiScoreCard.CNT_CONSECUTIVE_CONNECTION_FAILURE);
                 }
-            }
-            return overlapWithLastConnectionMs;
-        }
-    }
 
-    /**
-     * set the RoamType of the current ConnectionEvent (if any)
-     */
-    public void setConnectionEventRoamType(int roamType) {
-        synchronized (mLock) {
-            if (mCurrentConnectionEvent != null) {
-                mCurrentConnectionEvent.mConnectionEvent.roamType = roamType;
+                String ssid = currentConnectionEvent.mConfigSsid;
+                int nominator = currentConnectionEvent.mConnectionEvent.connectionNominator;
+                int trigger = WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED__TRIGGER__UNKNOWN;
+
+                if (nominator == WifiMetricsProto.ConnectionEvent.NOMINATOR_MANUAL) {
+                    trigger = WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED__TRIGGER__MANUAL;
+                } else if (mPreviousSession == null) {
+                    trigger = WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED__TRIGGER__AUTOCONNECT_BOOT;
+                } else if (ssid != null && ssid.equals(mPreviousSession.mSsid)) {
+                    trigger = WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED__TRIGGER__RECONNECT_SAME_NETWORK;
+                } else if (nominator != WifiMetricsProto.ConnectionEvent.NOMINATOR_UNKNOWN) {
+                    trigger = WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED__TRIGGER__AUTOCONNECT_CONFIGURED_NETWORK;
+                }
+                currentConnectionEvent.mTrigger = trigger;
             }
+
+            return overlapWithLastConnectionMs;
         }
     }
 
     /**
      * Set AP related metrics from ScanDetail
      */
-    public void setConnectionScanDetail(ScanDetail scanDetail) {
+    public void setConnectionScanDetail(String ifaceName, ScanDetail scanDetail) {
         synchronized (mLock) {
-            if (mCurrentConnectionEvent != null && scanDetail != null) {
-                NetworkDetail networkDetail = scanDetail.getNetworkDetail();
-                ScanResult scanResult = scanDetail.getScanResult();
-                //Ensure that we have a networkDetail, and that it corresponds to the currently
-                //tracked connection attempt
-                if (networkDetail != null && scanResult != null
-                        && mCurrentConnectionEvent.mConfigSsid != null
-                        && mCurrentConnectionEvent.mConfigSsid
-                        .equals("\"" + networkDetail.getSSID() + "\"")) {
-                    updateMetricsFromNetworkDetail(networkDetail);
-                    updateMetricsFromScanResult(scanResult);
-                }
+            ConnectionEvent currentConnectionEvent = mCurrentConnectionEventPerIface.get(ifaceName);
+            if (currentConnectionEvent == null || scanDetail == null) {
+                return;
             }
+            NetworkDetail networkDetail = scanDetail.getNetworkDetail();
+            ScanResult scanResult = scanDetail.getScanResult();
+            // Ensure that we have a networkDetail, and that it corresponds to the currently
+            // tracked connection attempt
+            if (networkDetail == null || scanResult == null
+                    || currentConnectionEvent.mConfigSsid == null
+                    || !currentConnectionEvent.mConfigSsid
+                    .equals("\"" + networkDetail.getSSID() + "\"")) {
+                return;
+            }
+            updateMetricsFromNetworkDetail(currentConnectionEvent, networkDetail);
+            updateMetricsFromScanResult(currentConnectionEvent, scanResult);
         }
     }
 
     /**
      * Set PMK cache status for a connection event
      */
-    public void setConnectionPmkCache(boolean isEnabled) {
+    public void setConnectionPmkCache(String ifaceName, boolean isEnabled) {
         synchronized (mLock) {
-            if (mCurrentConnectionEvent != null) {
-                mCurrentConnectionEvent.mRouterFingerPrint.setPmkCache(isEnabled);
+            ConnectionEvent currentConnectionEvent = mCurrentConnectionEventPerIface.get(ifaceName);
+            if (currentConnectionEvent != null) {
+                currentConnectionEvent.mRouterFingerPrint.setPmkCache(isEnabled);
             }
         }
     }
@@ -1671,11 +1961,12 @@
     /**
      * Set the max link speed supported by current network
      */
-    public void setConnectionMaxSupportedLinkSpeedMbps(int maxSupportedTxLinkSpeedMbps,
-            int maxSupportedRxLinkSpeedMbps) {
+    public void setConnectionMaxSupportedLinkSpeedMbps(
+            String ifaceName, int maxSupportedTxLinkSpeedMbps, int maxSupportedRxLinkSpeedMbps) {
         synchronized (mLock) {
-            if (mCurrentConnectionEvent != null) {
-                mCurrentConnectionEvent.mRouterFingerPrint.setMaxSupportedLinkSpeedMbps(
+            ConnectionEvent currentConnectionEvent = mCurrentConnectionEventPerIface.get(ifaceName);
+            if (currentConnectionEvent != null) {
+                currentConnectionEvent.mRouterFingerPrint.setMaxSupportedLinkSpeedMbps(
                         maxSupportedTxLinkSpeedMbps, maxSupportedRxLinkSpeedMbps);
             }
         }
@@ -1686,39 +1977,110 @@
      * If a Connection event has not been started and is active when .end is called, then this
      * method will do nothing.
      *
+     * @param ifaceName
      * @param level2FailureCode Level 2 failure code returned by supplicant
      * @param connectivityFailureCode WifiMetricsProto.ConnectionEvent.HLF_X
      * @param level2FailureReason Breakdown of level2FailureCode with more detailed reason
      */
-    public void endConnectionEvent(int level2FailureCode, int connectivityFailureCode,
-            int level2FailureReason) {
+    public void endConnectionEvent(
+            String ifaceName,
+            int level2FailureCode,
+            int connectivityFailureCode,
+            int level2FailureReason,
+            int frequency) {
         synchronized (mLock) {
-            if (mCurrentConnectionEvent != null) {
-                boolean result = (level2FailureCode == 1)
+            ConnectionEvent currentConnectionEvent = mCurrentConnectionEventPerIface.get(ifaceName);
+            if (currentConnectionEvent != null) {
+                boolean connectionSucceeded = (level2FailureCode == 1)
                         && (connectivityFailureCode == WifiMetricsProto.ConnectionEvent.HLF_NONE);
-                mCurrentConnectionEvent.mConnectionEvent.connectionResult = result ? 1 : 0;
-                mCurrentConnectionEvent.mRealEndTime = mClock.getElapsedSinceBootMillis();
-                mCurrentConnectionEvent.mConnectionEvent.durationTakenToConnectMillis = (int)
-                        (mCurrentConnectionEvent.mRealEndTime
-                        - mCurrentConnectionEvent.mRealStartTime);
-                mCurrentConnectionEvent.mConnectionEvent.level2FailureCode = level2FailureCode;
-                mCurrentConnectionEvent.mConnectionEvent.connectivityLevelFailureCode =
+
+                int band = KnownBandsChannelHelper.getBand(frequency);
+                int durationTakenToConnectMillis =
+                        (int) (mClock.getElapsedSinceBootMillis()
+                                - currentConnectionEvent.mConnectionEvent.startTimeSinceBootMillis);
+
+                if (connectionSucceeded) {
+                    mCurrentSession = new SessionData(currentConnectionEvent.mConfigSsid,
+                            mClock.getElapsedSinceBootMillis(),
+                            band, currentConnectionEvent.mAuthType);
+
+                    // TODO(b/166309727) need to add ifaceName to WifiStatsLog
+                    WifiStatsLog.write(WifiStatsLog.WIFI_CONNECTION_STATE_CHANGED,
+                            true, band, currentConnectionEvent.mAuthType);
+                }
+
+                currentConnectionEvent.mConnectionEvent.connectionResult =
+                        connectionSucceeded ? 1 : 0;
+                currentConnectionEvent.mConnectionEvent.durationTakenToConnectMillis =
+                        durationTakenToConnectMillis;
+                currentConnectionEvent.mConnectionEvent.level2FailureCode = level2FailureCode;
+                currentConnectionEvent.mConnectionEvent.connectivityLevelFailureCode =
                         connectivityFailureCode;
-                mCurrentConnectionEvent.mConnectionEvent.level2FailureReason = level2FailureReason;
+                currentConnectionEvent.mConnectionEvent.level2FailureReason = level2FailureReason;
 
                 // Write metrics to statsd
                 int wwFailureCode = getConnectionResultFailureCode(level2FailureCode,
                         level2FailureReason);
+
                 if (wwFailureCode != -1) {
-                    WifiStatsLog.write(WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED, result,
-                            wwFailureCode, mCurrentConnectionEvent.mConnectionEvent.signalStrength);
+                    int timeSinceConnectedSeconds = (int) ((mPreviousSession != null ?
+                            (mClock.getElapsedSinceBootMillis()
+                                    - mPreviousSession.mSessionEndTimeMillis) :
+                            mClock.getElapsedSinceBootMillis()) / 1000);
+
+                    WifiStatsLog.write(WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED,
+                            connectionSucceeded,
+                            wwFailureCode, currentConnectionEvent.mConnectionEvent.signalStrength,
+                            durationTakenToConnectMillis, band, currentConnectionEvent.mAuthType,
+                            currentConnectionEvent.mTrigger,
+                            currentConnectionEvent.mHasEverConnected,
+                            timeSinceConnectedSeconds
+                    );
                 }
-                // ConnectionEvent already added to ConnectionEvents List. Safe to null current here
-                mCurrentConnectionEvent = null;
-                if (!result) {
+
+                // ConnectionEvent already added to ConnectionEvents List. Safe to remove here.
+                mCurrentConnectionEventPerIface.remove(ifaceName);
+                if (!connectionSucceeded) {
                     mScanResultRssiTimestampMillis = -1;
                 }
-                mWifiStatusBuilder.setConnected(result);
+                mWifiStatusBuilder.setConnected(connectionSucceeded);
+            }
+        }
+    }
+
+    /**
+     * Report that an active Wifi network connection was dropped.
+     *
+     * @param disconnectReason Error code for the disconnect.
+     * @param rssi Last seen RSSI.
+     * @param linkSpeed Last seen link speed.
+     */
+    public void reportNetworkDisconnect(String ifaceName, int disconnectReason, int rssi,
+            int linkSpeed) {
+        synchronized (mLock) {
+            if (!isPrimary(ifaceName)) {
+                return;
+            }
+            WifiStatsLog.write(WifiStatsLog.WIFI_CONNECTION_STATE_CHANGED,
+                    false,
+                    mCurrentSession != null ? mCurrentSession.mBand : 0,
+                    mCurrentSession != null ? mCurrentSession.mAuthType : 0);
+
+            if (mCurrentSession != null) {
+                mCurrentSession.mSessionEndTimeMillis = mClock.getElapsedSinceBootMillis();
+                int durationSeconds = (int) (mCurrentSession.mSessionEndTimeMillis
+                        - mCurrentSession.mSessionStartTimeMillis) / 1000;
+
+                WifiStatsLog.write(WifiStatsLog.WIFI_DISCONNECT_REPORTED,
+                        durationSeconds,
+                        disconnectReason,
+                        mCurrentSession.mBand,
+                        mCurrentSession.mAuthType,
+                        rssi,
+                        linkSpeed);
+
+                mPreviousSession = mCurrentSession;
+                mCurrentSession = null;
             }
         }
     }
@@ -1736,7 +2098,7 @@
                     case WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_EAP_FAILURE:
                         return WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED__FAILURE_CODE__FAILURE_AUTHENTICATION_EAP;
                     case WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_WRONG_PSWD:
-                        return -1;
+                        return WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED__FAILURE_CODE__FAILURE_WRONG_PASSWORD;
                     default:
                         return WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED__FAILURE_CODE__FAILURE_AUTHENTICATION_GENERAL;
                 }
@@ -1757,13 +2119,14 @@
     /**
      * Set ConnectionEvent DTIM Interval (if set), and 802.11 Connection mode, from NetworkDetail
      */
-    private void updateMetricsFromNetworkDetail(NetworkDetail networkDetail) {
+    private void updateMetricsFromNetworkDetail(
+            ConnectionEvent currentConnectionEvent, NetworkDetail networkDetail) {
         int dtimInterval = networkDetail.getDtimInterval();
         if (dtimInterval > 0) {
-            mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto.dtim =
+            currentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto.dtim =
                     dtimInterval;
         }
-        int connectionWifiMode;
+        final int connectionWifiMode;
         switch (networkDetail.getWifiMode()) {
             case InformationElementUtil.WifiMode.MODE_UNDEFINED:
                 connectionWifiMode = WifiMetricsProto.RouterFingerPrint.ROUTER_TECH_UNKNOWN;
@@ -1790,8 +2153,8 @@
                 connectionWifiMode = WifiMetricsProto.RouterFingerPrint.ROUTER_TECH_OTHER;
                 break;
         }
-        mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto
-                .routerTechnology = connectionWifiMode;
+        currentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto.routerTechnology =
+                connectionWifiMode;
 
         if (networkDetail.isMboSupported()) {
             mWifiLogProto.numConnectToNetworkSupportingMbo++;
@@ -1804,26 +2167,29 @@
     /**
      * Set ConnectionEvent RSSI and authentication type from ScanResult
      */
-    private void updateMetricsFromScanResult(ScanResult scanResult) {
-        mCurrentConnectionEvent.mConnectionEvent.signalStrength = scanResult.level;
-        mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto.authentication =
+    private void updateMetricsFromScanResult(
+            ConnectionEvent currentConnectionEvent, ScanResult scanResult) {
+        currentConnectionEvent.mConnectionEvent.signalStrength = scanResult.level;
+        currentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto.authentication =
                 WifiMetricsProto.RouterFingerPrint.AUTH_OPEN;
-        mCurrentConnectionEvent.mConfigBssid = scanResult.BSSID;
+        currentConnectionEvent.mConfigBssid = scanResult.BSSID;
         if (scanResult.capabilities != null) {
             if (ScanResultUtil.isScanResultForWepNetwork(scanResult)) {
-                mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto.authentication =
+                currentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto.authentication =
                         WifiMetricsProto.RouterFingerPrint.AUTH_PERSONAL;
             } else if (ScanResultUtil.isScanResultForPskNetwork(scanResult)
                     || ScanResultUtil.isScanResultForSaeNetwork(scanResult)) {
-                mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto.authentication =
+                currentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto.authentication =
                         WifiMetricsProto.RouterFingerPrint.AUTH_PERSONAL;
-            } else if (ScanResultUtil.isScanResultForEapNetwork(scanResult)
+            } else if (ScanResultUtil.isScanResultForWpa3EnterpriseTransitionNetwork(scanResult)
+                    || ScanResultUtil.isScanResultForWpa3EnterpriseOnlyNetwork(scanResult)
+                    || ScanResultUtil.isScanResultForEapNetwork(scanResult)
                     || ScanResultUtil.isScanResultForEapSuiteBNetwork(scanResult)) {
-                mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto.authentication =
+                currentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto.authentication =
                         WifiMetricsProto.RouterFingerPrint.AUTH_ENTERPRISE;
             }
         }
-        mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto.channelInfo =
+        currentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto.channelInfo =
                 scanResult.frequency;
     }
 
@@ -2187,7 +2553,10 @@
     /**
      * Increment various poll related metrics, and cache performance data for StaEvent logging
      */
-    public void handlePollResult(WifiInfo wifiInfo) {
+    public void handlePollResult(String ifaceName, WifiInfo wifiInfo) {
+        if (!isPrimary(ifaceName)) {
+            return;
+        }
         mLastPollRssi = wifiInfo.getRssi();
         mLastPollLinkSpeed = wifiInfo.getLinkSpeed();
         mLastPollFreq = wifiInfo.getFrequency();
@@ -2448,6 +2817,7 @@
         int oceSupportedNetworks = 0;
         int filsSupportedNetworks = 0;
         int band6gNetworks = 0;
+        int band6gPscNetworks = 0;
         int standard11axNetworks = 0;
 
         for (ScanDetail scanDetail : scanDetails) {
@@ -2487,8 +2857,13 @@
                 }
                 if (scanResult.is6GHz()) {
                     band6gNetworks++;
+                    if (scanResult.is6GhzPsc()) {
+                        band6gPscNetworks++;
+                    }
                 }
-                if (ScanResultUtil.isScanResultForEapSuiteBNetwork(scanResult)) {
+                if (ScanResultUtil.isScanResultForEapSuiteBNetwork(scanResult)
+                        || ScanResultUtil.isScanResultForWpa3EnterpriseTransitionNetwork(scanResult)
+                        || ScanResultUtil.isScanResultForWpa3EnterpriseOnlyNetwork(scanResult)) {
                     wpa3EnterpriseNetworks++;
                 } else if (ScanResultUtil.isScanResultForWapiPskNetwork(scanResult)) {
                     wapiPersonalNetworks++;
@@ -2528,6 +2903,7 @@
             mWifiLogProto.numFilsSupportedNetworkScanResults += filsSupportedNetworks;
             mWifiLogProto.num11AxNetworkScanResults += standard11axNetworks;
             mWifiLogProto.num6GNetworkScanResults += band6gNetworks;
+            mWifiLogProto.num6GPscNetworkScanResults += band6gPscNetworks;
             mWifiLogProto.numScans++;
         }
     }
@@ -2543,7 +2919,7 @@
      *
      * Also records events when the current score breaches significant thresholds.
      */
-    public void incrementWifiScoreCount(int score) {
+    public void incrementWifiScoreCount(String ifaceName, int score) {
         if (score < MIN_WIFI_SCORE || score > MAX_WIFI_SCORE) {
             return;
         }
@@ -2563,7 +2939,7 @@
                 mWifiWins = wifiWins;
                 StaEvent event = new StaEvent();
                 event.type = StaEvent.TYPE_SCORE_BREACH;
-                addStaEvent(event);
+                addStaEvent(ifaceName, event);
                 // Only record the first score breach by checking whether mScoreBreachLowTimeMillis
                 // has been set to -1
                 if (!wifiWins && mScoreBreachLowTimeMillis == -1) {
@@ -2619,22 +2995,49 @@
     /**
      * Adds a record indicating the current up state of soft AP
      */
-    public void addSoftApUpChangedEvent(boolean isUp, int mode, long defaultShutdownTimeoutMillis) {
+    public void addSoftApUpChangedEvent(boolean isUp, int mode, long defaultShutdownTimeoutMillis,
+            boolean isBridged) {
+        int numOfEventNeedToAdd = isBridged && isUp ? 2 : 1;
+        for (int i = 0; i < numOfEventNeedToAdd; i++) {
+            SoftApConnectedClientsEvent event = new SoftApConnectedClientsEvent();
+            if (isUp) {
+                event.eventType = isBridged ? SoftApConnectedClientsEvent.DUAL_AP_BOTH_INSTANCES_UP
+                        : SoftApConnectedClientsEvent.SOFT_AP_UP;
+            } else {
+                event.eventType = SoftApConnectedClientsEvent.SOFT_AP_DOWN;
+            }
+            event.numConnectedClients = 0;
+            event.defaultShutdownTimeoutSetting = defaultShutdownTimeoutMillis;
+            addSoftApConnectedClientsEvent(event, mode);
+        }
+    }
+
+    /**
+     * Adds a record indicating the one of the dual AP instances is down.
+     */
+    public void addSoftApInstanceDownEventInDualMode(int mode, @NonNull SoftApInfo info) {
         SoftApConnectedClientsEvent event = new SoftApConnectedClientsEvent();
-        event.eventType = isUp ? SoftApConnectedClientsEvent.SOFT_AP_UP :
-                SoftApConnectedClientsEvent.SOFT_AP_DOWN;
-        event.numConnectedClients = 0;
-        event.defaultShutdownTimeoutSetting = defaultShutdownTimeoutMillis;
+        event.eventType = SoftApConnectedClientsEvent.DUAL_AP_ONE_INSTANCE_DOWN;
+        event.channelFrequency = info.getFrequency();
+        event.channelBandwidth = info.getBandwidth();
+        event.generation = info.getWifiStandardInternal();
         addSoftApConnectedClientsEvent(event, mode);
     }
 
     /**
      * Adds a record for current number of associated stations to soft AP
      */
-    public void addSoftApNumAssociatedStationsChangedEvent(int numStations, int mode) {
+    public void addSoftApNumAssociatedStationsChangedEvent(int numTotalStations,
+            int numStationsOnCurrentFrequency, int mode, @Nullable SoftApInfo info) {
         SoftApConnectedClientsEvent event = new SoftApConnectedClientsEvent();
         event.eventType = SoftApConnectedClientsEvent.NUM_CLIENTS_CHANGED;
-        event.numConnectedClients = numStations;
+        if (info != null) {
+            event.channelFrequency = info.getFrequency();
+            event.channelBandwidth = info.getBandwidth();
+            event.generation = info.getWifiStandardInternal();
+        }
+        event.numConnectedClients = numTotalStations;
+        event.numConnectedClientsOnCurrentFrequency = numStationsOnCurrentFrequency;
         addSoftApConnectedClientsEvent(event, mode);
     }
 
@@ -2667,8 +3070,16 @@
     /**
      * Updates current soft AP events with channel info
      */
-    public void addSoftApChannelSwitchedEvent(int frequency, int bandwidth, int mode) {
+    public void addSoftApChannelSwitchedEvent(List<SoftApInfo> infos, int mode, boolean isBridged) {
         synchronized (mLock) {
+            int numOfEventNeededToUpdate = infos.size();
+            if (isBridged && numOfEventNeededToUpdate == 1) {
+                // Ignore the channel info update when only 1 info in bridged mode because it means
+                // that one of the instance was been shutdown.
+                return;
+            }
+            int apUpEvent = isBridged ? SoftApConnectedClientsEvent.DUAL_AP_BOTH_INSTANCES_UP
+                    : SoftApConnectedClientsEvent.SOFT_AP_UP;
             List<SoftApConnectedClientsEvent> softApEventList;
             switch (mode) {
                 case WifiManager.IFACE_IP_MODE_TETHERED:
@@ -2681,13 +3092,15 @@
                     return;
             }
 
-            for (int index = softApEventList.size() - 1; index >= 0; index--) {
+            for (int index = softApEventList.size() - 1;
+                    index >= 0 && numOfEventNeededToUpdate != 0; index--) {
                 SoftApConnectedClientsEvent event = softApEventList.get(index);
-
-                if (event != null && event.eventType == SoftApConnectedClientsEvent.SOFT_AP_UP) {
-                    event.channelFrequency = frequency;
-                    event.channelBandwidth = bandwidth;
-                    break;
+                if (event != null && event.eventType == apUpEvent) {
+                    int infoIndex = numOfEventNeededToUpdate - 1;
+                    event.channelFrequency = infos.get(infoIndex).getFrequency();
+                    event.channelBandwidth = infos.get(infoIndex).getBandwidth();
+                    event.generation = infos.get(infoIndex).getWifiStandardInternal();
+                    numOfEventNeededToUpdate--;
                 }
             }
         }
@@ -2696,7 +3109,7 @@
     /**
      * Updates current soft AP events with softap configuration
      */
-    public void updateSoftApConfiguration(SoftApConfiguration config, int mode) {
+    public void updateSoftApConfiguration(SoftApConfiguration config, int mode, boolean isBridged) {
         synchronized (mLock) {
             List<SoftApConnectedClientsEvent> softApEventList;
             switch (mode) {
@@ -2710,16 +3123,20 @@
                     return;
             }
 
-            for (int index = softApEventList.size() - 1; index >= 0; index--) {
-                SoftApConnectedClientsEvent event = softApEventList.get(index);
+            int numOfEventNeededToUpdate = isBridged ? 2 : 1;
+            int apUpEvent = isBridged ? SoftApConnectedClientsEvent.DUAL_AP_BOTH_INSTANCES_UP
+                    : SoftApConnectedClientsEvent.SOFT_AP_UP;
 
-                if (event != null && event.eventType == SoftApConnectedClientsEvent.SOFT_AP_UP) {
+            for (int index = softApEventList.size() - 1;
+                    index >= 0 && numOfEventNeededToUpdate != 0; index--) {
+                SoftApConnectedClientsEvent event = softApEventList.get(index);
+                if (event != null && event.eventType == apUpEvent) {
                     event.maxNumClientsSettingInSoftapConfiguration =
                             config.getMaxNumberOfClients();
                     event.shutdownTimeoutSettingInSoftapConfiguration =
                             config.getShutdownTimeoutMillis();
                     event.clientControlIsEnabled = config.isClientControlByUserEnabled();
-                    break;
+                    numOfEventNeededToUpdate--;
                 }
             }
         }
@@ -2728,7 +3145,7 @@
     /**
      * Updates current soft AP events with softap capability
      */
-    public void updateSoftApCapability(SoftApCapability capability, int mode) {
+    public void updateSoftApCapability(SoftApCapability capability, int mode, boolean isBridged) {
         synchronized (mLock) {
             List<SoftApConnectedClientsEvent> softApEventList;
             switch (mode) {
@@ -2742,12 +3159,17 @@
                     return;
             }
 
-            for (int index = softApEventList.size() - 1; index >= 0; index--) {
+            int numOfEventNeededToUpdate = isBridged ? 2 : 1;
+            int apUpEvent = isBridged ? SoftApConnectedClientsEvent.DUAL_AP_BOTH_INSTANCES_UP
+                    : SoftApConnectedClientsEvent.SOFT_AP_UP;
+
+            for (int index = softApEventList.size() - 1;
+                    index >= 0 && numOfEventNeededToUpdate != 0; index--) {
                 SoftApConnectedClientsEvent event = softApEventList.get(index);
-                if (event != null && event.eventType == SoftApConnectedClientsEvent.SOFT_AP_UP) {
+                if (event != null && event.eventType == apUpEvent) {
                     event.maxNumClientsSettingInSoftapCapability =
                             capability.getMaxSupportedClients();
-                    break;
+                    numOfEventNeededToUpdate--;
                 }
             }
         }
@@ -3063,9 +3485,10 @@
 
                 ssids.add(matchInfo);
                 bssids++;
-                boolean isOpen = matchInfo.networkType == WifiConfiguration.SECURITY_TYPE_OPEN;
+                boolean isOpen = ScanResultUtil.isScanResultForOpenNetwork(scanResult)
+                        || ScanResultUtil.isScanResultForOweNetwork(scanResult);
                 WifiConfiguration config =
-                        mWifiConfigManager.getConfiguredNetworkForScanDetail(scanDetail);
+                        mWifiConfigManager.getSavedNetworkForScanDetail(scanDetail);
                 boolean isSaved = (config != null) && !config.isEphemeral()
                         && !config.isPasspoint();
                 if (isOpen) {
@@ -3175,10 +3598,10 @@
     }
 
     /** Log firmware alert related metrics */
-    public void logFirmwareAlert(int errorCode) {
+    public void logFirmwareAlert(String ifaceName, int errorCode) {
         incrementAlertReasonCount(errorCode);
-        logWifiIsUnusableEvent(WifiIsUnusableEvent.TYPE_FIRMWARE_ALERT, errorCode);
-        addToWifiUsabilityStatsList(WifiUsabilityStats.LABEL_BAD,
+        logWifiIsUnusableEvent(ifaceName, WifiIsUnusableEvent.TYPE_FIRMWARE_ALERT, errorCode);
+        addToWifiUsabilityStatsList(ifaceName, WifiUsabilityStats.LABEL_BAD,
                 WifiUsabilityStats.TYPE_FIRMWARE_ALERT, errorCode);
     }
 
@@ -3217,7 +3640,7 @@
                 pw.println("mConnectionEvents:");
                 for (ConnectionEvent event : mConnectionEventList) {
                     String eventLine = event.toString();
-                    if (event == mCurrentConnectionEvent) {
+                    if (mCurrentConnectionEventPerIface.containsValue(event)) {
                         eventLine += " CURRENTLY OPEN EVENT";
                     }
                     pw.println(eventLine);
@@ -3437,12 +3860,16 @@
                         + mWifiLogProto.num11AxNetworkScanResults);
                 pw.println("mWifiLogProto.num6GNetworkScanResults"
                         + mWifiLogProto.num6GNetworkScanResults);
+                pw.println("mWifiLogProto.num6GPscNetworkScanResults"
+                        + mWifiLogProto.num6GPscNetworkScanResults);
                 pw.println("mWifiLogProto.numBssidFilteredDueToMboAssocDisallowInd="
                         + mWifiLogProto.numBssidFilteredDueToMboAssocDisallowInd);
                 pw.println("mWifiLogProto.numConnectToNetworkSupportingMbo="
                         + mWifiLogProto.numConnectToNetworkSupportingMbo);
                 pw.println("mWifiLogProto.numConnectToNetworkSupportingOce="
                         + mWifiLogProto.numConnectToNetworkSupportingOce);
+                pw.println("mWifiLogProto.numSteeringRequest="
+                        + mWifiLogProto.numSteeringRequest);
                 pw.println("mWifiLogProto.numForceScanDueToSteeringRequest="
                         + mWifiLogProto.numForceScanDueToSteeringRequest);
                 pw.println("mWifiLogProto.numMboCellularSwitchRequest="
@@ -3453,6 +3880,8 @@
                         + mWifiLogProto.numConnectRequestWithFilsAkm);
                 pw.println("mWifiLogProto.numL2ConnectionThroughFilsAuthentication="
                         + mWifiLogProto.numL2ConnectionThroughFilsAuthentication);
+                pw.println("mWifiLogProto.recentFailureAssociationStatus="
+                        + mRecentFailureAssociationStatus.toString());
 
                 pw.println("mWifiLogProto.numScans=" + mWifiLogProto.numScans);
                 pw.println("mWifiLogProto.WifiScoreCount: [" + MIN_WIFI_SCORE + ", "
@@ -3527,9 +3956,24 @@
                         + mInstalledPasspointProfileTypeForR2);
 
                 pw.println("mWifiLogProto.passpointProvisionStats.numProvisionSuccess="
-                            + mNumProvisionSuccess);
+                        + mNumProvisionSuccess);
                 pw.println("mWifiLogProto.passpointProvisionStats.provisionFailureCount:"
-                            + mPasspointProvisionFailureCounts);
+                        + mPasspointProvisionFailureCounts);
+                pw.println("mWifiLogProto.totalNumberOfPasspointConnectionsWithVenueUrl="
+                        + mWifiLogProto.totalNumberOfPasspointConnectionsWithVenueUrl);
+                pw.println(
+                        "mWifiLogProto.totalNumberOfPasspointConnectionsWithTermsAndConditionsUrl="
+                                + mWifiLogProto
+                                .totalNumberOfPasspointConnectionsWithTermsAndConditionsUrl);
+                pw.println(
+                        "mWifiLogProto"
+                                + ".totalNumberOfPasspointAcceptanceOfTermsAndConditions="
+                                + mWifiLogProto
+                                .totalNumberOfPasspointAcceptanceOfTermsAndConditions);
+                pw.println("mWifiLogProto.totalNumberOfPasspointProfilesWithDecoratedIdentity="
+                        + mWifiLogProto.totalNumberOfPasspointProfilesWithDecoratedIdentity);
+                pw.println("mWifiLogProto.passpointDeauthImminentScope="
+                        + mPasspointDeauthImminentScope.toString());
 
                 pw.println("mWifiLogProto.numRadioModeChangeToMcc="
                         + mWifiLogProto.numRadioModeChangeToMcc);
@@ -3601,6 +4045,20 @@
                         + mWifiLinkLayerUsageStats.radioPnoScanTimeMs);
                 pw.println("mWifiLinkLayerUsageStats.radioHs20ScanTimeMs="
                         + mWifiLinkLayerUsageStats.radioHs20ScanTimeMs);
+                pw.println("mWifiLinkLayerUsageStats per Radio Stats: ");
+                for (int i = 0; i < mRadioStats.size(); i++) {
+                    RadioStats radioStat = mRadioStats.valueAt(i);
+                    pw.println("radioId=" + radioStat.radioId);
+                    pw.println("totalRadioOnTimeMs=" + radioStat.totalRadioOnTimeMs);
+                    pw.println("totalRadioTxTimeMs=" + radioStat.totalRadioTxTimeMs);
+                    pw.println("totalRadioRxTimeMs=" + radioStat.totalRadioRxTimeMs);
+                    pw.println("totalScanTimeMs=" + radioStat.totalScanTimeMs);
+                    pw.println("totalNanScanTimeMs=" + radioStat.totalNanScanTimeMs);
+                    pw.println("totalBackgroundScanTimeMs=" + radioStat.totalBackgroundScanTimeMs);
+                    pw.println("totalRoamScanTimeMs=" + radioStat.totalRoamScanTimeMs);
+                    pw.println("totalPnoScanTimeMs=" + radioStat.totalPnoScanTimeMs);
+                    pw.println("totalHotspot2ScanTimeMs=" + radioStat.totalHotspot2ScanTimeMs);
+                }
 
                 pw.println("mWifiLogProto.connectToNetworkNotificationCount="
                         + mConnectToNetworkNotificationCount.toString());
@@ -3645,8 +4103,11 @@
                     eventLine.append("event_type=" + event.eventType);
                     eventLine.append(",time_stamp_millis=" + event.timeStampMillis);
                     eventLine.append(",num_connected_clients=" + event.numConnectedClients);
+                    eventLine.append(",num_connected_clients_on_current_frequency="
+                            + event.numConnectedClientsOnCurrentFrequency);
                     eventLine.append(",channel_frequency=" + event.channelFrequency);
                     eventLine.append(",channel_bandwidth=" + event.channelBandwidth);
+                    eventLine.append(",generation=" + event.generation);
                     eventLine.append(",max_num_clients_setting_in_softap_configuration="
                             + event.maxNumClientsSettingInSoftapConfiguration);
                     eventLine.append(",max_num_clients_setting_in_softap_capability="
@@ -3664,8 +4125,11 @@
                     eventLine.append("event_type=" + event.eventType);
                     eventLine.append(",time_stamp_millis=" + event.timeStampMillis);
                     eventLine.append(",num_connected_clients=" + event.numConnectedClients);
+                    eventLine.append(",num_connected_clients_on_current_frequency="
+                            + event.numConnectedClientsOnCurrentFrequency);
                     eventLine.append(",channel_frequency=" + event.channelFrequency);
                     eventLine.append(",channel_bandwidth=" + event.channelBandwidth);
+                    eventLine.append(",generation=" + event.generation);
                     eventLine.append(",max_num_clients_setting_in_softap_configuration="
                             + event.maxNumClientsSettingInSoftapConfiguration);
                     eventLine.append(",max_num_clients_setting_in_softap_capability="
@@ -3769,11 +4233,21 @@
                 pw.println("mWifiNetworkRequestApiLog:\n" + mWifiNetworkRequestApiLog);
                 pw.println("mWifiNetworkRequestApiMatchSizeHistogram:\n"
                         + mWifiNetworkRequestApiMatchSizeHistogram);
+                pw.println("mWifiNetworkRequestApiConnectionDurationSecOnPrimaryIfaceHistogram:\n"
+                        + mWifiNetworkRequestApiConnectionDurationSecOnPrimaryIfaceHistogram);
+                pw.println("mWifiNetworkRequestApiConnectionDurationSecOnSecondaryIfaceHistogram:\n"
+                        + mWifiNetworkRequestApiConnectionDurationSecOnSecondaryIfaceHistogram);
+                pw.println("mWifiNetworkRequestApiConcurrentConnectionDurationSecHistogram:\n"
+                        + mWifiNetworkRequestApiConcurrentConnectionDurationSecHistogram);
                 pw.println("mWifiNetworkSuggestionApiLog:\n" + mWifiNetworkSuggestionApiLog);
                 pw.println("mWifiNetworkSuggestionApiMatchSizeHistogram:\n"
                         + mWifiNetworkSuggestionApiListSizeHistogram);
                 pw.println("mWifiNetworkSuggestionApiAppTypeCounter:\n"
                         + mWifiNetworkSuggestionApiAppTypeCounter);
+                pw.println("mWifiNetworkSuggestionPriorityGroups:\n"
+                        + mWifiNetworkSuggestionPriorityGroups.toString());
+                pw.println("mWifiNetworkSuggestionCoexistSavedNetworks:\n"
+                        + mWifiNetworkSuggestionCoexistSavedNetworks.toString());
                 printUserApprovalSuggestionAppReaction(pw);
                 printUserApprovalCarrierReaction(pw);
                 pw.println("mNetworkIdToNominatorId:\n" + mNetworkIdToNominatorId);
@@ -3832,6 +4306,8 @@
                         + mRxThroughputMbpsHistogramAbove2G);
                 pw.println("mCarrierWifiMetrics:\n"
                         + mCarrierWifiMetrics);
+                pw.println(firstConnectAfterBootStatsToString(mFirstConnectAfterBootStats));
+                pw.println(wifiToWifiSwitchStatsToString(mWifiToWifiSwitchStats));
 
                 dumpInitPartialScanMetrics(pw);
             }
@@ -3855,6 +4331,20 @@
         line.append(",total_tx_retries=" + entry.totalTxRetries);
         line.append(",total_tx_bad=" + entry.totalTxBad);
         line.append(",total_rx_success=" + entry.totalRxSuccess);
+        if (entry.radioStats != null) {
+            for (RadioStats radioStat : entry.radioStats) {
+                line.append(",Radio Stats from radio_id=" + radioStat.radioId);
+                line.append(",radio_on_time_ms=" + radioStat.totalRadioOnTimeMs);
+                line.append(",radio_tx_time_ms=" + radioStat.totalRadioTxTimeMs);
+                line.append(",radio_rx_time_ms=" + radioStat.totalRadioRxTimeMs);
+                line.append(",scan_time_ms=" + radioStat.totalScanTimeMs);
+                line.append(",nan_scan_time_ms=" + radioStat.totalNanScanTimeMs);
+                line.append(",background_scan_time_ms=" + radioStat.totalBackgroundScanTimeMs);
+                line.append(",roam_scan_time_ms=" + radioStat.totalRoamScanTimeMs);
+                line.append(",pno_scan_time_ms=" + radioStat.totalPnoScanTimeMs);
+                line.append(",hotspot_2_scan_time_ms=" + radioStat.totalHotspot2ScanTimeMs);
+            }
+        }
         line.append(",total_radio_on_time_ms=" + entry.totalRadioOnTimeMs);
         line.append(",total_radio_tx_time_ms=" + entry.totalRadioTxTimeMs);
         line.append(",total_radio_rx_time_ms=" + entry.totalRadioRxTimeMs);
@@ -3879,6 +4369,35 @@
         line.append(",seq_num_inside_framework=" + entry.seqNumInsideFramework);
         line.append(",is_same_bssid_and_freq=" + entry.isSameBssidAndFreq);
         line.append(",device_mobility_state=" + entry.deviceMobilityState);
+        line.append(",time_slice_duty_cycle_in_percent=" + entry.timeSliceDutyCycleInPercent);
+        if (entry.contentionTimeStats != null) {
+            for (ContentionTimeStats stat : entry.contentionTimeStats) {
+                line.append(",access_category=" + stat.accessCategory);
+                line.append(",contention_time_min_micros=" + stat.contentionTimeMinMicros);
+                line.append(",contention_time_max_micros=" + stat.contentionTimeMaxMicros);
+                line.append(",contention_time_avg_micros=" + stat.contentionTimeAvgMicros);
+                line.append(",contention_num_samples=" + stat.contentionNumSamples);
+            }
+        }
+        line.append(",channel_utilization_ratio=" + entry.channelUtilizationRatio);
+        line.append(",is_throughput_sufficient=" + entry.isThroughputSufficient);
+        line.append(",is_wifi_scoring_enabled=" + entry.isWifiScoringEnabled);
+        line.append(",is_cellular_data_available=" + entry.isCellularDataAvailable);
+        line.append(",sta_count=" + entry.staCount);
+        line.append(",channel_utilization=" + entry.channelUtilization);
+        if (entry.rateStats != null) {
+            for (RateStats rateStat : entry.rateStats) {
+                line.append(",preamble=" + rateStat.preamble);
+                line.append(",nss=" + rateStat.nss);
+                line.append(",bw=" + rateStat.bw);
+                line.append(",rate_mcs_idx=" + rateStat.rateMcsIdx);
+                line.append(",bit_rate_in_kbps=" + rateStat.bitRateInKbps);
+                line.append(",tx_mpdu=" + rateStat.txMpdu);
+                line.append(",rx_mpdu=" + rateStat.rxMpdu);
+                line.append(",mpdu_lost=" + rateStat.mpduLost);
+                line.append(",retries=" + rateStat.retries);
+            }
+        }
         pw.println(line.toString());
     }
 
@@ -3928,27 +4447,29 @@
             mWifiLogProto.numPasspointNetworks = 0;
 
             for (WifiConfiguration config : networks) {
-                if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)) {
+                if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_OPEN)) {
                     mWifiLogProto.numOpenNetworks++;
-                } else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
+                } else if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_OWE)) {
                     mWifiLogProto.numEnhancedOpenNetworks++;
-                } else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WAPI_PSK)) {
+                } else if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_WAPI_PSK)) {
                     mWifiLogProto.numWapiPersonalNetworks++;
                 } else if (config.isEnterprise()) {
-                    if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SUITE_B_192)) {
+                    if (config.isSecurityType(
+                            WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT)) {
                         mWifiLogProto.numWpa3EnterpriseNetworks++;
-                    } else if (config.allowedKeyManagement.get(
-                            WifiConfiguration.KeyMgmt.WAPI_CERT)) {
+                    } else if (config.isSecurityType(
+                            WifiConfiguration.SECURITY_TYPE_WAPI_CERT)) {
                         mWifiLogProto.numWapiEnterpriseNetworks++;
                     } else {
                         mWifiLogProto.numLegacyEnterpriseNetworks++;
                     }
                 } else {
-                    if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
-                        mWifiLogProto.numWpa3PersonalNetworks++;
-                    } else {
+                    if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_PSK)) {
                         mWifiLogProto.numLegacyPersonalNetworks++;
                     }
+                    else if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE)) {
+                        mWifiLogProto.numWpa3PersonalNetworks++;
+                    }
                 }
                 mWifiLogProto.numNetworksAddedByApps++;
                 if (config.hiddenSSID) {
@@ -3957,7 +4478,7 @@
                 if (config.isPasspoint()) {
                     mWifiLogProto.numPasspointNetworks++;
                 }
-                if (config.macRandomizationSetting == WifiConfiguration.RANDOMIZATION_PERSISTENT) {
+                if (config.macRandomizationSetting != WifiConfiguration.RANDOMIZATION_NONE) {
                     mWifiLogProto.numSavedNetworksWithMacRandomization++;
                 }
             }
@@ -4064,16 +4585,14 @@
     private void consolidateProto() {
         List<WifiMetricsProto.RssiPollCount> rssis = new ArrayList<>();
         synchronized (mLock) {
-            int connectionEventCount = mConnectionEventList.size();
-            // Exclude the current active un-ended connection event
-            if (mCurrentConnectionEvent != null) {
-                connectionEventCount--;
-            }
-            mWifiLogProto.connectionEvent =
-                    new WifiMetricsProto.ConnectionEvent[connectionEventCount];
-            for (int i = 0; i < connectionEventCount; i++) {
-                mWifiLogProto.connectionEvent[i] = mConnectionEventList.get(i).mConnectionEvent;
-            }
+            mWifiLogProto.connectionEvent = mConnectionEventList
+                    .stream()
+                    // Exclude active un-ended connection events
+                    .filter(connectionEvent ->
+                            !mCurrentConnectionEventPerIface.containsValue(connectionEvent))
+                    // unwrap WifiMetrics.ConnectionEvent to get WifiMetricsProto.ConnectionEvent
+                    .map(connectionEvent -> connectionEvent.mConnectionEvent)
+                    .toArray(WifiMetricsProto.ConnectionEvent[]::new);
 
             //Convert the SparseIntArray of scanReturnEntry integers into ScanReturnEntry proto list
             mWifiLogProto.scanReturnEntries =
@@ -4235,6 +4754,11 @@
 
             mWifiLogProto.pnoScanMetrics = mPnoScanMetrics;
             mWifiLogProto.wifiLinkLayerUsageStats = mWifiLinkLayerUsageStats;
+            mWifiLogProto.wifiLinkLayerUsageStats.radioStats =
+                    new WifiMetricsProto.RadioStats[mRadioStats.size()];
+            for (int i = 0; i < mRadioStats.size(); i++) {
+                mWifiLogProto.wifiLinkLayerUsageStats.radioStats[i] = mRadioStats.valueAt(i);
+            }
 
             /**
              * Convert the SparseIntArray of "Connect to Network" notification types and counts to
@@ -4280,7 +4804,7 @@
 
             mWifiLogProto.connectToNetworkNotificationActionCount = notificationActionCountArray;
 
-            mWifiLogProto.openNetworkRecommenderBlacklistSize =
+            mWifiLogProto.openNetworkRecommenderBlocklistSize =
                     mOpenNetworkRecommenderBlocklistSize;
             mWifiLogProto.isWifiNetworksAvailableNotificationOn =
                     mIsWifiNetworksAvailableNotificationOn;
@@ -4412,6 +4936,12 @@
 
             mWifiNetworkRequestApiLog.networkMatchSizeHistogram =
                     mWifiNetworkRequestApiMatchSizeHistogram.toProto();
+            mWifiNetworkRequestApiLog.connectionDurationSecOnPrimaryIfaceHistogram =
+                    mWifiNetworkRequestApiConnectionDurationSecOnPrimaryIfaceHistogram.toProto();
+            mWifiNetworkRequestApiLog.connectionDurationSecOnSecondaryIfaceHistogram =
+                    mWifiNetworkRequestApiConnectionDurationSecOnSecondaryIfaceHistogram.toProto();
+            mWifiNetworkRequestApiLog.concurrentConnectionDurationSecHistogram =
+                    mWifiNetworkRequestApiConcurrentConnectionDurationSecHistogram.toProto();
             mWifiLogProto.wifiNetworkRequestApiLog = mWifiNetworkRequestApiLog;
 
             mWifiNetworkSuggestionApiLog.networkListSizeHistogram =
@@ -4424,6 +4954,10 @@
                                 entry.count = count;
                                 return entry;
                             });
+            mWifiNetworkSuggestionApiLog.numPriorityGroups =
+                    mWifiNetworkSuggestionPriorityGroups.size();
+            mWifiNetworkSuggestionApiLog.numSavedNetworksWithConfiguredSuggestion =
+                    mWifiNetworkSuggestionCoexistSavedNetworks.size();
             mWifiLogProto.wifiNetworkSuggestionApiLog = mWifiNetworkSuggestionApiLog;
 
             UserReactionToApprovalUiEvent events = new UserReactionToApprovalUiEvent();
@@ -4517,10 +5051,21 @@
             mWifiLogProto.initPartialScanStats = initialPartialScanStats;
             mWifiLogProto.carrierWifiMetrics = mCarrierWifiMetrics.toProto();
             mWifiLogProto.mainlineModuleVersion = mWifiHealthMonitor.getWifiStackVersion();
-
+            mWifiLogProto.firstConnectAfterBootStats = mFirstConnectAfterBootStats;
+            mWifiLogProto.wifiToWifiSwitchStats = buildWifiToWifiSwitchStats();
+            mWifiLogProto.bandwidthEstimatorStats = mWifiScoreCard.dumpBandwidthEstimatorStats();
+            mWifiLogProto.passpointDeauthImminentScope = mPasspointDeauthImminentScope.toProto();
+            mWifiLogProto.recentFailureAssociationStatus =
+                    mRecentFailureAssociationStatus.toProto();
         }
     }
 
+    private WifiToWifiSwitchStats buildWifiToWifiSwitchStats() {
+        mWifiToWifiSwitchStats.makeBeforeBreakLingerDurationSeconds =
+                mMakeBeforeBreakLingeringDurationSeconds.toProto();
+        return mWifiToWifiSwitchStats;
+    }
+
     private static int linkProbeFailureReasonToProto(int reason) {
         switch (reason) {
             case WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_MCS_UNSUPPORTED:
@@ -4611,9 +5156,9 @@
     private void clear() {
         synchronized (mLock) {
             mConnectionEventList.clear();
-            if (mCurrentConnectionEvent != null) {
-                mConnectionEventList.add(mCurrentConnectionEvent);
-            }
+            // Add in-progress events back
+            mConnectionEventList.addAll(mCurrentConnectionEventPerIface.values());
+
             mScanReturnEntries.clear();
             mWifiSystemStateEntries.clear();
             mRecordStartTimeSec = mClock.getElapsedSinceBootMillis() / 1000;
@@ -4635,6 +5180,7 @@
             mRxLinkSpeedCount6gMid.clear();
             mRxLinkSpeedCount6gHigh.clear();
             mWifiAlertReasonCounts.clear();
+            mMakeBeforeBreakLingeringDurationSeconds.clear();
             mWifiScoreCounts.clear();
             mWifiUsabilityScoreCounts.clear();
             mWifiLogProto.clear();
@@ -4656,6 +5202,7 @@
             mAvailableSavedPasspointProviderBssidsInScanHistogram.clear();
             mPnoScanMetrics.clear();
             mWifiLinkLayerUsageStats.clear();
+            mRadioStats.clear();
             mConnectToNetworkNotificationCount.clear();
             mConnectToNetworkNotificationActionCount.clear();
             mNumOpenNetworkRecommendationUpdates = 0;
@@ -4712,6 +5259,9 @@
             mNetworkSelectionExperimentPairNumChoicesCounts.clear();
             mWifiNetworkSuggestionApiLog.clear();
             mWifiNetworkRequestApiMatchSizeHistogram.clear();
+            mWifiNetworkRequestApiConnectionDurationSecOnPrimaryIfaceHistogram.clear();
+            mWifiNetworkRequestApiConnectionDurationSecOnSecondaryIfaceHistogram.clear();
+            mWifiNetworkRequestApiConcurrentConnectionDurationSecHistogram.clear();
             mWifiNetworkSuggestionApiListSizeHistogram.clear();
             mWifiNetworkSuggestionApiAppTypeCounter.clear();
             mUserApprovalSuggestionAppUiReactionList.clear();
@@ -4742,28 +5292,41 @@
             mInitPartialScanSuccessHistogram.clear();
             mInitPartialScanFailureHistogram.clear();
             mCarrierWifiMetrics.clear();
+            mFirstConnectAfterBootStats = null;
+            mWifiToWifiSwitchStats.clear();
+            mPasspointDeauthImminentScope.clear();
+            mRecentFailureAssociationStatus.clear();
+            mWifiNetworkSuggestionPriorityGroups.clear();
+            mWifiNetworkSuggestionCoexistSavedNetworks.clear();
         }
     }
 
     /**
      *  Set screen state (On/Off)
      */
-    public void setScreenState(boolean screenOn) {
+    private void setScreenState(boolean screenOn) {
         synchronized (mLock) {
             mScreenOn = screenOn;
         }
     }
 
+    private boolean isPrimary(String ifaceName) {
+        return mIfaceToRoleMap.get(ifaceName) == ActiveModeManager.ROLE_CLIENT_PRIMARY;
+    }
+
     /**
      *  Set wifi state (WIFI_UNKNOWN, WIFI_DISABLED, WIFI_DISCONNECTED, WIFI_ASSOCIATED)
      */
-    public void setWifiState(int wifiState) {
+    public void setWifiState(String ifaceName, int wifiState) {
         synchronized (mLock) {
             mWifiState = wifiState;
-            mWifiWins = (wifiState == WifiMetricsProto.WifiLog.WIFI_ASSOCIATED);
-            mWifiWinsUsabilityScore = (wifiState == WifiMetricsProto.WifiLog.WIFI_ASSOCIATED);
-            if (wifiState == WifiMetricsProto.WifiLog.WIFI_DISCONNECTED
-                    || wifiState == WifiMetricsProto.WifiLog.WIFI_DISABLED) {
+            // set wifi priority over setting when any STA gets connected.
+            if (wifiState == WifiMetricsProto.WifiLog.WIFI_ASSOCIATED) {
+                mWifiWins = true;
+                mWifiWinsUsabilityScore = true;
+            }
+            if (isPrimary(ifaceName) && (wifiState == WifiMetricsProto.WifiLog.WIFI_DISCONNECTED
+                    || wifiState == WifiMetricsProto.WifiLog.WIFI_DISABLED)) {
                 mWifiStatusBuilder = new WifiStatusBuilder();
             }
         }
@@ -4773,13 +5336,16 @@
      * Message handler for interesting WifiMonitor messages. Generates StaEvents
      */
     private void processMessage(Message msg) {
+        String ifaceName = msg.getData().getString(WifiMonitor.KEY_IFACE);
+
         StaEvent event = new StaEvent();
         boolean logEvent = true;
         switch (msg.what) {
             case WifiMonitor.ASSOCIATION_REJECTION_EVENT:
                 event.type = StaEvent.TYPE_ASSOCIATION_REJECTION_EVENT;
-                event.associationTimedOut = msg.arg1 > 0 ? true : false;
-                event.status = msg.arg2;
+                AssocRejectEventInfo assocRejectEventInfo = (AssocRejectEventInfo) msg.obj;
+                event.associationTimedOut = assocRejectEventInfo.timedOut;
+                event.status = assocRejectEventInfo.statusCode;
                 break;
             case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
                 event.type = StaEvent.TYPE_AUTHENTICATION_FAILURE_EVENT;
@@ -4805,8 +5371,9 @@
                 break;
             case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
                 event.type = StaEvent.TYPE_NETWORK_DISCONNECTION_EVENT;
-                event.reason = msg.arg2;
-                event.localGen = msg.arg1 == 0 ? false : true;
+                DisconnectEventInfo disconnectEventInfo = (DisconnectEventInfo) msg.obj;
+                event.reason = disconnectEventInfo.reasonCode;
+                event.localGen = disconnectEventInfo.locallyGenerated;
                 break;
             case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
                 logEvent = false;
@@ -4823,7 +5390,7 @@
                 return;
         }
         if (logEvent) {
-            addStaEvent(event);
+            addStaEvent(ifaceName, event);
         }
     }
     /**
@@ -4831,8 +5398,8 @@
      * generated event types, which are logged through 'sendMessage'
      * @param type StaEvent.EventType describing the event
      */
-    public void logStaEvent(int type) {
-        logStaEvent(type, StaEvent.DISCONNECT_UNKNOWN, null);
+    public void logStaEvent(String ifaceName, int type) {
+        logStaEvent(ifaceName, type, StaEvent.DISCONNECT_UNKNOWN, null);
     }
     /**
      * Log a StaEvent from ClientModeImpl. The StaEvent must not be one of the supplicant
@@ -4840,8 +5407,8 @@
      * @param type StaEvent.EventType describing the event
      * @param config WifiConfiguration for a framework initiated connection attempt
      */
-    public void logStaEvent(int type, WifiConfiguration config) {
-        logStaEvent(type, StaEvent.DISCONNECT_UNKNOWN, config);
+    public void logStaEvent(String ifaceName, int type, WifiConfiguration config) {
+        logStaEvent(ifaceName, type, StaEvent.DISCONNECT_UNKNOWN, config);
     }
     /**
      * Log a StaEvent from ClientModeImpl. The StaEvent must not be one of the supplicant
@@ -4850,8 +5417,8 @@
      * @param frameworkDisconnectReason StaEvent.FrameworkDisconnectReason explaining why framework
      *                                  initiated a FRAMEWORK_DISCONNECT
      */
-    public void logStaEvent(int type, int frameworkDisconnectReason) {
-        logStaEvent(type, frameworkDisconnectReason, null);
+    public void logStaEvent(String ifaceName, int type, int frameworkDisconnectReason) {
+        logStaEvent(ifaceName, type, frameworkDisconnectReason, null);
     }
     /**
      * Log a StaEvent from ClientModeImpl. The StaEvent must not be one of the supplicant
@@ -4861,7 +5428,8 @@
      *                                  initiated a FRAMEWORK_DISCONNECT
      * @param config WifiConfiguration for a framework initiated connection attempt
      */
-    public void logStaEvent(int type, int frameworkDisconnectReason, WifiConfiguration config) {
+    public void logStaEvent(String ifaceName, int type, int frameworkDisconnectReason,
+            WifiConfiguration config) {
         switch (type) {
             case StaEvent.TYPE_CMD_IP_CONFIGURATION_SUCCESSFUL:
             case StaEvent.TYPE_CMD_IP_CONFIGURATION_LOST:
@@ -4888,10 +5456,17 @@
             event.frameworkDisconnectReason = frameworkDisconnectReason;
         }
         event.configInfo = createConfigInfo(config);
-        addStaEvent(event);
+        addStaEvent(ifaceName, event);
     }
 
-    private void addStaEvent(StaEvent staEvent) {
+    private void addStaEvent(String ifaceName, StaEvent staEvent) {
+        // Nano proto runtime will throw a NPE during serialization if interfaceName is null
+        if (ifaceName == null) {
+            Log.wtf(TAG, "Null StaEvent.ifaceName: " + staEventToString(staEvent));
+            return;
+        }
+        staEvent.interfaceName = ifaceName;
+        staEvent.interfaceRole = convertIfaceToEnum(ifaceName);
         staEvent.startTimeMillis = mClock.getElapsedSinceBootMillis();
         staEvent.lastRssi = mLastPollRssi;
         staEvent.lastFreq = mLastPollFreq;
@@ -4944,8 +5519,26 @@
         return info;
     }
 
-    public Handler getHandler() {
-        return mHandler;
+    private static final int[] WIFI_MONITOR_EVENTS = {
+            WifiMonitor.ASSOCIATION_REJECTION_EVENT,
+            WifiMonitor.AUTHENTICATION_FAILURE_EVENT,
+            WifiMonitor.NETWORK_CONNECTION_EVENT,
+            WifiMonitor.NETWORK_DISCONNECTION_EVENT,
+            WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT,
+            WifiMonitor.ASSOCIATED_BSSID_EVENT,
+            WifiMonitor.TARGET_BSSID_EVENT,
+    };
+
+    public void registerForWifiMonitorEvents(String ifaceName) {
+        for (int event : WIFI_MONITOR_EVENTS) {
+            mWifiMonitor.registerHandler(ifaceName, event, mHandler);
+        }
+    }
+
+    public void deregisterForWifiMonitorEvents(String ifaceName) {
+        for (int event : WIFI_MONITOR_EVENTS) {
+            mWifiMonitor.deregisterHandler(ifaceName, event, mHandler);
+        }
     }
 
     public WifiAwareMetrics getWifiAwareMetrics() {
@@ -5119,10 +5712,6 @@
             sb.append(" lastWifiUsabilityScore=").append(event.lastWifiUsabilityScore);
             sb.append(" lastPredictionHorizonSec=").append(event.lastPredictionHorizonSec);
         }
-        if (event.mobileTxBytes > 0) sb.append(" mobileTxBytes=").append(event.mobileTxBytes);
-        if (event.mobileRxBytes > 0) sb.append(" mobileRxBytes=").append(event.mobileRxBytes);
-        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);
         sb.append(" adaptiveConnectivity=").append(event.isAdaptiveConnectivityEnabled);
@@ -5133,10 +5722,48 @@
         if (event.configInfo != null) {
             sb.append(", ").append(configInfoToString(event.configInfo));
         }
-
+        if (event.mobileTxBytes > 0) sb.append(" mobileTxBytes=").append(event.mobileTxBytes);
+        if (event.mobileRxBytes > 0) sb.append(" mobileRxBytes=").append(event.mobileRxBytes);
+        if (event.totalTxBytes > 0) sb.append(" totalTxBytes=").append(event.totalTxBytes);
+        if (event.totalRxBytes > 0) sb.append(" totalRxBytes=").append(event.totalRxBytes);
+        sb.append(" interfaceName=").append(event.interfaceName);
+        sb.append(" interfaceRole=").append(clientRoleEnumToString(event.interfaceRole));
         return sb.toString();
     }
 
+    private int convertIfaceToEnum(String ifaceName) {
+        ActiveModeManager.ClientRole role = mIfaceToRoleMap.get(ifaceName);
+        if (role == ActiveModeManager.ROLE_CLIENT_SCAN_ONLY) {
+            return WifiMetricsProto.ROLE_CLIENT_SCAN_ONLY;
+        } else if (role == ActiveModeManager.ROLE_CLIENT_SECONDARY_TRANSIENT) {
+            return WifiMetricsProto.ROLE_CLIENT_SECONDARY_TRANSIENT;
+        } else if (role == ActiveModeManager.ROLE_CLIENT_LOCAL_ONLY) {
+            return WifiMetricsProto.ROLE_CLIENT_LOCAL_ONLY;
+        } else if (role == ActiveModeManager.ROLE_CLIENT_PRIMARY) {
+            return WifiMetricsProto.ROLE_CLIENT_PRIMARY;
+        } else if (role == ActiveModeManager.ROLE_CLIENT_SECONDARY_LONG_LIVED) {
+            return WifiMetricsProto.ROLE_CLIENT_SECONDARY_LONG_LIVED;
+        }
+        return WifiMetricsProto.ROLE_UNKNOWN;
+    }
+
+    private static String clientRoleEnumToString(int role) {
+        switch (role) {
+            case WifiMetricsProto.ROLE_CLIENT_SCAN_ONLY:
+                return "ROLE_CLIENT_SCAN_ONLY";
+            case WifiMetricsProto.ROLE_CLIENT_SECONDARY_TRANSIENT:
+                return "ROLE_CLIENT_SECONDARY_TRANSIENT";
+            case WifiMetricsProto.ROLE_CLIENT_LOCAL_ONLY:
+                return "ROLE_CLIENT_LOCAL_ONLY";
+            case WifiMetricsProto.ROLE_CLIENT_PRIMARY:
+                return "ROLE_CLIENT_PRIMARY";
+            case WifiMetricsProto.ROLE_CLIENT_SECONDARY_LONG_LIVED:
+                return "ROLE_CLIENT_SECONDARY_LONG_LIVED";
+            default:
+                return "ROLE_UNKNOWN";
+        }
+    }
+
     private static String authFailureReasonToString(int authFailureReason) {
         switch (authFailureReason) {
             case StaEvent.AUTH_FAILURE_NONE:
@@ -5166,6 +5793,24 @@
                 return "DISCONNECT_P2P_DISCONNECT_WIFI_REQUEST";
             case StaEvent.DISCONNECT_RESET_SIM_NETWORKS:
                 return "DISCONNECT_RESET_SIM_NETWORKS";
+            case StaEvent.DISCONNECT_MBB_NO_INTERNET:
+                return "DISCONNECT_MBB_NO_INTERNET";
+            case StaEvent.DISCONNECT_NETWORK_REMOVED:
+                return "DISCONNECT_NETWORK_REMOVED";
+            case StaEvent.DISCONNECT_NETWORK_METERED:
+                return "DISCONNECT_NETWORK_METERED";
+            case StaEvent.DISCONNECT_NETWORK_TEMPORARY_DISABLED:
+                return "DISCONNECT_NETWORK_TEMPORARY_DISABLED";
+            case StaEvent.DISCONNECT_NETWORK_PERMANENT_DISABLED:
+                return "DISCONNECT_NETWORK_PERMANENT_DISABLED";
+            case StaEvent.DISCONNECT_CARRIER_OFFLOAD_DISABLED:
+                return "DISCONNECT_CARRIER_OFFLOAD_DISABLED";
+            case StaEvent.DISCONNECT_PASSPOINT_TAC:
+                return "DISCONNECT_PASSPOINT_TAC";
+            case StaEvent.DISCONNECT_VCN_REQUEST:
+                return "DISCONNECT_VCN_REQUEST";
+            case StaEvent.DISCONNECT_UNKNOWN_NETWORK:
+                return "DISCONNECT_UNKNOWN_NETWORK";
             default:
                 return "DISCONNECT_UNKNOWN=" + frameworkDisconnectReason;
         }
@@ -5370,7 +6015,7 @@
             meteredDetail.isMeteredOverrideSet = config.meteredOverride
                     != WifiConfiguration.METERED_OVERRIDE_NONE;
             meteredDetail.isFromSuggestion = config.fromWifiNetworkSuggestion;
-            mNetworkMap.put(config.getKey(), meteredDetail);
+            mNetworkMap.put(config.getProfileKey(), meteredDetail);
         }
 
         void clear() {
@@ -5488,17 +6133,22 @@
     /**
      * Log a WifiIsUnusableEvent
      * @param triggerType WifiIsUnusableEvent.type describing the event
+     * @param ifaceName name of the interface.
      */
-    public void logWifiIsUnusableEvent(int triggerType) {
-        logWifiIsUnusableEvent(triggerType, -1);
+    public void logWifiIsUnusableEvent(String ifaceName, int triggerType) {
+        logWifiIsUnusableEvent(ifaceName, triggerType, -1);
     }
 
     /**
      * Log a WifiIsUnusableEvent
      * @param triggerType WifiIsUnusableEvent.type describing the event
      * @param firmwareAlertCode WifiIsUnusableEvent.firmwareAlertCode for firmware alert code
+     * @param ifaceName name of the interface.
      */
-    public void logWifiIsUnusableEvent(int triggerType, int firmwareAlertCode) {
+    public void logWifiIsUnusableEvent(String ifaceName, int triggerType, int firmwareAlertCode) {
+        if (!isPrimary(ifaceName)) {
+            return;
+        }
         mScoreBreachLowTimeMillis = -1;
         if (!mContext.getResources().getBoolean(R.bool.config_wifiIsUnusableEventMetricsEnabled)) {
             return;
@@ -5556,8 +6206,12 @@
      * into an internal ring buffer.
      * @param info
      * @param stats
+     * @param ifaceName
      */
-    public void updateWifiUsabilityStatsEntries(WifiInfo info, WifiLinkLayerStats stats) {
+    public void updateWifiUsabilityStatsEntries(String ifaceName, WifiInfo info,
+            WifiLinkLayerStats stats) {
+        // This is only collected for primary STA currently because RSSI polling is disabled for
+        // non-primary STAs.
         synchronized (mLock) {
             if (info == null) {
                 return;
@@ -5584,6 +6238,27 @@
                     + stats.lostmpdu_vi + stats.lostmpdu_vo;
             wifiUsabilityStatsEntry.totalRxSuccess = stats.rxmpdu_be + stats.rxmpdu_bk
                     + stats.rxmpdu_vi + stats.rxmpdu_vo;
+            /* Update per radio stats */
+            if (stats.radioStats != null && stats.radioStats.length > 0) {
+                int numRadios = stats.radioStats.length;
+                wifiUsabilityStatsEntry.radioStats =
+                        new RadioStats[numRadios];
+                for (int i = 0; i < numRadios; i++) {
+                    RadioStats radioStats = new RadioStats();
+                    WifiLinkLayerStats.RadioStat radio = stats.radioStats[i];
+                    radioStats.radioId = radio.radio_id;
+                    radioStats.totalRadioOnTimeMs = radio.on_time;
+                    radioStats.totalRadioTxTimeMs = radio.tx_time;
+                    radioStats.totalRadioRxTimeMs = radio.rx_time;
+                    radioStats.totalScanTimeMs = radio.on_time_scan;
+                    radioStats.totalNanScanTimeMs = radio.on_time_nan_scan;
+                    radioStats.totalBackgroundScanTimeMs = radio.on_time_background_scan;
+                    radioStats.totalRoamScanTimeMs = radio.on_time_roam_scan;
+                    radioStats.totalPnoScanTimeMs = radio.on_time_pno_scan;
+                    radioStats.totalHotspot2ScanTimeMs = radio.on_time_hs20_scan;
+                    wifiUsabilityStatsEntry.radioStats[i] = radioStats;
+                }
+            }
             wifiUsabilityStatsEntry.totalRadioOnTimeMs = stats.on_time;
             wifiUsabilityStatsEntry.totalRadioTxTimeMs = stats.tx_time;
             wifiUsabilityStatsEntry.totalRadioRxTimeMs = stats.rx_time;
@@ -5602,6 +6277,7 @@
                 wifiUsabilityStatsEntry.totalCcaBusyFreqTimeMs = statsMap.ccaBusyTimeMs;
             }
             wifiUsabilityStatsEntry.totalBeaconRx = stats.beacon_rx;
+            wifiUsabilityStatsEntry.timeSliceDutyCycleInPercent = stats.timeSliceDutyCycleInPercent;
 
             boolean isSameBssidAndFreq = mLastBssid == null || mLastFrequency == -1
                     || (mLastBssid.equals(info.getBSSID())
@@ -5637,11 +6313,107 @@
             wifiUsabilityStatsEntry.isSameBssidAndFreq = isSameBssidAndFreq;
             wifiUsabilityStatsEntry.seqNumInsideFramework = mSeqNumInsideFramework;
             wifiUsabilityStatsEntry.deviceMobilityState = mCurrentDeviceMobilityState;
+            wifiUsabilityStatsEntry.contentionTimeStats =
+                    new ContentionTimeStats[NUM_WME_ACCESS_CATEGORIES];
+            for (int ac = 0; ac < NUM_WME_ACCESS_CATEGORIES; ac++) {
+                ContentionTimeStats contentionTimeStats = new ContentionTimeStats();
+                switch (ac) {
+                    case ContentionTimeStats.WME_ACCESS_CATEGORY_BE:
+                        contentionTimeStats.accessCategory =
+                                ContentionTimeStats.WME_ACCESS_CATEGORY_BE;
+                        contentionTimeStats.contentionTimeMinMicros =
+                                stats.contentionTimeMinBeInUsec;
+                        contentionTimeStats.contentionTimeMaxMicros =
+                                stats.contentionTimeMaxBeInUsec;
+                        contentionTimeStats.contentionTimeAvgMicros =
+                                stats.contentionTimeAvgBeInUsec;
+                        contentionTimeStats.contentionNumSamples =
+                                stats.contentionNumSamplesBe;
+                        break;
+                    case ContentionTimeStats.WME_ACCESS_CATEGORY_BK:
+                        contentionTimeStats.accessCategory =
+                                ContentionTimeStats.WME_ACCESS_CATEGORY_BK;
+                        contentionTimeStats.contentionTimeMinMicros =
+                                stats.contentionTimeMinBkInUsec;
+                        contentionTimeStats.contentionTimeMaxMicros =
+                                stats.contentionTimeMaxBkInUsec;
+                        contentionTimeStats.contentionTimeAvgMicros =
+                                stats.contentionTimeAvgBkInUsec;
+                        contentionTimeStats.contentionNumSamples =
+                                stats.contentionNumSamplesBk;
+                        break;
+                    case ContentionTimeStats.WME_ACCESS_CATEGORY_VI:
+                        contentionTimeStats.accessCategory =
+                                ContentionTimeStats.WME_ACCESS_CATEGORY_VI;
+                        contentionTimeStats.contentionTimeMinMicros =
+                                stats.contentionTimeMinViInUsec;
+                        contentionTimeStats.contentionTimeMaxMicros =
+                                stats.contentionTimeMaxViInUsec;
+                        contentionTimeStats.contentionTimeAvgMicros =
+                                stats.contentionTimeAvgViInUsec;
+                        contentionTimeStats.contentionNumSamples =
+                                stats.contentionNumSamplesVi;
+                        break;
+                    case ContentionTimeStats.WME_ACCESS_CATEGORY_VO:
+                        contentionTimeStats.accessCategory =
+                                ContentionTimeStats.WME_ACCESS_CATEGORY_VO;
+                        contentionTimeStats.contentionTimeMinMicros =
+                                stats.contentionTimeMinVoInUsec;
+                        contentionTimeStats.contentionTimeMaxMicros =
+                                stats.contentionTimeMaxVoInUsec;
+                        contentionTimeStats.contentionTimeAvgMicros =
+                                stats.contentionTimeAvgVoInUsec;
+                        contentionTimeStats.contentionNumSamples =
+                                stats.contentionNumSamplesVo;
+                        break;
+                    default:
+                        Log.e(TAG, "Unknown WME Access Category: " + ac);
+                }
+                wifiUsabilityStatsEntry.contentionTimeStats[ac] = contentionTimeStats;
+            }
+            if (mWifiChannelUtilization != null) {
+                wifiUsabilityStatsEntry.channelUtilizationRatio =
+                        mWifiChannelUtilization.getUtilizationRatio(mLastFrequency);
+            }
+            if (mWifiDataStall != null) {
+                wifiUsabilityStatsEntry.isThroughputSufficient =
+                        mWifiDataStall.isThroughputSufficient();
+                wifiUsabilityStatsEntry.isCellularDataAvailable =
+                        mWifiDataStall.isCellularDataAvailable();
+            }
+            if (mWifiSettingsStore != null) {
+                wifiUsabilityStatsEntry.isWifiScoringEnabled =
+                        mWifiSettingsStore.isWifiScoringEnabled();
+            }
+            // Here it is assumed there is only one peer information from HAL and the peer is the
+            // AP that STA is associated with.
+            if (stats.peerInfo != null && stats.peerInfo.length > 0
+                    && stats.peerInfo[0].rateStats != null) {
+                wifiUsabilityStatsEntry.staCount = stats.peerInfo[0].staCount;
+                wifiUsabilityStatsEntry.channelUtilization = stats.peerInfo[0].chanUtil;
+                int numRates = stats.peerInfo[0].rateStats != null
+                        ? stats.peerInfo[0].rateStats.length : 0;
+                wifiUsabilityStatsEntry.rateStats = new RateStats[numRates];
+                for (int i = 0; i < numRates; i++) {
+                    RateStats rate = new RateStats();
+                    WifiLinkLayerStats.RateStat curRate = stats.peerInfo[0].rateStats[i];
+                    rate.preamble = curRate.preamble;
+                    rate.nss = curRate.nss;
+                    rate.bw = curRate.bw;
+                    rate.rateMcsIdx = curRate.rateMcsIdx;
+                    rate.bitRateInKbps = curRate.bitRateInKbps;
+                    rate.txMpdu = curRate.txMpdu;
+                    rate.rxMpdu = curRate.rxMpdu;
+                    rate.mpduLost = curRate.mpduLost;
+                    rate.retries = curRate.retries;
+                    wifiUsabilityStatsEntry.rateStats[i] = rate;
+                }
+            }
 
             mWifiUsabilityStatsEntriesList.add(wifiUsabilityStatsEntry);
             mWifiUsabilityStatsCounter++;
             if (mWifiUsabilityStatsCounter >= NUM_WIFI_USABILITY_STATS_ENTRIES_PER_WIFI_GOOD) {
-                addToWifiUsabilityStatsList(WifiUsabilityStats.LABEL_GOOD,
+                addToWifiUsabilityStatsList(ifaceName, WifiUsabilityStats.LABEL_GOOD,
                         WifiUsabilityStats.TYPE_UNKNOWN, -1);
             }
             if (mScoreBreachLowTimeMillis != -1) {
@@ -5649,13 +6421,15 @@
                 if (elapsedTime >= MIN_SCORE_BREACH_TO_GOOD_STATS_WAIT_TIME_MS) {
                     mScoreBreachLowTimeMillis = -1;
                     if (elapsedTime <= VALIDITY_PERIOD_OF_SCORE_BREACH_LOW_MS) {
-                        addToWifiUsabilityStatsList(WifiUsabilityStats.LABEL_GOOD,
+                        addToWifiUsabilityStatsList(ifaceName, WifiUsabilityStats.LABEL_GOOD,
                                 WifiUsabilityStats.TYPE_UNKNOWN, -1);
                     }
                 }
             }
 
             // Invoke Wifi usability stats listener.
+            // TODO(b/179518316): Enable this for secondary transient STA also if external scorer
+            // is in charge of MBB.
             sendWifiUsabilityStats(mSeqNumInsideFramework, isSameBssidAndFreq,
                     createNewWifiUsabilityStatsEntryParcelable(wifiUsabilityStatsEntry));
 
@@ -5675,14 +6449,16 @@
      */
     private void sendWifiUsabilityStats(int seqNum, boolean isSameBssidAndFreq,
             android.net.wifi.WifiUsabilityStatsEntry statsEntry) {
-        for (IOnWifiUsabilityStatsListener listener : mOnWifiUsabilityListeners.getCallbacks()) {
+        int itemCount = mOnWifiUsabilityListeners.beginBroadcast();
+        for (int i = 0; i < itemCount; i++) {
             try {
-                listener.onWifiUsabilityStats(seqNum, isSameBssidAndFreq, statsEntry);
+                mOnWifiUsabilityListeners.getBroadcastItem(i).onWifiUsabilityStats(seqNum,
+                        isSameBssidAndFreq, statsEntry);
             } catch (RemoteException e) {
-                Log.e(TAG, "Unable to invoke Wifi usability stats entry listener "
-                        + listener, e);
+                Log.e(TAG, "Unable to invoke Wifi usability stats entry listener ", e);
             }
         }
+        mOnWifiUsabilityListeners.finishBroadcast();
     }
 
     private android.net.wifi.WifiUsabilityStatsEntry createNewWifiUsabilityStatsEntryParcelable(
@@ -5702,6 +6478,18 @@
                 probeStatus = android.net.wifi.WifiUsabilityStatsEntry.PROBE_STATUS_UNKNOWN;
                 Log.e(TAG, "Unknown link probe status: " + s.probeStatusSinceLastUpdate);
         }
+        android.net.wifi.WifiUsabilityStatsEntry.ContentionTimeStats[] contentionTimeStats =
+                new android.net.wifi.WifiUsabilityStatsEntry.ContentionTimeStats[
+                        android.net.wifi.WifiUsabilityStatsEntry.NUM_WME_ACCESS_CATEGORIES];
+        createNewContentionTimeStatsParcelable(contentionTimeStats, s.contentionTimeStats);
+        int numRates = s.rateStats != null ? s.rateStats.length : 0;
+        android.net.wifi.WifiUsabilityStatsEntry.RateStats[] rateStats =
+                new android.net.wifi.WifiUsabilityStatsEntry.RateStats[numRates];
+        createNewRateStatsParcelable(rateStats, s.rateStats);
+        int numRadios = s.radioStats != null ? s.radioStats.length : 0;
+        android.net.wifi.WifiUsabilityStatsEntry.RadioStats[] radioStats =
+                new android.net.wifi.WifiUsabilityStatsEntry.RadioStats[numRadios];
+        createNewRadioStatsParcelable(radioStats, s.radioStats);
         // TODO: remove the following hardcoded values once if they are removed from public API
         return new android.net.wifi.WifiUsabilityStatsEntry(s.timeStampMs, s.rssi,
                 s.linkSpeedMbps, s.totalTxSuccess, s.totalTxRetries,
@@ -5711,10 +6499,152 @@
                 s.totalPnoScanTimeMs, s.totalHotspot2ScanTimeMs, s.totalCcaBusyFreqTimeMs,
                 s.totalRadioOnFreqTimeMs, s.totalBeaconRx, probeStatus,
                 s.probeElapsedTimeSinceLastUpdateMs, s.probeMcsRateSinceLastUpdate,
-                s.rxLinkSpeedMbps, 0, 0, 0, false
+                s.rxLinkSpeedMbps, s.timeSliceDutyCycleInPercent, contentionTimeStats, rateStats,
+                radioStats, s.channelUtilizationRatio, s.isThroughputSufficient,
+                s.isWifiScoringEnabled, s.isCellularDataAvailable, 0, 0, 0, false
         );
     }
 
+    private void createNewContentionTimeStatsParcelable(
+            android.net.wifi.WifiUsabilityStatsEntry.ContentionTimeStats[] statsParcelable,
+                    ContentionTimeStats[] stats) {
+        if (statsParcelable.length != stats.length || stats.length != NUM_WME_ACCESS_CATEGORIES) {
+            Log.e(TAG, "The two ContentionTimeStats do not match in length: "
+                    + " in proto: " + stats.length
+                    + " in system API: " + statsParcelable.length);
+            return;
+        }
+        for (int ac = 0; ac < NUM_WME_ACCESS_CATEGORIES; ac++) {
+            android.net.wifi.WifiUsabilityStatsEntry.ContentionTimeStats stat =
+                    new android.net.wifi.WifiUsabilityStatsEntry.ContentionTimeStats(
+                            stats[ac].contentionTimeMinMicros,
+                            stats[ac].contentionTimeMaxMicros,
+                            stats[ac].contentionTimeAvgMicros,
+                            stats[ac].contentionNumSamples);
+            switch (ac) {
+                case ContentionTimeStats.WME_ACCESS_CATEGORY_BE:
+                    statsParcelable[
+                            android.net.wifi.WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_BE] = stat;
+                    break;
+                case ContentionTimeStats.WME_ACCESS_CATEGORY_BK:
+                    statsParcelable[
+                            android.net.wifi.WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_BK] = stat;
+                    break;
+                case ContentionTimeStats.WME_ACCESS_CATEGORY_VI:
+                    statsParcelable[
+                            android.net.wifi.WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_VI] = stat;
+                    break;
+                case ContentionTimeStats.WME_ACCESS_CATEGORY_VO:
+                    statsParcelable[
+                            android.net.wifi.WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_VO] = stat;
+                    break;
+                default:
+                    Log.e(TAG, "Unknown WME Access Category: " + ac);
+            }
+        }
+    }
+
+    private void createNewRateStatsParcelable(
+            android.net.wifi.WifiUsabilityStatsEntry.RateStats[] statsParcelable,
+                    RateStats[] stats) {
+        if (stats == null) {
+            return;
+        }
+        for (int i = 0; i < stats.length; i++) {
+            statsParcelable[i] = new android.net.wifi.WifiUsabilityStatsEntry.RateStats(
+                    convertPreambleTypeEnumToUsabilityStatsType(stats[i].preamble),
+                    convertSpatialStreamEnumToUsabilityStatsType(stats[i].nss),
+                    convertBandwidthEnumToUsabilityStatsType(stats[i].bw),
+                    stats[i].rateMcsIdx, stats[i].bitRateInKbps, stats[i].txMpdu, stats[i].rxMpdu,
+                    stats[i].mpduLost, stats[i].retries
+            );
+        }
+    }
+
+    /**
+     * Converts bandwidth enum in proto to WifiUsabilityStatsEntry type.
+     * @param value
+     */
+    private static int convertBandwidthEnumToUsabilityStatsType(int value) {
+        switch (value) {
+            case RateStats.WIFI_BANDWIDTH_20_MHZ:
+                return android.net.wifi.WifiUsabilityStatsEntry.WIFI_BANDWIDTH_20_MHZ;
+            case RateStats.WIFI_BANDWIDTH_40_MHZ:
+                return android.net.wifi.WifiUsabilityStatsEntry.WIFI_BANDWIDTH_40_MHZ;
+            case RateStats.WIFI_BANDWIDTH_80_MHZ:
+                return android.net.wifi.WifiUsabilityStatsEntry.WIFI_BANDWIDTH_80_MHZ;
+            case RateStats.WIFI_BANDWIDTH_160_MHZ:
+                return android.net.wifi.WifiUsabilityStatsEntry.WIFI_BANDWIDTH_160_MHZ;
+            case RateStats.WIFI_BANDWIDTH_80P80_MHZ:
+                return android.net.wifi.WifiUsabilityStatsEntry.WIFI_BANDWIDTH_80P80_MHZ;
+            case RateStats.WIFI_BANDWIDTH_5_MHZ:
+                return android.net.wifi.WifiUsabilityStatsEntry.WIFI_BANDWIDTH_5_MHZ;
+            case RateStats.WIFI_BANDWIDTH_10_MHZ:
+                return android.net.wifi.WifiUsabilityStatsEntry.WIFI_BANDWIDTH_10_MHZ;
+        }
+        return android.net.wifi.WifiUsabilityStatsEntry.WIFI_BANDWIDTH_INVALID;
+    }
+
+    /**
+     * Converts spatial streams enum in proto to WifiUsabilityStatsEntry type.
+     * @param value
+     */
+    private static int convertSpatialStreamEnumToUsabilityStatsType(int value) {
+        switch (value) {
+            case RateStats.WIFI_SPATIAL_STREAMS_ONE:
+                return android.net.wifi.WifiUsabilityStatsEntry.WIFI_SPATIAL_STREAMS_ONE;
+            case RateStats.WIFI_SPATIAL_STREAMS_TWO:
+                return android.net.wifi.WifiUsabilityStatsEntry.WIFI_SPATIAL_STREAMS_TWO;
+            case RateStats.WIFI_SPATIAL_STREAMS_THREE:
+                return android.net.wifi.WifiUsabilityStatsEntry.WIFI_SPATIAL_STREAMS_THREE;
+            case RateStats.WIFI_SPATIAL_STREAMS_FOUR:
+                return android.net.wifi.WifiUsabilityStatsEntry.WIFI_SPATIAL_STREAMS_FOUR;
+        }
+        return android.net.wifi.WifiUsabilityStatsEntry.WIFI_SPATIAL_STREAMS_INVALID;
+    }
+
+    /**
+     * Converts preamble type enum in proto to WifiUsabilityStatsEntry type.
+     * @param value
+     */
+    private static int convertPreambleTypeEnumToUsabilityStatsType(int value) {
+        switch (value) {
+            case RateStats.WIFI_PREAMBLE_OFDM:
+                return android.net.wifi.WifiUsabilityStatsEntry.WIFI_PREAMBLE_OFDM;
+            case RateStats.WIFI_PREAMBLE_CCK:
+                return android.net.wifi.WifiUsabilityStatsEntry.WIFI_PREAMBLE_CCK;
+            case RateStats.WIFI_PREAMBLE_HT:
+                return android.net.wifi.WifiUsabilityStatsEntry.WIFI_PREAMBLE_HT;
+            case RateStats.WIFI_PREAMBLE_VHT:
+                return android.net.wifi.WifiUsabilityStatsEntry.WIFI_PREAMBLE_VHT;
+            case RateStats.WIFI_PREAMBLE_HE:
+                return android.net.wifi.WifiUsabilityStatsEntry.WIFI_PREAMBLE_HE;
+        }
+        return android.net.wifi.WifiUsabilityStatsEntry.WIFI_PREAMBLE_INVALID;
+    }
+
+    private void createNewRadioStatsParcelable(
+            android.net.wifi.WifiUsabilityStatsEntry.RadioStats[] statsParcelable,
+            RadioStats[] stats) {
+        if (stats == null) {
+            return;
+        }
+        for (int i = 0; i < stats.length; i++) {
+            statsParcelable[i] =
+                    new android.net.wifi.WifiUsabilityStatsEntry.RadioStats(
+                            stats[i].radioId,
+                            stats[i].totalRadioOnTimeMs,
+                            stats[i].totalRadioTxTimeMs,
+                            stats[i].totalRadioRxTimeMs,
+                            stats[i].totalScanTimeMs,
+                            stats[i].totalNanScanTimeMs,
+                            stats[i].totalBackgroundScanTimeMs,
+                            stats[i].totalRoamScanTimeMs,
+                            stats[i].totalPnoScanTimeMs,
+                            stats[i].totalHotspot2ScanTimeMs);
+        }
+    }
+
     private WifiUsabilityStatsEntry createNewWifiUsabilityStatsEntry(WifiUsabilityStatsEntry s) {
         WifiUsabilityStatsEntry out = new WifiUsabilityStatsEntry();
         out.timeStampMs = s.timeStampMs;
@@ -5747,6 +6677,16 @@
         out.isSameBssidAndFreq = s.isSameBssidAndFreq;
         out.seqNumInsideFramework = s.seqNumInsideFramework;
         out.deviceMobilityState = s.deviceMobilityState;
+        out.timeSliceDutyCycleInPercent = s.timeSliceDutyCycleInPercent;
+        out.contentionTimeStats = s.contentionTimeStats;
+        out.channelUtilizationRatio = s.channelUtilizationRatio;
+        out.isThroughputSufficient = s.isThroughputSufficient;
+        out.isWifiScoringEnabled = s.isWifiScoringEnabled;
+        out.isCellularDataAvailable = s.isCellularDataAvailable;
+        out.rateStats = s.rateStats;
+        out.staCount = s.staCount;
+        out.channelUtilization = s.channelUtilization;
+        out.radioStats = s.radioStats;
         return out;
     }
 
@@ -5773,8 +6713,12 @@
      * @param firmwareAlertCode the firmware alert code when the stats was triggered by a
      *        firmware alert
      */
-    public void addToWifiUsabilityStatsList(int label, int triggerType, int firmwareAlertCode) {
+    public void addToWifiUsabilityStatsList(String ifaceName, int label, int triggerType,
+            int firmwareAlertCode) {
         synchronized (mLock) {
+            if (!isPrimary(ifaceName)) {
+                return;
+            }
             if (mWifiUsabilityStatsEntriesList.isEmpty() || !mScreenOn) {
                 return;
             }
@@ -5914,28 +6858,40 @@
     }
 
     /**
+     * Logs that wifi bug report is taken
+     */
+    public void logBugReport() {
+        synchronized (mLock) {
+            for (ConnectionEvent connectionEvent : mCurrentConnectionEventPerIface.values()) {
+                if (connectionEvent != null) {
+                    connectionEvent.mConnectionEvent.automaticBugReportTaken = true;
+                }
+            }
+        }
+    }
+
+    /**
      * Add a new listener for Wi-Fi usability stats handling.
      */
-    public void addOnWifiUsabilityListener(IBinder binder, IOnWifiUsabilityStatsListener listener,
-            int listenerIdentifier) {
-        if (!mOnWifiUsabilityListeners.add(binder, listener, listenerIdentifier)) {
+    public void addOnWifiUsabilityListener(IOnWifiUsabilityStatsListener listener) {
+        if (!mOnWifiUsabilityListeners.register(listener)) {
             Log.e(TAG, "Failed to add listener");
             return;
         }
         if (DBG) {
             Log.v(TAG, "Adding listener. Num listeners: "
-                    + mOnWifiUsabilityListeners.getNumCallbacks());
+                    + mOnWifiUsabilityListeners.getRegisteredCallbackCount());
         }
     }
 
     /**
      * Remove an existing listener for Wi-Fi usability stats handling.
      */
-    public void removeOnWifiUsabilityListener(int listenerIdentifier) {
-        mOnWifiUsabilityListeners.remove(listenerIdentifier);
+    public void removeOnWifiUsabilityListener(IOnWifiUsabilityStatsListener listener) {
+        mOnWifiUsabilityListeners.unregister(listener);
         if (DBG) {
             Log.v(TAG, "Removing listener. Num listeners: "
-                    + mOnWifiUsabilityListeners.getNumCallbacks());
+                    + mOnWifiUsabilityListeners.getRegisteredCallbackCount());
         }
     }
 
@@ -5950,7 +6906,8 @@
      * @param score The Wi-Fi usability score.
      * @param predictionHorizonSec Prediction horizon of the Wi-Fi usability score.
      */
-    public void incrementWifiUsabilityScoreCount(int seqNum, int score, int predictionHorizonSec) {
+    public void incrementWifiUsabilityScoreCount(String ifaceName, int seqNum, int score,
+            int predictionHorizonSec) {
         if (score < MIN_WIFI_USABILITY_SCORE || score > MAX_WIFI_USABILITY_SCORE) {
             return;
         }
@@ -5973,7 +6930,7 @@
                 mWifiWinsUsabilityScore = wifiWins;
                 StaEvent event = new StaEvent();
                 event.type = StaEvent.TYPE_WIFI_USABILITY_SCORE_BREACH;
-                addStaEvent(event);
+                addStaEvent(ifaceName, event);
                 // Only record the first score breach by checking whether mScoreBreachLowTimeMillis
                 // has been set to -1
                 if (!wifiWins && mScoreBreachLowTimeMillis == -1) {
@@ -5996,7 +6953,7 @@
      *                      probe was ACKed. Note: this number should be correlated with the number
      *                      of retries that the driver attempted before the probe was ACKed.
      */
-    public void logLinkProbeSuccess(long timeSinceLastTxSuccessMs,
+    public void logLinkProbeSuccess(String ifaceName, long timeSinceLastTxSuccessMs,
             int rssi, int linkSpeed, int elapsedTimeMs) {
         synchronized (mLock) {
             mProbeStatusSinceLastUpdate =
@@ -6014,7 +6971,7 @@
                 event.type = StaEvent.TYPE_LINK_PROBE;
                 event.linkProbeWasSuccess = true;
                 event.linkProbeSuccessElapsedTimeMs = elapsedTimeMs;
-                addStaEvent(event);
+                addStaEvent(ifaceName, event);
             }
             mLinkProbeStaEventCount++;
         }
@@ -6031,7 +6988,7 @@
      * @param reason The error code for the failure. See
      * {@link WifiNl80211Manager.SendMgmtFrameError}.
      */
-    public void logLinkProbeFailure(long timeSinceLastTxSuccessMs,
+    public void logLinkProbeFailure(String ifaceName, long timeSinceLastTxSuccessMs,
             int rssi, int linkSpeed, int reason) {
         synchronized (mLock) {
             mProbeStatusSinceLastUpdate =
@@ -6049,7 +7006,7 @@
                 event.type = StaEvent.TYPE_LINK_PROBE;
                 event.linkProbeWasSuccess = false;
                 event.linkProbeFailureReason = linkProbeFailureReasonToProto(reason);
-                addStaEvent(event);
+                addStaEvent(ifaceName, event);
             }
             mLinkProbeStaEventCount++;
         }
@@ -6137,10 +7094,10 @@
         }
     }
 
-    /** Increment number of connection success via network request API */
-    public void incrementNetworkRequestApiNumConnectSuccess() {
+    /** Increment number of connection success on primary iface via network request API */
+    public void incrementNetworkRequestApiNumConnectSuccessOnPrimaryIface() {
         synchronized (mLock) {
-            mWifiNetworkRequestApiLog.numConnectSuccess++;
+            mWifiNetworkRequestApiLog.numConnectSuccessOnPrimaryIface++;
         }
     }
 
@@ -6165,6 +7122,61 @@
         }
     }
 
+    /** Add to the network request API connection duration histogram */
+    public void incrementNetworkRequestApiConnectionDurationSecOnPrimaryIfaceHistogram(
+            int durationSec) {
+        synchronized (mLock) {
+            mWifiNetworkRequestApiConnectionDurationSecOnPrimaryIfaceHistogram.increment(
+                    durationSec);
+        }
+    }
+
+    /** Add to the network request API connection duration on secondary iface histogram */
+    public void incrementNetworkRequestApiConnectionDurationSecOnSecondaryIfaceHistogram(
+            int durationSec) {
+        synchronized (mLock) {
+            mWifiNetworkRequestApiConnectionDurationSecOnSecondaryIfaceHistogram.increment(
+                    durationSec);
+        }
+    }
+
+    /** Increment number of connection on primary iface via network request API */
+    public void incrementNetworkRequestApiNumConnectOnPrimaryIface() {
+        synchronized (mLock) {
+            mWifiNetworkRequestApiLog.numConnectOnPrimaryIface++;
+        }
+    }
+
+    /** Increment number of connection on secondary iface via network request API */
+    public void incrementNetworkRequestApiNumConnectOnSecondaryIface() {
+        synchronized (mLock) {
+            mWifiNetworkRequestApiLog.numConnectOnSecondaryIface++;
+        }
+    }
+
+    /** Increment number of connection success on secondary iface via network request API */
+    public void incrementNetworkRequestApiNumConnectSuccessOnSecondaryIface() {
+        synchronized (mLock) {
+            mWifiNetworkRequestApiLog.numConnectSuccessOnSecondaryIface++;
+        }
+    }
+
+    /** Increment number of concurrent connection via network request API */
+    public void incrementNetworkRequestApiNumConcurrentConnection() {
+        synchronized (mLock) {
+            mWifiNetworkRequestApiLog.numConcurrentConnection++;
+        }
+    }
+
+    /** Add to the network request API concurrent connection duration histogram */
+    public void incrementNetworkRequestApiConcurrentConnectionDurationSecHistogram(
+            int durationSec) {
+        synchronized (mLock) {
+            mWifiNetworkRequestApiConcurrentConnectionDurationSecHistogram.increment(
+                    durationSec);
+        }
+    }
+
     /** Increment number of network suggestion API modification by app stats */
     public void incrementNetworkSuggestionApiNumModification() {
         synchronized (mLock) {
@@ -6195,6 +7207,38 @@
         }
     }
 
+    /**
+     * Increment number of times a ScanResult matches more than one WifiNetworkSuggestion.
+     */
+    public void incrementNetworkSuggestionMoreThanOneSuggestionForSingleScanResult() {
+        synchronized (mLock) {
+            mWifiNetworkSuggestionApiLog.numMultipleSuggestions++;
+        }
+    }
+
+    /**
+     * Add a saved network which has at least has one suggestion for same network on the device.
+     */
+    public void addSuggestionExistsForSavedNetwork(String key) {
+        synchronized (mLock) {
+            mWifiNetworkSuggestionCoexistSavedNetworks.add(key);
+        }
+    }
+
+    /**
+     * Add a priority group which is using on the device.(Except default priority group).
+     */
+    public void addNetworkSuggestionPriorityGroup(int priorityGroup) {
+        synchronized (mLock) {
+            // Ignore the default group
+            if (priorityGroup == 0) {
+                return;
+            }
+            mWifiNetworkSuggestionPriorityGroups.put(priorityGroup, true);
+        }
+
+    }
+
     /** Clear and set the latest network suggestion API max list size histogram */
     public void noteNetworkSuggestionApiListSizeHistogram(List<Integer> listSizes) {
         synchronized (mLock) {
@@ -6288,7 +7332,7 @@
             if (networkId == WifiConfiguration.INVALID_NETWORK_ID) return;
             mNetworkIdToNominatorId.put(networkId, nominatorId);
 
-            // user connect choice is preventing switcing off from the connected network
+            // user connect choice is preventing switching off from the connected network
             if (nominatorId
                     == WifiMetricsProto.ConnectionEvent.NOMINATOR_SAVED_USER_CONNECT_CHOICE
                     && mWifiStatusBuilder.getNetworkId() == networkId) {
@@ -6377,7 +7421,7 @@
      * Increment number of passpoint provision failure
      * @param failureCode indicates error condition
      */
-    public void incrementPasspointProvisionFailure(int failureCode) {
+    public void incrementPasspointProvisionFailure(@OsuFailure int failureCode) {
         int provisionFailureCode;
         synchronized (mLock) {
             switch (failureCode) {
@@ -6497,6 +7541,22 @@
     }
 
     /**
+     * Increment the number of times BSSIDs are blocked per reason.
+     * @param blockReason one of {@link WifiBlocklistMonitor.FailureReason}
+     */
+    public void incrementBssidBlocklistCount(int blockReason) {
+        mBssidBlocklistStats.incrementBssidBlocklistCount(blockReason);
+    }
+
+    /**
+     * Increment the number of times WifiConfigurations are blocked per reason.
+     * @param blockReason one of {@Link NetworkSelectionStatus.NetworkSelectionDisableReason}
+     */
+    public void incrementWificonfigurationBlocklistCount(int blockReason) {
+        mBssidBlocklistStats.incrementWificonfigurationBlocklistCount(blockReason);
+    }
+
+    /**
      * Increment number of passpoint provision success
      */
     public void incrementPasspointProvisionSuccess() {
@@ -6575,7 +7635,11 @@
             boolean isThroughputSufficient, boolean isCellularDataAvailable) {
         synchronized (mLock) {
             mConnectionDurationStats.incrementDurationCount(timeDeltaLastTwoPollsMs,
-                    isThroughputSufficient, isCellularDataAvailable);
+                    isThroughputSufficient, isCellularDataAvailable, mWifiWins);
+
+            int band = KnownBandsChannelHelper.getBand(mLastPollFreq);
+            WifiStatsLog.write(WifiStatsLog.WIFI_HEALTH_STAT_REPORTED, timeDeltaLastTwoPollsMs,
+                    isThroughputSufficient || !mWifiWins,  isCellularDataAvailable, band);
         }
     }
 
@@ -6615,6 +7679,15 @@
     }
 
     /**
+     * Increment number of times BSS transition management request frame is received from the AP.
+     */
+    public void incrementSteeringRequestCount() {
+        synchronized (mLock) {
+            mWifiLogProto.numSteeringRequest++;
+        }
+    }
+
+    /**
      * Increment number of times force scan is triggered due to a
      * BSS transition management request frame from AP.
      */
@@ -6733,4 +7806,547 @@
             mAdaptiveConnectivityEnabled = adaptiveConnectivityEnabled;
         }
     }
+
+    /**
+     * Get total beacon receive count
+     */
+    public long getTotalBeaconRxCount() {
+        if (mWifiUsabilityStatsEntriesList.isEmpty()) {
+            return -1;
+        } else {
+            return mWifiUsabilityStatsEntriesList.getLast().totalBeaconRx;
+        }
+    }
+
+    /** Note whether Wifi was enabled at boot time. */
+    public void noteWifiEnabledDuringBoot(boolean isWifiEnabled) {
+        synchronized (mLock) {
+            if (mIsFirstConnectionAttemptComplete
+                    || mFirstConnectAfterBootStats == null
+                    || mFirstConnectAfterBootStats.wifiEnabledAtBoot != null) {
+                return;
+            }
+            Attempt wifiEnabledAtBoot = new Attempt();
+            wifiEnabledAtBoot.isSuccess = isWifiEnabled;
+            wifiEnabledAtBoot.timestampSinceBootMillis = mClock.getElapsedSinceBootMillis();
+            mFirstConnectAfterBootStats.wifiEnabledAtBoot = wifiEnabledAtBoot;
+            if (!isWifiEnabled) {
+                mIsFirstConnectionAttemptComplete = true;
+            }
+        }
+    }
+
+    /** Note the first network selection after boot. */
+    public void noteFirstNetworkSelectionAfterBoot(boolean wasAnyCandidatesFound) {
+        synchronized (mLock) {
+            if (mIsFirstConnectionAttemptComplete
+                    || mFirstConnectAfterBootStats == null
+                    || mFirstConnectAfterBootStats.firstNetworkSelection != null) {
+                return;
+            }
+            Attempt firstNetworkSelection = new Attempt();
+            firstNetworkSelection.isSuccess = wasAnyCandidatesFound;
+            firstNetworkSelection.timestampSinceBootMillis = mClock.getElapsedSinceBootMillis();
+            mFirstConnectAfterBootStats.firstNetworkSelection = firstNetworkSelection;
+            if (!wasAnyCandidatesFound) {
+                mIsFirstConnectionAttemptComplete = true;
+            }
+        }
+    }
+
+    /** Note the first L2 connection after boot. */
+    public void noteFirstL2ConnectionAfterBoot(boolean wasConnectionSuccessful) {
+        synchronized (mLock) {
+            if (mIsFirstConnectionAttemptComplete
+                    || mFirstConnectAfterBootStats == null
+                    || mFirstConnectAfterBootStats.firstL2Connection != null) {
+                return;
+            }
+            Attempt firstL2Connection = new Attempt();
+            firstL2Connection.isSuccess = wasConnectionSuccessful;
+            firstL2Connection.timestampSinceBootMillis = mClock.getElapsedSinceBootMillis();
+            mFirstConnectAfterBootStats.firstL2Connection = firstL2Connection;
+            if (!wasConnectionSuccessful) {
+                mIsFirstConnectionAttemptComplete = true;
+            }
+        }
+    }
+
+    /** Note the first L3 connection after boot. */
+    public void noteFirstL3ConnectionAfterBoot(boolean wasConnectionSuccessful) {
+        synchronized (mLock) {
+            if (mIsFirstConnectionAttemptComplete
+                    || mFirstConnectAfterBootStats == null
+                    || mFirstConnectAfterBootStats.firstL3Connection != null) {
+                return;
+            }
+            Attempt firstL3Connection = new Attempt();
+            firstL3Connection.isSuccess = wasConnectionSuccessful;
+            firstL3Connection.timestampSinceBootMillis = mClock.getElapsedSinceBootMillis();
+            mFirstConnectAfterBootStats.firstL3Connection = firstL3Connection;
+            if (!wasConnectionSuccessful) {
+                mIsFirstConnectionAttemptComplete = true;
+            }
+        }
+    }
+
+    private static String attemptToString(@Nullable Attempt attempt) {
+        if (attempt == null) return "Attempt=null";
+        return "Attempt{"
+                + "timestampSinceBootMillis=" + attempt.timestampSinceBootMillis
+                + ",isSuccess=" + attempt.isSuccess
+                + "}";
+    }
+
+    private static String firstConnectAfterBootStatsToString(
+            @Nullable FirstConnectAfterBootStats stats) {
+        if (stats == null) return "FirstConnectAfterBootStats=null";
+        return "FirstConnectAfterBootStats{"
+                + "wifiEnabledAtBoot=" + attemptToString(stats.wifiEnabledAtBoot)
+                + ",firstNetworkSelection" + attemptToString(stats.firstNetworkSelection)
+                + ",firstL2Connection" + attemptToString(stats.firstL2Connection)
+                + ",firstL3Connection" + attemptToString(stats.firstL3Connection)
+                + "}";
+    }
+
+    public ScanMetrics getScanMetrics() {
+        return mScanMetrics;
+    }
+
+    public enum ScanType { SINGLE, BACKGROUND }
+
+    public enum PnoScanState { STARTED, FAILED_TO_START, COMPLETED_NETWORK_FOUND, FAILED }
+
+    /**
+     * This class reports Scan metrics to statsd and holds intermediate scan request state.
+     */
+    public static class ScanMetrics {
+        private static final String TAG_SCANS = "ScanMetrics";
+        private static final String GMS_PACKAGE = "com.google.android.gms";
+
+        // Scan types.
+        public static final int SCAN_TYPE_SINGLE = 0;
+        public static final int SCAN_TYPE_BACKGROUND = 1;
+        public static final int SCAN_TYPE_MAX_VALUE = SCAN_TYPE_BACKGROUND;
+        @IntDef(prefix = { "SCAN_TYPE_" }, value = {
+                SCAN_TYPE_SINGLE,
+                SCAN_TYPE_BACKGROUND,
+        })
+        public @interface ScanType {}
+
+        // PNO scan states.
+        public static final int PNO_SCAN_STATE_STARTED = 1;
+        public static final int PNO_SCAN_STATE_FAILED_TO_START = 2;
+        public static final int PNO_SCAN_STATE_COMPLETED_NETWORK_FOUND = 3;
+        public static final int PNO_SCAN_STATE_FAILED = 4;
+        @IntDef(prefix = { "PNO_SCAN_STATE_" }, value = {
+                PNO_SCAN_STATE_STARTED,
+                PNO_SCAN_STATE_FAILED_TO_START,
+                PNO_SCAN_STATE_COMPLETED_NETWORK_FOUND,
+                PNO_SCAN_STATE_FAILED
+        })
+        public @interface PnoScanState {}
+
+        private final Object mLock = new Object();
+        private Clock mClock;
+
+        private List<String> mSettingsPackages = new ArrayList<>();
+        private int mGmsUid = -1;
+
+        // mNextScanState collects metadata about the next scan that's about to happen.
+        // It is mutated by external callers via setX methods before the call to logScanStarted.
+        private State mNextScanState = new State();
+        // mActiveScanState is an immutable copy of mNextScanState during the scan process,
+        // i.e. between logScanStarted and logScanSucceeded/Failed. Since the state is pushed to
+        // statsd only when a scan ends, it's important to keep the immutable copy
+        // for the duration of the scan.
+        private State[] mActiveScanStates = new State[SCAN_TYPE_MAX_VALUE + 1];
+
+        ScanMetrics(Context context, Clock clock) {
+            mClock = clock;
+
+            PackageManager pm = context.getPackageManager();
+            if (pm != null) {
+                Intent settingsIntent = new Intent(Settings.ACTION_SETTINGS);
+                List<ResolveInfo> packages = pm.queryIntentActivities(settingsIntent, 0);
+                for (ResolveInfo res : packages) {
+                    String packageName = res.activityInfo.packageName;
+                    Log.d(TAG_SCANS, "Settings package: " + packageName);
+                    mSettingsPackages.add(packageName);
+                }
+            }
+
+            try {
+                mGmsUid = context.getPackageManager().getApplicationInfo(GMS_PACKAGE, 0).uid;
+                Log.d(TAG_SCANS, "GMS uid: " + mGmsUid);
+            } catch (Exception e) {
+                Log.e(TAG_SCANS, "Can't get GMS uid");
+            }
+        }
+
+        /**
+         * Set WorkSource for the upcoming scan request.
+         *
+         * @param workSource
+         */
+        public void setWorkSource(WorkSource workSource) {
+            synchronized (mLock) {
+                if (mNextScanState.mWorkSource == null) {
+                    mNextScanState.mWorkSource = workSource;
+                    if (DBG) Log.d(TAG_SCANS, "setWorkSource: workSource = " + workSource);
+                }
+            }
+        }
+
+        /**
+         * Set ClientUid for the upcoming scan request.
+         *
+         * @param uid
+         */
+        public void setClientUid(int uid) {
+            synchronized (mLock) {
+                mNextScanState.mClientUid = uid;
+
+                if (DBG) Log.d(TAG_SCANS, "setClientUid: uid = " + uid);
+            }
+        }
+
+        /**
+         * Set Importance for the upcoming scan request.
+         *
+         * @param packageImportance See {@link ActivityManager.RunningAppProcessInfo.Importance}
+         */
+        public void setImportance(int packageImportance) {
+            synchronized (mLock) {
+                mNextScanState.mPackageImportance = packageImportance;
+
+                if (DBG) {
+                    Log.d(TAG_SCANS,
+                            "setRequestFromBackground: packageImportance = " + packageImportance);
+                }
+            }
+        }
+
+        /**
+         * Indicate that a scan started.
+         * @param scanType See {@link ScanMetrics.ScanType}
+         */
+        public void logScanStarted(@ScanType int scanType) {
+            synchronized (mLock) {
+                if (DBG) Log.d(TAG_SCANS, "logScanStarted");
+
+                mNextScanState.mTimeStartMillis = mClock.getElapsedSinceBootMillis();
+                mActiveScanStates[scanType] = mNextScanState;
+                mNextScanState = new State();
+            }
+        }
+
+        /**
+         * Indicate that a scan failed to start.
+         * @param scanType See {@link ScanMetrics.ScanType}
+         */
+        public void logScanFailedToStart(@ScanType int scanType) {
+            synchronized (mLock) {
+                Log.d(TAG_SCANS, "logScanFailedToStart");
+
+                mNextScanState.mTimeStartMillis = mClock.getElapsedSinceBootMillis();
+                mActiveScanStates[scanType] = mNextScanState;
+                mNextScanState = new State();
+
+                log(scanType, WifiStatsLog.WIFI_SCAN_REPORTED__RESULT__RESULT_FAILED_TO_START, 0);
+                mActiveScanStates[scanType] = null;
+            }
+        }
+
+        /**
+         * Indicate that a scan finished successfully.
+         * @param scanType See {@link ScanMetrics.ScanType}
+         * @param countOfNetworksFound How many networks were found.
+         */
+        public void logScanSucceeded(@ScanType int scanType, int countOfNetworksFound) {
+            synchronized (mLock) {
+                if (DBG) Log.d(TAG_SCANS, "logScanSucceeded: found = " + countOfNetworksFound);
+
+                log(scanType, WifiStatsLog.WIFI_SCAN_REPORTED__RESULT__RESULT_SUCCESS,
+                        countOfNetworksFound);
+                mActiveScanStates[scanType] = null;
+            }
+        }
+
+        /**
+         * Log a PNO scan event: start/finish/fail.
+         * @param pnoScanState See {@link PnoScanState}
+         */
+        public void logPnoScanEvent(@PnoScanState int pnoScanState) {
+            synchronized (mLock) {
+                int state = 0;
+
+                switch (pnoScanState) {
+                    case PNO_SCAN_STATE_STARTED:
+                        state = WifiStatsLog.WIFI_PNO_SCAN_REPORTED__STATE__STARTED;
+                        break;
+                    case PNO_SCAN_STATE_FAILED_TO_START:
+                        state = WifiStatsLog.WIFI_PNO_SCAN_REPORTED__STATE__FAILED_TO_START;
+                        break;
+                    case PNO_SCAN_STATE_COMPLETED_NETWORK_FOUND:
+                        state = WifiStatsLog.WIFI_PNO_SCAN_REPORTED__STATE__FINISHED_NETWORKS_FOUND;
+                        break;
+                    case PNO_SCAN_STATE_FAILED:
+                        state = WifiStatsLog.WIFI_PNO_SCAN_REPORTED__STATE__FAILED;
+                        break;
+                }
+
+                WifiStatsLog.write(WifiStatsLog.WIFI_PNO_SCAN_REPORTED, state);
+
+                if (DBG) Log.d(TAG_SCANS, "logPnoScanEvent: pnoScanState = " + pnoScanState);
+            }
+        }
+
+        /**
+         * Indicate that a scan failed.
+         */
+        public void logScanFailed(@ScanType int scanType) {
+            synchronized (mLock) {
+                if (DBG) Log.d(TAG_SCANS, "logScanFailed");
+
+                log(scanType, WifiStatsLog.WIFI_SCAN_REPORTED__RESULT__RESULT_FAILED_TO_SCAN, 0);
+                mActiveScanStates[scanType] = null;
+            }
+        }
+
+        private void log(@ScanType int scanType, int result, int countNetworks) {
+            State state = mActiveScanStates[scanType];
+
+            if (state == null) {
+                if (DBG) Log.e(TAG_SCANS, "Wifi scan result log called with no prior start calls!");
+                return;
+            }
+
+            int type = WifiStatsLog.WIFI_SCAN_REPORTED__TYPE__TYPE_UNKNOWN;
+            if (scanType == SCAN_TYPE_SINGLE) {
+                type = WifiStatsLog.WIFI_SCAN_REPORTED__TYPE__TYPE_SINGLE;
+            } else if (scanType == SCAN_TYPE_BACKGROUND) {
+                type = WifiStatsLog.WIFI_SCAN_REPORTED__TYPE__TYPE_BACKGROUND;
+            }
+
+            long duration = mClock.getElapsedSinceBootMillis() - state.mTimeStartMillis;
+
+            int source = WifiStatsLog.WIFI_SCAN_REPORTED__SOURCE__SOURCE_NO_WORK_SOURCE;
+            if (state.mClientUid != -1 && state.mClientUid == mGmsUid) {
+                source = WifiStatsLog.WIFI_SCAN_REPORTED__SOURCE__SOURCE_GMS;
+            } else if (state.mWorkSource != null) {
+                if (state.mWorkSource.equals(ClientModeImpl.WIFI_WORK_SOURCE)) {
+                    source = WifiStatsLog.WIFI_SCAN_REPORTED__SOURCE__SOURCE_WIFI_STACK;
+                } else {
+                    source = WifiStatsLog.WIFI_SCAN_REPORTED__SOURCE__SOURCE_OTHER_APP;
+
+                    for (int i = 0; i < state.mWorkSource.size(); i++) {
+                        if (mSettingsPackages.contains(
+                                state.mWorkSource.getPackageName(i))) {
+                            source = WifiStatsLog.WIFI_SCAN_REPORTED__SOURCE__SOURCE_SETTINGS_APP;
+                            break;
+                        }
+                    }
+                }
+            }
+
+            int importance = WifiStatsLog.WIFI_SCAN_REPORTED__IMPORTANCE__IMPORTANCE_UNKNOWN;
+            if (state.mPackageImportance != -1) {
+                if (state.mPackageImportance
+                        <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
+                    importance = WifiStatsLog.WIFI_SCAN_REPORTED__IMPORTANCE__IMPORTANCE_FOREGROUND;
+                } else if (state.mPackageImportance
+                        <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE) {
+                    importance =
+                            WifiStatsLog.WIFI_SCAN_REPORTED__IMPORTANCE__IMPORTANCE_FOREGROUND_SERVICE;
+                } else {
+                    importance = WifiStatsLog.WIFI_SCAN_REPORTED__IMPORTANCE__IMPORTANCE_BACKGROUND;
+                }
+            }
+
+            WifiStatsLog.write(WifiStatsLog.WIFI_SCAN_REPORTED,
+                    type,
+                    result,
+                    source,
+                    importance,
+                    (int) duration,
+                    countNetworks);
+
+            if (DBG) {
+                Log.d(TAG_SCANS,
+                        "WifiScanReported: type = " + type
+                                + ", result = " + result
+                                + ", source = " + source
+                                + ", importance = " + importance
+                                + ", networks = " + countNetworks);
+            }
+        }
+
+        static class State {
+            WorkSource mWorkSource = null;
+            int mClientUid = -1;
+            // see @ActivityManager.RunningAppProcessInfo.Importance
+            int mPackageImportance = -1;
+
+            long mTimeStartMillis;
+        }
+    }
+
+    /** Set whether Make Before Break is supported by the hardware and enabled. */
+    public void setIsMakeBeforeBreakSupported(boolean supported) {
+        synchronized (mLock) {
+            mWifiToWifiSwitchStats.isMakeBeforeBreakSupported = supported;
+        }
+    }
+
+    /**
+     * Increment the number of times Wifi to Wifi switch was triggered. This includes Make Before
+     * Break and Break Before Make.
+     */
+    public void incrementWifiToWifiSwitchTriggerCount() {
+        synchronized (mLock) {
+            mWifiToWifiSwitchStats.wifiToWifiSwitchTriggerCount++;
+        }
+    }
+
+    /**
+     * Increment the Number of times Wifi to Wifi switch was triggered using Make Before Break
+     * (MBB). Note that MBB may not always be used for various reasons e.g. no additional iface
+     * available due to ongoing SoftAP, both old and new network have MAC randomization disabled,
+     * etc.
+     */
+    public void incrementMakeBeforeBreakTriggerCount() {
+        synchronized (mLock) {
+            mWifiToWifiSwitchStats.makeBeforeBreakTriggerCount++;
+        }
+    }
+
+    /**
+     * Increment the number of times Make Before Break was aborted due to the new network not having
+     * internet.
+     */
+    public void incrementMakeBeforeBreakNoInternetCount() {
+        synchronized (mLock) {
+            mWifiToWifiSwitchStats.makeBeforeBreakNoInternetCount++;
+        }
+    }
+
+    /**
+     * Increment the number of times where, for some reason, Make Before Break resulted in the
+     * loss of the primary ClientModeManager, and we needed to recover by making one of the
+     * SECONDARY_TRANSIENT ClientModeManagers primary.
+     */
+    public void incrementMakeBeforeBreakRecoverPrimaryCount() {
+        synchronized (mLock) {
+            mWifiToWifiSwitchStats.makeBeforeBreakRecoverPrimaryCount++;
+        }
+    }
+
+    /**
+     * Increment the number of times the new network in Make Before Break had its internet
+     * connection validated.
+     */
+    public void incrementMakeBeforeBreakInternetValidatedCount() {
+        synchronized (mLock) {
+            mWifiToWifiSwitchStats.makeBeforeBreakInternetValidatedCount++;
+        }
+    }
+
+    /**
+     * Increment the number of times the old network in Make Before Break was successfully
+     * transitioned from PRIMARY to SECONDARY_TRANSIENT role.
+     */
+    public void incrementMakeBeforeBreakSuccessCount() {
+        synchronized (mLock) {
+            mWifiToWifiSwitchStats.makeBeforeBreakSuccessCount++;
+        }
+    }
+
+    /**
+     * Increment the number of times the old network in Make Before Break completed lingering and
+     * was disconnected.
+     * @param duration the lingering duration in ms
+     */
+    public void incrementMakeBeforeBreakLingerCompletedCount(long duration) {
+        synchronized (mLock) {
+            mWifiToWifiSwitchStats.makeBeforeBreakLingerCompletedCount++;
+            int lingeringDurationSeconds = Math.min(MBB_LINGERING_DURATION_MAX_SECONDS,
+                    (int) duration / 1000);
+            mMakeBeforeBreakLingeringDurationSeconds.increment(lingeringDurationSeconds);
+        }
+    }
+
+    private String wifiToWifiSwitchStatsToString(WifiToWifiSwitchStats stats) {
+        return "WifiToWifiSwitchStats{"
+                + "isMakeBeforeBreakSupported=" + stats.isMakeBeforeBreakSupported
+                + ",wifiToWifiSwitchTriggerCount=" + stats.wifiToWifiSwitchTriggerCount
+                + ",makeBeforeBreakTriggerCount=" + stats.makeBeforeBreakTriggerCount
+                + ",makeBeforeBreakNoInternetCount=" + stats.makeBeforeBreakNoInternetCount
+                + ",makeBeforeBreakRecoverPrimaryCount=" + stats.makeBeforeBreakRecoverPrimaryCount
+                + ",makeBeforeBreakInternetValidatedCount="
+                + stats.makeBeforeBreakInternetValidatedCount
+                + ",makeBeforeBreakSuccessCount=" + stats.makeBeforeBreakSuccessCount
+                + ",makeBeforeBreakLingerCompletedCount="
+                + stats.makeBeforeBreakLingerCompletedCount
+                + ",makeBeforeBreakLingeringDurationSeconds="
+                + mMakeBeforeBreakLingeringDurationSeconds
+                + "}";
+    }
+
+    /**
+     * Increment number of number of Passpoint connections with a venue URL
+     */
+    public void incrementTotalNumberOfPasspointConnectionsWithVenueUrl() {
+        synchronized (mLock) {
+            mWifiLogProto.totalNumberOfPasspointConnectionsWithVenueUrl++;
+        }
+    }
+
+    /**
+     * Increment number of number of Passpoint connections with a T&C URL
+     */
+    public void incrementTotalNumberOfPasspointConnectionsWithTermsAndConditionsUrl() {
+        synchronized (mLock) {
+            mWifiLogProto.totalNumberOfPasspointConnectionsWithTermsAndConditionsUrl++;
+        }
+    }
+
+    /**
+     * Increment number of successful acceptance of Passpoint T&C
+     */
+    public void incrementTotalNumberOfPasspointAcceptanceOfTermsAndConditions() {
+        synchronized (mLock) {
+            mWifiLogProto.totalNumberOfPasspointAcceptanceOfTermsAndConditions++;
+        }
+    }
+
+    /**
+     * Increment number of Passpoint profiles with decorated identity prefix
+     */
+    public void incrementTotalNumberOfPasspointProfilesWithDecoratedIdentity() {
+        synchronized (mLock) {
+            mWifiLogProto.totalNumberOfPasspointProfilesWithDecoratedIdentity++;
+        }
+    }
+
+    /**
+     * Increment number of Passpoint Deauth-Imminent notification scope
+     */
+    public void incrementPasspointDeauthImminentScope(boolean isEss) {
+        synchronized (mLock) {
+            mPasspointDeauthImminentScope.increment(isEss ? PASSPOINT_DEAUTH_IMMINENT_SCOPE_ESS
+                    : PASSPOINT_DEAUTH_IMMINENT_SCOPE_BSS);
+        }
+    }
+
+    /**
+     * Increment number of times connection failure status reported per
+     * WifiConfiguration.RecentFailureReason
+     */
+    public void incrementRecentFailureAssociationStatusCount(
+            @WifiConfiguration.RecentFailureReason int reason) {
+        synchronized (mLock) {
+            mRecentFailureAssociationStatus.increment(reason);
+        }
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiMonitor.java b/service/java/com/android/server/wifi/WifiMonitor.java
index 32a96d0..5a69591 100644
--- a/service/java/com/android/server/wifi/WifiMonitor.java
+++ b/service/java/com/android/server/wifi/WifiMonitor.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wifi;
 
+import android.annotation.IntDef;
 import android.net.wifi.SupplicantState;
 import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiManager;
@@ -34,6 +35,8 @@
 import com.android.server.wifi.hotspot2.IconEvent;
 import com.android.server.wifi.hotspot2.WnmData;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
@@ -41,7 +44,6 @@
 /**
  * Listen for events from the wpa_supplicant & wificond and broadcast them on
  * to the various {@link ClientModeImpl} modules interested in handling these events.
- * @hide
  */
 public class WifiMonitor {
     private static final String TAG = "WifiMonitor";
@@ -49,10 +51,6 @@
     /* Supplicant events reported to a state machine */
     private static final int BASE = Protocol.BASE_WIFI_MONITOR;
 
-    /* Connection to supplicant established */
-    public static final int SUP_CONNECTION_EVENT                 = BASE + 1;
-    /* Connection to supplicant lost */
-    public static final int SUP_DISCONNECTION_EVENT              = BASE + 2;
    /* Network connection completed */
     public static final int NETWORK_CONNECTION_EVENT             = BASE + 3;
     /* Network disconnection completed */
@@ -88,18 +86,25 @@
     public static final int ANQP_DONE_EVENT                      = BASE + 44;
     public static final int ASSOCIATED_BSSID_EVENT               = BASE + 45;
     public static final int TARGET_BSSID_EVENT                   = BASE + 46;
+    public static final int NETWORK_NOT_FOUND_EVENT              = BASE + 47;
 
-    /* hotspot 2.0 ANQP events */
+    /* Passpoint ANQP events */
     public static final int GAS_QUERY_START_EVENT                = BASE + 51;
     public static final int GAS_QUERY_DONE_EVENT                 = BASE + 52;
     public static final int RX_HS20_ANQP_ICON_EVENT              = BASE + 53;
 
-    /* hotspot 2.0 events */
+    /* Passpoint events */
     public static final int HS20_REMEDIATION_EVENT               = BASE + 61;
+    public static final int HS20_DEAUTH_IMMINENT_EVENT           = BASE + 62;
+    public static final int HS20_TERMS_AND_CONDITIONS_ACCEPTANCE_REQUIRED_EVENT = BASE + 63;
 
     /* MBO/OCE events */
     public static final int MBO_OCE_BSS_TM_HANDLING_DONE         = BASE + 71;
 
+    /* Transition Disable Indication */
+    public static final int TRANSITION_DISABLE_INDICATION        = BASE + 72;
+
+
     /* WPS config errrors */
     private static final int CONFIG_MULTIPLE_PBC_DETECTED = 12;
     private static final int CONFIG_AUTH_FAILURE = 18;
@@ -108,20 +113,37 @@
     private static final int REASON_TKIP_ONLY_PROHIBITED = 1;
     private static final int REASON_WEP_PROHIBITED = 2;
 
-    private final WifiInjector mWifiInjector;
+    /* Transition disable indication */
+    public static final int TDI_USE_WPA3_PERSONAL = 1 << 0;
+    public static final int TDI_USE_SAE_PK = 1 << 1;
+    public static final int TDI_USE_WPA3_ENTERPRISE = 1 << 2;
+    public static final int TDI_USE_ENHANCED_OPEN = 1 << 3;
+
+    @IntDef(flag = true, prefix = { "TDI_" }, value = {
+            TDI_USE_WPA3_PERSONAL,
+            TDI_USE_SAE_PK,
+            TDI_USE_WPA3_ENTERPRISE,
+            TDI_USE_ENHANCED_OPEN,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface TransitionDisableIndication{}
+
+    /**
+     * Use this key to get the interface name of the message sent by WifiMonitor,
+     * or null if not available.
+     *
+     * <br />
+     * Sample code:
+     * <code>
+     * message.getData().getString(KEY_IFACE)
+     * </code>
+     */
+    public static final String KEY_IFACE = "com.android.server.wifi.WifiMonitor.KEY_IFACE";
+
     private boolean mVerboseLoggingEnabled = false;
-    private boolean mConnected = false;
 
-    public WifiMonitor(WifiInjector wifiInjector) {
-        mWifiInjector = wifiInjector;
-    }
-
-    void enableVerboseLogging(int verbose) {
-        if (verbose > 0) {
-            mVerboseLoggingEnabled = true;
-        } else {
-            mVerboseLoggingEnabled = false;
-        }
+    void enableVerboseLogging(boolean verbose) {
+        mVerboseLoggingEnabled = verbose;
     }
 
     private final Map<String, SparseArray<Set<Handler>>> mHandlerMap = new HashMap<>();
@@ -178,12 +200,6 @@
         mMonitoringMap.put(iface, enabled);
     }
 
-    private void setMonitoringNone() {
-        for (String iface : mMonitoringMap.keySet()) {
-            setMonitoring(iface, false);
-        }
-    }
-
     /**
      * Start Monitoring for wpa_supplicant events.
      *
@@ -192,7 +208,6 @@
     public synchronized void startMonitoring(String iface) {
         if (mVerboseLoggingEnabled) Log.d(TAG, "startMonitoring(" + iface + ")");
         setMonitoring(iface, true);
-        broadcastSupplicantConnectionEvent(iface);
     }
 
     /**
@@ -203,20 +218,9 @@
     public synchronized void stopMonitoring(String iface) {
         if (mVerboseLoggingEnabled) Log.d(TAG, "stopMonitoring(" + iface + ")");
         setMonitoring(iface, true);
-        broadcastSupplicantDisconnectionEvent(iface);
         setMonitoring(iface, false);
     }
 
-    /**
-     * Stop Monitoring for wpa_supplicant events.
-     *
-     * TODO: Add unit tests for these once we remove the legacy code.
-     */
-    public synchronized void stopAllMonitoring() {
-        mConnected = false;
-        setMonitoringNone();
-    }
-
 
     /**
      * Similar functions to Handler#sendMessage that send the message to the registered handler
@@ -251,7 +255,7 @@
                 if (ifaceWhatHandlers != null) {
                     for (Handler handler : ifaceWhatHandlers) {
                         if (handler != null) {
-                            sendMessage(handler, Message.obtain(message));
+                            sendMessage(iface, handler, Message.obtain(message));
                         }
                     }
                 }
@@ -265,11 +269,13 @@
                 Log.d(TAG, "Sending to all monitors because there's no matching iface");
             }
             for (Map.Entry<String, SparseArray<Set<Handler>>> entry : mHandlerMap.entrySet()) {
-                if (isMonitoring(entry.getKey())) {
+                iface = entry.getKey();
+                if (isMonitoring(iface)) {
                     Set<Handler> ifaceWhatHandlers = entry.getValue().get(message.what);
+                    if (ifaceWhatHandlers == null) continue;
                     for (Handler handler : ifaceWhatHandlers) {
                         if (handler != null) {
-                            sendMessage(handler, Message.obtain(message));
+                            sendMessage(iface, handler, Message.obtain(message));
                         }
                     }
                 }
@@ -279,8 +285,11 @@
         message.recycle();
     }
 
-    private void sendMessage(Handler handler, Message message) {
+    private void sendMessage(String iface, Handler handler, Message message) {
         message.setTarget(handler);
+        // getData() will return the existing Bundle if it exists, or create a new one
+        // This prevents clearing the existing data.
+        message.getData().putString(KEY_IFACE, iface);
         message.sendToTarget();
     }
 
@@ -375,7 +384,25 @@
      * @param wnmData Instance of WnmData containing the event data.
      */
     public void broadcastWnmEvent(String iface, WnmData wnmData) {
-        sendMessage(iface, HS20_REMEDIATION_EVENT, wnmData);
+        if (mVerboseLoggingEnabled) Log.d(TAG, "WNM-Notification " + wnmData.getEventType());
+        switch (wnmData.getEventType()) {
+            case WnmData.HS20_REMEDIATION_EVENT:
+                sendMessage(iface, HS20_REMEDIATION_EVENT, wnmData);
+                break;
+
+            case WnmData.HS20_DEAUTH_IMMINENT_EVENT:
+                sendMessage(iface, HS20_DEAUTH_IMMINENT_EVENT, wnmData);
+                break;
+
+            case WnmData.HS20_TERMS_AND_CONDITIONS_ACCEPTANCE_REQUIRED_EVENT:
+                sendMessage(iface, HS20_TERMS_AND_CONDITIONS_ACCEPTANCE_REQUIRED_EVENT, wnmData);
+                break;
+
+            default:
+                Log.e(TAG, "Broadcast request for an unknown WNM-notification "
+                        + wnmData.getEventType());
+                break;
+        }
     }
 
     /**
@@ -390,6 +417,19 @@
     }
 
     /**
+     * Broadcast the transition disable event to all the handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     * @param networkId ID of the network in wpa_supplicant.
+     * @param indicationBits bits of the disable indication.
+     */
+    public void broadcastTransitionDisableEvent(
+            String iface, int networkId,
+            @TransitionDisableIndication int indicationBits) {
+        sendMessage(iface, TRANSITION_DISABLE_INDICATION, networkId, indicationBits);
+    }
+
+    /**
      * Broadcast the Network Gsm Sim auth request event to all the handlers registered for this
      * event.
      *
@@ -463,13 +503,12 @@
      * Broadcast the association rejection event to all the handlers registered for this event.
      *
      * @param iface Name of iface on which this occurred.
-     * @param status Status code for association rejection.
-     * @param timedOut Indicates if the association timed out.
-     * @param bssid BSSID of the access point from which we received the reject.
+     * @param assocRejectInfo Instance of AssocRejectEventInfo containing the association
+     *                        rejection info.
      */
-    public void broadcastAssociationRejectionEvent(String iface, int status, boolean timedOut,
-                                                   String bssid) {
-        sendMessage(iface, ASSOCIATION_REJECTION_EVENT, timedOut ? 1 : 0, status, bssid);
+    public void broadcastAssociationRejectionEvent(String iface,
+            AssocRejectEventInfo assocRejectInfo) {
+        sendMessage(iface, ASSOCIATION_REJECTION_EVENT, assocRejectInfo);
     }
 
     /**
@@ -501,21 +540,24 @@
      * @param bssid BSSID of the access point.
      */
     public void broadcastNetworkConnectionEvent(String iface, int networkId, boolean filsHlpSent,
-            String bssid) {
-        sendMessage(iface, NETWORK_CONNECTION_EVENT, networkId, filsHlpSent ? 1 : 0, bssid);
+            WifiSsid ssid, String bssid) {
+        sendMessage(iface, NETWORK_CONNECTION_EVENT,
+                new NetworkConnectionEventInfo(networkId, ssid, bssid, filsHlpSent));
     }
 
     /**
      * Broadcast the network disconnection event to all the handlers registered for this event.
      *
      * @param iface Name of iface on which this occurred.
-     * @param local Whether the disconnect was locally triggered.
+     * @param locallyGenerated Whether the disconnect was locally triggered.
      * @param reason Disconnect reason code.
+     * @param ssid SSID of the access point.
      * @param bssid BSSID of the access point.
      */
-    public void broadcastNetworkDisconnectionEvent(String iface, int local, int reason,
-                                                   String bssid) {
-        sendMessage(iface, NETWORK_DISCONNECTION_EVENT, local, reason, bssid);
+    public void broadcastNetworkDisconnectionEvent(String iface, boolean locallyGenerated,
+            int reason, String ssid, String bssid) {
+        sendMessage(iface, NETWORK_DISCONNECTION_EVENT,
+                new DisconnectEventInfo(ssid, bssid, reason, locallyGenerated));
     }
 
     /**
@@ -534,26 +576,6 @@
     }
 
     /**
-     * Broadcast the connection to wpa_supplicant event to all the handlers registered for
-     * this event.
-     *
-     * @param iface Name of iface on which this occurred.
-     */
-    public void broadcastSupplicantConnectionEvent(String iface) {
-        sendMessage(iface, SUP_CONNECTION_EVENT);
-    }
-
-    /**
-     * Broadcast the loss of connection to wpa_supplicant event to all the handlers registered for
-     * this event.
-     *
-     * @param iface Name of iface on which this occurred.
-     */
-    public void broadcastSupplicantDisconnectionEvent(String iface) {
-        sendMessage(iface, SUP_DISCONNECTION_EVENT);
-    }
-
-    /**
      * Broadcast the bss transition management frame handling event
      * to all the handlers registered for this event.
      *
@@ -562,4 +584,14 @@
     public void broadcastBssTmHandlingDoneEvent(String iface, BtmFrameData btmFrmData) {
         sendMessage(iface, MBO_OCE_BSS_TM_HANDLING_DONE, btmFrmData);
     }
+
+    /**
+     * Broadcast network not found event
+     * to all the handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     */
+    public void broadcastNetworkNotFoundEvent(String iface, String ssid) {
+        sendMessage(iface, NETWORK_NOT_FOUND_EVENT, ssid);
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiMulticastLockManager.java b/service/java/com/android/server/wifi/WifiMulticastLockManager.java
index d836482..c88cfae 100644
--- a/service/java/com/android/server/wifi/WifiMulticastLockManager.java
+++ b/service/java/com/android/server/wifi/WifiMulticastLockManager.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wifi;
 
+import android.annotation.Nullable;
 import android.os.BatteryStatsManager;
 import android.os.Binder;
 import android.os.IBinder;
@@ -32,8 +33,6 @@
 /**
  * WifiMulticastLockManager tracks holders of multicast locks and
  * triggers enabling and disabling of filtering.
- *
- * @hide
  */
 public class WifiMulticastLockManager {
     private static final String TAG = "WifiMulticastLockManager";
@@ -42,7 +41,7 @@
     private int mMulticastDisabled = 0;
     private boolean mVerboseLoggingEnabled = false;
     private final BatteryStatsManager mBatteryStats;
-    private final FilterController mFilterController;
+    private final ActiveModeWarden mActiveModeWarden;
 
     /** Delegate for handling state change events for multicast filtering. */
     public interface FilterController {
@@ -53,10 +52,14 @@
         void stopFilteringMulticastPackets();
     }
 
-    public WifiMulticastLockManager(FilterController filterController,
+    public WifiMulticastLockManager(
+            ActiveModeWarden activeModeWarden,
             BatteryStatsManager batteryStats) {
         mBatteryStats = batteryStats;
-        mFilterController = filterController;
+        mActiveModeWarden = activeModeWarden;
+
+        mActiveModeWarden.registerPrimaryClientModeManagerChangedCallback(
+                new PrimaryClientModeManagerChangedCallback());
     }
 
     private class Multicaster implements IBinder.DeathRecipient {
@@ -114,21 +117,17 @@
     }
 
     protected void enableVerboseLogging(int verbose) {
-        if (verbose > 0) {
-            mVerboseLoggingEnabled = true;
-        } else {
-            mVerboseLoggingEnabled = false;
-        }
+        mVerboseLoggingEnabled = verbose > 0;
     }
 
     /** Start filtering if  no multicasters exist. */
     public void initializeFiltering() {
         synchronized (mMulticasters) {
             // if anybody had requested filters be off, leave off
-            if (mMulticasters.size() != 0) {
-                return;
-            } else {
-                mFilterController.startFilteringMulticastPackets();
+            if (mMulticasters.size() == 0) {
+                mActiveModeWarden.getPrimaryClientModeManager()
+                        .getMcastLockManagerFilterController()
+                        .startFilteringMulticastPackets();
             }
         }
     }
@@ -146,7 +145,9 @@
             // our new size == 1 (first call), but this function won't
             // be called often and by making the stopPacket call each
             // time we're less fragile and self-healing.
-            mFilterController.stopFilteringMulticastPackets();
+            mActiveModeWarden.getPrimaryClientModeManager()
+                    .getMcastLockManagerFilterController()
+                    .stopFilteringMulticastPackets();
         }
 
         int uid = Binder.getCallingUid();
@@ -181,7 +182,9 @@
             removed.unlinkDeathRecipient();
         }
         if (mMulticasters.size() == 0) {
-            mFilterController.startFilteringMulticastPackets();
+            mActiveModeWarden.getPrimaryClientModeManager()
+                    .getMcastLockManagerFilterController()
+                    .startFilteringMulticastPackets();
         }
 
         final long ident = Binder.clearCallingIdentity();
@@ -192,10 +195,31 @@
         Binder.restoreCallingIdentity(ident);
     }
 
-    /** Returns whether multicast should be allowed (filterning disabled). */
+    /** Returns whether multicast should be allowed (filtering disabled). */
     public boolean isMulticastEnabled() {
         synchronized (mMulticasters) {
-            return (mMulticasters.size() > 0);
+            return mMulticasters.size() > 0;
+        }
+    }
+
+    private class PrimaryClientModeManagerChangedCallback
+            implements ActiveModeWarden.PrimaryClientModeManagerChangedCallback {
+
+        @Override
+        public void onChange(
+                @Nullable ConcreteClientModeManager prevPrimaryClientModeManager,
+                @Nullable ConcreteClientModeManager newPrimaryClientModeManager) {
+            if (prevPrimaryClientModeManager != null) {
+                // no longer primary => start filtering out multicast packets
+                prevPrimaryClientModeManager.getMcastLockManagerFilterController()
+                        .startFilteringMulticastPackets();
+            }
+            if (newPrimaryClientModeManager != null
+                    && isMulticastEnabled()) { // this call is synchronized
+                // new primary and multicast enabled => stop filtering out multicast packets
+                newPrimaryClientModeManager.getMcastLockManagerFilterController()
+                        .stopFilteringMulticastPackets();
+            }
         }
     }
 }
diff --git a/service/java/com/android/server/wifi/WifiNative.java b/service/java/com/android/server/wifi/WifiNative.java
index 1ad7443..b07101c 100644
--- a/service/java/com/android/server/wifi/WifiNative.java
+++ b/service/java/com/android/server/wifi/WifiNative.java
@@ -20,27 +20,34 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.net.MacAddress;
 import android.net.TrafficStats;
 import android.net.apf.ApfCapabilities;
+import android.net.wifi.CoexUnsafeChannel;
 import android.net.wifi.ScanResult;
 import android.net.wifi.SoftApConfiguration;
 import android.net.wifi.WifiAnnotations;
+import android.net.wifi.WifiAvailableChannel;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiScanner;
 import android.net.wifi.WifiSsid;
 import android.net.wifi.nl80211.DeviceWiphyCapabilities;
 import android.net.wifi.nl80211.NativeScanResult;
+import android.net.wifi.nl80211.NativeWifiClient;
 import android.net.wifi.nl80211.RadioChainInfo;
 import android.net.wifi.nl80211.WifiNl80211Manager;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.SystemClock;
+import android.os.WorkSource;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
 
 import com.android.internal.annotations.Immutable;
 import com.android.internal.util.HexDump;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.hotspot2.NetworkDetail;
 import com.android.server.wifi.util.FrameParser;
 import com.android.server.wifi.util.InformationElementUtil;
@@ -86,15 +93,22 @@
     private final WifiMetrics mWifiMetrics;
     private final Handler mHandler;
     private final Random mRandom;
+    private final BuildProperties mBuildProperties;
     private final WifiInjector mWifiInjector;
     private NetdWrapper mNetdWrapper;
     private boolean mVerboseLoggingEnabled = false;
+    private boolean mIsEnhancedOpenSupported = false;
+    private final List<CoexUnsafeChannel> mCachedCoexUnsafeChannels = new ArrayList<>();
+    private int mCachedCoexRestrictions;
+    private CountryCodeChangeListenerInternal mCountryCodeChangeListener;
+    private boolean mUseFakeScanDetails;
+    private final ArrayList<ScanDetail> mFakeScanDetails = new ArrayList<>();
 
     public WifiNative(WifiVendorHal vendorHal,
                       SupplicantStaIfaceHal staIfaceHal, HostapdHal hostapdHal,
                       WifiNl80211Manager condManager, WifiMonitor wifiMonitor,
                       PropertyService propertyService, WifiMetrics wifiMetrics,
-                      Handler handler, Random random,
+                      Handler handler, Random random, BuildProperties buildProperties,
                       WifiInjector wifiInjector) {
         mWifiVendorHal = vendorHal;
         mSupplicantStaIfaceHal = staIfaceHal;
@@ -105,14 +119,16 @@
         mWifiMetrics = wifiMetrics;
         mHandler = handler;
         mRandom = random;
+        mBuildProperties = buildProperties;
         mWifiInjector = wifiInjector;
     }
 
     /**
      * Enable verbose logging for all sub modules.
      */
-    public void enableVerboseLogging(int verbose) {
-        mVerboseLoggingEnabled = verbose > 0 ? true : false;
+    public void enableVerboseLogging(boolean verbose) {
+        mVerboseLoggingEnabled = verbose;
+        setSupplicantLogLevel(mVerboseLoggingEnabled);
         mWifiCondManager.enableVerboseLogging(mVerboseLoggingEnabled);
         mSupplicantStaIfaceHal.enableVerboseLogging(mVerboseLoggingEnabled);
         mHostapdHal.enableVerboseLogging(mVerboseLoggingEnabled);
@@ -122,9 +138,91 @@
     /**
      * Callbacks for SoftAp interface.
      */
-    public interface SoftApListener extends WifiNl80211Manager.SoftApCallback {
-        // dummy for now - provide a shell so that clients don't use a
+    public class SoftApListenerFromWificond implements WifiNl80211Manager.SoftApCallback {
+        // placeholder for now - provide a shell so that clients don't use a
         // WifiNl80211Manager-specific API.
+        private String mIfaceName;
+        private SoftApListener mSoftApListener;
+
+        SoftApListenerFromWificond(String ifaceName,
+                SoftApListener softApListener) {
+            mIfaceName = ifaceName;
+            mSoftApListener = softApListener;
+        }
+
+        @Override
+        public void onFailure() {
+            mSoftApListener.onFailure();
+        }
+
+        @Override
+        public void onSoftApChannelSwitched(int frequency, int bandwidth) {
+            mSoftApListener.onInfoChanged(mIfaceName, frequency, bandwidth,
+                    ScanResult.WIFI_STANDARD_UNKNOWN, null);
+        }
+
+        @Override
+        public void onConnectedClientsChanged(NativeWifiClient client, boolean isConnected) {
+            mSoftApListener.onConnectedClientsChanged(mIfaceName,
+                    client.getMacAddress(), isConnected);
+        }
+    }
+
+    private static class CountryCodeChangeListenerInternal implements
+            WifiNl80211Manager.CountryCodeChangedListener {
+        private WifiCountryCode.ChangeListener mListener;
+
+        public void setChangeListener(@NonNull WifiCountryCode.ChangeListener listener) {
+            mListener = listener;
+        }
+
+        public void onSetCountryCodeSucceeded(String country) {
+            Log.d(TAG, "onSetCountryCodeSucceeded: " + country);
+            if (mListener != null) {
+                mListener.onSetCountryCodeSucceeded(country);
+            }
+        }
+
+        @Override
+        public void onCountryCodeChanged(String country) {
+            Log.d(TAG, "onCountryCodeChanged: " + country);
+            if (mListener != null) {
+                mListener.onDriverCountryCodeChanged(country);
+            }
+        }
+    }
+
+    /**
+     * Callbacks for SoftAp instance.
+     */
+    public interface SoftApListener {
+        /**
+         * Invoked when there is a fatal failure and the SoftAp is shutdown.
+         */
+        void onFailure();
+
+        /**
+         * Invoked when a channel switch event happens - i.e. the SoftAp is moved to a different
+         * channel. Also called on initial registration.
+         *
+         * @param apIfaceInstance The identity of the ap instance.
+         * @param frequency The new frequency of the SoftAp. A value of 0 is invalid and is an
+         *                     indication that the SoftAp is not enabled.
+         * @param bandwidth The new bandwidth of the SoftAp.
+         * @param generation The new generation of the SoftAp.
+         */
+        void onInfoChanged(String apIfaceInstance, int frequency, int bandwidth,
+                int generation, MacAddress apIfaceInstanceMacAddress);
+        /**
+         * Invoked when there is a change in the associated station (STA).
+         *
+         * @param apIfaceInstance The identity of the ap instance.
+         * @param clientAddress Macaddress of the client.
+         * @param isConnected Indication as to whether the client is connected (true), or
+         *                    disconnected (false).
+         */
+        void onConnectedClientsChanged(String apIfaceInstance, MacAddress clientAddress,
+                boolean isConnected);
     }
 
     /********************************************************
@@ -251,16 +349,6 @@
             return false;
         }
 
-        /** Checks if there are any iface of the given type active. */
-        private Iface findAnyIfaceOfType(@Iface.IfaceType int type) {
-            for (Iface iface : mIfaces.values()) {
-                if (iface.type == type) {
-                    return iface;
-                }
-            }
-            return null;
-        }
-
         /** Checks if there are any STA (for connectivity) iface active. */
         private boolean hasAnyStaIfaceForConnectivity() {
             return hasAnyIfaceOfType(Iface.IFACE_TYPE_STA_FOR_CONNECTIVITY);
@@ -276,27 +364,6 @@
             return hasAnyIfaceOfType(Iface.IFACE_TYPE_AP);
         }
 
-        /** Finds the name of any STA iface active. */
-        private String findAnyStaIfaceName() {
-            Iface iface = findAnyIfaceOfType(Iface.IFACE_TYPE_STA_FOR_CONNECTIVITY);
-            if (iface == null) {
-                iface = findAnyIfaceOfType(Iface.IFACE_TYPE_STA_FOR_SCAN);
-            }
-            if (iface == null) {
-                return null;
-            }
-            return iface.name;
-        }
-
-        /** Finds the name of any AP iface active. */
-        private String findAnyApIfaceName() {
-            Iface iface = findAnyIfaceOfType(Iface.IFACE_TYPE_AP);
-            if (iface == null) {
-                return null;
-            }
-            return iface.name;
-        }
-
         private @NonNull Set<String> findAllStaIfaceNames() {
             Set<String> ifaceNames = new ArraySet<>();
             for (Iface iface : mIfaces.values()) {
@@ -308,6 +375,16 @@
             return ifaceNames;
         }
 
+        private @NonNull Set<String> findAllApIfaceNames() {
+            Set<String> ifaceNames = new ArraySet<>();
+            for (Iface iface : mIfaces.values()) {
+                if (iface.type == Iface.IFACE_TYPE_AP) {
+                    ifaceNames.add(iface.name);
+                }
+            }
+            return ifaceNames;
+        }
+
         /** Removes the existing iface that does not match the provided id. */
         public Iface removeExistingIface(int newIfaceId) {
             Iface removedIface = null;
@@ -381,10 +458,15 @@
                         Log.e(TAG, "Failed to start vendor HAL");
                         return false;
                     }
+                    if (SdkLevel.isAtLeastS()) {
+                        mWifiVendorHal.setCoexUnsafeChannels(
+                                mCachedCoexUnsafeChannels, mCachedCoexRestrictions);
+                    }
                 } else {
                     Log.i(TAG, "Vendor Hal not supported, ignoring start.");
                 }
             }
+            registerWificondListenerIfNecessary();
             return true;
         }
     }
@@ -405,6 +487,18 @@
         }
     }
 
+    /**
+     * Helper method invoked to setup wificond related callback/listener.
+     */
+    private void registerWificondListenerIfNecessary() {
+        if (mCountryCodeChangeListener == null && SdkLevel.isAtLeastS()) {
+            // The country code listener is a new API in S.
+            mCountryCodeChangeListener = new CountryCodeChangeListenerInternal();
+            mWifiCondManager.registerCountryCodeChangedListener(Runnable::run,
+                    mCountryCodeChangeListener);
+        }
+    }
+
     private static final int CONNECT_TO_SUPPLICANT_RETRY_INTERVAL_MS = 100;
     private static final int CONNECT_TO_SUPPLICANT_RETRY_TIMES = 50;
     /**
@@ -574,8 +668,12 @@
             } else if (iface.type == Iface.IFACE_TYPE_AP) {
                 onSoftApInterfaceDestroyed(iface);
             }
-            // Invoke the external callback.
-            iface.externalListener.onDestroyed(iface.name);
+            // Invoke the external callback only if the iface was not destroyed because of vendor
+            // HAL crash. In case of vendor HAL crash, let the crash recovery destroy the mode
+            // managers.
+            if (mWifiVendorHal.isVendorHalReady()) {
+                iface.externalListener.onDestroyed(iface.name);
+            }
         }
     }
 
@@ -813,11 +911,11 @@
      * For devices which do not the support the HAL, this will bypass HalDeviceManager &
      * teardown any existing iface.
      */
-    private String createStaIface(@NonNull Iface iface) {
+    private String createStaIface(@NonNull Iface iface, @NonNull WorkSource requestorWs) {
         synchronized (mLock) {
             if (mWifiVendorHal.isVendorHalSupported()) {
                 return mWifiVendorHal.createStaIface(
-                        new InterfaceDestoyedListenerInternal(iface.id));
+                        new InterfaceDestoyedListenerInternal(iface.id), requestorWs);
             } else {
                 Log.i(TAG, "Vendor Hal not supported, ignoring createStaIface.");
                 return handleIfaceCreationWhenVendorHalNotSupported(iface);
@@ -830,11 +928,13 @@
      * For devices which do not the support the HAL, this will bypass HalDeviceManager &
      * teardown any existing iface.
      */
-    private String createApIface(@NonNull Iface iface) {
+    private String createApIface(@NonNull Iface iface, @NonNull WorkSource requestorWs,
+            @SoftApConfiguration.BandType int band, boolean isBridged) {
         synchronized (mLock) {
             if (mWifiVendorHal.isVendorHalSupported()) {
                 return mWifiVendorHal.createApIface(
-                        new InterfaceDestoyedListenerInternal(iface.id));
+                        new InterfaceDestoyedListenerInternal(iface.id), requestorWs,
+                        band, isBridged);
             } else {
                 Log.i(TAG, "Vendor Hal not supported, ignoring createApIface.");
                 return handleIfaceCreationWhenVendorHalNotSupported(iface);
@@ -842,6 +942,24 @@
         }
     }
 
+    /**
+     * Get list of instance name from this bridged AP iface.
+     *
+     * @param ifaceName Name of the bridged interface.
+     * @return list of instance name when succeed, otherwise null.
+     */
+    @Nullable
+    private List<String> getBridgedApInstances(@NonNull String ifaceName) {
+        synchronized (mLock) {
+            if (mWifiVendorHal.isVendorHalSupported()) {
+                return mWifiVendorHal.getBridgedApInstances(ifaceName);
+            } else {
+                Log.i(TAG, "Vendor Hal not supported, ignoring getBridgedApInstances.");
+                return null;
+            }
+        }
+    }
+
     // For devices that don't support the vendor HAL, we will not support any concurrency.
     // So simulate the HalDeviceManager behavior by triggering the destroy listener for
     // the interface.
@@ -885,6 +1003,37 @@
     }
 
     /**
+     * Helper function to remove specific instance in bridged AP iface.
+     *
+     * @param ifaceName Name of the iface.
+     * @param apIfaceInstance The identity of the ap instance.
+     * @return true if the operation succeeded, false if there is an error in Hal.
+     */
+    public boolean removeIfaceInstanceFromBridgedApIface(@NonNull String ifaceName,
+            @NonNull String apIfaceInstance) {
+        synchronized (mLock) {
+            if (mWifiVendorHal.isVendorHalSupported()) {
+                return mWifiVendorHal.removeIfaceInstanceFromBridgedApIface(ifaceName,
+                        apIfaceInstance);
+            } else {
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Register listener for subsystem restart event
+     *
+     * @param listener SubsystemRestartListener listener object.
+     */
+    public void registerSubsystemRestartListener(
+            HalDeviceManager.SubsystemRestartListener listener) {
+        if (listener != null) {
+            mWifiVendorHal.registerSubsystemRestartListener(listener);
+        }
+    }
+
+    /**
      * Initialize the native modules.
      *
      * @return true on success, false otherwise.
@@ -928,43 +1077,6 @@
     }
 
     /**
-     * Callback to notify when the availability of an interface has changed.
-     */
-    public interface InterfaceAvailableForRequestListener {
-        /**
-         * @param isAvailable Whether it is possible to create an iface of the specified type or
-         *                    not.
-         */
-        void onAvailabilityChanged(boolean isAvailable);
-    }
-
-    /**
-     * Register a callback to notify when the availability of Client interface has changed.
-     *
-     * It is safe to re-register the same callback object - duplicates are detected and only a
-     * single copy kept.
-     *
-     * @param listener Instance of {@link InterfaceAvailableForRequestListener}.
-     */
-    public void registerClientInterfaceAvailabilityListener(
-            @NonNull InterfaceAvailableForRequestListener listener) {
-        mWifiVendorHal.registerStaIfaceAvailabilityListener(listener);
-    }
-
-    /**
-     * Register a callback to notify when the availability of SoftAp interface has changed.
-     *
-     * It is safe to re-register the same callback object - duplicates are detected and only a
-     * single copy kept.
-     *
-     * @param listener Instance of {@link InterfaceAvailableForRequestListener}.
-     */
-    public void registerSoftApInterfaceAvailabilityListener(
-            @NonNull InterfaceAvailableForRequestListener listener) {
-        mWifiVendorHal.registerApIfaceAvailabilityListener(listener);
-    }
-
-    /**
      * Callback to notify when the associated interface is destroyed, up or down.
      */
     public interface InterfaceCallback {
@@ -1018,10 +1130,11 @@
      * (wificond, wpa_supplicant & vendor HAL).
      *
      * @param interfaceCallback Associated callback for notifying status changes for the iface.
+     * @param requestorWs Requestor worksource.
      * @return Returns the name of the allocated interface, will be null on failure.
      */
     public String setupInterfaceForClientInConnectivityMode(
-            @NonNull InterfaceCallback interfaceCallback) {
+            @NonNull InterfaceCallback interfaceCallback, @NonNull WorkSource requestorWs) {
         synchronized (mLock) {
             if (!startHal()) {
                 Log.e(TAG, "Failed to start Hal");
@@ -1039,7 +1152,7 @@
                 return null;
             }
             iface.externalListener = interfaceCallback;
-            iface.name = createStaIface(iface);
+            iface.name = createStaIface(iface, requestorWs);
             if (TextUtils.isEmpty(iface.name)) {
                 Log.e(TAG, "Failed to create STA iface in vendor HAL");
                 mIfaceMgr.removeIface(iface.id);
@@ -1074,6 +1187,7 @@
             Log.i(TAG, "Successfully setup " + iface);
 
             iface.featureSet = getSupportedFeatureSetInternal(iface.name);
+            mIsEnhancedOpenSupported = (iface.featureSet & WIFI_FEATURE_OWE) != 0;
             return iface.name;
         }
     }
@@ -1085,10 +1199,11 @@
      * (wificond, vendor HAL).
      *
      * @param interfaceCallback Associated callback for notifying status changes for the iface.
+     * @param requestorWs Requestor worksource.
      * @return Returns the name of the allocated interface, will be null on failure.
      */
     public String setupInterfaceForClientInScanMode(
-            @NonNull InterfaceCallback interfaceCallback) {
+            @NonNull InterfaceCallback interfaceCallback, @NonNull WorkSource requestorWs) {
         synchronized (mLock) {
             if (!startHal()) {
                 Log.e(TAG, "Failed to start Hal");
@@ -1101,7 +1216,7 @@
                 return null;
             }
             iface.externalListener = interfaceCallback;
-            iface.name = createStaIface(iface);
+            iface.name = createStaIface(iface, requestorWs);
             if (TextUtils.isEmpty(iface.name)) {
                 Log.e(TAG, "Failed to create iface in vendor HAL");
                 mIfaceMgr.removeIface(iface.id);
@@ -1140,9 +1255,13 @@
      * (wificond, wpa_supplicant & vendor HAL).
      *
      * @param interfaceCallback Associated callback for notifying status changes for the iface.
+     * @param requestorWs Requestor worksource.
+     * @param isBridged Whether or not AP interface is a bridge interface.
      * @return Returns the name of the allocated interface, will be null on failure.
      */
-    public String setupInterfaceForSoftApMode(@NonNull InterfaceCallback interfaceCallback) {
+    public String setupInterfaceForSoftApMode(
+            @NonNull InterfaceCallback interfaceCallback, @NonNull WorkSource requestorWs,
+            @SoftApConfiguration.BandType int band, boolean isBridged) {
         synchronized (mLock) {
             if (!startHal()) {
                 Log.e(TAG, "Failed to start Hal");
@@ -1160,14 +1279,26 @@
                 return null;
             }
             iface.externalListener = interfaceCallback;
-            iface.name = createApIface(iface);
+            iface.name = createApIface(iface, requestorWs, band, isBridged);
             if (TextUtils.isEmpty(iface.name)) {
                 Log.e(TAG, "Failed to create AP iface in vendor HAL");
                 mIfaceMgr.removeIface(iface.id);
                 mWifiMetrics.incrementNumSetupSoftApInterfaceFailureDueToHal();
                 return null;
             }
-            if (!mWifiCondManager.setupInterfaceForSoftApMode(iface.name)) {
+            String ifaceInstanceName = iface.name;
+            if (isBridged) {
+                List<String> instances = getBridgedApInstances(iface.name);
+                if (instances == null || instances.size() == 0) {
+                    Log.e(TAG, "Failed to get bridged AP instances" + iface.name);
+                    teardownInterface(iface.name);
+                    mWifiMetrics.incrementNumSetupSoftApInterfaceFailureDueToHal();
+                    return null;
+                }
+                // Always select first instance as wificond interface.
+                ifaceInstanceName = instances.get(0);
+            }
+            if (!mWifiCondManager.setupInterfaceForSoftApMode(ifaceInstanceName)) {
                 Log.e(TAG, "Failed to setup iface in wificond on " + iface);
                 teardownInterface(iface.name);
                 mWifiMetrics.incrementNumSetupSoftApInterfaceFailureDueToWificond();
@@ -1195,10 +1326,12 @@
      * {@link Iface#IFACE_TYPE_STA_FOR_SCAN}.
      *
      * @param ifaceName Name of the interface.
+     * @param requestorWs Requestor worksource.
      * @return true if the operation succeeded, false if there is an error or the iface is already
      * in scan mode.
      */
-    public boolean switchClientInterfaceToScanMode(@NonNull String ifaceName) {
+    public boolean switchClientInterfaceToScanMode(@NonNull String ifaceName,
+            @NonNull WorkSource requestorWs) {
         synchronized (mLock) {
             final Iface iface = mIfaceMgr.getIface(ifaceName);
             if (iface == null) {
@@ -1209,6 +1342,12 @@
                 Log.e(TAG, "Already in scan mode on iface=" + ifaceName);
                 return true;
             }
+            if (mWifiVendorHal.isVendorHalSupported()
+                    && !mWifiVendorHal.replaceStaIfaceRequestorWs(iface.name, requestorWs)) {
+                Log.e(TAG, "Failed to replace requestor ws on " + iface);
+                teardownInterface(iface.name);
+                return false;
+            }
             if (!mSupplicantStaIfaceHal.teardownIface(iface.name)) {
                 Log.e(TAG, "Failed to teardown iface in supplicant on " + iface);
                 teardownInterface(iface.name);
@@ -1229,10 +1368,12 @@
      * {@link Iface#IFACE_TYPE_STA_FOR_CONNECTIVITY}.
      *
      * @param ifaceName Name of the interface.
+     * @param requestorWs Requestor worksource.
      * @return true if the operation succeeded, false if there is an error or the iface is already
      * in scan mode.
      */
-    public boolean switchClientInterfaceToConnectivityMode(@NonNull String ifaceName) {
+    public boolean switchClientInterfaceToConnectivityMode(@NonNull String ifaceName,
+            @NonNull WorkSource requestorWs) {
         synchronized (mLock) {
             final Iface iface = mIfaceMgr.getIface(ifaceName);
             if (iface == null) {
@@ -1244,6 +1385,12 @@
                 Log.e(TAG, "Already in connectivity mode on iface=" + ifaceName);
                 return true;
             }
+            if (mWifiVendorHal.isVendorHalSupported()
+                    && !mWifiVendorHal.replaceStaIfaceRequestorWs(iface.name, requestorWs)) {
+                Log.e(TAG, "Failed to replace requestor ws on " + iface);
+                teardownInterface(iface.name);
+                return false;
+            }
             if (!startSupplicant()) {
                 Log.e(TAG, "Failed to start supplicant");
                 teardownInterface(iface.name);
@@ -1258,12 +1405,35 @@
             }
             iface.type = Iface.IFACE_TYPE_STA_FOR_CONNECTIVITY;
             iface.featureSet = getSupportedFeatureSetInternal(iface.name);
+            mIsEnhancedOpenSupported = (iface.featureSet & WIFI_FEATURE_OWE) != 0;
             Log.i(TAG, "Successfully switched to connectivity mode on iface=" + iface);
             return true;
         }
     }
 
     /**
+     * Change the requestor WorkSource for a given STA iface.
+     * @return true if the operation succeeded, false otherwise.
+     */
+    public boolean replaceStaIfaceRequestorWs(@NonNull String ifaceName, WorkSource newWorkSource) {
+        final Iface iface = mIfaceMgr.getIface(ifaceName);
+        if (iface == null) {
+            Log.e(TAG, "Called replaceStaIfaceRequestorWs() on an invalid iface=" + ifaceName);
+            return false;
+        }
+        if (!mWifiVendorHal.isVendorHalSupported()) {
+            // if vendor HAL isn't supported, return true since there's nothing to do.
+            return true;
+        }
+        if (!mWifiVendorHal.replaceStaIfaceRequestorWs(iface.name, newWorkSource)) {
+            Log.e(TAG, "Failed to replace requestor ws on " + iface);
+            teardownInterface(iface.name);
+            return false;
+        }
+        return true;
+    }
+
+    /**
      *
      * Check if the interface is up or down.
      *
@@ -1341,27 +1511,6 @@
     }
 
     /**
-     * Get name of the client interface.
-     *
-     * This is mainly used by external modules that needs to perform some
-     * client operations on the STA interface.
-     *
-     * TODO(b/70932231): This may need to be reworked once we start supporting STA + STA.
-     *
-     * @return Interface name of any active client interface, null if no active client interface
-     * exist.
-     * Return Values for the different scenarios are listed below:
-     * a) When there are no client interfaces, returns null.
-     * b) when there is 1 client interface, returns the name of that interface.
-     * c) When there are 2 or more client interface, returns the name of any client interface.
-     */
-    public String getClientInterfaceName() {
-        synchronized (mLock) {
-            return mIfaceMgr.findAnyStaIfaceName();
-        }
-    }
-
-    /**
      * Get names of all the client interfaces.
      *
      * @return List of interface name of all active client interfaces.
@@ -1373,23 +1522,13 @@
     }
 
     /**
-     * Get name of the softap interface.
+     * Get names of all the client interfaces.
      *
-     * This is mainly used by external modules that needs to perform some
-     * operations on the AP interface.
-     *
-     * TODO(b/70932231): This may need to be reworked once we start supporting AP + AP.
-     *
-     * @return Interface name of any active softap interface, null if no active softap interface
-     * exist.
-     * Return Values for the different scenarios are listed below:
-     * a) When there are no softap interfaces, returns null.
-     * b) when there is 1 softap interface, returns the name of that interface.
-     * c) When there are 2 or more softap interface, returns the name of any softap interface.
+     * @return List of interface name of all active client interfaces.
      */
-    public String getSoftApInterfaceName() {
+    public Set<String> getSoftApInterfaceNames() {
         synchronized (mLock) {
-            return mIfaceMgr.findAnyApIfaceName();
+            return mIfaceMgr.findAllApIfaceNames();
         }
     }
 
@@ -1418,10 +1557,15 @@
      * WifiScanner.WIFI_BAND_5_GHZ
      * WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY
      * WifiScanner.WIFI_BAND_6_GHZ
+     * WifiScanner.WIFI_BAND_60_GHZ
      * @return frequencies vector of valid frequencies (MHz), or null for error.
      * @throws IllegalArgumentException if band is not recognized.
      */
     public int [] getChannelsForBand(@WifiAnnotations.WifiBandBasic int band) {
+        if (!SdkLevel.isAtLeastS() && band == WifiScanner.WIFI_BAND_60_GHZ) {
+            // 60 GHz band is new in Android S, return empty array on older SDK versions
+            return new int[0];
+        }
         return mWifiCondManager.getChannelsMhzForBand(band);
     }
 
@@ -1432,11 +1576,12 @@
      * {@link WifiScanner#SCAN_TYPE_LOW_POWER} or {@link WifiScanner#SCAN_TYPE_HIGH_ACCURACY}.
      * @param freqs list of frequencies to scan for, if null scan all supported channels.
      * @param hiddenNetworkSSIDs List of hidden networks to be scanned for.
+     * @param enable6GhzRnr whether Reduced Neighbor Report should be enabled for 6Ghz scanning.
      * @return Returns true on success.
      */
     public boolean scan(
             @NonNull String ifaceName, @WifiAnnotations.ScanType int scanType, Set<Integer> freqs,
-            List<String> hiddenNetworkSSIDs) {
+            List<String> hiddenNetworkSSIDs, boolean enable6GhzRnr) {
         List<byte[]> hiddenNetworkSsidsArrays = new ArrayList<>();
         for (String hiddenNetworkSsid : hiddenNetworkSSIDs) {
             try {
@@ -1448,7 +1593,16 @@
                 continue;
             }
         }
-        return mWifiCondManager.startScan(ifaceName, scanType, freqs, hiddenNetworkSsidsArrays);
+        // enable6GhzRnr is a new parameter first introduced in Android S.
+        if (SdkLevel.isAtLeastS()) {
+            Bundle extraScanningParams = new Bundle();
+            extraScanningParams.putBoolean(WifiNl80211Manager.SCANNING_PARAM_ENABLE_6GHZ_RNR,
+                    enable6GhzRnr);
+            return mWifiCondManager.startScan(ifaceName, scanType, freqs, hiddenNetworkSsidsArrays,
+                    extraScanningParams);
+        } else {
+            return mWifiCondManager.startScan(ifaceName, scanType, freqs, hiddenNetworkSsidsArrays);
+        }
     }
 
     /**
@@ -1458,22 +1612,79 @@
      * Returns an empty ArrayList on failure.
      */
     public ArrayList<ScanDetail> getScanResults(@NonNull String ifaceName) {
-        return convertNativeScanResults(mWifiCondManager.getScanResults(
+        if (mUseFakeScanDetails) {
+            synchronized (mFakeScanDetails) {
+                ArrayList<ScanDetail> copy = new ArrayList<>();
+                for (ScanDetail sd: mFakeScanDetails) {
+                    sd.getScanResult().ifaceName = ifaceName;
+                    // otherwise the fake will be too old
+                    sd.getScanResult().timestamp = SystemClock.elapsedRealtime() * 1000;
+
+                    // clone the ScanResult (which was updated above) so that each call gets a
+                    // unique timestamp
+                    copy.add(new ScanDetail(new ScanResult(sd.getScanResult()),
+                            sd.getNetworkDetail()));
+                }
+                return copy;
+            }
+        }
+        return convertNativeScanResults(ifaceName, mWifiCondManager.getScanResults(
                 ifaceName, WifiNl80211Manager.SCAN_TYPE_SINGLE_SCAN));
     }
 
     /**
+     * Start faking scan results - using information provided via
+     * {@link #addFakeScanDetail(ScanDetail)}. Stop with {@link #stopFakingScanDetails()}.
+     */
+    public void startFakingScanDetails() {
+        if (mBuildProperties.isUserBuild()) {
+            Log.wtf(TAG, "Can't fake scan results in a user build!");
+            return;
+        }
+        Log.d(TAG, "Starting faking scan results - " + mFakeScanDetails);
+        mUseFakeScanDetails = true;
+    }
+
+    /**
+     * Add fake scan result. Fakes are not used until activated via
+     * {@link #startFakingScanDetails()}.
+     * @param fakeScanDetail
+     */
+    public void addFakeScanDetail(@NonNull ScanDetail fakeScanDetail) {
+        synchronized (mFakeScanDetails) {
+            mFakeScanDetails.add(fakeScanDetail);
+        }
+    }
+
+    /**
+     * Reset the fake scan result list updated via {@link #addFakeScanDetail(ScanDetail)} .}
+     */
+    public void resetFakeScanDetails() {
+        synchronized (mFakeScanDetails) {
+            mFakeScanDetails.clear();
+        }
+    }
+
+    /**
+     * Stop faking scan results. Started with {@link #startFakingScanDetails()}.
+     */
+    public void stopFakingScanDetails() {
+        mUseFakeScanDetails = false;
+    }
+
+    /**
      * Fetch the latest scan result from kernel via wificond.
      * @param ifaceName Name of the interface.
      * @return Returns an ArrayList of ScanDetail.
      * Returns an empty ArrayList on failure.
      */
     public ArrayList<ScanDetail> getPnoScanResults(@NonNull String ifaceName) {
-        return convertNativeScanResults(mWifiCondManager.getScanResults(ifaceName,
+        return convertNativeScanResults(ifaceName, mWifiCondManager.getScanResults(ifaceName,
                 WifiNl80211Manager.SCAN_TYPE_PNO_SCAN));
     }
 
-    private ArrayList<ScanDetail> convertNativeScanResults(List<NativeScanResult> nativeResults) {
+    private ArrayList<ScanDetail> convertNativeScanResults(@NonNull String ifaceName,
+            List<NativeScanResult> nativeResults) {
         ArrayList<ScanDetail> results = new ArrayList<>();
         for (NativeScanResult result : nativeResults) {
             WifiSsid wifiSsid = WifiSsid.createFromByteArray(result.getSsid());
@@ -1487,7 +1698,8 @@
                     InformationElementUtil.parseInformationElements(result.getInformationElements());
             InformationElementUtil.Capabilities capabilities =
                     new InformationElementUtil.Capabilities();
-            capabilities.from(ies, result.getCapabilities(), isEnhancedOpenSupported());
+            capabilities.from(ies, result.getCapabilities(), mIsEnhancedOpenSupported,
+                              result.getFrequencyMhz());
             String flags = capabilities.generateCapabilitiesString();
             NetworkDetail networkDetail;
             try {
@@ -1502,6 +1714,7 @@
                     null, result.getInformationElements());
             ScanResult scanResult = scanDetail.getScanResult();
             scanResult.setWifiStandard(wifiModeToWifiStandard(networkDetail.getWifiMode()));
+            scanResult.ifaceName = ifaceName;
 
             // Fill up the radio chain info.
             scanResult.radioChainInfos =
@@ -1541,30 +1754,6 @@
         }
     }
 
-    private boolean mIsEnhancedOpenSupportedInitialized = false;
-    private boolean mIsEnhancedOpenSupported;
-
-    /**
-     * Check if OWE (Enhanced Open) is supported on the device
-     *
-     * @return true if OWE is supported
-     */
-    private boolean isEnhancedOpenSupported() {
-        if (mIsEnhancedOpenSupportedInitialized) {
-            return mIsEnhancedOpenSupported;
-        }
-
-        String iface = getClientInterfaceName();
-        if (iface == null) {
-            // Client interface might not be initialized during boot or Wi-Fi off
-            return false;
-        }
-
-        mIsEnhancedOpenSupportedInitialized = true;
-        mIsEnhancedOpenSupported = (getSupportedFeatureSet(iface) & WIFI_FEATURE_OWE) != 0;
-        return mIsEnhancedOpenSupported;
-    }
-
     /**
      * Start PNO scan.
      * @param ifaceName Name of the interface.
@@ -1743,20 +1932,34 @@
      *
      * @param ifaceName Name of the interface.
      * @param config Configuration to use for the soft ap created.
+     * @param isMetered Indicates the network is metered or not.
      * @param listener Callback for AP events.
      * @return true on success, false otherwise.
      */
     public boolean startSoftAp(
-            @NonNull String ifaceName, SoftApConfiguration config, SoftApListener listener) {
-        if (!mWifiCondManager.registerApCallback(ifaceName, Runnable::run, listener)) {
-            Log.e(TAG, "Failed to register ap listener");
-            return false;
+            @NonNull String ifaceName, SoftApConfiguration config, boolean isMetered,
+            SoftApListener listener) {
+        if (mHostapdHal.isApInfoCallbackSupported()) {
+            if (!mHostapdHal.registerApCallback(ifaceName, listener)) {
+                Log.e(TAG, "Failed to register ap listener");
+                return false;
+            }
+        } else {
+            SoftApListenerFromWificond softApListenerFromWificond =
+                    new SoftApListenerFromWificond(ifaceName, listener);
+            if (!mWifiCondManager.registerApCallback(ifaceName,
+                    Runnable::run, softApListenerFromWificond)) {
+                Log.e(TAG, "Failed to register ap listener from wificond");
+                return false;
+            }
         }
-        if (!mHostapdHal.addAccessPoint(ifaceName, config, listener::onFailure)) {
+
+        if (!mHostapdHal.addAccessPoint(ifaceName, config, isMetered, listener::onFailure)) {
             Log.e(TAG, "Failed to add acccess point");
             mWifiMetrics.incrementNumSetupSoftApInterfaceFailureDueToHostapd();
             return false;
         }
+
         return true;
     }
 
@@ -1779,12 +1982,22 @@
      * @param mac Mac address to change into
      * @return true on success
      */
-    public boolean setMacAddress(String interfaceName, MacAddress mac) {
+    public boolean setStaMacAddress(String interfaceName, MacAddress mac) {
         // TODO(b/72459123): Suppress interface down/up events from this call
         // Trigger an explicit disconnect to avoid losing the disconnect event reason (if currently
         // connected) from supplicant if the interface is brought down for MAC address change.
         disconnect(interfaceName);
-        return mWifiVendorHal.setMacAddress(interfaceName, mac);
+        return mWifiVendorHal.setStaMacAddress(interfaceName, mac);
+    }
+
+    /**
+     * Set MAC address of the given interface
+     * @param interfaceName Name of the interface
+     * @param mac Mac address to change into
+     * @return true on success
+     */
+    public boolean setApMacAddress(String interfaceName, MacAddress mac) {
+        return mWifiVendorHal.setApMacAddress(interfaceName, mac);
     }
 
     /**
@@ -1792,8 +2005,17 @@
      *
      * @param interfaceName Name of the interface
      */
-    public boolean isSetMacAddressSupported(@NonNull String interfaceName) {
-        return mWifiVendorHal.isSetMacAddressSupported(interfaceName);
+    public boolean isStaSetMacAddressSupported(@NonNull String interfaceName) {
+        return mWifiVendorHal.isStaSetMacAddressSupported(interfaceName);
+    }
+
+    /**
+     * Returns true if Hal version supports setMacAddress, otherwise false.
+     *
+     * @param interfaceName Name of the interface
+     */
+    public boolean isApSetMacAddressSupported(@NonNull String interfaceName) {
+        return mWifiVendorHal.isApSetMacAddressSupported(interfaceName);
     }
 
     /**
@@ -1801,8 +2023,40 @@
      * @param interfaceName Name of the interface.
      * @return factory MAC address, or null on a failed call or if feature is unavailable.
      */
-    public MacAddress getFactoryMacAddress(@NonNull String interfaceName) {
-        return mWifiVendorHal.getFactoryMacAddress(interfaceName);
+    public MacAddress getStaFactoryMacAddress(@NonNull String interfaceName) {
+        return mWifiVendorHal.getStaFactoryMacAddress(interfaceName);
+    }
+
+    /**
+     * Get the factory MAC address of the given interface
+     * @param interfaceName Name of the interface.
+     * @return factory MAC address, or null on a failed call or if feature is unavailable.
+     */
+    public MacAddress getApFactoryMacAddress(@NonNull String interfaceName) {
+        return mWifiVendorHal.getApFactoryMacAddress(interfaceName);
+    }
+
+    /**
+     * Reset MAC address to factory MAC address on the given interface
+     *
+     * @param interfaceName Name of the interface
+     * @return true for success
+     */
+    public boolean resetApMacToFactoryMacAddress(@NonNull String interfaceName) {
+        return mWifiVendorHal.resetApMacToFactoryMacAddress(interfaceName);
+    }
+
+    /**
+     * Set the unsafe channels and restrictions to avoid for coex.
+     * @param unsafeChannels List of {@link CoexUnsafeChannel} to avoid
+     * @param restrictions Bitmask of WifiManager.COEX_RESTRICTION_ flags
+     */
+    public void setCoexUnsafeChannels(
+            @NonNull List<CoexUnsafeChannel> unsafeChannels, int restrictions) {
+        mCachedCoexUnsafeChannels.clear();
+        mCachedCoexUnsafeChannels.addAll(unsafeChannels);
+        mCachedCoexRestrictions = restrictions;
+        mWifiVendorHal.setCoexUnsafeChannels(mCachedCoexUnsafeChannels, mCachedCoexRestrictions);
     }
 
     /********************************************************
@@ -1995,14 +2249,20 @@
     }
 
     /**
-     * Set country code.
+     * Set country code for STA interface
      *
-     * @param ifaceName Name of the interface.
+     * @param ifaceName Name of the STA interface.
      * @param countryCode 2 byte ASCII string. For ex: US, CA.
      * @return true if request is sent successfully, false otherwise.
      */
-    public boolean setCountryCode(@NonNull String ifaceName, String countryCode) {
-        return mSupplicantStaIfaceHal.setCountryCode(ifaceName, countryCode);
+    public boolean setStaCountryCode(@NonNull String ifaceName, String countryCode) {
+        if (mSupplicantStaIfaceHal.setCountryCode(ifaceName, countryCode)) {
+            if (mCountryCodeChangeListener != null) {
+                mCountryCodeChangeListener.onSetCountryCodeSucceeded(countryCode);
+            }
+            return true;
+        }
+        return false;
     }
 
     /**
@@ -2163,7 +2423,26 @@
      * @return anonymous identity string if succeeds, null otherwise.
      */
     public String getEapAnonymousIdentity(@NonNull String ifaceName) {
-        return mSupplicantStaIfaceHal.getCurrentNetworkEapAnonymousIdentity(ifaceName);
+        String anonymousIdentity = mSupplicantStaIfaceHal
+                .getCurrentNetworkEapAnonymousIdentity(ifaceName);
+
+        if (TextUtils.isEmpty(anonymousIdentity)) {
+            return anonymousIdentity;
+        }
+
+        int indexOfDecoration = anonymousIdentity.lastIndexOf('!');
+        if (indexOfDecoration >= 0) {
+            if (anonymousIdentity.substring(indexOfDecoration).length() < 2) {
+                // Invalid identity, shouldn't happen
+                Log.e(TAG, "Unexpected anonymous identity: " + anonymousIdentity);
+                return null;
+            }
+            // Truncate RFC 7542 decorated prefix, if exists. Keep only the anonymous identity or
+            // pseudonym.
+            anonymousIdentity = anonymousIdentity.substring(indexOfDecoration + 1);
+        }
+
+        return anonymousIdentity;
     }
 
     /**
@@ -2357,12 +2636,21 @@
     }
 
     /**
+     * Disable the currently configured network in supplicant
+     *
+     * @param ifaceName Name of the interface.
+     */
+    public boolean disableNetwork(@NonNull String ifaceName) {
+        return mSupplicantStaIfaceHal.disableCurrentNetwork(ifaceName);
+    }
+
+    /**
      * Set the BSSID for the currently configured network in wpa_supplicant.
      *
      * @param ifaceName Name of the interface.
      * @return true if successful, false otherwise.
      */
-    public boolean setConfiguredNetworkBSSID(@NonNull String ifaceName, String bssid) {
+    public boolean setNetworkBSSID(@NonNull String ifaceName, String bssid) {
         return mSupplicantStaIfaceHal.setCurrentNetworkBssid(ifaceName, bssid);
     }
 
@@ -2410,6 +2698,22 @@
     }
 
     /**
+     * Initiate Venue URL ANQP query.
+     *
+     * @param ifaceName Name of the interface.
+     * @param bssid BSSID of the AP to be queried
+     * @return true on success, false otherwise.
+     */
+    public boolean requestVenueUrlAnqp(
+            @NonNull String ifaceName, String bssid) {
+        if (bssid == null) {
+            Log.e(TAG, "Invalid arguments for Venue URL ANQP request.");
+            return false;
+        }
+        return mSupplicantStaIfaceHal.initiateVenueUrlAnqpQuery(ifaceName, bssid);
+    }
+
+    /**
      * Get the currently configured network's WPS NFC token.
      *
      * @param ifaceName Name of the interface.
@@ -2545,6 +2849,57 @@
     }
 
     /**
+     * Class to get generated bootstrap info for DPP responder operation.
+     */
+    public static class DppBootstrapQrCodeInfo {
+        public int bootstrapId;
+        public int listenChannel;
+        public String uri = new String();
+        DppBootstrapQrCodeInfo() {
+            bootstrapId = -1;
+            listenChannel = -1;
+        }
+    }
+
+    /**
+     * Generate DPP bootstrap Information:Bootstrap ID, DPP URI and the listen channel.
+     *
+     * @param ifaceName Interface name
+     * @param deviceInfo Device specific info to attach in DPP URI.
+     * @param dppCurve Elliptic curve cryptography type used to generate DPP
+     *                 public/private key pair.
+     * @return ID, or -1 for failure
+     */
+    public DppBootstrapQrCodeInfo generateDppBootstrapInfoForResponder(@NonNull String ifaceName,
+            String deviceInfo, int dppCurve) {
+        return mSupplicantStaIfaceHal.generateDppBootstrapInfoForResponder(ifaceName,
+                getMacAddress(ifaceName), deviceInfo, dppCurve);
+    }
+
+    /**
+     * start DPP Enrollee responder mode.
+     *
+     * @param ifaceName Interface name
+     * @param listenChannel Listen channel to wait for DPP authentication request.
+     * @return ID, or -1 for failure
+     */
+    public boolean startDppEnrolleeResponder(@NonNull String ifaceName, int listenChannel) {
+        return mSupplicantStaIfaceHal.startDppEnrolleeResponder(ifaceName, listenChannel);
+    }
+
+    /**
+     * Stops/aborts DPP Responder request
+     *
+     * @param ifaceName Interface name
+     * @param ownBootstrapId Bootstrap (URI) ID
+     * @return true when operation is successful, or false for failure
+     */
+    public boolean stopDppResponder(@NonNull String ifaceName, int ownBootstrapId)  {
+        return mSupplicantStaIfaceHal.stopDppResponder(ifaceName, ownBootstrapId);
+    }
+
+
+    /**
      * Registers DPP event callbacks.
      *
      * @param dppEventCallback Callback object.
@@ -2685,6 +3040,7 @@
         public int report_threshold_percent;
         public int report_threshold_num_scans;
         public int num_buckets;
+        public boolean enable6GhzRnr;
         /* Not used for bg scans. Only works for single scans. */
         public HiddenNetwork[] hiddenNetworks;
         public BucketSettings[] buckets;
@@ -2866,7 +3222,22 @@
     }
 
     /**
-     * Returns whether STA/AP concurrency is supported or not.
+     * Gets the usable channels
+     * @param band one of the {@code WifiScanner#WIFI_BAND_*} constants.
+     * @param mode bitmask of {@code WifiAvailablechannel#OP_MODE_*} constants.
+     * @param filter bitmask of filters (regulatory, coex, concurrency).
+     *
+     * @return list of channels
+     */
+    public List<WifiAvailableChannel> getUsableChannels(
+            @WifiScanner.WifiBand int band,
+            @WifiAvailableChannel.OpMode int mode,
+            @WifiAvailableChannel.Filter int filter) {
+        return mWifiVendorHal.getUsableChannels(band, mode, filter);
+    }
+
+    /**
+     * Returns whether STA + AP concurrency is supported or not.
      */
     public boolean isStaApConcurrencySupported() {
         synchronized (mLock) {
@@ -2875,6 +3246,67 @@
     }
 
     /**
+     * Returns whether STA + STA concurrency is supported or not.
+     */
+    public boolean isStaStaConcurrencySupported() {
+        synchronized (mLock) {
+            return mWifiVendorHal.isStaStaConcurrencySupported();
+        }
+    }
+
+    /**
+     * Returns whether a new AP iface can be created or not.
+     */
+    public boolean isItPossibleToCreateApIface(@NonNull WorkSource requestorWs) {
+        synchronized (mLock) {
+            return mWifiVendorHal.isItPossibleToCreateApIface(requestorWs);
+        }
+    }
+
+    /**
+     * Returns whether a new STA iface can be created or not.
+     */
+    public boolean isItPossibleToCreateStaIface(@NonNull WorkSource requestorWs) {
+        synchronized (mLock) {
+            return mWifiVendorHal.isItPossibleToCreateStaIface(requestorWs);
+        }
+    }
+
+    /**
+     * Set primary connection when multiple STA ifaces are active.
+     *
+     * @param ifaceName Name of the interface.
+     * @return true for success
+     */
+    public boolean setMultiStaPrimaryConnection(@NonNull String ifaceName) {
+        synchronized (mLock) {
+            return mWifiVendorHal.setMultiStaPrimaryConnection(ifaceName);
+        }
+    }
+
+    /**
+     * Multi STA use case flags.
+     */
+    public static final int DUAL_STA_TRANSIENT_PREFER_PRIMARY = 0;
+    public static final int DUAL_STA_NON_TRANSIENT_UNBIASED = 1;
+
+    @IntDef({DUAL_STA_TRANSIENT_PREFER_PRIMARY, DUAL_STA_NON_TRANSIENT_UNBIASED})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface MultiStaUseCase{}
+
+    /**
+     * Set use-case when multiple STA ifaces are active.
+     *
+     * @param useCase one of the use cases.
+     * @return true for success
+     */
+    public boolean setMultiStaUseCase(@MultiStaUseCase int useCase) {
+        synchronized (mLock) {
+            return mWifiVendorHal.setMultiStaUseCase(useCase);
+        }
+    }
+
+    /**
      * Get the supported features
      *
      * @param ifaceName Name of the interface.
@@ -2899,7 +3331,7 @@
      * @return bitmask defined by WifiManager.WIFI_FEATURE_*
      */
     private long getSupportedFeatureSetInternal(@NonNull String ifaceName) {
-        return mSupplicantStaIfaceHal.getAdvancedKeyMgmtCapabilities(ifaceName)
+        return mSupplicantStaIfaceHal.getAdvancedCapabilities(ifaceName)
                 | mWifiVendorHal.getSupportedFeatureSet(ifaceName)
                 | mSupplicantStaIfaceHal.getWpaDriverFeatureSet(ifaceName);
     }
@@ -2912,11 +3344,13 @@
         public int channelBandwidth;
         public int maxNumberTxSpatialStreams;
         public int maxNumberRxSpatialStreams;
+        public boolean is11bMode;
         ConnectionCapabilities() {
             wifiStandard = ScanResult.WIFI_STANDARD_UNKNOWN;
             channelBandwidth = ScanResult.CHANNEL_WIDTH_20MHZ;
             maxNumberTxSpatialStreams = 1;
             maxNumberRxSpatialStreams = 1;
+            is11bMode = false;
         }
     }
 
@@ -2961,12 +3395,33 @@
 
     /**
      * Set country code for this AP iface.
-     * @param ifaceName Name of the interface.
+     * @param ifaceName Name of the AP interface.
      * @param countryCode - two-letter country code (as ISO 3166)
      * @return true for success
      */
-    public boolean setCountryCodeHal(@NonNull String ifaceName, String countryCode) {
-        return mWifiVendorHal.setCountryCodeHal(ifaceName, countryCode);
+    public boolean setApCountryCode(@NonNull String ifaceName, String countryCode) {
+        if (mWifiVendorHal.setApCountryCode(ifaceName, countryCode)) {
+            if (mCountryCodeChangeListener != null) {
+                mCountryCodeChangeListener.onSetCountryCodeSucceeded(countryCode);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Set country code for this chip
+     * @param countryCode - two-letter country code (as ISO 3166)
+     * @return true for success
+     */
+    public boolean setChipCountryCode(String countryCode) {
+        if (mWifiVendorHal.setChipCountryCode(countryCode)) {
+            if (mCountryCodeChangeListener != null) {
+                mCountryCodeChangeListener.onSetCountryCodeSucceeded(countryCode);
+            }
+            return true;
+        }
+        return false;
     }
 
     //---------------------------------------------------------------------------------
@@ -3323,18 +3778,21 @@
      * Fetch the most recent TX packet fates from the HAL. Fails unless HAL is started.
      *
      * @param ifaceName Name of the interface.
-     * @return true for success, false otherwise.
+     * @return TxFateReport list on success, empty list on failure. Never returns null.
      */
-    public boolean getTxPktFates(@NonNull String ifaceName, TxFateReport[] reportBufs) {
-        return mWifiVendorHal.getTxPktFates(ifaceName, reportBufs);
+    @NonNull
+    public List<TxFateReport> getTxPktFates(@NonNull String ifaceName) {
+        return mWifiVendorHal.getTxPktFates(ifaceName);
     }
 
     /**
      * Fetch the most recent RX packet fates from the HAL. Fails unless HAL is started.
      * @param ifaceName Name of the interface.
+     * @return RxFateReport list on success, empty list on failure. Never returns null.
      */
-    public boolean getRxPktFates(@NonNull String ifaceName, RxFateReport[] reportBufs) {
-        return mWifiVendorHal.getRxPktFates(ifaceName, reportBufs);
+    @NonNull
+    public List<RxFateReport> getRxPktFates(@NonNull String ifaceName) {
+        return mWifiVendorHal.getRxPktFates(ifaceName);
     }
 
     /**
@@ -3449,11 +3907,11 @@
     /**
      * Query the firmware roaming capabilities.
      * @param ifaceName Name of the interface.
-     * @return true for success, false otherwise.
+     * @return capabilities object on success, null otherwise.
      */
-    public boolean getRoamingCapabilities(
-            @NonNull String ifaceName, RoamingCapabilities capabilities) {
-        return mWifiVendorHal.getRoamingCapabilities(ifaceName, capabilities);
+    @Nullable
+    public RoamingCapabilities getRoamingCapabilities(@NonNull String ifaceName) {
+        return mWifiVendorHal.getRoamingCapabilities(ifaceName);
     }
 
     /**
@@ -3462,6 +3920,10 @@
     public static final int DISABLE_FIRMWARE_ROAMING = 0;
     public static final int ENABLE_FIRMWARE_ROAMING = 1;
 
+    @IntDef({ENABLE_FIRMWARE_ROAMING, DISABLE_FIRMWARE_ROAMING})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RoamingEnableState {}
+
     /**
      * Indicates success for enableFirmwareRoaming
      */
@@ -3477,6 +3939,10 @@
      */
     public static final int SET_FIRMWARE_ROAMING_BUSY = 2;
 
+    @IntDef({SET_FIRMWARE_ROAMING_SUCCESS, SET_FIRMWARE_ROAMING_FAILURE, SET_FIRMWARE_ROAMING_BUSY})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RoamingEnableStatus {}
+
     /**
      * Enable/disable firmware roaming.
      *
@@ -3484,7 +3950,8 @@
      * @return SET_FIRMWARE_ROAMING_SUCCESS, SET_FIRMWARE_ROAMING_FAILURE,
      *         or SET_FIRMWARE_ROAMING_BUSY
      */
-    public int enableFirmwareRoaming(@NonNull String ifaceName, int state) {
+    public @RoamingEnableStatus int enableFirmwareRoaming(@NonNull String ifaceName,
+            @RoamingEnableState int state) {
         return mWifiVendorHal.enableFirmwareRoaming(ifaceName, state);
     }
 
@@ -3534,7 +4001,6 @@
      */
     public void setMboCellularDataStatus(@NonNull String ifaceName, boolean available) {
         mSupplicantStaIfaceHal.setMboCellularDataStatus(ifaceName, available);
-        return;
     }
 
     /**
@@ -3594,4 +4060,47 @@
             iface.phyCapabilities = capabilities;
         }
     }
+
+    /**
+     * Notify scan mode state to driver to save power in scan-only mode.
+     *
+     * @param ifaceName Name of the interface.
+     * @param enable whether is in scan-only mode
+     * @return true for success
+     */
+    public boolean setScanMode(String ifaceName, boolean enable) {
+        return mWifiVendorHal.setScanMode(ifaceName, enable);
+    }
+
+    /** updates linked networks of the |networkId| in supplicant if it's the current network,
+     * if the current configured network matches |networkId|.
+     *
+     * @param ifaceName Name of the interface.
+     * @param networkId network id of the network to be updated from supplicant.
+     * @param linkedNetworks Map of config profile key and config for linking.
+     */
+    public boolean updateLinkedNetworks(@NonNull String ifaceName, int networkId,
+            Map<String, WifiConfiguration> linkedNetworks) {
+        return mSupplicantStaIfaceHal.updateLinkedNetworks(ifaceName, networkId, linkedNetworks);
+    }
+
+    /**
+     * Start Subsystem Restart
+     * @return true on success
+     */
+    public boolean startSubsystemRestart() {
+        return mWifiVendorHal.startSubsystemRestart();
+    }
+
+    /**
+     * Register the provided listener for country code event.
+     *
+     * @param listener listener for country code changed events.
+     */
+    public void registerCountryCodeEventListener(WifiCountryCode.ChangeListener listener) {
+        registerWificondListenerIfNecessary();
+        if (mCountryCodeChangeListener != null) {
+            mCountryCodeChangeListener.setChangeListener(listener);
+        }
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiNetworkAgent.java b/service/java/com/android/server/wifi/WifiNetworkAgent.java
new file mode 100644
index 0000000..b20387a
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiNetworkAgent.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.KeepalivePacketData;
+import android.net.LinkProperties;
+import android.net.NetworkAgent;
+import android.net.NetworkAgentConfig;
+import android.net.NetworkCapabilities;
+import android.net.NetworkProvider;
+import android.net.Uri;
+import android.os.Looper;
+
+import java.time.Duration;
+
+/** Created to facilitate mocking during unit testing. */
+public class WifiNetworkAgent extends NetworkAgent {
+
+    private static final String TAG = "WifiNetworkAgent";
+
+    public interface Callback {
+        void onNetworkUnwanted();
+        void onValidationStatus(int status, @Nullable Uri redirectUri);
+        void onSaveAcceptUnvalidated(boolean accept);
+        void onStartSocketKeepalive(int slot, @NonNull Duration interval,
+                @NonNull KeepalivePacketData packet);
+        void onStopSocketKeepalive(int slot);
+        void onAddKeepalivePacketFilter(int slot, @NonNull KeepalivePacketData packet);
+        void onRemoveKeepalivePacketFilter(int slot);
+        void onSignalStrengthThresholdsUpdated(@NonNull int[] thresholds);
+        void onAutomaticReconnectDisabled();
+    }
+
+    private final Callback mCallback;
+
+    /** Cache the latest NetworkCapabilities update */
+    @NonNull private NetworkCapabilities mCurrentNetworkCapabilities;
+
+    /**
+     * Create a new network agent.
+     *
+     * @param context  a {@link Context} to get system services from.
+     * @param looper   the {@link Looper} on which to invoke the callbacks.
+     * @param nc       the initial {@link NetworkCapabilities} of this network. Update with
+     *                 sendNetworkCapabilities.
+     * @param lp       the initial {@link LinkProperties} of this network. Update with
+     *                 sendLinkProperties.
+     * @param score    the initial score of this network. Update with sendNetworkScore.
+     * @param config   an immutable {@link NetworkAgentConfig} for this agent.
+     * @param provider the {@link NetworkProvider} managing this agent.
+     * @param wifiNetworkAgentCallback implementation
+     */
+    public WifiNetworkAgent(
+            @NonNull Context context,
+            @NonNull Looper looper,
+            @NonNull NetworkCapabilities nc,
+            @NonNull LinkProperties lp,
+            @NonNull NetworkAgentConfig config,
+            @Nullable NetworkProvider provider,
+            @NonNull Callback wifiNetworkAgentCallback) {
+        super(context, looper, TAG, nc, lp, ConnectedScore.WIFI_INITIAL_SCORE, config, provider);
+        mCurrentNetworkCapabilities = nc;
+        mCallback = wifiNetworkAgentCallback;
+        register();
+    }
+
+    @Override
+    public void onNetworkUnwanted() {
+        mCallback.onNetworkUnwanted();
+    }
+
+    @Override
+    public void onValidationStatus(int status, @Nullable Uri redirectUri) {
+        mCallback.onValidationStatus(status, redirectUri);
+    }
+
+    @Override
+    public void onSaveAcceptUnvalidated(boolean accept) {
+        mCallback.onSaveAcceptUnvalidated(accept);
+    }
+
+    @Override
+    public void onStartSocketKeepalive(int slot, @NonNull Duration interval,
+            @NonNull KeepalivePacketData packet) {
+        mCallback.onStartSocketKeepalive(slot, interval, packet);
+    }
+
+    @Override
+    public void onStopSocketKeepalive(int slot) {
+        mCallback.onStopSocketKeepalive(slot);
+    }
+
+    @Override
+    public void onAddKeepalivePacketFilter(int slot, @NonNull KeepalivePacketData packet) {
+        mCallback.onAddKeepalivePacketFilter(slot, packet);
+    }
+
+    @Override
+    public void onRemoveKeepalivePacketFilter(int slot) {
+        mCallback.onRemoveKeepalivePacketFilter(slot);
+    }
+
+    @Override
+    public void onSignalStrengthThresholdsUpdated(@NonNull int[] thresholds) {
+        mCallback.onSignalStrengthThresholdsUpdated(thresholds);
+    }
+
+    @Override
+    public void onAutomaticReconnectDisabled() {
+        mCallback.onAutomaticReconnectDisabled();
+    }
+
+    @NonNull
+    public Callback getCallback() {
+        return mCallback;
+    }
+
+    /** sendNetworkCapabilities is final in NetworkAgent, so can't override that method directly */
+    public void sendNetworkCapabilitiesAndCache(@NonNull NetworkCapabilities nc) {
+        mCurrentNetworkCapabilities = nc;
+        super.sendNetworkCapabilities(nc);
+    }
+
+    @NonNull
+    public NetworkCapabilities getCurrentNetworkCapabilities() {
+        return mCurrentNetworkCapabilities;
+    }
+}
diff --git a/service/java/com/android/server/wifi/WifiNetworkFactory.java b/service/java/com/android/server/wifi/WifiNetworkFactory.java
index bd63987..c757641 100644
--- a/service/java/com/android/server/wifi/WifiNetworkFactory.java
+++ b/service/java/com/android/server/wifi/WifiNetworkFactory.java
@@ -17,16 +17,22 @@
 package com.android.server.wifi;
 
 import static com.android.internal.util.Preconditions.checkNotNull;
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_LOCAL_ONLY;
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_PRIMARY;
 import static com.android.server.wifi.util.NativeUtil.addEnclosingQuotes;
 
+import static java.lang.Math.toIntExact;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.AppOpsManager;
 import android.companion.CompanionDeviceManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.net.MacAddress;
@@ -38,16 +44,17 @@
 import android.net.wifi.INetworkRequestMatchCallback;
 import android.net.wifi.INetworkRequestUserSelectionCallback;
 import android.net.wifi.ScanResult;
+import android.net.wifi.SecurityParams;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConfiguration.SecurityType;
 import android.net.wifi.WifiNetworkSpecifier;
 import android.net.wifi.WifiScanner;
-import android.os.Binder;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.Looper;
 import android.os.PatternMatcher;
+import android.os.PowerManager;
 import android.os.Process;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.WorkSource;
@@ -58,9 +65,10 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.modules.utils.HandlerExecutor;
 import com.android.server.wifi.proto.nano.WifiMetricsProto;
-import com.android.server.wifi.util.ExternalCallbackTracker;
+import com.android.server.wifi.util.ActionListenerWrapper;
 import com.android.server.wifi.util.ScanResultUtil;
 import com.android.server.wifi.util.WifiPermissionsUtil;
+import com.android.wifi.resources.R;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -75,6 +83,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 /**
@@ -85,7 +94,7 @@
     @VisibleForTesting
     private static final int SCORE_FILTER = 60;
     @VisibleForTesting
-    public static final int CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS = 20 * 1000;  // 20 seconds
+    public static final int CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS = 30 * 1000;
     @VisibleForTesting
     public static final int PERIODIC_SCAN_INTERVAL_MS = 10 * 1000; // 10 seconds
     @VisibleForTesting
@@ -119,15 +128,20 @@
     private final WifiConfigStore mWifiConfigStore;
     private final WifiPermissionsUtil mWifiPermissionsUtil;
     private final WifiMetrics mWifiMetrics;
+    private final ActiveModeWarden mActiveModeWarden;
     private final WifiScanner.ScanSettings mScanSettings;
     private final NetworkFactoryScanListener mScanListener;
     private final PeriodicScanAlarmListener mPeriodicScanTimerListener;
     private final ConnectionTimeoutAlarmListener mConnectionTimeoutAlarmListener;
-    private final ExternalCallbackTracker<INetworkRequestMatchCallback> mRegisteredCallbacks;
+    private final ConnectHelper mConnectHelper;
+    private final ClientModeImplMonitor mClientModeImplMonitor;
+    private RemoteCallbackList<INetworkRequestMatchCallback> mRegisteredCallbacks;
     // Store all user approved access points for apps.
     @VisibleForTesting
     public final Map<String, LinkedHashSet<AccessPoint>> mUserApprovedAccessPointMap;
     private WifiScanner mWifiScanner;
+    @Nullable private ClientModeManager mClientModeManager;
+    @Nullable private ActiveModeManager.ClientRole mClientModeManagerRole;
     private CompanionDeviceManager mCompanionDeviceManager;
     // Temporary approval set by shell commands.
     @Nullable private String mApprovedApp = null;
@@ -147,6 +161,13 @@
     //  - null, if there are no active requests.
     //  - empty, if there are no matching scan results received for the active request.
     @Nullable private Map<String, ScanResult> mActiveMatchedScanResults;
+    /** Connection start time to keep track of connection duration */
+    private long mConnectionStartTimeMillis = -1L;
+    /**
+     * CMI listener used for concurrent connection metrics collection.
+     * Not used when the connection is on primary STA (i.e not STA + STA).
+     */
+    @Nullable private CmiListener mCmiListener;
     // Verbose logging flag.
     private boolean mVerboseLoggingEnabled = false;
     private boolean mPeriodicScanTimerSet = false;
@@ -155,7 +176,6 @@
     private boolean mIsPeriodicScanPaused = false;
     // We sent a new connection request and are waiting for connection success.
     private boolean mPendingConnectionSuccess = false;
-    private boolean mWifiEnabled = false;
     /**
      * Indicates that we have new data to serialize.
      */
@@ -273,7 +293,7 @@
         @Override
         public void onAlarm() {
             Log.e(TAG, "Timed-out connecting to network");
-            handleNetworkConnectionFailure(mUserSelectedNetwork);
+            handleNetworkConnectionFailure(mUserSelectedNetwork, mUserSelectedNetwork.BSSID);
             mConnectionTimeoutSet = false;
         }
     }
@@ -321,7 +341,64 @@
         @Override
         public void onFailure(int reason) {
             Log.e(TAG, "Failed to trigger network connection");
-            handleNetworkConnectionFailure(mUserSelectedNetwork);
+            handleNetworkConnectionFailure(mUserSelectedNetwork, mUserSelectedNetwork.BSSID);
+        }
+    }
+
+    private final class ClientModeManagerRequestListener implements
+            ActiveModeWarden.ExternalClientModeManagerRequestListener {
+        @Override
+        public void onAnswer(@Nullable ClientModeManager modeManager) {
+            if (modeManager != null) {
+                // Remove the mode manager if the associated request is no longer active.
+                if (mActiveSpecificNetworkRequest == null
+                        && mConnectedSpecificNetworkRequest == null) {
+                    Log.w(TAG, "Client mode manager request answer received with no active"
+                            + " requests");
+                    mActiveModeWarden.removeClientModeManager(modeManager);
+                    return;
+                }
+                mClientModeManager = modeManager;
+                mClientModeManagerRole = modeManager.getRole();
+                handleClientModeManagerRetrieval();
+            } else {
+                handleClientModeManagerRemovalOrFailure();
+            }
+        }
+    }
+
+    private class ModeChangeCallback implements ActiveModeWarden.ModeChangeCallback {
+        @Override
+        public void onActiveModeManagerAdded(@NonNull ActiveModeManager activeModeManager) {
+            // ignored.
+            // Will get a dedicated ClientModeManager instance for our request via
+            // ClientModeManagerRequestListener.
+        }
+
+        @Override
+        public void onActiveModeManagerRemoved(@NonNull ActiveModeManager activeModeManager) {
+            if (!(activeModeManager instanceof ClientModeManager)) return;
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "ModeManager removed " + activeModeManager.getInterfaceName());
+            }
+            // Mode manager removed. Cleanup any ongoing requests.
+            if (activeModeManager == mClientModeManager
+                    || !mActiveModeWarden.hasPrimaryClientModeManager()) {
+                handleClientModeManagerRemovalOrFailure();
+            }
+        }
+
+        @Override
+        public void onActiveModeManagerRoleChanged(@NonNull ActiveModeManager activeModeManager) {
+            if (!(activeModeManager instanceof ClientModeManager)) return;
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "ModeManager role changed " + activeModeManager.getInterfaceName());
+            }
+            // Mode manager role changed. Cleanup any ongoing requests.
+            if (activeModeManager == mClientModeManager
+                    || !mActiveModeWarden.hasPrimaryClientModeManager()) {
+                handleClientModeManagerRemovalOrFailure();
+            }
         }
     }
 
@@ -353,6 +430,74 @@
         }
     }
 
+    /**
+     * To keep track of concurrent connections using this API surface (for metrics collection only).
+     *
+     * Only used if the connection is initiated on secondary STA.
+     */
+    private class CmiListener implements ClientModeImplListener {
+        /** Concurrent connection start time to keep track of connection duration */
+        private long mConcurrentConnectionStartTimeMillis = -1L;
+        /** Whether we have already indicated the presence of concurrent connection */
+        private boolean mHasAlreadyIncrementedConcurrentConnectionCount = false;
+
+        private boolean isLocalOnlyOrPrimary(@NonNull ClientModeManager cmm) {
+            return cmm.getRole() == ROLE_CLIENT_PRIMARY
+                    || cmm.getRole() == ROLE_CLIENT_LOCAL_ONLY;
+        }
+
+        private void checkForConcurrencyStartAndIncrementMetrics() {
+            int numLocalOnlyOrPrimaryConnectedCmms = 0;
+            for (ClientModeManager cmm : mActiveModeWarden.getClientModeManagers()) {
+                if (isLocalOnlyOrPrimary(cmm) && cmm.isConnected()) {
+                    numLocalOnlyOrPrimaryConnectedCmms++;
+                }
+            }
+            if (numLocalOnlyOrPrimaryConnectedCmms > 1) {
+                mConcurrentConnectionStartTimeMillis = mClock.getElapsedSinceBootMillis();
+                // Note: We could have multiple connect/disconnect of the primary connection
+                // while remaining connected to the local only connection. We want to keep track
+                // of the connection durations accurately across those disconnects. However, we do
+                // not want to increment the connection count metric since that should be a 1:1
+                // mapping with the number of requests processed (i.e don't indicate 2 concurrent
+                // connection count if the primary disconnected & connected back while processing
+                // the same local only request).
+                if (!mHasAlreadyIncrementedConcurrentConnectionCount) {
+                    mWifiMetrics.incrementNetworkRequestApiNumConcurrentConnection();
+                    mHasAlreadyIncrementedConcurrentConnectionCount = true;
+                }
+            }
+        }
+
+        public void checkForConcurrencyEndAndIncrementMetrics() {
+            if (mConcurrentConnectionStartTimeMillis != -1L) {
+                mWifiMetrics.incrementNetworkRequestApiConcurrentConnectionDurationSecHistogram(
+                        toIntExact(TimeUnit.MILLISECONDS.toSeconds(
+                                mClock.getElapsedSinceBootMillis()
+                                        - mConcurrentConnectionStartTimeMillis)));
+                mConcurrentConnectionStartTimeMillis = -1L;
+            }
+        }
+
+        CmiListener() {
+            checkForConcurrencyStartAndIncrementMetrics();
+        }
+
+        @Override
+        public void onL3Connected(@NonNull ConcreteClientModeManager clientModeManager) {
+            if (isLocalOnlyOrPrimary(clientModeManager)) {
+                checkForConcurrencyStartAndIncrementMetrics();
+            }
+        }
+
+        @Override
+        public void onConnectionEnd(@NonNull ConcreteClientModeManager clientModeManager) {
+            if (isLocalOnlyOrPrimary(clientModeManager)) {
+                checkForConcurrencyEndAndIncrementMetrics();
+            }
+        }
+    }
+
     public WifiNetworkFactory(Looper looper, Context context, NetworkCapabilities nc,
             ActivityManager activityManager, AlarmManager alarmManager,
             AppOpsManager appOpsManager,
@@ -361,7 +506,10 @@
             WifiConfigManager configManager,
             WifiConfigStore configStore,
             WifiPermissionsUtil wifiPermissionsUtil,
-            WifiMetrics wifiMetrics) {
+            WifiMetrics wifiMetrics,
+            ActiveModeWarden activeModeWarden,
+            ConnectHelper connectHelper,
+            ClientModeImplMonitor clientModeImplMonitor) {
         super(looper, context, TAG, nc);
         mContext = context;
         mActivityManager = activityManager;
@@ -375,6 +523,9 @@
         mWifiConfigStore = configStore;
         mWifiPermissionsUtil = wifiPermissionsUtil;
         mWifiMetrics = wifiMetrics;
+        mActiveModeWarden = activeModeWarden;
+        mConnectHelper = connectHelper;
+        mClientModeImplMonitor = clientModeImplMonitor;
         // Create the scan settings.
         mScanSettings = new WifiScanner.ScanSettings();
         mScanSettings.type = WifiScanner.SCAN_TYPE_HIGH_ACCURACY;
@@ -383,13 +534,31 @@
         mScanListener = new NetworkFactoryScanListener();
         mPeriodicScanTimerListener = new PeriodicScanAlarmListener();
         mConnectionTimeoutAlarmListener = new ConnectionTimeoutAlarmListener();
-        mRegisteredCallbacks = new ExternalCallbackTracker<INetworkRequestMatchCallback>(mHandler);
         mUserApprovedAccessPointMap = new HashMap<>();
 
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_SCREEN_ON);
+        filter.addAction(Intent.ACTION_SCREEN_OFF);
+        context.registerReceiver(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        String action = intent.getAction();
+                        if (action.equals(Intent.ACTION_SCREEN_ON)) {
+                            handleScreenStateChanged(true);
+                        } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
+                            handleScreenStateChanged(false);
+                        }
+                    }
+                }, filter, null, mHandler);
+        handleScreenStateChanged(context.getSystemService(PowerManager.class).isInteractive());
+
         // register the data store for serializing/deserializing data.
         configStore.registerStoreData(
                 wifiInjector.makeNetworkRequestStoreData(new NetworkRequestDataSource()));
 
+        activeModeWarden.registerModeChangeCallback(new ModeChangeCallback());
+
         setScoreFilter(SCORE_FILTER);
     }
 
@@ -404,15 +573,14 @@
     /**
      * Enable verbose logging.
      */
-    public void enableVerboseLogging(int verbose) {
-        mVerboseLoggingEnabled = (verbose > 0);
+    public void enableVerboseLogging(boolean verbose) {
+        mVerboseLoggingEnabled = verbose;
     }
 
     /**
      * Add a new callback for network request match handling.
      */
-    public void addCallback(IBinder binder, INetworkRequestMatchCallback callback,
-            int callbackIdentifier) {
+    public void addCallback(INetworkRequestMatchCallback callback) {
         if (mActiveSpecificNetworkRequest == null) {
             Log.wtf(TAG, "No valid network request. Ignoring callback registration");
             try {
@@ -422,12 +590,16 @@
             }
             return;
         }
-        if (!mRegisteredCallbacks.add(binder, callback, callbackIdentifier)) {
+        if (mRegisteredCallbacks == null) {
+            mRegisteredCallbacks = new RemoteCallbackList<>();
+        }
+        if (!mRegisteredCallbacks.register(callback)) {
             Log.e(TAG, "Failed to add callback");
             return;
         }
         if (mVerboseLoggingEnabled) {
-            Log.v(TAG, "Adding callback. Num callbacks: " + mRegisteredCallbacks.getNumCallbacks());
+            Log.v(TAG, "Adding callback. Num callbacks: "
+                    + mRegisteredCallbacks.getRegisteredCallbackCount());
         }
         // Register our user selection callback.
         try {
@@ -449,11 +621,12 @@
     /**
      * Remove an existing callback for network request match handling.
      */
-    public void removeCallback(int callbackIdentifier) {
-        mRegisteredCallbacks.remove(callbackIdentifier);
+    public void removeCallback(INetworkRequestMatchCallback callback) {
+        if (mRegisteredCallbacks == null) return;
+        mRegisteredCallbacks.unregister(callback);
         if (mVerboseLoggingEnabled) {
             Log.v(TAG, "Removing callback. Num callbacks: "
-                    + mRegisteredCallbacks.getNumCallbacks());
+                    + mRegisteredCallbacks.getRegisteredCallbackCount());
         }
     }
 
@@ -474,13 +647,7 @@
         return false;
     }
 
-    boolean isRequestWithNetworkSpecifierValid(NetworkRequest networkRequest) {
-        NetworkSpecifier ns = networkRequest.getNetworkSpecifier();
-        // Invalid network specifier.
-        if (!(ns instanceof WifiNetworkSpecifier)) {
-            Log.e(TAG, "Invalid network specifier mentioned. Rejecting");
-            return false;
-        }
+    boolean isRequestWithWifiNetworkSpecifierValid(NetworkRequest networkRequest) {
         // Request cannot have internet capability since such a request can never be fulfilled.
         // (NetworkAgent for connection with WifiNetworkSpecifier will not have internet capability)
         if (networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
@@ -505,9 +672,13 @@
                     + networkRequest.getRequestorPackageName() + ". Rejecting", e);
             return false;
         }
-        WifiNetworkSpecifier wns = (WifiNetworkSpecifier) ns;
+        WifiNetworkSpecifier wns = (WifiNetworkSpecifier) networkRequest.getNetworkSpecifier();
+        if (wns.getBand() != ScanResult.UNSPECIFIED) {
+            Log.e(TAG, "Requesting specific frequency bands is not yet supported. Rejecting");
+            return false;
+        }
         if (!WifiConfigurationUtil.validateNetworkSpecifier(wns)) {
-            Log.e(TAG, "Invalid network specifier. Rejecting ");
+            Log.e(TAG, "Invalid wifi network specifier: " + wns + ". Rejecting ");
             return false;
         }
         return true;
@@ -524,17 +695,21 @@
         if (ns == null) {
             // Generic wifi request. Always accept.
         } else {
-            // Invalid request with network specifier.
-            if (!isRequestWithNetworkSpecifierValid(networkRequest)) {
+            // Unsupported network specifier.
+            if (!(ns instanceof WifiNetworkSpecifier)) {
+                Log.e(TAG, "Unsupported network specifier: " + ns + ". Rejecting");
+                return false;
+            }
+            // Invalid request with wifi network specifier.
+            if (!isRequestWithWifiNetworkSpecifierValid(networkRequest)) {
                 releaseRequestAsUnfulfillableByAnyFactory(networkRequest);
                 return false;
             }
-            if (!mWifiEnabled) {
-                // Will re-evaluate when wifi is turned on.
-                Log.e(TAG, "Wifi off. Rejecting");
-                return false;
+            if (Objects.equals(mActiveSpecificNetworkRequest, networkRequest)
+                    || Objects.equals(mConnectedSpecificNetworkRequest, networkRequest)) {
+                Log.e(TAG, "acceptRequest: Already processing the request " + networkRequest);
+                return true;
             }
-            WifiNetworkSpecifier wns = (WifiNetworkSpecifier) ns;
             // Only allow specific wifi network request from foreground app/service.
             if (!mWifiPermissionsUtil.checkNetworkSettingsPermission(
                     networkRequest.getRequestorUid())
@@ -590,25 +765,38 @@
                 mWifiConnectivityManager.setTrustedConnectionAllowed(true);
             }
         } else {
-            // Invalid request with network specifier.
-            if (!isRequestWithNetworkSpecifierValid(networkRequest)) {
+            // Unsupported network specifier.
+            if (!(ns instanceof WifiNetworkSpecifier)) {
+                Log.e(TAG, "Unsupported network specifier: " + ns + ". Ignoring");
+                return;
+            }
+            // Invalid request with wifi network specifier.
+            if (!isRequestWithWifiNetworkSpecifierValid(networkRequest)) {
                 releaseRequestAsUnfulfillableByAnyFactory(networkRequest);
                 return;
             }
-            if (!mWifiEnabled) {
-                // Will re-evaluate when wifi is turned on.
-                Log.e(TAG, "Wifi off. Rejecting");
+            // Wifi-off abort early.
+            if (!mActiveModeWarden.hasPrimaryClientModeManager()) {
+                Log.e(TAG, "Request with wifi network specifier when wifi is off."
+                        + "Rejecting");
+                releaseRequestAsUnfulfillableByAnyFactory(networkRequest);
                 return;
             }
+            if (Objects.equals(mActiveSpecificNetworkRequest, networkRequest)
+                    || Objects.equals(mConnectedSpecificNetworkRequest, networkRequest)) {
+                Log.e(TAG, "needNetworkFor: Already processing the request " + networkRequest);
+                return;
+            }
+
             retrieveWifiScanner();
             // Reset state from any previous request.
             setupForActiveRequest();
-
             // Store the active network request.
             mActiveSpecificNetworkRequest = networkRequest;
             WifiNetworkSpecifier wns = (WifiNetworkSpecifier) ns;
             mActiveSpecificNetworkRequestSpecifier = new WifiNetworkSpecifier(
-                    wns.ssidPatternMatcher, wns.bssidPatternMatcher, wns.wifiConfiguration);
+                    wns.ssidPatternMatcher, wns.bssidPatternMatcher, ScanResult.UNSPECIFIED,
+                    wns.wifiConfiguration);
             mWifiMetrics.incrementNetworkRequestApiNumRequest();
 
             if (!triggerConnectIfUserApprovedMatchFound()) {
@@ -621,7 +809,7 @@
                 if (mVerboseLoggingEnabled) {
                     Log.v(TAG, "Using cached " + cachedScanResults.length + " scan results");
                 }
-                handleScanResults(cachedScanResults);          
+                handleScanResults(cachedScanResults);
                 if (mActiveMatchedScanResults != null) {
                     sendNetworkRequestMatchCallbacksForActiveRequest(
                             mActiveMatchedScanResults.values());
@@ -644,13 +832,9 @@
                 mWifiConnectivityManager.setTrustedConnectionAllowed(false);
             }
         } else {
-            // Invalid network specifier.
+            // Unsupported network specifier.
             if (!(ns instanceof WifiNetworkSpecifier)) {
-                Log.e(TAG, "Invalid network specifier mentioned. Ignoring");
-                return;
-            }
-            if (!mWifiEnabled) {
-                Log.e(TAG, "Wifi off. Ignoring");
+                Log.e(TAG, "Unsupported network specifier mentioned. Ignoring");
                 return;
             }
             if (mActiveSpecificNetworkRequest == null && mConnectedSpecificNetworkRequest == null) {
@@ -697,12 +881,13 @@
      * @return Pair of uid & package name of the specific request (if any), else <-1, "">.
      */
     public Pair<Integer, String> getSpecificNetworkRequestUidAndPackageName(
-            @NonNull WifiConfiguration connectedNetwork) {
+            @NonNull WifiConfiguration connectedNetwork, @NonNull String connectedBssid) {
         if (mUserSelectedNetwork == null || connectedNetwork == null) {
             return Pair.create(Process.INVALID_UID, "");
         }
-        if (!isUserSelectedNetwork(connectedNetwork)) {
-            Log.w(TAG, "Connected to unknown network " + connectedNetwork + ". Ignoring...");
+        if (!isUserSelectedNetwork(connectedNetwork, connectedBssid)) {
+            Log.w(TAG, "Connected to unknown network " + connectedNetwork + ":" + connectedBssid
+                    + ". Ignoring...");
             return Pair.create(Process.INVALID_UID, "");
         }
         if (mConnectedSpecificNetworkRequestSpecifier != null) {
@@ -722,7 +907,7 @@
     // If the network already exists, just return the network ID of the existing network.
     private int addNetworkToWifiConfigManager(@NonNull WifiConfiguration network) {
         WifiConfiguration existingSavedNetwork =
-                mWifiConfigManager.getConfiguredNetwork(network.getKey());
+                mWifiConfigManager.getConfiguredNetwork(network.getProfileKey());
         if (existingSavedNetwork != null) {
             if (WifiConfigurationUtil.hasCredentialChanged(existingSavedNetwork, network)) {
                 // TODO (b/142035508): What if the user has a saved network with different
@@ -736,9 +921,9 @@
                         network, mActiveSpecificNetworkRequest.getRequestorUid(),
                         mActiveSpecificNetworkRequest.getRequestorPackageName());
         if (mVerboseLoggingEnabled) {
-            Log.v(TAG, "Added network to config manager " + networkUpdateResult.netId);
+            Log.v(TAG, "Added network to config manager " + networkUpdateResult.getNetworkId());
         }
-        return networkUpdateResult.netId;
+        return networkUpdateResult.getNetworkId();
     }
 
     // Helper method to remove the provided network configuration from WifiConfigManager, if it was
@@ -746,11 +931,11 @@
     private void disconnectAndRemoveNetworkFromWifiConfigManager(
             @Nullable WifiConfiguration network) {
         // Trigger a disconnect first.
-        mWifiInjector.getClientModeImpl().disconnectCommand();
+        if (mClientModeManager != null) mClientModeManager.disconnect();
 
         if (network == null) return;
         WifiConfiguration wcmNetwork =
-                mWifiConfigManager.getConfiguredNetwork(network.getKey());
+                mWifiConfigManager.getConfiguredNetwork(network.getProfileKey());
         if (wcmNetwork == null) {
             Log.e(TAG, "Network not present in config manager");
             return;
@@ -782,12 +967,19 @@
 
         mWifiMetrics.setNominatorForNetwork(networkId,
                 WifiMetricsProto.ConnectionEvent.NOMINATOR_SPECIFIER);
+        if (mClientModeManagerRole == ROLE_CLIENT_PRIMARY) {
+            mWifiMetrics.incrementNetworkRequestApiNumConnectOnPrimaryIface();
+        } else {
+            mWifiMetrics.incrementNetworkRequestApiNumConnectOnSecondaryIface();
+        }
 
         // Send the connect request to ClientModeImpl.
         // TODO(b/117601161): Refactor this.
-        ConnectActionListener connectActionListener = new ConnectActionListener();
-        mWifiInjector.getClientModeImpl().connect(null, networkId, new Binder(),
-                connectActionListener, connectActionListener.hashCode(),
+        ConnectActionListener listener = new ConnectActionListener();
+        mConnectHelper.connectToNetwork(
+                mClientModeManager,
+                new NetworkUpdateResult(networkId),
+                new ActionListenerWrapper(listener),
                 mActiveSpecificNetworkRequest.getRequestorUid());
 
         // Post an alarm to handle connection timeout.
@@ -795,9 +987,6 @@
     }
 
     private void handleConnectToNetworkUserSelectionInternal(WifiConfiguration network) {
-        // Disable Auto-join so that NetworkFactory can take control of the network connection.
-        mWifiConnectivityManager.setSpecificNetworkRequestInProgress(true);
-
         // Copy over the credentials from the app's request and then copy the ssid from user
         // selection.
         WifiConfiguration networkToConnect =
@@ -819,16 +1008,19 @@
         networkToConnect.shared = false;
         networkToConnect.fromWifiNetworkSpecifier = true;
 
+        // TODO(b/188021807): Implement the band request from the specifier on the network to
+        // connect.
+
         // Store the user selected network.
         mUserSelectedNetwork = networkToConnect;
 
-        // Disconnect from the current network before issuing a new connect request.
-        disconnectAndRemoveNetworkFromWifiConfigManager(mUserSelectedNetwork);
-
-        // Trigger connection to the network.
-        connectToNetwork(networkToConnect);
-        // Triggered connection to network, now wait for the connection status.
-        mPendingConnectionSuccess = true;
+        // Request a new CMM for the connection processing.
+        if (mVerboseLoggingEnabled) Log.v(TAG, "Requesting new ClientModeManager instance");
+        mActiveModeWarden.requestLocalOnlyClientModeManager(
+                new ClientModeManagerRequestListener(),
+                new WorkSource(mActiveSpecificNetworkRequest.getRequestorUid(),
+                        mActiveSpecificNetworkRequest.getRequestorPackageName()),
+                networkToConnect.SSID, networkToConnect.BSSID);
     }
 
     private void handleConnectToNetworkUserSelection(WifiConfiguration network) {
@@ -851,7 +1043,7 @@
         mWifiMetrics.incrementNetworkRequestApiNumUserReject();
     }
 
-    private boolean isUserSelectedNetwork(WifiConfiguration config) {
+    private boolean isUserSelectedNetwork(WifiConfiguration config, String bssid) {
         if (!TextUtils.equals(mUserSelectedNetwork.SSID, config.SSID)) {
             return false;
         }
@@ -859,6 +1051,9 @@
                 mUserSelectedNetwork.allowedKeyManagement, config.allowedKeyManagement)) {
             return false;
         }
+        if (!TextUtils.equals(mUserSelectedNetwork.BSSID, bssid)) {
+            return false;
+        }
         return true;
     }
 
@@ -866,49 +1061,56 @@
      * Invoked by {@link ClientModeImpl} on end of connection attempt to a network.
      */
     public void handleConnectionAttemptEnded(
-            int failureCode, @NonNull WifiConfiguration network) {
+            int failureCode, @NonNull WifiConfiguration network, @NonNull String bssid) {
         if (failureCode == WifiMetrics.ConnectionEvent.FAILURE_NONE) {
-            handleNetworkConnectionSuccess(network);
+            handleNetworkConnectionSuccess(network, bssid);
         } else {
-            handleNetworkConnectionFailure(network);
+            handleNetworkConnectionFailure(network, bssid);
         }
     }
 
     /**
      * Invoked by {@link ClientModeImpl} on successful connection to a network.
      */
-    private void handleNetworkConnectionSuccess(@NonNull WifiConfiguration connectedNetwork) {
+    private void handleNetworkConnectionSuccess(@NonNull WifiConfiguration connectedNetwork,
+            @NonNull String connectedBssid) {
         if (mUserSelectedNetwork == null || connectedNetwork == null
                 || !mPendingConnectionSuccess) {
             return;
         }
-        if (!isUserSelectedNetwork(connectedNetwork)) {
-            Log.w(TAG, "Connected to unknown network " + connectedNetwork + ". Ignoring...");
+        if (!isUserSelectedNetwork(connectedNetwork, connectedBssid)) {
+            Log.w(TAG, "Connected to unknown network " + connectedNetwork + ":" + connectedBssid
+                    + ". Ignoring...");
             return;
         }
         Log.d(TAG, "Connected to network " + mUserSelectedNetwork);
-        for (INetworkRequestMatchCallback callback : mRegisteredCallbacks.getCallbacks()) {
-            try {
-                callback.onUserSelectionConnectSuccess(mUserSelectedNetwork);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Unable to invoke network request connect failure callback "
-                        + callback, e);
+        if (mRegisteredCallbacks != null) {
+            int itemCount = mRegisteredCallbacks.beginBroadcast();
+            for (int i = 0; i < itemCount; i++) {
+                try {
+                    mRegisteredCallbacks.getBroadcastItem(i).onUserSelectionConnectSuccess(
+                            mUserSelectedNetwork);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Unable to invoke network request connect failure callback ", e);
+                }
             }
+            mRegisteredCallbacks.finishBroadcast();
         }
         // transition the request from "active" to "connected".
         setupForConnectedRequest();
-        mWifiMetrics.incrementNetworkRequestApiNumConnectSuccess();
     }
 
     /**
      * Invoked by {@link ClientModeImpl} on failure to connect to a network.
      */
-    private void handleNetworkConnectionFailure(@NonNull WifiConfiguration failedNetwork) {
+    private void handleNetworkConnectionFailure(@NonNull WifiConfiguration failedNetwork,
+            @NonNull String failedBssid) {
         if (mUserSelectedNetwork == null || failedNetwork == null || !mPendingConnectionSuccess) {
             return;
         }
-        if (!isUserSelectedNetwork(failedNetwork)) {
-            Log.w(TAG, "Connection failed to unknown network " + failedNetwork + ". Ignoring...");
+        if (!isUserSelectedNetwork(failedNetwork, failedBssid)) {
+            Log.w(TAG, "Connection failed to unknown network " + failedNetwork + ":" + failedBssid
+                    + ". Ignoring...");
             return;
         }
         Log.w(TAG, "Failed to connect to network " + mUserSelectedNetwork);
@@ -919,13 +1121,17 @@
             return;
         }
         Log.e(TAG, "Connection failures, cancelling " + mUserSelectedNetwork);
-        for (INetworkRequestMatchCallback callback : mRegisteredCallbacks.getCallbacks()) {
-            try {
-                callback.onUserSelectionConnectFailure(mUserSelectedNetwork);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Unable to invoke network request connect failure callback "
-                        + callback, e);
+        if (mRegisteredCallbacks != null) {
+            int itemCount = mRegisteredCallbacks.beginBroadcast();
+            for (int i = 0; i < itemCount; i++) {
+                try {
+                    mRegisteredCallbacks.getBroadcastItem(i).onUserSelectionConnectFailure(
+                            mUserSelectedNetwork);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Unable to invoke network request connect failure callback ", e);
+                }
             }
+            mRegisteredCallbacks.finishBroadcast();
         }
         teardownForActiveRequest();
     }
@@ -933,7 +1139,7 @@
     /**
      * Invoked by {@link ClientModeImpl} to indicate screen state changes.
      */
-    public void handleScreenStateChanged(boolean screenOn) {
+    private 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 || !mIsPeriodicScanEnabled) return;
@@ -950,35 +1156,19 @@
         }
     }
 
-    /**
-     * Invoked by {@link ClientModeImpl} to indicate wifi state toggle.
-     */
-    public void setWifiState(boolean enabled) {
-        if (mVerboseLoggingEnabled) Log.v(TAG, "setWifiState " + enabled);
-        if (enabled) {
-            reevaluateAllRequests(); // Re-evaluate any pending requests.
-        } else {
-            if (mActiveSpecificNetworkRequest != null) {
-                Log.w(TAG, "Wifi off, cancelling " + mActiveSpecificNetworkRequest);
-                teardownForActiveRequest();
-            }
-            if (mConnectedSpecificNetworkRequest != null) {
-                Log.w(TAG, "Wifi off, cancelling " + mConnectedSpecificNetworkRequest);
-                teardownForConnectedNetwork();
-            }
-        }
-        mWifiEnabled = enabled;
-    }
-
     // Common helper method for start/end of active request processing.
     private void cleanupActiveRequest() {
         // Send the abort to the UI for the current active request.
-        for (INetworkRequestMatchCallback callback : mRegisteredCallbacks.getCallbacks()) {
-            try {
-                callback.onAbort();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Unable to invoke network request abort callback " + callback, e);
+        if (mRegisteredCallbacks != null) {
+            int itemCount = mRegisteredCallbacks.beginBroadcast();
+            for (int i = 0; i < itemCount; i++) {
+                try {
+                    mRegisteredCallbacks.getBroadcastItem(i).onAbort();
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Unable to invoke network request abort callback ", e);
+                }
             }
+            mRegisteredCallbacks.finishBroadcast();
         }
         // Force-release the network request to let the app know early that the attempt failed.
         if (mActiveSpecificNetworkRequest != null) {
@@ -997,7 +1187,8 @@
         cancelPeriodicScans();
         cancelConnectionTimeout();
         // Remove any callbacks registered for the request.
-        mRegisteredCallbacks.clear();
+        if (mRegisteredCallbacks != null) mRegisteredCallbacks.kill();
+        mRegisteredCallbacks = null;
     }
 
     // Invoked at the start of new active request processing.
@@ -1007,6 +1198,21 @@
         }
     }
 
+    private void removeClientModeManagerIfNecessary() {
+        if (mClientModeManager != null) {
+            if (mClientModeManagerRole == ROLE_CLIENT_PRIMARY) {
+                mWifiConnectivityManager.setSpecificNetworkRequestInProgress(false);
+            }
+            if (mContext.getResources().getBoolean(R.bool.config_wifiUseHalApiToDisableFwRoaming)) {
+                mClientModeManager.enableRoaming(true); // Re-enable roaming.
+            }
+            mActiveModeWarden.removeClientModeManager(mClientModeManager);
+            // For every connection attempt, get the appropriate client mode impl to use.
+            mClientModeManager = null;
+            mClientModeManagerRole = null;
+        }
+    }
+
     // Invoked at the termination of current active request processing.
     private void teardownForActiveRequest() {
         if (mPendingConnectionSuccess) {
@@ -1016,7 +1222,7 @@
         cleanupActiveRequest();
         // ensure there is no connected request in progress.
         if (mConnectedSpecificNetworkRequest == null) {
-            mWifiConnectivityManager.setSpecificNetworkRequestInProgress(false);
+            removeClientModeManagerIfNecessary();
         }
     }
 
@@ -1030,6 +1236,25 @@
         mPendingConnectionSuccess = false;
         // Cancel connection timeout alarm.
         cancelConnectionTimeout();
+
+        mConnectionStartTimeMillis = mClock.getElapsedSinceBootMillis();
+        if (mClientModeManagerRole == ROLE_CLIENT_PRIMARY) {
+            mWifiMetrics.incrementNetworkRequestApiNumConnectSuccessOnPrimaryIface();
+        } else {
+            mWifiMetrics.incrementNetworkRequestApiNumConnectSuccessOnSecondaryIface();
+            // secondary STA being used, register CMI listener for concurrent connection metrics
+            // collection.
+            mCmiListener = new CmiListener();
+            mClientModeImplMonitor.registerListener(mCmiListener);
+        }
+        // Disable roaming.
+        if (mContext.getResources().getBoolean(R.bool.config_wifiUseHalApiToDisableFwRoaming)) {
+            // Note: This is an old HAL API, but since it wasn't being exercised before, we are
+            // being extra cautious and only using it on devices running >= S.
+            if (!mClientModeManager.enableRoaming(false)) {
+                Log.w(TAG, "Failed to disable roaming");
+            }
+        }
     }
 
     // Invoked at the termination of current connected request processing.
@@ -1038,9 +1263,29 @@
         disconnectAndRemoveNetworkFromWifiConfigManager(mUserSelectedNetwork);
         mConnectedSpecificNetworkRequest = null;
         mConnectedSpecificNetworkRequestSpecifier = null;
+
+        if (mConnectionStartTimeMillis != -1) {
+            int connectionDurationSec = toIntExact(TimeUnit.MILLISECONDS.toSeconds(
+                    mClock.getElapsedSinceBootMillis() - mConnectionStartTimeMillis));
+            if (mClientModeManagerRole == ROLE_CLIENT_PRIMARY) {
+                mWifiMetrics.incrementNetworkRequestApiConnectionDurationSecOnPrimaryIfaceHistogram(
+                        connectionDurationSec);
+
+            } else {
+                mWifiMetrics
+                        .incrementNetworkRequestApiConnectionDurationSecOnSecondaryIfaceHistogram(
+                                connectionDurationSec);
+            }
+            mConnectionStartTimeMillis = -1L;
+        }
+        if (mCmiListener != null) {
+            mCmiListener.checkForConcurrencyEndAndIncrementMetrics();
+            mClientModeImplMonitor.unregisterListener(mCmiListener);
+            mCmiListener = null;
+        }
         // ensure there is no active request in progress.
         if (mActiveSpecificNetworkRequest == null) {
-            mWifiConnectivityManager.setSpecificNetworkRequestInProgress(false);
+            removeClientModeManagerIfNecessary();
         }
     }
 
@@ -1080,6 +1325,44 @@
         checkNotNull(mWifiScanner);
     }
 
+    private void handleClientModeManagerRetrieval() {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "ClientModeManager retrieved: " + mClientModeManager);
+        }
+        if (mUserSelectedNetwork == null) {
+            Log.e(TAG, "No user selected network to connect to. Ignoring ClientModeManager"
+                    + "retrieval..");
+            return;
+        }
+
+        // If using primary STA, disable Auto-join so that NetworkFactory can take control of the
+        // network connection.
+        if (mClientModeManagerRole == ROLE_CLIENT_PRIMARY) {
+            mWifiConnectivityManager.setSpecificNetworkRequestInProgress(true);
+        }
+
+        // Disconnect from the current network before issuing a new connect request.
+        disconnectAndRemoveNetworkFromWifiConfigManager(mUserSelectedNetwork);
+
+        // Trigger connection to the network.
+        connectToNetwork(mUserSelectedNetwork);
+        // Triggered connection to network, now wait for the connection status.
+        mPendingConnectionSuccess = true;
+    }
+
+    private void handleClientModeManagerRemovalOrFailure() {
+        if (mActiveSpecificNetworkRequest != null) {
+            Log.w(TAG, "ClientModeManager retrieval failed or removed, cancelling "
+                    + mActiveSpecificNetworkRequest);
+            teardownForActiveRequest();
+        }
+        if (mConnectedSpecificNetworkRequest != null) {
+            Log.w(TAG, "ClientModeManager retrieval failed or removed, cancelling "
+                    + mConnectedSpecificNetworkRequest);
+            teardownForConnectedNetwork();
+        }
+    }
+
     private void startPeriodicScans() {
         if (mActiveSpecificNetworkRequestSpecifier == null) {
             Log.e(TAG, "Periodic scan triggered when there is no active network request. "
@@ -1150,7 +1433,7 @@
         ScanResultMatchInfo fromScanResult = ScanResultMatchInfo.fromScanResult(scanResult);
         ScanResultMatchInfo fromWifiConfiguration =
                 ScanResultMatchInfo.fromWifiConfiguration(wns.wifiConfiguration);
-        return fromScanResult.networkTypeEquals(fromWifiConfiguration, false);
+        return fromScanResult.networkTypeEquals(fromWifiConfiguration);
     }
 
     // Loops through the scan results and finds scan results matching the active network
@@ -1179,18 +1462,22 @@
     private void sendNetworkRequestMatchCallbacksForActiveRequest(
             @NonNull Collection<ScanResult> matchedScanResults) {
         if (matchedScanResults.isEmpty()) return;
-        if (mRegisteredCallbacks.getNumCallbacks() == 0) {
+        if (mRegisteredCallbacks == null
+                || mRegisteredCallbacks.getRegisteredCallbackCount() == 0) {
             Log.e(TAG, "No callback registered for sending network request matches. "
                     + "Ignoring...");
             return;
         }
-        for (INetworkRequestMatchCallback callback : mRegisteredCallbacks.getCallbacks()) {
+        int itemCount = mRegisteredCallbacks.beginBroadcast();
+        for (int i = 0; i < itemCount; i++) {
             try {
-                callback.onMatch(new ArrayList<>(matchedScanResults));
+                mRegisteredCallbacks.getBroadcastItem(i).onMatch(
+                        new ArrayList<>(matchedScanResults));
             } catch (RemoteException e) {
-                Log.e(TAG, "Unable to invoke network request match callback " + callback, e);
+                Log.e(TAG, "Unable to invoke network request match callback ", e);
             }
         }
+        mRegisteredCallbacks.finishBroadcast();
     }
 
     private void cancelConnectionTimeout() {
@@ -1371,10 +1658,11 @@
                 ScanResultMatchInfo.fromWifiConfiguration(network);
         for (ScanResult scanResult : mActiveMatchedScanResults.values()) {
             ScanResultMatchInfo fromScanResult = ScanResultMatchInfo.fromScanResult(scanResult);
-            if (fromScanResult.equals(fromWifiConfiguration)) {
+            SecurityParams params = fromScanResult.matchForNetworkSelection(fromWifiConfiguration);
+            if (null != params) {
                 AccessPoint approvedAccessPoint =
                         new AccessPoint(scanResult.SSID, MacAddress.fromString(scanResult.BSSID),
-                                fromScanResult.networkType);
+                                params.getSecurityType());
                 newUserApprovedAccessPoints.add(approvedAccessPoint);
             }
         }
@@ -1412,9 +1700,12 @@
         if (!isActiveRequestForSingleAccessPoint()) return false;
         String ssid = mActiveSpecificNetworkRequestSpecifier.ssidPatternMatcher.getPath();
         MacAddress bssid = mActiveSpecificNetworkRequestSpecifier.bssidPatternMatcher.first;
-        int networkType =
+        SecurityParams params =
                 ScanResultMatchInfo.fromWifiConfiguration(
-                        mActiveSpecificNetworkRequestSpecifier.wifiConfiguration).networkType;
+                        mActiveSpecificNetworkRequestSpecifier.wifiConfiguration)
+                                .getFirstAvailableSecurityParams();
+        if (null == params) return false;
+        int networkType = params.getSecurityType();
         if (!isAccessPointApprovedForActiveRequest(ssid, bssid, networkType)
                 || mWifiConfigManager.isNetworkTemporarilyDisabledByUser(
                 ScanResultUtil.createQuotedSSID(ssid))) {
@@ -1454,13 +1745,12 @@
         mActiveMatchedScanResults.putAll(matchedScanResults
                 .stream()
                 .collect(Collectors.toMap(
-                        scanResult -> scanResult.BSSID, scanResult -> scanResult)));
+                        scanResult -> scanResult.BSSID, scanResult -> scanResult, (a, b) -> a)));
         // Weed out any stale cached scan results.
         long currentTimeInMillis = mClock.getElapsedSinceBootMillis();
         mActiveMatchedScanResults.entrySet().removeIf(
                 e -> ((currentTimeInMillis - (e.getValue().timestamp / 1000))
                         >= CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS));
-
     }
 
     /**
diff --git a/service/java/com/android/server/wifi/WifiNetworkSelector.java b/service/java/com/android/server/wifi/WifiNetworkSelector.java
index fbbcb31..f80468f 100644
--- a/service/java/com/android/server/wifi/WifiNetworkSelector.java
+++ b/service/java/com/android/server/wifi/WifiNetworkSelector.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.net.MacAddress;
 import android.net.wifi.ScanResult;
+import android.net.wifi.SecurityParams;
 import android.net.wifi.SupplicantState;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
@@ -49,6 +50,7 @@
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
@@ -91,6 +93,7 @@
     private final WifiConfigManager mWifiConfigManager;
     private final Clock mClock;
     private final LocalLog mLocalLog;
+    private boolean mVerboseLoggingEnabled = false;
     private final WifiMetrics mWifiMetrics;
     private long mLastNetworkSelectionTimeStamp = INVALID_TIME_STAMP;
     // Buffer of filtered scan results (Scan results considered by network selection) & associated
@@ -100,14 +103,15 @@
     private List<ScanDetail> mFilteredNetworks = new ArrayList<>();
     private final WifiScoreCard mWifiScoreCard;
     private final ScoringParams mScoringParams;
-    private final WifiNative mWifiNative;
+    private final WifiInjector mWifiInjector;
+    private final ThroughputPredictor mThroughputPredictor;
+    private final WifiChannelUtilization mWifiChannelUtilization;
+    private final WifiGlobals mWifiGlobals;
+    private final ScanRequestProxy mScanRequestProxy;
 
     private final Map<String, WifiCandidates.CandidateScorer> mCandidateScorers = new ArrayMap<>();
     private boolean mIsEnhancedOpenSupportedInitialized = false;
     private boolean mIsEnhancedOpenSupported;
-    private ThroughputPredictor mThroughputPredictor;
-    private boolean mIsBluetoothConnected = false;
-    private WifiChannelUtilization mWifiChannelUtilization;
 
     /**
      * Interface for WiFi Network Nominator
@@ -155,21 +159,15 @@
         /**
          * Evaluate all the networks from the scan results.
          *
-         * @param scanDetails             a list of scan details constructed from the scan results
-         * @param currentNetwork          configuration of the current connected network
-         *                                or null if disconnected
-         * @param currentBssid            BSSID of the current connected network or null if
-         *                                disconnected
-         * @param connected               a flag to indicate if ClientModeImpl is in connected
-         *                                state
-         * @param untrustedNetworkAllowed a flag to indicate if untrusted networks like
-         *                                ephemeral networks are allowed
-         * @param onConnectableListener   callback to record all of the connectable networks
+         * @param scanDetails              a list of scan details constructed from the scan results
+         * @param untrustedNetworkAllowed  a flag to indicate if untrusted networks are allowed
+         * @param oemPaidNetworkAllowed    a flag to indicate if oem paid networks are allowed
+         * @param oemPrivateNetworkAllowed a flag to indicate if oem private networks are allowed
+         * @param onConnectableListener    callback to record all of the connectable networks
          */
         void nominateNetworks(List<ScanDetail> scanDetails,
-                WifiConfiguration currentNetwork, String currentBssid,
-                boolean connected, boolean untrustedNetworkAllowed,
-                OnConnectableListener onConnectableListener);
+                boolean untrustedNetworkAllowed, boolean oemPaidNetworkAllowed,
+                boolean oemPrivateNetworkAllowed, OnConnectableListener onConnectableListener);
 
         /**
          * Callback for recording connectable candidates
@@ -188,9 +186,17 @@
     private final List<NetworkNominator> mNominators = new ArrayList<>(3);
 
     // A helper to log debugging information in the local log buffer, which can
-    // be retrieved in bugreport.
+    // be retrieved in bugreport. It is also used to print the log in the console.
     private void localLog(String log) {
         mLocalLog.log(log);
+        if (mVerboseLoggingEnabled) Log.d(TAG, log);
+    }
+
+    /**
+     * Enable verbose logging in the console
+     */
+    public void enableVerboseLogging(boolean verbose) {
+        mVerboseLoggingEnabled = verbose;
     }
 
     /**
@@ -272,6 +278,12 @@
             return true;
         }
 
+        // OEM paid/private networks are only available to system apps, so this is never sufficient.
+        if (network.oemPaid || network.oemPrivate) {
+            localLog("Current network is oem paid/private");
+            return false;
+        }
+
         // Network without internet access is not sufficient, unless expected
         if (!hasInternetOrExpectNoInternet(wifiInfo)) {
             localLog("Current network has [" + network.numNoInternetAccessReports
@@ -279,32 +291,21 @@
             return false;
         }
 
-        if (!hasSufficientLinkQuality(wifiInfo)) {
-            localLog("Current network link quality is not sufficient");
-            return false;
-        }
-
-        if (!hasActiveStream(wifiInfo)) {
-            localLog("Current network has low ongoing traffic");
+        if (!hasSufficientLinkQuality(wifiInfo) && !hasActiveStream(wifiInfo)) {
+            localLog("Current network link quality is not sufficient and has low ongoing traffic");
             return false;
         }
 
         return true;
     }
 
-    private boolean isNetworkSelectionNeeded(List<ScanDetail> scanDetails, WifiInfo wifiInfo,
-            boolean connected, boolean disconnected) {
-        if (scanDetails.size() == 0) {
-            localLog("Empty connectivity scan results. Skip network selection.");
-            return false;
-        }
-
-        if (connected) {
+    private boolean isNetworkSelectionNeededForCmm(@NonNull ClientModeManagerState cmmState) {
+        if (cmmState.connected) {
             // Is roaming allowed?
             if (!mContext.getResources().getBoolean(
                     R.bool.config_wifi_framework_enable_associated_network_selection)) {
-                localLog("Switching networks in connected state is not allowed."
-                        + " Skip network selection.");
+                localLog(cmmState.ifaceName + ": Switching networks in connected state is not "
+                        + "allowed. Skip network selection.");
                 return false;
             }
 
@@ -313,30 +314,49 @@
                 long gap = mClock.getElapsedSinceBootMillis()
                         - mLastNetworkSelectionTimeStamp;
                 if (gap < MINIMUM_NETWORK_SELECTION_INTERVAL_MS) {
-                    localLog("Too short since last network selection: " + gap + " ms."
-                            + " Skip network selection.");
+                    localLog(cmmState.ifaceName + ": Too short since last network selection: "
+                            + gap + " ms. Skip network selection.");
                     return false;
                 }
             }
             // Please note other scans (e.g., location scan or app scan) may also trigger network
             // selection and these scans may or may not run sufficiency check.
             // So it is better to run sufficiency check here before network selection.
-            if (isNetworkSufficient(wifiInfo)) {
-                localLog("Current connected network already sufficient. Skip network selection.");
+            if (isNetworkSufficient(cmmState.wifiInfo)) {
+                localLog(cmmState.ifaceName
+                        + ": Current connected network already sufficient."
+                        + " Skip network selection.");
                 return false;
             } else {
-                localLog("Current connected network is not sufficient.");
+                localLog(cmmState.ifaceName + ": Current connected network is not sufficient.");
                 return true;
             }
-        } else if (disconnected) {
+        } else if (cmmState.disconnected) {
             return true;
         } else {
             // No network selection if ClientModeImpl is in a state other than
-            // CONNECTED or DISCONNECTED.
-            localLog("ClientModeImpl is in neither CONNECTED nor DISCONNECTED state."
-                    + " Skip network selection.");
+            // connected or disconnected (i.e connecting).
+            localLog(cmmState.ifaceName + ": ClientModeImpl is in neither CONNECTED nor "
+                    + "DISCONNECTED state. Skip network selection.");
             return false;
         }
+
+    }
+
+    private boolean isNetworkSelectionNeeded(@NonNull List<ScanDetail> scanDetails,
+            @NonNull List<ClientModeManagerState> cmmStates) {
+        if (scanDetails.size() == 0) {
+            localLog("Empty connectivity scan results. Skip network selection.");
+            return false;
+        }
+        for (ClientModeManagerState cmmState : cmmStates) {
+            // network selection needed by this CMM instance, perform network selection
+            if (isNetworkSelectionNeededForCmm(cmmState)) {
+                return true;
+            }
+        }
+        // none of the CMM instances need network selection, skip network selection.
+        return false;
     }
 
     /**
@@ -366,13 +386,16 @@
     }
 
     private List<ScanDetail> filterScanResults(List<ScanDetail> scanDetails,
-            Set<String> bssidBlacklist, boolean isConnected, String currentBssid) {
+            Set<String> bssidBlocklist, List<ClientModeManagerState> cmmStates) {
         List<ScanDetail> validScanDetails = new ArrayList<>();
         StringBuffer noValidSsid = new StringBuffer();
-        StringBuffer blacklistedBssid = new StringBuffer();
+        StringBuffer blockedBssid = new StringBuffer();
         StringBuffer lowRssi = new StringBuffer();
         StringBuffer mboAssociationDisallowedBssid = new StringBuffer();
-        boolean scanResultsHaveCurrentBssid = false;
+        List<String> currentBssids = cmmStates.stream()
+                .map(cmmState -> cmmState.wifiInfo.getBSSID())
+                .collect(Collectors.toList());
+        Set<String> scanResultPresentForCurrentBssids = new ArraySet<>();
         int numBssidFiltered = 0;
 
         for (ScanDetail scanDetail : scanDetails) {
@@ -383,17 +406,17 @@
                 continue;
             }
 
-            // Check if the scan results contain the currently connected BSSID
-            if (scanResult.BSSID.equals(currentBssid)) {
-                scanResultsHaveCurrentBssid = true;
+            // Check if the scan results contain the currently connected BSSID's
+            if (currentBssids.contains(scanResult.BSSID)) {
+                scanResultPresentForCurrentBssids.add(scanResult.BSSID);
                 validScanDetails.add(scanDetail);
                 continue;
             }
 
             final String scanId = toScanId(scanResult);
 
-            if (bssidBlacklist.contains(scanResult.BSSID)) {
-                blacklistedBssid.append(scanId).append(" / ");
+            if (bssidBlocklist.contains(scanResult.BSSID)) {
+                blockedBssid.append(scanId).append(" / ");
                 numBssidFiltered++;
                 continue;
             }
@@ -435,19 +458,23 @@
         // network won't show up in the scan results. We don't act on these scan results
         // to avoid aggressive network switching which might trigger disconnection.
         // TODO(b/147751334) this may no longer be needed
-        if (isConnected && !scanResultsHaveCurrentBssid) {
-            localLog("Current connected BSSID " + currentBssid + " is not in the scan results."
-                    + " Skip network selection.");
-            validScanDetails.clear();
-            return validScanDetails;
+        for (ClientModeManagerState cmmState : cmmStates) {
+            // TODO (b/169413079): Disable network selection on corresponding CMM instead.
+            if (cmmState.connected && cmmState.wifiInfo.getScore() >= WIFI_POOR_SCORE
+                    && !scanResultPresentForCurrentBssids.contains(cmmState.wifiInfo.getBSSID())) {
+                localLog("Current connected BSSID " + cmmState.wifiInfo.getBSSID()
+                        + " is not in the scan results. Skip network selection.");
+                validScanDetails.clear();
+                return validScanDetails;
+            }
         }
 
         if (noValidSsid.length() != 0) {
             localLog("Networks filtered out due to invalid SSID: " + noValidSsid);
         }
 
-        if (blacklistedBssid.length() != 0) {
-            localLog("Networks filtered out due to blocklist: " + blacklistedBssid);
+        if (blockedBssid.length() != 0) {
+            localLog("Networks filtered out due to blocklist: " + blockedBssid);
         }
 
         if (lowRssi.length() != 0) {
@@ -479,8 +506,9 @@
         }
 
         mIsEnhancedOpenSupportedInitialized = true;
-        mIsEnhancedOpenSupported = (mWifiNative.getSupportedFeatureSet(
-                mWifiNative.getClientInterfaceName()) & WIFI_FEATURE_OWE) != 0;
+        ClientModeManager primaryManager =
+                mWifiInjector.getActiveModeWarden().getPrimaryClientModeManager();
+        mIsEnhancedOpenSupported = (primaryManager.getSupportedFeatures() & WIFI_FEATURE_OWE) != 0;
         return mIsEnhancedOpenSupported;
     }
 
@@ -489,7 +517,7 @@
      * The list is further filtered for only open unsaved networks.
      *
      * @return the list of ScanDetails for open unsaved networks that do not have invalid SSIDS,
-     * blacklisted BSSIDS, or low signal strength. This will return an empty list when there are
+     * blocked BSSIDS, or low signal strength. This will return an empty list when there are
      * no open unsaved networks, or when network selection has not been run.
      */
     public List<ScanDetail> getFilteredScanDetailsForOpenUnsavedNetworks() {
@@ -509,7 +537,7 @@
             }
 
             // Skip saved networks
-            if (mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(scanDetail) != null) {
+            if (mWifiConfigManager.getSavedNetworkForScanDetailAndCache(scanDetail) != null) {
                 continue;
             }
 
@@ -529,75 +557,10 @@
     }
 
     /**
-     * This API is called when user explicitly selects a network. Currently, it is used in following
-     * cases:
-     * (1) User explicitly chooses to connect to a saved network.
-     * (2) User saves a network after adding a new network.
-     * (3) User saves a network after modifying a saved network.
-     * Following actions will be triggered:
-     * 1. If this network is disabled, we need re-enable it again.
-     * 2. This network is favored over all the other networks visible in latest network
-     * selection procedure.
-     *
-     * @param netId ID for the network chosen by the user
-     * @return true -- There is change made to connection choice of any saved network.
-     * false -- There is no change made to connection choice of any saved network.
-     */
-    public boolean setUserConnectChoice(int netId) {
-        localLog("userSelectNetwork: network ID=" + netId);
-        WifiConfiguration selected = mWifiConfigManager.getConfiguredNetwork(netId);
-
-        if (selected == null || selected.SSID == null) {
-            localLog("userSelectNetwork: Invalid configuration with nid=" + netId);
-            return false;
-        }
-
-        // Enable the network if it is disabled.
-        if (!selected.getNetworkSelectionStatus().isNetworkEnabled()) {
-            mWifiConfigManager.updateNetworkSelectionStatus(netId,
-                    WifiConfiguration.NetworkSelectionStatus.DISABLED_NONE);
-        }
-        return setLegacyUserConnectChoice(selected);
-    }
-
-    /**
-     * This maintains the legacy user connect choice state in the config store
-     */
-    private boolean setLegacyUserConnectChoice(@NonNull final WifiConfiguration selected) {
-        boolean change = false;
-        String key = selected.getKey();
-        List<WifiConfiguration> configuredNetworks = mWifiConfigManager.getConfiguredNetworks();
-
-        for (WifiConfiguration network : configuredNetworks) {
-            WifiConfiguration.NetworkSelectionStatus status = network.getNetworkSelectionStatus();
-            if (network.networkId == selected.networkId) {
-                if (status.getConnectChoice() != null) {
-                    localLog("Remove user selection preference of " + status.getConnectChoice()
-                            + " from " + network.SSID + " : " + network.networkId);
-                    mWifiConfigManager.clearNetworkConnectChoice(network.networkId);
-                    change = true;
-                }
-                continue;
-            }
-
-            if (status.getSeenInLastQualifiedNetworkSelection()
-                    && !key.equals(status.getConnectChoice())) {
-                localLog("Add key: " + key + " to "
-                        + toNetworkString(network));
-                mWifiConfigManager.setNetworkConnectChoice(network.networkId, key);
-                change = true;
-            }
-        }
-
-        return change;
-    }
-
-
-    /**
      * Iterate thru the list of configured networks (includes all saved network configurations +
      * any ephemeral network configurations created for passpoint networks, suggestions, carrier
      * networks, etc) and do the following:
-     * a) Try to re-enable any temporarily enabled networks (if the blacklist duration has expired).
+     * a) Try to re-enable any temporarily enabled networks (if the blocklist duration has expired).
      * b) Clear the {@link WifiConfiguration.NetworkSelectionStatus#getCandidate()} field for all
      * of them to identify networks that are present in the current scan result.
      * c) Log any disabled networks.
@@ -661,12 +624,18 @@
 
         while (tempConfig.getNetworkSelectionStatus().getConnectChoice() != null) {
             String key = tempConfig.getNetworkSelectionStatus().getConnectChoice();
+            int userSelectedRssi = tempConfig.getNetworkSelectionStatus().getConnectChoiceRssi();
             tempConfig = mWifiConfigManager.getConfiguredNetwork(key);
 
             if (tempConfig != null) {
                 WifiConfiguration.NetworkSelectionStatus tempStatus =
                         tempConfig.getNetworkSelectionStatus();
-                if (tempStatus.getCandidate() != null && tempStatus.isNetworkEnabled()) {
+                boolean noInternetButInternetIsExpected = !tempConfig.isNoInternetAccessExpected()
+                        && tempConfig.hasNoInternetAccess();
+                if (tempStatus.getCandidate() != null && tempStatus.isNetworkEnabled()
+                        && !noInternetButInternetIsExpected
+                        && isUserChoiceRssiCloseToOrGreaterThanExpectedValue(
+                                tempStatus.getCandidate().level, userSelectedRssi)) {
                     scanResultCandidate = tempStatus.getCandidate();
                     candidate = tempConfig;
                 }
@@ -686,6 +655,16 @@
         return candidate;
     }
 
+    private boolean isUserChoiceRssiCloseToOrGreaterThanExpectedValue(int observedRssi,
+            int expectedRssi) {
+        // The expectedRssi may be 0 for newly upgraded devices which do not have this information,
+        // pass the test for those devices to avoid regression.
+        if (expectedRssi == 0) {
+            return true;
+        }
+        return observedRssi >= expectedRssi - mScoringParams.getEstimateRssiErrorMargin();
+    }
+
 
     /**
      * Indicates whether we have ever seen the network to be metered since wifi was enabled.
@@ -741,19 +720,89 @@
     }
 
     /**
+     * Container class for passing the ClientModeManager state for each instance that is managed by
+     * WifiConnectivityManager, i.e all {@link ClientModeManager#getRole()} equals
+     * {@link ActiveModeManager#ROLE_CLIENT_PRIMARY} or
+     * {@link ActiveModeManager#ROLE_CLIENT_SECONDARY_LONG_LIVED}.
+     */
+    public static class ClientModeManagerState {
+        /** Iface Name corresponding to iface (if known) */
+        public final String ifaceName;
+        /** True if the device is connected */
+        public final boolean connected;
+        /** True if the device is disconnected */
+        public final boolean disconnected;
+         /** Currently connected network */
+        public final WifiInfo wifiInfo;
+
+        ClientModeManagerState(@NonNull ClientModeManager clientModeManager) {
+            ifaceName = clientModeManager.getInterfaceName();
+            connected = clientModeManager.isConnected();
+            disconnected = clientModeManager.isDisconnected();
+            wifiInfo = clientModeManager.syncRequestConnectionInfo();
+        }
+
+        ClientModeManagerState() {
+            ifaceName = "unknown";
+            connected = false;
+            disconnected = true;
+            wifiInfo = new WifiInfo();
+        }
+
+        @VisibleForTesting
+        ClientModeManagerState(@NonNull String ifaceName, boolean connected, boolean disconnected,
+                @NonNull WifiInfo wifiInfo) {
+            this.ifaceName = ifaceName;
+            this.connected = connected;
+            this.disconnected = disconnected;
+            this.wifiInfo = wifiInfo;
+        }
+
+        @Override
+        public boolean equals(Object that) {
+            if (this == that) return true;
+            if (!(that instanceof ClientModeManagerState)) return false;
+            ClientModeManagerState thatCmmState = (ClientModeManagerState) that;
+            return Objects.equals(ifaceName, thatCmmState.ifaceName)
+                    && connected == thatCmmState.connected
+                    && disconnected == thatCmmState.disconnected
+                    // Since wifiinfo does not have equals currently.
+                    && Objects.equals(wifiInfo.getSSID(), thatCmmState.wifiInfo.getSSID())
+                    && Objects.equals(wifiInfo.getBSSID(), thatCmmState.wifiInfo.getBSSID());
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(ifaceName, connected, disconnected,
+                    wifiInfo.getSSID(), wifiInfo.getBSSID());
+        }
+
+        @Override
+        public String toString() {
+            return "ClientModeManagerState: " + ifaceName
+                    + ", connection state: "
+                    + (connected ? " connected" : (disconnected ? " disconnected" : "unknown"))
+                    + ", WifiInfo: " + wifiInfo;
+        }
+    }
+
+    /**
      * Returns the list of Candidates from networks in range.
      *
-     * @param scanDetails             List of ScanDetail for all the APs in range
-     * @param bssidBlacklist          Blacklisted BSSIDs
-     * @param wifiInfo                Currently connected network
-     * @param connected               True if the device is connected
-     * @param disconnected            True if the device is disconnected
-     * @param untrustedNetworkAllowed True if untrusted networks are allowed for connection
+     * @param scanDetails              List of ScanDetail for all the APs in range
+     * @param bssidBlocklist           Blocked BSSIDs
+     * @param cmmStates                State of all long lived client mode manager instances -
+     *                                 {@link ActiveModeManager#ROLE_CLIENT_PRIMARY} &
+     *                                 {@link ActiveModeManager#ROLE_CLIENT_SECONDARY_LONG_LIVED}.
+     * @param untrustedNetworkAllowed  True if untrusted networks are allowed for connection
+     * @param oemPaidNetworkAllowed    True if oem paid networks are allowed for connection
+     * @param oemPrivateNetworkAllowed True if oem private networks are allowed for connection
      * @return list of valid Candidate(s)
      */
     public List<WifiCandidates.Candidate> getCandidatesFromScan(
-            List<ScanDetail> scanDetails, Set<String> bssidBlacklist, WifiInfo wifiInfo,
-            boolean connected, boolean disconnected, boolean untrustedNetworkAllowed) {
+            @NonNull List<ScanDetail> scanDetails, @NonNull Set<String> bssidBlocklist,
+            @NonNull List<ClientModeManagerState> cmmStates, boolean untrustedNetworkAllowed,
+            boolean oemPaidNetworkAllowed, boolean oemPrivateNetworkAllowed) {
         mFilteredNetworks.clear();
         mConnectableNetworks.clear();
         if (scanDetails.size() == 0) {
@@ -761,67 +810,82 @@
             return null;
         }
 
-        WifiConfiguration currentNetwork =
-                mWifiConfigManager.getConfiguredNetwork(wifiInfo.getNetworkId());
-
-        // Always get the current BSSID from WifiInfo in case that firmware initiated
-        // roaming happened.
-        String currentBssid = wifiInfo.getBSSID();
-
         // Update the scan detail cache at the start, even if we skip network selection
         updateScanDetailCache(scanDetails);
 
-        // Shall we start network selection at all?
-        if (!isNetworkSelectionNeeded(scanDetails, wifiInfo, connected, disconnected)) {
-            return null;
-        }
-
-        // Update all configured networks before initiating network selection.
-        updateConfiguredNetworks();
-
         // Update the registered network nominators.
         for (NetworkNominator registeredNominator : mNominators) {
             registeredNominator.update(scanDetails);
         }
 
+        updateCandidatesSecurityParams(scanDetails);
+
+        // Shall we start network selection at all?
+        if (!isNetworkSelectionNeeded(scanDetails, cmmStates)) {
+            return null;
+        }
+
         // Filter out unwanted networks.
-        mFilteredNetworks = filterScanResults(scanDetails, bssidBlacklist,
-                connected && wifiInfo.getScore() >= WIFI_POOR_SCORE, currentBssid);
+        mFilteredNetworks = filterScanResults(scanDetails, bssidBlocklist, cmmStates);
         if (mFilteredNetworks.size() == 0) {
             return null;
         }
 
         WifiCandidates wifiCandidates = new WifiCandidates(mWifiScoreCard, mContext);
-        if (currentNetwork != null) {
-            wifiCandidates.setCurrent(currentNetwork.networkId, currentBssid);
-            // We always want the current network to be a candidate so that it can participate.
-            // It may also get re-added by a nominator, in which case this fallback
-            // will be replaced.
-            MacAddress bssid = MacAddress.fromString(currentBssid);
-            WifiCandidates.Key key = new WifiCandidates.Key(
-                    ScanResultMatchInfo.fromWifiConfiguration(currentNetwork),
-                    bssid, currentNetwork.networkId);
-            ScanDetail scanDetail = findScanDetailForBssid(mFilteredNetworks, currentBssid);
-            int predictedTputMbps = (scanDetail == null) ? 0 : predictThroughput(scanDetail);
-            wifiCandidates.add(key, currentNetwork,
-                    NetworkNominator.NOMINATOR_ID_CURRENT,
-                    wifiInfo.getRssi(),
-                    wifiInfo.getFrequency(),
-                    calculateLastSelectionWeight(currentNetwork.networkId),
-                    WifiConfiguration.isMetered(currentNetwork, wifiInfo),
-                    isFromCarrierOrPrivilegedApp(currentNetwork),
-                    predictedTputMbps);
+        for (ClientModeManagerState cmmState : cmmStates) {
+            // Always get the current BSSID from WifiInfo in case that firmware initiated
+            // roaming happened.
+            String currentBssid = cmmState.wifiInfo.getBSSID();
+            WifiConfiguration currentNetwork =
+                    mWifiConfigManager.getConfiguredNetwork(cmmState.wifiInfo.getNetworkId());
+            if (currentNetwork != null) {
+                wifiCandidates.setCurrent(currentNetwork.networkId, currentBssid);
+                // We always want the current network to be a candidate so that it can participate.
+                // It may also get re-added by a nominator, in which case this fallback
+                // will be replaced.
+                MacAddress bssid = MacAddress.fromString(currentBssid);
+                SecurityParams params = currentNetwork.getNetworkSelectionStatus()
+                        .getCandidateSecurityParams();
+                if (null == params) {
+                    localLog("No known candidate security params for current network.");
+                    continue;
+                }
+                WifiCandidates.Key key = new WifiCandidates.Key(
+                        ScanResultMatchInfo.fromWifiConfiguration(currentNetwork),
+                        bssid, currentNetwork.networkId,
+                        params.getSecurityType());
+                ScanDetail scanDetail = findScanDetailForBssid(mFilteredNetworks, currentBssid);
+                int predictedTputMbps = (scanDetail == null) ? 0 : predictThroughput(scanDetail);
+                wifiCandidates.add(key, currentNetwork,
+                        NetworkNominator.NOMINATOR_ID_CURRENT,
+                        cmmState.wifiInfo.getRssi(),
+                        cmmState.wifiInfo.getFrequency(),
+                        calculateLastSelectionWeight(currentNetwork.networkId),
+                        WifiConfiguration.isMetered(currentNetwork, cmmState.wifiInfo),
+                        isFromCarrierOrPrivilegedApp(currentNetwork),
+                        predictedTputMbps);
+            }
         }
+
+        // Update all configured networks before initiating network selection.
+        updateConfiguredNetworks();
+
         for (NetworkNominator registeredNominator : mNominators) {
             localLog("About to run " + registeredNominator.getName() + " :");
             registeredNominator.nominateNetworks(
-                    new ArrayList<>(mFilteredNetworks), currentNetwork, currentBssid, connected,
-                    untrustedNetworkAllowed,
+                    new ArrayList<>(mFilteredNetworks),
+                    untrustedNetworkAllowed, oemPaidNetworkAllowed, oemPrivateNetworkAllowed,
                     (scanDetail, config) -> {
                         WifiCandidates.Key key = wifiCandidates.keyFromScanDetailAndConfig(
                                 scanDetail, config);
                         if (key != null) {
-                            boolean metered = isEverMetered(config, wifiInfo, scanDetail);
+                            boolean metered = false;
+                            for (ClientModeManagerState cmmState : cmmStates) {
+                                if (isEverMetered(config, cmmState.wifiInfo, scanDetail)) {
+                                    metered = true;
+                                    break;
+                                }
+                            }
                             // TODO(b/151981920) Saved passpoint candidates are marked ephemeral
                             boolean added = wifiCandidates.add(key, config,
                                     registeredNominator.getId(),
@@ -849,13 +913,87 @@
     }
 
     /**
+     * For transition networks with only legacy networks,
+     * remove auto-upgrade type to use the legacy type to
+     * avoid roaming issues between two types.
+     */
+    private void removeAutoUpgradeSecurityParamsIfNecessary(
+            WifiConfiguration config,
+            List<SecurityParams> scanResultParamsList,
+            @WifiConfiguration.SecurityType int upgradableSecurityType,
+            boolean isLegacyNetworkInRange,
+            boolean isUpgradableTypeOnlyInRange,
+            boolean isAutoUpgradeEnabled) {
+        localLog("removeAutoUpgradeSecurityParamsIfNecessary:"
+                + " upgradableSecurityType: " + upgradableSecurityType
+                + " isLegacyNetworkInRange: " + isLegacyNetworkInRange
+                + " isUpgradableTypeOnlyInRange: " + isUpgradableTypeOnlyInRange
+                + " isAutoUpgradeEnabled: " + isAutoUpgradeEnabled);
+        if (isAutoUpgradeEnabled) {
+            // Consider removing the auto-upgraded type if legacy networks are in range.
+            if (!isLegacyNetworkInRange) return;
+            // If there are APs with standalone-upgradeable security type is in range,
+            // do not consider removing the auto-upgraded type.
+            if (isUpgradableTypeOnlyInRange) return;
+        }
+
+        SecurityParams upgradableParams = config.getSecurityParams(upgradableSecurityType);
+        if (null == upgradableParams) return;
+        if (!upgradableParams.isAddedByAutoUpgrade()) return;
+        localLog("Remove upgradable security type " + upgradableSecurityType + " for the network.");
+        scanResultParamsList.removeIf(p -> p.isSecurityType(upgradableSecurityType));
+    }
+
+    /** Helper function to place all conditions which need to remove auto-upgrade types. */
+    private void removeSecurityParamsIfNecessary(
+            WifiConfiguration config,
+            List<SecurityParams> scanResultParamsList) {
+        // When offload is supported, both types are passed down.
+        if (!mWifiGlobals.isWpa3SaeUpgradeOffloadEnabled()) {
+            removeAutoUpgradeSecurityParamsIfNecessary(
+                    config, scanResultParamsList,
+                    WifiConfiguration.SECURITY_TYPE_SAE,
+                    mScanRequestProxy.isWpa2PersonalOnlyNetworkInRange(config.SSID),
+                    mScanRequestProxy.isWpa3PersonalOnlyNetworkInRange(config.SSID),
+                    mWifiGlobals.isWpa3SaeUpgradeEnabled());
+        }
+        removeAutoUpgradeSecurityParamsIfNecessary(
+                config, scanResultParamsList,
+                WifiConfiguration.SECURITY_TYPE_OWE,
+                mScanRequestProxy.isOpenOnlyNetworkInRange(config.SSID),
+                mScanRequestProxy.isOweOnlyNetworkInRange(config.SSID),
+                mWifiGlobals.isOweUpgradeEnabled());
+        removeAutoUpgradeSecurityParamsIfNecessary(
+                config, scanResultParamsList,
+                WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE,
+                mScanRequestProxy.isWpa2EnterpriseOnlyNetworkInRange(config.SSID),
+                mScanRequestProxy.isWpa3EnterpriseOnlyNetworkInRange(config.SSID),
+                true);
+    }
+
+    /**
+     * For the transition mode, MFPC should be true, and MFPR should be false,
+     * see WPA3 SAE specification section 2.3 and 3.3.
+     */
+    private void updateSecurityParamsForTransitionModeIfNecessary(
+            ScanResult scanResult, SecurityParams params) {
+        if (params.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE)
+                && ScanResultUtil.isScanResultForPskSaeTransitionNetwork(scanResult)) {
+            params.setRequirePmf(false);
+        } else if (params.isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE)
+                && ScanResultUtil.isScanResultForWpa3EnterpriseTransitionNetwork(scanResult)) {
+            params.setRequirePmf(false);
+        }
+    }
+
+    /**
      * Using the registered Scorers, choose the best network from the list of Candidate(s).
      * The ScanDetailCache is also updated here.
      * @param candidates - Candidates to perferm network selection on.
      * @return WifiConfiguration - the selected network, or null.
      */
-    @NonNull
-    public WifiConfiguration selectNetwork(List<WifiCandidates.Candidate> candidates) {
+    @Nullable
+    public WifiConfiguration selectNetwork(@NonNull List<WifiCandidates.Candidate> candidates) {
         if (candidates == null || candidates.size() == 0) {
             return null;
         }
@@ -870,8 +1008,22 @@
             if (choice == null) continue;
             ScanDetail scanDetail = getScanDetailForCandidateKey(choice.candidateKey);
             if (scanDetail == null) continue;
+            WifiConfiguration config = mWifiConfigManager
+                    .getConfiguredNetwork(choice.candidateKey.networkId);
+            if (config == null) continue;
+            List<SecurityParams> scanResultParamsList = ScanResultUtil
+                    .generateSecurityParamsListFromScanResult(scanDetail.getScanResult());
+            if (scanResultParamsList == null) continue;
+            // Under some conditions, the legacy type is preferred to have better
+            // connectivity behaviors, and the auto-upgrade type should be removed.
+            removeSecurityParamsIfNecessary(config, scanResultParamsList);
+            SecurityParams params = ScanResultMatchInfo.getBestMatchingSecurityParams(
+                    config,
+                    scanResultParamsList);
+            if (params == null) continue;
+            updateSecurityParamsForTransitionModeIfNecessary(scanDetail.getScanResult(), params);
             mWifiConfigManager.setNetworkCandidateScanResult(choice.candidateKey.networkId,
-                    scanDetail.getScanResult(), 0);
+                    scanDetail.getScanResult(), 0, params);
         }
 
         for (Collection<WifiCandidates.Candidate> group : groupedCandidates) {
@@ -970,7 +1122,7 @@
 
     private void updateScanDetailCache(List<ScanDetail> scanDetails) {
         for (ScanDetail scanDetail : scanDetails) {
-            mWifiConfigManager.updateScanDetailCacheFromScanDetail(scanDetail);
+            mWifiConfigManager.updateScanDetailCacheFromScanDetailForSavedNetwork(scanDetail);
         }
     }
 
@@ -1032,8 +1184,10 @@
                     mWifiChannelUtilization.getUtilizationRatio(
                             scanDetail.getScanResult().frequency);
         }
+        ClientModeManager primaryManager =
+                mWifiInjector.getActiveModeWarden().getPrimaryClientModeManager();
         return mThroughputPredictor.predictThroughput(
-                mWifiNative.getDeviceWiphyCapabilities(mWifiNative.getClientInterfaceName()),
+                primaryManager.getDeviceWiphyCapabilities(),
                 scanDetail.getScanResult().getWifiStandard(),
                 scanDetail.getScanResult().channelWidth,
                 scanDetail.getScanResult().level,
@@ -1041,7 +1195,7 @@
                 scanDetail.getNetworkDetail().getMaxNumberSpatialStreams(),
                 scanDetail.getNetworkDetail().getChannelUtilization(),
                 channelUtilizationLinkLayerStats,
-                mIsBluetoothConnected);
+                mWifiGlobals.isBluetoothConnected());
     }
 
     /**
@@ -1104,32 +1258,48 @@
     private static final int ID_PREFIX = 42;
     private static final int MIN_SCORER_EXP_ID = ID_PREFIX * ID_SUFFIX_MOD;
 
-    /**
-     * Set Wifi channel utilization calculated from link layer stats
-     */
-    public void setWifiChannelUtilization(WifiChannelUtilization wifiChannelUtilization) {
-        mWifiChannelUtilization = wifiChannelUtilization;
-    }
-
-    /**
-     * Set whether bluetooth is in the connected state
-     */
-    public void setBluetoothConnected(boolean isBlueToothConnected) {
-        mIsBluetoothConnected = isBlueToothConnected;
-    }
-
-    WifiNetworkSelector(Context context, WifiScoreCard wifiScoreCard, ScoringParams scoringParams,
-            WifiConfigManager configManager, Clock clock, LocalLog localLog,
-            WifiMetrics wifiMetrics, WifiNative wifiNative,
-            ThroughputPredictor throughputPredictor) {
+    WifiNetworkSelector(
+            Context context,
+            WifiScoreCard wifiScoreCard,
+            ScoringParams scoringParams,
+            WifiConfigManager configManager,
+            Clock clock,
+            LocalLog localLog,
+            WifiMetrics wifiMetrics,
+            WifiInjector wifiInjector,
+            ThroughputPredictor throughputPredictor,
+            WifiChannelUtilization wifiChannelUtilization,
+            WifiGlobals wifiGlobals,
+            ScanRequestProxy scanRequestProxy) {
         mContext = context;
-        mWifiConfigManager = configManager;
-        mClock = clock;
         mWifiScoreCard = wifiScoreCard;
         mScoringParams = scoringParams;
+        mWifiConfigManager = configManager;
+        mClock = clock;
         mLocalLog = localLog;
         mWifiMetrics = wifiMetrics;
-        mWifiNative = wifiNative;
+        mWifiInjector = wifiInjector;
         mThroughputPredictor = throughputPredictor;
+        mWifiChannelUtilization = wifiChannelUtilization;
+        mWifiGlobals = wifiGlobals;
+        mScanRequestProxy = scanRequestProxy;
+    }
+
+    private void updateCandidatesSecurityParams(List<ScanDetail> scanDetails) {
+        for (ScanDetail scanDetail : scanDetails) {
+            WifiConfiguration network =
+                    mWifiConfigManager.getSavedNetworkForScanDetail(scanDetail);
+            if (network == null || network.getSecurityParamsList().size() < 2) continue;
+
+            List<SecurityParams> scanResultParamsList = ScanResultUtil
+                    .generateSecurityParamsListFromScanResult(scanDetail.getScanResult());
+            if (scanResultParamsList == null) continue;
+
+            SecurityParams params = ScanResultMatchInfo.getBestMatchingSecurityParams(network,
+                    scanResultParamsList);
+            if (params == null) continue;
+
+            network.getNetworkSelectionStatus().setCandidateSecurityParams(params);
+        }
     }
 }
diff --git a/service/java/com/android/server/wifi/WifiNetworkSuggestionsManager.java b/service/java/com/android/server/wifi/WifiNetworkSuggestionsManager.java
index 2632835..f5a1ebe 100644
--- a/service/java/com/android/server/wifi/WifiNetworkSuggestionsManager.java
+++ b/service/java/com/android/server/wifi/WifiNetworkSuggestionsManager.java
@@ -22,11 +22,11 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.AlertDialog;
 import android.app.AppOpsManager;
 import android.app.Notification;
-import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -39,26 +39,31 @@
 import android.net.MacAddress;
 import android.net.NetworkScoreManager;
 import android.net.wifi.ISuggestionConnectionStatusListener;
+import android.net.wifi.ISuggestionUserApprovalStatusListener;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiNetworkSuggestion;
 import android.net.wifi.WifiScanner;
 import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.Process;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
 import android.view.WindowManager;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
-import com.android.server.wifi.util.ExternalCallbackTracker;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.util.LruConnectionTracker;
 import com.android.server.wifi.util.WifiPermissionsUtil;
 import com.android.wifi.resources.R;
@@ -69,7 +74,6 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -86,6 +90,7 @@
  * NOTE: This class should always be invoked from the main wifi service thread.
  */
 @NotThreadSafe
+@SuppressLint("LongLogTag")
 public class WifiNetworkSuggestionsManager {
     private static final String TAG = "WifiNetworkSuggestionsManager";
 
@@ -116,6 +121,8 @@
     public static final int ACTION_USER_DISALLOWED_APP = 2;
     public static final int ACTION_USER_DISMISS = 3;
 
+    public static final int DEFAULT_PRIORITY_GROUP = 0;
+
     @IntDef(prefix = { "ACTION_USER_" }, value = {
             ACTION_USER_ALLOWED_APP,
             ACTION_USER_DISALLOWED_APP,
@@ -128,13 +135,21 @@
      * Limit number of hidden networks attach to scan
      */
     private static final int NUMBER_OF_HIDDEN_NETWORK_FOR_ONE_SCAN = 100;
+    /**
+     * Expiration timeout for user notification in milliseconds. (15 min)
+     */
+    private static final long NOTIFICATION_EXPIRY_MILLS = 15 * 60 * 1000;
+    /**
+     * Notification update delay in milliseconds. (10 min)
+     */
+    private static final long NOTIFICATION_UPDATE_DELAY_MILLS = 10 * 60 * 1000;
 
     private final WifiContext mContext;
     private final Resources mResources;
     private final Handler mHandler;
     private final AppOpsManager mAppOps;
     private final ActivityManager mActivityManager;
-    private final NotificationManager mNotificationManager;
+    private final WifiNotificationManager mNotificationManager;
     private final NetworkScoreManager mNetworkScoreManager;
     private final PackageManager mPackageManager;
     private final WifiPermissionsUtil mWifiPermissionsUtil;
@@ -144,9 +159,23 @@
     private final FrameworkFacade mFrameworkFacade;
     private final WifiCarrierInfoManager mWifiCarrierInfoManager;
     private final WifiKeyStore mWifiKeyStore;
+    private final Clock mClock;
     // Keep order of network connection.
     private final LruConnectionTracker mLruConnectionTracker;
 
+    private class OnNetworkUpdateListener implements
+            WifiConfigManager.OnNetworkUpdateListener {
+        @Override
+        public void onConnectChoiceSet(@NonNull List<WifiConfiguration> networks,
+                String choiceKey, int rssi) {
+            onUserConnectChoiceSet(networks, choiceKey, rssi);
+        }
+        @Override
+        public void onConnectChoiceRemoved(String choiceKey) {
+            onUserConnectChoiceRemove(choiceKey);
+        }
+    }
+
     /**
      * Per app meta data to store network suggestions, status, etc for each app providing network
      * suggestions on the device.
@@ -165,9 +194,10 @@
          */
         public final String featureId;
         /**
-         * Set of active network suggestions provided by the app.
+97         * Map of active network suggestions provided by the app keyed by hashcode.
          */
-        public final Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = new HashSet<>();
+        public final Map<Integer, ExtendedWifiNetworkSuggestion> extNetworkSuggestions =
+                new ArrayMap<>();
         /**
          * Whether we have shown the user a notification for this app.
          */
@@ -197,6 +227,16 @@
         }
 
         /**
+         * Needed when a normal App became carrier privileged when SIM insert
+         */
+        public void setCarrierId(int carrierId) {
+            if (this.carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) {
+                this.carrierId = carrierId;
+            }
+            // else ignored.
+        }
+
+        /**
          * Returns true if this app has the necessary approvals to place network suggestions.
          */
         private boolean isApproved(@Nullable String activeScorerPkg) {
@@ -254,6 +294,9 @@
         // Store the pointer to the corresponding app's meta data.
         public final PerAppInfo perAppInfo;
         public boolean isAutojoinEnabled;
+        public String anonymousIdentity = null;
+        public String connectChoice = null;
+        public int connectChoiceRssi = 0;
 
         public ExtendedWifiNetworkSuggestion(@NonNull WifiNetworkSuggestion wns,
                                              @NonNull PerAppInfo perAppInfo,
@@ -265,6 +308,14 @@
             this.wns.wifiConfiguration.ephemeral = true;
             this.wns.wifiConfiguration.creatorName = perAppInfo.packageName;
             this.wns.wifiConfiguration.creatorUid = perAppInfo.uid;
+            if (perAppInfo.carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) {
+                return;
+            }
+            // If App is carrier privileged, set carrier Id to the profile.
+            this.wns.wifiConfiguration.carrierId = perAppInfo.carrierId;
+            if (this.wns.passpointConfiguration != null) {
+                this.wns.passpointConfiguration.setCarrierId(perAppInfo.carrierId);
+            }
         }
 
         @Override
@@ -286,17 +337,6 @@
                     && TextUtils.equals(perAppInfo.packageName, other.perAppInfo.packageName);
         }
 
-        /**
-         * Helper method to set the carrier Id.
-         */
-        public void setCarrierId(int carrierId) {
-            if (wns.passpointConfiguration == null) {
-                wns.wifiConfiguration.carrierId = carrierId;
-            } else {
-                wns.passpointConfiguration.setCarrierId(carrierId);
-            }
-        }
-
         @Override
         public String toString() {
             return new StringBuilder(wns.toString())
@@ -316,9 +356,27 @@
         /**
          * Create a {@link WifiConfiguration} from suggestion for framework internal use.
          */
-        public WifiConfiguration createInternalWifiConfiguration() {
+        public WifiConfiguration createInternalWifiConfiguration(
+                @Nullable WifiCarrierInfoManager carrierInfoManager) {
             WifiConfiguration config = new WifiConfiguration(wns.getWifiConfiguration());
             config.allowAutojoin = isAutojoinEnabled;
+            if (config.enterpriseConfig
+                    != null && config.enterpriseConfig.isAuthenticationSimBased()) {
+                config.enterpriseConfig.setAnonymousIdentity(anonymousIdentity);
+            }
+            config.getNetworkSelectionStatus().setConnectChoice(connectChoice);
+            config.getNetworkSelectionStatus().setConnectChoiceRssi(connectChoiceRssi);
+            if (carrierInfoManager != null) {
+                config.subscriptionId = carrierInfoManager.getBestMatchSubscriptionId(config);
+                // shouldDisableMacRandomization checks if the SSID matches with a SSID configured
+                // in CarrierConfigManger for the provided subscriptionId.
+                if (carrierInfoManager.shouldDisableMacRandomization(config.SSID,
+                        config.carrierId, config.subscriptionId)) {
+                    Log.i(TAG, "Disabling MAC randomization on " + config.SSID
+                            + " due to CarrierConfig override");
+                    config.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_NONE;
+                }
+            }
             return config;
         }
     }
@@ -353,17 +411,16 @@
      */
     private final Map<Pair<ScanResultMatchInfo, MacAddress>, Set<ExtendedWifiNetworkSuggestion>>
             mActiveScanResultMatchInfoWithBssid = new HashMap<>();
-    /**
-     * List of {@link WifiNetworkSuggestion} matching the current connected network.
-     */
-    private Set<ExtendedWifiNetworkSuggestion> mActiveNetworkSuggestionsMatchingConnection;
 
     private final Map<String, Set<ExtendedWifiNetworkSuggestion>>
             mPasspointInfo = new HashMap<>();
 
-    private final HashMap<String, ExternalCallbackTracker<ISuggestionConnectionStatusListener>>
+    private final HashMap<String, RemoteCallbackList<ISuggestionConnectionStatusListener>>
             mSuggestionStatusListenerPerApp = new HashMap<>();
 
+    private final HashMap<String, RemoteCallbackList<ISuggestionUserApprovalStatusListener>>
+            mSuggestionUserApprovalStatusListenerPerApp = new HashMap<>();
+
     /**
      * Store the suggestion update listeners.
      */
@@ -383,15 +440,21 @@
      */
     private boolean mHasNewDataToSerialize = false;
     /**
-     * Indicates if the user approval notification is active.
+     * The {@link Clock#getElapsedSinceBootMillis()} must be at least this value for us
+     * to update/show the notification.
      */
-    private boolean mUserApprovalUiActive = false;
+    private long mNotificationUpdateTime;
 
     private boolean mIsLastUserApprovalUiDialog = false;
 
     private boolean mUserDataLoaded = false;
 
     /**
+     * Keep a set of packageNames which is treated as carrier provider.
+     */
+    private final Set<String> mCrossCarrierProvidersSet = new ArraySet<>();
+
+    /**
      * Listener for app-ops changes for active suggestor apps.
      */
     private final class AppOpsChangedListener implements AppOpsManager.OnOpChangedListener {
@@ -436,14 +499,14 @@
         @Override
         public Map<String, PerAppInfo> toSerialize() {
             for (Map.Entry<String, PerAppInfo> entry : mActiveNetworkSuggestionsPerApp.entrySet()) {
-                Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions =
-                        entry.getValue().extNetworkSuggestions;
-                for (ExtendedWifiNetworkSuggestion ewns : extNetworkSuggestions) {
+                for (ExtendedWifiNetworkSuggestion ewns : entry.getValue().extNetworkSuggestions
+                        .values()) {
                     if (ewns.wns.passpointConfiguration != null) {
                         continue;
                     }
                     ewns.wns.wifiConfiguration.isMostRecentlyConnected = mLruConnectionTracker
-                            .isMostRecentlyConnected(ewns.createInternalWifiConfiguration());
+                            .isMostRecentlyConnected(ewns.createInternalWifiConfiguration(
+                                    mWifiCarrierInfoManager));
                 }
             }
             // Clear the flag after writing to disk.
@@ -459,8 +522,8 @@
             // Build the scan cache.
             for (Map.Entry<String, PerAppInfo> entry : networkSuggestionsMap.entrySet()) {
                 String packageName = entry.getKey();
-                Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions =
-                        entry.getValue().extNetworkSuggestions;
+                Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestions =
+                        entry.getValue().extNetworkSuggestions.values();
                 if (!extNetworkSuggestions.isEmpty()) {
                     // Start tracking app-op changes from the app if they have active suggestions.
                     startTrackingAppOpsChange(packageName,
@@ -472,7 +535,8 @@
                     } else {
                         if (ewns.wns.wifiConfiguration.isMostRecentlyConnected) {
                             mLruConnectionTracker
-                                    .addNetwork(ewns.createInternalWifiConfiguration());
+                                    .addNetwork(ewns.createInternalWifiConfiguration(
+                                            mWifiCarrierInfoManager));
                         }
                         addToScanResultMatchInfoMap(ewns);
                     }
@@ -499,8 +563,8 @@
     private void handleUserAllowAction(int uid, String packageName) {
         Log.i(TAG, "User clicked to allow app");
         // Set the user approved flag.
-        setHasUserApprovedForApp(true, packageName);
-        mUserApprovalUiActive = false;
+        setHasUserApprovedForApp(true, uid, packageName);
+        mNotificationUpdateTime = 0;
         mWifiMetrics.addUserApprovalSuggestionAppUiReaction(
                 ACTION_USER_ALLOWED_APP,
                 mIsLastUserApprovalUiDialog);
@@ -508,12 +572,12 @@
 
     private void handleUserDisallowAction(int uid, String packageName) {
         Log.i(TAG, "User clicked to disallow app");
-        // Set the user approved flag.
-        setHasUserApprovedForApp(false, packageName);
         // Take away CHANGE_WIFI_STATE app-ops from the app.
         mAppOps.setMode(AppOpsManager.OPSTR_CHANGE_WIFI_STATE, uid, packageName,
                 MODE_IGNORED);
-        mUserApprovalUiActive = false;
+        // Set the user approved flag.
+        setHasUserApprovedForApp(false, uid, packageName);
+        mNotificationUpdateTime = 0;
         mWifiMetrics.addUserApprovalSuggestionAppUiReaction(
                 ACTION_USER_DISALLOWED_APP,
                 mIsLastUserApprovalUiDialog);
@@ -521,7 +585,7 @@
 
     private void handleUserDismissAction() {
         Log.i(TAG, "User dismissed the notification");
-        mUserApprovalUiActive = false;
+        mNotificationUpdateTime = 0;
         mWifiMetrics.addUserApprovalSuggestionAppUiReaction(
                 ACTION_USER_DISMISS,
                 mIsLastUserApprovalUiDialog);
@@ -583,14 +647,13 @@
             WifiInjector wifiInjector, WifiPermissionsUtil wifiPermissionsUtil,
             WifiConfigManager wifiConfigManager, WifiConfigStore wifiConfigStore,
             WifiMetrics wifiMetrics, WifiCarrierInfoManager wifiCarrierInfoManager,
-            WifiKeyStore keyStore, LruConnectionTracker lruConnectionTracker) {
+            WifiKeyStore keyStore, LruConnectionTracker lruConnectionTracker,
+            Clock clock) {
         mContext = context;
         mResources = context.getResources();
         mHandler = handler;
         mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
         mActivityManager = context.getSystemService(ActivityManager.class);
-        mNotificationManager =
-                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
         mNetworkScoreManager = context.getSystemService(NetworkScoreManager.class);
         mPackageManager = context.getPackageManager();
         mWifiInjector = wifiInjector;
@@ -600,6 +663,8 @@
         mWifiMetrics = wifiMetrics;
         mWifiCarrierInfoManager = wifiCarrierInfoManager;
         mWifiKeyStore = keyStore;
+        mNotificationManager = mWifiInjector.getWifiNotificationManager();
+        mClock = clock;
 
         // register the data store for serializing/deserializing data.
         wifiConfigStore.registerStoreData(
@@ -616,6 +681,8 @@
 
         mContext.registerReceiver(mBroadcastReceiver, mIntentFilter, null, handler);
         mLruConnectionTracker = lruConnectionTracker;
+        mWifiConfigManager.addOnNetworkUpdateListener(
+                new WifiNetworkSuggestionsManager.OnNetworkUpdateListener());
     }
 
     /**
@@ -715,7 +782,7 @@
 
     private void removeNetworkFromScoreCard(WifiConfiguration wifiConfiguration) {
         WifiConfiguration existing =
-                mWifiConfigManager.getConfiguredNetwork(wifiConfiguration.getKey());
+                mWifiConfigManager.getConfiguredNetwork(wifiConfiguration.getProfileKey());
         // If there is a saved network, do not remove from the score card.
         if (existing != null && !existing.fromWifiNetworkSuggestion) {
             return;
@@ -729,6 +796,7 @@
         if (extendedWifiNetworkSuggestions == null) {
             extendedWifiNetworkSuggestions = new HashSet<>();
         }
+        extendedWifiNetworkSuggestions.remove(ewns);
         extendedWifiNetworkSuggestions.add(ewns);
         mPasspointInfo.put(ewns.wns.wifiConfiguration.FQDN, extendedWifiNetworkSuggestions);
     }
@@ -747,27 +815,6 @@
         }
     }
 
-
-    // Issues a disconnect if the only serving network suggestion is removed.
-    private void removeFromConfigManagerIfServingNetworkSuggestionRemoved(
-            Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestionsRemoved) {
-        if (mActiveNetworkSuggestionsMatchingConnection == null
-                || mActiveNetworkSuggestionsMatchingConnection.isEmpty()) {
-            return;
-        }
-        WifiConfiguration activeWifiConfiguration =
-                mActiveNetworkSuggestionsMatchingConnection.iterator().next().wns.wifiConfiguration;
-        if (mActiveNetworkSuggestionsMatchingConnection.removeAll(extNetworkSuggestionsRemoved)) {
-            if (mActiveNetworkSuggestionsMatchingConnection.isEmpty()) {
-                Log.i(TAG, "Only network suggestion matching the connected network removed. "
-                        + "Removing from config manager...");
-                // will trigger a disconnect.
-                mWifiConfigManager.removeSuggestionConfiguredNetwork(
-                        activeWifiConfiguration.getKey());
-            }
-        }
-    }
-
     private void startTrackingAppOpsChange(@NonNull String packageName, int uid) {
         AppOpsChangedListener appOpsChangedListener =
                 new AppOpsChangedListener(packageName, uid);
@@ -785,10 +832,9 @@
             final PerAppInfo perAppInfo) {
         return networkSuggestions
                 .stream()
-                .collect(Collectors.mapping(
-                        n -> ExtendedWifiNetworkSuggestion.fromWns(n, perAppInfo,
-                                n.isInitialAutoJoinEnabled),
-                        Collectors.toSet()));
+                .map(n -> ExtendedWifiNetworkSuggestion.fromWns(n, perAppInfo,
+                        n.isInitialAutoJoinEnabled))
+                .collect(Collectors.toSet());
     }
 
     /**
@@ -800,15 +846,14 @@
             final Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestions) {
         return extNetworkSuggestions
                 .stream()
-                .collect(Collectors.mapping(
-                        n -> n.wns,
-                        Collectors.toSet()));
+                .map(n -> n.wns)
+                .collect(Collectors.toSet());
     }
 
     private void updateWifiConfigInWcmIfPresent(
             WifiConfiguration newConfig, int uid, String packageName) {
         WifiConfiguration configInWcm =
-                mWifiConfigManager.getConfiguredNetwork(newConfig.getKey());
+                mWifiConfigManager.getConfiguredNetwork(newConfig.getProfileKey());
         if (configInWcm == null) return;
         // !suggestion
         if (!configInWcm.fromWifiNetworkSuggestion) return;
@@ -821,11 +866,12 @@
                 newConfig, uid, packageName);
         if (!result.isSuccess()) {
             Log.e(TAG, "Failed to update config in WifiConfigManager");
-        } else {
-            if (mVerboseLoggingEnabled) {
-                Log.v(TAG, "Updated config in WifiConfigManager");
-            }
+            return;
         }
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "Updated config in WifiConfigManager");
+        }
+        mWifiConfigManager.allowAutojoin(result.getNetworkId(), newConfig.allowAutojoin);
     }
 
     /**
@@ -849,17 +895,25 @@
         if (mVerboseLoggingEnabled) {
             Log.v(TAG, "Adding " + networkSuggestions.size() + " networks from " + packageName);
         }
-        if (!validateNetworkSuggestions(networkSuggestions)) {
+        if (!validateNetworkSuggestions(networkSuggestions, packageName, uid)) {
             Log.e(TAG, "Invalid suggestion add from app: " + packageName);
             return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_INVALID;
         }
-        if (!validateCarrierNetworkSuggestions(networkSuggestions, uid, packageName)) {
+        int carrierId = mWifiCarrierInfoManager
+                .getCarrierIdForPackageWithCarrierPrivileges(packageName);
+        if (!validateCarrierNetworkSuggestions(networkSuggestions, uid, packageName, carrierId)) {
             Log.e(TAG, "bad wifi suggestion from app: " + packageName);
             return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_NOT_ALLOWED;
         }
+        for (WifiNetworkSuggestion wns : networkSuggestions) {
+            wns.wifiConfiguration.convertLegacyFieldsToSecurityParamsIfNeeded();
+            if (!WifiConfigurationUtil.addUpgradableSecurityTypeIfNecessary(
+                    wns.wifiConfiguration)) {
+                Log.e(TAG, "Invalid suggestion add from app: " + packageName);
+                return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_INVALID;
+            }
+        }
 
-        int carrierId = mWifiCarrierInfoManager
-                .getCarrierIdForPackageWithCarrierPrivileges(packageName);
         final String activeScorerPackage = mNetworkScoreManager.getActiveScorerPackage();
         PerAppInfo perAppInfo = mActiveNetworkSuggestionsPerApp.get(packageName);
         if (perAppInfo == null) {
@@ -870,9 +924,14 @@
                 perAppInfo.hasUserApproved = true;
                 mWifiMetrics.incrementNetworkSuggestionApiUsageNumOfAppInType(
                         APP_TYPE_NETWORK_PROVISIONING);
+            } else if (mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)
+                        || isAppWorkingAsCrossCarrierProvider(packageName)) {
+                // Bypass added for tests & shell commands.
+                Log.i(TAG, "Setting the test app approved");
+                perAppInfo.hasUserApproved = true;
             } else if (carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) {
                 Log.i(TAG, "Setting the carrier privileged app approved");
-                perAppInfo.carrierId = carrierId;
+                perAppInfo.setCarrierId(carrierId);
                 mWifiMetrics.incrementNetworkSuggestionApiUsageNumOfAppInType(
                         APP_TYPE_CARRIER_PRIVILEGED);
             } else if (perAppInfo.packageName.equals(activeScorerPackage)) {
@@ -890,9 +949,12 @@
                 mWifiMetrics.incrementNetworkSuggestionApiUsageNumOfAppInType(
                         APP_TYPE_NON_PRIVILEGED);
             }
+            onSuggestionUserApprovalStatusChanged(uid, packageName);
         }
         // If PerAppInfo is upgrade from pre-R, uid may not be set.
         perAppInfo.setUid(uid);
+        // If App became carrier privileged, set the carrier Id.
+        perAppInfo.setCarrierId(carrierId);
         Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions =
                 convertToExtendedWnsSet(networkSuggestions, perAppInfo);
         boolean isLowRamDevice = mActivityManager.isLowRamDevice();
@@ -900,10 +962,13 @@
                 WifiManager.getMaxNumberOfNetworkSuggestionsPerApp(isLowRamDevice);
         if (perAppInfo.extNetworkSuggestions.size() + extNetworkSuggestions.size()
                 > networkSuggestionsMaxPerApp) {
-            Set<ExtendedWifiNetworkSuggestion> savedNetworkSuggestions =
-                    new HashSet<>(perAppInfo.extNetworkSuggestions);
-            savedNetworkSuggestions.addAll(extNetworkSuggestions);
-            if (savedNetworkSuggestions.size() > networkSuggestionsMaxPerApp) {
+            Set<Integer> keySet = extNetworkSuggestions
+                    .stream()
+                    .map(ExtendedWifiNetworkSuggestion::hashCode)
+                    .collect(Collectors.toSet());
+            Set<Integer> savedKeySet = new HashSet<>(perAppInfo.extNetworkSuggestions.keySet());
+            savedKeySet.addAll(keySet);
+            if (savedKeySet.size() > networkSuggestionsMaxPerApp) {
                 Log.e(TAG, "Failed to add network suggestions for " + packageName
                         + ". Exceeds max per app, current list size: "
                         + perAppInfo.extNetworkSuggestions.size()
@@ -918,8 +983,17 @@
         }
 
         for (ExtendedWifiNetworkSuggestion ewns: extNetworkSuggestions) {
-            if (carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) {
-                ewns.setCarrierId(carrierId);
+            ExtendedWifiNetworkSuggestion oldEwns = perAppInfo.extNetworkSuggestions
+                    .get(ewns.hashCode());
+            // Keep the user connect choice and AnonymousIdentity
+            if (oldEwns != null) {
+                ewns.connectChoice = oldEwns.connectChoice;
+                ewns.connectChoiceRssi = oldEwns.connectChoiceRssi;
+                ewns.anonymousIdentity = oldEwns.anonymousIdentity;
+                // If user change the auto-join, keep the user choice.
+                if (oldEwns.isAutojoinEnabled != oldEwns.wns.isInitialAutoJoinEnabled) {
+                    ewns.isAutojoinEnabled = oldEwns.isAutojoinEnabled;
+                }
             }
             // If network has no IMSI protection and user didn't approve exemption, make it initial
             // auto join disabled
@@ -932,6 +1006,7 @@
                     ewns.isAutojoinEnabled = false;
                 }
             }
+            mWifiMetrics.addNetworkSuggestionPriorityGroup(ewns.wns.priorityGroup);
             if (ewns.wns.passpointConfiguration == null) {
                 if (ewns.wns.wifiConfiguration.isEnterprise()) {
                     if (!mWifiKeyStore.updateNetworkKeys(ewns.wns.wifiConfiguration, null)) {
@@ -943,8 +1018,8 @@
                 // If we have a config in WifiConfigManager for this suggestion, update
                 // WifiConfigManager with the latest WifiConfig.
                 // Note: Similar logic is present in PasspointManager for passpoint networks.
-                updateWifiConfigInWcmIfPresent(
-                        ewns.createInternalWifiConfiguration(), uid, packageName);
+                updateWifiConfigInWcmIfPresent(ewns.createInternalWifiConfiguration(
+                        mWifiCarrierInfoManager), uid, packageName);
                 addToScanResultMatchInfoMap(ewns);
             } else {
                 ewns.wns.passpointConfiguration.setAutojoinEnabled(ewns.isAutojoinEnabled);
@@ -958,8 +1033,8 @@
                 }
                 addToPasspointInfoMap(ewns);
             }
-            perAppInfo.extNetworkSuggestions.remove(ewns);
-            perAppInfo.extNetworkSuggestions.add(ewns);
+            perAppInfo.extNetworkSuggestions.remove(ewns.hashCode());
+            perAppInfo.extNetworkSuggestions.put(ewns.hashCode(), ewns);
         }
         for (OnSuggestionUpdateListener listener : mListeners) {
             listener.onSuggestionsAddedOrUpdated(networkSuggestions);
@@ -988,20 +1063,39 @@
         }
     }
 
-    private boolean validateNetworkSuggestions(List<WifiNetworkSuggestion> networkSuggestions) {
+    private boolean checkNetworkSuggestionsNoNulls(List<WifiNetworkSuggestion> networkSuggestions) {
         for (WifiNetworkSuggestion wns : networkSuggestions) {
             if (wns == null || wns.wifiConfiguration == null) {
                 return false;
             }
+        }
+        return true;
+    }
+
+    private boolean validateNetworkSuggestions(
+            List<WifiNetworkSuggestion> networkSuggestions, String packageName, int uid) {
+        if (!checkNetworkSuggestionsNoNulls(networkSuggestions)) {
+            return false;
+        }
+        for (WifiNetworkSuggestion wns : networkSuggestions) {
             if (wns.passpointConfiguration == null) {
-                if (!WifiConfigurationUtil.validate(wns.wifiConfiguration,
+                WifiConfiguration config = wns.wifiConfiguration;
+                if (!WifiConfigurationUtil.validate(config,
                         WifiConfigurationUtil.VALIDATE_FOR_ADD)) {
                     return false;
                 }
-                if (wns.wifiConfiguration.isEnterprise()
-                        && wns.wifiConfiguration.enterpriseConfig.isInsecure()) {
-                    Log.e(TAG, "Insecure enterprise suggestion is invalid.");
-                    return false;
+                if (config.isEnterprise()) {
+                    final WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
+                    if (enterpriseConfig.isEapMethodServerCertUsed()
+                            && !enterpriseConfig.isMandatoryParameterSetForServerCertValidation()) {
+                        Log.e(TAG, "Insecure enterprise suggestion is invalid.");
+                        return false;
+                    }
+                    final String alias = enterpriseConfig.getClientKeyPairAliasInternal();
+                    if (alias != null && !mWifiKeyStore.validateKeyChainAlias(alias, uid)) {
+                        Log.e(TAG, "Invalid client key pair KeyChain alias: " + alias);
+                        return false;
+                    }
                 }
 
             } else {
@@ -1009,36 +1103,116 @@
                     return false;
                 }
             }
+            if (!isAppWorkingAsCrossCarrierProvider(packageName)
+                    && !isValidCarrierMergedNetworkSuggestion(wns)) {
+                Log.e(TAG, "Merged carrier network must be a metered, enterprise config with a "
+                        + "valid subscription Id");
+                return false;
+            }
+            if (!SdkLevel.isAtLeastS()) {
+                if (wns.wifiConfiguration.oemPaid) {
+                    Log.e(TAG, "OEM paid suggestions are only allowed from Android S.");
+                    return false;
+                }
+                if (wns.wifiConfiguration.oemPrivate) {
+                    Log.e(TAG, "OEM private suggestions are only allowed from Android S.");
+                    return false;
+                }
+                if (wns.wifiConfiguration.subscriptionId
+                        != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                    Log.e(TAG, "Setting Subscription Id is only allowed from Android S.");
+                    return false;
+                }
+                if (wns.priorityGroup != 0) {
+                    Log.e(TAG, "Setting Priority group is only allowed from Android S.");
+                    return false;
+                }
+                if (wns.wifiConfiguration.carrierMerged) {
+                    Log.e(TAG, "Setting carrier merged network is only allowed from Android S.");
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    private boolean isValidCarrierMergedNetworkSuggestion(WifiNetworkSuggestion wns) {
+        if (!wns.wifiConfiguration.carrierMerged) {
+            // Not carrier merged.
+            return true;
+        }
+        if (!wns.wifiConfiguration.isEnterprise() && wns.passpointConfiguration == null) {
+            // Carrier merged network must be a enterprise network.
+            return false;
+        }
+        if (!WifiConfiguration.isMetered(wns.wifiConfiguration, null)) {
+            // Carrier merged network must be metered.
+            return false;
+        }
+        if (wns.wifiConfiguration.subscriptionId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            // Carrier merged network must have a valid subscription Id.
+            return false;
         }
         return true;
     }
 
     private boolean validateCarrierNetworkSuggestions(
-            List<WifiNetworkSuggestion> networkSuggestions, int uid, String packageName) {
-        if (mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(uid)
-                || mWifiCarrierInfoManager.getCarrierIdForPackageWithCarrierPrivileges(packageName)
-                != TelephonyManager.UNKNOWN_CARRIER_ID) {
-            return true;
-        }
-        // If an app doesn't have carrier privileges or carrier provisioning permission, suggests
-        // SIM-based network and sets CarrierId are illegal.
+            List<WifiNetworkSuggestion> networkSuggestions, int uid, String packageName,
+            int provisionerCarrierId) {
+        boolean isAppWorkingAsCrossCarrierProvider = isAppWorkingAsCrossCarrierProvider(
+                packageName);
+        boolean isCrossCarrierProvisioner =
+                mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(uid)
+                        || isAppWorkingAsCrossCarrierProvider;
+
         for (WifiNetworkSuggestion suggestion : networkSuggestions) {
             WifiConfiguration wifiConfiguration = suggestion.wifiConfiguration;
             PasspointConfiguration passpointConfiguration = suggestion.passpointConfiguration;
-            if (passpointConfiguration == null) {
-                if (wifiConfiguration.carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) {
-                    return false;
-                }
-                if (wifiConfiguration.enterpriseConfig != null
-                        && wifiConfiguration.enterpriseConfig.isAuthenticationSimBased()) {
-                    return false;
+            if (wifiConfiguration.carrierMerged && !areCarrierMergedSuggestionsAllowed(
+                    wifiConfiguration.subscriptionId, packageName)) {
+                // Carrier must be explicitly configured as merged carrier offload enabled
+                return false;
+            }
+            if (!isCrossCarrierProvisioner && provisionerCarrierId
+                    ==  TelephonyManager.UNKNOWN_CARRIER_ID) {
+                // If an app doesn't have carrier privileges or carrier provisioning permission,
+                // suggests SIM-based network, sets CarrierId and sets SubscriptionId are illegal.
+                if (passpointConfiguration == null) {
+                    if (wifiConfiguration.carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) {
+                        return false;
+                    }
+                    if (wifiConfiguration.subscriptionId
+                            != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                        return false;
+                    }
+                    if (wifiConfiguration.enterpriseConfig != null
+                            && wifiConfiguration.enterpriseConfig.isAuthenticationSimBased()) {
+                        return false;
+                    }
+                } else {
+                    if (passpointConfiguration.getCarrierId()
+                            != TelephonyManager.UNKNOWN_CARRIER_ID) {
+                        return false;
+                    }
+                    if (passpointConfiguration.getSubscriptionId()
+                            != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                        return false;
+                    }
+                    if (passpointConfiguration.getCredential() != null
+                            && passpointConfiguration.getCredential().getSimCredential() != null) {
+                        return false;
+                    }
                 }
             } else {
-                if (passpointConfiguration.getCarrierId() != TelephonyManager.UNKNOWN_CARRIER_ID) {
-                    return false;
-                }
-                if (passpointConfiguration.getCredential() != null
-                        && passpointConfiguration.getCredential().getSimCredential() != null) {
+                int carrierId = isCrossCarrierProvisioner ? wifiConfiguration.carrierId
+                        : provisionerCarrierId;
+                int subId = passpointConfiguration == null ? wifiConfiguration.subscriptionId
+                        : passpointConfiguration.getSubscriptionId();
+                if (!mWifiCarrierInfoManager
+                        .isSubIdMatchingCarrierId(subId, carrierId)) {
+                    Log.e(TAG, "Subscription ID doesn't match the carrier. CarrierId:"
+                            + carrierId + ", subscriptionId:" + subId + ", NetworkSuggestion:"
+                            + suggestion);
                     return false;
                 }
             }
@@ -1066,11 +1240,11 @@
             @NonNull PerAppInfo perAppInfo) {
         // Get internal suggestions
         Set<ExtendedWifiNetworkSuggestion> removingExtSuggestions =
-                new HashSet<>(perAppInfo.extNetworkSuggestions);
+                new HashSet<>(perAppInfo.extNetworkSuggestions.values());
         if (!extNetworkSuggestions.isEmpty()) {
             // Keep the internal suggestions need to remove.
             removingExtSuggestions.retainAll(extNetworkSuggestions);
-            perAppInfo.extNetworkSuggestions.removeAll(extNetworkSuggestions);
+            perAppInfo.extNetworkSuggestions.values().removeAll(extNetworkSuggestions);
         } else {
             // empty list is used to clear everything for the app. Store a copy for use below.
             perAppInfo.extNetworkSuggestions.clear();
@@ -1098,14 +1272,19 @@
                     mWifiKeyStore.removeKeys(ewns.wns.wifiConfiguration.enterpriseConfig);
                 }
                 removeFromScanResultMatchInfoMapAndRemoveRelatedScoreCard(ewns);
+                mWifiConfigManager.removeConnectChoiceFromAllNetworks(ewns
+                        .createInternalWifiConfiguration(mWifiCarrierInfoManager)
+                        .getProfileKey());
             }
             removingSuggestions.add(ewns.wns);
+            // Remove the config from WifiConfigManager. If current connected suggestion is remove,
+            // would trigger a disconnect.
+            mWifiConfigManager.removeSuggestionConfiguredNetwork(
+                    ewns.createInternalWifiConfiguration(mWifiCarrierInfoManager));
         }
         for (OnSuggestionUpdateListener listener : mListeners) {
             listener.onSuggestionsRemoved(removingSuggestions);
         }
-        // Disconnect suggested network if connected
-        removeFromConfigManagerIfServingNetworkSuggestionRemoved(removingExtSuggestions);
     }
 
     /**
@@ -1128,9 +1307,8 @@
         if (mVerboseLoggingEnabled) {
             Log.v(TAG, "Removing " + networkSuggestions.size() + " networks from " + packageName);
         }
-
-        if (!validateNetworkSuggestions(networkSuggestions)) {
-            Log.e(TAG, "Invalid suggestion remove from app: " + packageName);
+        if (!checkNetworkSuggestionsNoNulls(networkSuggestions)) {
+            Log.e(TAG, "Null in suggestion remove from app: " + packageName);
             return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID;
         }
         PerAppInfo perAppInfo = mActiveNetworkSuggestionsPerApp.get(packageName);
@@ -1141,9 +1319,13 @@
         }
         Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions =
                 convertToExtendedWnsSet(networkSuggestions, perAppInfo);
+        Set<Integer> keySet = extNetworkSuggestions
+                .stream()
+                .map(ExtendedWifiNetworkSuggestion::hashCode)
+                .collect(Collectors.toSet());
         // check if all the request network suggestions are present in the active list.
         if (!extNetworkSuggestions.isEmpty()
-                && !perAppInfo.extNetworkSuggestions.containsAll(extNetworkSuggestions)) {
+                && !perAppInfo.extNetworkSuggestions.keySet().containsAll(keySet)) {
             Log.e(TAG, "Failed to remove network suggestions for " + packageName
                     + ". Network suggestions not found in active network suggestions");
             return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID;
@@ -1161,12 +1343,12 @@
     public void removeApp(@NonNull String packageName) {
         PerAppInfo perAppInfo = mActiveNetworkSuggestionsPerApp.get(packageName);
         if (perAppInfo == null) return;
-        removeInternal(Collections.EMPTY_LIST, packageName, perAppInfo);
+        removeInternal(List.of(), packageName, perAppInfo);
         // Remove the package fully from the internal database.
         mActiveNetworkSuggestionsPerApp.remove(packageName);
-        ExternalCallbackTracker<ISuggestionConnectionStatusListener> listenerTracker =
+        RemoteCallbackList<ISuggestionConnectionStatusListener> listenerTracker =
                 mSuggestionStatusListenerPerApp.remove(packageName);
-        if (listenerTracker != null) listenerTracker.clear();
+        if (listenerTracker != null) listenerTracker.kill();
         saveToStore();
         Log.i(TAG, "Removed " + packageName);
     }
@@ -1188,7 +1370,8 @@
         PerAppInfo perAppInfo = mActiveNetworkSuggestionsPerApp.get(packageName);
         // if App never suggested return empty list.
         if (perAppInfo == null) return networkSuggestionList;
-        for (ExtendedWifiNetworkSuggestion extendedSuggestion : perAppInfo.extNetworkSuggestions) {
+        for (ExtendedWifiNetworkSuggestion extendedSuggestion : perAppInfo.extNetworkSuggestions
+                .values()) {
             networkSuggestionList.add(extendedSuggestion.wns);
         }
         return networkSuggestionList;
@@ -1202,10 +1385,12 @@
                 mActiveNetworkSuggestionsPerApp.entrySet().iterator();
         while (iter.hasNext()) {
             Map.Entry<String, PerAppInfo> entry = iter.next();
-            removeInternal(Collections.EMPTY_LIST, entry.getKey(), entry.getValue());
+            removeInternal(List.of(), entry.getKey(), entry.getValue());
             iter.remove();
         }
         mSuggestionStatusListenerPerApp.clear();
+        mSuggestionUserApprovalStatusListenerPerApp.clear();
+        resetNotification();
         saveToStore();
         Log.i(TAG, "Cleared all internal state");
     }
@@ -1223,7 +1408,7 @@
     /**
      * Enable or Disable network suggestions for the app.
      */
-    public void setHasUserApprovedForApp(boolean approved, String packageName) {
+    public void setHasUserApprovedForApp(boolean approved, int uid, String packageName) {
         PerAppInfo perAppInfo = mActiveNetworkSuggestionsPerApp.get(packageName);
         if (perAppInfo == null) return;
 
@@ -1232,6 +1417,7 @@
                     + (approved ? " approved" : " not approved"));
         }
         perAppInfo.hasUserApproved = approved;
+        onSuggestionUserApprovalStatusChanged(uid, packageName);
         saveToStore();
     }
 
@@ -1241,7 +1427,7 @@
      */
     private void restoreInitialAutojoinForCarrierId(int carrierId) {
         for (PerAppInfo appInfo : mActiveNetworkSuggestionsPerApp.values()) {
-            for (ExtendedWifiNetworkSuggestion ewns : appInfo.extNetworkSuggestions) {
+            for (ExtendedWifiNetworkSuggestion ewns : appInfo.extNetworkSuggestions.values()) {
                 if (!(isSimBasedSuggestion(ewns)
                         && getCarrierIdFromSuggestion(ewns) == carrierId)) {
                     continue;
@@ -1255,9 +1441,15 @@
                     mWifiInjector.getPasspointManager()
                             .enableAutojoin(ewns.wns.passpointConfiguration.getUniqueId(),
                                     null, ewns.isAutojoinEnabled);
+                } else {
+                    // Update WifiConfigManager to sync auto-join.
+                    updateWifiConfigInWcmIfPresent(ewns.createInternalWifiConfiguration(
+                            mWifiCarrierInfoManager),
+                            ewns.perAppInfo.uid, ewns.perAppInfo.packageName);
                 }
             }
         }
+        saveToStore();
     }
 
     /**
@@ -1267,7 +1459,7 @@
     public Set<WifiNetworkSuggestion> getAllNetworkSuggestions() {
         return mActiveNetworkSuggestionsPerApp.values()
                 .stream()
-                .flatMap(e -> convertToWnsSet(e.extNetworkSuggestions)
+                .flatMap(e -> convertToWnsSet(e.extNetworkSuggestions.values())
                         .stream())
                 .collect(Collectors.toSet());
     }
@@ -1280,7 +1472,7 @@
         return mActiveNetworkSuggestionsPerApp.values()
                 .stream()
                 .filter(e -> e.isApproved(activeScorerPackage))
-                .flatMap(e -> convertToWnsSet(e.extNetworkSuggestions)
+                .flatMap(e -> convertToWnsSet(e.extNetworkSuggestions.values())
                         .stream())
                 .collect(Collectors.toSet());
     }
@@ -1295,14 +1487,15 @@
             if (!info.isApproved(activeScorerPackage)) {
                 continue;
             }
-            for (ExtendedWifiNetworkSuggestion ewns : info.extNetworkSuggestions) {
+            for (ExtendedWifiNetworkSuggestion ewns : info.extNetworkSuggestions.values()) {
                 if (ewns.wns.getPasspointConfig() != null) {
                     continue;
                 }
                 WifiConfiguration network = mWifiConfigManager
-                        .getConfiguredNetwork(ewns.wns.getWifiConfiguration().getKey());
+                        .getConfiguredNetwork(ewns.wns.getWifiConfiguration()
+                                .getProfileKey());
                 if (network == null) {
-                    network = ewns.createInternalWifiConfiguration();
+                    network = ewns.createInternalWifiConfiguration(mWifiCarrierInfoManager);
                 }
                 networks.add(network);
             }
@@ -1320,11 +1513,11 @@
     private PendingIntent getPrivateBroadcast(@NonNull String action,
             @NonNull Pair<String, String> extra1, @NonNull Pair<String, Integer> extra2) {
         Intent intent = new Intent(action)
-                .setPackage(mWifiInjector.getWifiStackPackageName())
+                .setPackage(mContext.getServiceWifiPackageName())
                 .putExtra(extra1.first, extra1.second)
                 .putExtra(extra2.first, extra2.second);
         return mFrameworkFacade.getBroadcast(mContext, 0, intent,
-                PendingIntent.FLAG_UPDATE_CURRENT);
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
     }
 
     private @NonNull CharSequence getAppName(@NonNull String packageName, int uid) {
@@ -1376,7 +1569,6 @@
         dialog.getWindow().addSystemFlags(
                 WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
         dialog.show();
-        mUserApprovalUiActive = true;
         mIsLastUserApprovalUiDialog = true;
     }
 
@@ -1413,12 +1605,13 @@
                         mContext.getTheme()))
                 .addAction(userAllowAppNotificationAction)
                 .addAction(userDisallowAppNotificationAction)
+                .setTimeoutAfter(NOTIFICATION_EXPIRY_MILLS)
                 .build();
 
         // Post the notification.
-        mNotificationManager.notify(
-                SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE, notification);
-        mUserApprovalUiActive = true;
+        mNotificationManager.notify(SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE, notification);
+        mNotificationUpdateTime = mClock.getElapsedSinceBootMillis()
+                + NOTIFICATION_UPDATE_DELAY_MILLS;
         mIsLastUserApprovalUiDialog = false;
     }
 
@@ -1438,8 +1631,8 @@
             return false; // already approved.
         }
 
-        if (mUserApprovalUiActive) {
-            return false; // has active notification.
+        if (mNotificationUpdateTime > mClock.getElapsedSinceBootMillis()) {
+            return false; // Active notification is still available, do not update.
         }
         Log.i(TAG, "Sending user approval notification for " + packageName);
         sendUserApprovalNotification(packageName, uid);
@@ -1480,11 +1673,11 @@
     /**
      * Returns a set of all network suggestions matching the provided FQDN.
      */
-    public @Nullable Set<ExtendedWifiNetworkSuggestion> getNetworkSuggestionsForFqdn(String fqdn) {
+    public @NonNull Set<ExtendedWifiNetworkSuggestion> getNetworkSuggestionsForFqdn(String fqdn) {
         Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions =
                 getNetworkSuggestionsForFqdnMatch(fqdn);
         if (extNetworkSuggestions == null) {
-            return null;
+            return Set.of();
         }
         final String activeScorerPackage = mNetworkScoreManager.getActiveScorerPackage();
         Set<ExtendedWifiNetworkSuggestion> approvedExtNetworkSuggestions = new HashSet<>();
@@ -1494,6 +1687,10 @@
                         ewns.perAppInfo.uid);
                 continue;
             }
+            if (ewns.wns.wifiConfiguration.carrierMerged && !areCarrierMergedSuggestionsAllowed(
+                    ewns.wns.wifiConfiguration.subscriptionId, ewns.perAppInfo.packageName)) {
+                continue;
+            }
             if (isSimBasedSuggestion(ewns)) {
                 mWifiCarrierInfoManager.sendImsiProtectionExemptionNotificationIfRequired(
                         getCarrierIdFromSuggestion(ewns));
@@ -1502,7 +1699,7 @@
         }
 
         if (approvedExtNetworkSuggestions.isEmpty()) {
-            return null;
+            return Set.of();
         }
         if (mVerboseLoggingEnabled) {
             Log.v(TAG, "getNetworkSuggestionsForFqdn Found "
@@ -1514,12 +1711,12 @@
     /**
      * Returns a set of all network suggestions matching the provided scan detail.
      */
-    public @Nullable Set<ExtendedWifiNetworkSuggestion> getNetworkSuggestionsForScanDetail(
+    public @NonNull Set<ExtendedWifiNetworkSuggestion> getNetworkSuggestionsForScanDetail(
             @NonNull ScanDetail scanDetail) {
         ScanResult scanResult = scanDetail.getScanResult();
         if (scanResult == null) {
             Log.e(TAG, "No scan result found in scan detail");
-            return null;
+            return Set.of();
         }
         Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = null;
         try {
@@ -1531,7 +1728,7 @@
             Log.e(TAG, "Failed to lookup network from scan result match info map", e);
         }
         if (extNetworkSuggestions == null) {
-            return null;
+            return Set.of();
         }
         final String activeScorerPackage = mNetworkScoreManager.getActiveScorerPackage();
         Set<ExtendedWifiNetworkSuggestion> approvedExtNetworkSuggestions = new HashSet<>();
@@ -1541,6 +1738,10 @@
                         ewns.perAppInfo.uid);
                 continue;
             }
+            if (ewns.wns.wifiConfiguration.carrierMerged && !areCarrierMergedSuggestionsAllowed(
+                    ewns.wns.wifiConfiguration.subscriptionId, ewns.perAppInfo.packageName)) {
+                continue;
+            }
             if (isSimBasedSuggestion(ewns)) {
                 mWifiCarrierInfoManager.sendImsiProtectionExemptionNotificationIfRequired(
                         getCarrierIdFromSuggestion(ewns));
@@ -1549,7 +1750,7 @@
         }
 
         if (approvedExtNetworkSuggestions.isEmpty()) {
-            return null;
+            return Set.of();
         }
         if (mVerboseLoggingEnabled) {
             Log.v(TAG, "getNetworkSuggestionsForScanDetail Found "
@@ -1603,13 +1804,24 @@
      */
     public @NonNull List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(
             List<ScanResult> scanResults) {
+        // Create a temporary look-up table.
+        // As they are all single type configurations, they should have unique keys.
+        Map<String, WifiConfiguration> wifiConfigMap = new HashMap<>();
+        WifiConfigurationUtil.convertMultiTypeConfigsToLegacyConfigs(
+                mWifiConfigManager.getConfiguredNetworks())
+                        .forEach(c -> wifiConfigMap.put(c.getProfileKey(), c));
+
         // Create a HashSet to avoid return multiple result for duplicate ScanResult.
         Set<String> networkKeys = new HashSet<>();
         List<WifiConfiguration> sharedWifiConfigs = new ArrayList<>();
         for (ScanResult scanResult : scanResults) {
             ScanResultMatchInfo scanResultMatchInfo =
                     ScanResultMatchInfo.fromScanResult(scanResult);
-            if (scanResultMatchInfo.networkType == WifiConfiguration.SECURITY_TYPE_OPEN) {
+            if (scanResultMatchInfo.securityParamsList.size() == 0) continue;
+            // Only filter legacy Open network.
+            if (scanResultMatchInfo.securityParamsList.size() == 1
+                    && scanResultMatchInfo.getDefaultSecurityParams().getSecurityType()
+                            == WifiConfiguration.SECURITY_TYPE_OPEN) {
                 continue;
             }
             Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions =
@@ -1626,20 +1838,29 @@
             if (sharedNetworkSuggestions.isEmpty()) {
                 continue;
             }
-            ExtendedWifiNetworkSuggestion ewns =
-                    sharedNetworkSuggestions.stream().findFirst().get();
-            if (mVerboseLoggingEnabled) {
-                Log.v(TAG, "getWifiConfigForMatchedNetworkSuggestionsSharedWithUser Found "
-                        + ewns + " for " + scanResult.SSID + "[" + scanResult.capabilities + "]");
-            }
-            WifiConfiguration config = ewns.wns.wifiConfiguration;
-            WifiConfiguration existingConfig = mWifiConfigManager
-                    .getConfiguredNetwork(config.getKey());
-            if (existingConfig == null || !existingConfig.fromWifiNetworkSuggestion) {
-                continue;
-            }
-            if (networkKeys.add(existingConfig.getKey())) {
-                sharedWifiConfigs.add(existingConfig);
+            for (ExtendedWifiNetworkSuggestion ewns : sharedNetworkSuggestions) {
+                if (mVerboseLoggingEnabled) {
+                    Log.v(TAG, "getWifiConfigForMatchedNetworkSuggestionsSharedWithUser Found "
+                            + ewns + " for " + scanResult.SSID
+                            + "[" + scanResult.capabilities + "]");
+                }
+                WifiConfiguration config = ewns.createInternalWifiConfiguration(
+                        mWifiCarrierInfoManager);
+                if (config.carrierId != TelephonyManager.UNKNOWN_CARRIER_ID
+                        && !mWifiCarrierInfoManager.isSimReady(config.subscriptionId)) {
+                    continue;
+                }
+                if (config.carrierMerged && !areCarrierMergedSuggestionsAllowed(
+                        config.subscriptionId, ewns.perAppInfo.packageName)) {
+                    continue;
+                }
+                WifiConfiguration wCmWifiConfig = wifiConfigMap.get(config.getProfileKey());
+                if (wCmWifiConfig == null) {
+                    continue;
+                }
+                if (networkKeys.add(wCmWifiConfig.getProfileKey())) {
+                    sharedWifiConfigs.add(wCmWifiConfig);
+                }
             }
         }
         return sharedWifiConfigs;
@@ -1653,6 +1874,12 @@
                 && mWifiCarrierInfoManager.isCarrierNetworkFromNonDefaultDataSim(config)) {
             return false;
         }
+        if (config.carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) {
+            int subId = mWifiCarrierInfoManager.getBestMatchSubscriptionId(config);
+            if (!mWifiCarrierInfoManager.isSimReady(subId)) {
+                return false;
+            }
+        }
         Set<ExtendedWifiNetworkSuggestion> extendedWifiNetworkSuggestions =
                 getNetworkSuggestionsForFqdnMatch(config.FQDN);
         Set<ExtendedWifiNetworkSuggestion> matchedSuggestions =
@@ -1678,7 +1905,7 @@
         List<WifiScanner.ScanSettings.HiddenNetwork> hiddenNetworks = new ArrayList<>();
         for (PerAppInfo appInfo : mActiveNetworkSuggestionsPerApp.values()) {
             if (!appInfo.hasUserApproved) continue;
-            for (ExtendedWifiNetworkSuggestion ewns : appInfo.extNetworkSuggestions) {
+            for (ExtendedWifiNetworkSuggestion ewns : appInfo.extNetworkSuggestions.values()) {
                 if (!ewns.wns.wifiConfiguration.hiddenSSID) continue;
                 hiddenNetworks.add(
                         new WifiScanner.ScanSettings.HiddenNetwork(
@@ -1750,42 +1977,37 @@
         if (matchingExtNetworkSuggestions == null
                 || matchingExtNetworkSuggestions.isEmpty()) return;
 
+        Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestionsFromTargetApp;
         if (connectedNetwork.fromWifiNetworkSuggestion) {
-            // Find subset of network suggestions from app suggested the connected network.
-            matchingExtNetworkSuggestions =
-                    matchingExtNetworkSuggestions.stream()
-                            .filter(x -> x.perAppInfo.uid == connectedNetwork.creatorUid)
-                            .collect(Collectors.toSet());
-            if (matchingExtNetworkSuggestions.isEmpty()) {
+            matchingExtNetworkSuggestionsFromTargetApp =
+                    getMatchedSuggestionsWithSameProfileKey(matchingExtNetworkSuggestions,
+                            connectedNetwork);
+            if (matchingExtNetworkSuggestionsFromTargetApp.isEmpty()) {
                 Log.wtf(TAG, "Current connected network suggestion is missing!");
                 return;
             }
-            // Store the set of matching network suggestions.
-            mActiveNetworkSuggestionsMatchingConnection =
-                    new HashSet<>(matchingExtNetworkSuggestions);
         } else {
-            if (connectedNetwork.isOpenNetwork()) {
-                // For saved open network, found the matching suggestion from carrier privileged
-                // apps. As we only expect one suggestor app to take action on post connection, if
-                // multiple apps suggested matched suggestions, framework will randomly pick one.
-                matchingExtNetworkSuggestions = matchingExtNetworkSuggestions.stream()
-                        .filter(x -> x.perAppInfo.carrierId != TelephonyManager.UNKNOWN_CARRIER_ID
-                                || mWifiPermissionsUtil
-                                .checkNetworkCarrierProvisioningPermission(x.perAppInfo.uid))
-                        .limit(1).collect(Collectors.toSet());
-                if (matchingExtNetworkSuggestions.isEmpty()) {
-                    if (mVerboseLoggingEnabled) {
-                        Log.v(TAG, "No suggestion matched connected user saved open network.");
-                    }
-                    return;
+            // If not suggestion, the connected network is open network.
+            // For saved open network, found the matching suggestion from carrier privileged
+            // apps. As we only expect one suggestor app to take action on post connection, if
+            // multiple apps suggested matched suggestions, framework will randomly pick one.
+            matchingExtNetworkSuggestionsFromTargetApp = matchingExtNetworkSuggestions.stream()
+                    .filter(x -> x.perAppInfo.carrierId != TelephonyManager.UNKNOWN_CARRIER_ID
+                            || mWifiPermissionsUtil
+                            .checkNetworkCarrierProvisioningPermission(x.perAppInfo.uid))
+                    .limit(1).collect(Collectors.toSet());
+            if (matchingExtNetworkSuggestionsFromTargetApp.isEmpty()) {
+                if (mVerboseLoggingEnabled) {
+                    Log.v(TAG, "No suggestion matched connected user saved open network.");
                 }
+                return;
             }
         }
 
         mWifiMetrics.incrementNetworkSuggestionApiNumConnectSuccess();
         // Find subset of network suggestions have set |isAppInteractionRequired|.
         Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestionsWithReqAppInteraction =
-                matchingExtNetworkSuggestions.stream()
+                matchingExtNetworkSuggestionsFromTargetApp.stream()
                         .filter(x -> x.wns.isAppInteractionRequired)
                         .collect(Collectors.toSet());
         if (matchingExtNetworkSuggestionsWithReqAppInteraction.isEmpty()) return;
@@ -1824,14 +2046,12 @@
                 || matchingExtNetworkSuggestions.isEmpty()) return;
 
         mWifiMetrics.incrementNetworkSuggestionApiNumConnectFailure();
-        // TODO (b/115504887, b/112196799): Blacklist the corresponding network suggestion if
+        // TODO (b/115504887, b/112196799): Blocklist the corresponding network suggestion if
         // the connection failed.
 
         // Find subset of network suggestions which suggested the connection failure network.
         Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestionsFromTargetApp =
-                matchingExtNetworkSuggestions.stream()
-                        .filter(x -> x.perAppInfo.uid == network.creatorUid)
-                        .collect(Collectors.toSet());
+                getMatchedSuggestionsWithSameProfileKey(matchingExtNetworkSuggestions, network);
         if (matchingExtNetworkSuggestionsFromTargetApp.isEmpty()) {
             Log.wtf(TAG, "Current connection failure network suggestion is missing!");
             return;
@@ -1846,8 +2066,22 @@
         }
     }
 
-    private void resetConnectionState() {
-        mActiveNetworkSuggestionsMatchingConnection = null;
+    private Set<ExtendedWifiNetworkSuggestion> getMatchedSuggestionsWithSameProfileKey(
+            Set<ExtendedWifiNetworkSuggestion> matchingSuggestions, WifiConfiguration network) {
+        if (matchingSuggestions == null || matchingSuggestions.isEmpty()) {
+            return Set.of();
+        }
+        Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestionsWithSameProfileKey =
+                new HashSet<>();
+        for (ExtendedWifiNetworkSuggestion ewns : matchingSuggestions) {
+            WifiConfiguration config = ewns
+                    .createInternalWifiConfiguration(mWifiCarrierInfoManager);
+            if (config.getProfileKey().equals(network.getProfileKey())
+                    && config.creatorName.equals(network.creatorName)) {
+                matchingExtNetworkSuggestionsWithSameProfileKey.add(ewns);
+            }
+        }
+        return matchingExtNetworkSuggestionsWithSameProfileKey;
     }
 
     /**
@@ -1862,7 +2096,6 @@
         if (mVerboseLoggingEnabled) {
             Log.v(TAG, "handleConnectionAttemptEnded " + failureCode + ", " + network);
         }
-        resetConnectionState();
         if (failureCode == WifiMetrics.ConnectionEvent.FAILURE_NONE) {
             handleConnectionSuccess(network, bssid);
         } else {
@@ -1871,16 +2104,6 @@
     }
 
     /**
-     * Invoked by {@link ClientModeImpl} on disconnect from network.
-     */
-    public void handleDisconnect(@NonNull WifiConfiguration network, @NonNull String bssid) {
-        if (mVerboseLoggingEnabled) {
-            Log.v(TAG, "handleDisconnect " + network);
-        }
-        resetConnectionState();
-    }
-
-    /**
      * Send network connection failure event to app when an connection attempt failure.
      * @param packageName package name to send event
      * @param featureId The feature in the package
@@ -1890,9 +2113,9 @@
      */
     private void sendConnectionFailureIfAllowed(String packageName, @Nullable String featureId,
             int uid, @NonNull WifiNetworkSuggestion matchingSuggestion, int connectionEvent) {
-        ExternalCallbackTracker<ISuggestionConnectionStatusListener> listenersTracker =
+        RemoteCallbackList<ISuggestionConnectionStatusListener> listenersTracker =
                 mSuggestionStatusListenerPerApp.get(packageName);
-        if (listenersTracker == null || listenersTracker.getNumCallbacks() == 0) {
+        if (listenersTracker == null || listenersTracker.getRegisteredCallbackCount() == 0) {
             return;
         }
         try {
@@ -1905,14 +2128,16 @@
         if (mVerboseLoggingEnabled) {
             Log.v(TAG, "Sending connection failure event to " + packageName);
         }
-        for (ISuggestionConnectionStatusListener listener : listenersTracker.getCallbacks()) {
+        int itemCount = listenersTracker.beginBroadcast();
+        for (int i = 0; i < itemCount; i++) {
             try {
-                listener.onConnectionStatus(matchingSuggestion,
+                listenersTracker.getBroadcastItem(i).onConnectionStatus(matchingSuggestion,
                         internalConnectionEventToSuggestionFailureCode(connectionEvent));
             } catch (RemoteException e) {
                 Log.e(TAG, "sendNetworkCallback: remote exception -- " + e);
             }
         }
+        listenersTracker.finishBroadcast();
     }
 
     private @WifiManager.SuggestionConnectionStatusCode
@@ -1932,49 +2157,87 @@
     }
 
     /**
+     * Register a SuggestionUserApprovalStatusListener on user approval status changes.
+     * @param listener ISuggestionUserApprovalStatusListener instance to add.
+     * @param uid uid of the app.
+     */
+    public void addSuggestionUserApprovalStatusListener(
+            @NonNull ISuggestionUserApprovalStatusListener listener, String packageName, int uid) {
+        RemoteCallbackList<ISuggestionUserApprovalStatusListener> listenersTracker =
+                mSuggestionUserApprovalStatusListenerPerApp.get(packageName);
+        if (listenersTracker == null) {
+            listenersTracker = new RemoteCallbackList<>();
+        }
+        listenersTracker.register(listener);
+        mSuggestionUserApprovalStatusListenerPerApp.put(packageName, listenersTracker);
+        try {
+            listener.onUserApprovalStatusChange(
+                    getNetworkSuggestionUserApprovalStatus(uid, packageName));
+        } catch (RemoteException e) {
+            Log.e(TAG, "sendUserApprovalStatusChange: remote exception -- " + e);
+        }
+    }
+
+    /**
+     * Unregister a listener on on user approval status changes.
+     * @param listener ISuggestionUserApprovalStatusListener instance to remove.
+     * @param uid uid of the app.
+     */
+    public void removeSuggestionUserApprovalStatusListener(
+            @NonNull ISuggestionUserApprovalStatusListener listener, String packageName, int uid) {
+        RemoteCallbackList<ISuggestionUserApprovalStatusListener> listenersTracker =
+                mSuggestionUserApprovalStatusListenerPerApp.get(packageName);
+        if (listenersTracker == null || !listenersTracker.unregister(listener)) {
+            Log.w(TAG, "removeSuggestionUserApprovalStatusListener: Listener from " + packageName
+                    + " already removed.");
+            return;
+        }
+        if (listenersTracker != null && listenersTracker.getRegisteredCallbackCount() == 0) {
+            mSuggestionUserApprovalStatusListenerPerApp.remove(packageName);
+        }
+    }
+
+    /**
      * Register a SuggestionConnectionStatusListener on network connection failure.
-     * @param binder IBinder instance to allow cleanup if the app dies.
      * @param listener ISuggestionNetworkCallback instance to add.
-     * @param listenerIdentifier identifier of the listener, should be hash code of listener.
      * @param uid uid of the app.
      * @return true if succeed otherwise false.
      */
-    public boolean registerSuggestionConnectionStatusListener(@NonNull IBinder binder,
+    public boolean registerSuggestionConnectionStatusListener(
             @NonNull ISuggestionConnectionStatusListener listener,
-            int listenerIdentifier, String packageName, int uid) {
+            String packageName, int uid) {
         if (!mWifiPermissionsUtil.doesUidBelongToCurrentUser(uid)) {
             Log.e(TAG, "UID " + uid + " not visible to the current user");
             return false;
         }
-        ExternalCallbackTracker<ISuggestionConnectionStatusListener> listenersTracker =
+        RemoteCallbackList<ISuggestionConnectionStatusListener> listenersTracker =
                 mSuggestionStatusListenerPerApp.get(packageName);
         if (listenersTracker == null) {
-            listenersTracker =
-                    new ExternalCallbackTracker<>(mHandler);
+            listenersTracker = new RemoteCallbackList<>();
         }
-        listenersTracker.add(binder, listener, listenerIdentifier);
+        listenersTracker.register(listener);
         mSuggestionStatusListenerPerApp.put(packageName, listenersTracker);
         return true;
     }
 
     /**
      * Unregister a listener on network connection failure.
-     * @param listenerIdentifier identifier of the listener, should be hash code of listener.
+     * @param listener ISuggestionNetworkCallback instance to remove.
      * @param uid uid of the app.
      */
-    public void unregisterSuggestionConnectionStatusListener(int listenerIdentifier,
-            String packageName, int uid) {
+    public void unregisterSuggestionConnectionStatusListener(
+            @NonNull ISuggestionConnectionStatusListener listener, String packageName, int uid) {
         if (!mWifiPermissionsUtil.doesUidBelongToCurrentUser(uid)) {
             Log.e(TAG, "UID " + uid + " not visible to the current user");
             return;
         }
-        ExternalCallbackTracker<ISuggestionConnectionStatusListener> listenersTracker =
+        RemoteCallbackList<ISuggestionConnectionStatusListener> listenersTracker =
                 mSuggestionStatusListenerPerApp.get(packageName);
-        if (listenersTracker == null || listenersTracker.remove(listenerIdentifier) == null) {
-            Log.w(TAG, "unregisterSuggestionConnectionStatusListener: Listener["
-                    + listenerIdentifier + "] from " + packageName + " already unregister.");
+        if (listenersTracker == null || !listenersTracker.unregister(listener)) {
+            Log.w(TAG, "unregisterSuggestionConnectionStatusListener: Listener from " + packageName
+                    + " already unregister.");
         }
-        if (listenersTracker != null && listenersTracker.getNumCallbacks() == 0) {
+        if (listenersTracker != null && listenersTracker.getRegisteredCallbackCount() == 0) {
             mSuggestionStatusListenerPerApp.remove(packageName);
         }
     }
@@ -1986,7 +2249,10 @@
      */
     public void resetCarrierPrivilegedApps() {
         Log.w(TAG, "SIM state is changed!");
-        for (PerAppInfo appInfo : mActiveNetworkSuggestionsPerApp.values()) {
+        Iterator<Map.Entry<String, PerAppInfo>> iter =
+                mActiveNetworkSuggestionsPerApp.entrySet().iterator();
+        while (iter.hasNext()) {
+            PerAppInfo appInfo = iter.next().getValue();
             int carrierId = mWifiCarrierInfoManager
                     .getCarrierIdForPackageWithCarrierPrivileges(appInfo.packageName);
             if (carrierId == appInfo.carrierId) {
@@ -1994,13 +2260,13 @@
             }
             if (carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) {
                 Log.i(TAG, "Carrier privilege revoked for " + appInfo.packageName);
-                removeInternal(Collections.EMPTY_LIST, appInfo.packageName, appInfo);
-                mActiveNetworkSuggestionsPerApp.remove(appInfo.packageName);
+                removeInternal(List.of(), appInfo.packageName, appInfo);
+                iter.remove();
                 continue;
             }
             Log.i(TAG, "Carrier privilege granted for " + appInfo.packageName);
             appInfo.carrierId = carrierId;
-            for (ExtendedWifiNetworkSuggestion ewns : appInfo.extNetworkSuggestions) {
+            for (ExtendedWifiNetworkSuggestion ewns : appInfo.extNetworkSuggestions.values()) {
                 ewns.wns.wifiConfiguration.carrierId = carrierId;
             }
         }
@@ -2008,6 +2274,16 @@
     }
 
     /**
+     * Resets all sim networks state.
+     */
+    public void resetSimNetworkSuggestions() {
+        mActiveNetworkSuggestionsPerApp.values().stream()
+                .flatMap(e -> e.extNetworkSuggestions.values().stream())
+                .forEach(ewns -> ewns.anonymousIdentity = null);
+        saveToStore();
+    }
+
+    /**
      * Set auto-join enable/disable for suggestion network
      * @param config WifiConfiguration which is to change.
      * @param choice true to enable auto-join, false to disable.
@@ -2020,14 +2296,21 @@
             return false;
         }
 
-        Set<ExtendedWifiNetworkSuggestion> matchingExtendedWifiNetworkSuggestions =
-                getNetworkSuggestionsForWifiConfiguration(config, config.BSSID);
         if (config.isPasspoint()) {
-            if (!mWifiInjector.getPasspointManager().enableAutojoin(config.getKey(),
+            if (!mWifiInjector.getPasspointManager().enableAutojoin(config.getProfileKey(),
                     null, choice)) {
                 return false;
             }
         }
+
+        Set<ExtendedWifiNetworkSuggestion> matchingExtendedWifiNetworkSuggestions =
+                getMatchedSuggestionsWithSameProfileKey(
+                        getNetworkSuggestionsForWifiConfiguration(config, config.BSSID), config);
+        if (matchingExtendedWifiNetworkSuggestions.isEmpty()) {
+            Log.e(TAG, "allowNetworkSuggestionAutojoin: network is missing: "
+                    + config);
+            return false;
+        }
         for (ExtendedWifiNetworkSuggestion ewns : matchingExtendedWifiNetworkSuggestions) {
             ewns.isAutojoinEnabled = choice;
         }
@@ -2141,6 +2424,28 @@
         return false;
     }
 
+    /**
+     * Check the suggestion user approval status.
+     */
+    private  @WifiManager.SuggestionUserApprovalStatus int getNetworkSuggestionUserApprovalStatus(
+            int uid, String packageName) {
+        if (mAppOps.unsafeCheckOpNoThrow(OPSTR_CHANGE_WIFI_STATE, uid, packageName)
+                == AppOpsManager.MODE_IGNORED) {
+            return WifiManager.STATUS_SUGGESTION_APPROVAL_REJECTED_BY_USER;
+        }
+        if (!mActiveNetworkSuggestionsPerApp.containsKey(packageName)) {
+            return WifiManager.STATUS_SUGGESTION_APPROVAL_UNKNOWN;
+        }
+        PerAppInfo info = mActiveNetworkSuggestionsPerApp.get(packageName);
+        if (info.hasUserApproved) {
+            return WifiManager.STATUS_SUGGESTION_APPROVAL_APPROVED_BY_USER;
+        }
+        if (info.carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) {
+            return WifiManager.STATUS_SUGGESTION_APPROVAL_APPROVED_BY_CARRIER_PRIVILEGE;
+        }
+        return WifiManager.STATUS_SUGGESTION_APPROVAL_PENDING;
+    }
+
     private boolean hasSecureSuggestionFromSameCarrierAvailable(
             ExtendedWifiNetworkSuggestion extendedWifiNetworkSuggestion,
             List<ScanDetail> scanDetails) {
@@ -2148,7 +2453,7 @@
                 extendedWifiNetworkSuggestion.wns.wifiConfiguration, null);
         Set<ExtendedWifiNetworkSuggestion> secureExtSuggestions = new HashSet<>();
         for (ExtendedWifiNetworkSuggestion ewns : extendedWifiNetworkSuggestion.perAppInfo
-                .extNetworkSuggestions) {
+                .extNetworkSuggestions.values()) {
             // Open network and auto-join disable suggestion, ignore.
             if (isOpenSuggestion(ewns) || !ewns.isAutojoinEnabled) {
                 continue;
@@ -2169,7 +2474,7 @@
                 continue;
             }
             WifiConfiguration wcmConfig = mWifiConfigManager
-                    .getConfiguredNetwork(ewns.wns.wifiConfiguration.getKey());
+                    .getConfiguredNetwork(ewns.wns.wifiConfiguration.getProfileKey());
             // Network selection is disabled, ignore.
             if (wcmConfig != null && !wcmConfig.getNetworkSelectionStatus().isNetworkEnabled()) {
                 continue;
@@ -2191,6 +2496,52 @@
         return false;
     }
 
+    /**
+     * Set the app treated as cross carrier provider. That can suggest for any carrier
+     * @param packageName App name to set.
+     * @param enabled True to set app treated as cross carrier provider, false otherwise.
+     */
+    public void setAppWorkingAsCrossCarrierProvider(String packageName, boolean enabled) {
+        if (enabled) {
+            mCrossCarrierProvidersSet.add(packageName);
+        } else {
+            mCrossCarrierProvidersSet.remove(packageName);
+        }
+    }
+
+    /**
+     * Check whether the app is treated as a cross carrier provider or not.
+     * @param packageName App name to check
+     * @return True for app is treated as a carrier provider, false otherwise.
+     */
+    public boolean isAppWorkingAsCrossCarrierProvider(String packageName) {
+        return mCrossCarrierProvidersSet.contains(packageName);
+    }
+
+    /**
+     * Store Anonymous Identity for SIM based suggestion after connection.
+     */
+    public void setAnonymousIdentity(WifiConfiguration config) {
+        if (config.isPasspoint() || !config.fromWifiNetworkSuggestion) {
+            return;
+        }
+        if (config.enterpriseConfig == null
+                || !config.enterpriseConfig.isAuthenticationSimBased()) {
+            Log.e(TAG, "Network is not SIM based, AnonymousIdentity is invalid");
+        }
+        Set<ExtendedWifiNetworkSuggestion> matchedSuggestionSet =
+                getMatchedSuggestionsWithSameProfileKey(
+                        getNetworkSuggestionsForWifiConfiguration(config, config.BSSID), config);
+        if (matchedSuggestionSet.isEmpty()) {
+            Log.wtf(TAG, "Current connected SIM based network suggestion is missing!");
+            return;
+        }
+        for (ExtendedWifiNetworkSuggestion ewns : matchedSuggestionSet) {
+            ewns.anonymousIdentity = config.enterpriseConfig.getAnonymousIdentity();
+        }
+        saveToStore();
+    }
+
     private boolean isOpenSuggestion(ExtendedWifiNetworkSuggestion extendedWifiNetworkSuggestion) {
         if (extendedWifiNetworkSuggestion.wns.passpointConfiguration != null) {
             return false;
@@ -2198,6 +2549,70 @@
         return extendedWifiNetworkSuggestion.wns.wifiConfiguration.isOpenNetwork();
     }
 
+    private void onUserConnectChoiceSet(Collection<WifiConfiguration> networks, String choiceKey,
+            int rssi) {
+        Set<String> networkKeys = networks.stream()
+                .filter(config -> config.fromWifiNetworkSuggestion)
+                .map(WifiConfiguration::getProfileKey)
+                .collect(Collectors.toSet());
+        mActiveNetworkSuggestionsPerApp.values().stream()
+                .flatMap(e -> e.extNetworkSuggestions.values().stream())
+                .forEach(ewns -> {
+                    String profileKey = ewns
+                            .createInternalWifiConfiguration(mWifiCarrierInfoManager)
+                            .getProfileKey();
+                    if (TextUtils.equals(profileKey, choiceKey)) {
+                        ewns.connectChoice = null;
+                        ewns.connectChoiceRssi = 0;
+                    } else if (networkKeys.contains(profileKey)) {
+                        ewns.connectChoice = choiceKey;
+                        ewns.connectChoiceRssi = rssi;
+                    }
+                });
+        saveToStore();
+    }
+
+    private void onUserConnectChoiceRemove(String choiceKey) {
+        mActiveNetworkSuggestionsPerApp.values().stream()
+                .flatMap(e -> e.extNetworkSuggestions.values().stream())
+                .filter(ewns -> TextUtils.equals(ewns.connectChoice, choiceKey))
+                .forEach(ewns -> {
+                    ewns.connectChoice = null;
+                    ewns.connectChoiceRssi = 0;
+                });
+        saveToStore();
+    }
+
+    private void onSuggestionUserApprovalStatusChanged(int uid, String packageName) {
+        RemoteCallbackList<ISuggestionUserApprovalStatusListener> listenersTracker =
+                mSuggestionUserApprovalStatusListenerPerApp.get(packageName);
+        if (listenersTracker == null || listenersTracker.getRegisteredCallbackCount() == 0) {
+            return;
+        }
+
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "Sending user approval status change event to " + packageName);
+        }
+        int itemCount = listenersTracker.beginBroadcast();
+        for (int i = 0; i < itemCount; i++) {
+            try {
+                listenersTracker.getBroadcastItem(i).onUserApprovalStatusChange(
+                        getNetworkSuggestionUserApprovalStatus(uid, packageName));
+            } catch (RemoteException e) {
+                Log.e(TAG, "sendUserApprovalStatusChange: remote exception -- " + e);
+            }
+        }
+        listenersTracker.finishBroadcast();
+    }
+
+    private boolean areCarrierMergedSuggestionsAllowed(int subId, String packageName) {
+        if (isAppWorkingAsCrossCarrierProvider(packageName)) {
+            return true;
+        }
+
+        return mWifiCarrierInfoManager.areMergedCarrierWifiNetworksAllowed(subId);
+    }
+
     /**
      * Dump of {@link WifiNetworkSuggestionsManager}.
      */
@@ -2214,12 +2629,15 @@
                     + (appInfo.carrierId != TelephonyManager.UNKNOWN_CARRIER_ID));
             pw.println("Is active scorer: " + appInfo.packageName.equals(activeScorerPackage));
             for (ExtendedWifiNetworkSuggestion extNetworkSuggestion
-                    : appInfo.extNetworkSuggestions) {
+                    : appInfo.extNetworkSuggestions.values()) {
                 pw.println("Network: " + extNetworkSuggestion);
             }
         }
         pw.println("WifiNetworkSuggestionsManager - Networks End ----");
-        pw.println("WifiNetworkSuggestionsManager - Network Suggestions matching connection: "
-                + mActiveNetworkSuggestionsMatchingConnection);
+    }
+
+    public void resetNotification() {
+        mNotificationManager.cancel(SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE);
+        mNotificationUpdateTime = 0;
     }
 }
diff --git a/service/java/com/android/server/wifi/WifiNotificationManager.java b/service/java/com/android/server/wifi/WifiNotificationManager.java
new file mode 100644
index 0000000..32d48fb
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiNotificationManager.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static com.android.server.wifi.WifiService.NOTIFICATION_NETWORK_ALERTS;
+import static com.android.server.wifi.WifiService.NOTIFICATION_NETWORK_AVAILABLE;
+import static com.android.server.wifi.WifiService.NOTIFICATION_NETWORK_STATUS;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+
+import com.android.wifi.resources.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Notification manager for Wifi. All notification will be sent to the current user.
+ */
+public class WifiNotificationManager {
+    private static final String TAG = "WifiNotificationManager";
+    private static final String NOTIFICATION_TAG = "com.android.wifi";
+
+    private final Context mContext;
+    private NotificationManager mNotificationManager;
+
+    public WifiNotificationManager(Context context) {
+        mContext = context;
+    }
+
+    private NotificationManager getNotificationManagerForCurrentUser() {
+        try {
+            return mContext.createPackageContextAsUser(mContext.getPackageName(), 0,
+                    UserHandle.CURRENT).getSystemService(NotificationManager.class);
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(TAG, "Failed to get NotificationManager for current user: " + e.getMessage());
+        }
+        return null;
+    }
+
+    /**
+     * Update to the notification manager fot current user and create notification channels.
+     */
+    public void createNotificationChannels() {
+        if (mNotificationManager != null) {
+            // Cancel all active notification from Wi-Fi Stack.
+            cleanAllWifiNotification();
+        }
+        mNotificationManager = getNotificationManagerForCurrentUser();
+        if (mNotificationManager == null) {
+            return;
+        }
+        List<NotificationChannel> channelsList = new ArrayList<>();
+        final NotificationChannel networkStatusChannel = new NotificationChannel(
+                NOTIFICATION_NETWORK_STATUS,
+                mContext.getResources().getString(
+                        R.string.notification_channel_network_status),
+                NotificationManager.IMPORTANCE_LOW);
+        channelsList.add(networkStatusChannel);
+
+        final NotificationChannel networkAlertsChannel = new NotificationChannel(
+                NOTIFICATION_NETWORK_ALERTS,
+                mContext.getResources().getString(
+                        R.string.notification_channel_network_alerts),
+                NotificationManager.IMPORTANCE_HIGH);
+        networkAlertsChannel.setBlockable(true);
+        channelsList.add(networkAlertsChannel);
+
+        final NotificationChannel networkAvailable = new NotificationChannel(
+                NOTIFICATION_NETWORK_AVAILABLE,
+                mContext.getResources().getString(
+                        R.string.notification_channel_network_available),
+                NotificationManager.IMPORTANCE_LOW);
+        networkAvailable.setBlockable(true);
+        channelsList.add(networkAvailable);
+
+        mNotificationManager.createNotificationChannels(channelsList);
+    }
+
+    private void cleanAllWifiNotification() {
+        for (StatusBarNotification notification : getActiveNotifications()) {
+            if (NOTIFICATION_TAG.equals(notification.getTag())) {
+                cancel(notification.getId());
+            }
+        }
+    }
+
+    /**
+     * Send notification to the current user.
+     */
+    public void notify(int id, Notification notification) {
+        if (mNotificationManager == null) {
+            return;
+        }
+        mNotificationManager.notify(NOTIFICATION_TAG, id, notification);
+    }
+
+    /**
+     * Cancel the notification fot current user.
+     */
+    public void cancel(int id) {
+        if (mNotificationManager == null) {
+            return;
+        }
+        mNotificationManager.cancel(NOTIFICATION_TAG, id);
+    }
+
+    /**
+     * Get active notifications for current user.
+     */
+    public StatusBarNotification[] getActiveNotifications() {
+        if (mNotificationManager == null) {
+            return new StatusBarNotification[0];
+        }
+        return mNotificationManager.getActiveNotifications();
+    }
+}
diff --git a/service/java/com/android/server/wifi/WifiP2pConnection.java b/service/java/com/android/server/wifi/WifiP2pConnection.java
new file mode 100644
index 0000000..67cbb5a
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiP2pConnection.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.wifi.p2p.WifiP2pManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import com.android.internal.util.AsyncChannel;
+import com.android.server.wifi.p2p.WifiP2pServiceImpl;
+
+/**
+ * Used by {@link ClientModeImpl} to communicate with
+ * {@link com.android.server.wifi.p2p.WifiP2pService}.
+ *
+ * TODO(b/159060934): need to think about how multiple STAs interact with P2P
+ */
+public class WifiP2pConnection {
+    private static final String TAG = "WifiP2pConnection";
+
+    private final Context mContext;
+    private final Handler mHandler;
+    private final ActiveModeWarden mActiveModeWarden;
+    /** Channel for sending replies. */
+    private final AsyncChannel mReplyChannel = new AsyncChannel();
+    /** Used to initiate a connection with WifiP2pService */
+    private AsyncChannel mWifiP2pChannel;
+    private boolean mTemporarilyDisconnectWifi = false;
+
+    public WifiP2pConnection(Context context, Looper looper, ActiveModeWarden activeModeWarden) {
+        mContext = context;
+        mHandler = new P2pHandler(looper);
+        mActiveModeWarden = activeModeWarden;
+    }
+
+    private void sendMessageToAllClientModeImpls(Message msg) {
+        for (ClientModeManager clientModeManager : mActiveModeWarden.getClientModeManagers()) {
+            // Need to make a copy of the message, or else MessageQueue will complain that the
+            // original message is already in use.
+            clientModeManager.sendMessageToClientModeImpl(Message.obtain(msg));
+        }
+    }
+
+    private class P2pHandler extends Handler {
+        P2pHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: {
+                    AsyncChannel ac = (AsyncChannel) msg.obj;
+                    if (ac == mWifiP2pChannel) {
+                        if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
+                            // Handler also has a sendMessage() method, but we want to call the
+                            // sendMessage() method on WifiP2pConnection.
+                            WifiP2pConnection.this
+                                    .sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
+                        } else {
+                            // TODO(b/34283611): We should probably do some cleanup or attempt a
+                            //  retry
+                            Log.e(TAG, "WifiP2pService connection failure, error=" + msg.arg1);
+                        }
+                    } else {
+                        Log.e(TAG, "got HALF_CONNECTED for unknown channel");
+                    }
+                } break;
+                case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
+                    AsyncChannel ac = (AsyncChannel) msg.obj;
+                    if (ac == mWifiP2pChannel) {
+                        Log.e(TAG, "WifiP2pService channel lost, message.arg1 =" + msg.arg1);
+                        // TODO(b/34283611): Re-establish connection to state machine after a delay
+                        // mWifiP2pChannel.connect(mContext, getHandler(),
+                        // mWifiP2pManager.getMessenger());
+                    }
+                } break;
+                case WifiP2pServiceImpl.DISCONNECT_WIFI_REQUEST: {
+                    mTemporarilyDisconnectWifi = (msg.arg1 == 1);
+                    if (mActiveModeWarden.getClientModeManagers().isEmpty()) {
+                        // no active client mode managers, so request is trivially satisfied
+                        replyToMessage(msg, WifiP2pServiceImpl.DISCONNECT_WIFI_RESPONSE);
+                    } else {
+                        // need to tell all client mode managers to disconnect
+                        sendMessageToAllClientModeImpls(msg);
+                    }
+                } break;
+                default: {
+                    // Assume P2P message, forward to all ClientModeImpl instances
+                    sendMessageToAllClientModeImpls(msg);
+                } break;
+            }
+        }
+    }
+
+    /** Setup the connection to P2P Service after boot is complete. */
+    public void handleBootCompleted() {
+        if (!isP2pSupported()) return;
+
+        WifiP2pManager p2pManager = mContext.getSystemService(WifiP2pManager.class);
+        if (p2pManager == null) return;
+
+        mWifiP2pChannel = new AsyncChannel();
+        mWifiP2pChannel.connect(mContext, mHandler, p2pManager.getP2pStateMachineMessenger());
+    }
+
+    private boolean isP2pSupported() {
+        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT);
+    }
+
+    /** Return true if the connection to P2P service is established, false otherwise. */
+    public boolean isConnected() {
+        return mWifiP2pChannel != null;
+    }
+
+    /** Send a message to P2P service. */
+    public boolean sendMessage(int what) {
+        if (mWifiP2pChannel == null) {
+            Log.e(TAG, "Tried to sendMessage to uninitialized mWifiP2pChannel!", new Throwable());
+            return false;
+        }
+        mWifiP2pChannel.sendMessage(what);
+        return true;
+    }
+
+    /** Send a message to P2P service. */
+    public boolean sendMessage(int what, int arg1) {
+        if (mWifiP2pChannel == null) {
+            Log.e(TAG, "Tried to sendMessage to uninitialized mWifiP2pChannel!", new Throwable());
+            return false;
+        }
+        mWifiP2pChannel.sendMessage(what, arg1);
+        return true;
+    }
+
+    /** Send a message to P2P service. */
+    public boolean sendMessage(int what, int arg1, int arg2) {
+        if (mWifiP2pChannel == null) {
+            Log.e(TAG, "Tried to sendMessage to uninitialized mWifiP2pChannel!", new Throwable());
+            return false;
+        }
+        mWifiP2pChannel.sendMessage(what, arg1, arg2);
+        return true;
+    }
+
+    /**
+     * State machine initiated requests can have replyTo set to null, indicating
+     * there are no recipients, we ignore those reply actions.
+     */
+    public void replyToMessage(Message msg, int what) {
+        if (msg.replyTo == null) return;
+        Message dstMsg = obtainMessageWithWhatAndArg2(msg, what);
+        mReplyChannel.replyToMessage(msg, dstMsg);
+    }
+
+    /**
+     * arg2 on the source message has a unique id that needs to be retained in replies
+     * to match the request
+     * <p>see WifiManager for details
+     */
+    private Message obtainMessageWithWhatAndArg2(Message srcMsg, int what) {
+        Message msg = Message.obtain();
+        msg.what = what;
+        msg.arg2 = srcMsg.arg2;
+        return msg;
+    }
+
+    /** Whether P2P service requested that we temporarily disconnect from Wifi */
+    public boolean shouldTemporarilyDisconnectWifi() {
+        return mTemporarilyDisconnectWifi;
+    }
+}
diff --git a/service/java/com/android/server/wifi/WifiScanAlwaysAvailableSettingsCompatibility.java b/service/java/com/android/server/wifi/WifiScanAlwaysAvailableSettingsCompatibility.java
index 6abdd31..f2552bf 100644
--- a/service/java/com/android/server/wifi/WifiScanAlwaysAvailableSettingsCompatibility.java
+++ b/service/java/com/android/server/wifi/WifiScanAlwaysAvailableSettingsCompatibility.java
@@ -77,7 +77,8 @@
                                 mContentResolver, SETTINGS_GLOBAL_WIFI_SCAN_ALWAYS_AVAILABLE, 0)
                                 == 1;
                 // Check if the new state is different from our current state.
-                if (mWifiSettingsStore.isScanAlwaysAvailable() != settingsIsAvailable) {
+                if (mWifiSettingsStore.isScanAlwaysAvailableToggleEnabled()
+                        != settingsIsAvailable) {
                     Log.i(TAG, "settings changed, new value: " + settingsIsAvailable
                             + ", triggering update");
                     mWifiSettingsStore.handleWifiScanAlwaysAvailableToggled(settingsIsAvailable);
diff --git a/service/java/com/android/server/wifi/WifiScoreCard.java b/service/java/com/android/server/wifi/WifiScoreCard.java
index 2ab5fb4..b5d8b75 100644
--- a/service/java/com/android/server/wifi/WifiScoreCard.java
+++ b/service/java/com/android/server/wifi/WifiScoreCard.java
@@ -26,6 +26,7 @@
 import static com.android.server.wifi.WifiHealthMonitor.REASON_ASSOC_TIMEOUT;
 import static com.android.server.wifi.WifiHealthMonitor.REASON_AUTH_FAILURE;
 import static com.android.server.wifi.WifiHealthMonitor.REASON_CONNECTION_FAILURE;
+import static com.android.server.wifi.WifiHealthMonitor.REASON_CONNECTION_FAILURE_DISCONNECTION;
 import static com.android.server.wifi.WifiHealthMonitor.REASON_DISCONNECTION_NONLOCAL;
 import static com.android.server.wifi.WifiHealthMonitor.REASON_NO_FAILURE;
 import static com.android.server.wifi.WifiHealthMonitor.REASON_SHORT_CONNECTION_NONLOCAL;
@@ -33,21 +34,28 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.Context;
 import android.net.MacAddress;
+import android.net.wifi.ScanResult;
 import android.net.wifi.SupplicantState;
 import android.net.wifi.WifiManager;
 import android.util.ArrayMap;
 import android.util.Base64;
+import android.util.LocalLog;
 import android.util.Log;
 import android.util.Pair;
 import android.util.SparseLongArray;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
-import com.android.server.wifi.BssidBlocklistMonitor.FailureReason;
+import com.android.server.wifi.WifiBlocklistMonitor.FailureReason;
 import com.android.server.wifi.WifiHealthMonitor.FailureStats;
 import com.android.server.wifi.proto.WifiScoreCardProto;
 import com.android.server.wifi.proto.WifiScoreCardProto.AccessPoint;
+import com.android.server.wifi.proto.WifiScoreCardProto.BandwidthStats;
+import com.android.server.wifi.proto.WifiScoreCardProto.BandwidthStatsAll;
+import com.android.server.wifi.proto.WifiScoreCardProto.BandwidthStatsAllLevel;
+import com.android.server.wifi.proto.WifiScoreCardProto.BandwidthStatsAllLink;
 import com.android.server.wifi.proto.WifiScoreCardProto.ConnectionStats;
 import com.android.server.wifi.proto.WifiScoreCardProto.Event;
 import com.android.server.wifi.proto.WifiScoreCardProto.HistogramBucket;
@@ -57,13 +65,18 @@
 import com.android.server.wifi.proto.WifiScoreCardProto.SecurityType;
 import com.android.server.wifi.proto.WifiScoreCardProto.Signal;
 import com.android.server.wifi.proto.WifiScoreCardProto.UnivariateStatistic;
+import com.android.server.wifi.proto.nano.WifiMetricsProto.BandwidthEstimatorStats;
 import com.android.server.wifi.util.IntHistogram;
 import com.android.server.wifi.util.LruList;
 import com.android.server.wifi.util.NativeUtil;
+import com.android.server.wifi.util.RssiUtil;
+import com.android.wifi.resources.R;
 
 import com.google.protobuf.ByteString;
 import com.google.protobuf.InvalidProtocolBufferException;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.nio.ByteBuffer;
@@ -75,6 +88,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
 
 import javax.annotation.concurrent.NotThreadSafe;
 
@@ -107,11 +121,23 @@
     static final int SUFFICIENT_RECENT_PREV_STATS = 2;
 
     private static final int MAX_FREQUENCIES_PER_SSID = 10;
+    private static final int MAX_TRAFFIC_STATS_POLL_TIME_DELTA_MS = 6_000;
 
     private final Clock mClock;
     private final String mL2KeySeed;
     private MemoryStore mMemoryStore;
     private final DeviceConfigFacade mDeviceConfigFacade;
+    private final FrameworkFacade mFrameworkFacade;
+    private final Context mContext;
+    private final LocalLog mLocalLog = new LocalLog(256);
+    private final long[][][] mL2ErrorAccPercent =
+            new long[NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL];
+    private final long[][][] mBwEstErrorAccPercent =
+            new long[NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL];
+    private final long[][][] mBwEstValue =
+            new long[NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL];
+    private final int[][][] mBwEstCount =
+            new int[NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL];
 
     @VisibleForTesting
     static final int[] RSSI_BUCKETS = intsInRange(-100, -20);
@@ -177,68 +203,86 @@
         mVerboseLoggingEnabled = verbose;
     }
 
-    /**
-     * Timestamp of the start of the most recent connection attempt.
-     *
-     * Based on mClock.getElapsedSinceBootMillis().
-     *
-     * This is for calculating the time to connect and the duration of the connection.
-     * Any negative value means we are not currently connected.
-     */
-    private long mTsConnectionAttemptStart = TS_NONE;
     @VisibleForTesting
     static final long TS_NONE = -1;
 
-    /**
-     * Timestamp captured when we find out about a firmware roam
-     */
-    private long mTsRoam = TS_NONE;
+    /** Tracks the connection status per Wifi interface. */
+    private static final class IfaceInfo {
+        /**
+         * Timestamp of the start of the most recent connection attempt.
+         *
+         * Based on mClock.getElapsedSinceBootMillis().
+         *
+         * This is for calculating the time to connect and the duration of the connection.
+         * Any negative value means we are not currently connected.
+         */
+        public long tsConnectionAttemptStart = TS_NONE;
+
+        /**
+         * Timestamp captured when we find out about a firmware roam
+         */
+        public long tsRoam = TS_NONE;
+
+        /**
+         * Becomes true the first time we see a poll with a valid RSSI in a connection
+         */
+        public boolean polled = false;
+
+        /**
+         * Records validation success for the current connection.
+         *
+         * We want to gather statistics only on the first success.
+         */
+        public boolean validatedThisConnectionAtLeastOnce = false;
+
+        /**
+         * A note to ourself that we are attempting a network switch
+         */
+        public boolean attemptingSwitch = false;
+
+        /**
+         *  SSID of currently connected or connecting network. Used during disconnection
+         */
+        public String ssidCurr = "";
+        /**
+         *  SSID of previously connected network. Used during disconnection when connection attempt
+         *  of current network is issued before the disconnection of previous network.
+         */
+        public String ssidPrev = "";
+        /**
+         * A flag that notes that current disconnection is not generated by wpa_supplicant
+         * which may indicate abnormal disconnection.
+         */
+        public boolean nonlocalDisconnection = false;
+        public int disconnectionReason;
+
+        public long firmwareAlertTimeMs = TS_NONE;
+    }
 
     /**
-     * Becomes true the first time we see a poll with a valid RSSI in a connection
+     * String key: iface name
+     * IfaceInfo value: current status of iface
      */
-    private boolean mPolled = false;
+    private final Map<String, IfaceInfo> mIfaceToInfoMap = new ArrayMap<>();
 
-    /**
-     * Records validation success for the current connection.
-     *
-     * We want to gather statistics only on the first success.
-     */
-    private boolean mValidatedThisConnectionAtLeastOnce = false;
-
-    /**
-     * A note to ourself that we are attempting a network switch
-     */
-    private boolean mAttemptingSwitch = false;
-
-    /**
-     *  SSID of currently connected or connecting network. Used during disconnection
-     */
-    private String mSsidCurr = "";
-    /**
-     *  SSID of previously connected network. Used during disconnection when connection attempt
-     *  of current network is issued before the disconnection of previous network.
-     */
-    private String mSsidPrev = "";
-    /**
-     * A flag that notes that current disconnection is not generated by wpa_supplicant
-     * which may indicate abnormal disconnection.
-     */
-    private boolean mNonlocalDisconnection = false;
-    private int mDisconnectionReason;
-
-    private long mFirmwareAlertTimeMs = TS_NONE;
+    /** Gets the IfaceInfo, or create it if it doesn't exist. */
+    private IfaceInfo getIfaceInfo(String ifaceName) {
+        return mIfaceToInfoMap.computeIfAbsent(ifaceName, k -> new IfaceInfo());
+    }
 
     /**
      * @param clock is the time source
      * @param l2KeySeed is for making our L2Keys usable only on this device
      */
-    public WifiScoreCard(Clock clock, String l2KeySeed, DeviceConfigFacade deviceConfigFacade) {
+    public WifiScoreCard(Clock clock, String l2KeySeed, DeviceConfigFacade deviceConfigFacade,
+            FrameworkFacade frameworkFacade, Context context) {
         mClock = clock;
+        mContext = context;
         mL2KeySeed = l2KeySeed;
-        mDummyPerBssid = new PerBssid("", MacAddress.fromString(DEFAULT_MAC_ADDRESS));
-        mDummyPerNetwork = new PerNetwork("");
+        mPlaceholderPerBssid = new PerBssid("", MacAddress.fromString(DEFAULT_MAC_ADDRESS));
+        mPlaceholderPerNetwork = new PerNetwork("");
         mDeviceConfigFacade = deviceConfigFacade;
+        mFrameworkFacade = frameworkFacade;
     }
 
     /**
@@ -246,7 +290,7 @@
      */
     public @NonNull Pair<String, String> getL2KeyAndGroupHint(ExtendedWifiInfo wifiInfo) {
         PerBssid perBssid = lookupBssid(wifiInfo.getSSID(), wifiInfo.getBSSID());
-        if (perBssid == mDummyPerBssid) {
+        if (perBssid == mPlaceholderPerBssid) {
             return new Pair<>(null, null);
         }
         return new Pair<>(perBssid.getL2Key(), groupHintFromSsid(perBssid.ssid));
@@ -256,39 +300,59 @@
      * Computes the GroupHint associated with the given ssid.
      */
     public @NonNull String groupHintFromSsid(String ssid) {
-        final long groupIdHash = computeHashLong(ssid, mDummyPerBssid.bssid, mL2KeySeed);
+        final long groupIdHash = computeHashLong(ssid, mPlaceholderPerBssid.bssid, mL2KeySeed);
         return groupHintFromLong(groupIdHash);
     }
 
-    /**
-     * Handle network disconnection or shutdown event
-     */
-    public void resetConnectionState() {
-        String ssidDisconnected = mAttemptingSwitch ? mSsidPrev : mSsidCurr;
+    /** Handle network disconnection. */
+    public void resetConnectionState(String ifaceName) {
+        IfaceInfo ifaceInfo = getIfaceInfo(ifaceName);
+        noteDisconnectionForIface(ifaceInfo);
+        resetConnectionStateForIfaceInternal(ifaceInfo, true);
+    }
+
+    /** Handle shutdown event. */
+    public void resetAllConnectionStates() {
+        for (IfaceInfo ifaceInfo : mIfaceToInfoMap.values()) {
+            noteDisconnectionForIface(ifaceInfo);
+            resetConnectionStateForIfaceInternal(ifaceInfo, true);
+        }
+    }
+
+    private void noteDisconnectionForIface(IfaceInfo ifaceInfo) {
+        String ssidDisconnected = ifaceInfo.attemptingSwitch
+                ? ifaceInfo.ssidPrev : ifaceInfo.ssidCurr;
         updatePerNetwork(Event.DISCONNECTION, ssidDisconnected, INVALID_RSSI, LINK_SPEED_UNKNOWN,
-                UNKNOWN_REASON);
-        if (mVerboseLoggingEnabled && mTsConnectionAttemptStart > TS_NONE && !mAttemptingSwitch) {
+                UNKNOWN_REASON, ifaceInfo);
+        if (mVerboseLoggingEnabled && ifaceInfo.tsConnectionAttemptStart > TS_NONE
+                && !ifaceInfo.attemptingSwitch) {
             Log.v(TAG, "handleNetworkDisconnect", new Exception());
         }
-        resetConnectionStateInternal(true);
+    }
+
+    private void resetAllConnectionStatesInternal() {
+        for (IfaceInfo ifaceInfo : mIfaceToInfoMap.values()) {
+            resetConnectionStateForIfaceInternal(ifaceInfo, false);
+        }
     }
 
     /**
      * @param calledFromResetConnectionState says the call is from outside the class,
      *        indicating that we need to respect the value of mAttemptingSwitch.
      */
-    private void resetConnectionStateInternal(boolean calledFromResetConnectionState) {
+    private void resetConnectionStateForIfaceInternal(IfaceInfo ifaceInfo,
+            boolean calledFromResetConnectionState) {
         if (!calledFromResetConnectionState) {
-            mAttemptingSwitch = false;
+            ifaceInfo.attemptingSwitch = false;
         }
-        if (!mAttemptingSwitch) {
-            mTsConnectionAttemptStart = TS_NONE;
+        if (!ifaceInfo.attemptingSwitch) {
+            ifaceInfo.tsConnectionAttemptStart = TS_NONE;
         }
-        mTsRoam = TS_NONE;
-        mPolled = false;
-        mValidatedThisConnectionAtLeastOnce = false;
-        mNonlocalDisconnection = false;
-        mFirmwareAlertTimeMs = TS_NONE;
+        ifaceInfo.tsRoam = TS_NONE;
+        ifaceInfo.polled = false;
+        ifaceInfo.validatedThisConnectionAtLeastOnce = false;
+        ifaceInfo.nonlocalDisconnection = false;
+        ifaceInfo.firmwareAlertTimeMs = TS_NONE;
     }
 
     /**
@@ -301,7 +365,8 @@
         perBssid.updateEventStats(event,
                 wifiInfo.getFrequency(),
                 wifiInfo.getRssi(),
-                wifiInfo.getLinkSpeed());
+                wifiInfo.getLinkSpeed(),
+                wifiInfo.getIfaceName());
         perBssid.setNetworkConfigId(wifiInfo.getNetworkId());
         logd("BSSID update " + event + " ID: " + perBssid.id + " " + wifiInfo);
     }
@@ -311,11 +376,11 @@
      * only during connection failure.
      */
     private void updatePerNetwork(WifiScoreCardProto.Event event, String ssid, int rssi,
-            int txSpeed, int failureReason) {
+            int txSpeed, int failureReason, IfaceInfo ifaceInfo) {
         PerNetwork perNetwork = lookupNetwork(ssid);
         logd("network update " + event + ((ssid == null) ? " " : " "
                     + ssid) + " ID: " + perNetwork.id + " RSSI " + rssi + " txSpeed " + txSpeed);
-        perNetwork.updateEventStats(event, rssi, txSpeed, failureReason);
+        perNetwork.updateEventStats(event, rssi, txSpeed, failureReason, ifaceInfo);
     }
 
     /**
@@ -324,19 +389,20 @@
      * @param wifiInfo object holding relevant values
      */
     public void noteSignalPoll(@NonNull ExtendedWifiInfo wifiInfo) {
-        if (!mPolled && wifiInfo.getRssi() != INVALID_RSSI) {
+        IfaceInfo ifaceInfo = getIfaceInfo(wifiInfo.getIfaceName());
+        if (!ifaceInfo.polled && wifiInfo.getRssi() != INVALID_RSSI) {
             updatePerBssid(Event.FIRST_POLL_AFTER_CONNECTION, wifiInfo);
-            mPolled = true;
+            ifaceInfo.polled = true;
         }
         updatePerBssid(Event.SIGNAL_POLL, wifiInfo);
         int validTxSpeed = geTxLinkSpeedWithSufficientTxRate(wifiInfo);
         updatePerNetwork(Event.SIGNAL_POLL, wifiInfo.getSSID(), wifiInfo.getRssi(),
-                validTxSpeed, UNKNOWN_REASON);
-        if (mTsRoam > TS_NONE && wifiInfo.getRssi() != INVALID_RSSI) {
-            long duration = mClock.getElapsedSinceBootMillis() - mTsRoam;
+                validTxSpeed, UNKNOWN_REASON, ifaceInfo);
+        if (ifaceInfo.tsRoam > TS_NONE && wifiInfo.getRssi() != INVALID_RSSI) {
+            long duration = mClock.getElapsedSinceBootMillis() - ifaceInfo.tsRoam;
             if (duration >= SUCCESS_MILLIS_SINCE_ROAM) {
                 updatePerBssid(Event.ROAM_SUCCESS, wifiInfo);
-                mTsRoam = TS_NONE;
+                ifaceInfo.tsRoam = TS_NONE;
                 doWritesBssid();
             }
         }
@@ -360,8 +426,13 @@
      * @param wifiInfo object holding relevant values
      */
     public void noteIpConfiguration(@NonNull ExtendedWifiInfo wifiInfo) {
+        IfaceInfo ifaceInfo = getIfaceInfo(wifiInfo.getIfaceName());
         updatePerBssid(Event.IP_CONFIGURATION_SUCCESS, wifiInfo);
-        mAttemptingSwitch = false;
+        updatePerNetwork(Event.IP_CONFIGURATION_SUCCESS, wifiInfo.getSSID(), wifiInfo.getRssi(),
+                wifiInfo.getTxLinkSpeedMbps(), UNKNOWN_REASON, ifaceInfo);
+        PerNetwork perNetwork = lookupNetwork(wifiInfo.getSSID());
+        perNetwork.initBandwidthFilter(wifiInfo);
+        ifaceInfo.attemptingSwitch = false;
         doWrites();
     }
 
@@ -371,9 +442,10 @@
      * @param wifiInfo object holding relevant values
      */
     public void noteValidationSuccess(@NonNull ExtendedWifiInfo wifiInfo) {
-        if (mValidatedThisConnectionAtLeastOnce) return; // Only once per connection
+        IfaceInfo ifaceInfo = getIfaceInfo(wifiInfo.getIfaceName());
+        if (ifaceInfo.validatedThisConnectionAtLeastOnce) return; // Only once per connection
         updatePerBssid(Event.VALIDATION_SUCCESS, wifiInfo);
-        mValidatedThisConnectionAtLeastOnce = true;
+        ifaceInfo.validatedThisConnectionAtLeastOnce = true;
         doWrites();
     }
 
@@ -395,23 +467,24 @@
      */
     public void noteConnectionAttempt(@NonNull ExtendedWifiInfo wifiInfo,
             int scanRssi, String ssid) {
+        IfaceInfo ifaceInfo = getIfaceInfo(wifiInfo.getIfaceName());
         // We may or may not be currently connected. If not, simply record the start.
         // But if we are connected, wrap up the old one first.
-        if (mTsConnectionAttemptStart > TS_NONE) {
-            if (mPolled) {
+        if (ifaceInfo.tsConnectionAttemptStart > TS_NONE) {
+            if (ifaceInfo.polled) {
                 updatePerBssid(Event.LAST_POLL_BEFORE_SWITCH, wifiInfo);
             }
-            mAttemptingSwitch = true;
+            ifaceInfo.attemptingSwitch = true;
         }
-        mTsConnectionAttemptStart = mClock.getElapsedSinceBootMillis();
-        mPolled = false;
-        mSsidPrev = mSsidCurr;
-        mSsidCurr = ssid;
-        mFirmwareAlertTimeMs = TS_NONE;
+        ifaceInfo.tsConnectionAttemptStart = mClock.getElapsedSinceBootMillis();
+        ifaceInfo.polled = false;
+        ifaceInfo.ssidPrev = ifaceInfo.ssidCurr;
+        ifaceInfo.ssidCurr = ssid;
+        ifaceInfo.firmwareAlertTimeMs = TS_NONE;
 
         updatePerNetwork(Event.CONNECTION_ATTEMPT, ssid, scanRssi, LINK_SPEED_UNKNOWN,
-                UNKNOWN_REASON);
-        logd("CONNECTION_ATTEMPT" + (mAttemptingSwitch ? " X " : " ") + wifiInfo);
+                UNKNOWN_REASON, ifaceInfo);
+        logd("CONNECTION_ATTEMPT" + (ifaceInfo.attemptingSwitch ? " X " : " ") + wifiInfo);
     }
 
     /**
@@ -427,9 +500,11 @@
      * Record disconnection not initiated by wpa_supplicant in connected mode
      * @param reason is detailed disconnection reason code
      */
-    public void noteNonlocalDisconnect(int reason) {
-        mNonlocalDisconnection = true;
-        mDisconnectionReason = reason;
+    public void noteNonlocalDisconnect(String ifaceName, int reason) {
+        IfaceInfo ifaceInfo = getIfaceInfo(ifaceName);
+
+        ifaceInfo.nonlocalDisconnection = true;
+        ifaceInfo.disconnectionReason = reason;
         logd("nonlocal disconnection with reason: " + reason);
     }
 
@@ -437,7 +512,11 @@
      * Record firmware alert timestamp and error code
      */
     public void noteFirmwareAlert(int errorCode) {
-        mFirmwareAlertTimeMs = mClock.getElapsedSinceBootMillis();
+        long ts = mClock.getElapsedSinceBootMillis();
+        // Firmware alert is device-level, not per-iface. Thus, note firmware alert on all ifaces.
+        for (IfaceInfo ifaceInfo : mIfaceToInfoMap.values()) {
+            ifaceInfo.firmwareAlertTimeMs = ts;
+        }
         logd("firmware alert with error code: " + errorCode);
     }
 
@@ -451,12 +530,12 @@
      */
     public void noteConnectionFailure(@NonNull ExtendedWifiInfo wifiInfo,
             int scanRssi, String ssid, @FailureReason int failureReason) {
+        IfaceInfo ifaceInfo = getIfaceInfo(wifiInfo.getIfaceName());
         // TODO: add the breakdown of level2FailureReason
         updatePerBssid(Event.CONNECTION_FAILURE, wifiInfo);
-
         updatePerNetwork(Event.CONNECTION_FAILURE, ssid, scanRssi, LINK_SPEED_UNKNOWN,
-                failureReason);
-        resetConnectionStateInternal(false);
+                failureReason, ifaceInfo);
+        resetConnectionStateForIfaceInternal(ifaceInfo, false);
     }
 
     /**
@@ -465,8 +544,9 @@
      * @param wifiInfo object holding relevant values
      */
     public void noteIpReachabilityLost(@NonNull ExtendedWifiInfo wifiInfo) {
-        if (mTsRoam > TS_NONE) {
-            mTsConnectionAttemptStart = mTsRoam; // just to update elapsed
+        IfaceInfo ifaceInfo = getIfaceInfo(wifiInfo.getIfaceName());
+        if (ifaceInfo.tsRoam > TS_NONE) {
+            ifaceInfo.tsConnectionAttemptStart = ifaceInfo.tsRoam; // just to update elapsed
             updatePerBssid(Event.ROAM_FAILURE, wifiInfo);
         } else {
             updatePerBssid(Event.IP_REACHABILITY_LOST, wifiInfo);
@@ -484,9 +564,9 @@
      *
      * @param wifiInfo object holding relevant values
      */
-    private void noteRoam(@NonNull ExtendedWifiInfo wifiInfo) {
+    private void noteRoam(IfaceInfo ifaceInfo, @NonNull ExtendedWifiInfo wifiInfo) {
         updatePerBssid(Event.LAST_POLL_BEFORE_ROAM, wifiInfo);
-        mTsRoam = mClock.getElapsedSinceBootMillis();
+        ifaceInfo.tsRoam = mClock.getElapsedSinceBootMillis();
     }
 
     /**
@@ -497,9 +577,10 @@
      */
     public void noteSupplicantStateChanging(@NonNull ExtendedWifiInfo wifiInfo,
             SupplicantState state) {
+        IfaceInfo ifaceInfo = getIfaceInfo(wifiInfo.getIfaceName());
         if (state == SupplicantState.COMPLETED && wifiInfo.getSupplicantState() == state) {
             // Our signal that a firmware roam has occurred
-            noteRoam(wifiInfo);
+            noteRoam(ifaceInfo, wifiInfo);
         }
         logd("Changing state to " + state + " " + wifiInfo);
     }
@@ -510,18 +591,16 @@
      * @param wifiInfo object holding old values
      */
     public void noteSupplicantStateChanged(ExtendedWifiInfo wifiInfo) {
-        logd("STATE " + wifiInfo);
+        logd("ifaceName=" + wifiInfo.getIfaceName() + ",wifiInfo=" + wifiInfo);
     }
 
     /**
-     * Updates the score card after wifi is disabled
+     * Updates the score card when wifi is disabled
      *
      * @param wifiInfo object holding relevant values
      */
     public void noteWifiDisabled(@NonNull ExtendedWifiInfo wifiInfo) {
         updatePerBssid(Event.WIFI_DISABLED, wifiInfo);
-        resetConnectionStateInternal(false);
-        doWrites();
     }
 
     /**
@@ -547,7 +626,7 @@
      * @return the updated count
      */
     public int incrementBssidBlocklistStreak(String ssid, String bssid,
-            @BssidBlocklistMonitor.FailureReason int reason) {
+            @WifiBlocklistMonitor.FailureReason int reason) {
         PerBssid perBssid = lookupBssid(ssid, bssid);
         return ++perBssid.blocklistStreakCount[reason];
     }
@@ -557,7 +636,7 @@
      * @return the blocklist streak count
      */
     public int getBssidBlocklistStreak(String ssid, String bssid,
-            @BssidBlocklistMonitor.FailureReason int reason) {
+            @WifiBlocklistMonitor.FailureReason int reason) {
         return lookupBssid(ssid, bssid).blocklistStreakCount[reason];
     }
 
@@ -565,7 +644,7 @@
      * Clear the blocklist streak count for a failure reason on an AP.
      */
     public void resetBssidBlocklistStreak(String ssid, String bssid,
-            @BssidBlocklistMonitor.FailureReason int reason) {
+            @WifiBlocklistMonitor.FailureReason int reason) {
         lookupBssid(ssid, bssid).blocklistStreakCount[reason] = 0;
     }
 
@@ -588,8 +667,9 @@
     /**
      * Detect abnormal disconnection at high RSSI with a high rate
      */
-    public int detectAbnormalDisconnection() {
-        String ssid = mAttemptingSwitch ? mSsidPrev : mSsidCurr;
+    public int detectAbnormalDisconnection(String ifaceName) {
+        IfaceInfo ifaceInfo = getIfaceInfo(ifaceName);
+        String ssid = ifaceInfo.attemptingSwitch ? ifaceInfo.ssidPrev : ifaceInfo.ssidCurr;
         PerNetwork perNetwork = lookupNetwork(ssid);
         NetworkConnectionStats recentStats = perNetwork.getRecentStats();
         if (recentStats.getRecentCountCode() == CNT_SHORT_CONNECTION_NONLOCAL) {
@@ -634,6 +714,12 @@
                     mDeviceConfigFacade.getAssocTimeoutHighThrPercent(),
                     mDeviceConfigFacade.getAssocTimeoutCountMin(),
                     CNT_CONNECTION_ATTEMPT);
+        } else if (recentCountCode == CNT_DISCONNECTION_NONLOCAL_CONNECTING) {
+            return detectAbnormalFailureReason(recentStats, CNT_DISCONNECTION_NONLOCAL_CONNECTING,
+                    REASON_CONNECTION_FAILURE_DISCONNECTION,
+                    mDeviceConfigFacade.getConnectionFailureDisconnectionHighThrPercent(),
+                    mDeviceConfigFacade.getConnectionFailureDisconnectionCountMin(),
+                    CNT_CONNECTION_ATTEMPT);
         } else if (recentCountCode == CNT_CONNECTION_FAILURE) {
             return detectAbnormalFailureReason(recentStats, CNT_CONNECTION_FAILURE,
                     REASON_CONNECTION_FAILURE,
@@ -677,7 +763,11 @@
         public final String ssid;
         public final MacAddress bssid;
         public final int[] blocklistStreakCount =
-                new int[BssidBlocklistMonitor.NUMBER_REASON_CODES];
+                new int[WifiBlocklistMonitor.NUMBER_REASON_CODES];
+        public long[][][] bandwidthStatsValue =
+                new long[NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL];
+        public int[][][] bandwidthStatsCount =
+                new int[NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL];
         // The wall clock time in milliseconds for the last successful l2 connection.
         public long lastConnectionTimestampMs;
         public boolean changed;
@@ -688,6 +778,7 @@
         private int mNetworkConfigId = Integer.MIN_VALUE;
         private final Map<Pair<Event, Integer>, PerSignal>
                 mSignalForEventAndFrequency = new ArrayMap<>();
+
         PerBssid(String ssid, MacAddress bssid) {
             super(computeHashLong(ssid, bssid, mL2KeySeed));
             this.ssid = ssid;
@@ -696,7 +787,8 @@
             this.changed = false;
             this.referenced = false;
         }
-        void updateEventStats(Event event, int frequency, int rssi, int linkspeed) {
+        void updateEventStats(Event event, int frequency, int rssi, int linkspeed,
+                String ifaceName) {
             PerSignal perSignal = lookupSignal(event, frequency);
             if (rssi != INVALID_RSSI) {
                 perSignal.rssi.update(rssi);
@@ -706,8 +798,10 @@
                 perSignal.linkspeed.update(linkspeed);
                 changed = true;
             }
-            if (perSignal.elapsedMs != null && mTsConnectionAttemptStart > TS_NONE) {
-                long millis = mClock.getElapsedSinceBootMillis() - mTsConnectionAttemptStart;
+            IfaceInfo ifaceInfo = getIfaceInfo(ifaceName);
+            if (perSignal.elapsedMs != null && ifaceInfo.tsConnectionAttemptStart > TS_NONE) {
+                long millis =
+                        mClock.getElapsedSinceBootMillis() - ifaceInfo.tsConnectionAttemptStart;
                 if (millis >= 0) {
                     perSignal.elapsedMs.update(millis);
                     changed = true;
@@ -757,6 +851,8 @@
             for (PerSignal sig: mSignalForEventAndFrequency.values()) {
                 builder.addEventStats(sig.toSignal());
             }
+            builder.setBandwidthStatsAll(toBandwidthStatsAll(
+                    bandwidthStatsValue, bandwidthStatsCount));
             return builder.build();
         }
         PerBssid merge(AccessPoint ap) {
@@ -787,6 +883,10 @@
                     changed = true;
                 }
             }
+            if (ap.hasBandwidthStatsAll()) {
+                mergeBandwidthStatsAll(ap.getBandwidthStatsAll(),
+                        bandwidthStatsValue, bandwidthStatsCount);
+            }
             return this;
         }
 
@@ -844,8 +944,108 @@
         }
     }
 
+    private BandwidthStatsAll toBandwidthStatsAll(long[][][] values, int[][][] counts) {
+        BandwidthStatsAll.Builder builder = BandwidthStatsAll.newBuilder();
+        builder.setStats2G(toBandwidthStatsAllLink(values[0], counts[0]));
+        builder.setStatsAbove2G(toBandwidthStatsAllLink(values[1], counts[1]));
+        return builder.build();
+    }
+
+    private BandwidthStatsAllLink toBandwidthStatsAllLink(long[][] values, int[][] counts) {
+        BandwidthStatsAllLink.Builder builder = BandwidthStatsAllLink.newBuilder();
+        builder.setTx(toBandwidthStatsAllLevel(values[LINK_TX], counts[LINK_TX]));
+        builder.setRx(toBandwidthStatsAllLevel(values[LINK_RX], counts[LINK_RX]));
+        return builder.build();
+    }
+
+    private BandwidthStatsAllLevel toBandwidthStatsAllLevel(long[] values, int[] counts) {
+        BandwidthStatsAllLevel.Builder builder = BandwidthStatsAllLevel.newBuilder();
+        for (int i = 0; i < NUM_SIGNAL_LEVEL; i++) {
+            builder.addLevel(toBandwidthStats(values[i], counts[i]));
+        }
+        return builder.build();
+    }
+
+    private BandwidthStats toBandwidthStats(long value, int count) {
+        BandwidthStats.Builder builder = BandwidthStats.newBuilder();
+        builder.setValue(value);
+        builder.setCount(count);
+        return builder.build();
+    }
+
+    private void mergeBandwidthStatsAll(BandwidthStatsAll source,
+            long[][][] values, int[][][] counts) {
+        if (source.hasStats2G()) {
+            mergeBandwidthStatsAllLink(source.getStats2G(), values[0], counts[0]);
+        }
+        if (source.hasStatsAbove2G()) {
+            mergeBandwidthStatsAllLink(source.getStatsAbove2G(), values[1], counts[1]);
+        }
+    }
+
+    private void mergeBandwidthStatsAllLink(BandwidthStatsAllLink source,
+            long[][] values, int[][] counts) {
+        if (source.hasTx()) {
+            mergeBandwidthStatsAllLevel(source.getTx(), values[LINK_TX], counts[LINK_TX]);
+        }
+        if (source.hasRx()) {
+            mergeBandwidthStatsAllLevel(source.getRx(), values[LINK_RX], counts[LINK_RX]);
+        }
+    }
+
+    private void mergeBandwidthStatsAllLevel(BandwidthStatsAllLevel source,
+            long[] values, int[] counts) {
+        int levelCnt = source.getLevelCount();
+        for (int i = 0; i < levelCnt; i++) {
+            BandwidthStats stats = source.getLevel(i);
+            if (stats.hasValue()) {
+                values[i] += stats.getValue();
+            }
+            if (stats.hasCount()) {
+                counts[i] += stats.getCount();
+            }
+        }
+    }
+
+    // TODO: b/178641307 move the following parameters to config.xml
+    // Array dimension : int [NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL]
+    static final int[][][] LINK_BANDWIDTH_INIT_KBPS =
+            {{{500, 2500, 10000, 12000, 12000}, {500, 2500, 10000, 30000, 30000}},
+            {{1500, 7500, 12000, 12000, 12000}, {1500, 7500, 30000, 60000, 60000}}};
+    // To be used in link bandwidth estimation, each TrafficStats poll sample needs to be above
+    // the following values. Defined per signal level.
+    // int [NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL]
+    // Use the low Tx threshold because xDSL UL speed could be below 1Mbps.
+    static final int[][] LINK_BANDWIDTH_BYTE_DELTA_THR_KBYTE =
+            {{200, 300, 300, 300, 300}, {200, 500, 1000, 2000, 2000}};
+    // To be used in the long term avg, each count needs to be above the following value
+    static final int BANDWIDTH_STATS_COUNT_THR = 5;
+    private static final int TIME_CONSTANT_SMALL_SEC = 6;
+    // If RSSI changes by more than the below value, update BW filter with small time constant
+    private static final int RSSI_DELTA_THR_DB = 8;
+    private static final int FILTER_SCALE = 128;
+    // Force weight to 0 if the elapsed time is above LARGE_TIME_DECAY_RATIO * time constant
+    private static final int LARGE_TIME_DECAY_RATIO = 4;
+    // Used to derive byte count threshold from avg BW
+    private static final int LOW_BW_TO_AVG_BW_RATIO_NUM = 6;
+    private static final int LOW_BW_TO_AVG_BW_RATIO_DEN = 8;
+    // For some high speed connections, heavy DL traffic could falsely trigger UL BW update due to
+    // TCP ACK and the low Tx byte count threshold. To work around the issue, skip Tx BW update if
+    // Rx Bytes / Tx Bytes > RX_OVER_TX_BYTE_RATIO_MAX (heavy DL and light UL traffic)
+    private static final int RX_OVER_TX_BYTE_RATIO_MAX = 5;
+    // radio on time below the following value is ignored.
+    static final int RADIO_ON_TIME_MIN_MS = 200;
+    static final int RADIO_ON_ELAPSED_TIME_DELTA_MAX_MS = 200;
+    static final int NUM_SIGNAL_LEVEL = 5;
+    static final int LINK_TX = 0;
+    static final int LINK_RX = 1;
+    private static final int NUM_LINK_BAND = 2;
+    private static final int NUM_LINK_DIRECTION = 2;
+    private static final long BW_UPDATE_TIME_RESET_MS = TIME_CONSTANT_SMALL_SEC * 1000 * -10;
+    private static final int MAX_ERROR_PERCENT = 100 * 100;
+    private static final int EXTRA_SAMPLE_BW_FILTERING = 2;
     /**
-     * A class collecting the connection stats of one network or SSID.
+     * A class collecting the connection and link bandwidth stats of one network or SSID.
      */
     final class PerNetwork extends MemoryStoreAccessBase {
         public int id;
@@ -861,6 +1061,25 @@
         private LruList<Integer> mFrequencyList;
         // In memory keep frequency with timestamp last time available, the elapsed time since boot.
         private SparseLongArray mFreqTimestamp;
+        private long mLastRxBytes;
+        private long mLastTxBytes;
+        private boolean mLastTrafficValid = true;
+        private String mBssid = "";
+        private int mSignalLevel;  // initialize to zero to handle possible race condition
+        private int mBandIdx;  // initialize to zero to handle possible race condition
+        private int[] mByteDeltaAccThr = new int[NUM_LINK_DIRECTION];
+        private int[] mFilterKbps = new int[NUM_LINK_DIRECTION];
+        private int[] mBandwidthSampleKbps = new int[NUM_LINK_DIRECTION];
+        private int[] mAvgUsedKbps = new int[NUM_LINK_DIRECTION];
+        private int mBandwidthUpdateRssiDbm = -1;
+        private int mBandwidthUpdateBandIdx = -1;
+        private boolean[] mBandwidthSampleValid = new boolean[NUM_LINK_DIRECTION];
+        private long[] mBandwidthSampleValidTimeMs = new long[]{BW_UPDATE_TIME_RESET_MS,
+                BW_UPDATE_TIME_RESET_MS};
+        long[][][] mBandwidthStatsValue =
+                new long[NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL];
+        int[][][] mBandwidthStatsCount =
+                new int[NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL];
 
         PerNetwork(String ssid) {
             super(computeHashLong(ssid, MacAddress.fromString(DEFAULT_MAC_ADDRESS), mL2KeySeed));
@@ -874,7 +1093,8 @@
             mFreqTimestamp = new SparseLongArray();
         }
 
-        void updateEventStats(Event event, int rssi, int txSpeed, int failureReason) {
+        void updateEventStats(Event event, int rssi, int txSpeed, int failureReason,
+                IfaceInfo ifaceInfo) {
             finishPendingRead();
             long currTimeMs = mClock.getElapsedSinceBootMillis();
             switch (event) {
@@ -895,39 +1115,56 @@
                 case CONNECTION_FAILURE:
                     mConnectionSessionStartTimeMs = TS_NONE;
                     if (rssi >= mDeviceConfigFacade.getHealthMonitorMinRssiThrDbm()) {
-                        if (failureReason != BssidBlocklistMonitor.REASON_WRONG_PASSWORD) {
+                        if (failureReason != WifiBlocklistMonitor.REASON_WRONG_PASSWORD) {
                             mRecentStats.incrementCount(CNT_CONNECTION_FAILURE);
                             mRecentStats.incrementCount(CNT_CONSECUTIVE_CONNECTION_FAILURE);
                         }
                         switch (failureReason) {
-                            case BssidBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA:
-                            case BssidBlocklistMonitor.REASON_ASSOCIATION_REJECTION:
+                            case WifiBlocklistMonitor.REASON_ASSOCIATION_REJECTION:
                                 mRecentStats.incrementCount(CNT_ASSOCIATION_REJECTION);
                                 break;
-                            case BssidBlocklistMonitor.REASON_ASSOCIATION_TIMEOUT:
+                            case WifiBlocklistMonitor.REASON_ASSOCIATION_TIMEOUT:
                                 mRecentStats.incrementCount(CNT_ASSOCIATION_TIMEOUT);
                                 break;
-                            case BssidBlocklistMonitor.REASON_AUTHENTICATION_FAILURE:
-                            case BssidBlocklistMonitor.REASON_EAP_FAILURE:
+                            case WifiBlocklistMonitor.REASON_AUTHENTICATION_FAILURE:
+                            case WifiBlocklistMonitor.REASON_EAP_FAILURE:
                                 mRecentStats.incrementCount(CNT_AUTHENTICATION_FAILURE);
                                 break;
-                            case BssidBlocklistMonitor.REASON_WRONG_PASSWORD:
-                            case BssidBlocklistMonitor.REASON_DHCP_FAILURE:
+                            case WifiBlocklistMonitor.REASON_NONLOCAL_DISCONNECT_CONNECTING:
+                                mRecentStats.incrementCount(CNT_DISCONNECTION_NONLOCAL_CONNECTING);
+                                break;
+                            case WifiBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA:
+                            case WifiBlocklistMonitor.REASON_WRONG_PASSWORD:
+                            case WifiBlocklistMonitor.REASON_DHCP_FAILURE:
                             default:
                                 break;
                         }
                     }
                     changed = true;
                     break;
+                case IP_CONFIGURATION_SUCCESS:
+                    // Reset CNT_CONSECUTIVE_CONNECTION_FAILURE since L3 is also connected
+                    mRecentStats.clearCount(CNT_CONSECUTIVE_CONNECTION_FAILURE);
+                    changed = true;
+                    logd(this.toString());
+                    break;
                 case WIFI_DISABLED:
                 case DISCONNECTION:
-                    handleDisconnection();
+                    if (mConnectionSessionStartTimeMs <= TS_NONE) {
+                        return;
+                    }
+                    handleDisconnectionAfterConnection(ifaceInfo);
+                    mConnectionSessionStartTimeMs = TS_NONE;
+                    mLastRssiPollTimeMs = TS_NONE;
+                    mFilterKbps[LINK_TX] = 0;
+                    mFilterKbps[LINK_RX] = 0;
+                    mBandwidthUpdateRssiDbm = -1;
+                    mBandwidthUpdateBandIdx = -1;
                     changed = true;
                     break;
                 default:
                     break;
             }
-            logd(this.toString());
         }
         @Override
         public String toString() {
@@ -943,46 +1180,58 @@
             sb.append(" StatsRecent: ").append(mRecentStats).append("\n");
             sb.append(" StatsCurr: ").append(mStatsCurrBuild).append("\n");
             sb.append(" StatsPrev: ").append(mStatsPrevBuild);
+            sb.append(" BandwidthStats:\n");
+            for (int i = 0; i < NUM_LINK_BAND; i++) {
+                for (int j = 0; j < NUM_LINK_DIRECTION; j++) {
+                    sb.append(" avgKbps: ");
+                    for (int k = 0; k < NUM_SIGNAL_LEVEL; k++) {
+                        int avgKbps = mBandwidthStatsCount[i][j][k] == 0 ? 0 : (int)
+                                (mBandwidthStatsValue[i][j][k] / mBandwidthStatsCount[i][j][k]);
+                        sb.append(" " + avgKbps);
+                    }
+                    sb.append("\n count: ");
+                    for (int k = 0; k < NUM_SIGNAL_LEVEL; k++) {
+                        sb.append(" " + mBandwidthStatsCount[i][j][k]);
+                    }
+                    sb.append("\n");
+                }
+                sb.append("\n");
+            }
             return sb.toString();
         }
-        private void handleDisconnection() {
-            if (mConnectionSessionStartTimeMs > TS_NONE) {
-                long currTimeMs = mClock.getElapsedSinceBootMillis();
-                int currSessionDurationMs = (int) (currTimeMs - mConnectionSessionStartTimeMs);
-                int currSessionDurationSec = currSessionDurationMs / 1000;
-                mRecentStats.accumulate(CNT_CONNECTION_DURATION_SEC, currSessionDurationSec);
-                long timeSinceLastRssiPollMs = currTimeMs - mLastRssiPollTimeMs;
-                boolean hasRecentRssiPoll = mLastRssiPollTimeMs > TS_NONE
-                        && timeSinceLastRssiPollMs <= mDeviceConfigFacade
-                        .getHealthMonitorRssiPollValidTimeMs();
-                if (hasRecentRssiPoll) {
-                    mRecentStats.incrementCount(CNT_DISCONNECTION);
-                }
-                int fwAlertValidTimeMs = mDeviceConfigFacade.getHealthMonitorFwAlertValidTimeMs();
-                long timeSinceLastFirmAlert = currTimeMs - mFirmwareAlertTimeMs;
-                boolean isInvalidFwAlertTime = mFirmwareAlertTimeMs == TS_NONE;
-                boolean disableFwAlertCheck = fwAlertValidTimeMs == -1;
-                boolean passFirmwareAlertCheck = disableFwAlertCheck ? true : (isInvalidFwAlertTime
-                        ? false : timeSinceLastFirmAlert < fwAlertValidTimeMs);
-                boolean hasHighRssiOrHighTxSpeed =
-                        mLastRssiPoll >= mDeviceConfigFacade.getHealthMonitorMinRssiThrDbm()
-                        || mLastTxSpeedPoll >= HEALTH_MONITOR_COUNT_TX_SPEED_MIN_MBPS;
-                if (mNonlocalDisconnection && hasRecentRssiPoll
-                        && isAbnormalDisconnectionReason(mDisconnectionReason)
-                        && passFirmwareAlertCheck
-                        && hasHighRssiOrHighTxSpeed) {
-                    mRecentStats.incrementCount(CNT_DISCONNECTION_NONLOCAL);
-                    if (currSessionDurationMs <= mDeviceConfigFacade
-                            .getHealthMonitorShortConnectionDurationThrMs()) {
-                        mRecentStats.incrementCount(CNT_SHORT_CONNECTION_NONLOCAL);
-                    }
+
+        private void handleDisconnectionAfterConnection(IfaceInfo ifaceInfo) {
+            long currTimeMs = mClock.getElapsedSinceBootMillis();
+            int currSessionDurationMs = (int) (currTimeMs - mConnectionSessionStartTimeMs);
+            int currSessionDurationSec = currSessionDurationMs / 1000;
+            mRecentStats.accumulate(CNT_CONNECTION_DURATION_SEC, currSessionDurationSec);
+            long timeSinceLastRssiPollMs = currTimeMs - mLastRssiPollTimeMs;
+            boolean hasRecentRssiPoll = mLastRssiPollTimeMs > TS_NONE
+                    && timeSinceLastRssiPollMs <= mDeviceConfigFacade
+                    .getHealthMonitorRssiPollValidTimeMs();
+            if (hasRecentRssiPoll) {
+                mRecentStats.incrementCount(CNT_DISCONNECTION);
+            }
+            int fwAlertValidTimeMs = mDeviceConfigFacade.getHealthMonitorFwAlertValidTimeMs();
+            long timeSinceLastFirmAlert = currTimeMs - ifaceInfo.firmwareAlertTimeMs;
+            boolean isInvalidFwAlertTime = ifaceInfo.firmwareAlertTimeMs == TS_NONE;
+            boolean disableFwAlertCheck = fwAlertValidTimeMs == -1;
+            boolean passFirmwareAlertCheck = disableFwAlertCheck ? true : (isInvalidFwAlertTime
+                    ? false : timeSinceLastFirmAlert < fwAlertValidTimeMs);
+            boolean hasHighRssiOrHighTxSpeed =
+                    mLastRssiPoll >= mDeviceConfigFacade.getHealthMonitorMinRssiThrDbm()
+                    || mLastTxSpeedPoll >= HEALTH_MONITOR_COUNT_TX_SPEED_MIN_MBPS;
+            if (ifaceInfo.nonlocalDisconnection && hasRecentRssiPoll
+                    && isAbnormalDisconnectionReason(ifaceInfo.disconnectionReason)
+                    && passFirmwareAlertCheck
+                    && hasHighRssiOrHighTxSpeed) {
+                mRecentStats.incrementCount(CNT_DISCONNECTION_NONLOCAL);
+                if (currSessionDurationMs <= mDeviceConfigFacade
+                        .getHealthMonitorShortConnectionDurationThrMs()) {
+                    mRecentStats.incrementCount(CNT_SHORT_CONNECTION_NONLOCAL);
                 }
             }
-            // Reset CNT_CONSECUTIVE_CONNECTION_FAILURE here so that it can report the correct
-            // failure count after a connection success
-            mRecentStats.clearCount(CNT_CONSECUTIVE_CONNECTION_FAILURE);
-            mConnectionSessionStartTimeMs = TS_NONE;
-            mLastRssiPollTimeMs = TS_NONE;
+
         }
 
         private boolean isAbnormalDisconnectionReason(int disconnectionReason) {
@@ -1028,6 +1277,342 @@
         }
 
         /**
+         * Update link bandwidth estimates based on TrafficStats byte counts and radio on time
+         */
+        void updateLinkBandwidth(WifiLinkLayerStats oldStats, WifiLinkLayerStats newStats,
+                ExtendedWifiInfo wifiInfo) {
+            mBandwidthSampleValid[LINK_TX] = false;
+            mBandwidthSampleValid[LINK_RX] = false;
+            long txBytes = mFrameworkFacade.getTotalTxBytes() - mFrameworkFacade.getMobileTxBytes();
+            long rxBytes = mFrameworkFacade.getTotalRxBytes() - mFrameworkFacade.getMobileRxBytes();
+            // Sometimes TrafficStats byte counts return invalid values
+            // Ignore next two polls if it happens
+            boolean trafficValid = txBytes >= mLastTxBytes && rxBytes >= mLastRxBytes;
+            if (!mLastTrafficValid || !trafficValid) {
+                mLastTrafficValid = trafficValid;
+                logv("invalid traffic count tx " + txBytes + " last " + mLastTxBytes
+                        + " rx " + rxBytes + " last " + mLastRxBytes);
+                mLastTxBytes = txBytes;
+                mLastRxBytes = rxBytes;
+                return;
+            }
+
+            updateWifiInfo(wifiInfo);
+            updateLinkBandwidthTxRxSample(oldStats, newStats, wifiInfo, txBytes, rxBytes);
+            mLastTxBytes = txBytes;
+            mLastRxBytes = rxBytes;
+
+            updateBandwidthWithFilterApplied(LINK_TX, wifiInfo);
+            updateBandwidthWithFilterApplied(LINK_RX, wifiInfo);
+            mBandwidthUpdateRssiDbm = wifiInfo.getRssi();
+            mBandwidthUpdateBandIdx = mBandIdx;
+        }
+
+        void updateWifiInfo(ExtendedWifiInfo wifiInfo) {
+            int rssi = wifiInfo.getRssi();
+            mSignalLevel = RssiUtil.calculateSignalLevel(mContext, rssi);
+            mSignalLevel = Math.min(mSignalLevel, NUM_SIGNAL_LEVEL - 1);
+            mBandIdx = getBandIdx(wifiInfo);
+            mBssid = wifiInfo.getBSSID();
+            mByteDeltaAccThr[LINK_TX] = getByteDeltaAccThr(LINK_TX);
+            mByteDeltaAccThr[LINK_RX] = getByteDeltaAccThr(LINK_RX);
+        }
+
+        private void updateLinkBandwidthTxRxSample(WifiLinkLayerStats oldStats,
+                WifiLinkLayerStats newStats, ExtendedWifiInfo wifiInfo,
+                long txBytes, long rxBytes) {
+            // oldStats is reset to null after screen off or disconnection
+            if (oldStats == null || newStats == null) {
+                return;
+            }
+
+            int elapsedTimeMs = (int) (newStats.timeStampInMs - oldStats.timeStampInMs);
+            if (elapsedTimeMs > MAX_TRAFFIC_STATS_POLL_TIME_DELTA_MS) {
+                return;
+            }
+
+            int onTimeMs = getTotalRadioOnTimeMs(newStats) - getTotalRadioOnTimeMs(oldStats);
+            if (onTimeMs <= RADIO_ON_TIME_MIN_MS
+                    || onTimeMs > RADIO_ON_ELAPSED_TIME_DELTA_MAX_MS + elapsedTimeMs) {
+                return;
+            }
+            onTimeMs = Math.min(elapsedTimeMs, onTimeMs);
+
+            long txBytesDelta = txBytes - mLastTxBytes;
+            long rxBytesDelta = rxBytes - mLastRxBytes;
+            if (txBytesDelta * RX_OVER_TX_BYTE_RATIO_MAX >= rxBytesDelta) {
+                updateBandwidthSample(txBytesDelta, LINK_TX, onTimeMs,
+                        wifiInfo.getMaxSupportedTxLinkSpeedMbps());
+            }
+
+            updateBandwidthSample(rxBytesDelta, LINK_RX, onTimeMs,
+                    wifiInfo.getMaxSupportedRxLinkSpeedMbps());
+
+            if (!mBandwidthSampleValid[LINK_RX] && !mBandwidthSampleValid[LINK_TX]) {
+                return;
+            }
+            StringBuilder sb = new StringBuilder();
+            logv(sb.append(" rssi ").append(wifiInfo.getRssi())
+                    .append(" level ").append(mSignalLevel)
+                    .append(" bssid ").append(wifiInfo.getBSSID())
+                    .append(" freq ").append(wifiInfo.getFrequency())
+                    .append(" onTimeMs ").append(onTimeMs)
+                    .append(" txKB ").append(txBytesDelta / 1024)
+                    .append(" rxKB ").append(rxBytesDelta / 1024)
+                    .append(" txKBThr ").append(mByteDeltaAccThr[LINK_TX] / 1024)
+                    .append(" rxKBThr ").append(mByteDeltaAccThr[LINK_RX] / 1024)
+                    .toString());
+        }
+
+        private int getTotalRadioOnTimeMs(@NonNull WifiLinkLayerStats stats) {
+            if (stats.radioStats != null && stats.radioStats.length > 0) {
+                int totalRadioOnTime = 0;
+                for (WifiLinkLayerStats.RadioStat stat : stats.radioStats) {
+                    totalRadioOnTime += stat.on_time;
+                }
+                return totalRadioOnTime;
+            }
+            return stats.on_time;
+        }
+
+        private int getBandIdx(ExtendedWifiInfo wifiInfo) {
+            return ScanResult.is24GHz(wifiInfo.getFrequency()) ? 0 : 1;
+        }
+
+        private void updateBandwidthSample(long bytesDelta, int link, int onTimeMs,
+                int maxSupportedLinkSpeedMbps) {
+            checkAndPossiblyResetBandwidthStats(link, maxSupportedLinkSpeedMbps);
+            if (bytesDelta < mByteDeltaAccThr[link]) {
+                return;
+            }
+            long speedKbps = bytesDelta / onTimeMs * 8;
+            if (speedKbps > (maxSupportedLinkSpeedMbps * 1000)) {
+                return;
+            }
+            int linkBandwidthKbps = (int) speedKbps;
+            changed = true;
+            mBandwidthSampleValid[link] = true;
+            mBandwidthSampleKbps[link] = linkBandwidthKbps;
+            // Update SSID level stats
+            mBandwidthStatsValue[mBandIdx][link][mSignalLevel] += linkBandwidthKbps;
+            mBandwidthStatsCount[mBandIdx][link][mSignalLevel]++;
+            // Update BSSID level stats
+            PerBssid perBssid = lookupBssid(ssid, mBssid);
+            if (perBssid != mPlaceholderPerBssid) {
+                perBssid.changed = true;
+                perBssid.bandwidthStatsValue[mBandIdx][link][mSignalLevel] += linkBandwidthKbps;
+                perBssid.bandwidthStatsCount[mBandIdx][link][mSignalLevel]++;
+            }
+        }
+
+        private void checkAndPossiblyResetBandwidthStats(int link, int maxSupportedLinkSpeedMbps) {
+            if (getAvgUsedLinkBandwidthKbps(link) > (maxSupportedLinkSpeedMbps * 1000)) {
+                resetBandwidthStats(link);
+            }
+        }
+
+        private void resetBandwidthStats(int link) {
+            changed = true;
+            // Reset SSID level stats
+            mBandwidthStatsValue[mBandIdx][link][mSignalLevel] = 0;
+            mBandwidthStatsCount[mBandIdx][link][mSignalLevel] = 0;
+            // Reset BSSID level stats
+            PerBssid perBssid = lookupBssid(ssid, mBssid);
+            if (perBssid != mPlaceholderPerBssid) {
+                perBssid.changed = true;
+                perBssid.bandwidthStatsValue[mBandIdx][link][mSignalLevel] = 0;
+                perBssid.bandwidthStatsCount[mBandIdx][link][mSignalLevel] = 0;
+            }
+        }
+
+        private int getByteDeltaAccThr(int link) {
+            int maxTimeDeltaMs = mContext.getResources().getInteger(
+                    R.integer.config_wifiPollRssiIntervalMilliseconds);
+            int lowBytes = calculateByteCountThreshold(getAvgUsedLinkBandwidthKbps(link),
+                    maxTimeDeltaMs);
+            // Start with a predefined value
+            int deltaAccThr = LINK_BANDWIDTH_BYTE_DELTA_THR_KBYTE[link][mSignalLevel] * 1024;
+            if (lowBytes > 0) {
+                // Raise the threshold if the avg usage BW is high
+                deltaAccThr = Math.max(lowBytes, deltaAccThr);
+                deltaAccThr = Math.min(deltaAccThr, mDeviceConfigFacade
+                        .getTrafficStatsThresholdMaxKbyte() * 1024);
+            }
+            return deltaAccThr;
+        }
+
+        private void initBandwidthFilter(ExtendedWifiInfo wifiInfo) {
+            updateWifiInfo(wifiInfo);
+            for (int link = 0; link < NUM_LINK_DIRECTION; link++) {
+                mFilterKbps[link] = getAvgLinkBandwidthKbps(link);
+            }
+        }
+
+        private void updateBandwidthWithFilterApplied(int link, ExtendedWifiInfo wifiInfo) {
+            int avgKbps = getAvgLinkBandwidthKbps(link);
+            // Feed the filter with the long term avg if there is no valid BW sample so that filter
+            // will gradually converge the long term avg.
+            int filterInKbps = mBandwidthSampleValid[link] ? mBandwidthSampleKbps[link] : avgKbps;
+
+            long currTimeMs = mClock.getElapsedSinceBootMillis();
+            int timeDeltaSec = (int) (currTimeMs - mBandwidthSampleValidTimeMs[link]) / 1000;
+
+            // If the operation condition changes since the last valid sample or the current sample
+            // has higher BW, use a faster filter. Otherwise, use a slow filter
+            int timeConstantSec;
+            if (Math.abs(mBandwidthUpdateRssiDbm - wifiInfo.getRssi()) > RSSI_DELTA_THR_DB
+                    || (mBandwidthSampleValid[link] && mBandwidthSampleKbps[link] > avgKbps)
+                    || mBandwidthUpdateBandIdx != mBandIdx) {
+                timeConstantSec = TIME_CONSTANT_SMALL_SEC;
+            } else {
+                timeConstantSec = mDeviceConfigFacade.getBandwidthEstimatorLargeTimeConstantSec();
+            }
+            // Update timestamp for next iteration
+            if (mBandwidthSampleValid[link]) {
+                mBandwidthSampleValidTimeMs[link] = currTimeMs;
+            }
+
+            if (filterInKbps == mFilterKbps[link]) {
+                return;
+            }
+            int alpha = timeDeltaSec > LARGE_TIME_DECAY_RATIO * timeConstantSec ? 0
+                    : (int) (FILTER_SCALE * Math.exp(-1.0 * timeDeltaSec / timeConstantSec));
+
+            if (alpha == 0) {
+                mFilterKbps[link] = filterInKbps;
+                return;
+            }
+            long filterOutKbps = (long) mFilterKbps[link] * alpha
+                    + filterInKbps * FILTER_SCALE - filterInKbps * alpha;
+            filterOutKbps = filterOutKbps / FILTER_SCALE;
+            mFilterKbps[link] = (int) Math.min(filterOutKbps, Integer.MAX_VALUE);
+            StringBuilder sb = new StringBuilder();
+            logd(sb.append(link)
+                    .append(" lastSampleWeight=").append(alpha)
+                    .append("/").append(FILTER_SCALE)
+                    .append(" filterInKbps=").append(filterInKbps)
+                    .append(" avgKbps=").append(avgKbps)
+                    .append(" filterOutKbps=").append(mFilterKbps[link])
+                    .toString());
+        }
+
+        private int getAvgLinkBandwidthKbps(int link) {
+            mAvgUsedKbps[link] = getAvgUsedLinkBandwidthKbps(link);
+            if (mAvgUsedKbps[link] > 0) {
+                return mAvgUsedKbps[link];
+            }
+
+            int avgBwAdjSignalKbps = getAvgUsedBandwidthAdjacentThreeLevelKbps(link);
+            if (avgBwAdjSignalKbps > 0) {
+                return avgBwAdjSignalKbps;
+            }
+
+            // Fall back to a cold-start value
+            return LINK_BANDWIDTH_INIT_KBPS[mBandIdx][link][mSignalLevel];
+        }
+
+        private int getAvgUsedLinkBandwidthKbps(int link) {
+            // Check if current BSSID/signal level has enough count
+            PerBssid perBssid = lookupBssid(ssid, mBssid);
+            int count = perBssid.bandwidthStatsCount[mBandIdx][link][mSignalLevel];
+            long value = perBssid.bandwidthStatsValue[mBandIdx][link][mSignalLevel];
+            if (count >= BANDWIDTH_STATS_COUNT_THR) {
+                return (int) (value / count);
+            }
+
+            // Check if current SSID/band/signal level has enough count
+            count = mBandwidthStatsCount[mBandIdx][link][mSignalLevel];
+            value = mBandwidthStatsValue[mBandIdx][link][mSignalLevel];
+            if (count >= BANDWIDTH_STATS_COUNT_THR) {
+                return (int) (value / count);
+            }
+
+            return -1;
+        }
+
+        private int getAvgUsedBandwidthAdjacentThreeLevelKbps(int link) {
+            int count = 0;
+            long value = 0;
+            for (int i = -1; i <= 1; i++) {
+                int currLevel = mSignalLevel + i;
+                if (currLevel < 0 || currLevel >= NUM_SIGNAL_LEVEL) {
+                    continue;
+                }
+                count += mBandwidthStatsCount[mBandIdx][link][currLevel];
+                value += mBandwidthStatsValue[mBandIdx][link][currLevel];
+            }
+            if (count >= BANDWIDTH_STATS_COUNT_THR) {
+                return (int) (value / count);
+            }
+
+            return -1;
+        }
+
+        // Calculate a byte count threshold for the given avg BW and observation window size
+        private int calculateByteCountThreshold(int avgBwKbps, int durationMs) {
+            long avgBytes = (long) avgBwKbps / 8 * durationMs;
+            long result = avgBytes * LOW_BW_TO_AVG_BW_RATIO_NUM / LOW_BW_TO_AVG_BW_RATIO_DEN;
+            return (int) Math.min(result, Integer.MAX_VALUE);
+        }
+
+        /**
+         * Get the latest TrafficStats based end-to-end Tx link bandwidth estimation in Kbps
+         */
+        public int getTxLinkBandwidthKbps() {
+            return (mFilterKbps[LINK_TX] > 0) ? mFilterKbps[LINK_TX]
+                    : getAvgLinkBandwidthKbps(LINK_TX);
+        }
+
+        /**
+         * Get the latest TrafficStats based end-to-end Rx link bandwidth estimation in Kbps
+         */
+        public int getRxLinkBandwidthKbps() {
+            return (mFilterKbps[LINK_RX] > 0) ? mFilterKbps[LINK_RX]
+                    : getAvgLinkBandwidthKbps(LINK_RX);
+        }
+
+        /**
+         * Update Bandwidth metrics with the latest reported bandwidth and L2 BW values
+         */
+        public void updateBwMetrics(int[] reportedKbps, int[] l2Kbps) {
+            for (int link = 0; link < NUM_LINK_DIRECTION; link++) {
+                calculateError(link, reportedKbps[link], l2Kbps[link]);
+            }
+        }
+
+        private void calculateError(int link, int reportedKbps, int l2Kbps) {
+            if (mBandwidthStatsCount[mBandIdx][link][mSignalLevel] < (BANDWIDTH_STATS_COUNT_THR
+                    + EXTRA_SAMPLE_BW_FILTERING) || !mBandwidthSampleValid[link]
+                    || mAvgUsedKbps[link] <= 0) {
+                return;
+            }
+            int bwSampleKbps = mBandwidthSampleKbps[link];
+            int bwEstExtErrPercent = calculateErrorPercent(reportedKbps, bwSampleKbps);
+            int bwEstIntErrPercent = calculateErrorPercent(mFilterKbps[link], bwSampleKbps);
+            int l2ErrPercent = calculateErrorPercent(l2Kbps, bwSampleKbps);
+            mBwEstErrorAccPercent[mBandIdx][link][mSignalLevel] += Math.abs(bwEstExtErrPercent);
+            mL2ErrorAccPercent[mBandIdx][link][mSignalLevel] += Math.abs(l2ErrPercent);
+            mBwEstValue[mBandIdx][link][mSignalLevel] += bwSampleKbps;
+            mBwEstCount[mBandIdx][link][mSignalLevel]++;
+            StringBuilder sb = new StringBuilder();
+            logv(sb.append(link)
+                    .append(" sampKbps ").append(bwSampleKbps)
+                    .append(" filtKbps ").append(mFilterKbps[link])
+                    .append(" reportedKbps ").append(reportedKbps)
+                    .append(" avgUsedKbps ").append(mAvgUsedKbps[link])
+                    .append(" l2Kbps ").append(l2Kbps)
+                    .append(" intErrPercent ").append(bwEstIntErrPercent)
+                    .append(" extErrPercent ").append(bwEstExtErrPercent)
+                    .append(" l2ErrPercent ").append(l2ErrPercent)
+                    .toString());
+        }
+
+        private int calculateErrorPercent(int inKbps, int bwSampleKbps) {
+            long errorPercent = 100L * (inKbps - bwSampleKbps) / bwSampleKbps;
+            return (int) Math.max(-MAX_ERROR_PERCENT, Math.min(errorPercent, MAX_ERROR_PERCENT));
+        }
+
+        /**
         /* Detect a significant failure stats change with historical data
         /* or high failure stats without historical data.
         /* @return 0 if recentStats doesn't have sufficient data
@@ -1078,6 +1663,10 @@
                     REASON_CONNECTION_FAILURE,
                     mDeviceConfigFacade.getConnectionFailureCountMin(),
                     CNT_CONNECTION_ATTEMPT);
+            statsDeltaDetection(statsDec, statsInc, CNT_DISCONNECTION_NONLOCAL_CONNECTING,
+                    REASON_CONNECTION_FAILURE_DISCONNECTION,
+                    mDeviceConfigFacade.getConnectionFailureDisconnectionCountMin(),
+                    CNT_CONNECTION_ATTEMPT);
             statsDeltaDetection(statsDec, statsInc, CNT_AUTHENTICATION_FAILURE,
                     REASON_AUTH_FAILURE,
                     mDeviceConfigFacade.getAuthFailureCountMin(),
@@ -1098,6 +1687,11 @@
                     mDeviceConfigFacade.getConnectionFailureHighThrPercent(),
                     mDeviceConfigFacade.getConnectionFailureCountMin(),
                     CNT_CONNECTION_ATTEMPT);
+            recentStatsHighDetection(statsHigh, CNT_DISCONNECTION_NONLOCAL_CONNECTING,
+                    REASON_CONNECTION_FAILURE_DISCONNECTION,
+                    mDeviceConfigFacade.getConnectionFailureDisconnectionHighThrPercent(),
+                    mDeviceConfigFacade.getConnectionFailureDisconnectionCountMin(),
+                    CNT_CONNECTION_ATTEMPT);
             recentStatsHighDetection(statsHigh, CNT_AUTHENTICATION_FAILURE,
                     REASON_AUTH_FAILURE,
                     mDeviceConfigFacade.getAuthFailureHighThrPercent(),
@@ -1213,6 +1807,8 @@
             if (mFrequencyList.size() > 0) {
                 builder.addAllFrequencies(mFrequencyList.getEntries());
             }
+            builder.setBandwidthStatsAll(toBandwidthStatsAll(
+                    mBandwidthStatsValue, mBandwidthStatsCount));
             return builder.build();
         }
 
@@ -1227,6 +1823,8 @@
             builder.setNumAssociationRejection(stats.getCount(CNT_ASSOCIATION_REJECTION));
             builder.setNumAssociationTimeout(stats.getCount(CNT_ASSOCIATION_TIMEOUT));
             builder.setNumAuthenticationFailure(stats.getCount(CNT_AUTHENTICATION_FAILURE));
+            builder.setNumDisconnectionNonlocalConnecting(
+                    stats.getCount(CNT_DISCONNECTION_NONLOCAL_CONNECTING));
             return builder.build();
         }
 
@@ -1272,6 +1870,10 @@
                     mFrequencyList.add(mergedFrequencyList.get(i));
                 }
             }
+            if (ns.hasBandwidthStatsAll()) {
+                mergeBandwidthStatsAll(ns.getBandwidthStatsAll(),
+                        mBandwidthStatsValue, mBandwidthStatsCount);
+            }
             return this;
         }
 
@@ -1280,7 +1882,7 @@
                 target.accumulate(CNT_CONNECTION_ATTEMPT, source.getNumConnectionAttempt());
             }
             if (source.hasNumConnectionFailure()) {
-                target.accumulate(CNT_CONNECTION_ATTEMPT, source.getNumConnectionFailure());
+                target.accumulate(CNT_CONNECTION_FAILURE, source.getNumConnectionFailure());
             }
             if (source.hasConnectionDurationSec()) {
                 target.accumulate(CNT_CONNECTION_DURATION_SEC, source.getConnectionDurationSec());
@@ -1304,6 +1906,10 @@
             if (source.hasNumAuthenticationFailure()) {
                 target.accumulate(CNT_AUTHENTICATION_FAILURE, source.getNumAuthenticationFailure());
             }
+            if (source.hasNumDisconnectionNonlocalConnecting()) {
+                target.accumulate(CNT_DISCONNECTION_NONLOCAL_CONNECTING,
+                        source.getNumDisconnectionNonlocalConnecting());
+            }
         }
     }
 
@@ -1319,8 +1925,9 @@
     public static final int CNT_DISCONNECTION_NONLOCAL = 7;
     public static final int CNT_DISCONNECTION = 8;
     public static final int CNT_CONSECUTIVE_CONNECTION_FAILURE = 9;
+    public static final int CNT_DISCONNECTION_NONLOCAL_CONNECTING = 10;
     // Constant being used to keep track of how many counter there are.
-    public static final int NUMBER_CONNECTION_CNT_CODE = 10;
+    public static final int NUMBER_CONNECTION_CNT_CODE = 11;
     private static final String[] CONNECTION_CNT_NAME = {
         " ConnectAttempt: ",
         " ConnectFailure: ",
@@ -1331,7 +1938,8 @@
         " ShortDiscNonlocal: ",
         " DisconnectNonlocal: ",
         " Disconnect: ",
-        " ConsecutiveConnectFailure: "
+        " ConsecutiveConnectFailure: ",
+        " ConnectFailureDiscon: "
     };
 
     @IntDef(prefix = { "CNT_" }, value = {
@@ -1344,7 +1952,8 @@
         CNT_SHORT_CONNECTION_NONLOCAL,
         CNT_DISCONNECTION_NONLOCAL,
         CNT_DISCONNECTION,
-        CNT_CONSECUTIVE_CONNECTION_FAILURE
+        CNT_CONSECUTIVE_CONNECTION_FAILURE,
+        CNT_DISCONNECTION_NONLOCAL_CONNECTING
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ConnectionCountCode {}
@@ -1449,6 +2058,7 @@
             return sb.toString();
         }
     }
+
     /**
      * A base class dealing with common operations of MemoryStore.
      */
@@ -1503,9 +2113,17 @@
             Log.d(TAG, string);
         }
     }
+
+    private void logv(String string) {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, string);
+        }
+        mLocalLog.log(string);
+    }
+
     // Returned by lookupBssid when the BSSID is not available,
     // for instance when we are not associated.
-    private final PerBssid mDummyPerBssid;
+    private final PerBssid mPlaceholderPerBssid;
 
     private final Map<MacAddress, PerBssid> mApForBssid = new ArrayMap<>();
     private int mApForBssidTargetSize = TARGET_IN_MEMORY_ENTRIES;
@@ -1515,15 +2133,15 @@
     @NonNull PerBssid lookupBssid(String ssid, String bssid) {
         MacAddress mac;
         if (ssid == null || WifiManager.UNKNOWN_SSID.equals(ssid) || bssid == null) {
-            return mDummyPerBssid;
+            return mPlaceholderPerBssid;
         }
         try {
             mac = MacAddress.fromString(bssid);
         } catch (IllegalArgumentException e) {
-            return mDummyPerBssid;
+            return mPlaceholderPerBssid;
         }
-        if (mac.equals(mDummyPerBssid.bssid)) {
-            return mDummyPerBssid;
+        if (mac.equals(mPlaceholderPerBssid.bssid)) {
+            return mPlaceholderPerBssid;
         }
         PerBssid ans = mApForBssid.get(mac);
         if (ans == null || !ans.ssid.equals(ssid)) {
@@ -1560,11 +2178,11 @@
 
     // Returned by lookupNetwork when the network is not available,
     // for instance when we are not associated.
-    private final PerNetwork mDummyPerNetwork;
+    private final PerNetwork mPlaceholderPerNetwork;
     private final Map<String, PerNetwork> mApForNetwork = new ArrayMap<>();
-    PerNetwork lookupNetwork(String ssid) {
+    @NonNull PerNetwork lookupNetwork(String ssid) {
         if (ssid == null || WifiManager.UNKNOWN_SSID.equals(ssid)) {
-            return mDummyPerNetwork;
+            return mPlaceholderPerNetwork;
         }
 
         PerNetwork ans = mApForNetwork.get(ssid);
@@ -1979,6 +2597,115 @@
     public void clear() {
         mApForBssid.clear();
         mApForNetwork.clear();
-        resetConnectionStateInternal(false);
+        resetAllConnectionStatesInternal();
+    }
+
+    /**
+     * build bandwidth estimator stats proto and then clear all related counters
+     */
+    public BandwidthEstimatorStats dumpBandwidthEstimatorStats() {
+        BandwidthEstimatorStats stats = new BandwidthEstimatorStats();
+        stats.stats2G = dumpBandwdithStatsPerBand(0);
+        stats.statsAbove2G = dumpBandwdithStatsPerBand(1);
+        return stats;
+    }
+
+    private BandwidthEstimatorStats.PerBand dumpBandwdithStatsPerBand(int bandIdx) {
+        BandwidthEstimatorStats.PerBand stats = new BandwidthEstimatorStats.PerBand();
+        stats.tx = dumpBandwidthStatsPerLink(bandIdx, LINK_TX);
+        stats.rx = dumpBandwidthStatsPerLink(bandIdx, LINK_RX);
+        return stats;
+    }
+
+    private BandwidthEstimatorStats.PerLink dumpBandwidthStatsPerLink(
+            int bandIdx, int linkIdx) {
+        BandwidthEstimatorStats.PerLink stats = new BandwidthEstimatorStats.PerLink();
+        List<BandwidthEstimatorStats.PerLevel> levels = new ArrayList<>();
+        for (int level = 0; level < NUM_SIGNAL_LEVEL; level++) {
+            BandwidthEstimatorStats.PerLevel currStats =
+                    dumpBandwidthStatsPerLevel(bandIdx, linkIdx, level);
+            if (currStats != null) {
+                levels.add(currStats);
+            }
+        }
+        stats.level = levels.toArray(new BandwidthEstimatorStats.PerLevel[0]);
+        return stats;
+    }
+
+    private BandwidthEstimatorStats.PerLevel dumpBandwidthStatsPerLevel(
+            int bandIdx, int linkIdx, int level) {
+        int count = mBwEstCount[bandIdx][linkIdx][level];
+        if (count <= 0) {
+            return null;
+        }
+
+        BandwidthEstimatorStats.PerLevel stats = new BandwidthEstimatorStats.PerLevel();
+        stats.signalLevel = level;
+        stats.count = count;
+        stats.avgBandwidthKbps = calculateAvg(mBwEstValue[bandIdx][linkIdx][level], count);
+        stats.l2ErrorPercent = calculateAvg(
+                mL2ErrorAccPercent[bandIdx][linkIdx][level], count);
+        stats.bandwidthEstErrorPercent = calculateAvg(
+                mBwEstErrorAccPercent[bandIdx][linkIdx][level], count);
+
+        // reset counters for next run
+        mBwEstCount[bandIdx][linkIdx][level] = 0;
+        mBwEstValue[bandIdx][linkIdx][level] = 0;
+        mL2ErrorAccPercent[bandIdx][linkIdx][level] = 0;
+        mBwEstErrorAccPercent[bandIdx][linkIdx][level] = 0;
+        return stats;
+    }
+
+    private int calculateAvg(long acc, int count) {
+        return (count > 0) ? (int) (acc / count) : 0;
+    }
+
+    /**
+     * Dump the internal state and local logs
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("Dump of WifiScoreCard");
+        pw.println("current SSID(s):" + mIfaceToInfoMap.entrySet().stream()
+                .map(entry ->
+                        "{iface=" + entry.getKey() + ",ssid=" + entry.getValue().ssidCurr + "}")
+                .collect(Collectors.joining(",")));
+        try {
+            mLocalLog.dump(fd, pw, args);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        pw.println(" BW Estimation Stats");
+        for (int i = 0; i < 2; i++) {
+            pw.println((i == 0 ? "2G" : "5G"));
+            for (int j = 0; j < NUM_LINK_DIRECTION; j++) {
+                pw.println((j == 0 ? " Tx" : " Rx"));
+                pw.println(" Count");
+                printValues(mBwEstCount[i][j], pw);
+                pw.println(" AvgKbps");
+                printAvgStats(mBwEstValue[i][j], mBwEstCount[i][j], pw);
+                pw.println(" BwEst error");
+                printAvgStats(mBwEstErrorAccPercent[i][j], mBwEstCount[i][j], pw);
+                pw.println(" L2 error");
+                printAvgStats(mL2ErrorAccPercent[i][j], mBwEstCount[i][j], pw);
+            }
+        }
+        pw.println();
+    }
+
+    private void printValues(int[] values, PrintWriter pw) {
+        StringBuilder sb = new StringBuilder();
+        for (int k = 0; k < NUM_SIGNAL_LEVEL; k++) {
+            sb.append(" " + values[k]);
+        }
+        pw.println(sb.toString());
+    }
+
+    private void printAvgStats(long[] stats, int[] count, PrintWriter pw) {
+        StringBuilder sb = new StringBuilder();
+        for (int k = 0; k < NUM_SIGNAL_LEVEL; k++) {
+            sb.append(" " + calculateAvg(stats[k], count[k]));
+        }
+        pw.println(sb.toString());
     }
 }
diff --git a/service/java/com/android/server/wifi/WifiScoreReport.java b/service/java/com/android/server/wifi/WifiScoreReport.java
index c294423..067181b 100644
--- a/service/java/com/android/server/wifi/WifiScoreReport.java
+++ b/service/java/com/android/server/wifi/WifiScoreReport.java
@@ -16,24 +16,26 @@
 
 package com.android.server.wifi;
 
+import android.annotation.Nullable;
 import android.content.Context;
-import android.database.ContentObserver;
 import android.net.Network;
-import android.net.NetworkAgent;
+import android.net.NetworkCapabilities;
 import android.net.NetworkScore;
-import android.net.Uri;
-import android.net.wifi.IScoreUpdateObserver;
 import android.net.wifi.IWifiConnectedNetworkScorer;
+import android.net.wifi.WifiConnectedSessionInfo;
 import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
 import android.net.wifi.nl80211.WifiNl80211Manager;
-import android.os.Handler;
+import android.os.Build;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.RemoteException;
-import android.provider.Settings;
 import android.util.Log;
 
+import androidx.annotation.RequiresApi;
+
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
+import com.android.server.wifi.ActiveModeManager.ClientRole;
 import com.android.wifi.resources.R;
 
 import java.io.FileDescriptor;
@@ -64,151 +66,275 @@
     private static final long INVALID_WALL_CLOCK_MILLIS = -1;
 
     /**
-     * Copy of the settings string. Can't directly use the constant because it is @hide.
-     * See {@link android.provider.Settings.Secure.ADAPTIVE_CONNECTIVITY_ENABLED}.
-     * TODO(b/167709538) remove this hardcoded string and create new API in Wifi mainline.
+     * Set lingering score to be artificially lower than all other scores so that it will force
+     * ConnectivityService to prefer any other network over this one.
      */
     @VisibleForTesting
-    public static final String SETTINGS_SECURE_ADAPTIVE_CONNECTIVITY_ENABLED =
-            "adaptive_connectivity_enabled";
+    static final int LINGERING_SCORE = 1;
 
     // Cache of the last score
-    private int mLegacyIntScore = ConnectedScore.WIFI_MAX_SCORE;
-    // Builder for NetworkScore. Primary by default, unless mShouldReduceNetworkScore is true
-    // to make it lose against the primary STA.
-    private final NetworkScore.Builder mScoreBuilder =
-            new NetworkScore.Builder().setLegacyInt(mLegacyIntScore).setTransportPrimary(true);
+    private int mLegacyIntScore = ConnectedScore.WIFI_INITIAL_SCORE;
+    // Cache of the last usability status
+    private boolean mIsUsable = true;
+
+    /**
+     * If true, indicates that the associated {@link ClientModeImpl} instance is lingering
+     * as a part of make before break STA + STA use-case, and will always send
+     * {@link #LINGERING_SCORE} to NetworkAgent.
+     */
+    private boolean mShouldReduceNetworkScore = false;
+
+    /** The current role of the ClientModeManager which owns this instance of WifiScoreReport. */
+    @Nullable
+    private ClientRole mCurrentRole = null;
 
     private final ScoringParams mScoringParams;
     private final Clock mClock;
     private int mSessionNumber = 0; // not to be confused with sessionid, this just counts resets
-    private String mInterfaceName;
-    private final BssidBlocklistMonitor mBssidBlocklistMonitor;
+    private final String mInterfaceName;
+    private final WifiBlocklistMonitor mWifiBlocklistMonitor;
+    private final WifiScoreCard mWifiScoreCard;
     private final Context mContext;
     private long mLastScoreBreachLowTimeMillis = INVALID_WALL_CLOCK_MILLIS;
     private long mLastScoreBreachHighTimeMillis = INVALID_WALL_CLOCK_MILLIS;
 
-    ConnectedScore mAggressiveConnectedScore;
-    VelocityBasedConnectedScore mVelocityBasedConnectedScore;
+    private final ConnectedScore mAggressiveConnectedScore;
+    private VelocityBasedConnectedScore mVelocityBasedConnectedScore;
+    private final WifiSettingsStore mWifiSettingsStore;
+    private int mSessionIdNoReset = INVALID_SESSION_ID;
+    // Indicate whether current network is selected by the user
+    private boolean mIsUserSelected = false;
 
-    NetworkAgent mNetworkAgent;
-    WifiMetrics mWifiMetrics;
-    WifiInfo mWifiInfo;
-    WifiNative mWifiNative;
-    WifiThreadRunner mWifiThreadRunner;
-    DeviceConfigFacade mDeviceConfigFacade;
-    Handler mHandler;
-    FrameworkFacade mFrameworkFacade;
+    @Nullable
+    private WifiNetworkAgent mNetworkAgent;
+    private final WifiMetrics mWifiMetrics;
+    private final ExtendedWifiInfo mWifiInfo;
+    private final WifiNative mWifiNative;
+    private final WifiThreadRunner mWifiThreadRunner;
+    private final DeviceConfigFacade mDeviceConfigFacade;
+    private final ExternalScoreUpdateObserverProxy mExternalScoreUpdateObserverProxy;
+    private final WifiInfo mWifiInfoNoReset;
 
     /**
-     * Callback proxy. See {@link android.net.wifi.WifiManager.ScoreUpdateObserver}.
+     * Callback from {@link ExternalScoreUpdateObserverProxy}
      */
-    private class ScoreUpdateObserverProxy extends IScoreUpdateObserver.Stub {
+    private class ScoreUpdateObserverProxy implements WifiManager.ScoreUpdateObserver {
         @Override
         public void notifyScoreUpdate(int sessionId, int score) {
-            mWifiThreadRunner.post(() -> {
-                if (mWifiConnectedNetworkScorerHolder == null
-                        || sessionId == INVALID_SESSION_ID
-                        || sessionId != getCurrentSessionId()) {
-                    Log.w(TAG, "Ignoring stale/invalid external score"
-                             + " sessionId=" + sessionId
-                             + " currentSessionId=" + getCurrentSessionId()
-                             + " score=" + score);
-                    return;
-                }
-                long millis = mClock.getWallClockMillis();
-                if (score < ConnectedScore.WIFI_TRANSITION_SCORE) {
-                    if (mLegacyIntScore >= ConnectedScore.WIFI_TRANSITION_SCORE) {
-                        mLastScoreBreachLowTimeMillis = millis;
-                    }
-                } else {
-                    mLastScoreBreachLowTimeMillis = INVALID_WALL_CLOCK_MILLIS;
-                }
-                if (score > ConnectedScore.WIFI_TRANSITION_SCORE) {
-                    if (mLegacyIntScore <= ConnectedScore.WIFI_TRANSITION_SCORE) {
-                        mLastScoreBreachHighTimeMillis = millis;
-                    }
-                } else {
-                    mLastScoreBreachHighTimeMillis = INVALID_WALL_CLOCK_MILLIS;
-                }
-                reportNetworkScoreToConnectivityServiceIfNecessary(score);
+            if (mWifiConnectedNetworkScorerHolder == null
+                    || sessionId == INVALID_SESSION_ID
+                    || sessionId != getCurrentSessionId()) {
+                Log.w(TAG, "Ignoring stale/invalid external score"
+                        + " sessionId=" + sessionId
+                        + " currentSessionId=" + getCurrentSessionId()
+                        + " score=" + score);
+                return;
+            }
+            long millis = mClock.getWallClockMillis();
+            if (SdkLevel.isAtLeastS()) {
                 mLegacyIntScore = score;
-                updateWifiMetrics(millis, -1, mLegacyIntScore);
-            });
+                updateWifiMetrics(millis, -1);
+                return;
+            }
+            if (score < ConnectedScore.WIFI_TRANSITION_SCORE) {
+                if (mLegacyIntScore >= ConnectedScore.WIFI_TRANSITION_SCORE) {
+                    mLastScoreBreachLowTimeMillis = millis;
+                }
+            } else {
+                mLastScoreBreachLowTimeMillis = INVALID_WALL_CLOCK_MILLIS;
+            }
+            if (score > ConnectedScore.WIFI_TRANSITION_SCORE) {
+                if (mLegacyIntScore <= ConnectedScore.WIFI_TRANSITION_SCORE) {
+                    mLastScoreBreachHighTimeMillis = millis;
+                }
+            } else {
+                mLastScoreBreachHighTimeMillis = INVALID_WALL_CLOCK_MILLIS;
+            }
+            mLegacyIntScore = score;
+            reportNetworkScoreToConnectivityServiceIfNecessary();
+            updateWifiMetrics(millis, -1);
         }
 
         @Override
         public void triggerUpdateOfWifiUsabilityStats(int sessionId) {
-            mWifiThreadRunner.post(() -> {
-                if (mWifiConnectedNetworkScorerHolder == null
-                        || sessionId == INVALID_SESSION_ID
-                        || sessionId != getCurrentSessionId()
-                        || mInterfaceName == null) {
-                    Log.w(TAG, "Ignoring triggerUpdateOfWifiUsabilityStats"
-                             + " sessionId=" + sessionId
-                             + " currentSessionId=" + getCurrentSessionId()
-                             + " interfaceName=" + mInterfaceName);
-                    return;
+            if (mWifiConnectedNetworkScorerHolder == null
+                    || sessionId == INVALID_SESSION_ID
+                    || sessionId != getCurrentSessionId()
+                    || mInterfaceName == null) {
+                Log.w(TAG, "Ignoring triggerUpdateOfWifiUsabilityStats"
+                        + " sessionId=" + sessionId
+                        + " currentSessionId=" + getCurrentSessionId()
+                        + " interfaceName=" + mInterfaceName);
+                return;
+            }
+            WifiLinkLayerStats stats = mWifiNative.getWifiLinkLayerStats(mInterfaceName);
+
+            // update mWifiInfo
+            // TODO(b/153075963): Better coordinate this class and ClientModeImpl to remove
+            // redundant codes below and in ClientModeImpl#fetchRssiLinkSpeedAndFrequencyNative.
+            WifiNl80211Manager.SignalPollResult pollResult =
+                    mWifiNative.signalPoll(mInterfaceName);
+            if (pollResult != null) {
+                int newRssi = pollResult.currentRssiDbm;
+                int newTxLinkSpeed = pollResult.txBitrateMbps;
+                int newFrequency = pollResult.associationFrequencyMHz;
+                int newRxLinkSpeed = pollResult.rxBitrateMbps;
+
+                if (newRssi > WifiInfo.INVALID_RSSI && newRssi < WifiInfo.MAX_RSSI) {
+                    if (newRssi > (WifiInfo.INVALID_RSSI + 256)) {
+                        Log.wtf(TAG, "Error! +ve value RSSI: " + newRssi);
+                        newRssi -= 256;
+                    }
+                    mWifiInfo.setRssi(newRssi);
+                } else {
+                    mWifiInfo.setRssi(WifiInfo.INVALID_RSSI);
                 }
-                WifiLinkLayerStats stats = mWifiNative.getWifiLinkLayerStats(mInterfaceName);
-
-                // update mWifiInfo
-                // TODO(b/153075963): Better coordinate this class and ClientModeImpl to remove
-                // redundant codes below and in ClientModeImpl#fetchRssiLinkSpeedAndFrequencyNative.
-                WifiNl80211Manager.SignalPollResult pollResult =
-                        mWifiNative.signalPoll(mInterfaceName);
-                if (pollResult != null) {
-                    int newRssi = pollResult.currentRssiDbm;
-                    int newTxLinkSpeed = pollResult.txBitrateMbps;
-                    int newFrequency = pollResult.associationFrequencyMHz;
-                    int newRxLinkSpeed = pollResult.rxBitrateMbps;
-
-                    if (newRssi > WifiInfo.INVALID_RSSI && newRssi < WifiInfo.MAX_RSSI) {
-                        if (newRssi > (WifiInfo.INVALID_RSSI + 256)) {
-                            Log.wtf(TAG, "Error! +ve value RSSI: " + newRssi);
-                            newRssi -= 256;
-                        }
-                        mWifiInfo.setRssi(newRssi);
-                    } else {
-                        mWifiInfo.setRssi(WifiInfo.INVALID_RSSI);
-                    }
-                    /*
-                     * set Tx link speed only if it is valid
-                     */
-                    if (newTxLinkSpeed > 0) {
-                        mWifiInfo.setLinkSpeed(newTxLinkSpeed);
-                        mWifiInfo.setTxLinkSpeedMbps(newTxLinkSpeed);
-                    }
-                    /*
-                     * set Rx link speed only if it is valid
-                     */
-                    if (newRxLinkSpeed > 0) {
-                        mWifiInfo.setRxLinkSpeedMbps(newRxLinkSpeed);
-                    }
-                    if (newFrequency > 0) {
-                        mWifiInfo.setFrequency(newFrequency);
-                    }
+                /*
+                 * set Tx link speed only if it is valid
+                 */
+                if (newTxLinkSpeed > 0) {
+                    mWifiInfo.setLinkSpeed(newTxLinkSpeed);
+                    mWifiInfo.setTxLinkSpeedMbps(newTxLinkSpeed);
                 }
+                /*
+                 * set Rx link speed only if it is valid
+                 */
+                if (newRxLinkSpeed > 0) {
+                    mWifiInfo.setRxLinkSpeedMbps(newRxLinkSpeed);
+                }
+                if (newFrequency > 0) {
+                    mWifiInfo.setFrequency(newFrequency);
+                }
+            }
 
-                // TODO(b/153075963): This should not be plumbed through WifiMetrics
-                mWifiMetrics.updateWifiUsabilityStatsEntries(mWifiInfo, stats);
-            });
+            // TODO(b/153075963): This should not be plumbed through WifiMetrics
+            mWifiMetrics.updateWifiUsabilityStatsEntries(mInterfaceName, mWifiInfo, stats);
         }
+
+        @Override
+        public void notifyStatusUpdate(int sessionId, boolean isUsable) {
+            if (mWifiConnectedNetworkScorerHolder == null
+                    || sessionId == INVALID_SESSION_ID
+                    || sessionId != getCurrentSessionId()) {
+                Log.w(TAG, "Ignoring stale/invalid external status"
+                        + " sessionId=" + sessionId
+                        + " currentSessionId=" + getCurrentSessionId()
+                        + " isUsable=" + isUsable);
+                return;
+            }
+            if (mNetworkAgent == null) {
+                return;
+            }
+            if (mShouldReduceNetworkScore) {
+                return;
+            }
+            mIsUsable = isUsable;
+            // Wifi is set to be usable if adaptive connectivity is disabled.
+            if (!mAdaptiveConnectivityEnabledSettingObserver.get()
+                    || !mWifiSettingsStore.isWifiScoringEnabled()) {
+                mIsUsable = true;
+                if (mVerboseLoggingEnabled) {
+                    Log.d(TAG, "Wifi scoring disabled - Notify that Wifi is usable");
+                }
+            }
+            // Send `exiting` to NetworkScore, but don't update and send mLegacyIntScore
+            // and don't change any other fields. All we want to do is relay to ConnectivityService
+            // whether the current network is usable.
+            if (SdkLevel.isAtLeastS()) {
+                mNetworkAgent.sendNetworkScore(
+                        getScoreBuilder()
+                                .setLegacyInt(mLegacyIntScore)
+                                .setExiting(!mIsUsable)
+                                .build());
+            } else  {
+                mNetworkAgent.sendNetworkScore(mIsUsable ? ConnectedScore.WIFI_TRANSITION_SCORE + 1
+                        : ConnectedScore.WIFI_TRANSITION_SCORE - 1);
+            }
+        }
+
+        @Override
+        public void requestNudOperation(int sessionId) {
+            if (mWifiConnectedNetworkScorerHolder == null
+                    || sessionId == INVALID_SESSION_ID
+                    || sessionId != getCurrentSessionId()) {
+                Log.w(TAG, "Ignoring stale/invalid external input for NUD triggering"
+                        + " sessionId=" + sessionId
+                        + " currentSessionId=" + getCurrentSessionId());
+                return;
+            }
+            if (!mAdaptiveConnectivityEnabledSettingObserver.get()
+                    || !mWifiSettingsStore.isWifiScoringEnabled()) {
+                if (mVerboseLoggingEnabled) {
+                    Log.d(TAG, "Wifi scoring disabled - Cannot request a NUD operation");
+                }
+                return;
+            }
+            mWifiConnectedNetworkScorerHolder.setShouldCheckIpLayerOnce(true);
+        }
+
+        @Override
+        public void blocklistCurrentBssid(int sessionId) {
+            if (mWifiConnectedNetworkScorerHolder == null
+                    || sessionId == INVALID_SESSION_ID
+                    || sessionId != mSessionIdNoReset) {
+                Log.w(TAG, "Ignoring stale/invalid external input for blocklisting"
+                        + " sessionId=" + sessionId
+                        + " mSessionIdNoReset=" + mSessionIdNoReset);
+                return;
+            }
+            if (!mAdaptiveConnectivityEnabledSettingObserver.get()
+                    || !mWifiSettingsStore.isWifiScoringEnabled()) {
+                if (mVerboseLoggingEnabled) {
+                    Log.d(TAG, "Wifi scoring disabled - Cannot blocklist current BSSID");
+                }
+                return;
+            }
+            if (mWifiInfoNoReset.getBSSID() != null) {
+                mWifiBlocklistMonitor.handleBssidConnectionFailure(mWifiInfoNoReset.getBSSID(),
+                        mWifiInfoNoReset.getSSID(),
+                        WifiBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE,
+                        mWifiInfoNoReset.getRssi());
+            }
+        }
+    }
+
+    /**
+     * If true, put the WifiScoreReport in lingering mode. A very low score is reported to
+     * NetworkAgent, and the real score is never reported.
+     */
+    public void setShouldReduceNetworkScore(boolean shouldReduceNetworkScore) {
+        Log.d(TAG, "setShouldReduceNetworkScore=" + shouldReduceNetworkScore
+                + " mNetworkAgent is null? " + (mNetworkAgent == null));
+        mShouldReduceNetworkScore = shouldReduceNetworkScore;
+        // inform the external scorer that ongoing session has ended (since the score is no longer
+        // under their control)
+        if (mShouldReduceNetworkScore && mWifiConnectedNetworkScorerHolder != null) {
+            mWifiConnectedNetworkScorerHolder.stopSession();
+        }
+        // if set to true, send score below disconnect threshold to start lingering
+        sendNetworkScore();
     }
 
     /**
      * Report network score to connectivity service.
      */
-    private void reportNetworkScoreToConnectivityServiceIfNecessary(int score) {
+    private void reportNetworkScoreToConnectivityServiceIfNecessary() {
         if (mNetworkAgent == null) {
             return;
         }
-        if (mWifiConnectedNetworkScorerHolder == null && score == mWifiInfo.getScore()) {
+        if (mWifiConnectedNetworkScorerHolder == null && mLegacyIntScore == mWifiInfo.getScore()) {
+            return;
+        }
+        // only send network score if not lingering. If lingering, would have already sent score at
+        // start of lingering.
+        if (mShouldReduceNetworkScore) {
             return;
         }
         if (mWifiConnectedNetworkScorerHolder != null
                 && mContext.getResources().getBoolean(
-                        R.bool.config_wifiMinConfirmationDurationSendNetworkScoreEnabled)) {
+                        R.bool.config_wifiMinConfirmationDurationSendNetworkScoreEnabled)
+                /// Turn off hysteresis/dampening for shell commands.
+                && !mWifiConnectedNetworkScorerHolder.isShellCommandScorer()) {
             long millis = mClock.getWallClockMillis();
             if (mLastScoreBreachLowTimeMillis != INVALID_WALL_CLOCK_MILLIS) {
                 if (mWifiInfo.getRssi()
@@ -233,14 +359,14 @@
             }
         }
         // Stay a notch above the transition score if adaptive connectivity is disabled.
-        if (!mAdaptiveConnectivityEnabled) {
-            score = ConnectedScore.WIFI_TRANSITION_SCORE + 1;
+        if (!mAdaptiveConnectivityEnabledSettingObserver.get()
+                || !mWifiSettingsStore.isWifiScoringEnabled()) {
+            mLegacyIntScore = ConnectedScore.WIFI_TRANSITION_SCORE + 1;
+            if (mVerboseLoggingEnabled) {
+                Log.d(TAG, "Wifi scoring disabled - Stay a notch above the transition score");
+            }
         }
-        mNetworkAgent.sendNetworkScore(mScoreBuilder
-                .setLegacyInt(score)
-                .setTransportPrimary(true)
-                .setExiting(score < ConnectedScore.WIFI_TRANSITION_SCORE)
-                .build());
+        sendNetworkScore();
     }
 
     /**
@@ -250,6 +376,7 @@
         private final IBinder mBinder;
         private final IWifiConnectedNetworkScorer mScorer;
         private int mSessionId = INVALID_SESSION_ID;
+        private boolean mShouldCheckIpLayerOnce = false;
 
         WifiConnectedNetworkScorerHolder(IBinder binder, IWifiConnectedNetworkScorer scorer) {
             mBinder = binder;
@@ -287,7 +414,7 @@
         /**
          * Starts a new scoring session.
          */
-        public void startSession(int sessionId) {
+        public void startSession(int sessionId, boolean isUserSelected) {
             if (sessionId == INVALID_SESSION_ID) {
                 throw new IllegalArgumentException();
             }
@@ -301,8 +428,13 @@
                 return;
             }
             mSessionId = sessionId;
+            mSessionIdNoReset = sessionId;
             try {
-                mScorer.onStart(sessionId);
+                WifiConnectedSessionInfo sessionInfo =
+                        new WifiConnectedSessionInfo.Builder(sessionId)
+                                .setUserSelected(isUserSelected)
+                                .build();
+                mScorer.onStart(sessionInfo);
             } catch (RemoteException e) {
                 Log.e(TAG, "Unable to start Wifi connected network scorer " + this, e);
                 revertToDefaultConnectedScorer();
@@ -312,6 +444,7 @@
             final int sessionId = mSessionId;
             if (sessionId == INVALID_SESSION_ID) return;
             mSessionId = INVALID_SESSION_ID;
+            mShouldCheckIpLayerOnce = false;
             try {
                 mScorer.onStop(sessionId);
             } catch (RemoteException e) {
@@ -319,77 +452,55 @@
                 revertToDefaultConnectedScorer();
             }
         }
+
+        public boolean isShellCommandScorer() {
+            return mScorer instanceof WifiShellCommand.WifiScorer;
+        }
+
+        private void setShouldCheckIpLayerOnce(boolean shouldCheckIpLayerOnce) {
+            mShouldCheckIpLayerOnce = shouldCheckIpLayerOnce;
+        }
+
+        private boolean getShouldCheckIpLayerOnce() {
+            return mShouldCheckIpLayerOnce;
+        }
     }
 
-    private final ScoreUpdateObserverProxy mScoreUpdateObserver =
+    private final ScoreUpdateObserverProxy mScoreUpdateObserverCallback =
             new ScoreUpdateObserverProxy();
 
+    @Nullable
     private WifiConnectedNetworkScorerHolder mWifiConnectedNetworkScorerHolder;
 
-    /**
-     * Observer for adaptive connectivity enable settings changes.
-     * This is enabled by default. Will be toggled off via adb command or a settings
-     * toggle by the user to disable adaptive connectivity.
-     */
-    private class AdaptiveConnectivityEnabledSettingObserver extends ContentObserver {
-        AdaptiveConnectivityEnabledSettingObserver(Handler handler) {
-            super(handler);
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            super.onChange(selfChange);
-            mAdaptiveConnectivityEnabled = getValue();
-            Log.d(TAG, "Adaptive connectivity status changed: " + mAdaptiveConnectivityEnabled);
-            mWifiMetrics.setAdaptiveConnectivityState(mAdaptiveConnectivityEnabled);
-            mWifiMetrics.logUserActionEvent(
-                    mWifiMetrics.convertAdaptiveConnectivityStateToUserActionEventType(
-                            mAdaptiveConnectivityEnabled));
-        }
-
-        /**
-         * Register settings change observer.
-         */
-        public void initialize() {
-            Uri uri = Settings.Secure.getUriFor(SETTINGS_SECURE_ADAPTIVE_CONNECTIVITY_ENABLED);
-            if (uri == null) {
-                Log.e(TAG, "Adaptive connectivity user toggle does not exist in Settings");
-                return;
-            }
-            mFrameworkFacade.registerContentObserver(mContext, uri, true, this);
-            mAdaptiveConnectivityEnabled = mAdaptiveConnectivityEnabledSettingObserver.getValue();
-            mWifiMetrics.setAdaptiveConnectivityState(mAdaptiveConnectivityEnabled);
-        }
-
-        public boolean getValue() {
-            return mFrameworkFacade.getIntegerSetting(
-                    mContext, SETTINGS_SECURE_ADAPTIVE_CONNECTIVITY_ENABLED, 1) == 1;
-        }
-    }
-
     private final AdaptiveConnectivityEnabledSettingObserver
             mAdaptiveConnectivityEnabledSettingObserver;
-    private boolean mAdaptiveConnectivityEnabled = true;
 
     WifiScoreReport(ScoringParams scoringParams, Clock clock, WifiMetrics wifiMetrics,
-            WifiInfo wifiInfo, WifiNative wifiNative, BssidBlocklistMonitor bssidBlocklistMonitor,
-            WifiThreadRunner wifiThreadRunner, DeviceConfigFacade deviceConfigFacade,
-            Context context, Looper looper, FrameworkFacade frameworkFacade) {
+            ExtendedWifiInfo wifiInfo, WifiNative wifiNative,
+            WifiBlocklistMonitor wifiBlocklistMonitor,
+            WifiThreadRunner wifiThreadRunner, WifiScoreCard wifiScoreCard,
+            DeviceConfigFacade deviceConfigFacade, Context context,
+            AdaptiveConnectivityEnabledSettingObserver adaptiveConnectivityEnabledSettingObserver,
+            String interfaceName,
+            ExternalScoreUpdateObserverProxy externalScoreUpdateObserverProxy,
+            WifiSettingsStore wifiSettingsStore) {
         mScoringParams = scoringParams;
         mClock = clock;
+        mAdaptiveConnectivityEnabledSettingObserver = adaptiveConnectivityEnabledSettingObserver;
         mAggressiveConnectedScore = new AggressiveConnectedScore(scoringParams, clock);
         mVelocityBasedConnectedScore = new VelocityBasedConnectedScore(scoringParams, clock);
         mWifiMetrics = wifiMetrics;
         mWifiInfo = wifiInfo;
         mWifiNative = wifiNative;
-        mBssidBlocklistMonitor = bssidBlocklistMonitor;
+        mWifiBlocklistMonitor = wifiBlocklistMonitor;
         mWifiThreadRunner = wifiThreadRunner;
+        mWifiScoreCard = wifiScoreCard;
         mDeviceConfigFacade = deviceConfigFacade;
         mContext = context;
-        mFrameworkFacade = frameworkFacade;
-        mHandler = new Handler(looper);
-        mAdaptiveConnectivityEnabledSettingObserver =
-                new AdaptiveConnectivityEnabledSettingObserver(mHandler);
+        mInterfaceName = interfaceName;
+        mExternalScoreUpdateObserverProxy = externalScoreUpdateObserverProxy;
+        mWifiSettingsStore = wifiSettingsStore;
+        mWifiInfoNoReset = new WifiInfo(mWifiInfo);
     }
 
     /**
@@ -397,7 +508,8 @@
      */
     public void reset() {
         mSessionNumber++;
-        mLegacyIntScore = ConnectedScore.WIFI_MAX_SCORE;
+        mLegacyIntScore = ConnectedScore.WIFI_INITIAL_SCORE;
+        mIsUsable = true;
         mLastKnownNudCheckScore = ConnectedScore.WIFI_TRANSITION_SCORE;
         mAggressiveConnectedScore.reset();
         if (mVelocityBasedConnectedScore != null) {
@@ -445,9 +557,9 @@
         if (mWifiInfo.getScore() > ConnectedScore.WIFI_TRANSITION_SCORE
                 && score <= ConnectedScore.WIFI_TRANSITION_SCORE
                 && mWifiInfo.getSuccessfulTxPacketsPerSecond()
-                        >= mScoringParams.getYippeeSkippyPacketsPerSecond()
+                >= mScoringParams.getYippeeSkippyPacketsPerSecond()
                 && mWifiInfo.getSuccessfulRxPacketsPerSecond()
-                        >= mScoringParams.getYippeeSkippyPacketsPerSecond()
+                >= mScoringParams.getYippeeSkippyPacketsPerSecond()
         ) {
             score = ConnectedScore.WIFI_TRANSITION_SCORE + 1;
         }
@@ -486,10 +598,10 @@
             score = 0;
         }
 
-        //report score
-        reportNetworkScoreToConnectivityServiceIfNecessary(score);
-        updateWifiMetrics(millis, s2, score);
+        // report score
         mLegacyIntScore = score;
+        reportNetworkScoreToConnectivityServiceIfNecessary();
+        updateWifiMetrics(millis, s2);
     }
 
     private int getCurrentNetId() {
@@ -503,6 +615,11 @@
         return netId;
     }
 
+    @Nullable
+    private NetworkCapabilities getCurrentNetCapabilities() {
+        return mNetworkAgent == null ? null : mNetworkAgent.getCurrentNetworkCapabilities();
+    }
+
     private int getCurrentSessionId() {
         return sessionIdFromNetId(getCurrentNetId());
     }
@@ -520,20 +637,20 @@
         return (int) (((long) netId * 10 + (8 - (netId % 9))) % Integer.MAX_VALUE + 1);
     }
 
-    private void updateWifiMetrics(long now, int s2, int score) {
+    private void updateWifiMetrics(long now, int s2) {
         int netId = getCurrentNetId();
 
         mAggressiveConnectedScore.updateUsingWifiInfo(mWifiInfo, now);
         int s1 = mAggressiveConnectedScore.generateScore();
-        logLinkMetrics(now, netId, s1, s2, score);
+        logLinkMetrics(now, netId, s1, s2, mLegacyIntScore);
 
-        if (score != mWifiInfo.getScore()) {
+        if (mLegacyIntScore != mWifiInfo.getScore()) {
             if (mVerboseLoggingEnabled) {
-                Log.d(TAG, "report new wifi score " + score);
+                Log.d(TAG, "report new wifi score " + mLegacyIntScore);
             }
-            mWifiInfo.setScore(score);
+            mWifiInfo.setScore(mLegacyIntScore);
         }
-        mWifiMetrics.incrementWifiScoreCount(score);
+        mWifiMetrics.incrementWifiScoreCount(mInterfaceName, mLegacyIntScore);
     }
 
     private static final double TIME_CONSTANT_MILLIS = 30.0e+3;
@@ -553,11 +670,11 @@
      */
     public boolean shouldCheckIpLayer() {
         // Don't recommend if adaptive connectivity is disabled.
-        if (!mAdaptiveConnectivityEnabled) {
-            return false;
-        }
-        int nud = mScoringParams.getNudKnob();
-        if (nud == 0) {
+        if (!mAdaptiveConnectivityEnabledSettingObserver.get()
+                || !mWifiSettingsStore.isWifiScoringEnabled()) {
+            if (mVerboseLoggingEnabled) {
+                Log.d(TAG, "Wifi scoring disabled - Don't check IP layer");
+            }
             return false;
         }
         long millis = mClock.getWallClockMillis();
@@ -567,6 +684,17 @@
         if (deltaMillis < NUD_THROTTLE_MILLIS) {
             return false;
         }
+        if (SdkLevel.isAtLeastS() && mWifiConnectedNetworkScorerHolder != null) {
+            if (!mWifiConnectedNetworkScorerHolder.getShouldCheckIpLayerOnce()) {
+                return false;
+            }
+            mNudYes++;
+            return true;
+        }
+        int nud = mScoringParams.getNudKnob();
+        if (nud == 0) {
+            return false;
+        }
         // nextNudBreach is the bar the score needs to cross before we ask for NUD
         double nextNudBreach = ConnectedScore.WIFI_TRANSITION_SCORE;
         if (mWifiConnectedNetworkScorerHolder == null) {
@@ -600,6 +728,10 @@
         mLastKnownNudCheckTimeMillis = millis;
         mLastKnownNudCheckScore = mLegacyIntScore;
         mNudCount++;
+        // Make sure that only one NUD operation can be triggered.
+        if (mWifiConnectedNetworkScorerHolder != null) {
+            mWifiConnectedNetworkScorerHolder.setShouldCheckIpLayerOnce(false);
+        }
     }
 
     /**
@@ -624,20 +756,26 @@
         int freq = mWifiInfo.getFrequency();
         int txLinkSpeed = mWifiInfo.getLinkSpeed();
         int rxLinkSpeed = mWifiInfo.getRxLinkSpeedMbps();
+        WifiScoreCard.PerNetwork network = mWifiScoreCard.lookupNetwork(mWifiInfo.getSSID());
+        int txThroughputMbps = network.getTxLinkBandwidthKbps() / 1000;
+        int rxThroughputMbps = network.getRxLinkBandwidthKbps() / 1000;
         double txSuccessRate = mWifiInfo.getSuccessfulTxPacketsPerSecond();
         double txRetriesRate = mWifiInfo.getRetriedTxPacketsPerSecond();
         double txBadRate = mWifiInfo.getLostTxPacketsPerSecond();
         double rxSuccessRate = mWifiInfo.getSuccessfulRxPacketsPerSecond();
+        long totalBeaconRx = mWifiMetrics.getTotalBeaconRxCount();
         String s;
         try {
             String timestamp = new SimpleDateFormat("MM-dd HH:mm:ss.SSS").format(new Date(now));
             s = String.format(Locale.US, // Use US to avoid comma/decimal confusion
-                    "%s,%d,%d,%.1f,%.1f,%.1f,%d,%d,%d,%.2f,%.2f,%.2f,%.2f,%d,%d,%d,%d,%d",
+                    "%s,%d,%d,%.1f,%.1f,%.1f,%d,%d,%d,%d,%d,%d,%.2f,%.2f,%.2f,%.2f,%d,%d,%d,%d,%d",
                     timestamp, mSessionNumber, netId,
                     rssi, filteredRssi, rssiThreshold, freq, txLinkSpeed, rxLinkSpeed,
+                    txThroughputMbps, rxThroughputMbps, totalBeaconRx,
                     txSuccessRate, txRetriesRate, txBadRate, rxSuccessRate,
                     mNudYes, mNudCount,
                     s1, s2, score);
+
         } catch (Exception e) {
             Log.e(TAG, "format problem", e);
             return;
@@ -667,11 +805,14 @@
             history = new LinkedList<>(mLinkMetricsHistory);
         }
         pw.println("time,session,netid,rssi,filtered_rssi,rssi_threshold,freq,txLinkSpeed,"
-                + "rxLinkSpeed,tx_good,tx_retry,tx_bad,rx_pps,nudrq,nuds,s1,s2,score");
+                + "rxLinkSpeed,txTput,rxTput,bcnCnt,tx_good,tx_retry,tx_bad,rx_pps,nudrq,nuds,"
+                + "s1,s2,score");
         for (String line : history) {
             pw.println(line);
         }
         history.clear();
+        pw.println("externalScorerActive=" + (mWifiConnectedNetworkScorerHolder != null));
+        pw.println("mShouldReduceNetworkScore=" + mShouldReduceNetworkScore);
     }
 
     /**
@@ -694,20 +835,16 @@
         }
         mWifiConnectedNetworkScorerHolder = scorerHolder;
 
-        try {
-            scorer.onSetScoreUpdateObserver(mScoreUpdateObserver);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Unable to set score update observer " + scorer, e);
-            revertToDefaultConnectedScorer();
-            return false;
-        }
+        // Register to receive updates from external scorer.
+        mExternalScoreUpdateObserverProxy.registerCallback(mScoreUpdateObserverCallback);
+
         // Disable AOSP scorer
         mVelocityBasedConnectedScore = null;
         mWifiMetrics.setIsExternalWifiScorerOn(true);
         // If there is already a connection, start a new session
         final int netId = getCurrentNetId();
-        if (netId > 0) {
-            startConnectedNetworkScorer(netId);
+        if (netId > 0 && !mShouldReduceNetworkScore) {
+            startConnectedNetworkScorer(netId, mIsUserSelected);
         }
         return true;
     }
@@ -724,22 +861,56 @@
     }
 
     /**
+     * If this connection is not going to be the default route on the device when cellular is
+     * present, don't send this connection to external scorer for scoring (since scoring only makes
+     * sense if we need to score wifi vs cellular to determine the default network).
+     *
+     * Hence, we ignore local only or restricted wifi connections.
+     * @return true if the connection is local only or restricted, false otherwise.
+     */
+    private boolean isLocalOnlyOrRestrictedConnection() {
+        final NetworkCapabilities nc = getCurrentNetCapabilities();
+        if (nc == null) return false;
+        if (SdkLevel.isAtLeastS()) {
+            // restricted connection support only added in S.
+            if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID)
+                    || nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE)) {
+                // restricted connection.
+                Log.v(TAG, "Restricted connection, ignore.");
+                return true;
+            }
+        }
+        if (!nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
+            // local only connection.
+            Log.v(TAG, "Local only connection, ignore.");
+            return true;
+        }
+        return false;
+    }
+
+    /**
      * Start the registered Wi-Fi connected network scorer.
      * @param netId identifies the current android.net.Network
      */
-    public void startConnectedNetworkScorer(int netId) {
+    public void startConnectedNetworkScorer(int netId, boolean isUserSelected) {
+        mIsUserSelected = isUserSelected;
         final int sessionId = getCurrentSessionId();
         if (mWifiConnectedNetworkScorerHolder == null
                 || netId != getCurrentNetId()
+                || isLocalOnlyOrRestrictedConnection()
                 || sessionId == INVALID_SESSION_ID) {
             Log.w(TAG, "Cannot start external scoring"
                     + " netId=" + netId
                     + " currentNetId=" + getCurrentNetId()
+                    + " currentNetCapabilities=" + getCurrentNetCapabilities()
                     + " sessionId=" + sessionId);
             return;
         }
         mWifiInfo.setScore(ConnectedScore.WIFI_MAX_SCORE);
-        mWifiConnectedNetworkScorerHolder.startSession(sessionId);
+        mWifiConnectedNetworkScorerHolder.startSession(sessionId, mIsUserSelected);
+        mWifiInfoNoReset.setBSSID(mWifiInfo.getBSSID());
+        mWifiInfoNoReset.setSSID(mWifiInfo.getWifiSsid());
+        mWifiInfoNoReset.setRssi(mWifiInfo.getRssi());
         mLastScoreBreachLowTimeMillis = INVALID_WALL_CLOCK_MILLIS;
         mLastScoreBreachHighTimeMillis = INVALID_WALL_CLOCK_MILLIS;
     }
@@ -759,9 +930,9 @@
         if ((mLastScoreBreachLowTimeMillis != INVALID_WALL_CLOCK_MILLIS)
                 && ((millis - mLastScoreBreachLowTimeMillis)
                         >= MIN_TIME_TO_WAIT_BEFORE_BLOCKLIST_BSSID_MILLIS)) {
-            mBssidBlocklistMonitor.handleBssidConnectionFailure(mWifiInfo.getBSSID(),
+            mWifiBlocklistMonitor.handleBssidConnectionFailure(mWifiInfo.getBSSID(),
                     mWifiInfo.getSSID(),
-                    BssidBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE,
+                    WifiBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE,
                     mWifiInfo.getRssi());
             mLastScoreBreachLowTimeMillis = INVALID_WALL_CLOCK_MILLIS;
         }
@@ -770,41 +941,88 @@
     /**
      * Set NetworkAgent
      */
-    public void setNetworkAgent(NetworkAgent agent) {
+    public void setNetworkAgent(WifiNetworkAgent agent) {
+        WifiNetworkAgent oldAgent = mNetworkAgent;
         mNetworkAgent = agent;
+        // if mNetworkAgent was null previously, then the score wasn't sent to ConnectivityService.
+        // Send it now that the NetworkAgent has been set.
+        if (oldAgent == null && mNetworkAgent != null) {
+            sendNetworkScore();
+        }
     }
 
-    /**
-     * Get cached score
-     */
+    /** Get cached score */
     @VisibleForTesting
+    @RequiresApi(Build.VERSION_CODES.S)
     public NetworkScore getScore() {
-        return mScoreBuilder
-                .setLegacyInt(mLegacyIntScore)
-                .setTransportPrimary(true)
-                .setExiting(mLegacyIntScore < ConnectedScore.WIFI_TRANSITION_SCORE)
-                .build();
+        return getScoreBuilder().build();
     }
 
-    /**
-     * Set interface name
-     * @param ifaceName
-     */
-    public void setInterfaceName(String ifaceName) {
-        mInterfaceName = ifaceName;
+    @RequiresApi(Build.VERSION_CODES.S)
+    private NetworkScore.Builder getScoreBuilder() {
+        // We should force keep connected for a MBB CMM which is not lingering.
+        boolean shouldForceKeepConnected =
+                mCurrentRole == ActiveModeManager.ROLE_CLIENT_SECONDARY_TRANSIENT
+                        && !mShouldReduceNetworkScore;
+        int keepConnectedReason =
+                shouldForceKeepConnected
+                        ? NetworkScore.KEEP_CONNECTED_FOR_HANDOVER
+                        : NetworkScore.KEEP_CONNECTED_NONE;
+        boolean exiting = SdkLevel.isAtLeastS() && mWifiConnectedNetworkScorerHolder != null
+                ? !mIsUsable : mLegacyIntScore < ConnectedScore.WIFI_TRANSITION_SCORE;
+        return new NetworkScore.Builder()
+                .setLegacyInt(mShouldReduceNetworkScore ? LINGERING_SCORE : mLegacyIntScore)
+                .setTransportPrimary(mCurrentRole == ActiveModeManager.ROLE_CLIENT_PRIMARY)
+                .setExiting(exiting)
+                .setKeepConnectedReason(keepConnectedReason);
+    }
+
+    /** Get legacy int score. */
+    @VisibleForTesting
+    public int getLegacyIntScore() {
+        // When S Wifi module is run on R:
+        // - mShouldReduceNetworkScore is useless since MBB doesn't exist on R, so there isn't any
+        //   forced lingering.
+        // - mIsUsable can't be set as notifyStatusUpdate() for external scorer didn't exist on R
+        //   SDK (assume that only R platform + S Wifi module + R external scorer is possible,
+        //   and R platform + S Wifi module + S external scorer is not possible)
+        // Thus, it's ok to return the raw int score on R.
+        return mLegacyIntScore;
+    }
+
+    /** Get counts when we voted for a NUD. */
+    @VisibleForTesting
+    public int getNudYes() {
+        return mNudYes;
     }
 
     private void revertToDefaultConnectedScorer() {
         Log.d(TAG, "Using VelocityBasedConnectedScore");
         mVelocityBasedConnectedScore = new VelocityBasedConnectedScore(mScoringParams, mClock);
         mWifiConnectedNetworkScorerHolder = null;
+        mExternalScoreUpdateObserverProxy.unregisterCallback(mScoreUpdateObserverCallback);
         mWifiMetrics.setIsExternalWifiScorerOn(false);
     }
 
     /**
-     * Initialize WifiScoreReport
+     * This is a function of {@link #mCurrentRole} {@link #mShouldReduceNetworkScore}, and
+     * {@link #mLegacyIntScore}, and should be called when any of them changes.
      */
-    public void initialize() {
-        mAdaptiveConnectivityEnabledSettingObserver.initialize();
+    private void sendNetworkScore() {
+        if (mNetworkAgent == null) {
+            return;
+        }
+        if (SdkLevel.isAtLeastS()) {
+            // NetworkScore was introduced in S
+            mNetworkAgent.sendNetworkScore(getScore());
+        } else {
+            mNetworkAgent.sendNetworkScore(getLegacyIntScore());
+        }
+    }
+
+    /** Called when the owner {@link ConcreteClientModeManager}'s role changes. */
+    public void onRoleChanged(@Nullable ClientRole role) {
+        mCurrentRole = role;
+        sendNetworkScore();
     }
 }
diff --git a/service/java/com/android/server/wifi/WifiService.java b/service/java/com/android/server/wifi/WifiService.java
index 0197246..3ebbb98 100644
--- a/service/java/com/android/server/wifi/WifiService.java
+++ b/service/java/com/android/server/wifi/WifiService.java
@@ -16,16 +16,10 @@
 
 package com.android.server.wifi;
 
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
 import android.content.Context;
 import android.util.Log;
 
 import com.android.server.SystemService;
-import com.android.server.wifi.util.WifiAsyncChannel;
-
-import java.util.ArrayList;
-import java.util.List;
 
 /**
  * Service implementing Wi-Fi functionality. Delegates actual interface
@@ -46,8 +40,7 @@
         super(contextBase);
         mWifiContext = new WifiContext(contextBase);
         WifiInjector injector = new WifiInjector(mWifiContext);
-        WifiAsyncChannel channel =  new WifiAsyncChannel(TAG);
-        mImpl = new WifiServiceImpl(mWifiContext, injector, channel);
+        mImpl = new WifiServiceImpl(mWifiContext, injector);
     }
 
     @Override
@@ -59,7 +52,6 @@
     @Override
     public void onBootPhase(int phase) {
         if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
-            createNotificationChannels(mWifiContext);
             mImpl.checkAndStartWifi();
         } else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
             mImpl.handleBootCompleted();
@@ -80,34 +72,4 @@
     public void onUserStopping(TargetUser user) {
         mImpl.handleUserStop(user.getUserHandle().getIdentifier());
     }
-
-    // Create notification channels used by wifi.
-    private static void createNotificationChannels(Context ctx) {
-        final NotificationManager nm = ctx.getSystemService(NotificationManager.class);
-        List<NotificationChannel> channelsList = new ArrayList<>();
-        final NotificationChannel networkStatusChannel = new NotificationChannel(
-                NOTIFICATION_NETWORK_STATUS,
-                ctx.getResources().getString(
-                        com.android.wifi.resources.R.string.notification_channel_network_status),
-                NotificationManager.IMPORTANCE_LOW);
-        channelsList.add(networkStatusChannel);
-
-        final NotificationChannel networkAlertsChannel = new NotificationChannel(
-                NOTIFICATION_NETWORK_ALERTS,
-                ctx.getResources().getString(
-                        com.android.wifi.resources.R.string.notification_channel_network_alerts),
-                NotificationManager.IMPORTANCE_HIGH);
-        networkAlertsChannel.setBlockable(true);
-        channelsList.add(networkAlertsChannel);
-
-        final NotificationChannel networkAvailable = new NotificationChannel(
-                NOTIFICATION_NETWORK_AVAILABLE,
-                ctx.getResources().getString(
-                        com.android.wifi.resources.R.string.notification_channel_network_available),
-                NotificationManager.IMPORTANCE_LOW);
-        networkAvailable.setBlockable(true);
-        channelsList.add(networkAvailable);
-
-        nm.createNotificationChannels(channelsList);
-    }
 }
diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java
index 7daca08..f7a51a5 100644
--- a/service/java/com/android/server/wifi/WifiServiceImpl.java
+++ b/service/java/com/android/server/wifi/WifiServiceImpl.java
@@ -27,8 +27,18 @@
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING;
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED;
 
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_LOCAL_ONLY;
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SECONDARY_LONG_LIVED;
+import static com.android.server.wifi.ClientModeImpl.RESET_SIM_REASON_DEFAULT_DATA_SIM_CHANGED;
+import static com.android.server.wifi.ClientModeImpl.RESET_SIM_REASON_SIM_INSERTED;
+import static com.android.server.wifi.ClientModeImpl.RESET_SIM_REASON_SIM_REMOVED;
+import static com.android.server.wifi.SelfRecovery.REASON_API_CALL;
+import static com.android.server.wifi.WifiConfigurationUtil.addSecurityTypeToNetworkId;
+import static com.android.server.wifi.WifiConfigurationUtil.convertWifiInfoSecurityTypeToWifiConfiguration;
+import static com.android.server.wifi.WifiConfigurationUtil.removeSecurityTypeFromNetworkId;
 import static com.android.server.wifi.WifiSettingsConfigStore.WIFI_VERBOSE_LOGGING_ENABLED;
 
+import android.Manifest;
 import android.annotation.CheckResult;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -42,7 +52,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
 import android.net.DhcpInfo;
 import android.net.DhcpResultsParcelable;
@@ -52,7 +61,9 @@
 import android.net.NetworkStack;
 import android.net.Uri;
 import android.net.ip.IpClientUtil;
+import android.net.wifi.CoexUnsafeChannel;
 import android.net.wifi.IActionListener;
+import android.net.wifi.ICoexCallback;
 import android.net.wifi.IDppCallback;
 import android.net.wifi.ILocalOnlyHotspotCallback;
 import android.net.wifi.INetworkRequestMatchCallback;
@@ -60,21 +71,30 @@
 import android.net.wifi.IOnWifiUsabilityStatsListener;
 import android.net.wifi.IScanResultsCallback;
 import android.net.wifi.ISoftApCallback;
+import android.net.wifi.ISubsystemRestartCallback;
 import android.net.wifi.ISuggestionConnectionStatusListener;
+import android.net.wifi.ISuggestionUserApprovalStatusListener;
 import android.net.wifi.ITrafficStateCallback;
 import android.net.wifi.IWifiConnectedNetworkScorer;
+import android.net.wifi.IWifiVerboseLoggingStatusChangedListener;
 import android.net.wifi.ScanResult;
 import android.net.wifi.SoftApCapability;
 import android.net.wifi.SoftApConfiguration;
 import android.net.wifi.SoftApInfo;
 import android.net.wifi.WifiAnnotations.WifiStandard;
+import android.net.wifi.WifiAvailableChannel;
 import android.net.wifi.WifiClient;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
+import android.net.wifi.WifiManager.AddNetworkResult;
+import android.net.wifi.WifiManager.CoexRestriction;
 import android.net.wifi.WifiManager.DeviceMobilityState;
 import android.net.wifi.WifiManager.LocalOnlyHotspotCallback;
+import android.net.wifi.WifiManager.SapClientBlockedReason;
+import android.net.wifi.WifiManager.SapStartFailure;
 import android.net.wifi.WifiManager.SuggestionConnectionStatusListener;
+import android.net.wifi.WifiManager.WifiApState;
 import android.net.wifi.WifiNetworkSuggestion;
 import android.net.wifi.WifiScanner;
 import android.net.wifi.hotspot2.IProvisioningCallback;
@@ -86,11 +106,11 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
-import android.os.Message;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
 import android.os.PowerManager;
 import android.os.Process;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -103,21 +123,25 @@
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
-import android.util.MutableBoolean;
+
+import androidx.annotation.RequiresApi;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.AsyncChannel;
 import com.android.modules.utils.HandlerExecutor;
+import com.android.modules.utils.ParceledListSlice;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.Inet4AddressUtils;
+import com.android.server.wifi.coex.CoexManager;
 import com.android.server.wifi.hotspot2.PasspointManager;
 import com.android.server.wifi.hotspot2.PasspointProvider;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.UserActionEvent;
+import com.android.server.wifi.util.ActionListenerWrapper;
 import com.android.server.wifi.util.ApConfigUtil;
-import com.android.server.wifi.util.ExternalCallbackTracker;
+import com.android.server.wifi.util.GeneralUtil.Mutable;
+import com.android.server.wifi.util.LastCallerInfoManager;
 import com.android.server.wifi.util.RssiUtil;
 import com.android.server.wifi.util.ScanResultUtil;
-import com.android.server.wifi.util.WifiHandler;
 import com.android.server.wifi.util.WifiPermissionsUtil;
 import com.android.wifi.resources.R;
 
@@ -140,9 +164,9 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
@@ -153,14 +177,13 @@
  */
 public class WifiServiceImpl extends BaseWifiService {
     private static final String TAG = "WifiService";
-    private static final int APP_INFO_FLAGS_SYSTEM_APP =
-            ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
     private static final boolean VDBG = false;
 
     /** Max wait time for posting blocking runnables */
     private static final int RUN_WITH_SCISSORS_TIMEOUT_MILLIS = 4000;
+    @VisibleForTesting
+    static final int AUTO_DISABLE_SHOW_KEY_COUNTDOWN_MILLIS = 24 * 60 * 60 * 1000;
 
-    private final ClientModeImpl mClientModeImpl;
     private final ActiveModeWarden mActiveModeWarden;
     private final ScanRequestProxy mScanRequestProxy;
 
@@ -184,20 +207,19 @@
     /** Backup/Restore Module */
     private final WifiBackupRestore mWifiBackupRestore;
     private final SoftApBackupRestore mSoftApBackupRestore;
+    private final CoexManager mCoexManager;
     private final WifiNetworkSuggestionsManager mWifiNetworkSuggestionsManager;
     private final WifiConfigManager mWifiConfigManager;
     private final PasspointManager mPasspointManager;
     private final WifiLog mLog;
-    /**
-     * Verbose logging flag. Toggled by developer options.
-     */
-    private boolean mVerboseLoggingEnabled = false;
-
-    /**
-     * Asynchronous channel to ClientModeImpl
-     */
-    @VisibleForTesting
-    AsyncChannel mClientModeImplChannel;
+    private final WifiConnectivityManager mWifiConnectivityManager;
+    private final ConnectHelper mConnectHelper;
+    private final WifiGlobals mWifiGlobals;
+    private final WifiCarrierInfoManager mWifiCarrierInfoManager;
+    private @WifiManager.VerboseLoggingLevel int mVerboseLoggingLevel =
+            WifiManager.VERBOSE_LOGGING_LEVEL_DISABLED;
+    private final RemoteCallbackList<IWifiVerboseLoggingStatusChangedListener>
+            mRegisteredWifiLoggingStatusListeners = new RemoteCallbackList<>();
 
     private final FrameworkFacade mFrameworkFacade;
 
@@ -207,7 +229,9 @@
 
     private final LohsSoftApTracker mLohsSoftApTracker;
 
-    private WifiScanner mWifiScanner;
+    private final BuildProperties mBuildProperties;
+
+    private final DefaultClientModeManager mDefaultClientModeManager;
 
     /**
      * Callback for use with LocalOnlyHotspot to unregister requesting applications upon death.
@@ -226,46 +250,6 @@
     }
 
     /**
-     * Handles interaction with ClientModeImpl
-     */
-    private class ClientModeImplHandler extends WifiHandler {
-        private AsyncChannel mCmiChannel;
-
-        ClientModeImplHandler(String tag, Looper looper, AsyncChannel asyncChannel) {
-            super(tag, looper);
-            mCmiChannel = asyncChannel;
-            mCmiChannel.connect(mContext, this, mClientModeImpl.getHandler());
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            super.handleMessage(msg);
-            switch (msg.what) {
-                case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: {
-                    if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
-                        mClientModeImplChannel = mCmiChannel;
-                    } else {
-                        Log.e(TAG, "ClientModeImpl connection failure, error=" + msg.arg1);
-                        mClientModeImplChannel = null;
-                    }
-                    break;
-                }
-                case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
-                    Log.e(TAG, "ClientModeImpl channel lost, msg.arg1 =" + msg.arg1);
-                    mClientModeImplChannel = null;
-                    //Re-establish connection to state machine
-                    mCmiChannel.connect(mContext, this, mClientModeImpl.getHandler());
-                    break;
-                }
-                default: {
-                    Log.d(TAG, "ClientModeImplHandler.handleMessage ignoring msg=" + msg);
-                    break;
-                }
-            }
-        }
-    }
-
-    /**
      * Listen for phone call state events to get active data subcription id.
      */
     private class WifiPhoneStateListener extends PhoneStateListener {
@@ -275,14 +259,16 @@
 
         @Override
         public void onActiveDataSubscriptionIdChanged(int subId) {
-            Log.d(TAG, "OBSERVED active data subscription change, subId: " + subId);
-
-            mTetheredSoftApTracker.updateSoftApCapability(subId);
-            mActiveModeWarden.updateSoftApCapability(mTetheredSoftApTracker.getSoftApCapability());
+            // post operation to handler thread
+            mWifiThreadRunner.post(() -> {
+                Log.d(TAG, "OBSERVED active data subscription change, subId: " + subId);
+                mTetheredSoftApTracker.updateSoftApCapabilityWhenCarrierConfigChanged(subId);
+                mActiveModeWarden.updateSoftApCapability(
+                        mTetheredSoftApTracker.getSoftApCapability());
+            });
         }
     }
 
-    private final ClientModeImplHandler mClientModeImplHandler;
     private final WifiLockManager mWifiLockManager;
     private final WifiMulticastLockManager mWifiMulticastLockManager;
     private final DppManager mDppManager;
@@ -290,8 +276,48 @@
     private final WifiThreadRunner mWifiThreadRunner;
     private final MemoryStoreImpl mMemoryStoreImpl;
     private final WifiScoreCard mWifiScoreCard;
+    private final WifiHealthMonitor mWifiHealthMonitor;
+    private final WifiDataStall mWifiDataStall;
+    private final WifiNative mWifiNative;
+    private final SimRequiredNotifier mSimRequiredNotifier;
+    private final MakeBeforeBreakManager mMakeBeforeBreakManager;
+    private final LastCallerInfoManager mLastCallerInfoManager;
 
-    public WifiServiceImpl(Context context, WifiInjector wifiInjector, AsyncChannel asyncChannel) {
+    /**
+     * The wrapper of SoftApCallback is used in WifiService internally.
+     * see: {@code WifiManager.SoftApCallback}
+     */
+    public interface SoftApCallbackInternal {
+        /**
+         * see: {@code WifiManager.SoftApCallback#onStateChanged(int, int)}
+         */
+        default void onStateChanged(@WifiApState int state, @SapStartFailure int failureReason) {}
+
+        /**
+         * The callback which only is used in service internally and pass to WifiManager.
+         * It will base on the change to send corresponding callback as below:
+         * 1. onInfoChanged(SoftApInfo)
+         * 2. onInfoChanged(List<SoftApInfo>)
+         * 3. onConnectedClientsChanged(SoftApInfo, List<WifiClient>)
+         * 4. onConnectedClientsChanged(List<WifiClient>)
+         */
+        default void onConnectedClientsOrInfoChanged(Map<String, SoftApInfo> infos,
+                Map<String, List<WifiClient>> clients, boolean isBridged) {}
+
+        /**
+         * see: {@code WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)}
+         */
+        default void onCapabilityChanged(@NonNull SoftApCapability softApCapability) {}
+
+        /**
+         * see: {@code WifiManager.SoftApCallback#onBlockedClientConnecting(WifiClient, int)}
+         */
+        default void onBlockedClientConnecting(@NonNull WifiClient client,
+                @SapClientBlockedReason int blockedReason) {}
+    }
+
+
+    public WifiServiceImpl(Context context, WifiInjector wifiInjector) {
         mContext = context;
         mWifiInjector = wifiInjector;
         mClock = wifiInjector.getClock();
@@ -301,7 +327,6 @@
         mWifiTrafficPoller = mWifiInjector.getWifiTrafficPoller();
         mUserManager = mWifiInjector.getUserManager();
         mCountryCode = mWifiInjector.getWifiCountryCode();
-        mClientModeImpl = mWifiInjector.getClientModeImpl();
         mActiveModeWarden = mWifiInjector.getActiveModeWarden();
         mScanRequestProxy = mWifiInjector.getScanRequestProxy();
         mSettingsStore = mWifiInjector.getWifiSettingsStore();
@@ -309,8 +334,6 @@
         mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
         mWifiLockManager = mWifiInjector.getWifiLockManager();
         mWifiMulticastLockManager = mWifiInjector.getWifiMulticastLockManager();
-        mClientModeImplHandler = new ClientModeImplHandler(TAG,
-                mWifiInjector.getAsyncChannelHandlerThread().getLooper(), asyncChannel);
         mWifiBackupRestore = mWifiInjector.getWifiBackupRestore();
         mSoftApBackupRestore = mWifiInjector.getSoftApBackupRestore();
         mWifiApConfigStore = mWifiInjector.getWifiApConfigStore();
@@ -327,8 +350,21 @@
         mWifiConfigManager = mWifiInjector.getWifiConfigManager();
         mPasspointManager = mWifiInjector.getPasspointManager();
         mWifiScoreCard = mWifiInjector.getWifiScoreCard();
+        mWifiHealthMonitor = wifiInjector.getWifiHealthMonitor();
         mMemoryStoreImpl = new MemoryStoreImpl(mContext, mWifiInjector,
-                mWifiScoreCard,  mWifiInjector.getWifiHealthMonitor());
+                mWifiScoreCard,  mWifiHealthMonitor);
+        mWifiConnectivityManager = wifiInjector.getWifiConnectivityManager();
+        mWifiDataStall = wifiInjector.getWifiDataStall();
+        mWifiNative = wifiInjector.getWifiNative();
+        mCoexManager = wifiInjector.getCoexManager();
+        mConnectHelper = wifiInjector.getConnectHelper();
+        mWifiGlobals = wifiInjector.getWifiGlobals();
+        mSimRequiredNotifier = wifiInjector.getSimRequiredNotifier();
+        mWifiCarrierInfoManager = wifiInjector.getWifiCarrierInfoManager();
+        mMakeBeforeBreakManager = mWifiInjector.getMakeBeforeBreakManager();
+        mLastCallerInfoManager = mWifiInjector.getLastCallerInfoManager();
+        mBuildProperties = mWifiInjector.getBuildProperties();
+        mDefaultClientModeManager = mWifiInjector.getDefaultClientModeManager();
     }
 
     /**
@@ -344,25 +380,18 @@
             if (!mWifiConfigManager.loadFromStore()) {
                 Log.e(TAG, "Failed to load from config store");
             }
+            mWifiConfigManager.incrementNumRebootsSinceLastUse();
             // config store is read, check if verbose logging is enabled.
-            enableVerboseLoggingInternal(getVerboseLoggingLevel());
+            enableVerboseLoggingInternal(
+                    mWifiInjector.getSettingsConfigStore().get(WIFI_VERBOSE_LOGGING_ENABLED)
+                    ? 1 : 0);
             // Check if wi-fi needs to be enabled
             boolean wifiEnabled = mSettingsStore.isWifiToggleEnabled();
             Log.i(TAG,
                     "WifiService starting up with Wi-Fi " + (wifiEnabled ? "enabled" : "disabled"));
 
             mWifiInjector.getWifiScanAlwaysAvailableSettingsCompatibility().initialize();
-            mContext.registerReceiver(
-                    new BroadcastReceiver() {
-                        @Override
-                        public void onReceive(Context context, Intent intent) {
-                            if (mSettingsStore.handleAirplaneModeToggled()) {
-                                mActiveModeWarden.airplaneModeToggled();
-                            }
-                        }
-                    },
-                    new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
-
+            mWifiInjector.getWifiNotificationManager().createNotificationChannels();
             mContext.registerReceiver(
                     new BroadcastReceiver() {
                         @Override
@@ -371,8 +400,7 @@
                                     TelephonyManager.SIM_STATE_UNKNOWN);
                             if (TelephonyManager.SIM_STATE_ABSENT == state) {
                                 Log.d(TAG, "resetting networks because SIM was removed");
-                                mClientModeImpl.resetSimAuthNetworks(
-                                        ClientModeImpl.RESET_SIM_REASON_SIM_REMOVED);
+                                resetCarrierNetworks(RESET_SIM_REASON_SIM_REMOVED);
                             }
                         }
                     },
@@ -386,8 +414,7 @@
                                     TelephonyManager.SIM_STATE_UNKNOWN);
                             if (TelephonyManager.SIM_STATE_LOADED == state) {
                                 Log.d(TAG, "resetting networks because SIM was loaded");
-                                mClientModeImpl.resetSimAuthNetworks(
-                                        ClientModeImpl.RESET_SIM_REASON_SIM_INSERTED);
+                                resetCarrierNetworks(RESET_SIM_REASON_SIM_INSERTED);
                             }
                         }
                     },
@@ -402,23 +429,75 @@
                                     SubscriptionManager.INVALID_SUBSCRIPTION_ID);
                             if (subId != mLastSubId) {
                                 Log.d(TAG, "resetting networks as default data SIM is changed");
-                                mClientModeImpl.resetSimAuthNetworks(
-                                        ClientModeImpl.RESET_SIM_REASON_DEFAULT_DATA_SIM_CHANGED);
+                                resetCarrierNetworks(RESET_SIM_REASON_DEFAULT_DATA_SIM_CHANGED);
                                 mLastSubId = subId;
+                                mWifiThreadRunner.post(() -> {
+                                    mWifiDataStall.resetPhoneStateListener();
+                                });
                             }
                         }
                     },
                     new IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED));
 
+            mContext.registerReceiver(
+                    new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        String countryCode = intent.getStringExtra(
+                                TelephonyManager.EXTRA_NETWORK_COUNTRY);
+                        Log.d(TAG, "Country code changed to :" + countryCode);
+                        mCountryCode.setTelephonyCountryCodeAndUpdate(countryCode);
+                    }}, new IntentFilter(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED));
+            mContext.registerReceiver(
+                    new BroadcastReceiver() {
+                        @Override
+                        public void onReceive(Context context, Intent intent) {
+                            Log.d(TAG, "locale changed");
+                            resetNotificationManager();
+                        }}, new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
+
             // Adding optimizations of only receiving broadcasts when wifi is enabled
             // can result in race conditions when apps toggle wifi in the background
             // without active user involvement. Always receive broadcasts.
             registerForBroadcasts();
             mInIdleMode = mPowerManager.isDeviceIdleMode();
 
-            mClientModeImpl.initialize();
             mActiveModeWarden.start();
             registerForCarrierConfigChange();
+            mWifiInjector.getAdaptiveConnectivityEnabledSettingObserver().initialize();
+        });
+    }
+
+    private void resetCarrierNetworks(@ClientModeImpl.ResetSimReason int resetReason) {
+        mWifiThreadRunner.post(() -> {
+            Log.d(TAG, "resetting carrier networks since SIM was changed");
+            if (resetReason == RESET_SIM_REASON_SIM_INSERTED
+                    || resetReason == RESET_SIM_REASON_DEFAULT_DATA_SIM_CHANGED) {
+                // clear all SIM related notifications since some action was taken to address
+                // "missing" SIM issue
+                mSimRequiredNotifier.dismissSimRequiredNotification();
+            }
+            if (resetReason != RESET_SIM_REASON_SIM_INSERTED) {
+                mWifiConfigManager.resetSimNetworks();
+                mWifiNetworkSuggestionsManager.resetSimNetworkSuggestions();
+                mPasspointManager.resetSimPasspointNetwork();
+                mWifiConfigManager.stopRestrictingAutoJoinToSubscriptionId();
+            }
+
+            // do additional handling if we are current connected to a sim auth network
+            for (ClientModeManager cmm : mActiveModeWarden.getClientModeManagers()) {
+                cmm.resetSimAuthNetworks(resetReason);
+            }
+            mWifiNetworkSuggestionsManager.resetCarrierPrivilegedApps();
+            if (resetReason == RESET_SIM_REASON_SIM_INSERTED) {
+                // clear the blocklists in case any SIM based network were disabled due to the SIM
+                // not being available.
+                mWifiConfigManager.enableTemporaryDisabledNetworks();
+                mWifiConnectivityManager.forceConnectivityScan(ClientModeImpl.WIFI_WORK_SOURCE);
+            } else {
+                // Remove all ephemeral carrier networks keep subscriptionId update with SIM changes
+                mWifiConfigManager.removeEphemeralCarrierNetworks();
+            }
         });
     }
 
@@ -431,25 +510,64 @@
             intentFilter.addAction(Intent.ACTION_USER_REMOVED);
             intentFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
             intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
-            intentFilter.addAction(TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
             intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
             intentFilter.addAction(Intent.ACTION_SHUTDOWN);
-            boolean trackEmergencyCallState = mContext.getResources().getBoolean(
-                    R.bool.config_wifi_turn_off_during_emergency_call);
-            if (trackEmergencyCallState) {
-                intentFilter.addAction(TelephonyManager.ACTION_EMERGENCY_CALL_STATE_CHANGED);
-            }
-            mContext.registerReceiver(mReceiver, intentFilter);
+            mContext.registerReceiver(new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    String action = intent.getAction();
+                    if (action.equals(Intent.ACTION_USER_REMOVED)) {
+                        UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER);
+                        if (userHandle == null) {
+                            Log.e(TAG, "User removed broadcast received with no user handle");
+                            return;
+                        }
+                        mWifiThreadRunner.post(() -> mWifiConfigManager
+                                .removeNetworksForUser(userHandle.getIdentifier()));
+                    } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
+                        int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
+                                BluetoothAdapter.STATE_DISCONNECTED);
+                        boolean isConnected = state != BluetoothAdapter.STATE_DISCONNECTED;
+                        mWifiGlobals.setBluetoothConnected(isConnected);
+                        for (ClientModeManager cmm : mActiveModeWarden.getClientModeManagers()) {
+                            cmm.onBluetoothConnectionStateChanged();
+                        }
+                    } else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
+                        int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
+                                BluetoothAdapter.STATE_OFF);
+                        boolean isEnabled = state != BluetoothAdapter.STATE_OFF;
+                        mWifiGlobals.setBluetoothEnabled(isEnabled);
+                        for (ClientModeManager cmm : mActiveModeWarden.getClientModeManagers()) {
+                            cmm.onBluetoothConnectionStateChanged();
+                        }
+                    } else if (action.equals(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)) {
+                        handleIdleModeChanged();
+                    } else if (action.equals(Intent.ACTION_SHUTDOWN)) {
+                        handleShutDown();
+                    }
+                }
+            }, intentFilter);
             mMemoryStoreImpl.start();
             mPasspointManager.initializeProvisioner(
                     mWifiInjector.getPasspointProvisionerHandlerThread().getLooper());
-            mClientModeImpl.handleBootCompleted();
+            mWifiInjector.getWifiNetworkFactory().register();
+            mWifiInjector.getUntrustedWifiNetworkFactory().register();
+            mWifiInjector.getOemWifiNetworkFactory().register();
+            mWifiInjector.getWifiP2pConnection().handleBootCompleted();
+            // Start to listen country code change.
+            mCountryCode.registerListener(new CountryCodeListenerProxy());
+            mTetheredSoftApTracker.handleBootCompleted();
+            mWifiInjector.getSarManager().handleBootCompleted();
         });
     }
 
     public void handleUserSwitch(int userId) {
         Log.d(TAG, "Handle user switch " + userId);
-        mWifiThreadRunner.post(() -> mWifiConfigManager.handleUserSwitch(userId));
+
+        mWifiThreadRunner.post(() -> {
+            mWifiConfigManager.handleUserSwitch(userId);
+            resetNotificationManager();
+        });
     }
 
     public void handleUserUnlock(int userId) {
@@ -505,7 +623,7 @@
                 return false;
             }
         } catch (SecurityException e) {
-            Log.e(TAG, "Permission violation - startScan not allowed for"
+            Log.w(TAG, "Permission violation - startScan not allowed for"
                     + " uid=" + callingUid + ", packageName=" + packageName + ", reason=" + e);
             return false;
         } finally {
@@ -537,7 +655,7 @@
     public String getCurrentNetworkWpsNfcConfigurationToken() {
         // while CLs are in flight, return null here, will be removed (b/72423090)
         enforceNetworkStackPermission();
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("getCurrentNetworkWpsNfcConfigurationToken uid=%")
                     .c(Binder.getCallingUid()).flush();
         }
@@ -578,7 +696,7 @@
             // There is no explicit disconnection event in clientModeImpl during shutdown.
             // Call resetConnectionState() so that connection duration is calculated
             // before memory store write triggered by mMemoryStoreImpl.stop().
-            mWifiScoreCard.resetConnectionState();
+            mWifiScoreCard.resetAllConnectionStates();
             mMemoryStoreImpl.stop();
         });
     }
@@ -593,6 +711,11 @@
                 == PackageManager.PERMISSION_GRANTED;
     }
 
+    private boolean checkMainlineNetworkStackPermission(int pid, int uid) {
+        return mContext.checkPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, pid, uid)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
     private boolean checkNetworkStackPermission(int pid, int uid) {
         return mContext.checkPermission(android.Manifest.permission.NETWORK_STACK, pid, uid)
                 == PackageManager.PERMISSION_GRANTED;
@@ -611,7 +734,14 @@
         return checkNetworkSettingsPermission(pid, uid)
                 || checkNetworkSetupWizardPermission(pid, uid)
                 || checkNetworkStackPermission(pid, uid)
-                || checkNetworkManagedProvisioningPermission(pid, uid);
+                || checkNetworkManagedProvisioningPermission(pid, uid)
+                || isSignedWithPlatformKey(uid);
+    }
+
+    /** Whether the uid is signed with the same key as the platform. */
+    private boolean isSignedWithPlatformKey(int uid) {
+        return mContext.getPackageManager().checkSignatures(uid, Process.SYSTEM_UID)
+                == PackageManager.SIGNATURE_MATCH;
     }
 
     /**
@@ -623,23 +753,6 @@
                 || checkNetworkSetupWizardPermission(pid, uid);
     }
 
-    /** Helper method to check if the entity initiating the binder call is a system app. */
-    private boolean isSystem(String packageName, int uid) {
-        long ident = Binder.clearCallingIdentity();
-        try {
-            ApplicationInfo info = mContext.getPackageManager().getApplicationInfoAsUser(
-                    packageName, 0, UserHandle.getUserHandleForUid(uid));
-            return (info.flags & APP_INFO_FLAGS_SYSTEM_APP) != 0;
-        } catch (PackageManager.NameNotFoundException e) {
-            // In case of exception, assume unknown app (more strict checking)
-            // Note: This case will never happen since checkPackage is
-            // called to verify validity before checking App's version.
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-        return false;
-    }
-
     /** Helper method to check if the entity initiating the binder call is a DO/PO app. */
     private boolean isDeviceOrProfileOwner(int uid, String packageName) {
         return mWifiPermissionsUtil.isDeviceOwner(uid, packageName)
@@ -667,12 +780,6 @@
         }
     }
 
-    private void enforceNetworkStackOrSettingsPermission() {
-        enforceAnyPermissionOf(
-                android.Manifest.permission.NETWORK_SETTINGS,
-                NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
-    }
-
     private void enforceNetworkStackPermission() {
         // TODO(b/142554155): Only check for MAINLINE_NETWORK_STACK permission
         boolean granted = mContext.checkCallingOrSelfPermission(
@@ -690,6 +797,11 @@
                 "WifiService");
     }
 
+    private void enforceRestartWifiSubsystemPermission() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RESTART_WIFI_SUBSYSTEM,
+                "WifiService");
+    }
+
     /**
      * Checks whether the caller can change the wifi state.
      * Possible results:
@@ -741,7 +853,7 @@
         return mWifiPermissionsUtil.isTargetSdkLessThan(packageName, Build.VERSION_CODES.Q, uid)
                 || isPrivileged(pid, uid)
                 || isDeviceOrProfileOwner(uid, packageName)
-                || isSystem(packageName, uid)
+                || mWifiPermissionsUtil.isSystem(packageName, uid)
                 // TODO(b/140540984): Remove this bypass.
                 || mWifiPermissionsUtil.checkSystemAlertWindowPermission(uid, packageName);
     }
@@ -755,7 +867,17 @@
         return mWifiPermissionsUtil.isTargetSdkLessThan(packageName, Build.VERSION_CODES.R, uid)
                 || isPrivileged(pid, uid)
                 || isDeviceOrProfileOwner(uid, packageName)
-                || isSystem(packageName, uid);
+                || mWifiPermissionsUtil.isSystem(packageName, uid);
+    }
+
+    /**
+     * Get the current primary ClientModeManager in a thread safe manner, but blocks on the main
+     * Wifi thread.
+     */
+    private ClientModeManager getPrimaryClientModeManagerBlockingThreadSafe() {
+        return mWifiThreadRunner.call(
+                () -> mActiveModeWarden.getPrimaryClientModeManager(),
+                mDefaultClientModeManager);
     }
 
     /**
@@ -773,7 +895,7 @@
         if (!isPrivileged && !isDeviceOrProfileOwner(Binder.getCallingUid(), packageName)
                 && !mWifiPermissionsUtil.isTargetSdkLessThan(packageName, Build.VERSION_CODES.Q,
                   Binder.getCallingUid())
-                && !isSystem(packageName, Binder.getCallingUid())) {
+                && !mWifiPermissionsUtil.isSystem(packageName, Binder.getCallingUid())) {
             mLog.info("setWifiEnabled not allowed for uid=%")
                     .c(Binder.getCallingUid()).flush();
             return false;
@@ -805,16 +927,73 @@
             if (enable) {
                 mWifiMetrics.logUserActionEvent(UserActionEvent.EVENT_TOGGLE_WIFI_ON);
             } else {
-                WifiInfo wifiInfo = mClientModeImpl.syncRequestConnectionInfo();
+                WifiInfo wifiInfo =
+                        getPrimaryClientModeManagerBlockingThreadSafe().syncRequestConnectionInfo();
                 mWifiMetrics.logUserActionEvent(UserActionEvent.EVENT_TOGGLE_WIFI_OFF,
                         wifiInfo == null ? -1 : wifiInfo.getNetworkId());
             }
         }
         mWifiMetrics.incrementNumWifiToggles(isPrivileged, enable);
-        mActiveModeWarden.wifiToggled();
+        mActiveModeWarden.wifiToggled(new WorkSource(Binder.getCallingUid(), packageName));
+        mLastCallerInfoManager.put(LastCallerInfoManager.WIFI_ENABLED, Process.myTid(),
+                Binder.getCallingUid(), Binder.getCallingPid(), packageName, enable);
         return true;
     }
 
+    @RequiresApi(Build.VERSION_CODES.S)
+    @Override
+    public void registerSubsystemRestartCallback(ISubsystemRestartCallback callback) {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        enforceAccessPermission();
+        if (isVerboseLoggingEnabled()) {
+            mLog.info("registerSubsystemRestartCallback uid=%").c(Binder.getCallingUid()).flush();
+        }
+
+        mWifiThreadRunner.post(() -> {
+            if (!mActiveModeWarden.registerSubsystemRestartCallback(callback)) {
+                Log.e(TAG, "registerSubsystemRestartCallback: Failed to register callback");
+            }
+        });
+    }
+
+    @RequiresApi(Build.VERSION_CODES.S)
+    @Override
+    public void unregisterSubsystemRestartCallback(ISubsystemRestartCallback callback) {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        enforceAccessPermission();
+        if (isVerboseLoggingEnabled()) {
+            mLog.info("registerSubsystemRestartCallback uid=%").c(Binder.getCallingUid()).flush();
+        }
+        mWifiThreadRunner.post(() -> {
+            if (!mActiveModeWarden.unregisterSubsystemRestartCallback(callback)) {
+                Log.e(TAG, "unregisterSubsystemRestartCallback: Failed to register callback");
+            }
+        });
+    }
+
+    @RequiresApi(Build.VERSION_CODES.S)
+    @Override
+    public void restartWifiSubsystem() {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        enforceRestartWifiSubsystemPermission();
+        if (isVerboseLoggingEnabled()) {
+            mLog.info("restartWifiSubsystem uid=%").c(Binder.getCallingUid()).flush();
+        }
+        mWifiThreadRunner.post(() -> {
+            WifiInfo wifiInfo =
+                    mActiveModeWarden.getPrimaryClientModeManager().syncRequestConnectionInfo();
+            mWifiMetrics.logUserActionEvent(UserActionEvent.EVENT_RESTART_WIFI_SUB_SYSTEM,
+                    wifiInfo == null ? -1 : wifiInfo.getNetworkId());
+            mWifiInjector.getSelfRecovery().trigger(REASON_API_CALL);
+        });
+    }
+
     /**
      * see {@link WifiManager#getWifiState()}
      * @return One of {@link WifiManager#WIFI_STATE_DISABLED},
@@ -826,10 +1005,10 @@
     @Override
     public int getWifiEnabledState() {
         enforceAccessPermission();
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("getWifiEnabledState uid=%").c(Binder.getCallingUid()).flush();
         }
-        return mClientModeImpl.syncGetWifiState();
+        return getPrimaryClientModeManagerBlockingThreadSafe().syncGetWifiState();
     }
 
     /**
@@ -843,7 +1022,7 @@
     @Override
     public int getWifiApEnabledState() {
         enforceAccessPermission();
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("getWifiApEnabledState uid=%").c(Binder.getCallingUid()).flush();
         }
         return mTetheredSoftApTracker.getState();
@@ -866,19 +1045,91 @@
         // NETWORK_STACK is a signature only permission.
         enforceNetworkStackPermission();
         mLog.info("updateInterfaceIpState uid=%").c(Binder.getCallingUid()).flush();
-
         // hand off the work to our handler thread
         mWifiThreadRunner.post(() -> mLohsSoftApTracker.updateInterfaceIpState(ifaceName, mode));
     }
 
     /**
+     * see {@link WifiManager#isDefaultCoexAlgorithmEnabled()}
+     * @return {@code true} if the default coex algorithm is enabled. {@code false} otherwise.
+     */
+    @Override
+    public boolean isDefaultCoexAlgorithmEnabled() {
+        return mContext.getResources().getBoolean(R.bool.config_wifiDefaultCoexAlgorithmEnabled);
+    }
+
+    /**
+     * see {@link android.net.wifi.WifiManager#setCoexUnsafeChannels(List, int)}
+     * @param unsafeChannels List of {@link CoexUnsafeChannel} to avoid.
+     * @param restrictions Bitmap of {@link CoexRestriction} specifying the mandatory
+     *                     uses of the specified channels.
+     */
+    @Override
+    @RequiresApi(Build.VERSION_CODES.S)
+    public void setCoexUnsafeChannels(
+            @NonNull List<CoexUnsafeChannel> unsafeChannels, int restrictions) {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        mContext.enforceCallingOrSelfPermission(
+                Manifest.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS, "WifiService");
+        if (unsafeChannels == null) {
+            throw new IllegalArgumentException("unsafeChannels cannot be null");
+        }
+        if (mContext.getResources().getBoolean(R.bool.config_wifiDefaultCoexAlgorithmEnabled)) {
+            Log.e(TAG, "setCoexUnsafeChannels called but default coex algorithm is enabled");
+            return;
+        }
+        mWifiThreadRunner.post(() ->
+                mCoexManager.setCoexUnsafeChannels(unsafeChannels, restrictions));
+    }
+
+    /**
+     * See {@link WifiManager#registerCoexCallback(WifiManager.CoexCallback)}
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    public void registerCoexCallback(@NonNull ICoexCallback callback) {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        mContext.enforceCallingOrSelfPermission(
+                Manifest.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS, "WifiService");
+        if (callback == null) {
+            throw new IllegalArgumentException("callback must not be null");
+        }
+        if (isVerboseLoggingEnabled()) {
+            mLog.info("registerCoexCallback uid=%").c(Binder.getCallingUid()).flush();
+        }
+        mWifiThreadRunner.post(() -> mCoexManager.registerRemoteCoexCallback(callback));
+    }
+
+    /**
+     * See {@link WifiManager#unregisterCoexCallback(WifiManager.CoexCallback)}
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    public void unregisterCoexCallback(@NonNull ICoexCallback callback) {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        mContext.enforceCallingOrSelfPermission(
+                Manifest.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS, "WifiService");
+        if (callback == null) {
+            throw new IllegalArgumentException("callback must not be null");
+        }
+        if (isVerboseLoggingEnabled()) {
+            mLog.info("unregisterCoexCallback uid=%").c(Binder.getCallingUid()).flush();
+        }
+        mWifiThreadRunner.post(() -> mCoexManager.unregisterRemoteCoexCallback(callback));
+    }
+
+    /**
      * see {@link android.net.wifi.WifiManager#startSoftAp(WifiConfiguration)}
      * @param wifiConfig SSID, security and channel details as part of WifiConfiguration
      * @return {@code true} if softap start was triggered
      * @throws SecurityException if the caller does not have permission to start softap
      */
     @Override
-    public boolean startSoftAp(WifiConfiguration wifiConfig) {
+    public boolean startSoftAp(WifiConfiguration wifiConfig, String packageName) {
         // NETWORK_STACK is a signature only permission.
         enforceNetworkStackPermission();
 
@@ -897,43 +1148,21 @@
             return false;
         }
 
+        WorkSource requestorWs = new WorkSource(Binder.getCallingUid(), packageName);
         if (!mWifiThreadRunner.call(
-                () -> mActiveModeWarden.canRequestMoreSoftApManagers(), false)) {
+                () -> mActiveModeWarden.canRequestMoreSoftApManagers(requestorWs), false)) {
             // Take down LOHS if it is up.
             mLohsSoftApTracker.stopAll();
         }
 
         if (!startSoftApInternal(new SoftApModeConfiguration(
                 WifiManager.IFACE_IP_MODE_TETHERED, softApConfig,
-                mTetheredSoftApTracker.getSoftApCapability()))) {
+                mTetheredSoftApTracker.getSoftApCapability()), requestorWs)) {
             mTetheredSoftApTracker.setFailedWhileEnabling();
             return false;
         }
-
-        return true;
-    }
-
-    private boolean validateSoftApBand(int apBand) {
-        if (!ApConfigUtil.isBandValid(apBand)) {
-            mLog.err("Invalid SoftAp band. ").flush();
-            return false;
-        }
-
-        if (ApConfigUtil.containsBand(apBand, SoftApConfiguration.BAND_5GHZ)
-                && !is5GhzBandSupportedInternal()) {
-            mLog.err("Can not start softAp with 5GHz band, not supported.").flush();
-            return false;
-        }
-
-        if (ApConfigUtil.containsBand(apBand, SoftApConfiguration.BAND_6GHZ)) {
-            if (!is6GhzBandSupportedInternal()
-                    || !mContext.getResources().getBoolean(
-                            R.bool.config_wifiSoftap6ghzSupported)) {
-                mLog.err("Can not start softAp with 6GHz band, not supported.").flush();
-                return false;
-            }
-        }
-
+        mLastCallerInfoManager.put(LastCallerInfoManager.SOFT_AP, Process.myTid(),
+                Binder.getCallingUid(), Binder.getCallingPid(), packageName, true);
         return true;
     }
 
@@ -944,7 +1173,8 @@
      * @throws SecurityException if the caller does not have permission to start softap
      */
     @Override
-    public boolean startTetheredHotspot(@Nullable SoftApConfiguration softApConfig) {
+    public boolean startTetheredHotspot(@Nullable SoftApConfiguration softApConfig,
+            @NonNull String packageName) {
         // NETWORK_STACK is a signature only permission.
         enforceNetworkStackPermission();
 
@@ -955,19 +1185,21 @@
             return false;
         }
 
+        WorkSource requestorWs = new WorkSource(Binder.getCallingUid(), packageName);
         if (!mWifiThreadRunner.call(
-                () -> mActiveModeWarden.canRequestMoreSoftApManagers(), false)) {
+                () -> mActiveModeWarden.canRequestMoreSoftApManagers(requestorWs), false)) {
             // Take down LOHS if it is up.
             mLohsSoftApTracker.stopAll();
         }
 
         if (!startSoftApInternal(new SoftApModeConfiguration(
                 WifiManager.IFACE_IP_MODE_TETHERED, softApConfig,
-                mTetheredSoftApTracker.getSoftApCapability()))) {
+                mTetheredSoftApTracker.getSoftApCapability()), requestorWs)) {
             mTetheredSoftApTracker.setFailedWhileEnabling();
             return false;
         }
-
+        mLastCallerInfoManager.put(LastCallerInfoManager.TETHERED_HOTSPOT, Process.myTid(),
+                Binder.getCallingUid(), Binder.getCallingPid(), packageName, true);
         return true;
     }
 
@@ -975,7 +1207,7 @@
      * Internal method to start softap mode. Callers of this method should have already checked
      * proper permissions beyond the NetworkStack permission.
      */
-    private boolean startSoftApInternal(SoftApModeConfiguration apConfig) {
+    private boolean startSoftApInternal(SoftApModeConfiguration apConfig, WorkSource requestorWs) {
         int uid = Binder.getCallingUid();
         boolean privileged = isSettingsOrSuw(Binder.getCallingPid(), uid);
         mLog.trace("startSoftApInternal uid=% mode=%")
@@ -985,13 +1217,13 @@
         // AP config.
         SoftApConfiguration softApConfig = apConfig.getSoftApConfiguration();
         if (softApConfig != null
-                && (!WifiApConfigStore.validateApWifiConfiguration(softApConfig, privileged)
-                    || !validateSoftApBand(softApConfig.getBand()))) {
+                && (!WifiApConfigStore.validateApWifiConfiguration(
+                    softApConfig, privileged, mContext))) {
             Log.e(TAG, "Invalid SoftApConfiguration");
             return false;
         }
 
-        mActiveModeWarden.startSoftAp(apConfig);
+        mActiveModeWarden.startSoftAp(apConfig, requestorWs);
         return true;
     }
 
@@ -1011,6 +1243,8 @@
         mLog.info("stopSoftAp uid=%").c(Binder.getCallingUid()).flush();
 
         stopSoftApInternal(WifiManager.IFACE_IP_MODE_TETHERED);
+        mLastCallerInfoManager.put(LastCallerInfoManager.SOFT_AP, Process.myTid(),
+                Binder.getCallingUid(), Binder.getCallingPid(), "<unknown>", false);
         return true;
     }
 
@@ -1031,10 +1265,23 @@
         mActiveModeWarden.stopSoftAp(mode);
     }
 
+    private final class CountryCodeListenerProxy implements WifiCountryCode.ChangeListener {
+        @Override
+        public void onDriverCountryCodeChanged(String countryCode) {
+            // post operation to handler thread
+            mWifiThreadRunner.post(() -> {
+                Log.i(TAG, "onDriverCountryCodeChanged " + countryCode);
+                mTetheredSoftApTracker.updateAvailChannelListInSoftApCapability();
+                mActiveModeWarden.updateSoftApCapability(
+                        mTetheredSoftApTracker.getSoftApCapability());
+            });
+        }
+    }
+
     /**
      * SoftAp callback
      */
-    private final class TetheredSoftApTracker implements WifiManager.SoftApCallback {
+    private final class TetheredSoftApTracker implements SoftApCallbackInternal {
         /**
          * State of tethered SoftAP
          * One of:  {@link WifiManager#WIFI_AP_STATE_DISABLED},
@@ -1045,10 +1292,17 @@
          */
         private final Object mLock = new Object();
         private int mTetheredSoftApState = WIFI_AP_STATE_DISABLED;
-        private List<WifiClient> mTetheredSoftApConnectedClients = new ArrayList<>();
-        private SoftApInfo mTetheredSoftApInfo = new SoftApInfo();
+        private Map<String, List<WifiClient>> mTetheredSoftApConnectedClientsMap = new HashMap();
+        private Map<String, SoftApInfo> mTetheredSoftApInfoMap = new HashMap();
+        private boolean mIsBridgedMode = false;
         // TODO: We need to maintain two capability. One for LTE + SAP and one for WIFI + SAP
         private SoftApCapability mTetheredSoftApCapability = null;
+        private boolean mIsBootComplete = false;
+
+        public void handleBootCompleted() {
+            mIsBootComplete = true;
+            updateAvailChannelListInSoftApCapability();
+        }
 
         public int getState() {
             synchronized (mLock) {
@@ -1075,15 +1329,21 @@
             }
         }
 
-        public List<WifiClient> getConnectedClients() {
+        public Map<String, List<WifiClient>> getConnectedClients() {
             synchronized (mLock) {
-                return mTetheredSoftApConnectedClients;
+                return mTetheredSoftApConnectedClientsMap;
             }
         }
 
-        public SoftApInfo getSoftApInfo() {
+        public Map<String, SoftApInfo> getSoftApInfos() {
             synchronized (mLock) {
-                return mTetheredSoftApInfo;
+                return mTetheredSoftApInfoMap;
+            }
+        }
+
+        public boolean getIsBridgedMode() {
+            synchronized (mLock) {
+                return mIsBridgedMode;
             }
         }
 
@@ -1091,46 +1351,100 @@
             synchronized (mLock) {
                 if (mTetheredSoftApCapability == null) {
                     mTetheredSoftApCapability = ApConfigUtil.updateCapabilityFromResource(mContext);
+                    // Default country code
+                    mTetheredSoftApCapability = updateSoftApCapabilityWithAvailableChannelList(
+                            mTetheredSoftApCapability);
                 }
                 return mTetheredSoftApCapability;
             }
         }
 
-        public void updateSoftApCapability(int subId) {
-            synchronized (mLock) {
-                CarrierConfigManager carrierConfigManager =
-                        (CarrierConfigManager) mContext.getSystemService(
-                        Context.CARRIER_CONFIG_SERVICE);
-                if (carrierConfigManager == null) return;
-                PersistableBundle carrierConfig = carrierConfigManager.getConfigForSubId(subId);
-                if (carrierConfig == null) return;
-                int carrierMaxClient = carrierConfig.getInt(
-                        CarrierConfigManager.Wifi.KEY_HOTSPOT_MAX_CLIENT_COUNT);
-                int finalSupportedClientNumber = mContext.getResources().getInteger(
-                        R.integer.config_wifiHardwareSoftapMaxClientCount);
-                if (carrierMaxClient > 0) {
-                    finalSupportedClientNumber = Math.min(finalSupportedClientNumber,
-                            carrierMaxClient);
-                }
-                if (finalSupportedClientNumber == getSoftApCapability().getMaxSupportedClients()) {
-                    return;
-                }
-                mTetheredSoftApCapability.setMaxSupportedClients(
-                        finalSupportedClientNumber);
+        private SoftApCapability updateSoftApCapabilityWithAvailableChannelList(
+                @NonNull SoftApCapability softApCapability) {
+            SoftApCapability newSoftApCapability = new SoftApCapability(softApCapability);
+            if (!mIsBootComplete) {
+                // The available channel list is from wificond.
+                // It might be a failure or stuck during wificond init.
+                return newSoftApCapability;
             }
-            onCapabilityChanged(mTetheredSoftApCapability);
+            List<Integer> supportedChannelList = null;
+            if (ApConfigUtil.isSoftAp24GhzSupported(mContext)) {
+                supportedChannelList = ApConfigUtil.getAvailableChannelFreqsForBand(
+                    SoftApConfiguration.BAND_2GHZ, mWifiNative, mContext.getResources(), false);
+                if (supportedChannelList != null) {
+                    newSoftApCapability.setSupportedChannelList(
+                            SoftApConfiguration.BAND_2GHZ,
+                            supportedChannelList.stream().mapToInt(Integer::intValue).toArray());
+                }
+            }
+            if (ApConfigUtil.isSoftAp5GhzSupported(mContext)) {
+                supportedChannelList = ApConfigUtil.getAvailableChannelFreqsForBand(
+                        SoftApConfiguration.BAND_5GHZ, mWifiNative, mContext.getResources(), false);
+                if (supportedChannelList != null) {
+                    newSoftApCapability.setSupportedChannelList(
+                            SoftApConfiguration.BAND_5GHZ,
+                            supportedChannelList.stream().mapToInt(Integer::intValue).toArray());
+                }
+            }
+            if (ApConfigUtil.isSoftAp6GhzSupported(mContext)) {
+                supportedChannelList = ApConfigUtil.getAvailableChannelFreqsForBand(
+                        SoftApConfiguration.BAND_6GHZ, mWifiNative, mContext.getResources(), false);
+                if (supportedChannelList != null) {
+                    newSoftApCapability.setSupportedChannelList(
+                            SoftApConfiguration.BAND_6GHZ,
+                            supportedChannelList.stream().mapToInt(Integer::intValue).toArray());
+                }
+            }
+            if (ApConfigUtil.isSoftAp60GhzSupported(mContext)) {
+                supportedChannelList = ApConfigUtil.getAvailableChannelFreqsForBand(
+                        SoftApConfiguration.BAND_60GHZ, mWifiNative, mContext.getResources(),
+                        false);
+                if (supportedChannelList != null) {
+                    newSoftApCapability.setSupportedChannelList(
+                            SoftApConfiguration.BAND_60GHZ,
+                            supportedChannelList.stream().mapToInt(Integer::intValue).toArray());
+                }
+            }
+            return newSoftApCapability;
         }
 
-        private final ExternalCallbackTracker<ISoftApCallback> mRegisteredSoftApCallbacks =
-                new ExternalCallbackTracker<>(mClientModeImplHandler);
-
-        public boolean registerSoftApCallback(IBinder binder, ISoftApCallback callback,
-                int callbackIdentifier) {
-            return mRegisteredSoftApCallbacks.add(binder, callback, callbackIdentifier);
+        public void updateAvailChannelListInSoftApCapability() {
+            onCapabilityChanged(updateSoftApCapabilityWithAvailableChannelList(
+                    getSoftApCapability()));
         }
 
-        public void unregisterSoftApCallback(int callbackIdentifier) {
-            mRegisteredSoftApCallbacks.remove(callbackIdentifier);
+        public void updateSoftApCapabilityWhenCarrierConfigChanged(int subId) {
+            CarrierConfigManager carrierConfigManager =
+                    mContext.getSystemService(CarrierConfigManager.class);
+            if (carrierConfigManager == null) return;
+            PersistableBundle carrierConfig = carrierConfigManager.getConfigForSubId(subId);
+            if (carrierConfig == null) return;
+            int carrierMaxClient = carrierConfig.getInt(
+                    CarrierConfigManager.Wifi.KEY_HOTSPOT_MAX_CLIENT_COUNT);
+            int finalSupportedClientNumber = mContext.getResources().getInteger(
+                    R.integer.config_wifiHardwareSoftapMaxClientCount);
+            if (carrierMaxClient > 0) {
+                finalSupportedClientNumber = Math.min(finalSupportedClientNumber,
+                        carrierMaxClient);
+            }
+            if (finalSupportedClientNumber == getSoftApCapability().getMaxSupportedClients()) {
+                return;
+            }
+            SoftApCapability newSoftApCapability = new SoftApCapability(mTetheredSoftApCapability);
+            newSoftApCapability.setMaxSupportedClients(
+                    finalSupportedClientNumber);
+            onCapabilityChanged(newSoftApCapability);
+        }
+
+        private final RemoteCallbackList<ISoftApCallback> mRegisteredSoftApCallbacks =
+                new RemoteCallbackList<>();
+
+        public boolean registerSoftApCallback(ISoftApCallback callback) {
+            return mRegisteredSoftApCallbacks.register(callback);
+        }
+
+        public void unregisterSoftApCallback(ISoftApCallback callback) {
+            mRegisteredSoftApCallbacks.unregister(callback);
         }
 
         /**
@@ -1148,18 +1462,16 @@
                 mTetheredSoftApState = state;
             }
 
-            Iterator<ISoftApCallback> iterator =
-                    mRegisteredSoftApCallbacks.getCallbacks().iterator();
-            while (iterator.hasNext()) {
-                ISoftApCallback callback = iterator.next();
+            int itemCount = mRegisteredSoftApCallbacks.beginBroadcast();
+            for (int i = 0; i < itemCount; i++) {
                 try {
-                    callback.onStateChanged(state, failureReason);
+                    mRegisteredSoftApCallbacks.getBroadcastItem(i).onStateChanged(state,
+                            failureReason);
                 } catch (RemoteException e) {
                     Log.e(TAG, "onStateChanged: remote exception -- " + e);
-                    // TODO(b/138863863) remove does nothing, getCallbacks() returns a copy
-                    iterator.remove();
                 }
             }
+            mRegisteredSoftApCallbacks.finishBroadcast();
         }
 
         /**
@@ -1168,46 +1480,30 @@
          * @param clients connected clients to soft AP
          */
         @Override
-        public void onConnectedClientsChanged(List<WifiClient> clients) {
+        public void onConnectedClientsOrInfoChanged(Map<String, SoftApInfo> infos,
+                Map<String, List<WifiClient>> clients, boolean isBridged) {
             synchronized (mLock) {
-                mTetheredSoftApConnectedClients = new ArrayList<>(clients);
+                mIsBridgedMode = isBridged;
+                if (infos.size() == 0 && isBridged) {
+                    Log.d(TAG, "ShutDown bridged mode, clear isBridged cache in Service");
+                    mIsBridgedMode = false;
+                }
+                mTetheredSoftApConnectedClientsMap =
+                        ApConfigUtil.deepCopyForWifiClientListMap(clients);
+                mTetheredSoftApInfoMap = ApConfigUtil.deepCopyForSoftApInfoMap(infos);
             }
-
-            Iterator<ISoftApCallback> iterator =
-                    mRegisteredSoftApCallbacks.getCallbacks().iterator();
-            while (iterator.hasNext()) {
-                ISoftApCallback callback = iterator.next();
+            int itemCount = mRegisteredSoftApCallbacks.beginBroadcast();
+            for (int i = 0; i < itemCount; i++) {
                 try {
-                    callback.onConnectedClientsChanged(mTetheredSoftApConnectedClients);
+                    mRegisteredSoftApCallbacks.getBroadcastItem(i).onConnectedClientsOrInfoChanged(
+                            ApConfigUtil.deepCopyForSoftApInfoMap(mTetheredSoftApInfoMap),
+                            ApConfigUtil.deepCopyForWifiClientListMap(
+                                    mTetheredSoftApConnectedClientsMap), isBridged, false);
                 } catch (RemoteException e) {
-                    Log.e(TAG, "onConnectedClientsChanged: remote exception -- " + e);
-                    // TODO(b/138863863) remove does nothing, getCallbacks() returns a copy
-                    iterator.remove();
+                    Log.e(TAG, "onConnectedClientsOrInfoChanged: remote exception -- " + e);
                 }
             }
-        }
-
-        /**
-         * Called when information of softap changes.
-         *
-         * @param softApInfo is the softap information. {@link SoftApInfo}
-         */
-        @Override
-        public void onInfoChanged(SoftApInfo softApInfo) {
-            synchronized (mLock) {
-                mTetheredSoftApInfo = new SoftApInfo(softApInfo);
-            }
-
-            Iterator<ISoftApCallback> iterator =
-                    mRegisteredSoftApCallbacks.getCallbacks().iterator();
-            while (iterator.hasNext()) {
-                ISoftApCallback callback = iterator.next();
-                try {
-                    callback.onInfoChanged(mTetheredSoftApInfo);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "onInfoChanged: remote exception -- " + e);
-                }
-            }
+            mRegisteredSoftApCallbacks.finishBroadcast();
         }
 
         /**
@@ -1218,19 +1514,21 @@
         @Override
         public void onCapabilityChanged(SoftApCapability capability) {
             synchronized (mLock) {
+                if (Objects.equals(capability, mTetheredSoftApCapability)) {
+                    return;
+                }
                 mTetheredSoftApCapability = new SoftApCapability(capability);
             }
-
-            Iterator<ISoftApCallback> iterator =
-                    mRegisteredSoftApCallbacks.getCallbacks().iterator();
-            while (iterator.hasNext()) {
-                ISoftApCallback callback = iterator.next();
+            int itemCount = mRegisteredSoftApCallbacks.beginBroadcast();
+            for (int i = 0; i < itemCount; i++) {
                 try {
-                    callback.onCapabilityChanged(mTetheredSoftApCapability);
+                    mRegisteredSoftApCallbacks.getBroadcastItem(i).onCapabilityChanged(
+                            mTetheredSoftApCapability);
                 } catch (RemoteException e) {
                     Log.e(TAG, "onCapabiliyChanged: remote exception -- " + e);
                 }
             }
+            mRegisteredSoftApCallbacks.finishBroadcast();
         }
 
         /**
@@ -1242,23 +1540,23 @@
          */
         @Override
         public void onBlockedClientConnecting(WifiClient client, int blockedReason) {
-            Iterator<ISoftApCallback> iterator =
-                    mRegisteredSoftApCallbacks.getCallbacks().iterator();
-            while (iterator.hasNext()) {
-                ISoftApCallback callback = iterator.next();
+            int itemCount = mRegisteredSoftApCallbacks.beginBroadcast();
+            for (int i = 0; i < itemCount; i++) {
                 try {
-                    callback.onBlockedClientConnecting(client, blockedReason);
+                    mRegisteredSoftApCallbacks.getBroadcastItem(i).onBlockedClientConnecting(client,
+                            blockedReason);
                 } catch (RemoteException e) {
                     Log.e(TAG, "onBlockedClientConnecting: remote exception -- " + e);
                 }
             }
+            mRegisteredSoftApCallbacks.finishBroadcast();
         }
     }
 
     /**
      * Implements LOHS behavior on top of the existing SoftAp API.
      */
-    private final class LohsSoftApTracker implements WifiManager.SoftApCallback {
+    private final class LohsSoftApTracker implements SoftApCallbackInternal {
         @GuardedBy("mLocalOnlyHotspotRequests")
         private final HashMap<Integer, LocalOnlyHotspotRequestInfo>
                 mLocalOnlyHotspotRequests = new HashMap<>();
@@ -1434,7 +1732,10 @@
 
                 // At this point, the request is accepted.
                 if (mLocalOnlyHotspotRequests.isEmpty()) {
-                    startForFirstRequestLocked(request);
+                    mWifiThreadRunner.post(() -> {
+                        startForFirstRequestLocked(request);
+                    });
+
                 } else if (mLohsInterfaceMode == WifiManager.IFACE_IP_MODE_LOCAL_ONLY) {
                     // LOHS has already started up for an earlier request, so we can send the
                     // current config to the incoming request right away.
@@ -1453,22 +1754,20 @@
 
         @GuardedBy("mLocalOnlyHotspotRequests")
         private void startForFirstRequestLocked(LocalOnlyHotspotRequestInfo request) {
-            int band = SoftApConfiguration.BAND_2GHZ;
+            int band = WifiApConfigStore.generateDefaultBand(mContext);
 
             // For auto only
             if (hasAutomotiveFeature(mContext)) {
                 if (mContext.getResources().getBoolean(R.bool.config_wifiLocalOnlyHotspot6ghz)
-                        && mContext.getResources().getBoolean(R.bool.config_wifiSoftap6ghzSupported)
-                        && is6GhzBandSupportedInternal()) {
+                        && ApConfigUtil.isBandSupported(SoftApConfiguration.BAND_6GHZ, mContext)) {
                     band = SoftApConfiguration.BAND_6GHZ;
                 } else if (mContext.getResources().getBoolean(
                         R.bool.config_wifi_local_only_hotspot_5ghz)
-                        && is5GhzBandSupportedInternal()) {
+                        && ApConfigUtil.isBandSupported(SoftApConfiguration.BAND_5GHZ, mContext)) {
                     band = SoftApConfiguration.BAND_5GHZ;
                 }
             }
-
-            SoftApConfiguration softApConfig = WifiApConfigStore.generateLocalOnlyHotspotConfig(
+            SoftApConfiguration softApConfig = mWifiApConfigStore.generateLocalOnlyHotspotConfig(
                     mContext, band, request.getCustomConfig());
 
             mActiveConfig = new SoftApModeConfiguration(
@@ -1476,7 +1775,7 @@
                     softApConfig, mLohsSoftApTracker.getSoftApCapability());
             mIsExclusive = (request.getCustomConfig() != null);
 
-            startSoftApInternal(mActiveConfig);
+            startSoftApInternal(mActiveConfig, request.getWorkSource());
         }
 
         /**
@@ -1592,86 +1891,51 @@
                 mLohsState = state;
             }
         }
-
-        @Override
-        public void onConnectedClientsChanged(List<WifiClient> clients) {
-            // Nothing to do
-        }
-
-        /**
-         * Called when information of softap changes.
-         *
-         * @param softApInfo is the softap information. {@link SoftApInfo}
-         */
-        @Override
-        public void onInfoChanged(SoftApInfo softApInfo) {
-            // Nothing to do
-        }
-
-        /**
-         * Called when capability of softap changes.
-         *
-         * @param capability is the softap information. {@link SoftApCapability}
-         */
-        @Override
-        public void onCapabilityChanged(SoftApCapability capability) {
-            // Nothing to do
-        }
-
-        /**
-         * Called when client trying to connect but device blocked the client with specific reason.
-         *
-         * @param client the currently blocked client.
-         * @param blockedReason one of blocked reason from
-         * {@link WifiManager.SapClientBlockedReason}
-         */
-        @Override
-        public void onBlockedClientConnecting(WifiClient client, int blockedReason) {
-            // Nothing to do
-        }
     }
 
     /**
-     * see {@link android.net.wifi.WifiManager#registerSoftApCallback(Executor, SoftApCallback)}
+     * see {@link android.net.wifi.WifiManager#registerSoftApCallback(Executor,
+     * WifiManager.SoftApCallback)}
      *
-     * @param binder IBinder instance to allow cleanup if the app dies
      * @param callback Soft AP callback to register
-     * @param callbackIdentifier Unique ID of the registering callback. This ID will be used to
-     *        unregister the callback. See {@link unregisterSoftApCallback(int)}
      *
      * @throws SecurityException if the caller does not have permission to register a callback
      * @throws RemoteException if remote exception happens
      * @throws IllegalArgumentException if the arguments are null or invalid
      */
     @Override
-    public void registerSoftApCallback(IBinder binder, ISoftApCallback callback,
-            int callbackIdentifier) {
+    public void registerSoftApCallback(ISoftApCallback callback) {
         // verify arguments
-        if (binder == null) {
-            throw new IllegalArgumentException("Binder must not be null");
-        }
         if (callback == null) {
             throw new IllegalArgumentException("Callback must not be null");
         }
 
-        enforceNetworkStackOrSettingsPermission();
+        int uid = Binder.getCallingUid();
+        int pid = Binder.getCallingPid();
+        if (!mWifiPermissionsUtil.checkConfigOverridePermission(uid)
+                && !checkNetworkSettingsPermission(pid, uid)
+                && !checkMainlineNetworkStackPermission(pid, uid)) {
+            // random apps should not be allowed to read the user specified config
+            throw new SecurityException("App not allowed to read  WiFi Ap information "
+                    + "(uid/pid = " + uid + "/" + pid + ")");
+        }
 
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("registerSoftApCallback uid=%").c(Binder.getCallingUid()).flush();
         }
 
         // post operation to handler thread
         mWifiThreadRunner.post(() -> {
-            if (!mTetheredSoftApTracker.registerSoftApCallback(binder, callback,
-                    callbackIdentifier)) {
+            if (!mTetheredSoftApTracker.registerSoftApCallback(callback)) {
                 Log.e(TAG, "registerSoftApCallback: Failed to add callback");
                 return;
             }
             // Update the client about the current state immediately after registering the callback
             try {
                 callback.onStateChanged(mTetheredSoftApTracker.getState(), 0);
-                callback.onConnectedClientsChanged(mTetheredSoftApTracker.getConnectedClients());
-                callback.onInfoChanged(mTetheredSoftApTracker.getSoftApInfo());
+                callback.onConnectedClientsOrInfoChanged(mTetheredSoftApTracker.getSoftApInfos(),
+                        mTetheredSoftApTracker.getConnectedClients(),
+                        mTetheredSoftApTracker.getIsBridgedMode(), true);
                 callback.onCapabilityChanged(mTetheredSoftApTracker.getSoftApCapability());
             } catch (RemoteException e) {
                 Log.e(TAG, "registerSoftApCallback: remote exception -- " + e);
@@ -1680,23 +1944,31 @@
     }
 
     /**
-     * see {@link android.net.wifi.WifiManager#unregisterSoftApCallback(SoftApCallback)}
+     * see {@link android.net.wifi.WifiManager#unregisterSoftApCallback(WifiManager.SoftApCallback)}
      *
-     * @param callbackIdentifier Unique ID of the callback to be unregistered.
+     * @param callback Soft AP callback to unregister
      *
      * @throws SecurityException if the caller does not have permission to register a callback
      */
     @Override
-    public void unregisterSoftApCallback(int callbackIdentifier) {
-        enforceNetworkStackOrSettingsPermission();
+    public void unregisterSoftApCallback(ISoftApCallback callback) {
+        int uid = Binder.getCallingUid();
+        int pid = Binder.getCallingPid();
+        if (!mWifiPermissionsUtil.checkConfigOverridePermission(uid)
+                && !checkNetworkSettingsPermission(pid, uid)
+                && !checkMainlineNetworkStackPermission(pid, uid)) {
+            // random apps should not be allowed to read the user specified config
+            throw new SecurityException("App not allowed to read  WiFi Ap information "
+                    + "(uid/pid = " + uid + "/" + pid + ")");
+        }
 
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("unregisterSoftApCallback uid=%").c(Binder.getCallingUid()).flush();
         }
 
         // post operation to handler thread
         mWifiThreadRunner.post(() ->
-                mTetheredSoftApTracker.unregisterSoftApCallback(callbackIdentifier));
+                mTetheredSoftApTracker.unregisterSoftApCallback(callback));
     }
 
     /**
@@ -1742,6 +2014,7 @@
 
         mLog.info("start uid=% pid=%").c(uid).c(pid).flush();
 
+        final WorkSource requestorWs;
         // Permission requirements are different with/without custom config.
         if (customConfig == null) {
             if (enforceChangePermission(packageName) != MODE_ALLOWED) {
@@ -1754,6 +2027,17 @@
                 if (!mWifiPermissionsUtil.isLocationModeEnabled()) {
                     throw new SecurityException("Location mode is not enabled.");
                 }
+                // TODO(b/162344695): Exception added for LOHS. This exception is need to avoid
+                // breaking existing LOHS behavior: LOHS AP iface is allowed to delete STA iface
+                // (even if LOHS app has lower priority than user toggled on STA iface). This does
+                // not fit in with the new context based concurrency priority in HalDeviceManager,
+                // but we cannot break existing API's. So, we artificially boost the priority of
+                // the request by "faking" the requestor context as settings app.
+                // We probably need some UI dialog to allow the user to grant the app's LOHS
+                // request. Once that UI dialog is added, we can get rid of this hack and use the UI
+                // to elevate the priority of LOHS request only if user approves the request to
+                // toggle wifi off for LOHS.
+                requestorWs = mFrameworkFacade.getSettingsWorkSource(mContext);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -1761,6 +2045,8 @@
             if (!isSettingsOrSuw(Binder.getCallingPid(), Binder.getCallingUid())) {
                 throw new SecurityException(TAG + ": Permission denied");
             }
+            // Already privileged, no need to fake.
+            requestorWs = new WorkSource(uid, packageName);
         }
 
         // verify that tethering is not disabled
@@ -1781,7 +2067,7 @@
         }
 
         // check if we are currently tethering
-        if (!mActiveModeWarden.canRequestMoreSoftApManagers()
+        if (!mActiveModeWarden.canRequestMoreSoftApManagers(requestorWs)
                 && mTetheredSoftApTracker.getState() == WIFI_AP_STATE_ENABLED) {
             // Tethering is enabled, cannot start LocalOnlyHotspot
             mLog.info("Cannot start localOnlyHotspot when WiFi Tethering is active.")
@@ -1790,8 +2076,8 @@
         }
 
         // now create the new LOHS request info object
-        LocalOnlyHotspotRequestInfo request = new LocalOnlyHotspotRequestInfo(callback,
-                new LocalOnlyRequestorCallback(), customConfig);
+        LocalOnlyHotspotRequestInfo request = new LocalOnlyHotspotRequestInfo(
+                requestorWs, callback, new LocalOnlyRequestorCallback(), customConfig);
 
         return mLohsSoftApTracker.start(pid, request);
     }
@@ -1864,7 +2150,7 @@
                     + "(uid = " + uid + ")");
         }
 
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("getWifiApConfiguration uid=%").c(uid).flush();
         }
 
@@ -1883,9 +2169,14 @@
     @NonNull
     @Override
     public SoftApConfiguration getSoftApConfiguration() {
-        enforceNetworkSettingsPermission();
         int uid = Binder.getCallingUid();
-        if (mVerboseLoggingEnabled) {
+        if (!mWifiPermissionsUtil.checkConfigOverridePermission(uid)
+                && !mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)) {
+            // random apps should not be allowed to read the user specified config
+            throw new SecurityException("App not allowed to read or update stored WiFi Ap config "
+                    + "(uid = " + uid + ")");
+        }
+        if (isVerboseLoggingEnabled()) {
             mLog.info("getSoftApConfiguration uid=%").c(uid).flush();
         }
 
@@ -1919,7 +2210,7 @@
         SoftApConfiguration softApConfig = ApConfigUtil.fromWifiConfiguration(wifiConfig);
         if (softApConfig == null) return false;
         if (WifiApConfigStore.validateApWifiConfiguration(
-                softApConfig, false)) {
+                softApConfig, false, mContext)) {
             mWifiThreadRunner.post(() -> mWifiApConfigStore.setApConfiguration(softApConfig));
             return true;
         } else {
@@ -1937,12 +2228,17 @@
     @Override
     public boolean setSoftApConfiguration(
             @NonNull SoftApConfiguration softApConfig, @NonNull String packageName) {
-        enforceNetworkSettingsPermission();
         int uid = Binder.getCallingUid();
         boolean privileged = mWifiPermissionsUtil.checkNetworkSettingsPermission(uid);
+        if (!mWifiPermissionsUtil.checkConfigOverridePermission(uid)
+                && !privileged) {
+            // random apps should not be allowed to read the user specified config
+            throw new SecurityException("App not allowed to read or update stored WiFi Ap config "
+                    + "(uid = " + uid + ")");
+        }
         mLog.info("setSoftApConfiguration uid=%").c(uid).flush();
         if (softApConfig == null) return false;
-        if (WifiApConfigStore.validateApWifiConfiguration(softApConfig, privileged)) {
+        if (WifiApConfigStore.validateApWifiConfiguration(softApConfig, privileged, mContext)) {
             mActiveModeWarden.updateSoftApConfiguration(softApConfig);
             mWifiThreadRunner.post(() -> mWifiApConfigStore.setApConfiguration(softApConfig));
             return true;
@@ -1956,9 +2252,13 @@
      * see {@link android.net.wifi.WifiManager#setScanAlwaysAvailable(boolean)}
      */
     @Override
-    public void setScanAlwaysAvailable(boolean isAvailable) {
+    public void setScanAlwaysAvailable(boolean isAvailable, String packageName) {
         enforceNetworkSettingsPermission();
-        mLog.info("setScanAlwaysAvailable uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("setScanAlwaysAvailable uid=% package=% isAvailable=%")
+                .c(Binder.getCallingUid())
+                .c(packageName)
+                .c(isAvailable)
+                .flush();
         mSettingsStore.handleWifiScanAlwaysAvailableToggled(isAvailable);
         long ident = Binder.clearCallingIdentity();
         try {
@@ -1976,10 +2276,10 @@
     @Override
     public boolean isScanAlwaysAvailable() {
         enforceAccessPermission();
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("isScanAlwaysAvailable uid=%").c(Binder.getCallingUid()).flush();
         }
-        return mSettingsStore.isScanAlwaysAvailable();
+        return mSettingsStore.isScanAlwaysAvailableToggleEnabled();
     }
 
     /**
@@ -1997,7 +2297,7 @@
             return false;
         }
         mLog.info("disconnect uid=%").c(Binder.getCallingUid()).flush();
-        mClientModeImpl.disconnectCommand();
+        mWifiThreadRunner.post(() -> mActiveModeWarden.getPrimaryClientModeManager().disconnect());
         return true;
     }
 
@@ -2009,14 +2309,16 @@
         if (enforceChangePermission(packageName) != MODE_ALLOWED) {
             return false;
         }
-        if (!isTargetSdkLessThanQOrPrivileged(
-                packageName, Binder.getCallingPid(), Binder.getCallingUid())) {
-            mLog.info("reconnect not allowed for uid=%")
-                    .c(Binder.getCallingUid()).flush();
+        int callingUid = Binder.getCallingUid();
+        if (!isTargetSdkLessThanQOrPrivileged(packageName, Binder.getCallingPid(), callingUid)) {
+            mLog.info("reconnect not allowed for uid=%").c(callingUid).flush();
             return false;
         }
-        mLog.info("reconnect uid=%").c(Binder.getCallingUid()).flush();
-        mClientModeImpl.reconnectCommand(new WorkSource(Binder.getCallingUid()));
+        mLog.info("reconnect uid=%").c(callingUid).flush();
+
+        mWifiThreadRunner.post(() -> {
+            mActiveModeWarden.getPrimaryClientModeManager().reconnect(new WorkSource(callingUid));
+        });
         return true;
     }
 
@@ -2035,25 +2337,54 @@
             return false;
         }
         mLog.info("reassociate uid=%").c(Binder.getCallingUid()).flush();
-        mClientModeImpl.reassociateCommand();
+        mWifiThreadRunner.post(() -> mActiveModeWarden.getPrimaryClientModeManager().reassociate());
         return true;
     }
 
     /**
+     * Returns true if we should log the call to getSupportedFeatures.
+     *
+     * Because of the way getSupportedFeatures is used in WifiManager, there are
+     * often clusters of several back-to-back calls; avoid repeated logging if
+     * the feature set has not changed and the time interval is short.
+     */
+    private boolean needToLogSupportedFeatures(long features) {
+        if (isVerboseLoggingEnabled()) {
+            long now = mClock.getElapsedSinceBootMillis();
+            synchronized (this) {
+                if (now > mLastLoggedSupportedFeaturesTimestamp + A_FEW_MILLISECONDS
+                        || features != mLastLoggedSupportedFeatures) {
+                    mLastLoggedSupportedFeaturesTimestamp = now;
+                    mLastLoggedSupportedFeatures = features;
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+    private static final int A_FEW_MILLISECONDS = 250;
+    private long mLastLoggedSupportedFeatures = -1;
+    private long mLastLoggedSupportedFeaturesTimestamp = 0;
+
+    /**
      * see {@link android.net.wifi.WifiManager#getSupportedFeatures}
      */
     @Override
     public long getSupportedFeatures() {
         enforceAccessPermission();
-        if (mVerboseLoggingEnabled) {
-            mLog.info("getSupportedFeatures uid=%").c(Binder.getCallingUid()).flush();
+        long features = getSupportedFeaturesInternal();
+        if (needToLogSupportedFeatures(features)) {
+            mLog.info("getSupportedFeatures uid=% returns %")
+                    .c(Binder.getCallingUid())
+                    .c(Long.toHexString(features))
+                    .flush();
         }
-        return getSupportedFeaturesInternal();
+        return features;
     }
 
     @Override
     public void getWifiActivityEnergyInfoAsync(IOnWifiActivityEnergyInfoListener listener) {
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("getWifiActivityEnergyInfoAsync uid=%")
                     .c(Binder.getCallingUid())
                     .flush();
@@ -2069,40 +2400,25 @@
 
     private WifiActivityEnergyInfo getWifiActivityEnergyInfo() {
         enforceAccessPermission();
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("getWifiActivityEnergyInfo uid=%").c(Binder.getCallingUid()).flush();
         }
         if ((getSupportedFeatures() & WifiManager.WIFI_FEATURE_LINK_LAYER_STATS) == 0) {
             return null;
         }
-        if (mClientModeImplChannel == null) {
-            Log.e(TAG, "mClientModeImplChannel is not initialized");
-            return null;
-        }
-        WifiLinkLayerStats stats = mClientModeImpl.syncGetLinkLayerStats(mClientModeImplChannel);
+        WifiLinkLayerStats stats = mWifiThreadRunner.call(
+                () -> mActiveModeWarden.getPrimaryClientModeManager().getWifiLinkLayerStats(),
+                null);
         if (stats == null) {
             return null;
         }
 
         final long rxIdleTimeMillis = stats.on_time - stats.tx_time - stats.rx_time;
-        final long[] txTimePerLevelMillis;
-        if (stats.tx_time_per_level == null) {
-            // This will happen if the HAL get link layer API returned null.
-            txTimePerLevelMillis = new long[0];
-        } else {
-            // need to manually copy since we are converting an int[] to a long[]
-            txTimePerLevelMillis = new long[stats.tx_time_per_level.length];
-            for (int i = 0; i < txTimePerLevelMillis.length; i++) {
-                txTimePerLevelMillis[i] = stats.tx_time_per_level[i];
-                // TODO(b/27227497): Need to read the power consumed per level from config
-            }
-        }
         if (VDBG || rxIdleTimeMillis < 0 || stats.on_time < 0 || stats.tx_time < 0
                 || stats.rx_time < 0 || stats.on_time_scan < 0) {
             Log.d(TAG, " getWifiActivityEnergyInfo: "
                     + " on_time_millis=" + stats.on_time
                     + " tx_time_millis=" + stats.tx_time
-                    + " tx_time_per_level_millis=" + Arrays.toString(txTimePerLevelMillis)
                     + " rx_time_millis=" + stats.rx_time
                     + " rxIdleTimeMillis=" + rxIdleTimeMillis
                     + " scan_time_millis=" + stats.on_time_scan);
@@ -2123,21 +2439,36 @@
      *
      * @param packageName String name of the calling package
      * @param featureId The feature in the package
+     * @param callerNetworksOnly Whether to only return networks created by the caller
      * @return the list of configured networks
      */
     @Override
     public ParceledListSlice<WifiConfiguration> getConfiguredNetworks(String packageName,
-            String featureId) {
+            String featureId, boolean callerNetworksOnly) {
         enforceAccessPermission();
         int callingUid = Binder.getCallingUid();
-        // bypass shell: can get varioud pkg name
-        if (callingUid != Process.SHELL_UID && callingUid != Process.ROOT_UID) {
+        boolean isDeviceOrProfileOwner = isDeviceOrProfileOwner(callingUid, packageName);
+        boolean isCarrierApp = mWifiInjector.makeTelephonyManager()
+                .checkCarrierPrivilegesForPackageAnyPhone(packageName)
+                == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
+        boolean isPrivileged = isPrivileged(getCallingPid(), callingUid);
+        // Only DO, PO, carrier app or system app can use callerNetworksOnly argument
+        if (callerNetworksOnly) {
+            if (!isDeviceOrProfileOwner && !isCarrierApp && !isPrivileged) {
+                throw new SecurityException(
+                        "Not a DO, PO, carrier or privileged app");
+            }
+        }
+        // bypass shell: can get various pkg name
+        // also bypass if caller is only retrieving networks added by itself
+        if (callingUid != Process.SHELL_UID && callingUid != Process.ROOT_UID
+                && !callerNetworksOnly) {
             long ident = Binder.clearCallingIdentity();
             try {
                 mWifiPermissionsUtil.enforceCanAccessScanResults(packageName, featureId,
                         callingUid, null);
             } catch (SecurityException e) {
-                Log.e(TAG, "Permission violation - getConfiguredNetworks not allowed for uid="
+                Log.w(TAG, "Permission violation - getConfiguredNetworks not allowed for uid="
                         + callingUid + ", packageName=" + packageName + ", reason=" + e);
                 return new ParceledListSlice<>(new ArrayList<>());
             } finally {
@@ -2146,21 +2477,17 @@
         }
         boolean isTargetSdkLessThanQOrPrivileged = isTargetSdkLessThanQOrPrivileged(
                 packageName, Binder.getCallingPid(), callingUid);
-        boolean isCarrierApp = mWifiInjector.makeTelephonyManager()
-                .checkCarrierPrivilegesForPackageAnyPhone(packageName)
-                == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
         if (!isTargetSdkLessThanQOrPrivileged && !isCarrierApp) {
             mLog.info("getConfiguredNetworks not allowed for uid=%")
                     .c(callingUid).flush();
             return new ParceledListSlice<>(new ArrayList<>());
         }
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("getConfiguredNetworks uid=%").c(callingUid).flush();
         }
 
         int targetConfigUid = Process.INVALID_UID; // don't expose any MAC addresses
-        if (isPrivileged(getCallingPid(), callingUid)
-                || isDeviceOrProfileOwner(callingUid, packageName)) {
+        if (isPrivileged || isDeviceOrProfileOwner) {
             targetConfigUid = Process.WIFI_UID; // expose all MAC addresses
         } else if (isCarrierApp) {
             targetConfigUid = callingUid; // expose only those configs created by the Carrier App
@@ -2169,17 +2496,19 @@
         List<WifiConfiguration> configs = mWifiThreadRunner.call(
                 () -> mWifiConfigManager.getSavedNetworks(finalTargetConfigUid),
                 Collections.emptyList());
-        if (isTargetSdkLessThanQOrPrivileged) {
-            return new ParceledListSlice<>(configs);
+        if (isTargetSdkLessThanQOrPrivileged && !callerNetworksOnly) {
+            return new ParceledListSlice<>(
+                    WifiConfigurationUtil.convertMultiTypeConfigsToLegacyConfigs(configs));
         }
-        // Carrier app: should only get its own configs
+        // Should only get its own configs
         List<WifiConfiguration> creatorConfigs = new ArrayList<>();
         for (WifiConfiguration config : configs) {
             if (config.creatorUid == callingUid) {
                 creatorConfigs.add(config);
             }
         }
-        return new ParceledListSlice<>(creatorConfigs);
+        return new ParceledListSlice<>(
+                WifiConfigurationUtil.convertMultiTypeConfigsToLegacyConfigs(creatorConfigs));
     }
 
     /**
@@ -2200,19 +2529,20 @@
             mWifiPermissionsUtil.enforceCanAccessScanResults(packageName, featureId, callingUid,
                     null);
         } catch (SecurityException e) {
-            Log.e(TAG, "Permission violation - getPrivilegedConfiguredNetworks not allowed for"
+            Log.w(TAG, "Permission violation - getPrivilegedConfiguredNetworks not allowed for"
                     + " uid=" + callingUid + ", packageName=" + packageName + ", reason=" + e);
             return null;
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("getPrivilegedConfiguredNetworks uid=%").c(callingUid).flush();
         }
         List<WifiConfiguration> configs = mWifiThreadRunner.call(
                 () -> mWifiConfigManager.getConfiguredNetworksWithPasswords(),
                 Collections.emptyList());
-        return new ParceledListSlice<>(configs);
+        return new ParceledListSlice<>(
+                WifiConfigurationUtil.convertMultiTypeConfigsToLegacyConfigs(configs));
     }
 
     /**
@@ -2230,7 +2560,7 @@
         if (!isSettingsOrSuw(Binder.getCallingPid(), Binder.getCallingUid())) {
             throw new SecurityException(TAG + ": Permission denied");
         }
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("getMatchingPasspointConfigurations uid=%").c(Binder.getCallingUid()).flush();
         }
         if (!ScanResultUtil.validateScanResultList(scanResults)) {
@@ -2254,12 +2584,12 @@
         if (!isSettingsOrSuw(Binder.getCallingPid(), Binder.getCallingUid())) {
             throw new SecurityException(TAG + ": Permission denied");
         }
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("getMatchingOsuProviders uid=%").c(Binder.getCallingUid()).flush();
         }
 
         if (!ScanResultUtil.validateScanResultList(scanResults)) {
-            Log.e(TAG, "Attempt to retrieve OsuProviders with invalid scanResult List");
+            Log.w(TAG, "Attempt to retrieve OsuProviders with invalid scanResult List");
             return Collections.emptyMap();
         }
         return mWifiThreadRunner.call(
@@ -2278,7 +2608,7 @@
         if (!isSettingsOrSuw(Binder.getCallingPid(), Binder.getCallingUid())) {
             throw new SecurityException(TAG + ": Permission denied");
         }
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("getMatchingPasspointConfigsForOsuProviders uid=%").c(
                     Binder.getCallingUid()).flush();
         }
@@ -2305,7 +2635,7 @@
         if (!isSettingsOrSuw(Binder.getCallingPid(), Binder.getCallingUid())) {
             throw new SecurityException(TAG + ": Permission denied");
         }
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("getWifiConfigsForPasspointProfiles uid=%").c(
                     Binder.getCallingUid()).flush();
         }
@@ -2334,12 +2664,12 @@
         if (!isSettingsOrSuw(Binder.getCallingPid(), Binder.getCallingUid())) {
             throw new SecurityException(TAG + ": Permission denied");
         }
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("getWifiConfigsForMatchedNetworkSuggestions uid=%").c(
                     Binder.getCallingUid()).flush();
         }
         if (!ScanResultUtil.validateScanResultList(scanResults)) {
-            Log.e(TAG, "Attempt to retrieve WifiConfiguration with invalid scanResult List");
+            Log.w(TAG, "Attempt to retrieve WifiConfiguration with invalid scanResult List");
             return new ArrayList<>();
         }
         return mWifiThreadRunner.call(
@@ -2349,6 +2679,29 @@
     }
 
     /**
+     * see {@link WifiManager#addNetworkPrivileged(WifiConfiguration)}
+     * @return WifiManager.AddNetworkResult Object.
+     */
+    @Override
+    public @NonNull WifiManager.AddNetworkResult addOrUpdateNetworkPrivileged(
+            WifiConfiguration config, String packageName) {
+        int pid = Binder.getCallingPid();
+        int uid = Binder.getCallingUid();
+        mWifiPermissionsUtil.checkPackage(uid, packageName);
+        boolean hasPermission = isPrivileged(pid, uid)
+                || isDeviceOrProfileOwner(uid, packageName)
+                || mWifiPermissionsUtil.isSystem(packageName, uid);
+        if (!hasPermission) {
+            throw new SecurityException("Caller is not a device owner, profile owner, system app,"
+                    + " or privileged app");
+        }
+        if (config != null) {
+            config.networkId = removeSecurityTypeFromNetworkId(config.networkId);
+        }
+        return addOrUpdateNetworkInternal(config, packageName, uid);
+    }
+
+    /**
      * see {@link android.net.wifi.WifiManager#addOrUpdateNetwork(WifiConfiguration)}
      * @return the supplicant-assigned identifier for the new or updated
      * network if the operation succeeds, or {@code -1} if it fails
@@ -2365,11 +2718,19 @@
                     .c(Binder.getCallingUid()).flush();
             return -1;
         }
+        if (config != null) {
+            config.networkId = removeSecurityTypeFromNetworkId(config.networkId);
+        }
         mLog.info("addOrUpdateNetwork uid=%").c(Binder.getCallingUid()).flush();
+        return addOrUpdateNetworkInternal(config, packageName, callingUid).networkId;
+    }
 
+    private @NonNull AddNetworkResult addOrUpdateNetworkInternal(WifiConfiguration config,
+            String packageName, int callingUid) {
         if (config == null) {
             Log.e(TAG, "bad network configuration");
-            return -1;
+            return new AddNetworkResult(
+                    AddNetworkResult.STATUS_INVALID_CONFIGURATION, -1);
         }
         mWifiMetrics.incrementNumAddOrUpdateNetworkCalls();
 
@@ -2380,7 +2741,8 @@
                     PasspointProvider.convertFromWifiConfig(config);
             if (passpointConfig == null || passpointConfig.getCredential() == null) {
                 Log.e(TAG, "Missing credential for Passpoint profile");
-                return -1;
+                return new AddNetworkResult(
+                        AddNetworkResult.STATUS_ADD_PASSPOINT_FAILURE, -1);
             }
 
             // Copy over certificates and keys.
@@ -2396,19 +2758,42 @@
                     config.enterpriseConfig.getClientPrivateKey());
             if (!addOrUpdatePasspointConfiguration(passpointConfig, packageName)) {
                 Log.e(TAG, "Failed to add Passpoint profile");
-                return -1;
+                return new AddNetworkResult(
+                        AddNetworkResult.STATUS_ADD_PASSPOINT_FAILURE, -1);
             }
             // There is no network ID associated with a Passpoint profile.
-            return 0;
+            return new AddNetworkResult(AddNetworkResult.STATUS_SUCCESS, 0);
         }
 
-        Log.i("addOrUpdateNetwork", " uid = " + Binder.getCallingUid()
+        if (config.isEnterprise() && config.enterpriseConfig.isEapMethodServerCertUsed()
+                && !config.enterpriseConfig.isMandatoryParameterSetForServerCertValidation()) {
+            if (!(mContext.getResources().getBoolean(
+                    R.bool.config_wifiAllowInsecureEnterpriseConfigurationsForSettingsAndSUW)
+                    && isSettingsOrSuw(Binder.getCallingPid(), Binder.getCallingUid()))) {
+                Log.e(TAG, "Enterprise network configuration is missing either a Root CA "
+                        + "or a domain name");
+                return new AddNetworkResult(
+                        AddNetworkResult.STATUS_INVALID_CONFIGURATION_ENTERPRISE, -1);
+            }
+            Log.w(TAG, "Insecure Enterprise network " + config.SSID
+                    + " configured by Settings/SUW");
+        }
+
+        Log.i("addOrUpdateNetworkInternal", " uid = " + Binder.getCallingUid()
                 + " SSID " + config.SSID
                 + " nid=" + config.networkId);
-        return mWifiThreadRunner.call(
-            () -> mWifiConfigManager.addOrUpdateNetwork(config, callingUid, packageName)
-                    .getNetworkId(),
+        // TODO: b/171981339, add more detailed failure reason into
+        //  WifiConfigManager.NetworkUpdateResult, and plumb that reason up.
+        int networkId =  mWifiThreadRunner.call(
+                () -> mWifiConfigManager.addOrUpdateNetwork(config, callingUid, packageName)
+                        .getNetworkId(),
                 WifiConfiguration.INVALID_NETWORK_ID);
+        if (networkId >= 0) {
+            return new AddNetworkResult(AddNetworkResult.STATUS_SUCCESS, addSecurityTypeToNetworkId(
+                    networkId, config.getDefaultSecurityParams().getSecurityType()));
+        }
+        return new AddNetworkResult(
+                AddNetworkResult.STATUS_ADD_WIFI_CONFIG_FAILURE, -1);
     }
 
     public static void verifyCert(X509Certificate caCert)
@@ -2442,10 +2827,25 @@
                     .c(Binder.getCallingUid()).flush();
             return false;
         }
+        final int internalNetId = removeSecurityTypeFromNetworkId(netId);
         int callingUid = Binder.getCallingUid();
         mLog.info("removeNetwork uid=%").c(callingUid).flush();
         return mWifiThreadRunner.call(
-                () -> mWifiConfigManager.removeNetwork(netId, callingUid, packageName), false);
+                () -> mWifiConfigManager.removeNetwork(internalNetId, callingUid, packageName),
+                false);
+    }
+
+    @Override
+    public boolean removeNonCallerConfiguredNetworks(String packageName) {
+        if (enforceChangePermission(packageName) != MODE_ALLOWED) {
+            throw new SecurityException("Caller does not hold CHANGE_WIFI_STATE permission");
+        }
+        final int callingUid = Binder.getCallingUid();
+        if (!mWifiPermissionsUtil.isDeviceOwner(callingUid, packageName)) {
+            throw new SecurityException("Caller is not device owner");
+        }
+        return mWifiThreadRunner.call(
+                () -> mWifiConfigManager.removeNonCallerConfiguredNetwork(callingUid), false);
     }
 
     /**
@@ -2456,7 +2856,7 @@
      */
     private boolean triggerConnectAndReturnStatus(int netId, int callingUid) {
         final CountDownLatch countDownLatch = new CountDownLatch(1);
-        final MutableBoolean success = new MutableBoolean(false);
+        final Mutable<Boolean> success = new Mutable<>(false);
         IActionListener.Stub connectListener = new IActionListener.Stub() {
             @Override
             public void onSuccess() {
@@ -2469,8 +2869,14 @@
                 countDownLatch.countDown();
             }
         };
-        mClientModeImpl.connect(null, netId, new Binder(), connectListener,
-                connectListener.hashCode(), callingUid);
+        mWifiThreadRunner.post(() ->
+                mMakeBeforeBreakManager.stopAllSecondaryTransientClientModeManagers(() ->
+                        mConnectHelper.connectToNetwork(
+                                new NetworkUpdateResult(netId),
+                                new ActionListenerWrapper(connectListener),
+                                callingUid)
+                )
+        );
         // now wait for response.
         try {
             countDownLatch.await(RUN_WITH_SCISSORS_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
@@ -2498,6 +2904,7 @@
                     .c(Binder.getCallingUid()).flush();
             return false;
         }
+        final int internalNetId = removeSecurityTypeFromNetworkId(netId);
         int callingUid = Binder.getCallingUid();
         // TODO b/33807876 Log netId
         mLog.info("enableNetwork uid=% disableOthers=%")
@@ -2506,10 +2913,11 @@
 
         mWifiMetrics.incrementNumEnableNetworkCalls();
         if (disableOthers) {
-            return triggerConnectAndReturnStatus(netId, callingUid);
+            return triggerConnectAndReturnStatus(internalNetId, callingUid);
         } else {
             return mWifiThreadRunner.call(
-                    () -> mWifiConfigManager.enableNetwork(netId, false, callingUid, packageName),
+                    () -> mWifiConfigManager.enableNetwork(
+                            internalNetId, false, callingUid, packageName),
                     false);
         }
     }
@@ -2531,10 +2939,60 @@
                     .c(Binder.getCallingUid()).flush();
             return false;
         }
+        final int internalNetId = removeSecurityTypeFromNetworkId(netId);
         int callingUid = Binder.getCallingUid();
         mLog.info("disableNetwork uid=%").c(callingUid).flush();
         return mWifiThreadRunner.call(
-                () -> mWifiConfigManager.disableNetwork(netId, callingUid, packageName), false);
+                () -> mWifiConfigManager.disableNetwork(
+                        internalNetId, callingUid, packageName), false);
+    }
+
+    /**
+     * See
+     * {@link android.net.wifi.WifiManager#startRestrictingAutoJoinToSubscriptionId(int)}
+     * @param subscriptionId the subscription ID of the carrier whose merged wifi networks won't be
+     *                       disabled.
+     */
+    @Override
+    @RequiresApi(Build.VERSION_CODES.S)
+    public void startRestrictingAutoJoinToSubscriptionId(int subscriptionId) {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        if (!isSettingsOrSuw(Binder.getCallingPid(), Binder.getCallingUid())) {
+            throw new SecurityException(TAG + ": Permission denied");
+        }
+
+        mLog.info("startRestrictingAutoJoinToSubscriptionId=% uid=%").c(subscriptionId)
+                .c(Binder.getCallingUid()).flush();
+        mWifiThreadRunner.post(() -> {
+            mWifiConfigManager
+                    .startRestrictingAutoJoinToSubscriptionId(subscriptionId);
+            // always disconnect here and rely on auto-join to find the appropriate carrier network
+            // to join. Even if we are currently connected to the carrier-merged wifi, it's still
+            // better to disconnect here because it's possible that carrier wifi offload is
+            // disabled.
+            mActiveModeWarden.getPrimaryClientModeManager().disconnect();
+        });
+    }
+
+    /**
+     * See {@link android.net.wifi.WifiManager#stopRestrictingAutoJoinToSubscriptionId()}
+     */
+    @Override
+    @RequiresApi(Build.VERSION_CODES.S)
+    public void stopRestrictingAutoJoinToSubscriptionId() {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        if (!isSettingsOrSuw(Binder.getCallingPid(), Binder.getCallingUid())) {
+            throw new SecurityException(TAG + ": Permission denied");
+        }
+
+        mLog.info("stopRestrictingAutoJoinToSubscriptionId uid=%")
+                .c(Binder.getCallingUid()).flush();
+        mWifiThreadRunner.post(() ->
+                mWifiConfigManager.stopRestrictingAutoJoinToSubscriptionId());
     }
 
     /**
@@ -2546,9 +3004,10 @@
         enforceNetworkSettingsPermission();
 
         int callingUid = Binder.getCallingUid();
-        mLog.info("allowAutojoin=% uid=%").c(choice).c(callingUid).flush();
-
-        mWifiThreadRunner.post(() -> mClientModeImpl.allowAutoJoinGlobal(choice));
+        mLog.info("allowAutojoinGlobal=% uid=%").c(choice).c(callingUid).flush();
+        mWifiThreadRunner.post(() -> mWifiConnectivityManager.setAutoJoinEnabledExternal(choice));
+        mLastCallerInfoManager.put(LastCallerInfoManager.AUTOJOIN_GLOBAL, Process.myTid(),
+                callingUid, Binder.getCallingPid(), "<unknown>", choice);
     }
 
     /**
@@ -2560,10 +3019,11 @@
     public void allowAutojoin(int netId, boolean choice) {
         enforceNetworkSettingsPermission();
 
+        final int internalNetId = removeSecurityTypeFromNetworkId(netId);
         int callingUid = Binder.getCallingUid();
         mLog.info("allowAutojoin=% uid=%").c(choice).c(callingUid).flush();
         mWifiThreadRunner.post(() -> {
-            WifiConfiguration config = mWifiConfigManager.getConfiguredNetwork(netId);
+            WifiConfiguration config = mWifiConfigManager.getConfiguredNetwork(internalNetId);
             if (config == null) {
                 return;
             }
@@ -2590,13 +3050,13 @@
             // even for Suggestion, modify the current ephemeral configuration so that
             // existing configuration auto-connection is updated correctly
             if (choice != config.allowAutojoin) {
-                mWifiConfigManager.allowAutojoin(netId, choice);
+                mWifiConfigManager.allowAutojoin(internalNetId, choice);
                 // do not log this metrics for passpoint networks again here since it's already
                 // logged in PasspointManager.
                 if (!config.isPasspoint()) {
                     mWifiMetrics.logUserActionEvent(choice
                             ? UserActionEvent.EVENT_CONFIGURE_AUTO_CONNECT_ON
-                            : UserActionEvent.EVENT_CONFIGURE_AUTO_CONNECT_OFF, netId);
+                            : UserActionEvent.EVENT_CONFIGURE_AUTO_CONNECT_OFF, internalNetId);
                 }
             }
         });
@@ -2660,6 +3120,30 @@
     }
 
     /**
+     * Provides backward compatibility for apps using
+     * {@link WifiManager#getConnectionInfo()}, {@link WifiManager#getDhcpInfo()} when a
+     * secondary STA is created as a result of a request from their app (peer to peer
+     * WifiNetworkSpecifier request or oem paid/private suggestion).
+     */
+    private ClientModeManager getClientModeManagerIfSecondaryCmmRequestedByCallerPresent(
+            int callingUid, @NonNull String callingPackageName) {
+        List<ConcreteClientModeManager> secondaryCmms =
+                mActiveModeWarden.getClientModeManagersInRoles(
+                        ROLE_CLIENT_LOCAL_ONLY, ROLE_CLIENT_SECONDARY_LONG_LIVED);
+        for (ConcreteClientModeManager cmm : secondaryCmms) {
+            WorkSource reqWs = cmm.getRequestorWs();
+            // If there are more than 1 secondary CMM for same app, return any one (should not
+            // happen currently since we don't support 3 STA's concurrently).
+            if (reqWs.equals(new WorkSource(callingUid, callingPackageName))) {
+                mLog.info("getConnectionInfo providing secondary CMM info").flush();
+                return cmm;
+            }
+        }
+        // No secondary CMM's created for the app, return primary CMM.
+        return mActiveModeWarden.getPrimaryClientModeManager();
+    }
+
+    /**
      * See {@link android.net.wifi.WifiManager#getConnectionInfo()}
      * @return the Wi-Fi information, contained in {@link WifiInfo}.
      */
@@ -2667,25 +3151,48 @@
     public WifiInfo getConnectionInfo(String callingPackage, String callingFeatureId) {
         enforceAccessPermission();
         int uid = Binder.getCallingUid();
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("getConnectionInfo uid=%").c(uid).flush();
         }
         long ident = Binder.clearCallingIdentity();
         try {
-            WifiInfo wifiInfo = mClientModeImpl.syncRequestConnectionInfo();
+            WifiInfo wifiInfo = mWifiThreadRunner.call(
+                    () -> getClientModeManagerIfSecondaryCmmRequestedByCallerPresent(
+                            uid, callingPackage)
+                            .syncRequestConnectionInfo(), new WifiInfo());
             long redactions = wifiInfo.getApplicableRedactions();
             if (mWifiPermissionsUtil.checkLocalMacAddressPermission(uid)) {
+                if (isVerboseLoggingEnabled()) {
+                    Log.v(TAG, "Clearing REDACT_FOR_LOCAL_MAC_ADDRESS for " + callingPackage
+                            + "(uid=" + uid + ")");
+                }
                 redactions &= ~NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS;
             }
             if (mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)) {
+                if (isVerboseLoggingEnabled()) {
+                    Log.v(TAG, "Clearing REDACT_FOR_NETWORK_SETTINGS for " + callingPackage
+                            + "(uid=" + uid + ")");
+                }
                 redactions &= ~NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS;
             }
             try {
+                if (isVerboseLoggingEnabled()) {
+                    Log.v(TAG, "Clearing REDACT_FOR_ACCESS_FINE_LOCATION for " + callingPackage
+                            + "(uid=" + uid + ")");
+                }
                 mWifiPermissionsUtil.enforceCanAccessScanResults(callingPackage, callingFeatureId,
                         uid, null);
                 redactions &= ~NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION;
-            } catch (SecurityException ignored) { }
-            return wifiInfo.makeCopy(redactions);
+            } catch (SecurityException ignored) {
+                if (isVerboseLoggingEnabled()) {
+                    Log.v(TAG, "Keeping REDACT_FOR_ACCESS_FINE_LOCATION:" + ignored);
+                }
+            }
+            WifiInfo wifiInfoCopy = wifiInfo.makeCopy(redactions);
+            wifiInfoCopy.setNetworkId(addSecurityTypeToNetworkId(wifiInfoCopy.getNetworkId(),
+                    convertWifiInfoSecurityTypeToWifiConfiguration(
+                            wifiInfoCopy.getCurrentSecurityType())));
+            return wifiInfoCopy;
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -2701,7 +3208,7 @@
         enforceAccessPermission();
         int uid = Binder.getCallingUid();
         long ident = Binder.clearCallingIdentity();
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("getScanResults uid=%").c(uid).flush();
         }
         try {
@@ -2711,7 +3218,7 @@
                     mScanRequestProxy::getScanResults, Collections.emptyList());
             return scanResults;
         } catch (SecurityException e) {
-            Log.e(TAG, "Permission violation - getScanResults not allowed for uid="
+            Log.w(TAG, "Permission violation - getScanResults not allowed for uid="
                     + uid + ", packageName=" + callingPackage + ", reason=" + e);
             return new ArrayList<>();
         } finally {
@@ -2750,7 +3257,7 @@
                     },
                     Collections.emptyMap());
         } catch (SecurityException e) {
-            Log.e(TAG, "Permission violation - getMatchingScanResults not allowed for uid="
+            Log.w(TAG, "Permission violation - getMatchingScanResults not allowed for uid="
                     + uid + ", packageName=" + callingPackage + ", reason + e");
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -2832,7 +3339,7 @@
                 || mWifiPermissionsUtil.checkNetworkSetupWizardPermission(uid)) {
             privileged = true;
         }
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("getPasspointConfigurations uid=%").c(Binder.getCallingUid()).flush();
         }
         final boolean privilegedFinal = privileged;
@@ -2850,7 +3357,9 @@
     public void queryPasspointIcon(long bssid, String fileName) {
         enforceAccessPermission();
         mLog.info("queryPasspointIcon uid=%").c(Binder.getCallingUid()).flush();
-        mClientModeImpl.syncQueryPasspointIcon(mClientModeImplChannel, bssid, fileName);
+        mWifiThreadRunner.post(() -> {
+            mActiveModeWarden.getPrimaryClientModeManager().syncQueryPasspointIcon(bssid, fileName);
+        });
     }
 
     /**
@@ -2864,17 +3373,6 @@
         return 0;
     }
 
-    /**
-     * Deauthenticate and set the re-authentication hold off time for the current network
-     * @param holdoff hold off time in milliseconds
-     * @param ess set if the hold off pertains to an ESS rather than a BSS
-     */
-    @Override
-    public void deauthenticateNetwork(long holdoff, boolean ess) {
-        mLog.info("deauthenticateNetwork uid=%").c(Binder.getCallingUid()).flush();
-        mClientModeImpl.deauthenticateNetwork(mClientModeImplChannel, holdoff, ess);
-    }
-
      /**
      * Get the country code
      * @return Get the best choice country code for wifi, regardless of if it was set or
@@ -2884,15 +3382,105 @@
     @Override
     public String getCountryCode() {
         enforceNetworkSettingsPermission();
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("getCountryCode uid=%").c(Binder.getCallingUid()).flush();
         }
         return mCountryCode.getCountryCode();
     }
 
+    /**
+     * Set the Wifi country code. This call will override the country code set by telephony.
+     * @param countryCode A 2-Character alphanumeric country code.
+     *
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    @Override
+    public void setOverrideCountryCode(@NonNull String countryCode) {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        mContext.enforceCallingOrSelfPermission(
+                Manifest.permission.MANAGE_WIFI_COUNTRY_CODE, "WifiService");
+        if (!WifiCountryCode.isValid(countryCode)) {
+            throw new IllegalArgumentException("Country code must be a 2-Character alphanumeric"
+                    + " code. But got countryCode " + countryCode
+                    + " instead");
+        }
+        if (isVerboseLoggingEnabled()) {
+            mLog.info("setOverrideCountryCode uid=% countryCode=%")
+                    .c(Binder.getCallingUid()).c(countryCode).flush();
+        }
+        // Post operation to handler thread
+        mWifiThreadRunner.post(() -> mCountryCode.setOverrideCountryCode(countryCode));
+    }
+
+    /**
+     * Clear the country code previously set through setOverrideCountryCode method.
+     *
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    @Override
+    public void clearOverrideCountryCode() {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        mContext.enforceCallingOrSelfPermission(
+                Manifest.permission.MANAGE_WIFI_COUNTRY_CODE, "WifiService");
+        if (isVerboseLoggingEnabled()) {
+            mLog.info("clearCountryCode uid=%").c(Binder.getCallingUid()).flush();
+        }
+        // Post operation to handler thread
+        mWifiThreadRunner.post(() -> mCountryCode.clearOverrideCountryCode());
+    }
+
+    /**
+     * Change the default country code previously set from ro.boot.wificountrycode.
+     * @param countryCode A 2-Character alphanumeric country code.
+     *
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    @Override
+    public void setDefaultCountryCode(@NonNull String countryCode) {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        mContext.enforceCallingOrSelfPermission(
+                Manifest.permission.MANAGE_WIFI_COUNTRY_CODE, "WifiService");
+        if (!WifiCountryCode.isValid(countryCode)) {
+            throw new IllegalArgumentException("Country code must be a 2-Character alphanumeric"
+                    + " code. But got countryCode " + countryCode
+                    + " instead");
+        }
+        if (isVerboseLoggingEnabled()) {
+            mLog.info("setDefaultCountryCode uid=% countryCode=%")
+                    .c(Binder.getCallingUid()).c(countryCode).flush();
+        }
+        // Post operation to handler thread
+        mWifiThreadRunner.post(() -> mCountryCode.setDefaultCountryCode(countryCode));
+    }
+
+    @Override
+    public boolean is24GHzBandSupported() {
+        if (isVerboseLoggingEnabled()) {
+            mLog.info("is24GHzBandSupported uid=%").c(Binder.getCallingUid()).flush();
+        }
+
+        return is24GhzBandSupportedInternal();
+    }
+
+    private boolean is24GhzBandSupportedInternal() {
+        if (mContext.getResources().getBoolean(R.bool.config_wifi24ghzSupport)) {
+            return true;
+        }
+        return mWifiThreadRunner.call(
+                () -> mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_24_GHZ).length > 0,
+                false);
+    }
+
+
     @Override
     public boolean is5GHzBandSupported() {
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("is5GHzBandSupported uid=%").c(Binder.getCallingUid()).flush();
         }
 
@@ -2900,13 +3488,17 @@
     }
 
     private boolean is5GhzBandSupportedInternal() {
+        if (mContext.getResources().getBoolean(R.bool.config_wifi5ghzSupport)) {
+            return true;
+        }
         return mWifiThreadRunner.call(
-                () -> mClientModeImpl.isWifiBandSupported(WifiScanner.WIFI_BAND_5_GHZ), false);
+                () -> mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ).length > 0,
+                false);
     }
 
     @Override
     public boolean is6GHzBandSupported() {
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("is6GHzBandSupported uid=%").c(Binder.getCallingUid()).flush();
         }
 
@@ -2914,14 +3506,41 @@
     }
 
     private boolean is6GhzBandSupportedInternal() {
+        if (mContext.getResources().getBoolean(R.bool.config_wifi6ghzSupport)) {
+            return true;
+        }
         return mWifiThreadRunner.call(
-                () -> mClientModeImpl.isWifiBandSupported(WifiScanner.WIFI_BAND_6_GHZ), false);
+                () -> mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_6_GHZ).length > 0,
+                false);
+    }
+
+    @Override
+    public boolean is60GHzBandSupported() {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+
+        if (isVerboseLoggingEnabled()) {
+            mLog.info("is60GHzBandSupported uid=%").c(Binder.getCallingUid()).flush();
+        }
+
+        return is60GhzBandSupportedInternal();
+    }
+
+    private boolean is60GhzBandSupportedInternal() {
+        if (mContext.getResources().getBoolean(R.bool.config_wifi60ghzSupport)) {
+            return true;
+        }
+        return mWifiThreadRunner.call(
+                () -> mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_60_GHZ).length > 0,
+                false);
     }
 
     @Override
     public boolean isWifiStandardSupported(@WifiStandard int standard) {
         return mWifiThreadRunner.call(
-                () -> mClientModeImpl.isWifiStandardSupported(standard), false);
+                () -> mActiveModeWarden.getPrimaryClientModeManager().isWifiStandardSupported(
+                        standard), false);
     }
 
     /**
@@ -2931,13 +3550,16 @@
      * @deprecated
      */
     @Override
-    @Deprecated
-    public DhcpInfo getDhcpInfo() {
+    public DhcpInfo getDhcpInfo(@NonNull String packageName) {
         enforceAccessPermission();
-        if (mVerboseLoggingEnabled) {
-            mLog.info("getDhcpInfo uid=%").c(Binder.getCallingUid()).flush();
+        int callingUid = Binder.getCallingUid();
+        if (isVerboseLoggingEnabled()) {
+            mLog.info("getDhcpInfo uid=%").c(callingUid).flush();
         }
-        DhcpResultsParcelable dhcpResults = mClientModeImpl.syncGetDhcpResultsParcelable();
+        DhcpResultsParcelable dhcpResults = mWifiThreadRunner.call(
+                () -> getClientModeManagerIfSecondaryCmmRequestedByCallerPresent(
+                        callingUid, packageName)
+                        .syncGetDhcpResultsParcelable(), new DhcpResultsParcelable());
 
         DhcpInfo info = new DhcpInfo();
 
@@ -3060,8 +3682,9 @@
         if (remoteMacAddress == null) {
           throw new IllegalArgumentException("remoteMacAddress cannot be null");
         }
-
-        mClientModeImpl.enableTdls(remoteMacAddress, enable);
+        mWifiThreadRunner.post(() ->
+                mActiveModeWarden.getPrimaryClientModeManager().enableTdls(
+                        remoteMacAddress, enable));
     }
 
     /**
@@ -3081,42 +3704,22 @@
                 Binder.getCallingUid()));
     }
 
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (action.equals(Intent.ACTION_USER_REMOVED)) {
-                UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER);
-                if (userHandle == null) {
-                    Log.e(TAG, "User removed broadcast received with no user handle");
-                    return;
-                }
-                mWifiThreadRunner.post(() ->
-                        mWifiConfigManager.removeNetworksForUser(userHandle.getIdentifier()));
-            } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
-                int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
-                        BluetoothAdapter.STATE_DISCONNECTED);
-                mClientModeImpl.sendBluetoothAdapterConnectionStateChange(state);
-            } else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
-                int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
-                        BluetoothAdapter.STATE_OFF);
-                mClientModeImpl.sendBluetoothAdapterStateChange(state);
-            } else if (action.equals(TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED)) {
-                boolean emergencyMode =
-                        intent.getBooleanExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false);
-                mActiveModeWarden.emergencyCallbackModeChanged(emergencyMode);
-            } else if (action.equals(TelephonyManager.ACTION_EMERGENCY_CALL_STATE_CHANGED)) {
-                boolean inCall =
-                        intent.getBooleanExtra(
-                                TelephonyManager.EXTRA_PHONE_IN_EMERGENCY_CALL, false);
-                mActiveModeWarden.emergencyCallStateChanged(inCall);
-            } else if (action.equals(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)) {
-                handleIdleModeChanged();
-            } else if (action.equals(Intent.ACTION_SHUTDOWN)) {
-                handleShutDown();
-            }
-        }
-    };
+    private void removeAppStateInternal(int uid, @NonNull String pkgName) {
+        ApplicationInfo ai = new ApplicationInfo();
+        ai.packageName = pkgName;
+        ai.uid = uid;
+        mWifiConfigManager.removeNetworksForApp(ai);
+        mScanRequestProxy.clearScanRequestTimestampsForApp(pkgName, uid);
+
+        // Remove all suggestions from the package.
+        mWifiNetworkSuggestionsManager.removeApp(pkgName);
+        mWifiInjector.getWifiNetworkFactory().removeUserApprovedAccessPointsForApp(
+                pkgName);
+
+        // Remove all Passpoint profiles from package.
+        mWifiInjector.getPasspointManager().removePasspointProviderWithPackage(
+                pkgName);
+    }
 
     private void registerForBroadcasts() {
         IntentFilter intentFilter = new IntentFilter();
@@ -3150,19 +3753,7 @@
                 Log.d(TAG, "Remove settings for package:" + pkgName);
                 // Call the method in the main Wifi thread.
                 mWifiThreadRunner.post(() -> {
-                    ApplicationInfo ai = new ApplicationInfo();
-                    ai.packageName = pkgName;
-                    ai.uid = uid;
-                    mWifiConfigManager.removeNetworksForApp(ai);
-                    mScanRequestProxy.clearScanRequestTimestampsForApp(pkgName, uid);
-
-                    // Remove all suggestions from the package.
-                    mWifiNetworkSuggestionsManager.removeApp(pkgName);
-                    mClientModeImpl.removeNetworkRequestUserApprovedAccessPointsForApp(pkgName);
-
-                    // Remove all Passpoint profiles from package.
-                    mWifiInjector.getPasspointManager().removePasspointProviderWithPackage(
-                            pkgName);
+                    removeAppStateInternal(uid, pkgName);
                 });
             }
         }, intentFilter);
@@ -3175,11 +3766,13 @@
             @Override
             public void onReceive(Context context, Intent intent) {
                 final int subId = SubscriptionManager.getActiveDataSubscriptionId();
-                Log.d(TAG, "ACTION_CARRIER_CONFIG_CHANGED, active subId: " + subId);
-
-                mTetheredSoftApTracker.updateSoftApCapability(subId);
-                mActiveModeWarden.updateSoftApCapability(
-                        mTetheredSoftApTracker.getSoftApCapability());
+                // post operation to handler thread
+                mWifiThreadRunner.post(() -> {
+                    Log.d(TAG, "ACTION_CARRIER_CONFIG_CHANGED, active subId: " + subId);
+                    mTetheredSoftApTracker.updateSoftApCapabilityWhenCarrierConfigChanged(subId);
+                    mActiveModeWarden.updateSoftApCapability(
+                            mTetheredSoftApTracker.getSoftApCapability());
+                });
             }
         }, filter);
 
@@ -3194,23 +3787,26 @@
     public int handleShellCommand(@NonNull ParcelFileDescriptor in,
             @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
             @NonNull String[] args) {
-        return new WifiShellCommand(mWifiInjector, this, mContext).exec(
-                this, in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(),
-                args);
+        WifiShellCommand shellCommand =  new WifiShellCommand(mWifiInjector, this, mContext,
+                mWifiGlobals, mWifiThreadRunner);
+        return shellCommand.exec(this, in.getFileDescriptor(), out.getFileDescriptor(),
+                err.getFileDescriptor(), args);
     }
 
     private void updateWifiMetrics() {
         mWifiThreadRunner.run(() -> {
             mWifiMetrics.updateSavedNetworks(
                     mWifiConfigManager.getSavedNetworks(Process.WIFI_UID));
+            mActiveModeWarden.updateMetrics();
             mPasspointManager.updateMetrics();
         });
         boolean isEnhancedMacRandEnabled = mFrameworkFacade.getIntegerSetting(mContext,
                 WifiConfigManager.ENHANCED_MAC_RANDOMIZATION_FEATURE_FORCE_ENABLE_FLAG, 0) == 1
                 ? true : false;
         mWifiMetrics.setEnhancedMacRandomizationForceEnabled(isEnhancedMacRandEnabled);
-        mWifiMetrics.setIsScanningAlwaysEnabled(mSettingsStore.isScanAlwaysAvailable());
-        mWifiMetrics.setVerboseLoggingEnabled(mVerboseLoggingEnabled);
+        mWifiMetrics.setIsScanningAlwaysEnabled(
+                mSettingsStore.isScanAlwaysAvailableToggleEnabled());
+        mWifiMetrics.setVerboseLoggingEnabled(isVerboseLoggingEnabled());
         mWifiMetrics.setWifiWakeEnabled(mWifiInjector.getWakeupController().isEnabled());
     }
 
@@ -3231,27 +3827,26 @@
             // IpClient dump was requested. Pass it along and take no further action.
             String[] ipClientArgs = new String[args.length - 1];
             System.arraycopy(args, 1, ipClientArgs, 0, ipClientArgs.length);
-            mClientModeImpl.dumpIpClient(fd, pw, ipClientArgs);
+            mActiveModeWarden.getPrimaryClientModeManager().dumpIpClient(fd, pw, ipClientArgs);
         } else if (args != null && args.length > 0 && WifiScoreReport.DUMP_ARG.equals(args[0])) {
-            WifiScoreReport wifiScoreReport = mClientModeImpl.getWifiScoreReport();
-            if (wifiScoreReport != null) wifiScoreReport.dump(fd, pw, args);
+            mActiveModeWarden.getPrimaryClientModeManager().dumpWifiScoreReport(fd, pw, args);
         } else if (args != null && args.length > 0 && WifiScoreCard.DUMP_ARG.equals(args[0])) {
             WifiScoreCard wifiScoreCard = mWifiInjector.getWifiScoreCard();
             String networkListBase64 = mWifiThreadRunner.call(() ->
                     wifiScoreCard.getNetworkListBase64(true), "");
             pw.println(networkListBase64);
         } else {
-            // Polls link layer stats and RSSI. This allows the stats to show up in
-            // WifiScoreReport's dump() output when taking a bug report even if the screen is off.
-            mClientModeImpl.updateLinkLayerStatsRssiAndScoreReport();
-            pw.println("Wi-Fi is " + mClientModeImpl.syncGetWifiStateByName());
-            pw.println("Verbose logging is " + (mVerboseLoggingEnabled ? "on" : "off"));
+            pw.println("Verbose logging is " + (isVerboseLoggingEnabled() ? "on" : "off"));
             pw.println("Stay-awake conditions: " +
                     mFacade.getIntegerSetting(mContext,
                             Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0));
             pw.println("mInIdleMode " + mInIdleMode);
             pw.println("mScanPending " + mScanPending);
+            pw.println("SettingsStore:");
             mSettingsStore.dump(fd, pw, args);
+            mActiveModeWarden.dump(fd, pw, args);
+            mMakeBeforeBreakManager.dump(fd, pw, args);
+            pw.println();
             mWifiTrafficPoller.dump(fd, pw, args);
             pw.println();
             pw.println("Locks held:");
@@ -3259,10 +3854,6 @@
             pw.println();
             mWifiMulticastLockManager.dump(pw);
             pw.println();
-            mActiveModeWarden.dump(fd, pw, args);
-            pw.println();
-            mClientModeImpl.dump(fd, pw, args);
-            pw.println();
             WifiScoreCard wifiScoreCard = mWifiInjector.getWifiScoreCard();
             String networkListBase64 = mWifiThreadRunner.call(() ->
                     wifiScoreCard.getNetworkListBase64(true), "");
@@ -3279,19 +3870,41 @@
             pw.println();
             pw.println("ScoringParams: " + mWifiInjector.getScoringParams());
             pw.println();
-            pw.println("WifiScoreReport:");
-            WifiScoreReport wifiScoreReport = mClientModeImpl.getWifiScoreReport();
-            wifiScoreReport.dump(fd, pw, args);
-            pw.println();
-            SarManager sarManager = mWifiInjector.getSarManager();
-            sarManager.dump(fd, pw, args);
-            pw.println();
             mWifiThreadRunner.run(() -> {
                 mWifiInjector.getWifiNetworkScoreCache().dumpWithLatestScanResults(
                         fd, pw, args, mScanRequestProxy.getScanResults());
                 mWifiInjector.getSettingsConfigStore().dump(fd, pw, args);
             });
             pw.println();
+            mCountryCode.dump(fd, pw, args);
+            mWifiInjector.getWifiNetworkFactory().dump(fd, pw, args);
+            mWifiInjector.getUntrustedWifiNetworkFactory().dump(fd, pw, args);
+            mWifiInjector.getOemWifiNetworkFactory().dump(fd, pw, args);
+            pw.println("Wlan Wake Reasons:" + mWifiNative.getWlanWakeReasonCount());
+            pw.println();
+            mWifiConfigManager.dump(fd, pw, args);
+            pw.println();
+            mPasspointManager.dump(pw);
+            pw.println();
+            mWifiInjector.getWifiDiagnostics().captureBugReportData(
+                    WifiDiagnostics.REPORT_REASON_USER_ACTION);
+            mWifiInjector.getWifiDiagnostics().dump(fd, pw, args);
+            mWifiConnectivityManager.dump(fd, pw, args);
+            mWifiThreadRunner.run(() -> {
+                mWifiHealthMonitor.dump(fd, pw, args);
+            });
+            mWifiThreadRunner.run(() -> {
+                mWifiScoreCard.dump(fd, pw, args);
+            });
+            mWifiInjector.getWakeupController().dump(fd, pw, args);
+            mWifiInjector.getWifiLastResortWatchdog().dump(fd, pw, args);
+            mWifiInjector.getAdaptiveConnectivityEnabledSettingObserver().dump(fd, pw, args);
+            mWifiInjector.getWifiGlobals().dump(fd, pw, args);
+            mWifiInjector.getSarManager().dump(fd, pw, args);
+            pw.println();
+            mLastCallerInfoManager.dump(pw);
+            pw.println();
+            mWifiInjector.getLinkProbeManager().dump(fd, pw, args);
         }
     }
 
@@ -3367,7 +3980,7 @@
     @Override
     public boolean isMulticastEnabled() {
         enforceAccessPermission();
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("isMulticastEnabled uid=%").c(Binder.getCallingUid()).flush();
         }
         return mWifiMulticastLockManager.isMulticastEnabled();
@@ -3380,26 +3993,73 @@
         mLog.info("enableVerboseLogging uid=% verbose=%")
                 .c(Binder.getCallingUid())
                 .c(verbose).flush();
-        mWifiInjector.getSettingsConfigStore().put(WIFI_VERBOSE_LOGGING_ENABLED, verbose > 0);
+        boolean enabled = verbose > 0;
+        mWifiInjector.getSettingsConfigStore().put(WIFI_VERBOSE_LOGGING_ENABLED, enabled);
+        onVerboseLoggingStatusChanged(enabled);
         enableVerboseLoggingInternal(verbose);
     }
 
+    private void onVerboseLoggingStatusChanged(boolean enabled) {
+        int itemCount = mRegisteredWifiLoggingStatusListeners.beginBroadcast();
+        for (int i = 0; i < itemCount; i++) {
+            try {
+                mRegisteredWifiLoggingStatusListeners.getBroadcastItem(i)
+                        .onStatusChanged(enabled);
+            } catch (RemoteException e) {
+                Log.e(TAG, "onVerboseLoggingStatusChanged: RemoteException -- ", e);
+            }
+
+        }
+        mRegisteredWifiLoggingStatusListeners.finishBroadcast();
+    }
+
+    private boolean isVerboseLoggingEnabled() {
+        return WifiManager.VERBOSE_LOGGING_LEVEL_DISABLED != mVerboseLoggingLevel;
+    }
+
     private void enableVerboseLoggingInternal(int verbose) {
-        mVerboseLoggingEnabled = verbose > 0;
-        mClientModeImpl.enableVerboseLogging(verbose);
+        if (verbose > WifiManager.VERBOSE_LOGGING_LEVEL_ENABLED
+                && mBuildProperties.isUserBuild()) {
+            throw new SecurityException(TAG + ": Not allowed for the user build.");
+        }
+        mVerboseLoggingLevel = verbose;
+
+        // Update wifi globals before sending the verbose logging change.
+        mWifiThreadRunner.removeCallbacks(mAutoDisableShowKeyVerboseLoggingModeRunnable);
+        if (WifiManager.VERBOSE_LOGGING_LEVEL_ENABLED_SHOW_KEY == mVerboseLoggingLevel) {
+            mWifiGlobals.setShowKeyVerboseLoggingModeEnabled(true);
+            mWifiThreadRunner.postDelayed(mAutoDisableShowKeyVerboseLoggingModeRunnable,
+                    AUTO_DISABLE_SHOW_KEY_COUNTDOWN_MILLIS);
+        } else {
+            // Ensure the show key mode is disabled.
+            mWifiGlobals.setShowKeyVerboseLoggingModeEnabled(false);
+        }
+
+        mActiveModeWarden.enableVerboseLogging(isVerboseLoggingEnabled());
         mWifiLockManager.enableVerboseLogging(verbose);
         mWifiMulticastLockManager.enableVerboseLogging(verbose);
         mWifiInjector.enableVerboseLogging(verbose);
+        mWifiInjector.getSarManager().enableVerboseLogging(verbose);
     }
 
     @Override
     public int getVerboseLoggingLevel() {
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("getVerboseLoggingLevel uid=%").c(Binder.getCallingUid()).flush();
         }
-        return mWifiInjector.getSettingsConfigStore().get(WIFI_VERBOSE_LOGGING_ENABLED) ? 1 : 0;
+        return mVerboseLoggingLevel;
     }
 
+    private Runnable mAutoDisableShowKeyVerboseLoggingModeRunnable = new Runnable() {
+        @Override
+        public void run() {
+            // If still enabled, fallback to the regular verbose logging mode.
+            if (isVerboseLoggingEnabled()) {
+                enableVerboseLoggingInternal(WifiManager.VERBOSE_LOGGING_LEVEL_ENABLED);
+            }
+        }
+    };
+
     @Override
     public void factoryReset(String packageName) {
         enforceNetworkSettingsPermission();
@@ -3442,10 +4102,11 @@
             mPasspointManager.clearAnqpRequestsAndFlushCache();
             mWifiConfigManager.clearUserTemporarilyDisabledList();
             mWifiConfigManager.removeAllEphemeralOrPasspointConfiguredNetworks();
-            mClientModeImpl.clearNetworkRequestUserApprovedAccessPoints();
+            mWifiInjector.getWifiNetworkFactory().clear();
             mWifiNetworkSuggestionsManager.clear();
             mWifiInjector.getWifiScoreCard().clear();
-            mWifiInjector.getWifiHealthMonitor().clear();
+            mWifiHealthMonitor.clear();
+            mWifiCarrierInfoManager.clear();
             notifyFactoryReset();
         });
     }
@@ -3469,7 +4130,7 @@
             intentToSend.setComponent(new ComponentName(
                     resolveInfo.activityInfo.applicationInfo.packageName,
                     resolveInfo.activityInfo.name));
-            mContext.sendBroadcastAsUser(intentToSend, UserHandle.ALL,
+            mContext.sendBroadcastAsUser(intentToSend, UserHandle.CURRENT,
                     android.Manifest.permission.NETWORK_CARRIER_PROVISIONING);
         }
     }
@@ -3479,10 +4140,10 @@
         if (!isSettingsOrSuw(Binder.getCallingPid(), Binder.getCallingUid())) {
             throw new SecurityException(TAG + ": Permission denied");
         }
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("getCurrentNetwork uid=%").c(Binder.getCallingUid()).flush();
         }
-        return mClientModeImpl.syncGetCurrentNetwork(mClientModeImplChannel);
+        return getPrimaryClientModeManagerBlockingThreadSafe().syncGetCurrentNetwork();
     }
 
     public static String toHexString(String s) {
@@ -3506,11 +4167,6 @@
     public byte[] retrieveBackupData() {
         enforceNetworkSettingsPermission();
         mLog.info("retrieveBackupData uid=%").c(Binder.getCallingUid()).flush();
-        if (mClientModeImplChannel == null) {
-            Log.e(TAG, "mClientModeImplChannel is not initialized");
-            return null;
-        }
-
         Log.d(TAG, "Retrieving backup data");
         List<WifiConfiguration> wifiConfigurations = mWifiThreadRunner.call(
                 () -> mWifiConfigManager.getConfiguredNetworksWithPasswords(), null);
@@ -3538,7 +4194,8 @@
                                 mWifiConfigManager.addOrUpdateNetwork(configuration, callingUid)
                                         .getNetworkId();
                         if (networkId == WifiConfiguration.INVALID_NETWORK_ID) {
-                            Log.e(TAG, "Restore network failed: " + configuration.getKey());
+                            Log.e(TAG, "Restore network failed: "
+                                    + configuration.getProfileKey());
                             continue;
                         }
                         // Enable all networks restored.
@@ -3558,11 +4215,6 @@
     public void restoreBackupData(byte[] data) {
         enforceNetworkSettingsPermission();
         mLog.info("restoreBackupData uid=%").c(Binder.getCallingUid()).flush();
-        if (mClientModeImplChannel == null) {
-            Log.e(TAG, "mClientModeImplChannel is not initialized");
-            return;
-        }
-
         Log.d(TAG, "Restoring backup data");
         List<WifiConfiguration> wifiConfigurations =
                 mWifiBackupRestore.retrieveConfigurationsFromBackupData(data);
@@ -3601,7 +4253,8 @@
                 mSoftApBackupRestore.retrieveSoftApConfigurationFromBackupData(data);
         if (softApConfig != null) {
             mWifiThreadRunner.post(() -> mWifiApConfigStore.setApConfiguration(
-                    mWifiApConfigStore.resetToDefaultForUnsupportedConfig(softApConfig)));
+                    mWifiApConfigStore.resetToDefaultForUnsupportedConfig(
+                    mWifiApConfigStore.upgradeSoftApConfiguration(softApConfig))));
             Log.d(TAG, "Restored soft ap backup data");
         }
         return softApConfig;
@@ -3618,11 +4271,6 @@
     public void restoreSupplicantBackupData(byte[] supplicantData, byte[] ipConfigData) {
         enforceNetworkSettingsPermission();
         mLog.trace("restoreSupplicantBackupData uid=%").c(Binder.getCallingUid()).flush();
-        if (mClientModeImplChannel == null) {
-            Log.e(TAG, "mClientModeImplChannel is not initialized");
-            return;
-        }
-
         Log.d(TAG, "Restoring supplicant backup data");
         List<WifiConfiguration> wifiConfigurations =
                 mWifiBackupRestore.retrieveConfigurationsFromSupplicantBackupData(
@@ -3651,8 +4299,8 @@
         }
         final int uid = Binder.getCallingUid();
         mLog.trace("startSubscriptionProvisioning uid=%").c(uid).flush();
-        if (mClientModeImpl.syncStartSubscriptionProvisioning(uid, provider,
-                callback, mClientModeImplChannel)) {
+        if (getPrimaryClientModeManagerBlockingThreadSafe()
+                .syncStartSubscriptionProvisioning(uid, provider, callback)) {
             mLog.trace("Subscription provisioning started with %")
                     .c(provider.toString()).flush();
         }
@@ -3662,62 +4310,48 @@
      * See
      * {@link WifiManager#registerTrafficStateCallback(Executor, WifiManager.TrafficStateCallback)}
      *
-     * @param binder IBinder instance to allow cleanup if the app dies
      * @param callback Traffic State callback to register
-     * @param callbackIdentifier Unique ID of the registering callback. This ID will be used to
-     *        unregister the callback. See {@link unregisterTrafficStateCallback(int)}
      *
      * @throws SecurityException if the caller does not have permission to register a callback
      * @throws RemoteException if remote exception happens
      * @throws IllegalArgumentException if the arguments are null or invalid
      */
     @Override
-    public void registerTrafficStateCallback(IBinder binder, ITrafficStateCallback callback,
-                                             int callbackIdentifier) {
+    public void registerTrafficStateCallback(ITrafficStateCallback callback) {
         // verify arguments
-        if (binder == null) {
-            throw new IllegalArgumentException("Binder must not be null");
-        }
         if (callback == null) {
             throw new IllegalArgumentException("Callback must not be null");
         }
         enforceNetworkSettingsPermission();
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("registerTrafficStateCallback uid=%").c(Binder.getCallingUid()).flush();
         }
         // Post operation to handler thread
-        mWifiThreadRunner.post(() ->
-                mWifiTrafficPoller.addCallback(binder, callback, callbackIdentifier));
+        mWifiThreadRunner.post(() -> mWifiTrafficPoller.addCallback(callback));
     }
 
     /**
      * see {@link android.net.wifi.WifiManager#unregisterTrafficStateCallback(
      * WifiManager.TrafficStateCallback)}
      *
-     * @param callbackIdentifier Unique ID of the callback to be unregistered.
+     * @param callback Traffic State callback to unregister
      *
      * @throws SecurityException if the caller does not have permission to register a callback
      */
     @Override
-    public void unregisterTrafficStateCallback(int callbackIdentifier) {
+    public void unregisterTrafficStateCallback(ITrafficStateCallback callback) {
         enforceNetworkSettingsPermission();
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("unregisterTrafficStateCallback uid=%").c(Binder.getCallingUid()).flush();
         }
         // Post operation to handler thread
-        mWifiThreadRunner.post(() ->
-                mWifiTrafficPoller.removeCallback(callbackIdentifier));
+        mWifiThreadRunner.post(() -> mWifiTrafficPoller.removeCallback(callback));
     }
 
     private long getSupportedFeaturesInternal() {
-        final AsyncChannel channel = mClientModeImplChannel;
-        long supportedFeatureSet = 0L;
-        if (channel != null) {
-            supportedFeatureSet = mClientModeImpl.syncGetSupportedFeatures(channel);
-        } else {
-            Log.e(TAG, "mClientModeImplChannel is not initialized");
-            return supportedFeatureSet;
-        }
+        long supportedFeatureSet = mWifiThreadRunner.call(
+                () -> mActiveModeWarden.getPrimaryClientModeManager().getSupportedFeatures(),
+                0L);
         // Mask the feature set against system properties.
         boolean rttSupported = mContext.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_WIFI_RTT);
@@ -3736,16 +4370,40 @@
             // no corresponding flags in vendor HAL, set if overlay enables it.
             supportedFeatureSet |= WifiManager.WIFI_FEATURE_CONNECTED_RAND_MAC;
         }
-        if (mContext.getResources().getBoolean(
-                R.bool.config_wifi_ap_mac_randomization_supported)) {
+        if (ApConfigUtil.isApMacRandomizationSupported(mContext)) {
             // no corresponding flags in vendor HAL, set if overlay enables it.
             supportedFeatureSet |= WifiManager.WIFI_FEATURE_AP_RAND_MAC;
         }
-        if (mWifiThreadRunner.call(
-                () -> mActiveModeWarden.isStaApConcurrencySupported(),
-                false)) {
-            supportedFeatureSet |= WifiManager.WIFI_FEATURE_AP_STA;
+        if (SdkLevel.isAtLeastS()) {
+            if (ApConfigUtil.isBridgedModeSupported(mContext)) {
+                // The bridged mode requires the kernel network modules support.
+                // It doesn't relate the vendor HAL, set if overlay enables it.
+                supportedFeatureSet |= WifiManager.WIFI_FEATURE_BRIDGED_AP;
+            }
+            if (mContext.getResources().getBoolean(
+                    R.bool.config_wifiStaWithBridgedSoftApConcurrencySupported)) {
+                // The bridged mode requires the kernel network modules support.
+                // It doesn't relate the vendor HAL, set if overlay enables it.
+                supportedFeatureSet |= WifiManager.WIFI_FEATURE_STA_BRIDGED_AP;
+            }
         }
+        supportedFeatureSet |= mWifiThreadRunner.call(
+                () -> {
+                    long concurrencyFeatureSet = 0L;
+                    if (mActiveModeWarden.isStaApConcurrencySupported()) {
+                        concurrencyFeatureSet |= WifiManager.WIFI_FEATURE_AP_STA;
+                    }
+                    if (mActiveModeWarden.isStaStaConcurrencySupportedForLocalOnlyConnections()) {
+                        concurrencyFeatureSet |= WifiManager.WIFI_FEATURE_ADDITIONAL_STA_LOCAL_ONLY;
+                    }
+                    if (mActiveModeWarden.isStaStaConcurrencySupportedForMbb()) {
+                        concurrencyFeatureSet |= WifiManager.WIFI_FEATURE_ADDITIONAL_STA_MBB;
+                    }
+                    if (mActiveModeWarden.isStaStaConcurrencySupportedForRestrictedConnections()) {
+                        concurrencyFeatureSet |= WifiManager.WIFI_FEATURE_ADDITIONAL_STA_RESTRICTED;
+                    }
+                    return concurrencyFeatureSet;
+                }, 0L);
         return supportedFeatureSet;
     }
 
@@ -3758,55 +4416,46 @@
      * {@link WifiManager#registerNetworkRequestMatchCallback(
      * Executor, WifiManager.NetworkRequestMatchCallback)}
      *
-     * @param binder IBinder instance to allow cleanup if the app dies
      * @param callback Network Request Match callback to register
-     * @param callbackIdentifier Unique ID of the registering callback. This ID will be used to
-     *                           unregister the callback.
-     *                           See {@link #unregisterNetworkRequestMatchCallback(int)} (int)}
      *
      * @throws SecurityException if the caller does not have permission to register a callback
      * @throws RemoteException if remote exception happens
      * @throws IllegalArgumentException if the arguments are null or invalid
      */
     @Override
-    public void registerNetworkRequestMatchCallback(IBinder binder,
-                                                    INetworkRequestMatchCallback callback,
-                                                    int callbackIdentifier) {
+    public void registerNetworkRequestMatchCallback(INetworkRequestMatchCallback callback) {
         // verify arguments
-        if (binder == null) {
-            throw new IllegalArgumentException("Binder must not be null");
-        }
         if (callback == null) {
             throw new IllegalArgumentException("Callback must not be null");
         }
         enforceNetworkSettingsPermission();
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("registerNetworkRequestMatchCallback uid=%")
                     .c(Binder.getCallingUid()).flush();
         }
         // Post operation to handler thread
-        mWifiThreadRunner.post(() -> mClientModeImpl.addNetworkRequestMatchCallback(
-                binder, callback, callbackIdentifier));
+        mWifiThreadRunner.post(() ->
+                mWifiInjector.getWifiNetworkFactory().addCallback(callback));
     }
 
     /**
      * see {@link android.net.wifi.WifiManager#unregisterNetworkRequestMatchCallback(
      * WifiManager.NetworkRequestMatchCallback)}
      *
-     * @param callbackIdentifier Unique ID of the callback to be unregistered.
+     * @param callback Network Request Match callback to unregister
      *
      * @throws SecurityException if the caller does not have permission to register a callback
      */
     @Override
-    public void unregisterNetworkRequestMatchCallback(int callbackIdentifier) {
+    public void unregisterNetworkRequestMatchCallback(INetworkRequestMatchCallback callback) {
         enforceNetworkSettingsPermission();
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("unregisterNetworkRequestMatchCallback uid=%")
                     .c(Binder.getCallingUid()).flush();
         }
         // Post operation to handler thread
         mWifiThreadRunner.post(() ->
-                mClientModeImpl.removeNetworkRequestMatchCallback(callbackIdentifier));
+                mWifiInjector.getWifiNetworkFactory().removeCallback(callback));
     }
 
     /**
@@ -3825,7 +4474,7 @@
         if (enforceChangePermission(callingPackageName) != MODE_ALLOWED) {
             return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_APP_DISALLOWED;
         }
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("addNetworkSuggestions uid=%").c(Binder.getCallingUid()).flush();
         }
         int callingUid = Binder.getCallingUid();
@@ -3853,7 +4502,7 @@
         if (enforceChangePermission(callingPackageName) != MODE_ALLOWED) {
             return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_APP_DISALLOWED;
         }
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("removeNetworkSuggestions uid=%").c(Binder.getCallingUid()).flush();
         }
         int callingUid = Binder.getCallingUid();
@@ -3872,11 +4521,12 @@
      * @param callingPackageName Package Name of the app getting the suggestions.
      * @return a list of network suggestions suggested by this app
      */
+    @Override
     public List<WifiNetworkSuggestion> getNetworkSuggestions(String callingPackageName) {
         int callingUid = Binder.getCallingUid();
         mAppOps.checkPackage(callingUid, callingPackageName);
         enforceAccessPermission();
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("getNetworkSuggestionList uid=%").c(Binder.getCallingUid()).flush();
         }
         return mWifiThreadRunner.call(() ->
@@ -3896,7 +4546,9 @@
             throw new SecurityException("App not allowed to get Wi-Fi factory MAC address "
                     + "(uid = " + uid + ")");
         }
-        String result = mWifiThreadRunner.call(mClientModeImpl::getFactoryMacAddress, null);
+        String result = mWifiThreadRunner.call(
+                () -> mActiveModeWarden.getPrimaryClientModeManager().getFactoryMacAddress(),
+                null);
         // result can be empty array if either: WifiThreadRunner.call() timed out, or
         // ClientModeImpl.getFactoryMacAddress() returned null.
         // In this particular instance, we don't differentiate the two types of nulls.
@@ -3915,14 +4567,18 @@
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.WIFI_SET_DEVICE_MOBILITY_STATE, "WifiService");
 
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("setDeviceMobilityState uid=% state=%")
                     .c(Binder.getCallingUid())
                     .c(state)
                     .flush();
         }
         // Post operation to handler thread
-        mWifiThreadRunner.post(() -> mClientModeImpl.setDeviceMobilityState(state));
+        mWifiThreadRunner.post(() -> {
+            mWifiConnectivityManager.setDeviceMobilityState(state);
+            mWifiHealthMonitor.setDeviceMobilityState(state);
+            mWifiDataStall.setDeviceMobilityState(state);
+        });
     }
 
     /**
@@ -3938,14 +4594,15 @@
      * with a peer, and send the SSID and password of the selected network.
      *
      * @param binder Caller's binder context
+     * @param packageName Package name of the calling app
      * @param enrolleeUri URI of the Enrollee obtained externally (e.g. QR code scanning)
      * @param selectedNetworkId Selected network ID to be sent to the peer
      * @param netRole The network role of the enrollee
      * @param callback Callback for status updates
      */
     @Override
-    public void startDppAsConfiguratorInitiator(IBinder binder, String enrolleeUri,
-            int selectedNetworkId, int netRole, IDppCallback callback) {
+    public void startDppAsConfiguratorInitiator(IBinder binder, @NonNull String packageName,
+            String enrolleeUri, int selectedNetworkId, int netRole, IDppCallback callback) {
         // verify arguments
         if (binder == null) {
             throw new IllegalArgumentException("Binder must not be null");
@@ -3962,12 +4619,21 @@
 
         final int uid = getMockableCallingUid();
 
-        if (!isSettingsOrSuw(Binder.getCallingPid(), Binder.getCallingUid())) {
+        int callingUid = Binder.getCallingUid();
+        mAppOps.checkPackage(callingUid, packageName);
+        if (!isSettingsOrSuw(Binder.getCallingPid(), callingUid)) {
             throw new SecurityException(TAG + ": Permission denied");
         }
-
-        mWifiThreadRunner.post(() -> mDppManager.startDppAsConfiguratorInitiator(
-                uid, binder, enrolleeUri, selectedNetworkId, netRole, callback));
+        // Stop MBB (if in progress) when DPP is initiated. Otherwise, DPP operation will fail
+        // when the previous primary iface is removed after MBB completion.
+        mWifiThreadRunner.post(() ->
+                mMakeBeforeBreakManager.stopAllSecondaryTransientClientModeManagers(() ->
+                        mDppManager.startDppAsConfiguratorInitiator(
+                                uid, packageName,
+                                mActiveModeWarden.getPrimaryClientModeManager().getInterfaceName(),
+                                binder, enrolleeUri,
+                                removeSecurityTypeFromNetworkId(selectedNetworkId), netRole,
+                                callback)));
     }
 
     /**
@@ -3998,8 +4664,70 @@
             throw new SecurityException(TAG + ": Permission denied");
         }
 
+        // Stop MBB (if in progress) when DPP is initiated. Otherwise, DPP operation will fail
+        // when the previous primary iface is removed after MBB completion.
         mWifiThreadRunner.post(() ->
-                mDppManager.startDppAsEnrolleeInitiator(uid, binder, configuratorUri, callback));
+                mMakeBeforeBreakManager.stopAllSecondaryTransientClientModeManagers(() ->
+                        mDppManager.startDppAsEnrolleeInitiator(uid,
+                                mActiveModeWarden.getPrimaryClientModeManager().getInterfaceName(),
+                                binder, configuratorUri, callback)));
+    }
+
+    /**
+     * Start DPP in Enrollee-Responder role. The current device will generate the
+     * bootstrap code and wait for the peer device to start the DPP authentication process.
+     *
+     * @param binder Caller's binder context
+     * @param deviceInfo Device specific info to display in QR code(e.g. Easy_connect_demo)
+     * @param curve Elliptic curve cryptography type used to generate DPP public/private key pair.
+     * @param callback Callback for status updates
+     */
+    @Override
+    @RequiresApi(Build.VERSION_CODES.S)
+    public void startDppAsEnrolleeResponder(IBinder binder, @Nullable String deviceInfo,
+            @WifiManager.EasyConnectCryptographyCurve int curve, IDppCallback callback) {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        // verify arguments
+        if (binder == null) {
+            throw new IllegalArgumentException("Binder must not be null");
+        }
+        if (callback == null) {
+            throw new IllegalArgumentException("Callback must not be null");
+        }
+
+        final int uid = getMockableCallingUid();
+
+        if (!isSettingsOrSuw(Binder.getCallingPid(), Binder.getCallingUid())) {
+            throw new SecurityException(TAG + ": Permission denied");
+        }
+
+        if (deviceInfo != null) {
+            int deviceInfoLen = deviceInfo.length();
+            if (deviceInfoLen > WifiManager.getEasyConnectMaxAllowedResponderDeviceInfoLength()) {
+                throw new IllegalArgumentException("Device info length: " + deviceInfoLen
+                        + " must be less than "
+                        + WifiManager.getEasyConnectMaxAllowedResponderDeviceInfoLength());
+            }
+            char c;
+            for (int i = 0; i < deviceInfoLen; i++) {
+                c = deviceInfo.charAt(i);
+                if (c < '!' || c > '~' || c == ';') {
+                    throw new IllegalArgumentException("Allowed Range of ASCII characters in"
+                            + "deviceInfo - %x20-7E; semicolon and space are not allowed!"
+                            + "Found c: " + c);
+                }
+            }
+        }
+
+        // Stop MBB (if in progress) when DPP is initiated. Otherwise, DPP operation will fail
+        // when the previous primary iface is removed after MBB completion.
+        mWifiThreadRunner.post(() ->
+                mMakeBeforeBreakManager.stopAllSecondaryTransientClientModeManagers(() ->
+                        mDppManager.startDppAsEnrolleeResponder(uid,
+                                mActiveModeWarden.getPrimaryClientModeManager().getInterfaceName(),
+                                binder, deviceInfo, curve, callback)));
     }
 
     /**
@@ -4016,58 +4744,92 @@
     }
 
     /**
-     * see {@link android.net.wifi.WifiManager#addOnWifiUsabilityStatsListener(Executor,
-     * OnWifiUsabilityStatsListener)}
+     * see {@link android.net.wifi.WifiManager#addWifiVerboseLoggingStatusChangedListener(Executor,
+     * WifiManager.WifiVerboseLoggingStatusChangedListener)}
      *
-     * @param binder IBinder instance to allow cleanup if the app dies
+     * @param listener IWifiVerboseLoggingStatusChangedListener listener to add
+     *
+     * @throws SecurityException if the caller does not have permission to add a listener.
+     * @throws IllegalArgumentException if the argument is null.
+     */
+    @Override
+    public void addWifiVerboseLoggingStatusChangedListener(
+            IWifiVerboseLoggingStatusChangedListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("Listener must not be null");
+        }
+        enforceAccessPermission();
+        // Post operation to handler thread
+        mWifiThreadRunner.post(() ->
+                mRegisteredWifiLoggingStatusListeners.register(listener));
+    }
+
+    /**
+     * see {@link android.net.wifi.WifiManager#unregisterWifiVerboseLoggingStatusCallback
+     * (WifiManager.WifiVerboseLoggingStatusCallback)}
+     *
+     * @param listener the listener to be removed.
+     *
+     * @throws SecurityException if the caller does not have permission to add a listener.
+     * @throws IllegalArgumentException if the argument is null.
+     */
+    @Override
+    public void removeWifiVerboseLoggingStatusChangedListener(
+            IWifiVerboseLoggingStatusChangedListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("Listener must not be null");
+        }
+        enforceAccessPermission();
+        // Post operation to handler thread
+        mWifiThreadRunner.post(() ->
+                mRegisteredWifiLoggingStatusListeners.unregister(listener));
+    }
+
+    /**
+     * see {@link android.net.wifi.WifiManager#addOnWifiUsabilityStatsListener(Executor,
+     * WifiManager.OnWifiUsabilityStatsListener)}
+     *
      * @param listener WifiUsabilityStatsEntry listener to add
-     * @param listenerIdentifier Unique ID of the adding listener. This ID will be used to
-     *        remove the listener. See {@link removeOnWifiUsabilityStatsListener(int)}
      *
      * @throws SecurityException if the caller does not have permission to add a listener
      * @throws RemoteException if remote exception happens
      * @throws IllegalArgumentException if the arguments are null or invalid
      */
     @Override
-    public void addOnWifiUsabilityStatsListener(IBinder binder,
-            IOnWifiUsabilityStatsListener listener, int listenerIdentifier) {
-        // verify arguments
-        if (binder == null) {
-            throw new IllegalArgumentException("Binder must not be null");
-        }
+    public void addOnWifiUsabilityStatsListener(IOnWifiUsabilityStatsListener listener) {
         if (listener == null) {
             throw new IllegalArgumentException("Listener must not be null");
         }
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE, "WifiService");
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("addOnWifiUsabilityStatsListener uid=%")
                 .c(Binder.getCallingUid()).flush();
         }
         // Post operation to handler thread
         mWifiThreadRunner.post(() ->
-                mWifiMetrics.addOnWifiUsabilityListener(binder, listener, listenerIdentifier));
+                mWifiMetrics.addOnWifiUsabilityListener(listener));
     }
 
     /**
-     * see {@link android.net.wifi.WifiManager#removeOnWifiUsabilityStatsListener(
-     * OnWifiUsabilityStatsListener)}
+     * see {@link android.net.wifi.WifiManager#removeOnWifiUsabilityStatsListener
+     * (WifiManager.OnWifiUsabilityStatsListener)}
      *
-     * @param listenerIdentifier Unique ID of the listener to be removed.
+     * @param listener listener to be removed.
      *
      * @throws SecurityException if the caller does not have permission to add a listener
      */
     @Override
-    public void removeOnWifiUsabilityStatsListener(int listenerIdentifier) {
+    public void removeOnWifiUsabilityStatsListener(IOnWifiUsabilityStatsListener listener) {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE, "WifiService");
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("removeOnWifiUsabilityStatsListener uid=%")
                     .c(Binder.getCallingUid()).flush();
         }
         // Post operation to handler thread
         mWifiThreadRunner.post(() ->
-                mWifiMetrics.removeOnWifiUsabilityListener(listenerIdentifier));
+                mWifiMetrics.removeOnWifiUsabilityListener(listener));
     }
 
     /**
@@ -4081,7 +4843,7 @@
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE, "WifiService");
 
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("updateWifiUsabilityScore uid=% seqNum=% score=% predictionHorizonSec=%")
                     .c(Binder.getCallingUid())
                     .c(seqNum)
@@ -4090,30 +4852,113 @@
                     .flush();
         }
         // Post operation to handler thread
-        mWifiThreadRunner.post(() ->
-                mClientModeImpl.updateWifiUsabilityScore(seqNum, score, predictionHorizonSec));
+        mWifiThreadRunner.post(() -> {
+            String ifaceName = mActiveModeWarden.getPrimaryClientModeManager().getInterfaceName();
+            mWifiMetrics.incrementWifiUsabilityScoreCount(
+                    ifaceName, seqNum, score, predictionHorizonSec);
+        });
     }
 
     /**
-     * see {@link android.net.wifi.WifiManager#connect(int, WifiManager.ActionListener)}
+     * Notify interested parties if a wifi config has been changed.
+     *
+     * @param wifiCredentialEventType WIFI_CREDENTIAL_SAVED or WIFI_CREDENTIAL_FORGOT
+     * @param config Must have a WifiConfiguration object to succeed
+     */
+    private void broadcastWifiCredentialChanged(int wifiCredentialEventType,
+            WifiConfiguration config) {
+        Intent intent = new Intent(WifiManager.WIFI_CREDENTIAL_CHANGED_ACTION);
+        if (config != null && config.SSID != null && mWifiPermissionsUtil.isLocationModeEnabled()) {
+            intent.putExtra(WifiManager.EXTRA_WIFI_CREDENTIAL_SSID, config.SSID);
+        }
+        intent.putExtra(WifiManager.EXTRA_WIFI_CREDENTIAL_EVENT_TYPE,
+                wifiCredentialEventType);
+        mContext.createContextAsUser(UserHandle.CURRENT, 0)
+                .sendBroadcastWithMultiplePermissions(
+                        intent,
+                        new String[]{
+                                android.Manifest.permission.RECEIVE_WIFI_CREDENTIAL_CHANGE,
+                                android.Manifest.permission.ACCESS_FINE_LOCATION,
+                        });
+    }
+
+    /**
+     * Connects to a network.
+     *
+     * If the supplied config is not null, then the netId argument will be ignored and the config
+     * will be saved (or updated if its networkId or profile key already exist) and connected to.
+     *
+     * If the supplied config is null, then the netId argument will be matched to a saved config to
+     * be connected to.
+     *
+     * @param config New or existing config to add/update and connect to
+     * @param netId Network ID of existing config to connect to if the supplied config is null
+     * @param callback Listener to notify action result
+     *
+     * see: {@link WifiManager#connect(WifiConfiguration, WifiManager.ActionListener)}
+     *      {@link WifiManager#connect(int, WifiManager.ActionListener)}
      */
     @Override
-    public void connect(WifiConfiguration config, int netId, IBinder binder,
-            @Nullable IActionListener callback, int callbackIdentifier) {
+    public void connect(WifiConfiguration config, int netId, @Nullable IActionListener callback) {
         int uid = Binder.getCallingUid();
         if (!isPrivileged(Binder.getCallingPid(), uid)) {
             throw new SecurityException(TAG + ": Permission denied");
         }
-        mLog.info("connect uid=%").c(uid).flush();
-        if (mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)) {
-            if (config == null) {
-                mWifiMetrics.logUserActionEvent(UserActionEvent.EVENT_MANUAL_CONNECT, netId);
-            } else {
-                mWifiMetrics.logUserActionEvent(
-                        UserActionEvent.EVENT_ADD_OR_UPDATE_NETWORK, config.networkId);
-            }
+        if (config != null) {
+            config.networkId = removeSecurityTypeFromNetworkId(config.networkId);
         }
-        mClientModeImpl.connect(config, netId, binder, callback, callbackIdentifier, uid);
+        final int netIdArg = removeSecurityTypeFromNetworkId(netId);
+        mLog.info("connect uid=%").c(uid).flush();
+        mWifiThreadRunner.post(() -> {
+            ActionListenerWrapper wrapper = new ActionListenerWrapper(callback);
+            final NetworkUpdateResult result;
+            // if connecting using WifiConfiguration, save the network first
+            if (config != null) {
+                if (mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)) {
+                    mWifiMetrics.logUserActionEvent(
+                            UserActionEvent.EVENT_ADD_OR_UPDATE_NETWORK, config.networkId);
+                }
+                result = mWifiConfigManager.addOrUpdateNetwork(config, uid);
+                if (!result.isSuccess()) {
+                    Log.e(TAG, "connect adding/updating config=" + config + " failed");
+                    wrapper.sendFailure(WifiManager.ERROR);
+                    return;
+                }
+                broadcastWifiCredentialChanged(WifiManager.WIFI_CREDENTIAL_SAVED, config);
+            } else {
+                if (mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)) {
+                    mWifiMetrics.logUserActionEvent(
+                            UserActionEvent.EVENT_MANUAL_CONNECT, netIdArg);
+                }
+                result = new NetworkUpdateResult(netIdArg);
+            }
+            WifiConfiguration configuration = mWifiConfigManager
+                    .getConfiguredNetwork(result.getNetworkId());
+            if (configuration == null) {
+                Log.e(TAG, "connect to Invalid network Id=" + netIdArg);
+                wrapper.sendFailure(WifiManager.ERROR);
+                return;
+            }
+            if (configuration.enterpriseConfig != null
+                    && configuration.enterpriseConfig.isAuthenticationSimBased()) {
+                int subId = mWifiCarrierInfoManager.getBestMatchSubscriptionId(configuration);
+                if (!mWifiCarrierInfoManager.isSimReady(subId)) {
+                    Log.e(TAG, "connect to SIM-based config=" + configuration
+                            + "while SIM is absent");
+                    wrapper.sendFailure(WifiManager.ERROR);
+                    return;
+                }
+                if (mWifiCarrierInfoManager.requiresImsiEncryption(subId)
+                        && !mWifiCarrierInfoManager.isImsiEncryptionInfoAvailable(subId)) {
+                    Log.e(TAG, "Imsi protection required but not available for Network="
+                            + configuration);
+                    wrapper.sendFailure(WifiManager.ERROR);
+                    return;
+                }
+            }
+            mMakeBeforeBreakManager.stopAllSecondaryTransientClientModeManagers(() ->
+                    mConnectHelper.connectToNetwork(result, wrapper, uid));
+        });
     }
 
     /**
@@ -4121,37 +4966,62 @@
      * WifiManager.ActionListener)}
      */
     @Override
-    public void save(WifiConfiguration config, IBinder binder, @Nullable IActionListener callback,
-            int callbackIdentifier) {
-        if (!isPrivileged(Binder.getCallingPid(), Binder.getCallingUid())) {
+    public void save(WifiConfiguration config, @Nullable IActionListener callback) {
+        int uid = Binder.getCallingUid();
+        if (!isPrivileged(Binder.getCallingPid(), uid)) {
             throw new SecurityException(TAG + ": Permission denied");
         }
-        mLog.info("save uid=%").c(Binder.getCallingUid()).flush();
-        if (mWifiPermissionsUtil.checkNetworkSettingsPermission(Binder.getCallingUid())) {
-            mWifiMetrics.logUserActionEvent(
-                    UserActionEvent.EVENT_ADD_OR_UPDATE_NETWORK, config.networkId);
+        if (config != null) {
+            config.networkId = removeSecurityTypeFromNetworkId(config.networkId);
         }
-        mClientModeImpl.save(
-                config, binder, callback, callbackIdentifier, Binder.getCallingUid());
+        mLog.info("save uid=%").c(uid).flush();
+        mWifiThreadRunner.post(() -> {
+            ActionListenerWrapper wrapper = new ActionListenerWrapper(callback);
+            NetworkUpdateResult result =
+                    mWifiConfigManager.updateBeforeSaveNetwork(config, uid);
+            if (result.isSuccess()) {
+                broadcastWifiCredentialChanged(WifiManager.WIFI_CREDENTIAL_SAVED, config);
+                mMakeBeforeBreakManager.stopAllSecondaryTransientClientModeManagers(() ->
+                        mActiveModeWarden.getPrimaryClientModeManager()
+                                .saveNetwork(result, wrapper, uid));
+                if (mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)) {
+                    mWifiMetrics.logUserActionEvent(
+                            UserActionEvent.EVENT_ADD_OR_UPDATE_NETWORK, config.networkId);
+                }
+            } else {
+                wrapper.sendFailure(WifiManager.ERROR);
+            }
+        });
     }
 
     /**
      * see {@link android.net.wifi.WifiManager#forget(int, WifiManager.ActionListener)}
      */
     @Override
-    public void forget(int netId, IBinder binder, @Nullable IActionListener callback,
-            int callbackIdentifier) {
+    public void forget(int netId, @Nullable IActionListener callback) {
         int uid = Binder.getCallingUid();
         if (!isPrivileged(Binder.getCallingPid(), uid)) {
             throw new SecurityException(TAG + ": Permission denied");
         }
+        final int internalNetId = removeSecurityTypeFromNetworkId(netId);
         mLog.info("forget uid=%").c(Binder.getCallingUid()).flush();
         if (mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)) {
             // It's important to log this metric before the actual forget executes because
             // the netId becomes invalid after the forget operation.
-            mWifiMetrics.logUserActionEvent(UserActionEvent.EVENT_FORGET_WIFI, netId);
+            mWifiMetrics.logUserActionEvent(UserActionEvent.EVENT_FORGET_WIFI, internalNetId);
         }
-        mClientModeImpl.forget(netId, binder, callback, callbackIdentifier, uid);
+        mWifiThreadRunner.post(() -> {
+            WifiConfiguration config = mWifiConfigManager.getConfiguredNetwork(internalNetId);
+            boolean success = mWifiConfigManager.removeNetwork(internalNetId, uid, null);
+            ActionListenerWrapper wrapper = new ActionListenerWrapper(callback);
+            if (success) {
+                wrapper.sendSuccess();
+                broadcastWifiCredentialChanged(WifiManager.WIFI_CREDENTIAL_FORGOT, config);
+            } else {
+                Log.e(TAG, "Failed to remove network");
+                wrapper.sendFailure(WifiManager.ERROR);
+            }
+        });
     }
 
     /**
@@ -4163,7 +5033,7 @@
         }
         enforceAccessPermission();
 
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("registerScanResultsCallback uid=%").c(Binder.getCallingUid()).flush();
         }
         mWifiThreadRunner.post(() -> {
@@ -4177,7 +5047,7 @@
      * See {@link WifiManager#registerScanResultsCallback(WifiManager.ScanResultsCallback)}
      */
     public void unregisterScanResultsCallback(@NonNull IScanResultsCallback callback) {
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("unregisterScanResultCallback uid=%").c(Binder.getCallingUid()).flush();
         }
         enforceAccessPermission();
@@ -4191,25 +5061,21 @@
      * See {@link WifiManager#addSuggestionConnectionStatusListener(Executor,
      * SuggestionConnectionStatusListener)}
      */
-    public void registerSuggestionConnectionStatusListener(IBinder binder,
-            @NonNull ISuggestionConnectionStatusListener listener,
-            int listenerIdentifier, String packageName, @Nullable String featureId) {
-        if (binder == null) {
-            throw new IllegalArgumentException("Binder must not be null");
-        }
+    public void registerSuggestionConnectionStatusListener(
+            @NonNull ISuggestionConnectionStatusListener listener, String packageName,
+            @Nullable String featureId) {
         if (listener == null) {
             throw new IllegalArgumentException("listener must not be null");
         }
         final int uid = Binder.getCallingUid();
         enforceAccessPermission();
         enforceLocationPermission(packageName, featureId, uid);
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("registerSuggestionConnectionStatusListener uid=%").c(uid).flush();
         }
         mWifiThreadRunner.post(() ->
                 mWifiNetworkSuggestionsManager
-                        .registerSuggestionConnectionStatusListener(binder, listener,
-                                listenerIdentifier, packageName, uid));
+                        .registerSuggestionConnectionStatusListener(listener, packageName, uid));
     }
 
     /**
@@ -4217,17 +5083,16 @@
      * SuggestionConnectionStatusListener)}
      */
     public void unregisterSuggestionConnectionStatusListener(
-            int listenerIdentifier, String packageName) {
+            @NonNull ISuggestionConnectionStatusListener listener, String packageName) {
         enforceAccessPermission();
         int uid = Binder.getCallingUid();
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("unregisterSuggestionConnectionStatusListener uid=%")
                     .c(uid).flush();
         }
         mWifiThreadRunner.post(() ->
                 mWifiNetworkSuggestionsManager
-                        .unregisterSuggestionConnectionStatusListener(listenerIdentifier,
-                                packageName, uid));
+                        .unregisterSuggestionConnectionStatusListener(listener, packageName, uid));
     }
 
     @Override
@@ -4237,7 +5102,7 @@
 
     /**
      * See {@link android.net.wifi.WifiManager#setWifiConnectedNetworkScorer(Executor,
-     * WifiConnectedNetworkScorer)}
+     * WifiManager.WifiConnectedNetworkScorer)}
      *
      * @param binder IBinder instance to allow cleanup if the app dies.
      * @param scorer Wifi connected network scorer to set.
@@ -4257,29 +5122,26 @@
         }
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE, "WifiService");
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("setWifiConnectedNetworkScorer uid=%").c(Binder.getCallingUid()).flush();
         }
         // Post operation to handler thread
-        WifiScoreReport wifiScoreReport = mClientModeImpl.getWifiScoreReport();
-        return mWifiThreadRunner.call(() -> wifiScoreReport.setWifiConnectedNetworkScorer(
-                binder, scorer), false);
+        return mWifiThreadRunner.call(
+                () -> mActiveModeWarden.setWifiConnectedNetworkScorer(binder, scorer), false);
     }
+
     /**
-     * See {@link android.net.wifi.WifiManager#clearWifiConnectedNetworkScorer(
-     * WifiConnectedNetworkScorer)}
+     * See {@link WifiManager#clearWifiConnectedNetworkScorer()}
      */
     @Override
     public void clearWifiConnectedNetworkScorer() {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE, "WifiService");
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("clearWifiConnectedNetworkScorer uid=%").c(Binder.getCallingUid()).flush();
         }
         // Post operation to handler thread
-        WifiScoreReport wifiScoreReport = mClientModeImpl.getWifiScoreReport();
-        mWifiThreadRunner.post(() ->
-                wifiScoreReport.clearWifiConnectedNetworkScorer());
+        mWifiThreadRunner.post(() -> mActiveModeWarden.clearWifiConnectedNetworkScorer());
     }
 
     /**
@@ -4300,7 +5162,7 @@
     @Override
     public boolean isScanThrottleEnabled() {
         enforceAccessPermission();
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("isScanThrottleEnabled uid=%").c(Binder.getCallingUid()).flush();
         }
         return mWifiThreadRunner.call(()-> mScanRequestProxy.isScanThrottleEnabled(), true);
@@ -4324,9 +5186,204 @@
     @Override
     public boolean isAutoWakeupEnabled() {
         enforceAccessPermission();
-        if (mVerboseLoggingEnabled) {
+        if (isVerboseLoggingEnabled()) {
             mLog.info("isAutoWakeupEnabled uid=%").c(Binder.getCallingUid()).flush();
         }
         return mWifiThreadRunner.call(()-> mWifiInjector.getWakeupController().isEnabled(), false);
     }
+
+    /**
+     * See {@link android.net.wifi.WifiManager#setCarrierNetworkOffloadEnabled(int, boolean, boolean)}
+     */
+    @Override
+    public void setCarrierNetworkOffloadEnabled(int subscriptionId, boolean merged,
+            boolean enabled) {
+        if (!isSettingsOrSuw(Binder.getCallingPid(), Binder.getCallingUid())) {
+            throw new SecurityException(TAG + ": Permission denied");
+        }
+        if (isVerboseLoggingEnabled()) {
+            mLog.info("setCarrierNetworkOffloadEnabled uid=%").c(Binder.getCallingUid()).flush();
+        }
+        mWifiThreadRunner.post(() ->
+                mWifiCarrierInfoManager.setCarrierNetworkOffloadEnabled(subscriptionId, merged, enabled));
+    }
+
+    /**
+     * See {@link android.net.wifi.WifiManager#isCarrierNetworkOffloadEnabled(int, boolean)}
+     */
+    @Override
+    public boolean isCarrierNetworkOffloadEnabled(int subId, boolean merged) {
+        enforceAccessPermission();
+        if (isVerboseLoggingEnabled()) {
+            mLog.info("isCarrierNetworkOffload uid=%").c(Binder.getCallingUid()).flush();
+        }
+
+        return mWifiThreadRunner.call(()->
+                mWifiCarrierInfoManager.isCarrierNetworkOffloadEnabled(subId, merged), true);
+    }
+
+    /**
+     * See {@link android.net.wifi.WifiManager#addSuggestionUserApprovalStatusListener(Executor,
+     * WifiManager.SuggestionUserApprovalStatusListener)}
+     */
+    @Override
+    public void addSuggestionUserApprovalStatusListener(
+            ISuggestionUserApprovalStatusListener listener, String packageName) {
+        if (listener == null) {
+            throw new NullPointerException("listener must not be null");
+        }
+        final int uid = Binder.getCallingUid();
+        enforceAccessPermission();
+        long callingIdentity = Binder.clearCallingIdentity();
+        try {
+            if (!mWifiPermissionsUtil.doesUidBelongToCurrentUser(uid)) {
+                Log.e(TAG, "UID " + uid + " not visible to the current user");
+                throw new SecurityException("UID " + uid + " not visible to the current user");
+            }
+        } finally {
+            // restore calling identity
+            Binder.restoreCallingIdentity(callingIdentity);
+        }
+        if (isVerboseLoggingEnabled()) {
+            mLog.info("addSuggestionUserApprovalStatusListener uid=%").c(uid).flush();
+        }
+        mWifiThreadRunner.post(() -> mWifiNetworkSuggestionsManager
+                .addSuggestionUserApprovalStatusListener(listener, packageName, uid));
+    }
+
+    /**
+     * See {@link android.net.wifi.WifiManager#removeSuggestionUserApprovalStatusListener(
+     * WifiManager.SuggestionUserApprovalStatusListener)}
+     */
+    @Override
+    public void removeSuggestionUserApprovalStatusListener(
+            ISuggestionUserApprovalStatusListener listener, String packageName) {
+        enforceAccessPermission();
+        int uid = Binder.getCallingUid();
+        long callingIdentity = Binder.clearCallingIdentity();
+        try {
+            if (!mWifiPermissionsUtil.doesUidBelongToCurrentUser(uid)) {
+                Log.e(TAG, "UID " + uid + " not visible to the current user");
+                throw new SecurityException("UID " + uid + " not visible to the current user");
+            }
+        } finally {
+            // restore calling identity
+            Binder.restoreCallingIdentity(callingIdentity);
+        }
+        if (isVerboseLoggingEnabled()) {
+            mLog.info("removeSuggestionUserApprovalStatusListener uid=%")
+                    .c(uid).flush();
+        }
+        mWifiThreadRunner.post(() ->
+                mWifiNetworkSuggestionsManager
+                        .removeSuggestionUserApprovalStatusListener(listener, packageName, uid));
+    }
+
+    /**
+     * See {@link android.net.wifi.WifiManager#setEmergencyScanRequestInProgress(boolean)}.
+     */
+    @Override
+    public void setEmergencyScanRequestInProgress(boolean inProgress) {
+        enforceNetworkStackPermission();
+        int uid = Binder.getCallingUid();
+        mLog.info("setEmergencyScanRequestInProgress uid=%").c(uid).flush();
+        mActiveModeWarden.setEmergencyScanRequestInProgress(inProgress);
+    }
+
+    /**
+     * See {@link android.net.wifi.WifiManager#removeAppState(int, String)}.
+     */
+    @Override
+    public void removeAppState(int targetAppUid, @NonNull String targetAppPackageName) {
+        enforceNetworkSettingsPermission();
+        mLog.info("removeAppState uid=%").c(Binder.getCallingUid()).flush();
+
+        mWifiThreadRunner.post(() -> {
+            removeAppStateInternal(targetAppUid, targetAppPackageName);
+        });
+    }
+
+    /**
+     * See {@link android.net.wifi.WifiManager#setWifiScoringEnabled(boolean)}.
+     */
+    @Override
+    public boolean setWifiScoringEnabled(boolean enabled) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.NETWORK_SETTINGS, "WifiService");
+        // Post operation to handler thread
+        return mWifiThreadRunner.call(
+                () -> mSettingsStore.handleWifiScoringEnabled(enabled), false);
+    }
+
+    @VisibleForTesting
+    static boolean isValidBandForGetUsableChannels(@WifiScanner.WifiBand int band) {
+        switch (band) {
+            case WifiScanner.WIFI_BAND_UNSPECIFIED:
+            case WifiScanner.WIFI_BAND_24_GHZ:
+            case WifiScanner.WIFI_BAND_5_GHZ_WITH_DFS:
+            case WifiScanner.WIFI_BAND_BOTH_WITH_DFS:
+            case WifiScanner.WIFI_BAND_6_GHZ:
+            case WifiScanner.WIFI_BAND_24_5_WITH_DFS_6_GHZ:
+            case WifiScanner.WIFI_BAND_60_GHZ:
+            case WifiScanner.WIFI_BAND_24_5_WITH_DFS_6_60_GHZ:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * See {@link android.net.wifi.WifiManager#getUsableChannels(int, int) and
+     * See {@link android.net.wifi.WifiManager#getAllowedChannels(int, int).
+     *
+     * @throws SecurityException if the caller does not have permission
+     * or IllegalArgumentException if the band is invalid for this method.
+     */
+    @Override
+    public List<WifiAvailableChannel> getUsableChannels(@WifiScanner.WifiBand int band,
+            @WifiAvailableChannel.OpMode int mode, @WifiAvailableChannel.Filter int filter) {
+        // Location mode must be enabled
+        if (!mWifiPermissionsUtil.isLocationModeEnabled()) {
+            throw new SecurityException("Location mode is disabled for the device");
+        }
+        final int uid = Binder.getCallingUid();
+        if (isVerboseLoggingEnabled()) {
+            mLog.info("getUsableChannels uid=%").c(Binder.getCallingUid()).flush();
+        }
+        if (!mWifiPermissionsUtil.checkCallersHardwareLocationPermission(uid)) {
+            throw new SecurityException("UID " + uid + " does not have location h/w permission");
+        }
+        if (!isValidBandForGetUsableChannels(band)) {
+            throw new IllegalArgumentException("Unsupported band: " + band);
+        }
+        List<WifiAvailableChannel> channels = mWifiThreadRunner.call(
+                () -> mWifiNative.getUsableChannels(band, mode, filter), null);
+        if (channels == null) {
+            throw new UnsupportedOperationException();
+        }
+        return channels;
+    }
+
+    private void resetNotificationManager() {
+        mWifiInjector.getWifiNotificationManager().createNotificationChannels();
+        mWifiInjector.getOpenNetworkNotifier().clearPendingNotification(false);
+        mWifiCarrierInfoManager.resetNotification();
+        mWifiNetworkSuggestionsManager.resetNotification();
+        mWifiInjector.getWakeupController().resetNotification();
+    }
+
+    /**
+     * See {@link android.net.wifi.WifiManager#flushPasspointAnqpCache()}.
+     */
+    @Override
+    public void flushPasspointAnqpCache(@NonNull String packageName) {
+        mWifiPermissionsUtil.checkPackage(Binder.getCallingUid(), packageName);
+
+        if (!isDeviceOrProfileOwner(Binder.getCallingUid(), packageName)) {
+            enforceAnyPermissionOf(android.Manifest.permission.NETWORK_SETTINGS,
+                    android.Manifest.permission.NETWORK_MANAGED_PROVISIONING,
+                    android.Manifest.permission.NETWORK_CARRIER_PROVISIONING);
+        }
+        mWifiThreadRunner.post(mPasspointManager::clearAnqpRequestsAndFlushCache);
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiSettingsConfigStore.java b/service/java/com/android/server/wifi/WifiSettingsConfigStore.java
index d77aa48..23d7f6c 100644
--- a/service/java/com/android/server/wifi/WifiSettingsConfigStore.java
+++ b/service/java/com/android/server/wifi/WifiSettingsConfigStore.java
@@ -82,6 +82,24 @@
     public static final Key<String> WIFI_P2P_DEVICE_NAME =
             new Key<>("wifi_p2p_device_name", null);
 
+    /**
+     * Whether Wifi scoring is enabled or not.
+     */
+    public static final Key<Boolean> WIFI_SCORING_ENABLED =
+            new Key<>("wifi_scoring_enabled", true);
+
+    /**
+     * Store the STA factory MAC address retrieved from the driver on the first bootup.
+     */
+    public static final Key<String> WIFI_STA_FACTORY_MAC_ADDRESS =
+            new Key<>("wifi_sta_factory_mac_address", null);
+
+    /**
+     * Store the default country code updated via {@link WifiManager#setDefaultCountryCode(String)}
+     */
+    public static final Key<String> WIFI_DEFAULT_COUNTRY_CODE =
+            new Key<>("wifi_default_country_code", WifiCountryCode.getOemDefaultCountryCode());
+
     /******** Wifi shared pref keys ***************/
 
     private final Context mContext;
diff --git a/service/java/com/android/server/wifi/WifiSettingsStore.java b/service/java/com/android/server/wifi/WifiSettingsStore.java
index 5cc27d0..39fff7e 100644
--- a/service/java/com/android/server/wifi/WifiSettingsStore.java
+++ b/service/java/com/android/server/wifi/WifiSettingsStore.java
@@ -65,10 +65,18 @@
         return mAirplaneModeOn;
     }
 
+    public synchronized boolean isScanAlwaysAvailableToggleEnabled() {
+        return getPersistedScanAlwaysAvailable();
+    }
+
     public synchronized boolean isScanAlwaysAvailable() {
         return !mAirplaneModeOn && getPersistedScanAlwaysAvailable();
     }
 
+    public synchronized boolean isWifiScoringEnabled() {
+        return getPersistedWifiScoringEnabled();
+    }
+
     public synchronized boolean handleWifiToggled(boolean wifiEnabled) {
         // Can Wi-Fi be toggled in airplane mode ?
         if (mAirplaneModeOn && !isAirplaneToggleable()) {
@@ -117,9 +125,16 @@
         persistScanAlwaysAvailableState(isAvailable);
     }
 
+    synchronized boolean handleWifiScoringEnabled(boolean enabled) {
+        persistWifiScoringEnabledState(enabled);
+        return true;
+    }
+
     void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("mPersistWifiState " + mPersistWifiState);
-        pw.println("mAirplaneModeOn " + mAirplaneModeOn);
+        pw.println("WifiState " + getPersistedWifiState());
+        pw.println("AirplaneModeOn " + getPersistedAirplaneModeOn());
+        pw.println("ScanAlwaysAvailable " + getPersistedScanAlwaysAvailable());
+        pw.println("WifiScoringState " + getPersistedWifiScoringEnabled());
     }
 
     private void persistWifiState(int state) {
@@ -133,6 +148,11 @@
                 WifiSettingsConfigStore.WIFI_SCAN_ALWAYS_AVAILABLE, isAvailable);
     }
 
+    private void persistWifiScoringEnabledState(boolean enabled) {
+        mSettingsConfigStore.put(
+                WifiSettingsConfigStore.WIFI_SCORING_ENABLED, enabled);
+    }
+
     /* Does Wi-Fi need to be disabled when airplane mode is on ? */
     private boolean isAirplaneSensitive() {
         String airplaneModeRadios = Settings.Global.getString(mContext.getContentResolver(),
@@ -168,4 +188,9 @@
         return mSettingsConfigStore.get(
                 WifiSettingsConfigStore.WIFI_SCAN_ALWAYS_AVAILABLE);
     }
+
+    private boolean getPersistedWifiScoringEnabled() {
+        return mSettingsConfigStore.get(
+                WifiSettingsConfigStore.WIFI_SCORING_ENABLED);
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiShellCommand.java b/service/java/com/android/server/wifi/WifiShellCommand.java
index d445394..a86cd8f 100644
--- a/service/java/com/android/server/wifi/WifiShellCommand.java
+++ b/service/java/com/android/server/wifi/WifiShellCommand.java
@@ -17,12 +17,20 @@
 package com.android.server.wifi;
 
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.wifi.WifiConfiguration.METERED_OVERRIDE_METERED;
+import static android.net.wifi.WifiManager.WIFI_STATE_DISABLED;
 import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED;
 
+import static com.android.server.wifi.SelfRecovery.REASON_API_CALL;
+
+import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.pm.ParceledListSlice;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.net.ConnectivityManager;
 import android.net.MacAddress;
 import android.net.Network;
@@ -30,39 +38,62 @@
 import android.net.NetworkRequest;
 import android.net.wifi.IActionListener;
 import android.net.wifi.IScoreUpdateObserver;
+import android.net.wifi.ISoftApCallback;
 import android.net.wifi.IWifiConnectedNetworkScorer;
 import android.net.wifi.ScanResult;
+import android.net.wifi.SoftApCapability;
 import android.net.wifi.SoftApConfiguration;
+import android.net.wifi.SoftApInfo;
 import android.net.wifi.SupplicantState;
+import android.net.wifi.WifiClient;
 import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiConnectedSessionInfo;
 import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
 import android.net.wifi.WifiNetworkSpecifier;
 import android.net.wifi.WifiNetworkSuggestion;
 import android.net.wifi.WifiScanner;
-import android.net.wifi.nl80211.WifiNl80211Manager;
+import android.net.wifi.WifiSsid;
 import android.os.Binder;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.telephony.Annotation;
+import android.telephony.PhysicalChannelConfig;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Pair;
 
+import androidx.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.modules.utils.BasicShellCommandHandler;
+import com.android.modules.utils.ParceledListSlice;
+import com.android.modules.utils.build.SdkLevel;
+import com.android.server.wifi.ClientMode.LinkProbeCallback;
+import com.android.server.wifi.coex.CoexManager;
+import com.android.server.wifi.coex.CoexUtils;
+import com.android.server.wifi.hotspot2.NetworkDetail;
 import com.android.server.wifi.util.ApConfigUtil;
 import com.android.server.wifi.util.ArrayUtils;
-import com.android.server.wifi.util.GeneralUtil;
 import com.android.server.wifi.util.ScanResultUtil;
 
 import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 /**
  * Interprets and executes 'adb shell cmd wifi [args]'.
@@ -76,7 +107,9 @@
  * enforce the corresponding API permissions.
  */
 public class WifiShellCommand extends BasicShellCommandHandler {
-    private static String SHELL_PACKAGE_NAME = "com.android.shell";
+    @VisibleForTesting
+    public static String SHELL_PACKAGE_NAME = "com.android.shell";
+
     // These don't require root access.
     // However, these do perform permission checks in the corresponding WifiService methods.
     private static final String[] NON_PRIVILEGED_COMMANDS = {
@@ -87,6 +120,7 @@
             "get-country-code",
             "help",
             "-h",
+            "is-verbose-logging",
             "list-scan-results",
             "list-networks",
             "list-suggestions",
@@ -106,32 +140,85 @@
     private static final Map<String, Pair<NetworkRequest, ConnectivityManager.NetworkCallback>>
             sActiveRequests = new ConcurrentHashMap<>();
 
-    private final ClientModeImpl mClientModeImpl;
+    private final ActiveModeWarden mActiveModeWarden;
+    private final WifiGlobals mWifiGlobals;
     private final WifiLockManager mWifiLockManager;
     private final WifiNetworkSuggestionsManager mWifiNetworkSuggestionsManager;
     private final WifiConfigManager mWifiConfigManager;
     private final WifiNative mWifiNative;
-    private final HostapdHal mHostapdHal;
+    private final CoexManager mCoexManager;
     private final WifiCountryCode mWifiCountryCode;
     private final WifiLastResortWatchdog mWifiLastResortWatchdog;
     private final WifiServiceImpl mWifiService;
     private final Context mContext;
     private final ConnectivityManager mConnectivityManager;
     private final WifiCarrierInfoManager mWifiCarrierInfoManager;
+    private final WifiNetworkFactory mWifiNetworkFactory;
+    private final SelfRecovery mSelfRecovery;
+    private final WifiThreadRunner mWifiThreadRunner;
+    private final WifiApConfigStore mWifiApConfigStore;
+    private int mSapState = WifiManager.WIFI_STATE_UNKNOWN;
+    private final ScanRequestProxy mScanRequestProxy;
 
-    WifiShellCommand(WifiInjector wifiInjector, WifiServiceImpl wifiService, Context context) {
-        mClientModeImpl = wifiInjector.getClientModeImpl();
+    /**
+     * Used for shell command testing of scorer.
+     */
+    public static class WifiScorer extends IWifiConnectedNetworkScorer.Stub {
+        private final WifiServiceImpl mWifiService;
+        private final CountDownLatch mCountDownLatch;
+        private Integer mSessionId;
+        private IScoreUpdateObserver mScoreUpdateObserver;
+
+        public WifiScorer(WifiServiceImpl wifiService, CountDownLatch countDownLatch) {
+            mWifiService = wifiService;
+            mCountDownLatch  = countDownLatch;
+        }
+
+        @Override
+        public void onStart(WifiConnectedSessionInfo sessionInfo) {
+            mSessionId = sessionInfo.getSessionId();
+            mCountDownLatch.countDown();
+        }
+        @Override
+        public void onStop(int sessionId) {
+            // clear the external scorer on disconnect.
+            mWifiService.clearWifiConnectedNetworkScorer();
+        }
+        @Override
+        public void onSetScoreUpdateObserver(IScoreUpdateObserver observerImpl) {
+            mScoreUpdateObserver = observerImpl;
+            mCountDownLatch.countDown();
+        }
+
+        public Integer getSessionId() {
+            return mSessionId;
+        }
+
+        public IScoreUpdateObserver getScoreUpdateObserver() {
+            return mScoreUpdateObserver;
+        }
+    }
+
+    WifiShellCommand(WifiInjector wifiInjector, WifiServiceImpl wifiService, Context context,
+            WifiGlobals wifiGlobals, WifiThreadRunner wifiThreadRunner) {
+        mWifiGlobals = wifiGlobals;
+        mWifiThreadRunner = wifiThreadRunner;
+        mActiveModeWarden = wifiInjector.getActiveModeWarden();
         mWifiLockManager = wifiInjector.getWifiLockManager();
         mWifiNetworkSuggestionsManager = wifiInjector.getWifiNetworkSuggestionsManager();
         mWifiConfigManager = wifiInjector.getWifiConfigManager();
-        mHostapdHal = wifiInjector.getHostapdHal();
         mWifiNative = wifiInjector.getWifiNative();
+        mCoexManager = wifiInjector.getCoexManager();
         mWifiCountryCode = wifiInjector.getWifiCountryCode();
         mWifiLastResortWatchdog = wifiInjector.getWifiLastResortWatchdog();
         mWifiService = wifiService;
         mContext = context;
         mConnectivityManager = context.getSystemService(ConnectivityManager.class);
         mWifiCarrierInfoManager = wifiInjector.getWifiCarrierInfoManager();
+        mWifiNetworkFactory = wifiInjector.getWifiNetworkFactory();
+        mSelfRecovery = wifiInjector.getSelfRecovery();
+        mWifiApConfigStore = wifiInjector.getWifiApConfigStore();
+        mScanRequestProxy = wifiInjector.getScanRequestProxy();
     }
 
     @Override
@@ -145,7 +232,8 @@
             final int uid = Binder.getCallingUid();
             if (uid != Process.ROOT_UID) {
                 throw new SecurityException(
-                        "Uid " + uid + " does not have access to " + cmd + " wifi command");
+                        "Uid " + uid + " does not have access to " + cmd + " wifi command "
+                                + "(or such command doesn't exist)");
             }
         }
 
@@ -154,12 +242,12 @@
             switch (cmd) {
                 case "set-ipreach-disconnect": {
                     boolean enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled");
-                    mClientModeImpl.setIpReachabilityDisconnectEnabled(enabled);
+                    mWifiGlobals.setIpReachabilityDisconnectEnabled(enabled);
                     return 0;
                 }
                 case "get-ipreach-disconnect":
                     pw.println("IPREACH_DISCONNECT state is "
-                            + mClientModeImpl.getIpReachabilityDisconnectEnabled());
+                            + mWifiGlobals.getIpReachabilityDisconnectEnabled());
                     return 0;
                 case "set-poll-rssi-interval-msecs":
                     int newPollIntervalMsecs;
@@ -179,11 +267,11 @@
                         return -1;
                     }
 
-                    mClientModeImpl.setPollRssiIntervalMsecs(newPollIntervalMsecs);
+                    mWifiGlobals.setPollRssiIntervalMillis(newPollIntervalMsecs);
                     return 0;
                 case "get-poll-rssi-interval-msecs":
-                    pw.println("ClientModeImpl.mPollRssiIntervalMsecs = "
-                            + mClientModeImpl.getPollRssiIntervalMsecs());
+                    pw.println("WifiGlobals.getPollRssiIntervalMillis() = "
+                            + mWifiGlobals.getPollRssiIntervalMillis());
                     return 0;
                 case "force-hi-perf-mode": {
                     boolean enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled");
@@ -202,7 +290,8 @@
                 case "network-suggestions-set-user-approved": {
                     String packageName = getNextArgRequired();
                     boolean approved = getNextArgRequiredTrueOrFalse("yes", "no");
-                    mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(approved, packageName);
+                    mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(approved,
+                            Binder.getCallingUid(), packageName);
                     return 0;
                 }
                 case "network-suggestions-has-user-approved": {
@@ -260,7 +349,7 @@
                 }
                 case "network-requests-remove-user-approved-access-points": {
                     String packageName = getNextArgRequired();
-                    mClientModeImpl.removeNetworkRequestUserApprovedAccessPointsForApp(packageName);
+                    mWifiNetworkFactory.removeUserApprovedAccessPointsForApp(packageName);
                     return 0;
                 }
                 case "clear-user-disabled-networks": {
@@ -270,6 +359,31 @@
                 case "send-link-probe": {
                     return sendLinkProbe(pw);
                 }
+                case "force-softap-band": {
+                    boolean forceBandEnabled = getNextArgRequiredTrueOrFalse("enabled", "disabled");
+                    if (forceBandEnabled) {
+                        String forcedBand = getNextArgRequired();
+                        if (forcedBand.equals("2")) {
+                            mWifiApConfigStore.enableForceSoftApBandOrChannel(
+                                    SoftApConfiguration.BAND_2GHZ, 0);
+                        } else if (forcedBand.equals("5")) {
+                            mWifiApConfigStore.enableForceSoftApBandOrChannel(
+                                    SoftApConfiguration.BAND_5GHZ, 0);
+                        } else if (forcedBand.equals("6")) {
+                            mWifiApConfigStore.enableForceSoftApBandOrChannel(
+                                    SoftApConfiguration.BAND_6GHZ, 0);
+                        } else {
+                            pw.println("Invalid argument to 'force-softap-band enabled' "
+                                    + "- must be a valid band integer (2|5|6)");
+                            return -1;
+                        }
+                        return 0;
+                    } else {
+                        mWifiApConfigStore.disableForceSoftApBandOrChannel();
+                        return 0;
+                    }
+
+                }
                 case "force-softap-channel": {
                     boolean enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled");
                     if (enabled) {
@@ -281,37 +395,91 @@
                                     + "- must be a positive integer");
                             return -1;
                         }
-                        int apChannel = ScanResult.convertFrequencyMhzToChannel(apChannelMHz);
+                        int apChannel = ScanResult.convertFrequencyMhzToChannelIfSupported(
+                                apChannelMHz);
                         int band = ApConfigUtil.convertFrequencyToBand(apChannelMHz);
-                        if (apChannel == -1 || band == -1 || !isApChannelMHzValid(apChannelMHz)) {
+                        pw.println("channel: " + apChannel + " band: " + band);
+                        if (apChannel == -1 || band == -1) {
                             pw.println("Invalid argument to 'force-softap-channel enabled' "
                                     + "- must be a valid WLAN channel");
                             return -1;
                         }
-
-                        if ((band == SoftApConfiguration.BAND_5GHZ
+                        boolean isTemporarilyEnablingWifiNeeded = mWifiService.getWifiEnabledState()
+                                != WIFI_STATE_ENABLED;
+                        if (isTemporarilyEnablingWifiNeeded) {
+                            waitForWifiEnabled(true);
+                        }
+                        // Following calls will fail if wifi is not enabled
+                        boolean isValidChannel = isApChannelMHzValid(pw, apChannelMHz);
+                        if (isTemporarilyEnablingWifiNeeded) {
+                            waitForWifiEnabled(false);
+                        }
+                        if (!isValidChannel
+                                || (band == SoftApConfiguration.BAND_5GHZ
                                 && !mWifiService.is5GHzBandSupported())
                                 || (band == SoftApConfiguration.BAND_6GHZ
-                                && !mWifiService.is6GHzBandSupported())) {
+                                && !mWifiService.is6GHzBandSupported())
+                                || (band == SoftApConfiguration.BAND_60GHZ
+                                && !mWifiService.is60GHzBandSupported())) {
                             pw.println("Invalid argument to 'force-softap-channel enabled' "
-                                    + "- channel band is not supported by the device");
+                                    + "- must be a valid WLAN channel"
+                                    + " in a band supported by the device");
                             return -1;
                         }
-
-                        mHostapdHal.enableForceSoftApChannel(apChannel, band);
+                        mWifiApConfigStore.enableForceSoftApBandOrChannel(band, apChannel);
                         return 0;
                     } else {
-                        mHostapdHal.disableForceSoftApChannel();
+                        mWifiApConfigStore.disableForceSoftApBandOrChannel();
                         return 0;
                     }
                 }
                 case "start-softap": {
+                    CountDownLatch countDownLatch = new CountDownLatch(1);
+                    ISoftApCallback.Stub softApCallback = new ISoftApCallback.Stub() {
+                        @Override
+                        public void onStateChanged(int state, int failureReason) {
+                            pw.println("onStateChanged with state: " + state
+                                    + " failure reason: " + failureReason);
+                            mSapState = state;
+                            if (state == WifiManager.WIFI_AP_STATE_ENABLED) {
+                                pw.println(" SAP is enabled successfully");
+                                // Skip countDown() and wait for onInfoChanged() which has
+                                // the confirmed softAp channel information
+                            } else if (state == WifiManager.WIFI_AP_STATE_DISABLED) {
+                                pw.println(" SAP is disabled");
+                            } else if (state == WifiManager.WIFI_AP_STATE_FAILED) {
+                                pw.println(" SAP failed to start");
+                                countDownLatch.countDown();
+                            }
+                        }
+
+                        @Override
+                        public void onConnectedClientsOrInfoChanged(Map<String, SoftApInfo> infos,
+                                Map<String, List<WifiClient>> clients, boolean isBridged,
+                                boolean isRegistration) {
+                            if (mSapState == WifiManager.WIFI_AP_STATE_ENABLED) {
+                                countDownLatch.countDown();
+                            }
+                        }
+
+                        @Override
+                        public void onCapabilityChanged(SoftApCapability capability) {
+                            pw.println("onCapabilityChanged " + capability);
+                        }
+
+                        @Override
+                        public void onBlockedClientConnecting(WifiClient client, int reason) {
+                        }
+
+                    };
+                    mWifiService.registerSoftApCallback(softApCallback);
                     SoftApConfiguration config = buildSoftApConfiguration(pw);
-                    if (mWifiService.startTetheredHotspot(config)) {
-                        pw.println("Soft AP started successfully");
-                    } else {
+                    if (!mWifiService.startTetheredHotspot(config, SHELL_PACKAGE_NAME)) {
                         pw.println("Soft AP failed to start. Please check config parameters");
                     }
+                    // Wait for softap to start and complete callback
+                    countDownLatch.await(3000, TimeUnit.MILLISECONDS);
+                    mWifiService.unregisterSoftApCallback(softApCallback);
                     return 0;
                 }
                 case "stop-softap": {
@@ -326,16 +494,16 @@
                     boolean enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled");
                     if (enabled) {
                         String countryCode = getNextArgRequired();
-                        if (!(countryCode.length() == 2
-                                && countryCode.chars().allMatch(Character::isLetter))) {
-                            pw.println("Invalid argument to 'force-country-code enabled' "
-                                    + "- must be a two-letter string");
+                        if (!WifiCountryCode.isValid(countryCode)) {
+                            pw.println("Invalid argument: Country code must be a 2-Character"
+                                    + " alphanumeric code. But got countryCode " + countryCode
+                                    + " instead");
                             return -1;
                         }
-                        mWifiCountryCode.enableForceCountryCode(countryCode);
+                        mWifiCountryCode.setOverrideCountryCode(countryCode);
                         return 0;
                     } else {
-                        mWifiCountryCode.disableForceCountryCode();
+                        mWifiCountryCode.clearOverrideCountryCode();
                         return 0;
                     }
                 }
@@ -361,7 +529,7 @@
                 }
                 case "set-scan-always-available": {
                     boolean enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled");
-                    mWifiService.setScanAlwaysAvailable(enabled);
+                    mWifiService.setScanAlwaysAvailable(enabled, SHELL_PACKAGE_NAME);
                     return 0;
                 }
                 case "get-softap-supported-features":
@@ -372,8 +540,20 @@
                     if (ApConfigUtil.isWpa3SaeSupported(mContext)) {
                         pw.println("wifi_softap_wpa3_sae_supported");
                     }
+                    if ((mWifiService.getSupportedFeatures()
+                            & WifiManager.WIFI_FEATURE_BRIDGED_AP)
+                            == WifiManager.WIFI_FEATURE_BRIDGED_AP) {
+                        pw.println("wifi_softap_bridged_ap_supported");
+                    }
+                    if ((mWifiService.getSupportedFeatures()
+                            & WifiManager.WIFI_FEATURE_STA_BRIDGED_AP)
+                            == WifiManager.WIFI_FEATURE_STA_BRIDGED_AP) {
+                        pw.println("wifi_softap_bridged_ap_with_sta_supported");
+                    }
                     return 0;
                 case "settings-reset":
+                    mWifiNative.stopFakingScanDetails();
+                    mWifiNative.resetFakeScanDetails();
                     mWifiService.factoryReset(SHELL_PACKAGE_NAME);
                     return 0;
                 case "list-scan-results":
@@ -391,24 +571,17 @@
                     return 0;
                 case "list-networks":
                     ParceledListSlice<WifiConfiguration> networks =
-                            mWifiService.getConfiguredNetworks(SHELL_PACKAGE_NAME, null);
+                            mWifiService.getConfiguredNetworks(SHELL_PACKAGE_NAME, null, false);
                     if (networks == null || networks.getList().isEmpty()) {
                         pw.println("No networks");
                     } else {
                         pw.println("Network Id      SSID                         Security type");
                         for (WifiConfiguration network : networks.getList()) {
-                            String securityType = null;
-                            if (WifiConfigurationUtil.isConfigForSaeNetwork(network)) {
-                                securityType = "wpa3";
-                            } else if (WifiConfigurationUtil.isConfigForPskNetwork(network)) {
-                                securityType = "wpa2";
-                            } else if (WifiConfigurationUtil.isConfigForEapNetwork(network)) {
-                                securityType = "eap";
-                            } else if (WifiConfigurationUtil.isConfigForOweNetwork(network)) {
-                                securityType = "owe";
-                            } else if (WifiConfigurationUtil.isConfigForOpenNetwork(network)) {
-                                securityType = "open";
-                            }
+                            String securityType = network.getSecurityParamsList().stream()
+                                    .map(p -> WifiConfiguration.getSecurityTypeName(
+                                                    p.getSecurityType())
+                                            + (p.isAddedByAutoUpgrade() ? "^" : ""))
+                                    .collect(Collectors.joining("/"));
                             pw.println(String.format("%-12d %-32s %-4s",
                                     network.networkId, WifiInfo.sanitizeSsid(network.SSID),
                                     securityType));
@@ -431,8 +604,7 @@
                         }
                     };
                     WifiConfiguration config = buildWifiConfiguration(pw);
-                    mWifiService.connect(
-                            config, -1, new Binder(), actionListener, actionListener.hashCode());
+                    mWifiService.connect(config, -1, actionListener);
                     // wait for status.
                     countDownLatch.await(500, TimeUnit.MILLISECONDS);
                     setAutoJoin(pw, config.SSID, config.allowAutojoin);
@@ -454,8 +626,7 @@
                         }
                     };
                     WifiConfiguration config = buildWifiConfiguration(pw);
-                    mWifiService.save(
-                            config, new Binder(), actionListener, actionListener.hashCode());
+                    mWifiService.save(config, actionListener);
                     // wait for status.
                     countDownLatch.await(500, TimeUnit.MILLISECONDS);
                     setAutoJoin(pw, config.SSID, config.allowAutojoin);
@@ -477,13 +648,22 @@
                             countDownLatch.countDown();
                         }
                     };
-                    mWifiService.forget(
-                            Integer.parseInt(networkId), new Binder(), actionListener,
-                            actionListener.hashCode());
+                    mWifiService.forget(Integer.parseInt(networkId), actionListener);
                     // wait for status.
                     countDownLatch.await(500, TimeUnit.MILLISECONDS);
                     return 0;
                 }
+                case "pmksa-flush": {
+                    String networkId = getNextArgRequired();
+                    int netId = Integer.parseInt(networkId);
+                    WifiConfiguration config = mWifiConfigManager.getConfiguredNetwork(netId);
+                    if (config == null) {
+                        pw.println("No Wifi config corresponding to networkId: " + netId);
+                        return -1;
+                    }
+                    mWifiNative.removeNetworkCachedData(netId);
+                    return 0;
+                }
                 case "status":
                     printStatus(pw);
                     return 0;
@@ -492,10 +672,66 @@
                     mWifiService.enableVerboseLogging(enabled ? 1 : 0);
                     return 0;
                 }
+                case "is-verbose-logging": {
+                    int enabled = mWifiService.getVerboseLoggingLevel();
+                    pw.println(enabled > 0 ? "enabled" : "disabled");
+                    return 0;
+                }
+                case "start-restricting-auto-join-to-subscription-id": {
+                    if (!SdkLevel.isAtLeastS()) {
+                        pw.println("This feature is only supported on SdkLevel S or later.");
+                        return -1;
+                    }
+                    int subId = Integer.parseInt(getNextArgRequired());
+                    mWifiService.startRestrictingAutoJoinToSubscriptionId(subId);
+                    return 0;
+                }
+                case "stop-restricting-auto-join-to-subscription-id": {
+                    if (!SdkLevel.isAtLeastS()) {
+                        pw.println("This feature is only supported on SdkLevel S or later.");
+                        return -1;
+                    }
+                    mWifiService.stopRestrictingAutoJoinToSubscriptionId();
+                    return 0;
+                }
                 case "add-suggestion": {
                     WifiNetworkSuggestion suggestion = buildSuggestion(pw);
-                    mWifiService.addNetworkSuggestions(
+                    if (suggestion  == null) {
+                        pw.println("Invalid network suggestion parameter");
+                        return -1;
+                    }
+                    int errorCode = mWifiService.addNetworkSuggestions(
                             Arrays.asList(suggestion), SHELL_PACKAGE_NAME, null);
+                    if (errorCode != WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS) {
+                        pw.println("Add network suggestion failed with error code: " + errorCode);
+                        return -1;
+                    }
+                    // untrusted/oem-paid networks need a corresponding NetworkRequest.
+                    if (suggestion.isUntrusted()
+                            || (SdkLevel.isAtLeastS()
+                            && (suggestion.isOemPaid() || suggestion.isOemPrivate()))) {
+                        NetworkRequest.Builder networkRequestBuilder =
+                                new NetworkRequest.Builder()
+                                        .addTransportType(TRANSPORT_WIFI);
+                        if (suggestion.isUntrusted()) {
+                            networkRequestBuilder.removeCapability(NET_CAPABILITY_TRUSTED);
+                        }
+                        if (SdkLevel.isAtLeastS()) {
+                            if (suggestion.isOemPaid()) {
+                                networkRequestBuilder.addCapability(NET_CAPABILITY_OEM_PAID);
+                            }
+                            if (suggestion.isOemPrivate()) {
+                                networkRequestBuilder.addCapability(NET_CAPABILITY_OEM_PRIVATE);
+                            }
+                        }
+                        NetworkRequest networkRequest = networkRequestBuilder.build();
+                        ConnectivityManager.NetworkCallback networkCallback =
+                                new ConnectivityManager.NetworkCallback();
+                        pw.println("Adding request: " + networkRequest);
+                        mConnectivityManager.requestNetwork(networkRequest, networkCallback);
+                        sActiveRequests.put(
+                                suggestion.getSsid(), Pair.create(networkRequest, networkCallback));
+                    }
                     return 0;
                 }
                 case "remove-suggestion": {
@@ -512,6 +748,19 @@
                     }
                     mWifiService.removeNetworkSuggestions(
                             Arrays.asList(suggestion), SHELL_PACKAGE_NAME);
+                    // untrusted/oem-paid networks need a corresponding NetworkRequest.
+                    if (suggestion.isUntrusted()
+                            || (SdkLevel.isAtLeastS()
+                            && (suggestion.isOemPaid() || suggestion.isOemPrivate()))) {
+                        Pair<NetworkRequest, ConnectivityManager.NetworkCallback> nrAndNc =
+                                sActiveRequests.remove(suggestion.getSsid());
+                        if (nrAndNc == null) {
+                            pw.println("No matching request to remove");
+                            return -1;
+                        }
+                        pw.println("Removing request: " + nrAndNc.first);
+                        mConnectivityManager.unregisterNetworkCallback(nrAndNc.second);
+                    }
                     return 0;
                 }
                 case "remove-all-suggestions":
@@ -521,33 +770,20 @@
                 case "list-suggestions": {
                     List<WifiNetworkSuggestion> suggestions =
                             mWifiService.getNetworkSuggestions(SHELL_PACKAGE_NAME);
-                    if (suggestions == null || suggestions.isEmpty()) {
-                        pw.println("No suggestions");
-                    } else {
-                        pw.println("SSID                         Security type");
-                        for (WifiNetworkSuggestion suggestion : suggestions) {
-                            String securityType = null;
-                            if (WifiConfigurationUtil.isConfigForSaeNetwork(
-                                    suggestion.getWifiConfiguration())) {
-                                securityType = "wpa3";
-                            } else if (WifiConfigurationUtil.isConfigForPskNetwork(
-                                    suggestion.getWifiConfiguration())) {
-                                securityType = "wpa2";
-                            } else if (WifiConfigurationUtil.isConfigForEapNetwork(
-                                    suggestion.getWifiConfiguration())) {
-                                securityType = "eap";
-                            } else if (WifiConfigurationUtil.isConfigForOweNetwork(
-                                    suggestion.getWifiConfiguration())) {
-                                securityType = "owe";
-                            } else if (WifiConfigurationUtil.isConfigForOpenNetwork(
-                                    suggestion.getWifiConfiguration())) {
-                                securityType = "open";
-                            }
-                            pw.println(String.format("%-32s %-4s",
-                                    WifiInfo.sanitizeSsid(suggestion.getWifiConfiguration().SSID),
-                                    securityType));
-                        }
-                    }
+                    printWifiNetworkSuggestions(pw, suggestions);
+                    return 0;
+                }
+                case "list-all-suggestions": {
+                    Set<WifiNetworkSuggestion> suggestions =
+                            mWifiNetworkSuggestionsManager.getAllNetworkSuggestions();
+                    printWifiNetworkSuggestions(pw, suggestions);
+                    return 0;
+                }
+                case "list-suggestions-from-app": {
+                    String packageName = getNextArgRequired();
+                    List<WifiNetworkSuggestion> suggestions =
+                            mWifiService.getNetworkSuggestions(packageName);
+                    printWifiNetworkSuggestions(pw, suggestions);
                     return 0;
                 }
                 case "add-request": {
@@ -600,57 +836,57 @@
                 case "network-requests-set-user-approved": {
                     String packageName = getNextArgRequired();
                     boolean approved = getNextArgRequiredTrueOrFalse("yes", "no");
-                    mClientModeImpl.setNetworkRequestUserApprovedApp(packageName, approved);
+                    mWifiNetworkFactory.setUserApprovedApp(packageName, approved);
                     return 0;
                 }
                 case "network-requests-has-user-approved": {
                     String packageName = getNextArgRequired();
-                    boolean hasUserApproved =
-                            mClientModeImpl.hasNetworkRequestUserApprovedApp(packageName);
+                    boolean hasUserApproved = mWifiNetworkFactory.hasUserApprovedApp(packageName);
                     pw.println(hasUserApproved ? "yes" : "no");
                     return 0;
                 }
+                case "set-coex-cell-channels": {
+                    if (!SdkLevel.isAtLeastS()) {
+                        return handleDefaultCommands(cmd);
+                    }
+                    mCoexManager.setMockCellChannels(buildCoexCellChannels());
+                    return 0;
+                }
+                case "reset-coex-cell-channels": {
+                    if (!SdkLevel.isAtLeastS()) {
+                        return handleDefaultCommands(cmd);
+                    }
+                    mCoexManager.resetMockCellChannels();
+                    return 0;
+                }
+                case "get-coex-cell-channels": {
+                    if (!SdkLevel.isAtLeastS()) {
+                        return handleDefaultCommands(cmd);
+                    }
+                    pw.println("Cell channels: " + mCoexManager.getCellChannels());
+                    return 0;
+                }
                 case "set-connected-score": {
                     int score = Integer.parseInt(getNextArgRequired());
                     CountDownLatch countDownLatch = new CountDownLatch(2);
-                    GeneralUtil.Mutable<IScoreUpdateObserver> scoreUpdateObserverMutable =
-                            new GeneralUtil.Mutable<>();
-                    GeneralUtil.Mutable<Integer> sessionIdMutable = new GeneralUtil.Mutable<>();
-                    IWifiConnectedNetworkScorer.Stub connectedScorer =
-                            new IWifiConnectedNetworkScorer.Stub() {
-                        @Override
-                        public void onStart(int sessionId) {
-                            sessionIdMutable.value = sessionId;
-                            countDownLatch.countDown();
-                        }
-                        @Override
-                        public void onStop(int sessionId) {
-                            // clear the external scorer on disconnect.
-                            mWifiService.clearWifiConnectedNetworkScorer();
-                        }
-                        @Override
-                        public void onSetScoreUpdateObserver(IScoreUpdateObserver observerImpl) {
-                            scoreUpdateObserverMutable.value = observerImpl;
-                            countDownLatch.countDown();
-                        }
-                    };
                     mWifiService.clearWifiConnectedNetworkScorer(); // clear any previous scorer
+                    WifiScorer connectedScorer = new WifiScorer(mWifiService, countDownLatch);
                     if (mWifiService.setWifiConnectedNetworkScorer(new Binder(), connectedScorer)) {
                         // wait for retrieving the session id & score observer.
                         countDownLatch.await(1000, TimeUnit.MILLISECONDS);
                     }
-                    if (scoreUpdateObserverMutable.value == null
-                            || sessionIdMutable.value == null) {
+                    if (connectedScorer.getSessionId() == null
+                            || connectedScorer.getScoreUpdateObserver() == null) {
                         pw.println("Did not receive session id and/or the score update observer. "
                                 + "Is the device connected to a wifi network?");
                         mWifiService.clearWifiConnectedNetworkScorer();
                         return -1;
                     }
                     pw.println("Updating score: " + score + " for session id: "
-                            + sessionIdMutable.value);
+                            + connectedScorer.getSessionId());
                     try {
-                        scoreUpdateObserverMutable.value.notifyScoreUpdate(
-                                sessionIdMutable.value, score);
+                        connectedScorer.getScoreUpdateObserver().notifyScoreUpdate(
+                                connectedScorer.getSessionId(), score);
                     } catch (RemoteException e) {
                         pw.println("Failed to send the score update");
                         mWifiService.clearWifiConnectedNetworkScorer();
@@ -662,6 +898,103 @@
                     mWifiService.clearWifiConnectedNetworkScorer(); // clear any previous scorer
                     return 0;
                 }
+                case "network-suggestions-set-as-carrier-provider": {
+                    String packageName = getNextArgRequired();
+                    boolean enabled = getNextArgRequiredTrueOrFalse("yes", "no");
+                    mWifiNetworkSuggestionsManager
+                            .setAppWorkingAsCrossCarrierProvider(packageName, enabled);
+                    return 0;
+                }
+                case "is-network-suggestions-set-as-carrier-provider": {
+                    String packageName = getNextArgRequired();
+                    pw.println(mWifiNetworkSuggestionsManager
+                            .isAppWorkingAsCrossCarrierProvider(packageName) ? "yes" : "no");
+                    return 0;
+                }
+                case "remove-shell-app-from-suggestion_database <packageName>": {
+                    String packageName = getNextArgRequired();
+                    mWifiNetworkSuggestionsManager.removeApp(packageName);
+                    return 0;
+                }
+                case "set-emergency-callback-mode": {
+                    boolean enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled");
+                    mActiveModeWarden.emergencyCallbackModeChanged(enabled);
+                    return 0;
+                }
+                case "set-emergency-call-state": {
+                    boolean enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled");
+                    mActiveModeWarden.emergencyCallStateChanged(enabled);
+                    return 0;
+                }
+                case "trigger-recovery": {
+                    mSelfRecovery.trigger(REASON_API_CALL);
+                    return 0;
+                }
+                case "add-fake-scan": {
+                    String ssid = getNextArgRequired();
+                    String bssid = getNextArgRequired();
+                    String capabilities = getNextArgRequired();
+                    int frequency;
+                    int dbm;
+                    String freqStr = getNextArgRequired();
+                    try {
+                        frequency = Integer.parseInt(freqStr);
+                    } catch (NumberFormatException e) {
+                        pw.println(
+                                "Invalid frequency argument to 'add-fake-scan' "
+                                        + "- must be an integer: " + freqStr);
+                        return -1;
+                    }
+                    if (frequency <= 0) {
+                        pw.println("Invalid frequency argument to 'add-fake-scan' - must be a "
+                                + "positive integer: " + freqStr);
+                    }
+                    String dbmString = getNextArgRequired();
+                    try {
+                        dbm = Integer.parseInt(dbmString);
+                    } catch (NumberFormatException e) {
+                        pw.println(
+                                "Invalid dbm argument to 'add-fake-scan' "
+                                        + "- must be an integer: " + dbmString);
+                        return -1;
+                    }
+                    ScanResult.InformationElement ieSSid = new ScanResult.InformationElement(
+                            ScanResult.InformationElement.EID_SSID,
+                            0,
+                            ssid.getBytes(StandardCharsets.UTF_8));
+                    ScanResult.InformationElement[] ies =
+                            new ScanResult.InformationElement[]{ieSSid};
+                    ScanDetail sd = new ScanDetail(new NetworkDetail(bssid, ies, null, frequency),
+                            WifiSsid.createFromAsciiEncoded(ssid), bssid, capabilities, dbm,
+                            frequency, SystemClock.elapsedRealtime() * 1000, ies, null, null);
+                    mWifiNative.addFakeScanDetail(sd);
+                    return 0;
+                }
+                case "reset-fake-scans":
+                    mWifiNative.resetFakeScanDetails();
+                    return 0;
+                case "start-faking-scans":
+                    mWifiNative.startFakingScanDetails();
+                    mWifiService.startScan(SHELL_PACKAGE_NAME, null); // to trigger update
+                    return 0;
+                case "stop-faking-scans":
+                    mWifiNative.stopFakingScanDetails();
+                    return 0;
+                case "enable-scanning":
+                    boolean enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled");
+                    boolean hiddenEnabled = false;
+                    String option = getNextOption();
+                    if (option != null) {
+                        if (option.equals("-h")) {
+                            hiddenEnabled = true;
+                        } else {
+                            pw.println("Invalid argument to 'enable-scanning' "
+                                    + "- only allowed option is '-h'");
+                            return -1;
+                        }
+                    }
+                    mScanRequestProxy.enableScanning(enabled, hiddenEnabled);
+                    return 0;
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -714,6 +1047,28 @@
                 configuration.allowAutojoin = false;
             } else if (option.equals("-b")) {
                 configuration.BSSID = getNextArgRequired();
+            } else if (option.equals("-r")) {
+                String macRandomizationScheme = getNextArgRequired();
+                if (macRandomizationScheme.equals("auto")) {
+                    configuration.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_AUTO;
+                } else if (macRandomizationScheme.equals("none")) {
+                    configuration.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_NONE;
+                } else if (macRandomizationScheme.equals("persistent")) {
+                    configuration.macRandomizationSetting =
+                            WifiConfiguration.RANDOMIZATION_PERSISTENT;
+                } else if (macRandomizationScheme.equals("non_persistent")) {
+                    if (SdkLevel.isAtLeastS()) {
+                        configuration.macRandomizationSetting =
+                                WifiConfiguration.RANDOMIZATION_NON_PERSISTENT;
+                    } else {
+                        throw new IllegalArgumentException(
+                                "-r non_persistent MAC randomization not supported before S");
+                    }
+                }
+            } else if (option.equals("-h")) {
+                configuration.hiddenSSID = true;
+            } else if (option.equals("-p")) {
+                configuration.shared = false;
             } else {
                 pw.println("Ignoring unknown option " + option);
             }
@@ -726,10 +1081,16 @@
         String ssid = getNextArgRequired();
         String type = getNextArgRequired();
         SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder();
-        configBuilder.setSsid("\"" + ssid + "\"");
+        configBuilder.setSsid(ssid);
         if (TextUtils.equals(type, "wpa2")) {
             configBuilder.setPassphrase(getNextArgRequired(),
                     SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
+        } else if (TextUtils.equals(type, "wpa3")) {
+            configBuilder.setPassphrase(getNextArgRequired(),
+                    SoftApConfiguration.SECURITY_TYPE_WPA3_SAE);
+        } else if (TextUtils.equals(type, "wpa3_transition")) {
+            configBuilder.setPassphrase(getNextArgRequired(),
+                    SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION);
         } else if (TextUtils.equals(type, "open")) {
             configBuilder.setPassphrase(null, SoftApConfiguration.SECURITY_TYPE_OPEN);
         } else {
@@ -746,7 +1107,17 @@
                 } else if (preferredBand.equals("6")) {
                     configBuilder.setBand(SoftApConfiguration.BAND_6GHZ);
                 } else if (preferredBand.equals("any")) {
-                    configBuilder.setBand(SoftApConfiguration.BAND_ANY);
+                    configBuilder.setBand(SoftApConfiguration.BAND_2GHZ
+                            | SoftApConfiguration.BAND_5GHZ | SoftApConfiguration.BAND_6GHZ);
+                } else if (preferredBand.equals("bridged")) {
+                    if (SdkLevel.isAtLeastS()) {
+                        int[] dualBands = new int[] {
+                                SoftApConfiguration.BAND_2GHZ, SoftApConfiguration.BAND_5GHZ};
+                        configBuilder.setBands(dualBands);
+                    } else {
+                        throw new IllegalArgumentException(
+                                "-b bridged option is not supported before S");
+                    }
                 } else {
                     throw new IllegalArgumentException("Invalid band option " + preferredBand);
                 }
@@ -775,10 +1146,25 @@
         } else {
             throw new IllegalArgumentException("Unknown network type " + type);
         }
+        boolean isCarrierMerged = false;
         String option = getNextOption();
         while (option != null) {
             if (option.equals("-u")) {
                 suggestionBuilder.setUntrusted(true);
+            } else if (option.equals("-o")) {
+                if (SdkLevel.isAtLeastS()) {
+                    suggestionBuilder.setOemPaid(true);
+                } else {
+                    throw new IllegalArgumentException(
+                            "-o OEM paid suggestions not supported before S");
+                }
+            } else if (option.equals("-p")) {
+                if (SdkLevel.isAtLeastS()) {
+                    suggestionBuilder.setOemPrivate(true);
+                } else {
+                    throw new IllegalArgumentException(
+                            "-p OEM private suggestions not supported before S");
+                }
             } else if (option.equals("-m")) {
                 suggestionBuilder.setIsMetered(true);
             } else if (option.equals("-s")) {
@@ -787,12 +1173,48 @@
                 suggestionBuilder.setIsInitialAutojoinEnabled(false);
             } else if (option.equals("-b")) {
                 suggestionBuilder.setBssid(MacAddress.fromString(getNextArgRequired()));
+            } else if (option.equals("-r")) {
+                if (SdkLevel.isAtLeastS()) {
+                    suggestionBuilder.setMacRandomizationSetting(
+                            WifiNetworkSuggestion.RANDOMIZATION_NON_PERSISTENT);
+                } else {
+                    throw new IllegalArgumentException(
+                            "-r non_persistent MAC randomization not supported before S");
+                }
+            } else if (option.equals("-a")) {
+                if (SdkLevel.isAtLeastS()) {
+                    isCarrierMerged = true;
+                } else {
+                    throw new IllegalArgumentException("-a option is not supported before S");
+                }
+            } else if (option.equals("-i")) {
+                if (SdkLevel.isAtLeastS()) {
+                    int subId = Integer.parseInt(getNextArgRequired());
+                    suggestionBuilder.setSubscriptionId(subId);
+                } else {
+                    throw new IllegalArgumentException(
+                            "-i subscription ID option is not supported before S");
+                }
+            } else if (option.equals("-c")) {
+                int carrierId = Integer.parseInt(getNextArgRequired());
+                suggestionBuilder.setCarrierId(carrierId);
+            } else if (option.equals("-h")) {
+                suggestionBuilder.setIsHiddenSsid(true);
             } else {
                 pw.println("Ignoring unknown option " + option);
             }
             option = getNextOption();
         }
-        return suggestionBuilder.build();
+        WifiNetworkSuggestion suggestion = suggestionBuilder.build();
+        if (isCarrierMerged) {
+            if (suggestion.wifiConfiguration.subscriptionId
+                    == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                pw.println("Carrier merged network must have valid subscription Id");
+                return null;
+            }
+            suggestion.wifiConfiguration.carrierMerged = true;
+        }
+        return suggestion;
     }
 
     private NetworkRequest buildNetworkRequest(PrintWriter pw) {
@@ -803,6 +1225,8 @@
         specifierBuilder.setSsid(ssid);
         if (TextUtils.equals(type, "wpa3")) {
             specifierBuilder.setWpa3Passphrase(getNextArgRequired());
+        } else if (TextUtils.equals(type, "wpa3_transition")) {
+            specifierBuilder.setWpa3Passphrase(getNextArgRequired());
         } else if (TextUtils.equals(type, "wpa2")) {
             specifierBuilder.setWpa2Passphrase(getNextArgRequired());
         } else if (TextUtils.equals(type, "owe")) {
@@ -846,6 +1270,57 @@
                 .build();
     }
 
+    @NonNull
+    private List<CoexUtils.CoexCellChannel> buildCoexCellChannels() {
+        List<CoexUtils.CoexCellChannel> cellChannels = new ArrayList<>();
+        while (getRemainingArgsCount() > 0) {
+            final @Annotation.NetworkType int rat;
+            final String ratArg = getNextArgRequired();
+            if (TextUtils.equals(ratArg, "lte")) {
+                rat = TelephonyManager.NETWORK_TYPE_LTE;
+            } else if (TextUtils.equals(ratArg, "nr")) {
+                rat = TelephonyManager.NETWORK_TYPE_NR;
+            } else {
+                throw new IllegalArgumentException("Unknown rat type " + ratArg);
+            }
+            final int band = Integer.parseInt(getNextArgRequired());
+            if (band < 1 || band > 261) {
+                throw new IllegalArgumentException("Band is " + band
+                        + " but should be a value from 1 to 261");
+            }
+            final int downlinkFreqKhz = Integer.parseInt(getNextArgRequired());
+            if (downlinkFreqKhz < 0 && downlinkFreqKhz != PhysicalChannelConfig.FREQUENCY_UNKNOWN) {
+                throw new IllegalArgumentException("Downlink frequency is " + downlinkFreqKhz
+                        + " but should be >= 0 or UNKNOWN: "
+                        + PhysicalChannelConfig.FREQUENCY_UNKNOWN);
+            }
+            final int downlinkBandwidthKhz = Integer.parseInt(getNextArgRequired());
+            if (downlinkBandwidthKhz <= 0
+                    && downlinkBandwidthKhz != PhysicalChannelConfig.CELL_BANDWIDTH_UNKNOWN) {
+                throw new IllegalArgumentException("Downlink bandwidth is " + downlinkBandwidthKhz
+                        + " but should be > 0 or UNKNOWN: "
+                        + PhysicalChannelConfig.CELL_BANDWIDTH_UNKNOWN);
+            }
+            final int uplinkFreqKhz = Integer.parseInt(getNextArgRequired());
+            if (uplinkFreqKhz < 0 && uplinkFreqKhz != PhysicalChannelConfig.FREQUENCY_UNKNOWN) {
+                throw new IllegalArgumentException("Uplink frequency is " + uplinkFreqKhz
+                        + " but should be >= 0 or UNKNOWN: "
+                        + PhysicalChannelConfig.FREQUENCY_UNKNOWN);
+            }
+            final int uplinkBandwidthKhz = Integer.parseInt(getNextArgRequired());
+            if (uplinkBandwidthKhz <= 0
+                    && uplinkBandwidthKhz != PhysicalChannelConfig.CELL_BANDWIDTH_UNKNOWN) {
+                throw new IllegalArgumentException("Uplink bandwidth is " + uplinkBandwidthKhz
+                        + " but should be > 0 or UNKNOWN: "
+                        + PhysicalChannelConfig.CELL_BANDWIDTH_UNKNOWN);
+            }
+            cellChannels.add(new CoexUtils.CoexCellChannel(rat, band,
+                    downlinkFreqKhz, downlinkBandwidthKhz, uplinkFreqKhz, uplinkBandwidthKhz,
+                    SubscriptionManager.INVALID_SUBSCRIPTION_ID));
+        }
+        return cellChannels;
+    }
+
     private void setAutoJoin(PrintWriter pw, String ssid, boolean allowAutojoin) {
         // For suggestions, this will work only if the config has already been added
         // to WifiConfigManager.
@@ -868,17 +1343,19 @@
         final int sendMgmtFrameTimeoutMs = 1000;
 
         ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
-        mClientModeImpl.probeLink(new WifiNl80211Manager.SendMgmtFrameCallback() {
-            @Override
-            public void onAck(int elapsedTimeMs) {
-                queue.offer("Link probe succeeded after " + elapsedTimeMs + " ms");
-            }
+        mWifiThreadRunner.post(() ->
+                mActiveModeWarden.getPrimaryClientModeManager().probeLink(new LinkProbeCallback() {
+                    @Override
+                    public void onAck(int elapsedTimeMs) {
+                        queue.offer("Link probe succeeded after " + elapsedTimeMs + " ms");
+                    }
 
-            @Override
-            public void onFailure(int reason) {
-                queue.offer("Link probe failed with reason " + reason);
-            }
-        }, -1);
+                    @Override
+                    public void onFailure(int reason) {
+                        queue.offer("Link probe failed with reason "
+                                + LinkProbeCallback.failureReasonToString(reason));
+                    }
+                }, -1));
 
         // block until msg is received, or timed out
         String msg = queue.poll(sendMgmtFrameTimeoutMs + 1000, TimeUnit.MILLISECONDS);
@@ -890,12 +1367,13 @@
         return 0;
     }
 
-    private boolean isApChannelMHzValid(int apChannelMHz) {
+    private boolean isApChannelMHzValid(PrintWriter pw, int apChannelMHz) {
         int[] allowed2gFreq = mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_24_GHZ);
         int[] allowed5gFreq = mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ);
         int[] allowed5gDfsFreq =
             mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY);
         int[] allowed6gFreq = mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_6_GHZ);
+        int[] allowed60gFreq = mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_60_GHZ);
         if (allowed2gFreq == null) {
             allowed2gFreq = new int[0];
         }
@@ -908,23 +1386,45 @@
         if (allowed6gFreq == null) {
             allowed6gFreq = new int[0];
         }
-
+        if (allowed60gFreq == null) {
+            allowed60gFreq = new int[0];
+        }
+        pw.println("2G freq: " + Arrays.toString(allowed2gFreq));
+        pw.println("5G freq: " + Arrays.toString(allowed5gFreq));
+        pw.println("5G DFS: " + Arrays.toString(allowed5gDfsFreq));
+        pw.println("6G freq: " + Arrays.toString(allowed6gFreq));
+        pw.println("60G freq: " + Arrays.toString(allowed60gFreq));
         return (Arrays.binarySearch(allowed2gFreq, apChannelMHz) >= 0
                 || Arrays.binarySearch(allowed5gFreq, apChannelMHz) >= 0
                 || Arrays.binarySearch(allowed5gDfsFreq, apChannelMHz) >= 0)
-                || Arrays.binarySearch(allowed6gFreq, apChannelMHz) >= 0;
+                || Arrays.binarySearch(allowed6gFreq, apChannelMHz) >= 0
+                || Arrays.binarySearch(allowed60gFreq, apChannelMHz) >= 0;
     }
 
-    private void printStatus(PrintWriter pw) {
-        boolean wifiEnabled = mWifiService.getWifiEnabledState() == WIFI_STATE_ENABLED;
-        pw.println("Wifi is " + (wifiEnabled ? "enabled" : "disabled"));
-        pw.println("Wifi scanning is "
-                + (mWifiService.isScanAlwaysAvailable()
-                ? "always available" : "only available when wifi is enabled"));
-        if (!wifiEnabled) {
-            return;
-        }
-        WifiInfo info = mWifiService.getConnectionInfo(SHELL_PACKAGE_NAME, null);
+    private void waitForWifiEnabled(boolean enabled) throws InterruptedException {
+        CountDownLatch countDownLatch = new CountDownLatch(1);
+        BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                String action = intent.getAction();
+                if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
+                    int state = mWifiService.getWifiEnabledState();
+                    if ((enabled && state == WIFI_STATE_ENABLED)
+                            || (!enabled && state == WIFI_STATE_DISABLED)) {
+                        countDownLatch.countDown();
+                    }
+                }
+            }
+        };
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+        mContext.registerReceiver(broadcastReceiver, filter);
+        mWifiService.setWifiEnabled(SHELL_PACKAGE_NAME, enabled);
+        countDownLatch.await(5000, TimeUnit.MILLISECONDS);
+        mContext.unregisterReceiver(broadcastReceiver);
+    }
+
+    private void printWifiInfo(PrintWriter pw, WifiInfo info) {
         if (info.getSupplicantState() != SupplicantState.COMPLETED) {
             pw.println("Wifi is not connected");
             return;
@@ -940,13 +1440,36 @@
         pw.println("lostTxPacketsPerSecond: " + info.getLostTxPacketsPerSecond());
         pw.println("successfulRxPackets: " + info.rxSuccess);
         pw.println("successfulRxPacketsPerSecond: " + info.getSuccessfulRxPacketsPerSecond());
+    }
 
-        Network network = mWifiService.getCurrentNetwork();
-        try {
-            NetworkCapabilities capabilities = mConnectivityManager.getNetworkCapabilities(network);
-            pw.println("NetworkCapabilities: " + capabilities);
-        } catch (SecurityException e) {
-            // ignore on unrooted shell.
+    private void printStatus(PrintWriter pw) {
+        boolean wifiEnabled = mWifiService.getWifiEnabledState() == WIFI_STATE_ENABLED;
+        pw.println("Wifi is " + (wifiEnabled ? "enabled" : "disabled"));
+        pw.println("Wifi scanning is "
+                + (mWifiService.isScanAlwaysAvailable()
+                ? "always available" : "only available when wifi is enabled"));
+        if (!wifiEnabled) {
+            return;
+        }
+        if (Binder.getCallingUid() != Process.ROOT_UID) {
+            // not privileged, just dump the primary client mode manager manager status
+            // (public API contents).
+            pw.println("==== Primary ClientModeManager instance ====");
+            printWifiInfo(pw, mWifiService.getConnectionInfo(SHELL_PACKAGE_NAME, null));
+        } else {
+            // privileged, dump out all the client mode manager manager statuses
+            for (ClientModeManager cm : mActiveModeWarden.getClientModeManagers()) {
+                pw.println("==== ClientModeManager instance: " + cm + " ====");
+                WifiInfo info = cm.syncRequestConnectionInfo();
+                printWifiInfo(pw, info);
+                if (info.getSupplicantState() != SupplicantState.COMPLETED) {
+                    continue;
+                }
+                Network network = cm.syncGetCurrentNetwork();
+                NetworkCapabilities capabilities =
+                        mConnectivityManager.getNetworkCapabilities(network);
+                pw.println("NetworkCapabilities: " + capabilities);
+            }
         }
     }
 
@@ -964,7 +1487,7 @@
         pw.println("  list-networks");
         pw.println("    Lists the saved networks");
         pw.println("  connect-network <ssid> open|owe|wpa2|wpa3 [<passphrase>] [-m] [-d] "
-                + "[-b <bssid>]");
+                + "[-b <bssid>] [-r auto|none|persistent|non_persistent]");
         pw.println("    Connect to a network with provided params and add to saved networks list");
         pw.println("    <ssid> - SSID of the network");
         pw.println("    open|owe|wpa2|wpa3 - Security type of the network.");
@@ -976,9 +1499,13 @@
         pw.println("           - 'wpa3' - WPA-3 PSK networks");
         pw.println("    -m - Mark the network metered.");
         pw.println("    -d - Mark the network autojoin disabled.");
+        pw.println("    -h - Mark the network hidden.");
+        pw.println("    -p - Mark the network private (not shared).");
         pw.println("    -b <bssid> - Set specific BSSID.");
+        pw.println("    -r auto|none|persistent|non_persistent - MAC randomization scheme for the"
+                + " network");
         pw.println("  add-network <ssid> open|owe|wpa2|wpa3 [<passphrase>] [-m] [-d] "
-                + "[-b <bssid>]");
+                + "[-b <bssid>] [-r auto|none|persistent|non_persistent]");
         pw.println("    Add/update saved network with provided params");
         pw.println("    <ssid> - SSID of the network");
         pw.println("    open|owe|wpa2|wpa3 - Security type of the network.");
@@ -990,7 +1517,11 @@
         pw.println("           - 'wpa3' - WPA-3 PSK networks");
         pw.println("    -m - Mark the network metered.");
         pw.println("    -d - Mark the network autojoin disabled.");
+        pw.println("    -h - Mark the network hidden.");
+        pw.println("    -p - Mark the network private (not shared).");
         pw.println("    -b <bssid> - Set specific BSSID.");
+        pw.println("    -r auto|none|persistent|non_persistent - MAC randomization scheme for the"
+                + " network");
         pw.println("  forget-network <networkId>");
         pw.println("    Remove the network mentioned by <networkId>");
         pw.println("        - Use list-networks to retrieve <networkId> for the network");
@@ -998,8 +1529,16 @@
         pw.println("    Current wifi status");
         pw.println("  set-verbose-logging enabled|disabled ");
         pw.println("    Set the verbose logging enabled or disabled");
-        pw.println("  add-suggestion <ssid> open|owe|wpa2|wpa3 [<passphrase>] [-u] [-m] [-s] [-d]"
-                + "[-b <bssid>]");
+        pw.println("  is-verbose-logging");
+        pw.println("    Check whether verbose logging enabled or disabled");
+        pw.println("  start-restricting-auto-join-to-subscription-id subId");
+        pw.println("    temporarily disable all wifi networks except merged carrier networks with"
+                + " the given subId");
+        pw.println("  stop-restricting-auto-join-to-subscription-id");
+        pw.println("    Undo the effects of "
+                + "start-restricting-auto-join-to-subscription-id");
+        pw.println("  add-suggestion <ssid> open|owe|wpa2|wpa3 [<passphrase>] [-u] [-o] [-p] [-m] "
+                + " [-s] [-d] [-b <bssid>] [-e] [-i] [-a <carrierId>] [-c <subscriptionId>]");
         pw.println("    Add a network suggestion with provided params");
         pw.println("    Use 'network-suggestions-set-user-approved " + SHELL_PACKAGE_NAME + " yes'"
                 +  " to approve suggestions added via shell (Needs root access)");
@@ -1012,16 +1551,43 @@
         pw.println("           - 'wpa2' - WPA-2 PSK networks (Most prevalent)");
         pw.println("           - 'wpa3' - WPA-3 PSK networks");
         pw.println("    -u - Mark the suggestion untrusted.");
+        pw.println("    -o - Mark the suggestion oem paid.");
+        pw.println("    -p - Mark the suggestion oem private.");
         pw.println("    -m - Mark the suggestion metered.");
+        pw.println("    -h - Mark the network hidden.");
         pw.println("    -s - Share the suggestion with user.");
         pw.println("    -d - Mark the suggestion autojoin disabled.");
         pw.println("    -b <bssid> - Set specific BSSID.");
+        pw.println("    -r - Enable non_persistent randomization (disabled by default)");
+        pw.println("    -a - Mark the suggestion carrier merged");
+        pw.println("    -c <carrierId> - set carrier Id");
+        pw.println("    -i <subscriptionId> - set subscription Id, if -a is used, "
+                + "this must be set");
         pw.println("  remove-suggestion <ssid>");
         pw.println("    Remove a network suggestion with provided SSID of the network");
         pw.println("  remove-all-suggestions");
         pw.println("    Removes all suggestions added via shell");
         pw.println("  list-suggestions");
         pw.println("    Lists the suggested networks added via shell");
+        if (SdkLevel.isAtLeastS()) {
+            pw.println("  set-coex-cell-channels [lte|nr <bandNumber 1-261> "
+                    + "<downlinkFreqKhz or UNKNOWN: "
+                    + PhysicalChannelConfig.FREQUENCY_UNKNOWN + "> "
+                    + "<downlinkBandwidthKhz or UNKNOWN: "
+                    + PhysicalChannelConfig.CELL_BANDWIDTH_UNKNOWN + "> "
+                    + "<uplinkFreqKhz or UNKNOWN: "
+                    + PhysicalChannelConfig.FREQUENCY_UNKNOWN + "> "
+                    + "<uplinkBandwidthKhz or UNKNOWN: "
+                    + PhysicalChannelConfig.CELL_BANDWIDTH_UNKNOWN + ">] ...");
+            pw.println("    Sets a list of zero or more cell channels to use for coex calculations."
+                    + " Actual device reported cell channels will be ignored until"
+                    + " reset-coex-cell-channels is called.");
+            pw.println("  reset-coex-cell-channels");
+            pw.println("    Removes all cell channels set in set-coex-cell-channels and returns to "
+                    + "listening on actual device reported cell channels");
+            pw.println("  get-coex-cell-channels");
+            pw.println("    Prints the cell channels being used for coex.");
+        }
         pw.println("  set-connected-score <score>");
         pw.println("    Set connected wifi network score (to choose between LTE & Wifi for "
                 + "default route).");
@@ -1033,25 +1599,31 @@
         pw.println("  reset-connected-score");
         pw.println("    Turns on the default connected scorer.");
         pw.println("    Note: Will clear any external scorer set.");
-        pw.println("  start-softap <ssid> (open|wpa2) <passphrase> [-b 2|5|6|any]");
+        pw.println("  start-softap <ssid> (open|wpa2|wpa3|wpa3_transition) <passphrase> "
+                + "[-b 2|5|6|any]");
         pw.println("    Start softap with provided params");
         pw.println("    Note that the shell command doesn't activate internet tethering. In some "
                 + "devices, internet sharing is possible when Wi-Fi STA is also enabled and is"
                 + "associated to another AP with internet access.");
         pw.println("    <ssid> - SSID of the network");
-        pw.println("    open|wpa2 - Security type of the network.");
+        pw.println("    open|wpa2|wpa3|wpa3_transition - Security type of the network.");
         pw.println("        - Use 'open' for networks with no passphrase");
-        pw.println("        - Use 'wpa2' for networks with passphrase");
-        pw.println("    -b 2|5|6|any - select the preferred band.");
+        pw.println("        - Use 'wpa2', 'wpa3', 'wpa3_transition' for networks with passphrase");
+        pw.println("    -b 2|5|6|any|bridged - select the preferred band.");
         pw.println("        - Use '2' to select 2.4GHz band as the preferred band");
         pw.println("        - Use '5' to select 5GHz band as the preferred band");
         pw.println("        - Use '6' to select 6GHz band as the preferred band");
         pw.println("        - Use 'any' to indicate no band preference");
+        pw.println("        - Use 'bridged' to indicate bridged AP which enables APs on both "
+                + "2.4G + 5G");
         pw.println("    Note: If the band option is not provided, 2.4GHz is the preferred band.");
         pw.println("          The exact channel is auto-selected by FW unless overridden by "
                 + "force-softap-channel command");
         pw.println("  stop-softap");
         pw.println("    Stop softap (hotspot)");
+        pw.println("  pmksa-flush <networkId>");
+        pw.println("        - Flush the local PMKSA cache associated with the network id."
+                + " Use list-networks to retrieve <networkId> for the network");
     }
 
     private void onHelpPrivileged(PrintWriter pw) {
@@ -1083,6 +1655,8 @@
         pw.println("    Clears the user disabled networks list.");
         pw.println("  send-link-probe");
         pw.println("    Manually triggers a link probe.");
+        pw.println("  force-softap-band enabled <int> | disabled");
+        pw.println("    Forces soft AP band to 2|5|6");
         pw.println("  force-softap-channel enabled <int> | disabled");
         pw.println("    Sets whether soft AP channel is forced to <int> MHz");
         pw.println("    or left for normal   operation.");
@@ -1094,7 +1668,10 @@
         pw.println("    Gets setting of wifi watchdog trigger recovery.");
         pw.println("  get-softap-supported-features");
         pw.println("    Gets softap supported features. Will print 'wifi_softap_acs_supported'");
-        pw.println("    and/or 'wifi_softap_wpa3_sae_supported', each on a separate line.");
+        pw.println("    and/or 'wifi_softap_wpa3_sae_supported',");
+        pw.println("    and/or 'wifi_softap_bridged_ap_supported',");
+        pw.println("    and/or 'wifi_softap_bridged_ap_with_sta_supported',");
+        pw.println("    each on a separate line.");
         pw.println("  settings-reset");
         pw.println("    Initiates wifi settings reset");
         pw.println("  add-request <ssid> open|owe|wpa2|wpa3 [<passphrase>] [-b <bssid>]");
@@ -1121,8 +1698,59 @@
         pw.println("    Note: Only 1 such app can be approved from the shell at a time");
         pw.println("  network-requests-has-user-approved <package name>");
         pw.println("    Queries whether network requests from the app is approved or not.");
-        pw.println("    Note: This only returns whether the app was set via the " +
-                "'network-requests-set-user-approved' shell command");
+        pw.println("    Note: This only returns whether the app was set via the "
+                + "'network-requests-set-user-approved' shell command");
+        pw.println("  list-all-suggestions");
+        pw.println("    Lists all suggested networks on this device");
+        pw.println("  list-suggestions-from-app <package name>");
+        pw.println("    Lists the suggested networks from the app");
+        pw.println("  set-emergency-callback-mode enabled|disabled");
+        pw.println("    Sets whether Emergency Callback Mode (ECBM) is enabled.");
+        pw.println("    Equivalent to receiving the "
+                + "TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED broadcast.");
+        pw.println("  set-emergency-call-state enabled|disabled");
+        pw.println("    Sets whether we are in the middle of an emergency call.");
+        pw.println("Equivalent to receiving the "
+                + "TelephonyManager.ACTION_EMERGENCY_CALL_STATE_CHANGED broadcast.");
+        pw.println("  network-suggestions-set-as-carrier-provider <packageName> yes|no");
+        pw.println("    Set the <packageName> work as carrier provider or not.");
+        pw.println("  is-network-suggestions-set-as-carrier-provider <packageName>");
+        pw.println("    Queries whether the <packageName> is working as carrier provider or not.");
+        pw.println("  remove-app-from-suggestion_database <packageName>");
+        pw.println("    Remove <packageName> from the suggestion database, all suggestions and user"
+                + " approval will be deleted, it is the same as uninstalling this app.");
+        pw.println("  trigger-recovery");
+        pw.println("    Trigger Wi-Fi subsystem restart.");
+        pw.println("  start-faking-scans");
+        pw.println("    Start faking scan results into the framework (configured with "
+                + "'add-fake-scan'), stop with 'stop-faking-scans'.");
+        pw.println("  stop-faking-scans");
+        pw.println("    Stop faking scan results - started with 'start-faking-scans'.");
+        pw.println("  add-fake-scan <ssid> <bssid> <capabilities> <frequency> <dbm>");
+        pw.println("    Add a fake scan result to be used when enabled via `start-faking-scans'.");
+        pw.println("    Example WPA2: add-fake-scan fakeWpa2 80:01:02:03:04:05 "
+                + "\"[WPA2-PSK-CCMP][RSN-PSK-CCMP][ESS]\" 2412 -55");
+        pw.println("    Example WPA3: add-fake-scan fakeWpa3 80:01:02:03:04:06 "
+                + "\"[RSN-SAE+FT/SAE-CCMP][ESS]\" 2412 -55");
+        pw.println(
+                "    Example Open: add-fake-scan fakeOpen 80:01:02:03:04:07 \"[ESS]\" 2412 -55");
+        pw.println("    Example OWE: add-fake-scan fakeOwe 80:01:02:03:04:08 \"[RSN-OWE-CCMP]\" "
+                + "2412 -55");
+        pw.println(
+                "    Example WPA2/WPA3 transition mode: add-fake-scan fakeWpa2t3 80:01:02:03:04:09 "
+                        + "\"[WPA2-PSK-CCMP][RSN-PSK+SAE-CCMP][ESS][MFPC]\" 2412 -55");
+        pw.println(
+                "    Example Open/OWE transition mode: add-fake-scan fakeOpenOwe 80:01:02:03:04:0A "
+                        + "\"[RSN-OWE_TRANSITION-CCMP][ESS]\" 2412 -55");
+        pw.println(
+                "    Example Passpoint: add-fake-scan fakePasspoint 80:01:02:03:04:0B "
+                        + "\"[WPA2-EAP/SHA1-CCMP][RSN-EAP/SHA1-CCMP][ESS][MFPR][MFPC]"
+                        + "[PASSPOINT]\" 2412 -55");
+        pw.println("  reset-fake-scans");
+        pw.println("    Resets all fake scan results added by 'add-fake-scan'.");
+        pw.println("  enable-scanning enabled|disabled [-h]");
+        pw.println("    Sets whether all scanning should be enabled or disabled");
+        pw.println("    -h - Enable scanning for hidden networks.");
     }
 
     @Override
@@ -1137,4 +1765,22 @@
         }
         pw.println();
     }
+
+    private void printWifiNetworkSuggestions(PrintWriter pw,
+            Collection<WifiNetworkSuggestion> suggestions) {
+        if (suggestions == null || suggestions.isEmpty()) {
+            pw.println("No suggestions on this device");
+        } else {
+            pw.println("SSID                         Security type(s)");
+            for (WifiNetworkSuggestion suggestion : suggestions) {
+                pw.println(String.format("%-32s %-4s",
+                        WifiInfo.sanitizeSsid(suggestion.getWifiConfiguration().SSID),
+                        suggestion.getWifiConfiguration().getSecurityParamsList().stream()
+                                .map(p -> WifiConfiguration.getSecurityTypeName(
+                                        p.getSecurityType())
+                                        + (p.isAddedByAutoUpgrade() ? "^" : ""))
+                                .collect(Collectors.joining("/"))));
+            }
+        }
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiStateTracker.java b/service/java/com/android/server/wifi/WifiStateTracker.java
index c516a8f..63d2677 100644
--- a/service/java/com/android/server/wifi/WifiStateTracker.java
+++ b/service/java/com/android/server/wifi/WifiStateTracker.java
@@ -16,9 +16,14 @@
 
 package com.android.server.wifi;
 
+import android.annotation.IntDef;
 import android.os.BatteryStatsManager;
+import android.util.ArrayMap;
 import android.util.Log;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Map;
 import java.util.concurrent.RejectedExecutionException;
 
 /**
@@ -27,57 +32,93 @@
 public class WifiStateTracker {
     private static final String TAG = "WifiStateTracker";
 
-    public static final int INVALID = 0;
-    public static final int SCAN_MODE = 1;
+    @IntDef(value = {
+            INVALID,
+            SCAN_MODE,
+            DISCONNECTED,
+            CONNECTED,
+            SOFT_AP,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface WifiStateTrackerState {}
+
+    public static final int INVALID = -1;
+    public static final int SCAN_MODE = 1; // unused
     public static final int DISCONNECTED = 2;
     public static final int CONNECTED = 3;
-    public static final int SOFT_AP = 4;
-    private int mWifiState;
-    private BatteryStatsManager mBatteryStatsManager;
+    public static final int SOFT_AP = 4; // unused
+
+    /**
+     * String key: interface name
+     * int value: last wifi state, one of the {@link WifiStateTrackerState} values.
+     */
+    private final Map<String, Integer> mIfaceToWifiState = new ArrayMap<>();
+    private final BatteryStatsManager mBatteryStatsManager;
 
     public WifiStateTracker(BatteryStatsManager batteryStatsManager) {
-        mWifiState = INVALID;
         mBatteryStatsManager = batteryStatsManager;
     }
 
-    private void informWifiStateBatteryStats(int state) {
+    @WifiStateTrackerState
+    private int mLastReportedState = INVALID;
+
+    private void informWifiStateBatteryStats(@WifiStateTrackerState int state) {
+        if (state == mLastReportedState) {
+            return;
+        }
+        mLastReportedState = state;
+
+        int batteryStatsState = internalToBatteryStatsWifiState(state);
+        if (batteryStatsState == INVALID) {
+            return;
+        }
+
         try {
-            mBatteryStatsManager.reportWifiState(state, null);
+            mBatteryStatsManager.reportWifiState(batteryStatsState, null);
         } catch (RejectedExecutionException e) {
             Log.e(TAG, "Battery stats executor is being shutdown " + e.getMessage());
         }
     }
 
     /**
+     * Reports the most active state among Wifi ifaces. e.g. Connected is more active than
+     * disconnected.
+     */
+    @WifiStateTrackerState
+    private int getHighestPriorityState() {
+        int highest = INVALID;
+        for (int i : mIfaceToWifiState.values()) {
+            highest = Math.max(highest, i);
+        }
+        return highest;
+    }
+
+    /**
      * Inform the WifiState to this tracker to translate into the
      * WifiState corresponding to BatteryStatsManager.
-     * @param state state corresponding to the ClientModeImpl state
+     * @param ifaceName iface name whose state has changed
+     * @param state state corresponding to the ClientModeImpl state, one of the
+     *        {@link WifiStateTrackerState} values.
      */
-    public void updateState(int state) {
-        int reportState = BatteryStatsManager.WIFI_STATE_OFF;
-        if (state != mWifiState) {
-            switch(state) {
-                case SCAN_MODE:
-                    reportState = BatteryStatsManager.WIFI_STATE_OFF_SCANNING;
-                    break;
-                case DISCONNECTED:
-                    reportState = BatteryStatsManager.WIFI_STATE_ON_DISCONNECTED;
-                    break;
-                case CONNECTED:
-                    reportState = BatteryStatsManager.WIFI_STATE_ON_CONNECTED_STA;
-                    break;
-                case SOFT_AP:
-                    reportState = BatteryStatsManager.WIFI_STATE_SOFT_AP;
-                    break;
-                case INVALID:
-                    mWifiState = INVALID;
-                    /* Fall through */
-                default:
-                    return;
-            }
-            mWifiState = state;
-            informWifiStateBatteryStats(reportState);
+    public void updateState(String ifaceName, @WifiStateTrackerState int state) {
+        mIfaceToWifiState.put(ifaceName, state);
+        int highestPriorityState = getHighestPriorityState();
+        informWifiStateBatteryStats(highestPriorityState);
+    }
+
+    private static int internalToBatteryStatsWifiState(@WifiStateTrackerState int internal) {
+        switch (internal) {
+            case SCAN_MODE:
+                return BatteryStatsManager.WIFI_STATE_OFF_SCANNING;
+            case DISCONNECTED:
+                return BatteryStatsManager.WIFI_STATE_ON_DISCONNECTED;
+            case CONNECTED:
+                return BatteryStatsManager.WIFI_STATE_ON_CONNECTED_STA;
+            case SOFT_AP:
+                return BatteryStatsManager.WIFI_STATE_SOFT_AP;
+            case INVALID:
+            default:
+                return INVALID;
         }
-        return;
     }
 }
diff --git a/service/java/com/android/server/wifi/WifiThreadRunner.java b/service/java/com/android/server/wifi/WifiThreadRunner.java
index e478c82..64ec5b2 100644
--- a/service/java/com/android/server/wifi/WifiThreadRunner.java
+++ b/service/java/com/android/server/wifi/WifiThreadRunner.java
@@ -23,6 +23,7 @@
 import android.os.SystemClock;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.wifi.util.GeneralUtil.Mutable;
 
 import java.util.function.Supplier;
@@ -42,6 +43,10 @@
     /** Max wait time for posting blocking runnables */
     private static final int RUN_WITH_SCISSORS_TIMEOUT_MILLIS = 4000;
 
+    /** For test only */
+    private boolean mTimeoutsAreErrors = false;
+    private volatile Thread mDispatchThread = null;
+
     private final Handler mHandler;
 
     public WifiThreadRunner(Handler handler) {
@@ -77,7 +82,14 @@
         if (runWithScissorsSuccess) {
             return result.value;
         } else {
-            Log.e(TAG, "WifiThreadRunner.call() timed out!", new Throwable("Stack trace:"));
+            Throwable callerThreadThrowable = new Throwable("Caller thread Stack trace:");
+            Throwable wifiThreadThrowable = new Throwable("Wifi thread Stack trace:");
+            wifiThreadThrowable.setStackTrace(mHandler.getLooper().getThread().getStackTrace());
+            Log.e(TAG, "WifiThreadRunner.call() timed out!", callerThreadThrowable);
+            Log.e(TAG, "WifiThreadRunner.call() timed out!", wifiThreadThrowable);
+            if (mTimeoutsAreErrors) {
+                throw new RuntimeException("WifiThreadRunner.call() timed out!");
+            }
             return valueToReturnOnTimeout;
         }
     }
@@ -96,12 +108,48 @@
         if (runWithScissorsSuccess) {
             return true;
         } else {
-            Log.e(TAG, "WifiThreadRunner.run() timed out!", new Throwable("Stack trace:"));
+            Throwable callerThreadThrowable = new Throwable("Caller thread Stack trace:");
+            Throwable wifiThreadThrowable = new Throwable("Wifi thread Stack trace:");
+            wifiThreadThrowable.setStackTrace(mHandler.getLooper().getThread().getStackTrace());
+            Log.e(TAG, "WifiThreadRunner.run() timed out!", callerThreadThrowable);
+            Log.e(TAG, "WifiThreadRunner.run() timed out!", wifiThreadThrowable);
+            if (mTimeoutsAreErrors) {
+                throw new RuntimeException("WifiThreadRunner.run() timed out!");
+            }
             return false;
         }
     }
 
     /**
+     * Sets whether or not a RuntimeError should be thrown when a timeout occurs.
+     *
+     * For test purposes only!
+     */
+    @VisibleForTesting
+    public void setTimeoutsAreErrors(boolean timeoutsAreErrors) {
+        Log.d(TAG, "setTimeoutsAreErrors " + timeoutsAreErrors);
+        mTimeoutsAreErrors = timeoutsAreErrors;
+    }
+
+    /**
+     * Prepares to run on a different thread.
+     *
+     * Useful when TestLooper#startAutoDispatch is used to test code that uses #call or #run,
+     * because in this case the messages are dispatched by a thread that is not actually the
+     * thread associated with the looper. Should be called before each call
+     * to TestLooper#startAutoDispatch, without intervening calls to other TestLooper dispatch
+     * methods.
+     *
+     * For test purposes only!
+     */
+    @VisibleForTesting
+    public void prepareForAutoDispatch() {
+        mHandler.postAtFrontOfQueue(() -> {
+            mDispatchThread = Thread.currentThread();
+        });
+    }
+
+    /**
      * Asynchronously runs a Runnable on the main Wifi thread.
      *
      * @return true if the runnable was successfully posted <b>(not executed)</b> to the main Wifi
@@ -111,6 +159,28 @@
         return mHandler.post(runnable);
     }
 
+    /**
+     * Asynchronously runs a Runnable on the main Wifi thread with delay.
+     *
+     * @param runnable The Runnable that will be executed.
+     * @param delayMillis The delay (in milliseconds) until the Runnable
+     *        will be executed.
+     * @return true if the runnable was successfully posted <b>(not executed)</b> to the main Wifi
+     * thread, false otherwise
+     */
+    public boolean postDelayed(@NonNull Runnable runnable, long delayMillis) {
+        return mHandler.postDelayed(runnable, delayMillis);
+    }
+
+    /**
+     * Remove any pending posts of Runnable r that are in the message queue.
+     *
+     * @param r The Runnable that will be removed.
+     */
+    public final void removeCallbacks(@NonNull Runnable r) {
+        mHandler.removeCallbacks(r);
+    }
+
     // Note: @hide methods copied from android.os.Handler
     /**
      * Runs the specified task synchronously.
@@ -152,7 +222,7 @@
      * If we ever do make it part of the API, we might want to rename it to something
      * less funny like runUnsafe().
      */
-    private static boolean runWithScissors(@NonNull Handler handler, @NonNull Runnable r,
+    private boolean runWithScissors(@NonNull Handler handler, @NonNull Runnable r,
             long timeout) {
         if (r == null) {
             throw new IllegalArgumentException("runnable must not be null");
@@ -166,6 +236,11 @@
             return true;
         }
 
+        if (Thread.currentThread() == mDispatchThread) {
+            r.run();
+            return true;
+        }
+
         BlockingRunnable br = new BlockingRunnable(r);
         return br.postAndWait(handler, timeout);
     }
diff --git a/service/java/com/android/server/wifi/WifiTrafficPoller.java b/service/java/com/android/server/wifi/WifiTrafficPoller.java
index 8349f7b..ab5f086 100644
--- a/service/java/com/android/server/wifi/WifiTrafficPoller.java
+++ b/service/java/com/android/server/wifi/WifiTrafficPoller.java
@@ -16,15 +16,15 @@
 
 package com.android.server.wifi;
 
-import android.annotation.NonNull;
+import android.content.Context;
 import android.net.wifi.ITrafficStateCallback;
 import android.net.wifi.WifiManager;
-import android.os.Handler;
-import android.os.IBinder;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.util.Log;
+import android.util.SparseBooleanArray;
 
-import com.android.server.wifi.util.ExternalCallbackTracker;
+import com.android.wifi.resources.R;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -40,30 +40,21 @@
 
     private int mLastActivity = -1;
 
-    private static class CallbackWrapper {
-        public final ITrafficStateCallback callback;
-        /**
-         * On the first invocation, the callback is invoked no matter if the data activity changed
-         * or not.
-         */
-        public boolean isFirstInvocation = true;
+    private final SparseBooleanArray mCallbackFirstInvocationTracker = new SparseBooleanArray();
+    private final RemoteCallbackList<ITrafficStateCallback> mRegisteredCallbacks;
+    private final Context mContext;
 
-        CallbackWrapper(ITrafficStateCallback callback) {
-            this.callback = callback;
-        }
-    }
-
-    private final ExternalCallbackTracker<CallbackWrapper> mRegisteredCallbacks;
-
-    public WifiTrafficPoller(@NonNull Handler handler) {
-        mRegisteredCallbacks = new ExternalCallbackTracker<>(handler);
+    public WifiTrafficPoller(Context context) {
+        mContext = context;
+        mRegisteredCallbacks = new RemoteCallbackList<>();
     }
 
     /**
      * Add a new callback to the traffic poller.
      */
-    public void addCallback(IBinder binder, ITrafficStateCallback callback, int callbackId) {
-        if (!mRegisteredCallbacks.add(binder, new CallbackWrapper(callback), callbackId)) {
+    public void addCallback(ITrafficStateCallback callback) {
+        mCallbackFirstInvocationTracker.put(callback.hashCode(), true);
+        if (!mRegisteredCallbacks.register(callback)) {
             Log.e(TAG, "Failed to add callback");
         }
     }
@@ -71,8 +62,9 @@
     /**
      * Remove an existing callback from the traffic poller.
      */
-    public void removeCallback(int callbackId) {
-        mRegisteredCallbacks.remove(callbackId);
+    public void removeCallback(ITrafficStateCallback callback) {
+        mRegisteredCallbacks.unregister(callback);
+        mCallbackFirstInvocationTracker.delete(callback.hashCode());
     }
 
     /**
@@ -86,26 +78,34 @@
         long sent = newTxPkts - mTxPkts;
         long received = newRxPkts - mRxPkts;
         int dataActivity = WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE;
-        if (sent > 0) {
+        int txPacketThreshold = mContext.getResources().getInteger(
+                R.integer.config_wifiTrafficPollerTxPacketThreshold);
+        int rxPacketThreshold = mContext.getResources().getInteger(
+                R.integer.config_wifiTrafficPollerRxPacketThreshold);
+
+        if (sent > txPacketThreshold) {
             dataActivity |= WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT;
         }
-        if (received > 0) {
+        if (received > rxPacketThreshold) {
             dataActivity |= WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN;
         }
 
-        for (CallbackWrapper wrapper : mRegisteredCallbacks.getCallbacks()) {
-            // if this callback hasn't been triggered before, or the data activity changed,
-            // notify the callback
-            if (wrapper.isFirstInvocation || dataActivity != mLastActivity) {
-                wrapper.isFirstInvocation = false;
+        int itemCount = mRegisteredCallbacks.beginBroadcast();
+        for (int i = 0; i < itemCount; i++) {
+            ITrafficStateCallback callback = mRegisteredCallbacks.getBroadcastItem(i);
+            int callbackIdentifier = callback.hashCode();
+            if (mCallbackFirstInvocationTracker.get(callbackIdentifier)
+                    || dataActivity != mLastActivity) {
+                mCallbackFirstInvocationTracker.put(callbackIdentifier, false);
                 try {
-                    wrapper.callback.onStateChanged(dataActivity);
+                    callback.onStateChanged(dataActivity);
                 } catch (RemoteException e) {
                     // Failed to reach, skip
                     // Client removal is handled in WifiService
                 }
             }
         }
+        mRegisteredCallbacks.finishBroadcast();
 
         mTxPkts = newTxPkts;
         mRxPkts = newRxPkts;
@@ -119,6 +119,6 @@
         pw.println("mTxPkts " + mTxPkts);
         pw.println("mRxPkts " + mRxPkts);
         pw.println("mLastActivity " + mLastActivity);
-        pw.println("mRegisteredCallbacks " + mRegisteredCallbacks.getNumCallbacks());
+        pw.println("mRegisteredCallbacks " + mRegisteredCallbacks.getRegisteredCallbackCount());
     }
 }
diff --git a/service/java/com/android/server/wifi/WifiVendorHal.java b/service/java/com/android/server/wifi/WifiVendorHal.java
index 8ec029d..c48ae2b 100644
--- a/service/java/com/android/server/wifi/WifiVendorHal.java
+++ b/service/java/com/android/server/wifi/WifiVendorHal.java
@@ -15,7 +15,12 @@
  */
 package com.android.server.wifi;
 
+import static android.net.wifi.CoexUnsafeChannel.POWER_CAP_NONE;
+
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageManager;
 import android.hardware.wifi.V1_0.IWifiApIface;
 import android.hardware.wifi.V1_0.IWifiChip;
 import android.hardware.wifi.V1_0.IWifiChipEventCallback;
@@ -34,7 +39,6 @@
 import android.hardware.wifi.V1_0.StaScanData;
 import android.hardware.wifi.V1_0.StaScanDataFlagMask;
 import android.hardware.wifi.V1_0.StaScanResult;
-import android.hardware.wifi.V1_0.WifiBand;
 import android.hardware.wifi.V1_0.WifiDebugHostWakeReasonStats;
 import android.hardware.wifi.V1_0.WifiDebugPacketFateFrameType;
 import android.hardware.wifi.V1_0.WifiDebugRingBufferFlags;
@@ -47,28 +51,43 @@
 import android.hardware.wifi.V1_0.WifiStatus;
 import android.hardware.wifi.V1_0.WifiStatusCode;
 import android.hardware.wifi.V1_2.IWifiChipEventCallback.IfaceInfo;
+import android.hardware.wifi.V1_5.IWifiChip.MultiStaUseCase;
+import android.hardware.wifi.V1_5.IWifiChip.UsableChannelFilter;
+import android.hardware.wifi.V1_5.StaPeerInfo;
+import android.hardware.wifi.V1_5.StaRateStat;
+import android.hardware.wifi.V1_5.WifiBand;
+import android.hardware.wifi.V1_5.WifiIfaceMode;
+import android.hardware.wifi.V1_5.WifiUsableChannel;
 import android.net.MacAddress;
 import android.net.apf.ApfCapabilities;
 import android.net.wifi.ScanResult;
+import android.net.wifi.SoftApConfiguration;
+import android.net.wifi.WifiAvailableChannel;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiScanner;
 import android.net.wifi.WifiSsid;
 import android.os.Handler;
 import android.os.RemoteException;
+import android.os.WorkSource;
 import android.text.TextUtils;
 import android.util.Log;
-import android.util.MutableBoolean;
-import android.util.MutableLong;
+import android.util.Pair;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.HexDump;
-import com.android.internal.util.Preconditions;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.HalDeviceManager.InterfaceDestroyedListener;
 import com.android.server.wifi.WifiLinkLayerStats.ChannelStats;
-import com.android.server.wifi.util.ArrayUtils;
+import com.android.server.wifi.WifiLinkLayerStats.PeerInfo;
+import com.android.server.wifi.WifiLinkLayerStats.RadioStat;
+import com.android.server.wifi.WifiLinkLayerStats.RateStat;
+import com.android.server.wifi.WifiNative.RxFateReport;
+import com.android.server.wifi.WifiNative.TxFateReport;
 import com.android.server.wifi.util.BitMask;
+import com.android.server.wifi.util.GeneralUtil.Mutable;
 import com.android.server.wifi.util.NativeUtil;
+import com.android.wifi.resources.R;
 
 import com.google.errorprone.annotations.CompileTimeConstant;
 
@@ -156,13 +175,8 @@
         return result;
     }
 
-    /**
-     * Logs the argument along with the method name.
-     *
-     * Always returns its argument.
-     */
-    private String stringResult(String result) {
-        if (mVerboseLog == sNoLog) return result;
+    private <T> T objResult(T obj) {
+        if (mVerboseLog == sNoLog) return obj;
         // Currently only seen if verbose logging is on
 
         Thread cur = Thread.currentThread();
@@ -170,10 +184,30 @@
 
         mVerboseLog.err("% returns %")
                 .c(niceMethodName(trace, 3))
-                .c(result)
+                .c(String.valueOf(obj))
                 .flush();
 
-        return result;
+        return obj;
+    }
+
+    /**
+     * Logs the argument along with the method name.
+     *
+     * Always returns its argument.
+     */
+    private <T> T nullResult() {
+        if (mVerboseLog == sNoLog) return null;
+        // Currently only seen if verbose logging is on
+
+        Thread cur = Thread.currentThread();
+        StackTraceElement[] trace = cur.getStackTrace();
+
+        mVerboseLog.err("% returns %")
+                .c(niceMethodName(trace, 3))
+                .c(null)
+                .flush();
+
+        return null;
     }
 
     /**
@@ -239,11 +273,9 @@
     private IWifiChip mIWifiChip;
     private HashMap<String, IWifiStaIface> mIWifiStaIfaces = new HashMap<>();
     private HashMap<String, IWifiApIface> mIWifiApIfaces = new HashMap<>();
-    private HalDeviceManager.InterfaceAvailableForRequestListener
-            mStaIfaceAvailableForRequestListener;
-    private HalDeviceManager.InterfaceAvailableForRequestListener
-            mApIfaceAvailableForRequestListener;
+    private static Context sContext;
     private final HalDeviceManager mHalDeviceManager;
+    private final WifiGlobals mWifiGlobals;
     private final HalDeviceManagerStatusListener mHalDeviceManagerStatusCallbacks;
     private final IWifiStaIfaceEventCallback mIWifiStaIfaceEventCallback;
     private final ChipEventCallback mIWifiChipEventCallback;
@@ -257,9 +289,12 @@
     // https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5
     private final Handler mHalEventHandler;
 
-    public WifiVendorHal(HalDeviceManager halDeviceManager, Handler handler) {
+    public WifiVendorHal(Context context, HalDeviceManager halDeviceManager, Handler handler,
+            WifiGlobals wifiGlobals) {
+        sContext = context;
         mHalDeviceManager = halDeviceManager;
         mHalEventHandler = handler;
+        mWifiGlobals = wifiGlobals;
         mHalDeviceManagerStatusCallbacks = new HalDeviceManagerStatusListener();
         mIWifiStaIfaceEventCallback = new StaIfaceEventCallback();
         mIWifiChipEventCallback = new ChipEventCallback();
@@ -272,7 +307,7 @@
     private void handleRemoteException(RemoteException e) {
         String methodName = niceMethodName(Thread.currentThread().getStackTrace(), 3);
         mVerboseLog.err("% RemoteException in HIDL call %").c(methodName).c(e.toString()).flush();
-        clearState();
+        // Recovery on HAL crash will be triggered by death listener.
     }
 
     private WifiNative.VendorHalDeathEventHandler mDeathEventHandler;
@@ -308,6 +343,16 @@
     }
 
     /**
+     * Register to listen for subsystem restart events from the HAL.
+     *
+     * @param listener SubsystemRestartListener listener object.
+     */
+    public void registerSubsystemRestartListener(
+            HalDeviceManager.SubsystemRestartListener listener) {
+        mHalDeviceManager.registerSubsystemRestartListener(listener, mHalEventHandler);
+    }
+
+    /**
      * Returns whether the vendor HAL is supported on this device or not.
      */
     public boolean isVendorHalSupported() {
@@ -317,6 +362,15 @@
     }
 
     /**
+     * Returns whether the vendor HAL is ready or not.
+     */
+    public boolean isVendorHalReady() {
+        synchronized (sLock) {
+            return mHalDeviceManager.isReady();
+        }
+    }
+
+    /**
      * Bring up the HIDL Vendor HAL and configure for AP (Access Point) mode
      *
      * @return true for success
@@ -326,7 +380,8 @@
             if (!startVendorHal()) {
                 return false;
             }
-            if (TextUtils.isEmpty(createApIface(null))) {
+            if (TextUtils.isEmpty(createApIface(null, null,
+                    SoftApConfiguration.BAND_2GHZ, false))) {
                 stopVendorHal();
                 return false;
             }
@@ -344,7 +399,7 @@
             if (!startVendorHal()) {
                 return false;
             }
-            if (TextUtils.isEmpty(createStaIface(null))) {
+            if (TextUtils.isEmpty(createStaIface(null, null))) {
                 stopVendorHal();
                 return false;
             }
@@ -367,44 +422,6 @@
         }
     }
 
-    /**
-     * Register a STA iface availability listener listed with {@link HalDeviceManager}.
-     *
-     * @param listener Instance of {@link WifiNative.InterfaceAvailableForRequestListener}.
-     */
-    public void registerStaIfaceAvailabilityListener(
-            @NonNull WifiNative.InterfaceAvailableForRequestListener listener) {
-        synchronized (sLock) {
-            Preconditions.checkState(mStaIfaceAvailableForRequestListener == null);
-            mStaIfaceAvailableForRequestListener =
-                    (isAvailable) -> listener.onAvailabilityChanged(isAvailable);
-            if (mHalDeviceManager.isStarted()) {
-                mHalDeviceManager.registerInterfaceAvailableForRequestListener(
-                        IfaceType.STA, mStaIfaceAvailableForRequestListener,
-                        mHalEventHandler);
-            }
-        }
-    }
-
-    /**
-     * Register a AP iface availability listener listed with {@link HalDeviceManager}.
-     *
-     * @param listener Instance of {@link WifiNative.InterfaceAvailableForRequestListener}.
-     *
-     */
-    public void registerApIfaceAvailabilityListener(
-            @NonNull WifiNative.InterfaceAvailableForRequestListener listener) {
-        synchronized (sLock) {
-            Preconditions.checkState(mApIfaceAvailableForRequestListener == null);
-            mApIfaceAvailableForRequestListener =
-                    (isAvailable) -> listener.onAvailabilityChanged(isAvailable);
-            if (mHalDeviceManager.isStarted()) {
-                mHalDeviceManager.registerInterfaceAvailableForRequestListener(
-                        IfaceType.AP, mApIfaceAvailableForRequestListener,
-                        mHalEventHandler);
-            }
-        }
-    }
 
     /** Helper method to lookup the corresponding STA iface object using iface name. */
     private IWifiStaIface getStaIface(@NonNull String ifaceName) {
@@ -435,28 +452,31 @@
      * Create a STA iface using {@link HalDeviceManager}.
      *
      * @param destroyedListener Listener to be invoked when the interface is destroyed.
+     * @param requestorWs Requestor worksource.
      * @return iface name on success, null otherwise.
      */
-    public String createStaIface(InterfaceDestroyedListener destroyedListener) {
+    public String createStaIface(@Nullable InterfaceDestroyedListener destroyedListener,
+            @NonNull WorkSource requestorWs) {
         synchronized (sLock) {
             IWifiStaIface iface = mHalDeviceManager.createStaIface(
-                    new StaInterfaceDestroyedListenerInternal(destroyedListener), null);
+                    new StaInterfaceDestroyedListenerInternal(destroyedListener), null,
+                    requestorWs);
             if (iface == null) {
                 mLog.err("Failed to create STA iface").flush();
-                return stringResult(null);
+                return nullResult();
             }
             String ifaceName = mHalDeviceManager.getName((IWifiIface) iface);
             if (TextUtils.isEmpty(ifaceName)) {
                 mLog.err("Failed to get iface name").flush();
-                return stringResult(null);
+                return nullResult();
             }
             if (!registerStaIfaceCallback(iface)) {
                 mLog.err("Failed to register STA iface callback").flush();
-                return stringResult(null);
+                return nullResult();
             }
             if (!retrieveWifiChip((IWifiIface) iface)) {
                 mLog.err("Failed to get wifi chip").flush();
-                return stringResult(null);
+                return nullResult();
             }
             enableLinkLayerStats(iface);
             mIWifiStaIfaces.put(ifaceName, iface);
@@ -465,6 +485,27 @@
     }
 
     /**
+     * Replace the requestor worksource info for a STA iface using {@link HalDeviceManager}.
+     *
+     * @param ifaceName Name of the interface being removed.
+     * @param requestorWs Requestor worksource.
+     * @return true on success, false otherwise.
+     */
+    public boolean replaceStaIfaceRequestorWs(@NonNull String ifaceName,
+            @NonNull WorkSource requestorWs) {
+        synchronized (sLock) {
+            IWifiStaIface iface = getStaIface(ifaceName);
+            if (iface == null) return boolResult(false);
+
+            if (!mHalDeviceManager.replaceRequestorWs(iface, requestorWs)) {
+                mLog.err("Failed to replace requestor worksource for STA iface").flush();
+                return boolResult(false);
+            }
+            return true;
+        }
+    }
+
+    /**
      * Remove a STA iface using {@link HalDeviceManager}.
      *
      * @param ifaceName Name of the interface being removed.
@@ -474,7 +515,6 @@
         synchronized (sLock) {
             IWifiStaIface iface = getStaIface(ifaceName);
             if (iface == null) return boolResult(false);
-
             if (!mHalDeviceManager.removeIface((IWifiIface) iface)) {
                 mLog.err("Failed to remove STA iface").flush();
                 return boolResult(false);
@@ -509,28 +549,44 @@
         }
     }
 
+    private long getNecessaryCapabilitiesForSoftApMode(@SoftApConfiguration.BandType int band) {
+        long caps = HalDeviceManager.CHIP_CAPABILITY_ANY;
+        if ((band & SoftApConfiguration.BAND_60GHZ) != 0) {
+            caps |= android.hardware.wifi.V1_5.IWifiChip.ChipCapabilityMask.WIGIG;
+        }
+        return caps;
+    }
+
     /**
      * Create a AP iface using {@link HalDeviceManager}.
      *
      * @param destroyedListener Listener to be invoked when the interface is destroyed.
+     * @param requestorWs Requestor worksource.
+     * @param band The requesting band for this AP interface.
+     * @param isBridged Whether or not AP interface is a bridge interface.
      * @return iface name on success, null otherwise.
      */
-    public String createApIface(InterfaceDestroyedListener destroyedListener) {
+    public String createApIface(@Nullable InterfaceDestroyedListener destroyedListener,
+            @NonNull WorkSource requestorWs,
+            @SoftApConfiguration.BandType int band,
+            boolean isBridged) {
         synchronized (sLock) {
             IWifiApIface iface = mHalDeviceManager.createApIface(
-                    new ApInterfaceDestroyedListenerInternal(destroyedListener), null);
+                    getNecessaryCapabilitiesForSoftApMode(band),
+                    new ApInterfaceDestroyedListenerInternal(destroyedListener), null,
+                    requestorWs, isBridged);
             if (iface == null) {
                 mLog.err("Failed to create AP iface").flush();
-                return stringResult(null);
+                return nullResult();
             }
             String ifaceName = mHalDeviceManager.getName((IWifiIface) iface);
             if (TextUtils.isEmpty(ifaceName)) {
                 mLog.err("Failed to get iface name").flush();
-                return stringResult(null);
+                return nullResult();
             }
             if (!retrieveWifiChip((IWifiIface) iface)) {
                 mLog.err("Failed to get wifi chip").flush();
-                return stringResult(null);
+                return nullResult();
             }
             mIWifiApIfaces.put(ifaceName, iface);
             return ifaceName;
@@ -538,6 +594,27 @@
     }
 
     /**
+     * Replace the requestor worksource info for a AP iface using {@link HalDeviceManager}.
+     *
+     * @param ifaceName Name of the interface being removed.
+     * @param requestorWs Requestor worksource.
+     * @return true on success, false otherwise.
+     */
+    public boolean replaceApIfaceRequestorWs(@NonNull String ifaceName,
+            @NonNull WorkSource requestorWs) {
+        synchronized (sLock) {
+            IWifiApIface iface = getApIface(ifaceName);
+            if (iface == null) return boolResult(false);
+
+            if (!mHalDeviceManager.replaceRequestorWs((IWifiIface) iface, requestorWs)) {
+                mLog.err("Failed to replace requestor worksource for AP iface").flush();
+                return boolResult(false);
+            }
+            return true;
+        }
+    }
+
+    /**
      * Remove an AP iface using {@link HalDeviceManager}.
      *
      * @param ifaceName Name of the interface being removed.
@@ -557,6 +634,108 @@
         }
     }
 
+    /**
+     * Helper function to remove specific instance in bridged AP iface.
+     *
+     * @param ifaceName Name of the iface.
+     * @param apIfaceInstance The identity of the ap instance.
+     * @return true if the operation succeeded, false if there is an error in Hal.
+     */
+    public boolean removeIfaceInstanceFromBridgedApIface(@NonNull String ifaceName,
+            @NonNull String apIfaceInstance) {
+        try {
+            android.hardware.wifi.V1_5.IWifiChip iWifiChipV15 = getWifiChipForV1_5Mockable();
+            if (iWifiChipV15 == null) return boolResult(false);
+            return ok(iWifiChipV15.removeIfaceInstanceFromBridgedApIface(
+                    ifaceName, apIfaceInstance));
+        } catch (RemoteException e) {
+            handleRemoteException(e);
+            return false;
+        }
+    }
+
+    @NonNull
+    private ArrayList<android.hardware.wifi.V1_5.IWifiChip.CoexUnsafeChannel>
+            frameworkCoexUnsafeChannelsToHidl(
+                    @NonNull List<android.net.wifi.CoexUnsafeChannel> frameworkUnsafeChannels) {
+        final ArrayList<android.hardware.wifi.V1_5.IWifiChip.CoexUnsafeChannel> hidlList =
+                new ArrayList<>();
+        if (!SdkLevel.isAtLeastS()) {
+            return hidlList;
+        }
+        for (android.net.wifi.CoexUnsafeChannel frameworkUnsafeChannel : frameworkUnsafeChannels) {
+            final android.hardware.wifi.V1_5.IWifiChip.CoexUnsafeChannel hidlUnsafeChannel =
+                    new android.hardware.wifi.V1_5.IWifiChip.CoexUnsafeChannel();
+            switch (frameworkUnsafeChannel.getBand()) {
+                case (WifiScanner.WIFI_BAND_24_GHZ):
+                    hidlUnsafeChannel.band = WifiBand.BAND_24GHZ;
+                    break;
+                case (WifiScanner.WIFI_BAND_5_GHZ):
+                    hidlUnsafeChannel.band = WifiBand.BAND_5GHZ;
+                    break;
+                case (WifiScanner.WIFI_BAND_6_GHZ):
+                    hidlUnsafeChannel.band = WifiBand.BAND_6GHZ;
+                    break;
+                case (WifiScanner.WIFI_BAND_60_GHZ):
+                    hidlUnsafeChannel.band = WifiBand.BAND_60GHZ;
+                    break;
+                default:
+                    mLog.err("Tried to set unsafe channel with unknown band: %")
+                            .c(frameworkUnsafeChannel.getBand())
+                            .flush();
+                    continue;
+            }
+            hidlUnsafeChannel.channel = frameworkUnsafeChannel.getChannel();
+            final int powerCapDbm = frameworkUnsafeChannel.getPowerCapDbm();
+            if (powerCapDbm != POWER_CAP_NONE) {
+                hidlUnsafeChannel.powerCapDbm = powerCapDbm;
+            } else {
+                hidlUnsafeChannel.powerCapDbm =
+                        android.hardware.wifi.V1_5.IWifiChip.PowerCapConstant.NO_POWER_CAP;
+            }
+            hidlList.add(hidlUnsafeChannel);
+        }
+        return hidlList;
+    }
+
+    private int frameworkCoexRestrictionsToHidl(@WifiManager.CoexRestriction int restrictions) {
+        int hidlRestrictions = 0;
+        if (!SdkLevel.isAtLeastS()) {
+            return hidlRestrictions;
+        }
+        if ((restrictions & WifiManager.COEX_RESTRICTION_WIFI_DIRECT) != 0) {
+            hidlRestrictions |= android.hardware.wifi.V1_5.IWifiChip.CoexRestriction.WIFI_DIRECT;
+        }
+        if ((restrictions & WifiManager.COEX_RESTRICTION_SOFTAP) != 0) {
+            hidlRestrictions |= android.hardware.wifi.V1_5.IWifiChip.CoexRestriction.SOFTAP;
+        }
+        if ((restrictions & WifiManager.COEX_RESTRICTION_WIFI_AWARE) != 0) {
+            hidlRestrictions |= android.hardware.wifi.V1_5.IWifiChip.CoexRestriction.WIFI_AWARE;
+        }
+        return hidlRestrictions;
+    }
+
+    /**
+     * Set the current coex unsafe channels to avoid and their restrictions.
+     * @param unsafeChannels List of {@link android.net.wifi.CoexUnsafeChannel} to avoid.
+     * @param restrictions int containing a bitwise-OR combination of
+     *                     {@link WifiManager.CoexRestriction}.
+     * @return true if the operation succeeded, false if there is an error in Hal.
+     */
+    public boolean setCoexUnsafeChannels(
+            @NonNull List<android.net.wifi.CoexUnsafeChannel> unsafeChannels, int restrictions) {
+        try {
+            android.hardware.wifi.V1_5.IWifiChip iWifiChipV15 = getWifiChipForV1_5Mockable();
+            if (iWifiChipV15 == null) return boolResult(false);
+            return ok(iWifiChipV15.setCoexUnsafeChannels(
+                    frameworkCoexUnsafeChannelsToHidl(unsafeChannels),
+                    frameworkCoexRestrictionsToHidl(restrictions)));
+        } catch (RemoteException e) {
+            handleRemoteException(e);
+            return false;
+        }
+    }
+
     private boolean retrieveWifiChip(IWifiIface iface) {
         synchronized (sLock) {
             boolean registrationNeeded = mIWifiChip == null;
@@ -668,7 +847,7 @@
             IWifiStaIface iface = getStaIface(ifaceName);
             if (iface == null) return boolResult(false);
             try {
-                MutableBoolean ans = new MutableBoolean(false);
+                Mutable<Boolean> ans = new Mutable<>(false);
                 WifiNative.ScanCapabilities out = capabilities;
                 iface.getBackgroundScanCapabilities((status, cap) -> {
                             if (!ok(status)) return;
@@ -770,6 +949,19 @@
                 return WifiBand.BAND_24GHZ_5GHZ;
             case WifiScanner.WIFI_BAND_BOTH_WITH_DFS:
                 return WifiBand.BAND_24GHZ_5GHZ_WITH_DFS;
+            case WifiScanner.WIFI_BAND_6_GHZ:
+                return WifiBand.BAND_6GHZ;
+            case WifiScanner.WIFI_BAND_24_5_6_GHZ:
+                return WifiBand.BAND_24GHZ_5GHZ_6GHZ;
+            case WifiScanner.WIFI_BAND_24_5_WITH_DFS_6_GHZ:
+                return WifiBand.BAND_24GHZ_5GHZ_WITH_DFS_6GHZ;
+            case WifiScanner.WIFI_BAND_60_GHZ:
+                return WifiBand.BAND_60GHZ;
+            case WifiScanner.WIFI_BAND_24_5_6_60_GHZ:
+                return WifiBand.BAND_24GHZ_5GHZ_6GHZ_60GHZ;
+            case WifiScanner.WIFI_BAND_24_5_WITH_DFS_6_60_GHZ:
+                return WifiBand.BAND_24GHZ_5GHZ_WITH_DFS_6GHZ_60GHZ;
+            case WifiScanner.WIFI_BAND_24_GHZ_WITH_5GHZ_DFS:
             default:
                 throw new IllegalArgumentException("bad band " + frameworkBand);
         }
@@ -931,10 +1123,13 @@
      * @return the statistics, or null if unable to do so
      */
     public WifiLinkLayerStats getWifiLinkLayerStats(@NonNull String ifaceName) {
-        if (getWifiStaIfaceForV1_3Mockable(ifaceName) != null) {
+        if (getWifiStaIfaceForV1_5Mockable(ifaceName) != null) {
+            return getWifiLinkLayerStats_1_5_Internal(ifaceName);
+        } else if (getWifiStaIfaceForV1_3Mockable(ifaceName) != null) {
             return getWifiLinkLayerStats_1_3_Internal(ifaceName);
+        } else {
+            return getWifiLinkLayerStats_internal(ifaceName);
         }
-        return getWifiLinkLayerStats_internal(ifaceName);
     }
 
     private WifiLinkLayerStats getWifiLinkLayerStats_internal(@NonNull String ifaceName) {
@@ -982,6 +1177,29 @@
         return stats;
     }
 
+    private WifiLinkLayerStats getWifiLinkLayerStats_1_5_Internal(@NonNull String ifaceName) {
+        class AnswerBox {
+            public android.hardware.wifi.V1_5.StaLinkLayerStats value = null;
+        }
+        AnswerBox answer = new AnswerBox();
+        synchronized (sLock) {
+            try {
+                android.hardware.wifi.V1_5.IWifiStaIface iface =
+                        getWifiStaIfaceForV1_5Mockable(ifaceName);
+                if (iface == null) return null;
+                iface.getLinkLayerStats_1_5((status, stats) -> {
+                    if (!ok(status)) return;
+                    answer.value = stats;
+                });
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return null;
+            }
+        }
+        WifiLinkLayerStats stats = frameworkFromHalLinkLayerStats_1_5(answer.value);
+        return stats;
+    }
+
 
     /**
      * Makes the framework version of link layer stats from the hal version.
@@ -1012,6 +1230,21 @@
         return out;
     }
 
+    /**
+     * Makes the framework version of link layer stats from the hal version.
+     */
+    @VisibleForTesting
+    static WifiLinkLayerStats frameworkFromHalLinkLayerStats_1_5(
+            android.hardware.wifi.V1_5.StaLinkLayerStats stats) {
+        if (stats == null) return null;
+        WifiLinkLayerStats out = new WifiLinkLayerStats();
+        setIfaceStats_1_5(out, stats.iface);
+        setRadioStats_1_5(out, stats.radios);
+        setTimeStamp(out, stats.timeStampInMs);
+        out.version = WifiLinkLayerStats.V1_5;
+        return out;
+    }
+
     private static void setIfaceStats(WifiLinkLayerStats stats, StaLinkLayerIfaceStats iface) {
         if (iface == null) return;
         stats.beacon_rx = iface.beaconRx;
@@ -1039,10 +1272,61 @@
         stats.retries_vo = iface.wmeVoPktStats.retries;
     }
 
+    private static void setIfaceStats_1_5(WifiLinkLayerStats stats,
+            android.hardware.wifi.V1_5.StaLinkLayerIfaceStats iface) {
+        if (iface == null) return;
+        setIfaceStats(stats, iface.V1_0);
+        stats.timeSliceDutyCycleInPercent = iface.timeSliceDutyCycleInPercent;
+        // WME Best Effort Access Category
+        stats.contentionTimeMinBeInUsec = iface.wmeBeContentionTimeStats.contentionTimeMinInUsec;
+        stats.contentionTimeMaxBeInUsec = iface.wmeBeContentionTimeStats.contentionTimeMaxInUsec;
+        stats.contentionTimeAvgBeInUsec = iface.wmeBeContentionTimeStats.contentionTimeAvgInUsec;
+        stats.contentionNumSamplesBe = iface.wmeBeContentionTimeStats.contentionNumSamples;
+        // WME Background Access Category
+        stats.contentionTimeMinBkInUsec = iface.wmeBkContentionTimeStats.contentionTimeMinInUsec;
+        stats.contentionTimeMaxBkInUsec = iface.wmeBkContentionTimeStats.contentionTimeMaxInUsec;
+        stats.contentionTimeAvgBkInUsec = iface.wmeBkContentionTimeStats.contentionTimeAvgInUsec;
+        stats.contentionNumSamplesBk = iface.wmeBkContentionTimeStats.contentionNumSamples;
+        // WME Video Access Category
+        stats.contentionTimeMinViInUsec = iface.wmeViContentionTimeStats.contentionTimeMinInUsec;
+        stats.contentionTimeMaxViInUsec = iface.wmeViContentionTimeStats.contentionTimeMaxInUsec;
+        stats.contentionTimeAvgViInUsec = iface.wmeViContentionTimeStats.contentionTimeAvgInUsec;
+        stats.contentionNumSamplesVi = iface.wmeViContentionTimeStats.contentionNumSamples;
+        // WME Voice Access Category
+        stats.contentionTimeMinVoInUsec = iface.wmeVoContentionTimeStats.contentionTimeMinInUsec;
+        stats.contentionTimeMaxVoInUsec = iface.wmeVoContentionTimeStats.contentionTimeMaxInUsec;
+        stats.contentionTimeAvgVoInUsec = iface.wmeVoContentionTimeStats.contentionTimeAvgInUsec;
+        stats.contentionNumSamplesVo = iface.wmeVoContentionTimeStats.contentionNumSamples;
+        // Peer information statistics
+        stats.peerInfo = new PeerInfo[iface.peers.size()];
+        for (int i = 0; i < stats.peerInfo.length; i++) {
+            PeerInfo peer = new PeerInfo();
+            StaPeerInfo staPeerInfo = iface.peers.get(i);
+            peer.staCount = staPeerInfo.staCount;
+            peer.chanUtil = staPeerInfo.chanUtil;
+            RateStat[] rateStats = new RateStat[staPeerInfo.rateStats.size()];
+            for (int j = 0; j < staPeerInfo.rateStats.size(); j++) {
+                rateStats[j] = new RateStat();
+                StaRateStat staRateStat = staPeerInfo.rateStats.get(j);
+                rateStats[j].preamble = staRateStat.rateInfo.preamble;
+                rateStats[j].nss = staRateStat.rateInfo.nss;
+                rateStats[j].bw = staRateStat.rateInfo.bw;
+                rateStats[j].rateMcsIdx = staRateStat.rateInfo.rateMcsIdx;
+                rateStats[j].bitRateInKbps = staRateStat.rateInfo.bitRateInKbps;
+                rateStats[j].txMpdu = staRateStat.txMpdu;
+                rateStats[j].rxMpdu = staRateStat.rxMpdu;
+                rateStats[j].mpduLost = staRateStat.mpduLost;
+                rateStats[j].retries = staRateStat.retries;
+            }
+            peer.rateStats = rateStats;
+            stats.peerInfo[i] = peer;
+        }
+    }
+
     private static void setRadioStats(WifiLinkLayerStats stats,
             List<StaLinkLayerRadioStats> radios) {
         if (radios == null) return;
-        // NOTE(b/36176141): Figure out how to coalesce this info for multi radio devices.
+        // Do not coalesce this info for multi radio devices with older HALs.
         if (radios.size() > 0) {
             StaLinkLayerRadioStats radioStats = radios.get(0);
             stats.on_time = radioStats.onTimeInMs;
@@ -1053,38 +1337,105 @@
             }
             stats.rx_time = radioStats.rxTimeInMs;
             stats.on_time_scan = radioStats.onTimeInMsForScan;
+            stats.numRadios = 1;
         }
     }
 
+    /**
+     * Set individual radio stats from the hal radio stats
+     */
+    private static void setFrameworkPerRadioStatsFromHidl(int radioId, RadioStat radio,
+            android.hardware.wifi.V1_3.StaLinkLayerRadioStats hidlRadioStats) {
+        radio.radio_id = radioId;
+        radio.on_time = hidlRadioStats.V1_0.onTimeInMs;
+        radio.tx_time = hidlRadioStats.V1_0.txTimeInMs;
+        radio.rx_time = hidlRadioStats.V1_0.rxTimeInMs;
+        radio.on_time_scan = hidlRadioStats.V1_0.onTimeInMsForScan;
+        radio.on_time_nan_scan = hidlRadioStats.onTimeInMsForNanScan;
+        radio.on_time_background_scan = hidlRadioStats.onTimeInMsForBgScan;
+        radio.on_time_roam_scan = hidlRadioStats.onTimeInMsForRoamScan;
+        radio.on_time_pno_scan = hidlRadioStats.onTimeInMsForPnoScan;
+        radio.on_time_hs20_scan = hidlRadioStats.onTimeInMsForHs20Scan;
+        /* Copy list of channel stats */
+        for (android.hardware.wifi.V1_3.WifiChannelStats channelStats
+                : hidlRadioStats.channelStats) {
+            ChannelStats channelStatsEntry = new ChannelStats();
+            channelStatsEntry.frequency = channelStats.channel.centerFreq;
+            channelStatsEntry.radioOnTimeMs = channelStats.onTimeInMs;
+            channelStatsEntry.ccaBusyTimeMs = channelStats.ccaBusyTimeInMs;
+            radio.channelStatsMap.put(channelStats.channel.centerFreq, channelStatsEntry);
+        }
+    }
+
+    /**
+     * If config_wifiLinkLayerAllRadiosStatsAggregationEnabled is set to true, aggregate
+     * the radio stats from all the radios else process the stats from Radio 0 only.
+     */
+    private static void aggregateFrameworkRadioStatsFromHidl(int radioIndex,
+            WifiLinkLayerStats stats,
+            android.hardware.wifi.V1_3.StaLinkLayerRadioStats hidlRadioStats) {
+        if (!sContext.getResources()
+                .getBoolean(R.bool.config_wifiLinkLayerAllRadiosStatsAggregationEnabled)
+                && radioIndex > 0) {
+            return;
+        }
+        // Aggregate the radio stats from all the radios
+        stats.on_time += hidlRadioStats.V1_0.onTimeInMs;
+        stats.tx_time += hidlRadioStats.V1_0.txTimeInMs;
+        // Aggregate tx_time_per_level based on the assumption that the length of
+        // txTimeInMsPerLevel is the same across all radios. So txTimeInMsPerLevel on other
+        // radios at array indices greater than the length of first radio will be dropped.
+        if (stats.tx_time_per_level == null) {
+            stats.tx_time_per_level = new int[hidlRadioStats.V1_0.txTimeInMsPerLevel.size()];
+        }
+        for (int i = 0; i < hidlRadioStats.V1_0.txTimeInMsPerLevel.size()
+                && i < stats.tx_time_per_level.length; i++) {
+            stats.tx_time_per_level[i] += hidlRadioStats.V1_0.txTimeInMsPerLevel.get(i);
+        }
+        stats.rx_time += hidlRadioStats.V1_0.rxTimeInMs;
+        stats.on_time_scan += hidlRadioStats.V1_0.onTimeInMsForScan;
+        stats.on_time_nan_scan += hidlRadioStats.onTimeInMsForNanScan;
+        stats.on_time_background_scan += hidlRadioStats.onTimeInMsForBgScan;
+        stats.on_time_roam_scan += hidlRadioStats.onTimeInMsForRoamScan;
+        stats.on_time_pno_scan += hidlRadioStats.onTimeInMsForPnoScan;
+        stats.on_time_hs20_scan += hidlRadioStats.onTimeInMsForHs20Scan;
+        /* Copy list of channel stats */
+        for (android.hardware.wifi.V1_3.WifiChannelStats channelStats
+                : hidlRadioStats.channelStats) {
+            ChannelStats channelStatsEntry =
+                    stats.channelStatsMap.get(channelStats.channel.centerFreq);
+            if (channelStatsEntry == null) {
+                channelStatsEntry = new ChannelStats();
+                channelStatsEntry.frequency = channelStats.channel.centerFreq;
+                stats.channelStatsMap.put(channelStats.channel.centerFreq, channelStatsEntry);
+            }
+            channelStatsEntry.radioOnTimeMs += channelStats.onTimeInMs;
+            channelStatsEntry.ccaBusyTimeMs += channelStats.ccaBusyTimeInMs;
+        }
+        stats.numRadios++;
+    }
+
     private static void setRadioStats_1_3(WifiLinkLayerStats stats,
             List<android.hardware.wifi.V1_3.StaLinkLayerRadioStats> radios) {
         if (radios == null) return;
-        // NOTE(b/36176141): Figure out how to coalesce this info for multi radio devices.
-        if (radios.size() > 0) {
-            android.hardware.wifi.V1_3.StaLinkLayerRadioStats radioStats = radios.get(0);
-            stats.on_time = radioStats.V1_0.onTimeInMs;
-            stats.tx_time = radioStats.V1_0.txTimeInMs;
-            stats.tx_time_per_level = new int[radioStats.V1_0.txTimeInMsPerLevel.size()];
-            for (int i = 0; i < stats.tx_time_per_level.length; i++) {
-                stats.tx_time_per_level[i] = radioStats.V1_0.txTimeInMsPerLevel.get(i);
-            }
-            stats.rx_time = radioStats.V1_0.rxTimeInMs;
-            stats.on_time_scan = radioStats.V1_0.onTimeInMsForScan;
-            stats.on_time_nan_scan = radioStats.onTimeInMsForNanScan;
-            stats.on_time_background_scan = radioStats.onTimeInMsForBgScan;
-            stats.on_time_roam_scan = radioStats.onTimeInMsForRoamScan;
-            stats.on_time_pno_scan = radioStats.onTimeInMsForPnoScan;
-            stats.on_time_hs20_scan = radioStats.onTimeInMsForHs20Scan;
-            /* Copy list of channel stats */
-            for (int i = 0; i < radioStats.channelStats.size(); i++) {
-                android.hardware.wifi.V1_3.WifiChannelStats channelStats =
-                        radioStats.channelStats.get(i);
-                ChannelStats channelStatsEntry = new ChannelStats();
-                channelStatsEntry.frequency = channelStats.channel.centerFreq;
-                channelStatsEntry.radioOnTimeMs = channelStats.onTimeInMs;
-                channelStatsEntry.ccaBusyTimeMs = channelStats.ccaBusyTimeInMs;
-                stats.channelStatsMap.put(channelStats.channel.centerFreq, channelStatsEntry);
-            }
+        int radioIndex = 0;
+        for (android.hardware.wifi.V1_3.StaLinkLayerRadioStats radioStats : radios) {
+            aggregateFrameworkRadioStatsFromHidl(radioIndex, stats, radioStats);
+            radioIndex++;
+        }
+    }
+
+    private static void setRadioStats_1_5(WifiLinkLayerStats stats,
+            List<android.hardware.wifi.V1_5.StaLinkLayerRadioStats> radios) {
+        if (radios == null) return;
+        int radioIndex = 0;
+        stats.radioStats = new RadioStat[radios.size()];
+        for (android.hardware.wifi.V1_5.StaLinkLayerRadioStats radioStats : radios) {
+            RadioStat radio = new RadioStat();
+            setFrameworkPerRadioStatsFromHidl(radioStats.radioId, radio, radioStats.V1_3);
+            stats.radioStats[radioIndex] = radio;
+            aggregateFrameworkRadioStatsFromHidl(radioIndex, stats, radioStats.V1_3);
+            radioIndex++;
         }
     }
 
@@ -1133,6 +1484,16 @@
 
     /**
      * Translation table used by getSupportedFeatureSet for translating IWifiChip caps for
+     * additional capabilities introduced in V1.5
+     */
+    private static final long[][] sChipFeatureCapabilityTranslation15 = {
+            {WifiManager.WIFI_FEATURE_INFRA_60G,
+                    android.hardware.wifi.V1_5.IWifiChip.ChipCapabilityMask.WIGIG
+            }
+    };
+
+    /**
+     * Translation table used by getSupportedFeatureSet for translating IWifiChip caps for
      * additional capabilities introduced in V1.3
      */
     private static final long[][] sChipFeatureCapabilityTranslation13 = {
@@ -1163,6 +1524,26 @@
     }
 
     /**
+     * Feature bit mask translation for Chip V1.5
+     *
+     * @param capabilities bitmask defined IWifiChip.ChipCapabilityMask
+     * @return bitmask defined by WifiManager.WIFI_FEATURE_*
+     */
+    @VisibleForTesting
+    long wifiFeatureMaskFromChipCapabilities_1_5(int capabilities) {
+        // First collect features from previous versions
+        long features = wifiFeatureMaskFromChipCapabilities_1_3(capabilities);
+
+        // Next collect features for V1_5 version
+        for (int i = 0; i < sChipFeatureCapabilityTranslation15.length; i++) {
+            if ((capabilities & sChipFeatureCapabilityTranslation15[i][1]) != 0) {
+                features |= sChipFeatureCapabilityTranslation15[i][0];
+            }
+        }
+        return features;
+    }
+
+    /**
      * Feature bit mask translation for Chip V1.3
      *
      * @param capabilities bitmask defined IWifiChip.ChipCapabilityMask
@@ -1242,6 +1623,34 @@
     }
 
     /**
+     * Translation table used by getSupportedFeatureSetFromPackageManager
+     * for translating System caps
+     */
+    private static final Pair[] sSystemFeatureCapabilityTranslation = new Pair[] {
+            Pair.create(WifiManager.WIFI_FEATURE_INFRA, PackageManager.FEATURE_WIFI),
+            Pair.create(WifiManager.WIFI_FEATURE_P2P, PackageManager.FEATURE_WIFI_DIRECT),
+            Pair.create(WifiManager.WIFI_FEATURE_AWARE, PackageManager.FEATURE_WIFI_AWARE),
+    };
+
+    /**
+     * If VendorHal is not supported, reading PackageManager
+     * system features to return basic capabilities.
+     *
+     * @return bitmask defined by WifiManager.WIFI_FEATURE_*
+     */
+    private long getSupportedFeatureSetFromPackageManager() {
+        long featureSet = 0;
+        final PackageManager pm = sContext.getPackageManager();
+        for (Pair pair: sSystemFeatureCapabilityTranslation) {
+            if (pm.hasSystemFeature((String) pair.second)) {
+                featureSet |= (long) pair.first;
+            }
+        }
+        enter("System feature set: %").c(featureSet).flush();
+        return featureSet;
+    }
+
+    /**
      * Get the supported features
      *
      * The result may differ depending on the mode (STA or AP)
@@ -1251,14 +1660,20 @@
      */
     public long getSupportedFeatureSet(@NonNull String ifaceName) {
         long featureSet = 0;
-        if (!mHalDeviceManager.isStarted()) {
-            return featureSet; // TODO: can't get capabilities with Wi-Fi down
+        if (!mHalDeviceManager.isStarted() || !mHalDeviceManager.isSupported()) {
+            return getSupportedFeatureSetFromPackageManager();
         }
         try {
-            final MutableLong feat = new MutableLong(0);
+            final Mutable<Long> feat = new Mutable<>(0L);
             synchronized (sLock) {
                 android.hardware.wifi.V1_3.IWifiChip iWifiChipV13 = getWifiChipForV1_3Mockable();
-                if (iWifiChipV13 != null) {
+                android.hardware.wifi.V1_5.IWifiChip iWifiChipV15 = getWifiChipForV1_5Mockable();
+                if (iWifiChipV15 != null) {
+                    iWifiChipV15.getCapabilities_1_5((status, capabilities) -> {
+                        if (!ok(status)) return;
+                        feat.value = wifiFeatureMaskFromChipCapabilities_1_5(capabilities);
+                    });
+                } else if (iWifiChipV13 != null) {
                     iWifiChipV13.getCapabilities_1_3((status, capabilities) -> {
                         if (!ok(status)) return;
                         feat.value = wifiFeatureMaskFromChipCapabilities_1_3(capabilities);
@@ -1266,7 +1681,7 @@
                 } else if (mIWifiChip != null) {
                     mIWifiChip.getCapabilities((status, capabilities) -> {
                         if (!ok(status)) return;
-                        feat.value = wifiFeatureMaskFromChipCapabilities(capabilities);
+                        feat.value = (long) wifiFeatureMaskFromChipCapabilities(capabilities);
                     });
                 }
 
@@ -1284,6 +1699,10 @@
             return 0;
         }
 
+        if (mWifiGlobals.isWpa3SaeH2eSupported()) {
+            featureSet |= WifiManager.WIFI_FEATURE_SAE_H2E;
+        }
+
         Set<Integer> supportedIfaceTypes = mHalDeviceManager.getSupportedIfaceTypes();
         if (supportedIfaceTypes.contains(IfaceType.STA)) {
             featureSet |= WifiManager.WIFI_FEATURE_INFRA;
@@ -1308,27 +1727,64 @@
      * @param mac MAC address to change into
      * @return true for success
      */
-    public boolean setMacAddress(@NonNull String ifaceName, @NonNull MacAddress mac) {
+    public boolean setStaMacAddress(@NonNull String ifaceName, @NonNull MacAddress mac) {
         byte[] macByteArray = mac.toByteArray();
         synchronized (sLock) {
             try {
                 android.hardware.wifi.V1_2.IWifiStaIface sta12 =
                         getWifiStaIfaceForV1_2Mockable(ifaceName);
-                if (sta12 != null) {
-                    return ok(sta12.setMacAddress(macByteArray));
-                }
-
-                android.hardware.wifi.V1_4.IWifiApIface ap14 =
-                        getWifiApIfaceForV1_4Mockable(ifaceName);
-                if (ap14 != null) {
-                    return ok(ap14.setMacAddress(macByteArray));
-                }
+                if (sta12 == null) return boolResult(false);
+                return ok(sta12.setMacAddress(macByteArray));
             } catch (RemoteException e) {
                 handleRemoteException(e);
                 return false;
             }
         }
-        return boolResult(false);
+    }
+
+    /**
+     * Reset MAC address to factory MAC address on the given interface
+     *
+     * @param ifaceName Name of the interface
+     * @return true for success
+     */
+    public boolean resetApMacToFactoryMacAddress(@NonNull String ifaceName) {
+        synchronized (sLock) {
+            try {
+                android.hardware.wifi.V1_5.IWifiApIface ap15 =
+                        getWifiApIfaceForV1_5Mockable(ifaceName);
+                if (ap15 == null) {
+                    MacAddress mac = getApFactoryMacAddress(ifaceName);
+                    return mac != null && setApMacAddress(ifaceName, mac);
+                }
+                return ok(ap15.resetToFactoryMacAddress());
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Set Mac address on the given interface
+     *
+     * @param ifaceName Name of the interface
+     * @param mac MAC address to change into
+     * @return true for success
+     */
+    public boolean setApMacAddress(@NonNull String ifaceName, @NonNull MacAddress mac) {
+        byte[] macByteArray = mac.toByteArray();
+        synchronized (sLock) {
+            try {
+                android.hardware.wifi.V1_4.IWifiApIface ap14 =
+                        getWifiApIfaceForV1_4Mockable(ifaceName);
+                if (ap14 == null) return boolResult(false);
+                return ok(ap14.setMacAddress(macByteArray));
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return false;
+            }
+        }
     }
 
     /**
@@ -1336,21 +1792,25 @@
      *
      * @param ifaceName Name of the interface
      */
-    public boolean isSetMacAddressSupported(@NonNull String ifaceName) {
+    public boolean isStaSetMacAddressSupported(@NonNull String ifaceName) {
         synchronized (sLock) {
             android.hardware.wifi.V1_2.IWifiStaIface sta12 =
                     getWifiStaIfaceForV1_2Mockable(ifaceName);
-            if (sta12 != null) {
-                return true;
-            }
+            return sta12 != null;
+        }
+    }
 
+    /**
+     * Returns true if Hal version supports setMacAddress, otherwise false.
+     *
+     * @param ifaceName Name of the interface
+     */
+    public boolean isApSetMacAddressSupported(@NonNull String ifaceName) {
+        synchronized (sLock) {
             android.hardware.wifi.V1_4.IWifiApIface ap14 =
                     getWifiApIfaceForV1_4Mockable(ifaceName);
-            if (ap14 != null) {
-                return true;
-            }
+            return ap14 != null;
         }
-        return false;
     }
 
     /**
@@ -1359,7 +1819,7 @@
      * @param ifaceName Name of the interface
      * @return factory MAC address of the interface or null.
      */
-    public MacAddress getFactoryMacAddress(@NonNull String ifaceName) {
+    public MacAddress getStaFactoryMacAddress(@NonNull String ifaceName) {
         class AnswerBox {
             public MacAddress mac = null;
         }
@@ -1369,29 +1829,45 @@
 
                 android.hardware.wifi.V1_3.IWifiStaIface sta13 =
                         getWifiStaIfaceForV1_3Mockable(ifaceName);
-                if (sta13 != null) {
-                    sta13.getFactoryMacAddress((status, macBytes) -> {
-                        if (!ok(status)) return;
-                        box.mac = MacAddress.fromBytes(macBytes);
-                    });
-                    return box.mac;
-                }
-
-                android.hardware.wifi.V1_4.IWifiApIface ap14 =
-                        getWifiApIfaceForV1_4Mockable(ifaceName);
-                if (ap14 != null) {
-                    ap14.getFactoryMacAddress((status, macBytes) -> {
-                        if (!ok(status)) return;
-                        box.mac = MacAddress.fromBytes(macBytes);
-                    });
-                    return box.mac;
-                }
+                if (sta13 == null) return null;
+                sta13.getFactoryMacAddress((status, macBytes) -> {
+                    if (!ok(status)) return;
+                    box.mac = MacAddress.fromBytes(macBytes);
+                });
+                return box.mac;
             } catch (RemoteException e) {
                 handleRemoteException(e);
                 return null;
             }
         }
-        return null;
+    }
+
+    /**
+     * Get factory MAC address of the given interface
+     *
+     * @param ifaceName Name of the interface
+     * @return factory MAC address of the interface or null.
+     */
+    public MacAddress getApFactoryMacAddress(@NonNull String ifaceName) {
+        class AnswerBox {
+            public MacAddress mac = null;
+        }
+        synchronized (sLock) {
+            try {
+                AnswerBox box = new AnswerBox();
+                android.hardware.wifi.V1_4.IWifiApIface ap14 =
+                        getWifiApIfaceForV1_4Mockable(ifaceName);
+                if (ap14 == null) return null;
+                ap14.getFactoryMacAddress((status, macBytes) -> {
+                    if (!ok(status)) return;
+                    box.mac = MacAddress.fromBytes(macBytes);
+                });
+                return box.mac;
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return null;
+            }
+        }
     }
 
     /**
@@ -1484,13 +1960,68 @@
     }
 
     /**
+     * Set country code for this Wifi chip
+     *
+     * @param countryCode - two-letter country code (as ISO 3166)
+     * @return true for success
+     */
+    public boolean setChipCountryCode(String countryCode) {
+        if (countryCode == null) return boolResult(false);
+        if (countryCode.length() != 2) return boolResult(false);
+        byte[] code;
+        try {
+            code = NativeUtil.stringToByteArray(countryCode);
+        } catch (IllegalArgumentException e) {
+            return boolResult(false);
+        }
+        synchronized (sLock) {
+            try {
+                android.hardware.wifi.V1_5.IWifiChip iWifiChipV15 = getWifiChipForV1_5Mockable();
+                if (iWifiChipV15 == null) return boolResult(false);
+                WifiStatus status = iWifiChipV15.setCountryCode(code);
+                if (!ok(status)) return false;
+                return true;
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Get the names of the bridged AP instances.
+     *
+     * @param ifaceName Name of the bridged interface.
+     * @return A list which contains the names of the bridged AP instances.
+     */
+    @Nullable
+    public List<String> getBridgedApInstances(@NonNull String ifaceName) {
+        synchronized (sLock) {
+            try {
+                Mutable<List<String>> instancesResp  = new Mutable<>();
+                android.hardware.wifi.V1_5.IWifiApIface ap15 =
+                        getWifiApIfaceForV1_5Mockable(ifaceName);
+                if (ap15 == null) return null;
+                ap15.getBridgedInstances((status, instances) -> {
+                    if (!ok(status)) return;
+                    instancesResp.value = new ArrayList<>(instances);
+                });
+                return instancesResp.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return null;
+            }
+        }
+    }
+
+    /**
      * Set country code for this AP iface.
      *
      * @param ifaceName Name of the interface.
      * @param countryCode - two-letter country code (as ISO 3166)
      * @return true for success
      */
-    public boolean setCountryCodeHal(@NonNull String ifaceName, String countryCode) {
+    public boolean setApCountryCode(@NonNull String ifaceName, String countryCode) {
         if (countryCode == null) return boolResult(false);
         if (countryCode.length() != 2) return boolResult(false);
         byte[] code;
@@ -1901,38 +2432,31 @@
      * Reports the outbound frames for the most recent association (space allowing).
      *
      * @param ifaceName Name of the interface.
-     * @param reportBufs
-     * @return true for success
+     * @return list of TxFateReports up to size {@link WifiLoggerHal#MAX_FATE_LOG_LEN}, or empty
+     * list on failure.
      */
-    public boolean getTxPktFates(@NonNull String ifaceName, WifiNative.TxFateReport[] reportBufs) {
-        if (ArrayUtils.isEmpty(reportBufs)) return boolResult(false);
+    public List<TxFateReport> getTxPktFates(@NonNull String ifaceName) {
         synchronized (sLock) {
             IWifiStaIface iface = getStaIface(ifaceName);
-            if (iface == null) return boolResult(false);
+            if (iface == null) return objResult(new ArrayList<>());
             try {
-                MutableBoolean ok = new MutableBoolean(false);
+                List<TxFateReport> reportBufs = new ArrayList<>();
                 iface.getDebugTxPacketFates((status, fates) -> {
-                            if (!ok(status)) return;
-                            int i = 0;
-                            for (WifiDebugTxPacketFateReport fate : fates) {
-                                if (i >= reportBufs.length) break;
-                                byte code = halToFrameworkTxPktFate(fate.fate);
-                                long us = fate.frameInfo.driverTimestampUsec;
-                                byte type =
-                                        halToFrameworkPktFateFrameType(fate.frameInfo.frameType);
-                                byte[] frame =
-                                        NativeUtil.byteArrayFromArrayList(
-                                                fate.frameInfo.frameContent);
-                                reportBufs[i++] =
-                                        new WifiNative.TxFateReport(code, us, type, frame);
-                            }
-                            ok.value = true;
-                        }
-                );
-                return ok.value;
+                    if (!ok(status)) return;
+                    for (WifiDebugTxPacketFateReport fate : fates) {
+                        if (reportBufs.size() >= WifiLoggerHal.MAX_FATE_LOG_LEN) break;
+                        byte code = halToFrameworkTxPktFate(fate.fate);
+                        long us = fate.frameInfo.driverTimestampUsec;
+                        byte type = halToFrameworkPktFateFrameType(fate.frameInfo.frameType);
+                        byte[] frame = NativeUtil.byteArrayFromArrayList(
+                                fate.frameInfo.frameContent);
+                        reportBufs.add(new TxFateReport(code, us, type, frame));
+                    }
+                });
+                return reportBufs;
             } catch (RemoteException e) {
                 handleRemoteException(e);
-                return false;
+                return new ArrayList<>();
             }
         }
     }
@@ -1943,38 +2467,31 @@
      * Reports the inbound frames for the most recent association (space allowing).
      *
      * @param ifaceName Name of the interface.
-     * @param reportBufs
-     * @return true for success
+     * @return list of RxFateReports up to size {@link WifiLoggerHal#MAX_FATE_LOG_LEN}, or empty
+     * list on failure.
      */
-    public boolean getRxPktFates(@NonNull String ifaceName, WifiNative.RxFateReport[] reportBufs) {
-        if (ArrayUtils.isEmpty(reportBufs)) return boolResult(false);
+    public List<RxFateReport> getRxPktFates(@NonNull String ifaceName) {
         synchronized (sLock) {
             IWifiStaIface iface = getStaIface(ifaceName);
-            if (iface == null) return boolResult(false);
+            if (iface == null) return objResult(new ArrayList<>());
             try {
-                MutableBoolean ok = new MutableBoolean(false);
+                List<RxFateReport> reportBufs = new ArrayList<>();
                 iface.getDebugRxPacketFates((status, fates) -> {
-                            if (!ok(status)) return;
-                            int i = 0;
-                            for (WifiDebugRxPacketFateReport fate : fates) {
-                                if (i >= reportBufs.length) break;
-                                byte code = halToFrameworkRxPktFate(fate.fate);
-                                long us = fate.frameInfo.driverTimestampUsec;
-                                byte type =
-                                        halToFrameworkPktFateFrameType(fate.frameInfo.frameType);
-                                byte[] frame =
-                                        NativeUtil.byteArrayFromArrayList(
-                                                fate.frameInfo.frameContent);
-                                reportBufs[i++] =
-                                        new WifiNative.RxFateReport(code, us, type, frame);
-                            }
-                            ok.value = true;
-                        }
-                );
-                return ok.value;
+                    if (!ok(status)) return;
+                    for (WifiDebugRxPacketFateReport fate : fates) {
+                        if (reportBufs.size() >= WifiLoggerHal.MAX_FATE_LOG_LEN) break;
+                        byte code = halToFrameworkRxPktFate(fate.fate);
+                        long us = fate.frameInfo.driverTimestampUsec;
+                        byte type = halToFrameworkPktFateFrameType(fate.frameInfo.frameType);
+                        byte[] frame = NativeUtil.byteArrayFromArrayList(
+                                fate.frameInfo.frameContent);
+                        reportBufs.add(new RxFateReport(code, us, type, frame));
+                    }
+                });
+                return reportBufs;
             } catch (RemoteException e) {
                 handleRemoteException(e);
-                return false;
+                return new ArrayList<>();
             }
         }
     }
@@ -2199,27 +2716,30 @@
      * Query the firmware roaming capabilities.
      *
      * @param ifaceName Name of the interface.
-     * @param capabilities object to be filled in
-     * @return true for success; false for failure
+     * @return capabilities object on success, null otherwise.
      */
-    public boolean getRoamingCapabilities(@NonNull String ifaceName,
-                                          WifiNative.RoamingCapabilities capabilities) {
+    @Nullable
+    public WifiNative.RoamingCapabilities getRoamingCapabilities(@NonNull String ifaceName) {
         synchronized (sLock) {
             IWifiStaIface iface = getStaIface(ifaceName);
-            if (iface == null) return boolResult(false);
+            if (iface == null) return nullResult();
             try {
-                MutableBoolean ok = new MutableBoolean(false);
-                WifiNative.RoamingCapabilities out = capabilities;
+                Mutable<Boolean> ok = new Mutable<>(false);
+                WifiNative.RoamingCapabilities out = new WifiNative.RoamingCapabilities();
                 iface.getRoamingCapabilities((status, cap) -> {
                     if (!ok(status)) return;
                     out.maxBlocklistSize = cap.maxBlacklistSize;
                     out.maxAllowlistSize = cap.maxWhitelistSize;
                     ok.value = true;
                 });
-                return ok.value;
+                if (ok.value) {
+                    return out;
+                } else {
+                    return null;
+                }
             } catch (RemoteException e) {
                 handleRemoteException(e);
-                return false;
+                return null;
             }
         }
     }
@@ -2232,7 +2752,8 @@
      * @return SET_FIRMWARE_ROAMING_SUCCESS, SET_FIRMWARE_ROAMING_FAILURE,
      *         or SET_FIRMWARE_ROAMING_BUSY
      */
-    public int enableFirmwareRoaming(@NonNull String ifaceName, int state) {
+    public @WifiNative.RoamingEnableStatus int enableFirmwareRoaming(@NonNull String ifaceName,
+            @WifiNative.RoamingEnableState int state) {
         synchronized (sLock) {
             IWifiStaIface iface = getStaIface(ifaceName);
             if (iface == null) return WifiNative.SET_FIRMWARE_ROAMING_FAILURE;
@@ -2291,7 +2812,15 @@
                     for (String ssidStr : config.allowlistSsids) {
                         byte[] ssid = NativeUtil.byteArrayFromArrayList(
                                 NativeUtil.decodeSsid(ssidStr));
-                        roamingConfig.ssidWhitelist.add(ssid);
+                        // HIDL code is throwing InvalidArgumentException when ssidWhitelist has
+                        // SSIDs with less than 32 byte length this is due to HAL definition of
+                        // SSID declared it as 32-byte fixed length array. Thus pad additional
+                        // bytes with 0's to pass SSIDs as byte arrays of 32 length
+                        byte[] ssid_32 = new byte[32];
+                        for (int i = 0; i < ssid.length; i++) {
+                            ssid_32[i] = ssid[i];
+                        }
+                        roamingConfig.ssidWhitelist.add(ssid_32);
                     }
                 }
 
@@ -2353,6 +2882,17 @@
     }
 
     /**
+     * Method to mock out the V1_5 IWifiChip retrieval in unit tests.
+     *
+     * @return 1.5 IWifiChip object if the device is running the 1.5 wifi hal service, null
+     * otherwise.
+     */
+    protected android.hardware.wifi.V1_5.IWifiChip getWifiChipForV1_5Mockable() {
+        if (mIWifiChip == null) return null;
+        return android.hardware.wifi.V1_5.IWifiChip.castFrom(mIWifiChip);
+    }
+
+    /**
      * Method to mock out the V1_2 IWifiStaIface retrieval in unit tests.
      *
      * @param ifaceName Name of the interface
@@ -2380,6 +2920,20 @@
         return android.hardware.wifi.V1_3.IWifiStaIface.castFrom(iface);
     }
 
+    /**
+     * Method to mock out the V1_5 IWifiStaIface retrieval in unit tests.
+     *
+     * @param ifaceName Name of the interface
+     * @return 1.5 IWifiStaIface object if the device is running the 1.5 wifi hal service, null
+     * otherwise.
+     */
+    protected android.hardware.wifi.V1_5.IWifiStaIface getWifiStaIfaceForV1_5Mockable(
+            @NonNull String ifaceName) {
+        IWifiStaIface iface = getStaIface(ifaceName);
+        if (iface == null) return null;
+        return android.hardware.wifi.V1_5.IWifiStaIface.castFrom(iface);
+    }
+
     protected android.hardware.wifi.V1_4.IWifiApIface getWifiApIfaceForV1_4Mockable(
             String ifaceName) {
         IWifiApIface iface = getApIface(ifaceName);
@@ -2387,6 +2941,13 @@
         return android.hardware.wifi.V1_4.IWifiApIface.castFrom(iface);
     }
 
+    protected android.hardware.wifi.V1_5.IWifiApIface getWifiApIfaceForV1_5Mockable(
+            String ifaceName) {
+        IWifiApIface iface = getApIface(ifaceName);
+        if (iface == null) return null;
+        return android.hardware.wifi.V1_5.IWifiApIface.castFrom(iface);
+    }
+
     /**
      * sarPowerBackoffRequired_1_1()
      * This method checks if we need to backoff wifi Tx power due to SAR requirements.
@@ -2635,7 +3196,7 @@
     }
 
     /**
-     * Returns whether STA/AP concurrency is supported or not.
+     * Returns whether STA + AP concurrency is supported or not.
      */
     public boolean isStaApConcurrencySupported() {
         synchronized (sLock) {
@@ -2646,6 +3207,119 @@
         }
     }
 
+    /**
+     * Returns whether STA + STA concurrency is supported or not.
+     */
+    public boolean isStaStaConcurrencySupported() {
+        synchronized (sLock) {
+            return mHalDeviceManager.canSupportIfaceCombo(new SparseArray<Integer>() {{
+                    put(IfaceType.STA, 2);
+                }});
+        }
+    }
+
+    /**
+     * Returns whether a new AP iface can be created or not.
+     */
+    public boolean isItPossibleToCreateApIface(@NonNull WorkSource requestorWs) {
+        synchronized (sLock) {
+            return mHalDeviceManager.isItPossibleToCreateIface(IfaceType.AP, requestorWs);
+        }
+    }
+
+    /**
+     * Returns whether a new STA iface can be created or not.
+     */
+    public boolean isItPossibleToCreateStaIface(@NonNull WorkSource requestorWs) {
+        synchronized (sLock) {
+            return mHalDeviceManager.isItPossibleToCreateIface(IfaceType.STA, requestorWs);
+        }
+
+    }
+    /**
+     * Set primary connection when multiple STA ifaces are active.
+     *
+     * @param ifaceName Name of the interface.
+     * @return true for success
+     */
+    public boolean setMultiStaPrimaryConnection(@NonNull String ifaceName) {
+        if (TextUtils.isEmpty(ifaceName)) return boolResult(false);
+        synchronized (sLock) {
+            try {
+                android.hardware.wifi.V1_5.IWifiChip iWifiChipV15 = getWifiChipForV1_5Mockable();
+                if (iWifiChipV15 == null) return boolResult(false);
+                WifiStatus status = iWifiChipV15.setMultiStaPrimaryConnection(ifaceName);
+                if (!ok(status)) return false;
+                return true;
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return false;
+            }
+        }
+    }
+
+    private byte frameworkMultiStaUseCaseToHidl(@WifiNative.MultiStaUseCase int useCase)
+            throws IllegalArgumentException {
+        switch (useCase) {
+            case WifiNative.DUAL_STA_TRANSIENT_PREFER_PRIMARY:
+                return MultiStaUseCase.DUAL_STA_TRANSIENT_PREFER_PRIMARY;
+            case WifiNative.DUAL_STA_NON_TRANSIENT_UNBIASED:
+                return MultiStaUseCase.DUAL_STA_NON_TRANSIENT_UNBIASED;
+            default:
+                throw new IllegalArgumentException("Invalid use case " + useCase);
+        }
+    }
+
+    /**
+     * Set use-case when multiple STA ifaces are active.
+     *
+     * @param useCase one of the use cases.
+     * @return true for success
+     */
+    public boolean setMultiStaUseCase(@WifiNative.MultiStaUseCase int useCase) {
+        synchronized (sLock) {
+            try {
+                android.hardware.wifi.V1_5.IWifiChip iWifiChipV15 = getWifiChipForV1_5Mockable();
+                if (iWifiChipV15 == null) return boolResult(false);
+                WifiStatus status = iWifiChipV15.setMultiStaUseCase(
+                        frameworkMultiStaUseCaseToHidl(useCase));
+                if (!ok(status)) return false;
+                return true;
+            } catch (IllegalArgumentException e) {
+                mLog.e("Invalid use case " + e);
+                return boolResult(false);
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Notify scan mode state to driver to save power in scan-only mode.
+     *
+     * @param ifaceName Name of the interface.
+     * @param enable whether is in scan-only mode
+     * @return true for success
+     */
+    public boolean setScanMode(@NonNull String ifaceName, boolean enable) {
+        synchronized (sLock) {
+            try {
+                android.hardware.wifi.V1_5.IWifiStaIface iface =
+                        getWifiStaIfaceForV1_5Mockable(ifaceName);
+                if (iface != null) {
+                    WifiStatus status = iface.setScanMode(enable);
+                    if (!ok(status)) return false;
+                    return true;
+                }
+                return false;
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return false;
+            }
+        }
+    }
+
     // This creates a blob of IE elements from the array received.
     // TODO: This ugly conversion can be removed if we put IE elements in ScanResult.
     private static byte[] hidlIeArrayToFrameworkIeBlob(ArrayList<WifiInformationElement> ies) {
@@ -3096,20 +3770,128 @@
                     handler.onDeath();
                 }
             }
-            if (isStarted) {
-                synchronized (sLock) {
-                    if (mStaIfaceAvailableForRequestListener != null) {
-                        mHalDeviceManager.registerInterfaceAvailableForRequestListener(
-                                IfaceType.STA, mStaIfaceAvailableForRequestListener,
-                                mHalEventHandler);
-                    }
-                    if (mApIfaceAvailableForRequestListener != null) {
-                        mHalDeviceManager.registerInterfaceAvailableForRequestListener(
-                                IfaceType.AP, mApIfaceAvailableForRequestListener,
-                                mHalEventHandler);
-                    }
+        }
+    }
+
+    /**
+     * Trigger subsystem restart in vendor side
+     */
+    public boolean startSubsystemRestart() {
+        synchronized (sLock) {
+            android.hardware.wifi.V1_5.IWifiChip iWifiChipV15 = getWifiChipForV1_5Mockable();
+            if (iWifiChipV15 != null) {
+                try {
+                    return ok(iWifiChipV15.triggerSubsystemRestart());
+                } catch (RemoteException e) {
+                    handleRemoteException(e);
+                    return false;
                 }
             }
+            // HAL version does not support this api
+            return false;
+        }
+    }
+
+    /**
+     * Convert framework's operational mode to HAL's operational mode.
+     */
+    private int frameworkToHalIfaceMode(@WifiAvailableChannel.OpMode int mode) {
+        int halMode = 0;
+        if ((mode & WifiAvailableChannel.OP_MODE_STA) != 0) {
+            halMode |= WifiIfaceMode.IFACE_MODE_STA;
+        }
+        if ((mode & WifiAvailableChannel.OP_MODE_SAP) != 0) {
+            halMode |= WifiIfaceMode.IFACE_MODE_SOFTAP;
+        }
+        if ((mode & WifiAvailableChannel.OP_MODE_WIFI_DIRECT_CLI) != 0) {
+            halMode |= WifiIfaceMode.IFACE_MODE_P2P_CLIENT;
+        }
+        if ((mode & WifiAvailableChannel.OP_MODE_WIFI_DIRECT_GO) != 0) {
+            halMode |= WifiIfaceMode.IFACE_MODE_P2P_GO;
+        }
+        if ((mode & WifiAvailableChannel.OP_MODE_WIFI_AWARE) != 0) {
+            halMode |= WifiIfaceMode.IFACE_MODE_NAN;
+        }
+        if ((mode & WifiAvailableChannel.OP_MODE_TDLS) != 0) {
+            halMode |= WifiIfaceMode.IFACE_MODE_TDLS;
+        }
+        return halMode;
+    }
+
+    /**
+     * Convert from HAL's operational mode to framework's operational mode.
+     */
+    private @WifiAvailableChannel.OpMode int frameworkFromHalIfaceMode(int halMode) {
+        int mode = 0;
+        if ((halMode & WifiIfaceMode.IFACE_MODE_STA) != 0) {
+            mode |= WifiAvailableChannel.OP_MODE_STA;
+        }
+        if ((halMode & WifiIfaceMode.IFACE_MODE_SOFTAP) != 0) {
+            mode |= WifiAvailableChannel.OP_MODE_SAP;
+        }
+        if ((halMode & WifiIfaceMode.IFACE_MODE_P2P_CLIENT) != 0) {
+            mode |= WifiAvailableChannel.OP_MODE_WIFI_DIRECT_CLI;
+        }
+        if ((halMode & WifiIfaceMode.IFACE_MODE_P2P_GO) != 0) {
+            mode |= WifiAvailableChannel.OP_MODE_WIFI_DIRECT_GO;
+        }
+        if ((halMode & WifiIfaceMode.IFACE_MODE_NAN) != 0) {
+            mode |= WifiAvailableChannel.OP_MODE_WIFI_AWARE;
+        }
+        if ((halMode & WifiIfaceMode.IFACE_MODE_TDLS) != 0) {
+            mode |= WifiAvailableChannel.OP_MODE_TDLS;
+        }
+        return mode;
+    }
+
+    /**
+     * Convert framework's WifiAvailableChannel.FILTER_* to HAL's UsableChannelFilter.
+     */
+    private int frameworkToHalUsableFilter(@WifiAvailableChannel.Filter int filter) {
+        int halFilter = 0;  // O implies no additional filter other than regulatory (default)
+
+        if ((filter & WifiAvailableChannel.FILTER_CONCURRENCY) != 0) {
+            halFilter |= UsableChannelFilter.CONCURRENCY;
+        }
+        if ((filter & WifiAvailableChannel.FILTER_CELLULAR_COEXISTENCE) != 0) {
+            halFilter |= UsableChannelFilter.CELLULAR_COEXISTENCE;
+        }
+        return halFilter;
+    }
+
+    /**
+     * Retrieve the list of usable Wifi channels.
+     */
+    public List<WifiAvailableChannel> getUsableChannels(
+            @WifiScanner.WifiBand int band,
+            @WifiAvailableChannel.OpMode int mode,
+            @WifiAvailableChannel.Filter int filter) {
+        synchronized (sLock) {
+            try {
+                android.hardware.wifi.V1_5.IWifiChip iWifiChipV15 = getWifiChipForV1_5Mockable();
+                if (iWifiChipV15 == null) {
+                    return null;
+                }
+                Mutable<List<WifiAvailableChannel>> answer = new Mutable<>();
+                iWifiChipV15.getUsableChannels(
+                        makeWifiBandFromFrameworkBand(band),
+                        frameworkToHalIfaceMode(mode),
+                        frameworkToHalUsableFilter(filter), (status, channels) -> {
+                            if (!ok(status)) return;
+                            answer.value = new ArrayList<>();
+                            for (WifiUsableChannel ch : channels) {
+                                answer.value.add(new WifiAvailableChannel(ch.channel,
+                                        frameworkFromHalIfaceMode(ch.ifaceModeMask)));
+                            }
+                        });
+                return answer.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return null;
+            } catch (IllegalArgumentException e) {
+                mLog.e("Illegal argument for getUsableChannels() " + e);
+                return null;
+            }
         }
     }
 }
diff --git a/service/java/com/android/server/wifi/WrongPasswordNotifier.java b/service/java/com/android/server/wifi/WrongPasswordNotifier.java
index c645ccf..a5c039b 100644
--- a/service/java/com/android/server/wifi/WrongPasswordNotifier.java
+++ b/service/java/com/android/server/wifi/WrongPasswordNotifier.java
@@ -16,25 +16,16 @@
 
 package com.android.server.wifi;
 
-import android.app.ActivityManager;
 import android.app.Notification;
-import android.app.NotificationManager;
 import android.app.PendingIntent;
-import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.graphics.drawable.Icon;
-import android.os.UserHandle;
 import android.provider.Settings;
-import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.server.wifi.util.NativeUtil;
 
-import java.util.List;
-
 /**
  * Responsible for notifying user for wrong password errors.
  */
@@ -51,14 +42,14 @@
     private boolean mWrongPasswordDetected;
 
     private final WifiContext mContext;
-    private final NotificationManager mNotificationManager;
+    private final WifiNotificationManager mNotificationManager;
     private final FrameworkFacade mFrameworkFacade;
 
-    public WrongPasswordNotifier(WifiContext context, FrameworkFacade frameworkFacade) {
+    public WrongPasswordNotifier(WifiContext context, FrameworkFacade frameworkFacade,
+            WifiNotificationManager wifiNotificationManager) {
         mContext = context;
         mFrameworkFacade = frameworkFacade;
-        mNotificationManager =
-                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        mNotificationManager = wifiNotificationManager;
     }
 
     /**
@@ -81,26 +72,13 @@
         }
     }
 
-    private String getSettingsPackageName() {
-        Intent intent = new Intent(Settings.ACTION_WIFI_SETTINGS);
-        List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentActivitiesAsUser(
-                intent, PackageManager.MATCH_SYSTEM_ONLY | PackageManager.MATCH_DEFAULT_ONLY,
-                UserHandle.of(ActivityManager.getCurrentUser()));
-        if (resolveInfos == null || resolveInfos.isEmpty()) {
-            Log.e(TAG, "Failed to resolve wifi settings activity");
-            return null;
-        }
-        // Pick the first one if there are more than 1 since the list is ordered from best to worst.
-        return resolveInfos.get(0).activityInfo.packageName;
-    }
-
     /**
      * Display wrong password notification for a given Wi-Fi network (specified by its SSID).
      *
      * @param ssid SSID of the Wi-FI network
      */
     private void showNotification(String ssid) {
-        String settingsPackage = getSettingsPackageName();
+        String settingsPackage = mFrameworkFacade.getSettingsPackageName(mContext);
         if (settingsPackage == null) return;
         Intent intent = new Intent(Settings.ACTION_WIFI_SETTINGS)
                 .setPackage(settingsPackage)
@@ -116,7 +94,8 @@
                         com.android.wifi.resources.R.string.wifi_available_title_failed_to_connect))
                 .setContentText(ssid)
                 .setContentIntent(mFrameworkFacade.getActivity(
-                        mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT))
+                        mContext, 0, intent,
+                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
                 .setColor(mContext.getResources().getColor(
                         android.R.color.system_notification_accent_color));
         mNotificationManager.notify(NOTIFICATION_ID, builder.build());
@@ -130,6 +109,6 @@
     private void dismissNotification() {
         // Notification might have already been dismissed, either by user or timeout. It is
         // still okay to cancel it if already dismissed.
-        mNotificationManager.cancel(null, NOTIFICATION_ID);
+        mNotificationManager.cancel(NOTIFICATION_ID);
     }
 }
diff --git a/service/java/com/android/server/wifi/aware/Capabilities.java b/service/java/com/android/server/wifi/aware/Capabilities.java
index 68480c7..6c06945 100644
--- a/service/java/com/android/server/wifi/aware/Capabilities.java
+++ b/service/java/com/android/server/wifi/aware/Capabilities.java
@@ -39,6 +39,7 @@
     public int maxQueuedTransmitMessages;
     public int maxSubscribeInterfaceAddresses;
     public int supportedCipherSuites;
+    public boolean isInstantCommunicationModeSupported;
 
     /**
      * Converts the internal capabilities to a parcelable & potentially app-facing
@@ -52,6 +53,8 @@
         bundle.putInt(Characteristics.KEY_MAX_MATCH_FILTER_LENGTH, maxMatchFilterLen);
         bundle.putInt(Characteristics.KEY_SUPPORTED_CIPHER_SUITES,
                 toPublicCipherSuites(supportedCipherSuites));
+        bundle.putBoolean(Characteristics.KEY_IS_INSTANT_COMMUNICATION_MODE_SUPPORTED,
+                isInstantCommunicationModeSupported);
         return new Characteristics(bundle);
     }
 
@@ -81,6 +84,7 @@
                 + ", maxQueuedTransmitMessages=" + maxQueuedTransmitMessages
                 + ", maxSubscribeInterfaceAddresses=" + maxSubscribeInterfaceAddresses
                 + ", supportedCipherSuites=" + supportedCipherSuites
+                + ", isInstantCommunicationModeSupport=" + isInstantCommunicationModeSupported
                 + "]";
     }
 }
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareClientState.java b/service/java/com/android/server/wifi/aware/WifiAwareClientState.java
index c96cf28..e96ec30 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareClientState.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareClientState.java
@@ -23,6 +23,7 @@
 import android.net.wifi.aware.IWifiAwareEventCallback;
 import android.net.wifi.util.HexEncoding;
 import android.os.RemoteException;
+import android.os.WorkSource;
 import android.util.Log;
 import android.util.SparseArray;
 
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java b/service/java/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java
index bb90a06..68f4f71 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java
@@ -50,6 +50,7 @@
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Pair;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.modules.utils.build.SdkLevel;
@@ -57,6 +58,7 @@
 import com.android.server.wifi.util.NetdWrapper;
 import com.android.server.wifi.util.WifiPermissionsUtil;
 import com.android.server.wifi.util.WifiPermissionsWrapper;
+import com.android.wifi.resources.R;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -108,7 +110,7 @@
     private static final NetworkCapabilities sNetworkCapabilitiesFilter =
             makeNetworkCapabilitiesFilter();
     private final Set<String> mInterfaces = new HashSet<>();
-    private final Map<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation>
+    private final ArrayMap<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation>
             mNetworkRequestsCache = new ArrayMap<>();
     private Context mContext;
     private WifiAwareMetrics mAwareMetrics;
@@ -177,11 +179,22 @@
         mDbg = verbose | VDBG;
     }
 
+    /**
+     * Get the number of the NDPs is already set up.
+     */
+    public int getNumOfNdps() {
+        int numOfNdps = 0;
+        for (AwareNetworkRequestInformation requestInformation : mNetworkRequestsCache.values()) {
+            numOfNdps += requestInformation.ndpInfos.size();
+        }
+        return numOfNdps;
+    }
+
     private Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation>
                 getNetworkRequestByNdpId(int ndpId) {
         for (Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation> entry :
                 mNetworkRequestsCache.entrySet()) {
-            if (entry.getValue().ndpId == ndpId) {
+            if (entry.getValue().ndpInfos.contains(ndpId)) {
                 return entry;
             }
         }
@@ -249,7 +262,6 @@
             String name = AWARE_INTERFACE_PREFIX + i;
             mMgr.deleteDataPathInterface(name);
         }
-        mMgr.releaseAwareInterface();
     }
 
     /**
@@ -284,8 +296,10 @@
      *
      * @param networkSpecifier The network specifier provided as part of the initiate request.
      * @param ndpId            The ID assigned to the data-path.
+     * @return False if has error, otherwise return true
      */
-    public void onDataPathInitiateSuccess(WifiAwareNetworkSpecifier networkSpecifier, int ndpId) {
+    public boolean onDataPathInitiateSuccess(WifiAwareNetworkSpecifier networkSpecifier,
+            int ndpId) {
         if (mDbg) {
             Log.v(TAG,
                     "onDataPathInitiateSuccess: networkSpecifier=" + networkSpecifier + ", ndpId="
@@ -297,20 +311,27 @@
             Log.w(TAG, "onDataPathInitiateSuccess: network request not found for networkSpecifier="
                     + networkSpecifier);
             mMgr.endDataPath(ndpId);
-            return;
+            return false;
         }
 
         if (nnri.state
                 != AwareNetworkRequestInformation.STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE) {
             Log.w(TAG, "onDataPathInitiateSuccess: network request in incorrect state: state="
                     + nnri.state);
+            mMgr.endDataPath(ndpId);
             mNetworkRequestsCache.remove(networkSpecifier);
-            declareUnfullfillableAndEndDp(nnri, ndpId);
-            return;
+            declareUnfullfillable(nnri);
+            return false;
         }
 
-        nnri.state = AwareNetworkRequestInformation.STATE_WAIT_FOR_CONFIRM;
-        nnri.ndpId = ndpId;
+        NdpInfo ndpInfo = new NdpInfo(ndpId);
+        ndpInfo.state = NdpInfo.STATE_WAIT_FOR_CONFIRM;
+        ndpInfo.peerDiscoveryMac = nnri.specifiedPeerDiscoveryMac;
+        nnri.ndpInfos.put(ndpId, ndpInfo);
+
+        nnri.state = AwareNetworkRequestInformation.STATE_IN_SETUP;
+
+        return true;
     }
 
     /**
@@ -340,7 +361,8 @@
                     + nnri.state);
         }
 
-        mAwareMetrics.recordNdpStatus(reason, networkSpecifier.isOutOfBand(), nnri.startTimestamp);
+        mAwareMetrics.recordNdpStatus(reason, networkSpecifier.isOutOfBand(),
+                mClock.getElapsedSinceBootMillis());
     }
 
 
@@ -353,9 +375,9 @@
      * @param mac           The discovery MAC address of the peer.
      * @param ndpId         The locally assigned ID for the data-path.
      * @param message       The app_info HAL field (peer's info: binary blob)
-     * @return The network specifier of the data-path (or null if none/error)
+     * @return False if has error, otherwise return true
      */
-    public WifiAwareNetworkSpecifier onDataPathRequest(int pubSubId, byte[] mac, int ndpId,
+    public boolean onDataPathRequest(int pubSubId, byte[] mac, int ndpId,
             byte[] message) {
         if (mDbg) {
             Log.v(TAG,
@@ -363,38 +385,6 @@
                             HexEncoding.encode(mac)) + ", ndpId=" + ndpId);
         }
 
-        WifiAwareNetworkSpecifier networkSpecifier = null;
-        AwareNetworkRequestInformation nnri = null;
-        for (Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation> entry :
-                mNetworkRequestsCache.entrySet()) {
-            /*
-             * Checking that the incoming request (from the Initiator) matches the request
-             * we (the Responder) already have set up. The rules are:
-             * - The discovery session (pub/sub ID) must match.
-             * - The peer MAC address (if specified - i.e. non-null) must match. A null peer MAC ==
-             *   accept (otherwise matching) requests from any peer MAC.
-             * - The request must be pending (i.e. we could have completed requests for the same
-             *   parameters)
-             */
-            if (entry.getValue().pubSubId != 0 && entry.getValue().pubSubId != pubSubId) {
-                continue;
-            }
-
-            if (entry.getValue().peerDiscoveryMac != null && !Arrays.equals(
-                    entry.getValue().peerDiscoveryMac, mac)) {
-                continue;
-            }
-
-            if (entry.getValue().state
-                    != AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_REQUEST) {
-                continue;
-            }
-
-            networkSpecifier = entry.getKey();
-            nnri = entry.getValue();
-            break;
-        }
-
         // it is also possible that this is an initiator-side data-path request indication (which
         // happens when the Responder responds). In such a case it will be matched by the NDP ID.
         Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation> nnriE =
@@ -402,26 +392,69 @@
         if (nnriE != null) {
             if (VDBG) {
                 Log.v(TAG,
-                        "onDataPathRequest: initiator-side indication for " + nnriE.getValue());
+                        "onDataPathRequest: initiator-side indication for " + nnriE);
             }
-
+            NdpInfo ndpInfo = nnriE.getValue().ndpInfos.get(ndpId);
             // potential transmission mechanism for port/transport-protocol information from
             // Responder (alternative to confirm message)
             NetworkInformationData.ParsedResults peerServerInfo = NetworkInformationData.parseTlv(
                     message);
+            if (ndpInfo == null) {
+                Log.wtf(TAG, "onDataPathRequest: initiator-side ndpInfo is null?!");
+                return false;
+            }
             if (peerServerInfo != null) {
                 if (peerServerInfo.port != 0) {
-                    nnriE.getValue().peerPort = peerServerInfo.port;
+                    ndpInfo.peerPort = peerServerInfo.port;
                 }
                 if (peerServerInfo.transportProtocol != -1) {
-                    nnriE.getValue().peerTransportProtocol = peerServerInfo.transportProtocol;
+                    ndpInfo.peerTransportProtocol = peerServerInfo.transportProtocol;
                 }
                 if (peerServerInfo.ipv6Override != null) {
-                    nnriE.getValue().peerIpv6Override = peerServerInfo.ipv6Override;
+                    ndpInfo.peerIpv6Override = peerServerInfo.ipv6Override;
                 }
             }
 
-            return null; // ignore this for NDP set up flow: it is used to obtain app_info from Resp
+            return false; //ignore this for NDP set up flow: it is used to obtain app_info from Resp
+        }
+
+        AwareNetworkRequestInformation nnri = null;
+        WifiAwareNetworkSpecifier networkSpecifier = null;
+        for (int i = 0; i < mNetworkRequestsCache.size(); i++) {
+            AwareNetworkRequestInformation requestInfo = mNetworkRequestsCache.valueAt(i);
+            /*
+             * Checking that the incoming request (from the Initiator) matches the request
+             * we (the Responder) already have set up. The rules are:
+             * - The discovery session (pub/sub ID) must match.
+             * - The peer MAC address (if specified - i.e. non-null) must match. A null peer MAC ==
+             *   accept (otherwise matching) requests from any peer MAC.
+             * - The request must be pending (i.e. we could have completed requests for the same
+             *   parameters)
+             */
+            if (requestInfo.pubSubId != 0 && requestInfo.pubSubId != pubSubId) {
+                continue;
+            }
+
+            if (requestInfo.specifiedPeerDiscoveryMac != null) {
+                if (Arrays.equals(requestInfo.specifiedPeerDiscoveryMac, mac) && requestInfo.state
+                        == AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_REQUEST) {
+                    // If a peer specific request matches, use it.
+                    networkSpecifier = mNetworkRequestsCache.keyAt(i);
+                    nnri = requestInfo;
+                    break;
+                }
+                continue;
+            }
+            // For Accept any, multiple NDP may setup in the same time. In idle or terminating state
+            // it will not accept any request.
+            if (requestInfo.state != AwareNetworkRequestInformation.STATE_IDLE
+                    && requestInfo.state != AwareNetworkRequestInformation.STATE_TERMINATING) {
+                // If an accepts any request matches, continually find if there is a peer specific
+                // one. If there isn't any matched peer specific one, use the accepts any peer
+                // request.
+                networkSpecifier = mNetworkRequestsCache.keyAt(i);
+                nnri = requestInfo;
+            }
         }
 
         if (nnri == null) {
@@ -431,33 +464,35 @@
                 Log.v(TAG, "onDataPathRequest: network request cache = " + mNetworkRequestsCache);
             }
             mMgr.respondToDataPathRequest(false, ndpId, "", null, null, null, false);
-            return null;
+            return false;
         }
 
-        if (nnri.peerDiscoveryMac == null) {
-            // the "accept anyone" request is now specific
-            nnri.peerDiscoveryMac = mac;
+        if (nnri.interfaceName == null) {
+            nnri.interfaceName = selectInterfaceForRequest(nnri);
         }
-        nnri.interfaceName = selectInterfaceForRequest(nnri);
         if (nnri.interfaceName == null) {
             Log.w(TAG,
                     "onDataPathRequest: request " + networkSpecifier + " no interface available");
             mMgr.respondToDataPathRequest(false, ndpId, "", null, null, null, false);
             mNetworkRequestsCache.remove(networkSpecifier);
             mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri);
-            return null;
+            return false;
         }
 
-        nnri.state = AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_RESPOND_RESPONSE;
-        nnri.ndpId = ndpId;
-        nnri.startTimestamp = mClock.getElapsedSinceBootMillis();
+        NdpInfo ndpInfo = new NdpInfo(ndpId);
+        ndpInfo.state = NdpInfo.STATE_RESPONDER_WAIT_FOR_RESPOND_RESPONSE;
+        ndpInfo.peerDiscoveryMac = mac;
+        ndpInfo.startTimestamp = mClock.getElapsedSinceBootMillis();
+        nnri.ndpInfos.put(ndpId, ndpInfo);
+
+        nnri.state = AwareNetworkRequestInformation.STATE_IN_SETUP;
         mMgr.respondToDataPathRequest(true, ndpId, nnri.interfaceName, nnri.networkSpecifier.pmk,
                 nnri.networkSpecifier.passphrase,
                 NetworkInformationData.buildTlv(nnri.networkSpecifier.port,
                         nnri.networkSpecifier.transportProtocol),
                 nnri.networkSpecifier.isOutOfBand());
 
-        return networkSpecifier;
+        return true;
     }
 
     /**
@@ -470,19 +505,10 @@
         if (mDbg) {
             Log.v(TAG, "onRespondToDataPathRequest: ndpId=" + ndpId + ", success=" + success);
         }
+        Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation> nnriE =
+                getNetworkRequestByNdpId(ndpId);
 
-        WifiAwareNetworkSpecifier networkSpecifier = null;
-        AwareNetworkRequestInformation nnri = null;
-        for (Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation> entry :
-                mNetworkRequestsCache.entrySet()) {
-            if (entry.getValue().ndpId == ndpId) {
-                networkSpecifier = entry.getKey();
-                nnri = entry.getValue();
-                break;
-            }
-        }
-
-        if (nnri == null) {
+        if (nnriE == null) {
             Log.w(TAG, "onRespondToDataPathRequest: can't find a request with specified ndpId="
                     + ndpId);
             if (VDBG) {
@@ -492,28 +518,37 @@
             return;
         }
 
+        WifiAwareNetworkSpecifier networkSpecifier = nnriE.getKey();
+        AwareNetworkRequestInformation nnri = nnriE.getValue();
+        NdpInfo ndpInfo = nnri.ndpInfos.get(ndpId);
         if (!success) {
             Log.w(TAG, "onRespondToDataPathRequest: request " + networkSpecifier
                     + " failed responding");
             mMgr.endDataPath(ndpId);
-            mNetworkRequestsCache.remove(networkSpecifier);
-            mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri);
+            nnri.ndpInfos.remove(ndpId);
+            if (nnri.specifiedPeerDiscoveryMac != null) {
+                mNetworkRequestsCache.remove(networkSpecifier);
+                mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri);
+            }
             mAwareMetrics.recordNdpStatus(reasonOnFailure, networkSpecifier.isOutOfBand(),
-                    nnri.startTimestamp);
+                    ndpInfo.startTimestamp);
             return;
         }
 
-        if (nnri.state
-                != AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_RESPOND_RESPONSE) {
+        if (ndpInfo.state != NdpInfo.STATE_RESPONDER_WAIT_FOR_RESPOND_RESPONSE
+                || nnri.state == AwareNetworkRequestInformation.STATE_TERMINATING) {
             Log.w(TAG, "onRespondToDataPathRequest: request " + networkSpecifier
                     + " is incorrect state=" + nnri.state);
             mMgr.endDataPath(ndpId);
-            mNetworkRequestsCache.remove(networkSpecifier);
-            mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri);
+            nnri.ndpInfos.remove(ndpId);
+            if (nnri.specifiedPeerDiscoveryMac != null) {
+                mNetworkRequestsCache.remove(networkSpecifier);
+                mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri);
+            }
             return;
         }
 
-        nnri.state = AwareNetworkRequestInformation.STATE_WAIT_FOR_CONFIRM;
+        ndpInfo.state = NdpInfo.STATE_WAIT_FOR_CONFIRM;
     }
 
     /**
@@ -532,9 +567,9 @@
      * @param message       The message provided by the peer as part of the data-path setup
      *                      process.
      * @param channelInfo   Lists of channels used for this NDP.
-     * @return The network specifier of the data-path or a null if none/error.
+     * @return False if has error, otherwise return true
      */
-    public WifiAwareNetworkSpecifier onDataPathConfirm(int ndpId, byte[] mac, boolean accept,
+    public boolean onDataPathConfirm(int ndpId, byte[] mac, boolean accept,
             int reason, byte[] message, List<NanDataPathChannelInfo> channelInfo) {
         if (mDbg) {
             Log.v(TAG, "onDataPathConfirm: ndpId=" + ndpId + ", mac=" + String.valueOf(
@@ -550,38 +585,48 @@
             if (accept) {
                 mMgr.endDataPath(ndpId);
             }
-            return null;
+            return false;
         }
 
         WifiAwareNetworkSpecifier networkSpecifier = nnriE.getKey();
         AwareNetworkRequestInformation nnri = nnriE.getValue();
+        NdpInfo ndpInfo = nnri.ndpInfos.get(ndpId);
 
         // validate state
-        if (nnri.state != AwareNetworkRequestInformation.STATE_WAIT_FOR_CONFIRM) {
+        if (ndpInfo.state != NdpInfo.STATE_WAIT_FOR_CONFIRM
+                || nnri.state == AwareNetworkRequestInformation.STATE_TERMINATING) {
             Log.w(TAG, "onDataPathConfirm: invalid state=" + nnri.state);
-            mNetworkRequestsCache.remove(networkSpecifier);
-            mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri);
+            nnri.ndpInfos.remove(ndpId);
+            if (nnri.specifiedPeerDiscoveryMac != null) {
+                mNetworkRequestsCache.remove(networkSpecifier);
+                mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri);
+            }
             if (accept) {
                 mMgr.endDataPath(ndpId);
             }
-            return networkSpecifier;
+            return false;
         }
 
         if (accept) {
+            ndpInfo.peerDataMac = mac;
+            ndpInfo.state = NdpInfo.STATE_CONFIRMED;
+            ndpInfo.channelInfo = channelInfo;
             nnri.state = AwareNetworkRequestInformation.STATE_CONFIRMED;
-            nnri.peerDataMac = mac;
-            nnri.channelInfo = channelInfo;
-
-            if (!isInterfaceUpAndUsedByAnotherNdp(nnri)) {
+            // NetworkAgent may already be created for accept any peer request, interface should be
+            // ready in that case.
+            if (nnri.networkAgent == null && !isInterfaceUpAndUsedByAnotherNdp(nnri)) {
                 try {
                     mNetdWrapper.setInterfaceUp(nnri.interfaceName);
                     mNetdWrapper.enableIpv6(nnri.interfaceName);
                 } catch (Exception e) { // NwService throws runtime exceptions for errors
                     Log.e(TAG, "onDataPathConfirm: ACCEPT nnri=" + nnri
-                            + ": can't configure network - "
-                            + e);
-                    declareUnfullfillableAndEndDp(nnri, ndpId);
-                    return networkSpecifier;
+                                + ": can't configure network - "
+                                + e);
+                    mMgr.endDataPath(ndpId);
+                    if (nnri.specifiedPeerDiscoveryMac != null) {
+                        declareUnfullfillable(nnri);
+                    }
+                    return true;
                 }
             } else {
                 if (VDBG) {
@@ -597,96 +642,102 @@
                         NetworkInformationData.parseTlv(message);
                 if (peerServerInfo != null) {
                     if (peerServerInfo.port != 0) {
-                        nnri.peerPort = peerServerInfo.port;
+                        ndpInfo.peerPort = peerServerInfo.port;
                     }
                     if (peerServerInfo.transportProtocol != -1) {
-                        nnri.peerTransportProtocol = peerServerInfo.transportProtocol;
+                        ndpInfo.peerTransportProtocol = peerServerInfo.transportProtocol;
                     }
                     if (peerServerInfo.ipv6Override != null) {
-                        nnri.peerIpv6Override = peerServerInfo.ipv6Override;
+                        ndpInfo.peerIpv6Override = peerServerInfo.ipv6Override;
                     }
                 }
             }
 
             nnri.startValidationTimestamp = mClock.getElapsedSinceBootMillis();
-            handleAddressValidation(nnri, ndpId, networkSpecifier.isOutOfBand(), mac);
+            handleAddressValidation(nnri, ndpInfo, networkSpecifier.isOutOfBand(), mac);
         } else {
             if (VDBG) {
                 Log.v(TAG, "onDataPathConfirm: data-path for networkSpecifier=" + networkSpecifier
                         + " rejected - reason=" + reason);
             }
-            mNetworkRequestsCache.remove(networkSpecifier);
-            mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri);
+            nnri.ndpInfos.remove(ndpId);
+            if (nnri.specifiedPeerDiscoveryMac != null) {
+                mNetworkRequestsCache.remove(networkSpecifier);
+                mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri);
+            }
             mAwareMetrics.recordNdpStatus(reason, networkSpecifier.isOutOfBand(),
-                    nnri.startTimestamp);
+                    ndpInfo.startTimestamp);
         }
-
-        return networkSpecifier;
+        return true;
     }
 
-    private void getInet6Address(AwareNetworkRequestInformation nnri, byte[] mac) {
+    private void getInet6Address(NdpInfo ndpInfo, byte[] mac, String interfaceName) {
         try {
             byte[] addr;
-            if (nnri.peerIpv6Override == null) {
+            if (ndpInfo.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];
+                addr[8] = ndpInfo.peerIpv6Override[0];
+                addr[9] = ndpInfo.peerIpv6Override[1];
+                addr[10] = ndpInfo.peerIpv6Override[2];
+                addr[11] = ndpInfo.peerIpv6Override[3];
+                addr[12] = ndpInfo.peerIpv6Override[4];
+                addr[13] = ndpInfo.peerIpv6Override[5];
+                addr[14] = ndpInfo.peerIpv6Override[6];
+                addr[15] = ndpInfo.peerIpv6Override[7];
             }
-            nnri.peerIpv6 = Inet6Address.getByAddress(null, addr,
-                    NetworkInterface.getByName(nnri.interfaceName));
+            ndpInfo.peerIpv6 = Inet6Address.getByAddress(null, addr,
+                    NetworkInterface.getByName(interfaceName));
         } catch (SocketException | UnknownHostException e) {
             if (mDbg) {
                 Log.d(TAG, "onDataPathConfirm: error obtaining scoped IPv6 address -- " + e);
             }
-            nnri.peerIpv6 = null;
+            ndpInfo.peerIpv6 = null;
         }
     }
 
-    private void handleAddressValidation(AwareNetworkRequestInformation nnri, int ndpId,
+    private void handleAddressValidation(AwareNetworkRequestInformation nnri, NdpInfo ndpInfo,
             boolean isOutOfBand, byte[] mac) {
         final NetworkCapabilities.Builder ncBuilder = new NetworkCapabilities.Builder(
                 sNetworkCapabilitiesFilter);
         LinkProperties linkProperties = new LinkProperties();
-        getInet6Address(nnri, mac);
-        if (!(nnri.peerIpv6 != null && mNiWrapper.configureAgentProperties(nnri,
-                nnri.equivalentRequests, ndpId, ncBuilder, linkProperties)
+        getInet6Address(ndpInfo, mac, nnri.interfaceName);
+        if (!(ndpInfo.peerIpv6 != null && mNiWrapper.configureAgentProperties(nnri,
+                ncBuilder, linkProperties)
                 && mNiWrapper.isAddressUsable(linkProperties))) {
             if (VDBG) {
                 Log.d(TAG, "Failed address validation");
             }
-            if (!isAddressValidationExpired(nnri, ndpId)) {
-                mHandler.postDelayed(() -> {
-                    handleAddressValidation(nnri, ndpId, isOutOfBand, mac);
-                }, ADDRESS_VALIDATION_RETRY_INTERVAL_MS);
+            if (!isAddressValidationExpired(nnri, ndpInfo.ndpId)) {
+                mHandler.postDelayed(() ->
+                        handleAddressValidation(nnri, ndpInfo, isOutOfBand, mac),
+                        ADDRESS_VALIDATION_RETRY_INTERVAL_MS);
             }
             return;
         }
-        final WifiAwareNetworkInfo ni = new WifiAwareNetworkInfo(
-                nnri.peerIpv6, nnri.peerPort, nnri.peerTransportProtocol);
-        ncBuilder.setTransportInfo(ni);
-        if (VDBG) {
-            Log.v(TAG, "onDataPathConfirm: AwareNetworkInfo=" + ni);
+
+        // Network agent may already setup finished. Update peer network info.
+        if (nnri.networkAgent == null) {
+            // Setup first NDP for new networkAgent.
+            final WifiAwareNetworkInfo ni = new WifiAwareNetworkInfo(ndpInfo.peerIpv6,
+                    ndpInfo.peerPort, ndpInfo.peerTransportProtocol);
+            ncBuilder.setTransportInfo(ni);
+            if (VDBG) {
+                Log.v(TAG, "onDataPathConfirm: AwareNetworkInfo=" + ni);
+            }
+            final NetworkAgentConfig naConfig = new NetworkAgentConfig.Builder()
+                    .setLegacyType(ConnectivityManager.TYPE_NONE)
+                    .setLegacyTypeName(NETWORK_TAG)
+                    .build();
+            nnri.networkAgent = new WifiAwareNetworkAgent(mLooper, mContext,
+                    AGENT_TAG_PREFIX + ndpInfo.ndpId, ncBuilder.build(), linkProperties,
+                    NETWORK_FACTORY_SCORE_AVAIL, naConfig, mNetworkFactory.getProvider(), nnri);
+            mNiWrapper.setConnected(nnri.networkAgent);
         }
-        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.recordNdpStatus(NanStatusType.SUCCESS, isOutOfBand, ndpInfo.startTimestamp);
         mAwareMetrics.recordNdpCreation(nnri.uid, nnri.packageName, mNetworkRequestsCache);
     }
 
@@ -695,16 +746,16 @@
                 > ADDRESS_VALIDATION_TIMEOUT_MS) {
             Log.e(TAG, "Timed-out while waiting for IPv6 address to be usable");
             mMgr.endDataPath(ndpId);
-            nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING;
-            declareUnfullfillableAndEndDp(nnri, ndpId);
+            if (nnri.specifiedPeerDiscoveryMac != null) {
+                declareUnfullfillable(nnri);
+            }
             return true;
         }
         return false;
     }
 
-    private void declareUnfullfillableAndEndDp(AwareNetworkRequestInformation nnri, int ndpId) {
+    private void declareUnfullfillable(AwareNetworkRequestInformation nnri) {
         mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri);
-        mMgr.endDataPath(ndpId);
         nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING;
     }
 
@@ -725,15 +776,22 @@
             }
             return;
         }
+        AwareNetworkRequestInformation nnri = nnriE.getValue();
+        NdpInfo ndpInfo = nnri.ndpInfos.get(ndpId);
+        nnri.ndpInfos.remove(ndpId);
 
-        tearDownInterfaceIfPossible(nnriE.getValue());
-        if (nnriE.getValue().state == AwareNetworkRequestInformation.STATE_CONFIRMED
-                || nnriE.getValue().state == AwareNetworkRequestInformation.STATE_TERMINATING) {
-            mAwareMetrics.recordNdpSessionDuration(nnriE.getValue().startTimestamp);
+        if (ndpInfo.state == NdpInfo.STATE_CONFIRMED) {
+            mAwareMetrics.recordNdpSessionDuration(ndpInfo.startTimestamp);
         }
-        mNetworkRequestsCache.remove(nnriE.getKey());
-
-        mNetworkFactory.tickleConnectivityIfWaiting();
+        if (nnri.specifiedPeerDiscoveryMac == null
+                && nnri.state != AwareNetworkRequestInformation.STATE_TERMINATING) {
+            return;
+        }
+        if (nnri.ndpInfos.size() == 0) {
+            tearDownInterfaceIfPossible(nnri);
+            mNetworkRequestsCache.remove(nnriE.getKey());
+            mNetworkFactory.tickleConnectivityIfWaiting();
+        }
     }
 
     /**
@@ -754,14 +812,15 @@
                 Log.e(TAG, "onDataPathSchedUpdate: ndpId=" + ndpId + " - not found");
                 continue;
             }
-            if (!Arrays.equals(peerMac, nnriE.getValue().peerDiscoveryMac)) {
+            NdpInfo ndpInfo = nnriE.getValue().ndpInfos.get(ndpId);
+            if (!Arrays.equals(peerMac, ndpInfo.peerDiscoveryMac)) {
                 Log.e(TAG, "onDataPathSchedUpdate: ndpId=" + ndpId + ", report NMI="
                         + MacAddress.fromBytes(peerMac).toString() + " doesn't match NDP NMI="
-                        + MacAddress.fromBytes(nnriE.getValue().peerDiscoveryMac).toString());
+                        + MacAddress.fromBytes(ndpInfo.peerDiscoveryMac).toString());
                 continue;
             }
 
-            nnriE.getValue().channelInfo = channelInfo;
+            ndpInfo.channelInfo = channelInfo;
         }
     }
 
@@ -774,7 +833,9 @@
         Iterator<Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation>> it =
                 mNetworkRequestsCache.entrySet().iterator();
         while (it.hasNext()) {
-            tearDownInterfaceIfPossible(it.next().getValue());
+            AwareNetworkRequestInformation nnri = it.next().getValue();
+            nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING;
+            tearDownInterfaceIfPossible(nnri);
             it.remove();
         }
     }
@@ -785,24 +846,31 @@
      * and on the responder when received a request for data-path (in both cases only on success
      * - i.e. when we're proceeding with data-path setup).
      */
-    public void handleDataPathTimeout(NetworkSpecifier networkSpecifier) {
-        if (mDbg) Log.v(TAG, "handleDataPathTimeout: networkSpecifier=" + networkSpecifier);
+    public void handleDataPathTimeout(int ndpId) {
+        if (mDbg) Log.v(TAG, "handleDataPathTimeout: networkSpecifier=" + ndpId);
 
-        AwareNetworkRequestInformation nnri = mNetworkRequestsCache.remove(networkSpecifier);
-        if (nnri == null) {
+
+        Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation> nnriE =
+                getNetworkRequestByNdpId(ndpId);
+        if (nnriE == null) {
             if (VDBG) {
                 Log.v(TAG,
                         "handleDataPathTimeout: network request not found for networkSpecifier="
-                                + networkSpecifier);
+                                + ndpId);
             }
             return;
         }
+        AwareNetworkRequestInformation nnri = nnriE.getValue();
+        NdpInfo ndpInfo = nnri.ndpInfos.get(ndpId);
         mAwareMetrics.recordNdpStatus(NanStatusType.INTERNAL_FAILURE,
-                nnri.networkSpecifier.isOutOfBand(), nnri.startTimestamp);
-        mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri);
-
-        mMgr.endDataPath(nnri.ndpId);
-        nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING;
+                nnri.networkSpecifier.isOutOfBand(), ndpInfo.startTimestamp);
+        mMgr.endDataPath(ndpId);
+        nnri.ndpInfos.remove(ndpId);
+        if (nnri.specifiedPeerDiscoveryMac != null) {
+            mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri);
+            mNetworkRequestsCache.remove(nnri.networkSpecifier);
+            nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING;
+        }
     }
 
     private class WifiAwareNetworkFactory extends NetworkFactory {
@@ -828,24 +896,26 @@
                 Log.v(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request);
             }
 
+            NetworkSpecifier networkSpecifierBase = request.getNetworkSpecifier();
+            if (!(networkSpecifierBase instanceof WifiAwareNetworkSpecifier)) {
+                Log.w(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request
+                        + " - not a WifiAwareNetworkSpecifier");
+                return false;
+            }
+
             if (!mMgr.isUsageEnabled()) {
                 if (VDBG) {
                     Log.v(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request
                             + " -- Aware disabled");
                 }
+                releaseRequestAsUnfulfillableByAnyFactory(request);
                 return false;
             }
 
             if (mInterfaces.isEmpty()) {
                 Log.w(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request
                         + " -- No Aware interfaces are up");
-                return false;
-            }
-
-            NetworkSpecifier networkSpecifierBase = request.getNetworkSpecifier();
-            if (!(networkSpecifierBase instanceof WifiAwareNetworkSpecifier)) {
-                Log.w(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request
-                        + " - not a WifiAwareNetworkSpecifier");
+                releaseRequestAsUnfulfillableByAnyFactory(request);
                 return false;
             }
 
@@ -900,6 +970,7 @@
             }
 
             mNetworkRequestsCache.put(networkSpecifier, nnri);
+            mAwareMetrics.recordNdpRequestType(networkSpecifier.type);
 
             return true;
         }
@@ -943,14 +1014,13 @@
                     return;
                 }
 
-                mMgr.initiateDataPathSetup(networkSpecifier, nnri.peerInstanceId,
+                mMgr.initiateDataPathSetup(networkSpecifier, nnri.specifiedPeerInstanceId,
                         NanDataPathChannelCfg.CHANNEL_NOT_REQUESTED, selectChannelForRequest(nnri),
-                        nnri.peerDiscoveryMac, nnri.interfaceName, nnri.networkSpecifier.pmk,
-                        nnri.networkSpecifier.passphrase, nnri.networkSpecifier.isOutOfBand(),
-                        null);
+                        nnri.specifiedPeerDiscoveryMac, nnri.interfaceName,
+                        nnri.networkSpecifier.pmk, nnri.networkSpecifier.passphrase,
+                        nnri.networkSpecifier.isOutOfBand(), null);
                 nnri.state =
                         AwareNetworkRequestInformation.STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE;
-                nnri.startTimestamp = mClock.getElapsedSinceBootMillis();
             } else {
                 nnri.state = AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_REQUEST;
             }
@@ -997,15 +1067,14 @@
                     Log.v(TAG, "releaseNetworkFor: there are no further requests, networkRequest="
                             + networkRequest);
                 }
-                if (nnri.ndpId != 0) { // 0 is never a valid ID!
+                if (nnri.ndpInfos.size() != 0) {
                     if (VDBG) Log.v(TAG, "releaseNetworkFor: in progress NDP being terminated");
-                    mMgr.endDataPath(nnri.ndpId);
+                    for (int index = 0; index < nnri.ndpInfos.size(); index++) {
+                        mMgr.endDataPath(nnri.ndpInfos.keyAt(index));
+                    }
                     nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING;
                 } else {
                     mNetworkRequestsCache.remove(networkSpecifier);
-                    if (nnri.networkAgent != null) {
-                        letAppKnowThatRequestsAreUnavailable(nnri);
-                    }
                 }
             } else {
                 if (VDBG) {
@@ -1027,7 +1096,9 @@
      */
     @VisibleForTesting
     public class WifiAwareNetworkAgent extends NetworkAgent {
-        private AwareNetworkRequestInformation mAwareNetworkRequestInfo;
+        private final AwareNetworkRequestInformation mAwareNetworkRequestInfo;
+        @VisibleForTesting
+        public final NetworkCapabilities mDataPathCapabilities;
 
         WifiAwareNetworkAgent(Looper looper, Context context, String logTag,
                 NetworkCapabilities nc, LinkProperties lp, int score,
@@ -1035,6 +1106,7 @@
                 AwareNetworkRequestInformation anri) {
             super(context, looper, logTag, nc, lp, score, config, provider);
             mAwareNetworkRequestInfo = anri;
+            mDataPathCapabilities = nc;
             register();
         }
 
@@ -1043,8 +1115,9 @@
             if (mDbg) {
                 Log.v(TAG, "WifiAwareNetworkAgent.unwanted: request=" + mAwareNetworkRequestInfo);
             }
-
-            mMgr.endDataPath(mAwareNetworkRequestInfo.ndpId);
+            for (int index = 0; index < mAwareNetworkRequestInfo.ndpInfos.size(); index++) {
+                mMgr.endDataPath(mAwareNetworkRequestInfo.ndpInfos.keyAt(index));
+            }
             mAwareNetworkRequestInfo.state = AwareNetworkRequestInformation.STATE_TERMINATING;
 
             // Will get a callback (on both initiator and responder) when data-path actually
@@ -1107,13 +1180,17 @@
      * Select one of the existing interfaces for the new network request. A request is canonical
      * (otherwise it wouldn't be executed).
      *
-     * Construct a list of all interfaces currently used to communicate to the peer. The remaining
-     * interfaces are available for use for this request - if none are left then the request should
-     * fail (signaled to the caller by returning a null).
+     * Criteria:
+     * 1. Select a network interface which is unused. This is because the network stack does not
+     * support multiple networks per interface.
+     * 2. If no network interface is available then (based on a device overlay) either fail (new
+     * behavior) or (preserve legacy behavior) pick an interface which isn't used for
+     * communication to the same peer.
      */
     private String selectInterfaceForRequest(AwareNetworkRequestInformation req) {
-        SortedSet<String> potential = new TreeSet<>(mInterfaces);
-        Set<String> used = new HashSet<>();
+        SortedSet<String> unused = new TreeSet<>(mInterfaces);
+        Set<String> invalid = new HashSet<>();
+        SortedSet<String> inuse = new TreeSet<>();
 
         if (mDbg) {
             Log.v(TAG, "selectInterfaceForRequest: req=" + req + ", mNetworkRequestsCache="
@@ -1121,22 +1198,38 @@
         }
 
         for (AwareNetworkRequestInformation nnri : mNetworkRequestsCache.values()) {
-            if (nnri == req) {
+            if (nnri == req || nnri.interfaceName == null) {
                 continue;
             }
 
-            if (Arrays.equals(req.peerDiscoveryMac, nnri.peerDiscoveryMac)) {
-                used.add(nnri.interfaceName);
+            if (Arrays.equals(req.specifiedPeerDiscoveryMac, nnri.specifiedPeerDiscoveryMac)) {
+                invalid.add(nnri.interfaceName);
+            } else {
+                inuse.add(nnri.interfaceName);
             }
+            unused.remove(nnri.interfaceName);
         }
 
         if (VDBG) {
-            Log.v(TAG, "selectInterfaceForRequest: potential=" + potential + ", used=" + used);
+            Log.v(TAG, "selectInterfaceForRequest: unUsed=" + unused + ", inuse=" + inuse
+                    + ", invalid" + invalid + ", allInterfaces" + mInterfaces);
         }
 
-        for (String ifName: potential) {
-            if (!used.contains(ifName)) {
-                return ifName;
+        // If any interface is unused, pick it first
+        if (!unused.isEmpty()) {
+            return unused.first();
+        }
+
+        // If device doesn't allow to make multiple network on same interface, return null.
+        if (!mContext.getResources()
+                .getBoolean(R.bool.config_wifiAllowMultipleNetworksOnSameAwareNdi)) {
+            Log.e(TAG, "selectInterfaceForRequest: req=" + req + " - no interfaces available!");
+            return null;
+        }
+
+        for (String iface : inuse) {
+            if (!invalid.contains(iface)) {
+                return iface;
             }
         }
 
@@ -1154,6 +1247,50 @@
         return 2437;
     }
 
+    private static class NdpInfo {
+        static final int STATE_WAIT_FOR_CONFIRM = 107;
+        static final int STATE_CONFIRMED = 108;
+        static final int STATE_RESPONDER_WAIT_FOR_RESPOND_RESPONSE = 109;
+
+        public int state;
+
+        public byte[] peerDiscoveryMac = null;
+        public int peerInstanceId = 0;
+        public int ndpId = 0; // 0 is never a valid ID!
+        public byte[] peerDataMac;
+        public Inet6Address peerIpv6;
+        public int peerPort = 0; // uninitialized (invalid) value
+        public int peerTransportProtocol = -1; // uninitialized (invalid) value
+        public byte[] peerIpv6Override = null;
+        public List<NanDataPathChannelInfo> channelInfo;
+        public long startTimestamp = 0; // request is made (initiator) / get request (responder)
+
+        NdpInfo(int ndpId) {
+            this.ndpId = ndpId;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append(", ndpInfo[");
+            sb.append("ndpId=").append(ndpId).append(", peerInstanceId=").append(
+                    peerInstanceId).append(", peerDiscoveryMac=").append(
+                    peerDiscoveryMac == null ? ""
+                            : String.valueOf(HexEncoding.encode(peerDiscoveryMac)))
+                    .append(", peerDataMac=").append(
+                    peerDataMac == null ? ""
+                            : String.valueOf(HexEncoding.encode(peerDataMac)))
+                    .append(", peerIpv6=").append(peerIpv6).append(
+                    ", peerPort=").append(
+                    peerPort).append(", peerTransportProtocol=").append(
+                    peerTransportProtocol).append(", startTimestamp=").append(
+                    startTimestamp).append(", channelInfo=").append(
+                    channelInfo);
+            sb.append("]");
+            return sb.toString();
+        }
+    }
+
     /**
      * Aware network request. State object: contains network request information/state through its
      * lifetime.
@@ -1161,12 +1298,11 @@
     @VisibleForTesting
     public static class AwareNetworkRequestInformation {
         static final int STATE_IDLE = 100;
-        static final int STATE_WAIT_FOR_CONFIRM = 101;
-        static final int STATE_CONFIRMED = 102;
-        static final int STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE = 103;
-        static final int STATE_RESPONDER_WAIT_FOR_REQUEST = 104;
-        static final int STATE_RESPONDER_WAIT_FOR_RESPOND_RESPONSE = 105;
-        static final int STATE_TERMINATING = 106;
+        static final int STATE_CONFIRMED = 101;
+        static final int STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE = 102;
+        static final int STATE_RESPONDER_WAIT_FOR_REQUEST = 103;
+        static final int STATE_TERMINATING = 104;
+        static final int STATE_IN_SETUP = 105;
 
         public int state;
 
@@ -1174,18 +1310,11 @@
         public String packageName;
         public String interfaceName;
         public int pubSubId = 0;
-        public int peerInstanceId = 0;
-        public byte[] peerDiscoveryMac = null;
-        public int ndpId = 0; // 0 is never a valid ID!
-        public byte[] peerDataMac;
-        public Inet6Address peerIpv6;
-        public int peerPort = 0; // uninitialized (invalid) value
-        public int peerTransportProtocol = -1; // uninitialized (invalid) value
-        public byte[] peerIpv6Override = null;
+        public int specifiedPeerInstanceId = 0;
+        public byte[] specifiedPeerDiscoveryMac = null;
         public WifiAwareNetworkSpecifier networkSpecifier;
-        public List<NanDataPathChannelInfo> channelInfo;
-        public long startTimestamp = 0; // request is made (initiator) / get request (responder)
         public long startValidationTimestamp = 0; // NDP created and starting to validate IPv6 addr
+        public SparseArray<NdpInfo> ndpInfos = new SparseArray();
 
         public WifiAwareNetworkAgent networkAgent;
 
@@ -1223,9 +1352,17 @@
                     equivalentRequests.stream()
                             .map(NetworkRequest::getNetworkSpecifier)
                             .toArray(WifiAwareNetworkSpecifier[]::new)));
-            if (peerIpv6 != null) {
+
+            if (ndpInfos.size() == 0) {
+                Log.wtf(TAG, "Number of NDPs is 0 when Network agent is created?! "
+                        + "AwareNetworkRequestInformation" + this);
+                return builder.setTransportInfo(new WifiAwareNetworkInfo()).build();
+            }
+            if (ndpInfos.valueAt(0).peerIpv6 != null) {
                 builder.setTransportInfo(
-                        new WifiAwareNetworkInfo(peerIpv6, peerPort, peerTransportProtocol));
+                        new WifiAwareNetworkInfo(ndpInfos.valueAt(0).peerIpv6,
+                                ndpInfos.valueAt(0).peerPort,
+                                ndpInfos.valueAt(0).peerTransportProtocol));
             }
             return builder.build();
         }
@@ -1234,7 +1371,7 @@
          * Returns a canonical descriptor for the network request.
          */
         CanonicalConnectionInfo getCanonicalDescriptor() {
-            return new CanonicalConnectionInfo(peerDiscoveryMac, networkSpecifier.pmk,
+            return new CanonicalConnectionInfo(specifiedPeerDiscoveryMac, networkSpecifier.pmk,
                     networkSpecifier.sessionId, networkSpecifier.passphrase);
         }
 
@@ -1286,12 +1423,11 @@
             uid = client.getUid();
             packageName = client.getCallingPackage();
 
-            // API change post 27: no longer allow "ANY"-style responders (initiators were never
-            // permitted).
-            // Note: checks are done on the manager. This is a backup for apps which bypass the
-            // check.
-            if (!allowNdpResponderFromAnyOverride && !wifiPermissionsUtil.isTargetSdkLessThan(
-                    client.getCallingPackage(), Build.VERSION_CODES.P, uid)) {
+            // API change post 30: allow accepts any peer responder.
+
+            if (!SdkLevel.isAtLeastS() && !allowNdpResponderFromAnyOverride
+                    && !wifiPermissionsUtil.isTargetSdkLessThan(
+                            client.getCallingPackage(), Build.VERSION_CODES.P, uid)) {
                 if (ns.type != WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB
                         && ns.type != WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB) {
                     Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns
@@ -1394,8 +1530,8 @@
             nnri.uid = uid;
             nnri.packageName = packageName;
             nnri.pubSubId = pubSubId;
-            nnri.peerInstanceId = peerInstanceId;
-            nnri.peerDiscoveryMac = peerMac;
+            nnri.specifiedPeerInstanceId = peerInstanceId;
+            nnri.specifiedPeerDiscoveryMac = peerMac;
             nnri.networkSpecifier = ns;
             nnri.equivalentRequests.add(request);
 
@@ -1409,22 +1545,21 @@
                     .append(", uid=").append(uid)
                     .append(", packageName=").append(packageName)
                     .append(", interfaceName=").append(interfaceName).append(
-                    ", pubSubId=").append(pubSubId).append(", peerInstanceId=").append(
-                    peerInstanceId).append(", peerDiscoveryMac=").append(
-                    peerDiscoveryMac == null ? ""
-                            : String.valueOf(HexEncoding.encode(peerDiscoveryMac))).append(
-                    ", ndpId=").append(ndpId).append(", peerDataMac=").append(
-                    peerDataMac == null ? ""
-                            : String.valueOf(HexEncoding.encode(peerDataMac)))
-                    .append(", peerIpv6=").append(peerIpv6).append(", peerPort=").append(
-                    peerPort).append(", peerTransportProtocol=").append(
-                    peerTransportProtocol).append(", startTimestamp=").append(
-                    startTimestamp).append(", channelInfo=").append(
-                    channelInfo).append(", equivalentSpecifiers=[");
+                    ", pubSubId=").append(pubSubId).append(", specifiedPeerInstanceId=").append(
+                    specifiedPeerInstanceId).append(", specifiedPeerDiscoveryMac=").append(
+                    specifiedPeerDiscoveryMac == null ? ""
+                            : String.valueOf(HexEncoding.encode(specifiedPeerDiscoveryMac)))
+                    .append(", equivalentSpecifiers=[");
             for (NetworkRequest nr : equivalentRequests) {
                 sb.append(nr.toString()).append(", ");
             }
-            return sb.append("]").toString();
+            sb.append("]");
+            sb.append(", NdpInfos[");
+            for (int index = 0; index < ndpInfos.size(); index++) {
+                sb.append(" ").append(index).append(": ").append(ndpInfos.valueAt(index));
+            }
+            sb.append("]");
+            return sb.toString();
         }
     }
 
@@ -1454,8 +1589,7 @@
         public final String passphrase;
 
         public boolean matches(CanonicalConnectionInfo other) {
-            return (other.peerDiscoveryMac == null || Arrays
-                    .equals(peerDiscoveryMac, other.peerDiscoveryMac))
+            return Arrays.equals(peerDiscoveryMac, other.peerDiscoveryMac)
                     && Arrays.equals(pmk, other.pmk)
                     && TextUtils.equals(passphrase, other.passphrase)
                     && (TextUtils.isEmpty(passphrase) || sessionId == other.sessionId);
@@ -1482,7 +1616,6 @@
          * name. Delegated to enable mocking.
          */
         public boolean configureAgentProperties(AwareNetworkRequestInformation nnri,
-                Set<NetworkRequest> networkRequests, int ndpId,
                 NetworkCapabilities.Builder ncBuilder, LinkProperties linkProperties) {
             // find link-local address
             InetAddress linkLocal = null;
@@ -1516,7 +1649,7 @@
             ncBuilder.setRequestorUid(nnri.uid);
             ncBuilder.setRequestorPackageName(nnri.packageName);
             ncBuilder.setNetworkSpecifier(new WifiAwareAgentNetworkSpecifier(
-                    networkRequests.stream()
+                    nnri.equivalentRequests.stream()
                             .map(NetworkRequest::getNetworkSpecifier)
                             .toArray(WifiAwareNetworkSpecifier[]::new)));
 
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareDiscoverySessionState.java b/service/java/com/android/server/wifi/aware/WifiAwareDiscoverySessionState.java
index bfd3e2c..ffe0c25 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareDiscoverySessionState.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareDiscoverySessionState.java
@@ -128,6 +128,12 @@
      * if currently active.
      */
     public void terminate() {
+        try {
+            mCallback.onSessionTerminated(NanStatusType.SUCCESS);
+        } catch (RemoteException e) {
+            Log.w(TAG,
+                    "onSessionTerminatedLocal onSessionTerminated(): RemoteException (FYI): " + e);
+        }
         mCallback = null;
 
         if (mIsPublishSession) {
@@ -279,6 +285,33 @@
     }
 
     /**
+     * Callback from HAL when a discovered peer is lost - i.e. when a discovered peer with a matched
+     * session is no longer visible.
+     *
+     * @param requestorInstanceId The ID used to identify the peer in this matched session.
+     */
+    public void onMatchExpired(int requestorInstanceId) {
+        int peerId = 0;
+        for (int i = 0; i < mPeerInfoByRequestorInstanceId.size(); ++i) {
+            PeerInfo peerInfo = mPeerInfoByRequestorInstanceId.valueAt(i);
+            if (peerInfo.mInstanceId == requestorInstanceId) {
+                peerId = mPeerInfoByRequestorInstanceId.keyAt(i);
+                mPeerInfoByRequestorInstanceId.delete(peerId);
+                break;
+            }
+        }
+        if (peerId == 0) {
+            return;
+        }
+
+        try {
+            mCallback.onMatchExpired(peerId);
+        } catch (RemoteException e) {
+            Log.w(TAG, "onMatch: RemoteException (FYI): " + e);
+        }
+    }
+
+    /**
      * Callback from HAL when a message is received from a peer in a discovery
      * session. Propagated to client if registered.
      *
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareMetrics.java b/service/java/com/android/server/wifi/aware/WifiAwareMetrics.java
index e9043f8..5cbf3a6 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareMetrics.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareMetrics.java
@@ -16,6 +16,11 @@
 
 package com.android.server.wifi.aware;
 
+import static android.net.wifi.aware.WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB;
+import static android.net.wifi.aware.WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB_ANY_PEER;
+import static android.net.wifi.aware.WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB;
+import static android.net.wifi.aware.WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB_ANY_PEER;
+
 import android.hardware.wifi.V1_0.NanStatusType;
 import android.net.wifi.aware.WifiAwareNetworkSpecifier;
 import android.text.TextUtils;
@@ -130,6 +135,7 @@
     private long mNdpCreationTimeNumSamples = 0;
 
     private SparseIntArray mHistogramNdpDuration = new SparseIntArray();
+    private SparseIntArray mHistogramNdpRequestType = new SparseIntArray();
 
     public WifiAwareMetrics(Clock clock) {
         mClock = clock;
@@ -583,6 +589,7 @@
             log.histogramNdpSessionDurationMs = histogramToProtoArray(
                     MetricsUtils.logHistogramToGenericBuckets(mHistogramNdpDuration,
                             DURATION_LOG_HISTOGRAM));
+            log.histogramNdpRequestType = histogramToNanRequestProtoArray(mHistogramNdpRequestType);
         }
         return log;
     }
@@ -651,6 +658,7 @@
             mNdpCreationTimeNumSamples = 0;
 
             mHistogramNdpDuration.clear();
+            mHistogramNdpRequestType.clear();
         }
     }
 
@@ -786,6 +794,11 @@
                 pw.println("  " + mHistogramNdpDuration.keyAt(i) + ": "
                         + mHistogramNdpDuration.valueAt(i));
             }
+            pw.println("mNdpRequestType:");
+            for (int i = 0; i < mHistogramNdpRequestType.size(); ++i) {
+                pw.println("  " + mHistogramNdpRequestType.keyAt(i) + ": "
+                        + mHistogramNdpRequestType.valueAt(i));
+            }
         }
     }
 
@@ -872,4 +885,47 @@
                 return WifiMetricsProto.WifiAwareLog.UNKNOWN_HAL_STATUS;
         }
     }
+
+    /**
+     * Record NDP request type
+     */
+    public void recordNdpRequestType(int type) {
+        int protoType = convertNdpRequestTypeToProtoEnum(type);
+        mHistogramNdpRequestType.put(protoType, mHistogramNdpRequestType.get(protoType) + 1);
+    }
+
+    private int convertNdpRequestTypeToProtoEnum(int ndpRequestType) {
+        switch (ndpRequestType) {
+            case NETWORK_SPECIFIER_TYPE_IB:
+                return WifiMetricsProto.WifiAwareLog.NETWORK_SPECIFIER_TYPE_IB;
+            case NETWORK_SPECIFIER_TYPE_IB_ANY_PEER:
+                return WifiMetricsProto.WifiAwareLog.NETWORK_SPECIFIER_TYPE_IB_ANY_PEER;
+            case NETWORK_SPECIFIER_TYPE_OOB:
+                return WifiMetricsProto.WifiAwareLog.NETWORK_SPECIFIER_TYPE_OOB;
+            case NETWORK_SPECIFIER_TYPE_OOB_ANY_PEER:
+                return WifiMetricsProto.WifiAwareLog.NETWORK_SPECIFIER_TYPE_OOB_ANY_PEER;
+            default:
+                Log.e(TAG, "Unrecognized NdpRequestType: " + ndpRequestType);
+                return WifiMetricsProto.WifiAwareLog.NETWORK_SPECIFIER_TYPE_UNKNOWN;
+        }
+    }
+
+
+    /**
+     * Converts a histogram of proto NdpRequestTypeEnum to a raw proto histogram.
+     */
+    @VisibleForTesting
+    public static WifiMetricsProto.WifiAwareLog.NdpRequestTypeHistogramBucket[]
+            histogramToNanRequestProtoArray(SparseIntArray histogram) {
+        WifiMetricsProto.WifiAwareLog.NdpRequestTypeHistogramBucket[] protoArray =
+                new WifiMetricsProto.WifiAwareLog.NdpRequestTypeHistogramBucket[histogram.size()];
+
+        for (int i = 0; i < histogram.size(); ++i) {
+            protoArray[i] = new WifiMetricsProto.WifiAwareLog.NdpRequestTypeHistogramBucket();
+            protoArray[i].ndpRequestType = histogram.keyAt(i);
+            protoArray[i].count = histogram.valueAt(i);
+        }
+
+        return protoArray;
+    }
 }
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareNativeApi.java b/service/java/com/android/server/wifi/aware/WifiAwareNativeApi.java
index a85fa27..e132e45 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareNativeApi.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareNativeApi.java
@@ -33,7 +33,6 @@
 import android.hardware.wifi.V1_0.NanTxType;
 import android.hardware.wifi.V1_0.WifiStatus;
 import android.hardware.wifi.V1_0.WifiStatusCode;
-import android.hardware.wifi.V1_2.NanConfigRequestSupplemental;
 import android.net.wifi.aware.ConfigRequest;
 import android.net.wifi.aware.PublishConfig;
 import android.net.wifi.aware.SubscribeConfig;
@@ -89,7 +88,7 @@
         if (!VDBG) return;
 
         if (transactionId == 0) {
-            return; // tid == 0 is used as a dummy transaction ID in several commands - acceptable
+            return; // tid == 0 is used as a placeholder transaction ID in several commands
         }
 
         int count = mTransactionIds.get(transactionId);
@@ -117,6 +116,15 @@
         return android.hardware.wifi.V1_4.IWifiNanIface.castFrom(iface);
     }
 
+    /**
+     * (HIDL) Cast the input to a 1.5 NAN interface (possibly resulting in a null).
+     *
+     * Separate function so can be mocked in unit tests.
+     */
+    public android.hardware.wifi.V1_5.IWifiNanIface mockableCastTo_1_5(IWifiNanIface iface) {
+        return android.hardware.wifi.V1_5.IWifiNanIface.castFrom(iface);
+    }
+
     /*
      * Parameters settable through the shell command.
      * see wifi/1.0/types.hal NanBandSpecificConfig.discoveryWindowIntervalVal and
@@ -329,9 +337,16 @@
             Log.e(TAG, "getCapabilities: null interface");
             return false;
         }
+        android.hardware.wifi.V1_5.IWifiNanIface iface15 = mockableCastTo_1_5(iface);
+
 
         try {
-            WifiStatus status = iface.getCapabilitiesRequest(transactionId);
+            WifiStatus status;
+            if (iface15 == null) {
+                status = iface.getCapabilitiesRequest(transactionId);
+            }  else {
+                status = iface15.getCapabilitiesRequest_1_5(transactionId);
+            }
             if (status.code == WifiStatusCode.SUCCESS) {
                 return true;
             } else {
@@ -346,25 +361,28 @@
 
     /**
      * Enable and configure Aware.
-     *
      * @param transactionId Transaction ID for the transaction - used in the
      *            async callback to match with the original request.
      * @param configRequest Requested Aware configuration.
      * @param notifyIdentityChange Indicates whether or not to get address change callbacks.
      * @param initialConfiguration Specifies whether initial configuration
-     *            (true) or an update (false) to the configuration.
+*            (true) or an update (false) to the configuration.
      * @param isInteractive PowerManager.isInteractive
      * @param isIdle PowerManager.isIdle
      * @param rangingEnabled Indicates whether or not enable ranging.
+     * @param isInstantCommunicationEnabled Indicates whether or not enable instant communication
+     *                                      mode.
      */
     public boolean enableAndConfigure(short transactionId, ConfigRequest configRequest,
             boolean notifyIdentityChange, boolean initialConfiguration, boolean isInteractive,
-            boolean isIdle, boolean rangingEnabled) {
+            boolean isIdle, boolean rangingEnabled, boolean isInstantCommunicationEnabled) {
         if (mDbg) {
             Log.v(TAG, "enableAndConfigure: transactionId=" + transactionId + ", configRequest="
                     + configRequest + ", notifyIdentityChange=" + notifyIdentityChange
                     + ", initialConfiguration=" + initialConfiguration
-                    + ", isInteractive=" + isInteractive + ", isIdle=" + isIdle);
+                    + ", isInteractive=" + isInteractive + ", isIdle=" + isIdle
+                    + ", isRangingEnabled=" + rangingEnabled
+                    + ", isInstantCommunicationEnabled=" + isInstantCommunicationEnabled);
         }
         recordTransactionId(transactionId);
 
@@ -375,7 +393,11 @@
         }
         android.hardware.wifi.V1_2.IWifiNanIface iface12 = mockableCastTo_1_2(iface);
         android.hardware.wifi.V1_4.IWifiNanIface iface14 = mockableCastTo_1_4(iface);
-        NanConfigRequestSupplemental configSupplemental12 = new NanConfigRequestSupplemental();
+        android.hardware.wifi.V1_5.IWifiNanIface iface15 = mockableCastTo_1_5(iface);
+        android.hardware.wifi.V1_2.NanConfigRequestSupplemental configSupplemental12 =
+                new android.hardware.wifi.V1_2.NanConfigRequestSupplemental();
+        android.hardware.wifi.V1_5.NanConfigRequestSupplemental configSupplemental15 =
+                new android.hardware.wifi.V1_5.NanConfigRequestSupplemental();
         if (iface12 != null || iface14 != null) {
             configSupplemental12.discoveryBeaconIntervalMs = 0;
             configSupplemental12.numberOfSpatialStreamsInDiscovery = 0;
@@ -383,6 +405,11 @@
             configSupplemental12.enableRanging = rangingEnabled;
         }
 
+        if (iface15 != null) {
+            configSupplemental15.V1_2 = configSupplemental12;
+            configSupplemental15.enableInstantCommunicationMode = isInstantCommunicationEnabled;
+        }
+
         NanBandSpecificConfig config24 = new NanBandSpecificConfig();
         config24.rssiClose = 60;
         config24.rssiMiddle = 70;
@@ -436,7 +463,7 @@
         try {
             WifiStatus status;
             if (initialConfiguration) {
-                if (iface14 != null) {
+                if (iface14 != null || iface15 != null) {
                     // translate framework to HIDL configuration (V_1.4)
                     android.hardware.wifi.V1_4.NanEnableRequest req =
                             new android.hardware.wifi.V1_4.NanEnableRequest();
@@ -489,11 +516,16 @@
                     req.debugConfigs.useSdfInBandVal[NanBandIndex.NAN_BAND_5GHZ] = true;
                     req.debugConfigs.useSdfInBandVal[
                             android.hardware.wifi.V1_4.NanBandIndex.NAN_BAND_6GHZ] = true;
-
                     updateConfigForPowerSettings14(req.configParams, configSupplemental12,
                             isInteractive, isIdle);
 
-                    status = iface14.enableRequest_1_4(transactionId, req, configSupplemental12);
+                    if (iface15 != null) {
+                        status = iface15.enableRequest_1_5(transactionId, req,
+                                configSupplemental15);
+                    } else {
+                        status = iface14.enableRequest_1_4(transactionId, req,
+                                configSupplemental12);
+                    }
                 } else {
                     // translate framework to HIDL configuration (before V_1.4)
                     NanEnableRequest req = new NanEnableRequest();
@@ -548,7 +580,7 @@
                     }
                 }
             } else {
-                if (iface14 != null) {
+                if (iface14 != null || iface15 != null) {
                     android.hardware.wifi.V1_4.NanConfigRequest req =
                             new android.hardware.wifi.V1_4.NanConfigRequest();
                     req.masterPref = (byte) configRequest.mMasterPreference;
@@ -568,10 +600,15 @@
                     req.bandSpecificConfig[android.hardware.wifi.V1_4.NanBandIndex.NAN_BAND_6GHZ] =
                             config6;
 
-                    updateConfigForPowerSettings14(req, configSupplemental12, isInteractive,
-                            isIdle);
-
-                    status = iface14.configRequest_1_4(transactionId, req, configSupplemental12);
+                    updateConfigForPowerSettings14(req, configSupplemental12,
+                            isInteractive, isIdle);
+                    if (iface15 != null) {
+                        status = iface15.configRequest_1_5(transactionId, req,
+                                configSupplemental15);
+                    } else {
+                        status = iface14.configRequest_1_4(transactionId, req,
+                                configSupplemental12);
+                    }
                 } else {
                     NanConfigRequest req = new NanConfigRequest();
                     req.masterPref = (byte) configRequest.mMasterPreference;
@@ -629,6 +666,7 @@
 
         try {
             WifiStatus status = iface.disableRequest(transactionId);
+            mHal.releaseAware();
             if (status.code == WifiStatusCode.SUCCESS) {
                 return true;
             } else {
@@ -744,7 +782,7 @@
         req.baseConfigs.useRssiThreshold = false;
         req.baseConfigs.disableDiscoveryTerminationIndication =
                 !subscribeConfig.mEnableTerminateNotification;
-        req.baseConfigs.disableMatchExpirationIndication = true;
+        req.baseConfigs.disableMatchExpirationIndication = false;
         req.baseConfigs.disableFollowupReceivedIndication = false;
 
         req.baseConfigs.rangingRequired =
@@ -1178,8 +1216,8 @@
      * Update the NAN configuration to reflect the current power settings (before V1.4)
      */
     private void updateConfigForPowerSettings(NanConfigRequest req,
-            NanConfigRequestSupplemental configSupplemental12, boolean isInteractive,
-            boolean isIdle) {
+            android.hardware.wifi.V1_2.NanConfigRequestSupplemental configSupplemental12,
+            boolean isInteractive, boolean isIdle) {
         String key = POWER_PARAM_DEFAULT_KEY;
         if (isIdle) {
             key = POWER_PARAM_IDLE_KEY;
@@ -1204,8 +1242,8 @@
      * Update the NAN configuration to reflect the current power settings (V1.4)
      */
     private void updateConfigForPowerSettings14(android.hardware.wifi.V1_4.NanConfigRequest req,
-            NanConfigRequestSupplemental configSupplemental12, boolean isInteractive,
-            boolean isIdle) {
+            android.hardware.wifi.V1_2.NanConfigRequestSupplemental configSupplemental12,
+            boolean isInteractive, boolean isIdle) {
         String key = POWER_PARAM_DEFAULT_KEY;
         if (isIdle) {
             key = POWER_PARAM_IDLE_KEY;
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareNativeCallback.java b/service/java/com/android/server/wifi/aware/WifiAwareNativeCallback.java
index 733bb04..1a13e0b 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareNativeCallback.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareNativeCallback.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wifi.aware;
 
-import android.hardware.wifi.V1_0.NanCapabilities;
 import android.hardware.wifi.V1_0.NanClusterEventInd;
 import android.hardware.wifi.V1_0.NanClusterEventType;
 import android.hardware.wifi.V1_0.NanDataPathConfirmInd;
@@ -25,11 +24,12 @@
 import android.hardware.wifi.V1_0.NanMatchInd;
 import android.hardware.wifi.V1_0.NanStatusType;
 import android.hardware.wifi.V1_0.WifiNanStatus;
-import android.hardware.wifi.V1_2.IWifiNanIfaceEventCallback;
 import android.hardware.wifi.V1_2.NanDataPathChannelInfo;
 import android.hardware.wifi.V1_2.NanDataPathScheduleUpdateInd;
+import android.hardware.wifi.V1_5.IWifiNanIfaceEventCallback;
 import android.net.MacAddress;
 import android.net.wifi.util.HexEncoding;
+import android.os.RemoteException;
 import android.util.Log;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
@@ -54,6 +54,7 @@
     private boolean mDbg = false;
 
     /* package */ boolean mIsHal12OrLater = false;
+    /* package */ boolean mIsHal15OrLater = false;
 
     private final WifiAwareStateManager mWifiAwareStateManager;
 
@@ -165,32 +166,18 @@
 
     @Override
     public void notifyCapabilitiesResponse(short id, WifiNanStatus status,
-            NanCapabilities capabilities) {
+            android.hardware.wifi.V1_0.NanCapabilities capabilities) {
         if (mDbg) {
             Log.v(TAG, "notifyCapabilitiesResponse: id=" + id + ", status=" + statusString(status)
                     + ", capabilities=" + capabilities);
         }
 
+        if (mIsHal15OrLater) {
+            Log.wtf(TAG, "notifyCapabilitiesResponse should not be called by a >=1.5 HAL!");
+        }
+
         if (status.status == NanStatusType.SUCCESS) {
-            Capabilities frameworkCapabilities = new Capabilities();
-            frameworkCapabilities.maxConcurrentAwareClusters = capabilities.maxConcurrentClusters;
-            frameworkCapabilities.maxPublishes = capabilities.maxPublishes;
-            frameworkCapabilities.maxSubscribes = capabilities.maxSubscribes;
-            frameworkCapabilities.maxServiceNameLen = capabilities.maxServiceNameLen;
-            frameworkCapabilities.maxMatchFilterLen = capabilities.maxMatchFilterLen;
-            frameworkCapabilities.maxTotalMatchFilterLen = capabilities.maxTotalMatchFilterLen;
-            frameworkCapabilities.maxServiceSpecificInfoLen =
-                    capabilities.maxServiceSpecificInfoLen;
-            frameworkCapabilities.maxExtendedServiceSpecificInfoLen =
-                    capabilities.maxExtendedServiceSpecificInfoLen;
-            frameworkCapabilities.maxNdiInterfaces = capabilities.maxNdiInterfaces;
-            frameworkCapabilities.maxNdpSessions = capabilities.maxNdpSessions;
-            frameworkCapabilities.maxAppInfoLen = capabilities.maxAppInfoLen;
-            frameworkCapabilities.maxQueuedTransmitMessages =
-                    capabilities.maxQueuedTransmitFollowupMsgs;
-            frameworkCapabilities.maxSubscribeInterfaceAddresses =
-                    capabilities.maxSubscribeInterfaceAddresses;
-            frameworkCapabilities.supportedCipherSuites = capabilities.supportedCipherSuites;
+            Capabilities frameworkCapabilities = toFrameworkCapability10(capabilities);
 
             mWifiAwareStateManager.onCapabilitiesUpdateResponse(id, frameworkCapabilities);
         } else {
@@ -200,6 +187,57 @@
     }
 
     @Override
+    public void notifyCapabilitiesResponse_1_5(short id, WifiNanStatus status,
+            android.hardware.wifi.V1_5.NanCapabilities capabilities) throws RemoteException {
+        if (mDbg) {
+            Log.v(TAG, "notifyCapabilitiesResponse_1_5: id=" + id + ", status="
+                    + statusString(status) + ", capabilities=" + capabilities);
+        }
+
+        if (!mIsHal15OrLater) {
+            Log.wtf(TAG, "notifyCapabilitiesResponse_1_5 should not be called by a <1.5 HAL!");
+            return;
+        }
+
+        if (status.status == NanStatusType.SUCCESS) {
+            Capabilities frameworkCapabilities = toFrameworkCapability10(capabilities.V1_0);
+            frameworkCapabilities.isInstantCommunicationModeSupported =
+                    capabilities.instantCommunicationModeSupportFlag;
+
+            mWifiAwareStateManager.onCapabilitiesUpdateResponse(id, frameworkCapabilities);
+        } else {
+            Log.e(TAG, "notifyCapabilitiesResponse_1_5: error code=" + status.status + " ("
+                    + status.description + ")");
+        }
+
+    }
+
+    private Capabilities toFrameworkCapability10(
+            android.hardware.wifi.V1_0.NanCapabilities capabilities) {
+        Capabilities frameworkCapabilities = new Capabilities();
+        frameworkCapabilities.maxConcurrentAwareClusters = capabilities.maxConcurrentClusters;
+        frameworkCapabilities.maxPublishes = capabilities.maxPublishes;
+        frameworkCapabilities.maxSubscribes = capabilities.maxSubscribes;
+        frameworkCapabilities.maxServiceNameLen = capabilities.maxServiceNameLen;
+        frameworkCapabilities.maxMatchFilterLen = capabilities.maxMatchFilterLen;
+        frameworkCapabilities.maxTotalMatchFilterLen = capabilities.maxTotalMatchFilterLen;
+        frameworkCapabilities.maxServiceSpecificInfoLen =
+                capabilities.maxServiceSpecificInfoLen;
+        frameworkCapabilities.maxExtendedServiceSpecificInfoLen =
+                capabilities.maxExtendedServiceSpecificInfoLen;
+        frameworkCapabilities.maxNdiInterfaces = capabilities.maxNdiInterfaces;
+        frameworkCapabilities.maxNdpSessions = capabilities.maxNdpSessions;
+        frameworkCapabilities.maxAppInfoLen = capabilities.maxAppInfoLen;
+        frameworkCapabilities.maxQueuedTransmitMessages =
+                capabilities.maxQueuedTransmitFollowupMsgs;
+        frameworkCapabilities.maxSubscribeInterfaceAddresses =
+                capabilities.maxSubscribeInterfaceAddresses;
+        frameworkCapabilities.supportedCipherSuites = capabilities.supportedCipherSuites;
+        frameworkCapabilities.isInstantCommunicationModeSupported = false;
+        return frameworkCapabilities;
+    }
+
+    @Override
     public void notifyEnableResponse(short id, WifiNanStatus status) {
         if (mDbg) Log.v(TAG, "notifyEnableResponse: id=" + id + ", status=" + statusString(status));
 
@@ -450,8 +488,7 @@
                     + ", peerId=" + peerId);
         }
         incrementCbCount(CB_EV_MATCH_EXPIRED);
-
-        // NOP
+        mWifiAwareStateManager.onMatchExpiredNotification(discoverySessionId, peerId);
     }
 
     @Override
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareNativeManager.java b/service/java/com/android/server/wifi/aware/WifiAwareNativeManager.java
index ec6c0ad..ca68964 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareNativeManager.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareNativeManager.java
@@ -18,11 +18,11 @@
 
 import android.annotation.NonNull;
 import android.hardware.wifi.V1_0.IWifiNanIface;
-import android.hardware.wifi.V1_0.IfaceType;
 import android.hardware.wifi.V1_0.WifiStatus;
 import android.hardware.wifi.V1_0.WifiStatusCode;
 import android.os.Handler;
 import android.os.RemoteException;
+import android.os.WorkSource;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -47,10 +47,7 @@
     private WifiAwareNativeCallback mWifiAwareNativeCallback;
     private IWifiNanIface mWifiNanIface = null;
     private InterfaceDestroyedListener mInterfaceDestroyedListener;
-    private InterfaceAvailableForRequestListener mInterfaceAvailableForRequestListener =
-            new InterfaceAvailableForRequestListener();
     private int mReferenceCount = 0;
-    private volatile boolean mAwareNativeAvailable = false;
 
     WifiAwareNativeManager(WifiAwareStateManager awareStateManager,
             HalDeviceManager halDeviceManager,
@@ -77,6 +74,15 @@
     }
 
     /**
+     * (HIDL) Cast the input to a 1.5 NAN interface (possibly resulting in a null).
+     *
+     * Separate function so can be mocked in unit tests.
+     */
+    public android.hardware.wifi.V1_5.IWifiNanIface mockableCastTo_1_5(IWifiNanIface iface) {
+        return android.hardware.wifi.V1_5.IWifiNanIface.castFrom(iface);
+    }
+
+    /**
      * Initialize the class - intended for late initialization.
      *
      * @param handler Handler on which to execute interface available callbacks.
@@ -92,29 +98,18 @@
                         // only care about isStarted (Wi-Fi started) not isReady - since if not
                         // ready then Wi-Fi will also be down.
                         if (mHalDeviceManager.isStarted()) {
-                            // 1. no problem registering duplicates - only one will be called
-                            // 2. will be called immediately if available
-                            mHalDeviceManager.registerInterfaceAvailableForRequestListener(
-                                    IfaceType.NAN, mInterfaceAvailableForRequestListener, mHandler);
+                            mWifiAwareStateManager.tryToGetAwareCapability();
                         } else {
                             awareIsDown();
                         }
                     }
                 }, mHandler);
         if (mHalDeviceManager.isStarted()) {
-            mHalDeviceManager.registerInterfaceAvailableForRequestListener(
-                    IfaceType.NAN, mInterfaceAvailableForRequestListener, mHandler);
+            mWifiAwareStateManager.tryToGetAwareCapability();
         }
     }
 
     /**
-     * Return the Availability of WifiAware native HAL
-     */
-    public boolean isAwareNativeAvailable() {
-        return mAwareNativeAvailable;
-    }
-
-    /**
      * Returns the native HAL WifiNanIface through which commands to the NAN HAL are dispatched.
      * Return may be null if not initialized/available.
      */
@@ -128,11 +123,11 @@
     /**
      * Attempt to obtain the HAL NAN interface.
      */
-    public void tryToGetAware() {
+    public void tryToGetAware(@NonNull WorkSource requestorWs) {
         synchronized (mLock) {
             if (mDbg) {
                 Log.d(TAG, "tryToGetAware: mWifiNanIface=" + mWifiNanIface + ", mReferenceCount="
-                        + mReferenceCount);
+                        + mReferenceCount + ", requestorWs=" + requestorWs);
             }
 
             if (mWifiNanIface != null) {
@@ -147,7 +142,7 @@
 
             mInterfaceDestroyedListener = new InterfaceDestroyedListener();
             IWifiNanIface iface = mHalDeviceManager.createNanIface(mInterfaceDestroyedListener,
-                    mHandler);
+                    mHandler, requestorWs);
             if (iface == null) {
                 Log.e(TAG, "Was not able to obtain an IWifiNanIface (even though enabled!?)");
                 awareIsDown();
@@ -156,13 +151,17 @@
 
                 try {
                     android.hardware.wifi.V1_2.IWifiNanIface iface12 = mockableCastTo_1_2(iface);
+                    android.hardware.wifi.V1_5.IWifiNanIface iface15 = mockableCastTo_1_5(iface);
                     WifiStatus status;
-                    if (iface12 == null) {
-                        mWifiAwareNativeCallback.mIsHal12OrLater = false;
-                        status = iface.registerEventCallback(mWifiAwareNativeCallback);
-                    } else {
+                    if (iface15 != null) {
+                        mWifiAwareNativeCallback.mIsHal12OrLater = true;
+                        mWifiAwareNativeCallback.mIsHal15OrLater = true;
+                        status = iface15.registerEventCallback_1_5(mWifiAwareNativeCallback);
+                    } else if (iface12 != null) {
                         mWifiAwareNativeCallback.mIsHal12OrLater = true;
                         status = iface12.registerEventCallback_1_2(mWifiAwareNativeCallback);
+                    } else {
+                        status = iface.registerEventCallback(mWifiAwareNativeCallback);
                     }
                     if (status.code != WifiStatusCode.SUCCESS) {
                         Log.e(TAG, "IWifiNanIface.registerEventCallback error: " + statusString(
@@ -212,6 +211,29 @@
         }
     }
 
+    /**
+     * Replace requestorWs in-place when iface is already enabled.
+     */
+    public boolean replaceRequestorWs(@NonNull WorkSource requestorWs) {
+        synchronized (mLock) {
+            if (mDbg) {
+                Log.d(TAG, "replaceRequestorWs: mWifiNanIface=" + mWifiNanIface
+                        + ", mReferenceCount=" + mReferenceCount + ", requestorWs=" + requestorWs);
+            }
+
+            if (mWifiNanIface == null) {
+                return false;
+            }
+            if (mHalDeviceManager == null) {
+                Log.e(TAG, "tryToGetAware: mHalDeviceManager is null!?");
+                awareIsDown();
+                return false;
+            }
+
+            return mHalDeviceManager.replaceRequestorWs(mWifiNanIface, requestorWs);
+        }
+    }
+
     private void awareIsDown() {
         synchronized (mLock) {
             if (mDbg) {
@@ -220,8 +242,7 @@
             }
             mWifiNanIface = null;
             mReferenceCount = 0;
-            mAwareNativeAvailable = false;
-            mWifiAwareStateManager.disableUsage();
+            mWifiAwareStateManager.disableUsage(true);
         }
     }
 
@@ -241,26 +262,6 @@
         }
     }
 
-    private class InterfaceAvailableForRequestListener implements
-            HalDeviceManager.InterfaceAvailableForRequestListener {
-        @Override
-        public void onAvailabilityChanged(boolean isAvailable) {
-            if (mDbg) {
-                Log.d(TAG, "Interface availability = " + isAvailable + ", mWifiNanIface="
-                        + mWifiNanIface);
-            }
-            synchronized (mLock) {
-                if (isAvailable) {
-                    mAwareNativeAvailable = true;
-                    mWifiAwareStateManager.enableUsage();
-                } else if (mWifiNanIface == null) { // not available could mean already have NAN
-                    mAwareNativeAvailable = false;
-                    mWifiAwareStateManager.disableUsage();
-                }
-            }
-        }
-    }
-
     private static String statusString(WifiStatus status) {
         if (status == null) {
             return "status=null";
@@ -278,6 +279,5 @@
         pw.println("  mWifiNanIface: " + mWifiNanIface);
         pw.println("  mReferenceCount: " + mReferenceCount);
         mWifiAwareNativeCallback.dump(fd, pw, args);
-        mHalDeviceManager.dump(fd, pw, args);
     }
 }
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareServiceImpl.java b/service/java/com/android/server/wifi/aware/WifiAwareServiceImpl.java
index 3e33cc1..48fbee6 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareServiceImpl.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareServiceImpl.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.hardware.wifi.V1_0.NanStatusType;
+import android.net.wifi.aware.AwareResources;
 import android.net.wifi.aware.Characteristics;
 import android.net.wifi.aware.ConfigRequest;
 import android.net.wifi.aware.DiscoverySession;
@@ -160,6 +161,35 @@
     }
 
     @Override
+    public AwareResources getAvailableAwareResources() {
+        enforceAccessPermission();
+        return mStateManager.getAvailableAwareResources();
+    }
+
+    @Override
+    public boolean isDeviceAttached() {
+        enforceAccessPermission();
+        return mDeathRecipientsByClientId.size() != 0;
+    }
+
+    @Override
+    public void enableInstantCommunicationMode(String callingPackage, boolean enable) {
+        if (!mWifiPermissionsUtil.isSystem(callingPackage, Binder.getCallingUid())) {
+            Log.i(TAG, "enableInstantCommunicationMode not allowed for uid="
+                    + Binder.getCallingUid());
+            return;
+        }
+        enforceChangePermission();
+        mStateManager.enableInstantCommunicationMode(enable);
+    }
+
+    @Override
+    public boolean isInstantCommunicationModeEnabled() {
+        enforceAccessPermission();
+        return mStateManager.isInstantCommunicationModeEnabled();
+    }
+
+    @Override
     public void connect(final IBinder binder, String callingPackage, String callingFeatureId,
             IWifiAwareEventCallback callback, ConfigRequest configRequest,
             boolean notifyOnIdentityChanged) {
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareStateManager.java b/service/java/com/android/server/wifi/aware/WifiAwareStateManager.java
index 464723e..3ef797e 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareStateManager.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareStateManager.java
@@ -16,15 +16,18 @@
 
 package com.android.server.wifi.aware;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.hardware.wifi.V1_0.NanStatusType;
+import android.hardware.wifi.V1_0.WifiStatusCode;
 import android.hardware.wifi.V1_2.NanDataPathChannelInfo;
 import android.location.LocationManager;
 import android.net.wifi.WifiManager;
+import android.net.wifi.aware.AwareResources;
 import android.net.wifi.aware.Characteristics;
 import android.net.wifi.aware.ConfigRequest;
 import android.net.wifi.aware.IWifiAwareDiscoverySessionCallback;
@@ -39,11 +42,12 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.os.WorkSource;
 import android.text.TextUtils;
-import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Pair;
 import android.util.SparseArray;
@@ -54,6 +58,7 @@
 import com.android.internal.util.StateMachine;
 import com.android.internal.util.WakeupMessage;
 import com.android.modules.utils.BasicShellCommandHandler;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.Clock;
 import com.android.server.wifi.util.NetdWrapper;
 import com.android.server.wifi.util.WifiPermissionsUtil;
@@ -129,6 +134,7 @@
     private static final int COMMAND_TYPE_DELAYED_INITIALIZATION = 121;
     private static final int COMMAND_TYPE_GET_AWARE = 122;
     private static final int COMMAND_TYPE_RELEASE_AWARE = 123;
+    private static final int COMMAND_TYPE_DISABLE = 124;
 
     private static final int RESPONSE_TYPE_ON_CONFIG_SUCCESS = 200;
     private static final int RESPONSE_TYPE_ON_CONFIG_FAIL = 201;
@@ -157,6 +163,7 @@
     private static final int NOTIFICATION_TYPE_ON_DATA_PATH_CONFIRM = 310;
     private static final int NOTIFICATION_TYPE_ON_DATA_PATH_END = 311;
     private static final int NOTIFICATION_TYPE_ON_DATA_PATH_SCHED_UPDATE = 312;
+    private static final int NOTIFICATION_TYPE_MATCH_EXPIRED = 313;
 
     private static final SparseArray<String> sSmToString = MessageUtils.findMessageNames(
             new Class[]{WifiAwareStateManager.class},
@@ -227,9 +234,13 @@
     private ConfigRequest mCurrentAwareConfiguration = null;
     private boolean mCurrentIdentityNotification = false;
     private boolean mCurrentRangingEnabled = false;
+    private boolean mIsInstantCommunicationModeEnabled = false;
 
     private static final byte[] ALL_ZERO_MAC = new byte[] {0, 0, 0, 0, 0, 0};
     private byte[] mCurrentDiscoveryInterfaceMac = ALL_ZERO_MAC;
+    // Flag to help defer the connect request when disable Aware is not finished, to prevent race
+    // condition.
+    private boolean mAwareIsDisabling = false;
 
     public WifiAwareStateManager() {
         onReset();
@@ -322,6 +333,8 @@
                         j.put("maxSubscribeInterfaceAddresses",
                                 mCapabilities.maxSubscribeInterfaceAddresses);
                         j.put("supportedCipherSuites", mCapabilities.supportedCipherSuites);
+                        j.put("isInstantCommunicationModeSupported",
+                                mCapabilities.isInstantCommunicationModeSupported);
                     } catch (JSONException e) {
                         Log.e(TAG, "onCommand: get_capabilities e=" + e);
                     }
@@ -329,6 +342,26 @@
                 pw_out.println(j.toString());
                 return 0;
             }
+            case "get_aware_resources": {
+                if (!SdkLevel.isAtLeastS()) {
+                    return -1;
+                }
+                JSONObject j = new JSONObject();
+                AwareResources resources = getAvailableAwareResources();
+                if (resources != null) {
+                    try {
+                        j.put("numOfAvailableNdps", resources.getAvailableDataPathsCount());
+                        j.put("numOfAvailablePublishSessions",
+                                resources.getAvailablePublishSessionsCount());
+                        j.put("numOfAvailableSubscribeSessions",
+                                resources.getAvailableSubscribeSessionsCount());
+                    } catch (JSONException e) {
+                        Log.e(TAG, "onCommand: get_aware_resources e=" + e);
+                    }
+                }
+                pw_out.println(j.toString());
+                return 0;
+            }
             case "allow_ndp_any": {
                 String flag = parentShell.getNextArgRequired();
                 if (mDataPathMgr == null) {
@@ -337,8 +370,10 @@
                 }
                 if (TextUtils.equals("true", flag)) {
                     mDataPathMgr.mAllowNdpResponderFromAnyOverride = true;
+                    return 0;
                 } else  if (TextUtils.equals("false", flag)) {
                     mDataPathMgr.mAllowNdpResponderFromAnyOverride = false;
+                    return 0;
                 } else {
                     pw_err.println(
                             "Unknown configuration flag for 'allow_ndp_any' - true|false expected"
@@ -420,7 +455,7 @@
                 if (action.equals(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)) {
                     if (mSettableParameters.get(PARAM_ON_IDLE_DISABLE_AWARE) != 0) {
                         if (mPowerManager.isDeviceIdleMode()) {
-                            disableUsage();
+                            disableUsage(false);
                         } else {
                             enableUsage();
                         }
@@ -440,7 +475,7 @@
                 if (wifiPermissionsUtil.isLocationModeEnabled()) {
                     enableUsage();
                 } else {
-                    disableUsage();
+                    disableUsage(false);
                 }
             }
         }, intentFilter);
@@ -450,12 +485,13 @@
         mContext.registerReceiver(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
+                if (mDbg) Log.v(TAG, "onReceive: WIFI_STATE_CHANGED_ACTION: intent=" + intent);
                 boolean isEnabled = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
                         WifiManager.WIFI_STATE_UNKNOWN) == WifiManager.WIFI_STATE_ENABLED;
                 if (isEnabled) {
                     enableUsage();
                 } else {
-                    disableUsage();
+                    disableUsage(false);
                 }
             }
         }, intentFilter);
@@ -469,6 +505,17 @@
     }
 
     /**
+     * Try to get capability if it is null.
+     */
+    public void tryToGetAwareCapability() {
+        if (mCapabilities != null) return;
+        // Internal request for fetching capabilities.
+        getAwareInterface(new WorkSource(Process.WIFI_UID));
+        queryCapabilities();
+        releaseAwareInterface();
+    }
+
+    /**
      * Get the client state for the specified ID (or null if none exists).
      */
     /* package */ WifiAwareClientState getClient(int clientId) {
@@ -483,6 +530,52 @@
     }
 
     /**
+     * Get the available aware resources.
+     */
+    public AwareResources getAvailableAwareResources() {
+        if (mCapabilities == null) {
+            if (mDbg) {
+                Log.v(TAG, "Aware capability hasn't loaded, resources is unknown.");
+            }
+            return null;
+        }
+        Pair<Integer, Integer> numOfDiscoverySessions = getNumOfDiscoverySessions();
+        int numOfAvailableNdps = mCapabilities.maxNdpSessions - mDataPathMgr.getNumOfNdps();
+        int numOfAvailablePublishSessions =
+                mCapabilities.maxPublishes - numOfDiscoverySessions.first;
+        int numOfAvailableSubscribeSessions =
+                mCapabilities.maxSubscribes - numOfDiscoverySessions.second;
+        if (numOfAvailableNdps < 0) {
+            Log.w(TAG, "Available NDPs number is negative, wrong capability?");
+        }
+        if (numOfAvailablePublishSessions < 0) {
+            Log.w(TAG, "Available publish session number is negative, wrong capability?");
+        }
+        if (numOfAvailableSubscribeSessions < 0) {
+            Log.w(TAG, "Available subscribe session number is negative, wrong capability?");
+        }
+        return new AwareResources(numOfAvailableNdps, numOfAvailablePublishSessions,
+                numOfAvailableSubscribeSessions);
+    }
+
+    private Pair<Integer, Integer> getNumOfDiscoverySessions() {
+        int numOfPub = 0;
+        int numOfSub = 0;
+        for (int i = 0; i < mClients.size(); i++) {
+            WifiAwareClientState clientState = mClients.valueAt(i);
+            for (int j = 0; j < clientState.getSessions().size(); j++) {
+                WifiAwareDiscoverySessionState session = clientState.getSessions().valueAt(j);
+                if (session.isPublishSession()) {
+                    numOfPub++;
+                } else {
+                    numOfSub++;
+                }
+            }
+        }
+        return Pair.create(numOfPub, numOfSub);
+    }
+
+    /**
      * Get the public characteristics derived from the capabilities. Use lazy initialization.
      */
     public Characteristics getCharacteristics() {
@@ -552,9 +645,10 @@
      * Place a request to get the Wi-Fi Aware interface (before which no HAL command can be
      * executed).
      */
-    public void getAwareInterface() {
+    public void getAwareInterface(@NonNull WorkSource requestorWs) {
         Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
         msg.arg1 = COMMAND_TYPE_GET_AWARE;
+        msg.obj = requestorWs;
         mSm.sendMessage(msg);
     }
 
@@ -569,6 +663,40 @@
     }
 
     /**
+     * Enable instant communication mode if supported.
+     * @param enabled true for enable, false for disable.
+     */
+    public void enableInstantCommunicationMode(boolean enabled) {
+        if (mCapabilities == null) {
+            if (mDbg) {
+                Log.v(TAG, "Aware capability is not loaded.");
+            }
+            return;
+        }
+
+        if (!mCapabilities.isInstantCommunicationModeSupported) {
+            if (mDbg) {
+                Log.v(TAG, "Device does not support instant communication mode.");
+            }
+            return;
+        }
+        boolean changed = mIsInstantCommunicationModeEnabled != enabled;
+        mIsInstantCommunicationModeEnabled = enabled;
+        if (!changed) {
+            return;
+        }
+        reconfigure();
+    }
+
+    /**
+     * Get if instant communication mode is currently enabled.
+     * @return true if enabled, false otherwise.
+     */
+    public boolean isInstantCommunicationModeEnabled() {
+        return mIsInstantCommunicationModeEnabled;
+    }
+
+    /**
      * Place a request for a new client connection on the state machine queue.
      */
     public void connect(int clientId, int uid, int pid, String callingPackage,
@@ -600,6 +728,16 @@
     }
 
     /**
+     * Place a request to defer Disable Aware on the state machine queue.
+     */
+    private void deferDisableAware() {
+        mAwareIsDisabling = true;
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_DISABLE;
+        mSm.sendMessage(msg);
+    }
+
+    /**
      * Place a request to reconfigure Aware. No additional input - intended to use current
      * power settings when executed. Thus possibly entering or exiting power saving mode if
      * needed (or do nothing if Aware is not active).
@@ -711,10 +849,6 @@
             if (mDbg) Log.d(TAG, "enableUsage(): while Wi-Fi is disabled - ignoring");
             return;
         }
-        if (!mWifiAwareNativeManager.isAwareNativeAvailable()) {
-            if (mDbg) Log.d(TAG, "enableUsage(): while Aware Native isn't Available - ignoring");
-            return;
-        }
         Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
         msg.arg1 = COMMAND_TYPE_ENABLE_USAGE;
         mSm.sendMessage(msg);
@@ -722,10 +856,12 @@
 
     /**
      * Disable usage of Aware. Terminates all existing clients with onAwareDown().
+     * @param markAsAvailable mark the Aware service as available to all app or not.
      */
-    public void disableUsage() {
+    public void disableUsage(boolean markAsAvailable) {
         Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
         msg.arg1 = COMMAND_TYPE_DISABLE_USAGE;
+        msg.arg2 = markAsAvailable ? 1 : 0;
         mSm.sendMessage(msg);
     }
 
@@ -1003,7 +1139,7 @@
 
     /**
      * Response from firmware to
-     * {@link #respondToDataPathRequest(boolean, int, String, byte[], String, boolean)}
+     * {@link #respondToDataPathRequest(boolean, int, String, byte[], String, byte[], boolean)}
      */
     public void onRespondToDataPathSetupRequestResponse(short transactionId, boolean success,
             int reasonOnFailure) {
@@ -1075,6 +1211,18 @@
     }
 
     /**
+     * Place a callback request on the state machine queue: a discovered session
+     * has expired - e.g. some discovered peer is no longer visible.
+     */
+    public void onMatchExpiredNotification(int pubSubId, int requestorInstanceId) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_NOTIFICATION);
+        msg.arg1 = NOTIFICATION_TYPE_MATCH_EXPIRED;
+        msg.arg2 = pubSubId;
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_REQ_INSTANCE_ID, requestorInstanceId);
+        mSm.sendMessage(msg);
+    }
+
+    /**
      * Place a callback request on the state machine queue: a session (publish
      * or subscribe) has terminated (per plan or due to an error).
      */
@@ -1216,8 +1364,8 @@
                 HAL_SEND_MESSAGE_TIMEOUT_TAG, MESSAGE_TYPE_SEND_MESSAGE_TIMEOUT);
 
         private static final long AWARE_WAIT_FOR_DP_CONFIRM_TIMEOUT = 20_000;
-        private final Map<WifiAwareNetworkSpecifier, WakeupMessage>
-                mDataPathConfirmTimeoutMessages = new ArrayMap<>();
+        private final SparseArray<WakeupMessage>
+                mDataPathConfirmTimeoutMessages = new SparseArray<>();
 
         WifiAwareStateMachine(String name, Looper looper) {
             super(name, looper);
@@ -1250,16 +1398,15 @@
                         processSendMessageTimeout();
                         return HANDLED;
                     case MESSAGE_TYPE_DATA_PATH_TIMEOUT: {
-                        WifiAwareNetworkSpecifier networkSpecifier =
-                                (WifiAwareNetworkSpecifier) msg.obj;
+                        int ndpId = msg.arg1;
 
                         if (mDbg) {
-                            Log.v(TAG, "MESSAGE_TYPE_DATA_PATH_TIMEOUT: networkSpecifier="
-                                    + networkSpecifier);
+                            Log.v(TAG, "MESSAGE_TYPE_DATA_PATH_TIMEOUT: ndpId="
+                                    + ndpId);
                         }
 
-                        mDataPathMgr.handleDataPathTimeout(networkSpecifier);
-                        mDataPathConfirmTimeoutMessages.remove(networkSpecifier);
+                        mDataPathMgr.handleDataPathTimeout(ndpId);
+                        mDataPathConfirmTimeoutMessages.remove(ndpId);
                         return HANDLED;
                     }
                     default:
@@ -1398,6 +1545,13 @@
                             matchFilter, rangingIndication, rangeMm);
                     break;
                 }
+                case NOTIFICATION_TYPE_MATCH_EXPIRED: {
+                    int pubSubId = msg.arg2;
+                    int requestorInstanceId = msg.getData()
+                            .getInt(MESSAGE_BUNDLE_KEY_REQ_INSTANCE_ID);
+                    onMatchExpiredLocal(pubSubId, requestorInstanceId);
+                    break;
+                }
                 case NOTIFICATION_TYPE_SESSION_TERMINATED: {
                     int pubSubId = msg.arg2;
                     int reason = (Integer) msg.obj;
@@ -1493,15 +1647,16 @@
                     break;
                 }
                 case NOTIFICATION_TYPE_ON_DATA_PATH_REQUEST: {
-                    WifiAwareNetworkSpecifier networkSpecifier = mDataPathMgr.onDataPathRequest(
+                    int ndpId = (int) msg.obj;
+                    boolean success = mDataPathMgr.onDataPathRequest(
                             msg.arg2, msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MAC_ADDRESS),
-                            (int) msg.obj, msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE));
+                            ndpId, msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE));
 
-                    if (networkSpecifier != null) {
+                    if (success) {
                         WakeupMessage timeout = new WakeupMessage(mContext, getHandler(),
                                 HAL_DATA_PATH_CONFIRM_TIMEOUT_TAG, MESSAGE_TYPE_DATA_PATH_TIMEOUT,
-                                0, 0, networkSpecifier);
-                        mDataPathConfirmTimeoutMessages.put(networkSpecifier, timeout);
+                                ndpId);
+                        mDataPathConfirmTimeoutMessages.put(ndpId, timeout);
                         timeout.schedule(
                                 SystemClock.elapsedRealtime() + AWARE_WAIT_FOR_DP_CONFIRM_TIMEOUT);
                     }
@@ -1509,17 +1664,18 @@
                     break;
                 }
                 case NOTIFICATION_TYPE_ON_DATA_PATH_CONFIRM: {
-                    WifiAwareNetworkSpecifier networkSpecifier = mDataPathMgr.onDataPathConfirm(
-                            msg.arg2, msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MAC_ADDRESS),
+                    int ndpId = msg.arg2;
+                    boolean success = mDataPathMgr.onDataPathConfirm(
+                            ndpId, msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MAC_ADDRESS),
                             msg.getData().getBoolean(MESSAGE_BUNDLE_KEY_SUCCESS_FLAG),
                             msg.getData().getInt(MESSAGE_BUNDLE_KEY_STATUS_CODE),
                             msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE_DATA),
                             (List<NanDataPathChannelInfo>) msg.obj);
 
-                    if (networkSpecifier != null) {
-                        WakeupMessage timeout = mDataPathConfirmTimeoutMessages.remove(
-                                networkSpecifier);
+                    if (success) {
+                        WakeupMessage timeout = mDataPathConfirmTimeoutMessages.get(ndpId);
                         if (timeout != null) {
+                            mDataPathConfirmTimeoutMessages.remove(ndpId);
                             timeout.cancel();
                         }
                     }
@@ -1567,6 +1723,11 @@
 
             switch (msg.arg1) {
                 case COMMAND_TYPE_CONNECT: {
+                    if (mAwareIsDisabling) {
+                        deferMessage(msg);
+                        waitForResponse = false;
+                        break;
+                    }
                     int clientId = msg.arg2;
                     IWifiAwareEventCallback callback = (IWifiAwareEventCallback) msg.obj;
                     ConfigRequest configRequest = (ConfigRequest) msg.getData()
@@ -1591,6 +1752,14 @@
                     waitForResponse = disconnectLocal(mCurrentTransactionId, clientId);
                     break;
                 }
+                case COMMAND_TYPE_DISABLE: {
+                    mAwareIsDisabling = false;
+                    // Must trigger a state transition to execute the deferred connect command
+                    if (!mWifiAwareNativeApi.disable(mCurrentTransactionId)) {
+                        onDisableResponse(mCurrentTransactionId, WifiStatusCode.ERROR_UNKNOWN);
+                    }
+                    break;
+                }
                 case COMMAND_TYPE_RECONFIGURE:
                     waitForResponse = reconfigureLocal(mCurrentTransactionId);
                     break;
@@ -1709,7 +1878,8 @@
                     waitForResponse = false;
                     break;
                 case COMMAND_TYPE_DISABLE_USAGE:
-                    waitForResponse = disableUsageLocal(mCurrentTransactionId);
+                    disableUsageLocal(mCurrentTransactionId, msg.arg2 == 1);
+                    waitForResponse = false;
                     break;
                 case COMMAND_TYPE_GET_CAPABILITIES:
                     if (mCapabilities == null) {
@@ -1758,15 +1928,6 @@
                     waitForResponse = initiateDataPathSetupLocal(mCurrentTransactionId,
                             networkSpecifier, peerId, channelRequestType, channel, peer,
                             interfaceName, pmk, passphrase, isOutOfBand, appInfo);
-
-                    if (waitForResponse) {
-                        WakeupMessage timeout = new WakeupMessage(mContext, getHandler(),
-                                HAL_DATA_PATH_CONFIRM_TIMEOUT_TAG, MESSAGE_TYPE_DATA_PATH_TIMEOUT,
-                                0, 0, networkSpecifier);
-                        mDataPathConfirmTimeoutMessages.put(networkSpecifier, timeout);
-                        timeout.schedule(
-                                SystemClock.elapsedRealtime() + AWARE_WAIT_FOR_DP_CONFIRM_TIMEOUT);
-                    }
                     break;
                 }
                 case COMMAND_TYPE_RESPOND_TO_DATA_PATH_SETUP_REQUEST: {
@@ -1793,7 +1954,8 @@
                     waitForResponse = false;
                     break;
                 case COMMAND_TYPE_GET_AWARE:
-                    mWifiAwareNativeManager.tryToGetAware();
+                    WorkSource requestorWs = (WorkSource) msg.obj;
+                    mWifiAwareNativeManager.tryToGetAware(requestorWs);
                     waitForResponse = false;
                     break;
                 case COMMAND_TYPE_RELEASE_AWARE:
@@ -1911,7 +2073,18 @@
                             msg.getData().getInt(MESSAGE_BUNDLE_KEY_STATUS_CODE));
                     break;
                 case RESPONSE_TYPE_ON_INITIATE_DATA_PATH_SUCCESS:
-                    onInitiateDataPathResponseSuccessLocal(mCurrentCommand, (int) msg.obj);
+                    int ndpId = (int) msg.obj;
+                    boolean success = onInitiateDataPathResponseSuccessLocal(mCurrentCommand,
+                            ndpId);
+                    if (success) {
+                        WakeupMessage timeout = new WakeupMessage(mContext, getHandler(),
+                                HAL_DATA_PATH_CONFIRM_TIMEOUT_TAG, MESSAGE_TYPE_DATA_PATH_TIMEOUT,
+                                ndpId);
+                        mDataPathConfirmTimeoutMessages.put(ndpId, timeout);
+                        timeout.schedule(
+                                SystemClock.elapsedRealtime() + AWARE_WAIT_FOR_DP_CONFIRM_TIMEOUT);
+                    }
+
                     break;
                 case RESPONSE_TYPE_ON_INITIATE_DATA_PATH_FAIL:
                     onInitiateDataPathResponseFailLocal(mCurrentCommand, (int) msg.obj);
@@ -2048,6 +2221,9 @@
                     Log.wtf(TAG,
                             "processTimeout: COMMAND_TYPE_RELEASE_AWARE - shouldn't be waiting!");
                     break;
+                case COMMAND_TYPE_DISABLE:
+                    mAwareMetrics.recordDisableAware();
+                    break;
                 default:
                     Log.wtf(TAG, "processTimeout: this isn't a COMMAND -- msg=" + msg);
                     /* fall-through */
@@ -2238,19 +2414,22 @@
             client.onInterfaceAddressChange(mCurrentDiscoveryInterfaceMac);
             mClients.append(clientId, client);
             mAwareMetrics.recordAttachSession(uid, notifyIdentityChange, mClients);
+            if (!mWifiAwareNativeManager.replaceRequestorWs(createMergedRequestorWs())) {
+                Log.w(TAG, "Failed to replace requestorWs");
+            }
             return false;
         }
         boolean notificationRequired =
                 doesAnyClientNeedIdentityChangeNotifications() || notifyIdentityChange;
 
         if (mCurrentAwareConfiguration == null) {
-            mWifiAwareNativeManager.tryToGetAware();
+            mWifiAwareNativeManager.tryToGetAware(new WorkSource(uid, callingPackage));
         }
 
         boolean success = mWifiAwareNativeApi.enableAndConfigure(transactionId, merged,
                 notificationRequired, mCurrentAwareConfiguration == null,
                 mPowerManager.isInteractive(), mPowerManager.isDeviceIdleMode(),
-                mCurrentRangingEnabled);
+                mCurrentRangingEnabled, mIsInstantCommunicationModeEnabled);
         if (!success) {
             try {
                 callback.onConnectFail(NanStatusType.INTERNAL_FAILURE);
@@ -2285,8 +2464,13 @@
 
         if (mClients.size() == 0) {
             mCurrentAwareConfiguration = null;
-            deleteAllDataPathInterfaces();
-            return mWifiAwareNativeApi.disable(transactionId);
+            mDataPathMgr.deleteAllInterfaces();
+            deferDisableAware();
+            return false;
+        }
+
+        if (!mWifiAwareNativeManager.replaceRequestorWs(createMergedRequestorWs())) {
+            Log.w(TAG, "Failed to replace requestorWs");
         }
 
         ConfigRequest merged = mergeConfigRequests(null);
@@ -2304,7 +2488,7 @@
 
         return mWifiAwareNativeApi.enableAndConfigure(transactionId, merged, notificationReqs,
                 false, mPowerManager.isInteractive(), mPowerManager.isDeviceIdleMode(),
-                rangingEnabled);
+                rangingEnabled, mIsInstantCommunicationModeEnabled);
     }
 
     private boolean reconfigureLocal(short transactionId) {
@@ -2320,7 +2504,8 @@
 
         return mWifiAwareNativeApi.enableAndConfigure(transactionId, mCurrentAwareConfiguration,
                 notificationReqs, false, mPowerManager.isInteractive(),
-                mPowerManager.isDeviceIdleMode(), rangingEnabled);
+                mPowerManager.isDeviceIdleMode(), rangingEnabled,
+                mIsInstantCommunicationModeEnabled);
     }
 
     private void terminateSessionLocal(int clientId, int sessionId) {
@@ -2356,6 +2541,11 @@
         WifiAwareClientState client = mClients.get(clientId);
         if (client == null) {
             Log.e(TAG, "publishLocal: no client exists for clientId=" + clientId);
+            try {
+                callback.onSessionConfigFail(NanStatusType.INTERNAL_FAILURE);
+            } catch (RemoteException e) {
+                Log.w(TAG, "publishLocal onSessionConfigFail(): RemoteException (FYI): " + e);
+            }
             return false;
         }
 
@@ -2410,6 +2600,11 @@
 
         WifiAwareClientState client = mClients.get(clientId);
         if (client == null) {
+            try {
+                callback.onSessionConfigFail(NanStatusType.INTERNAL_FAILURE);
+            } catch (RemoteException e) {
+                Log.w(TAG, "subscribeLocal onSessionConfigFail(): RemoteException (FYI): " + e);
+            }
             Log.e(TAG, "subscribeLocal: no client exists for clientId=" + clientId);
             return false;
         }
@@ -2484,13 +2679,7 @@
     }
 
     private void enableUsageLocal() {
-        if (VDBG) Log.v(TAG, "enableUsageLocal: mUsageEnabled=" + mUsageEnabled);
-
-        if (mCapabilities == null) {
-            getAwareInterface();
-            queryCapabilities();
-            releaseAwareInterface();
-        }
+        if (mDbg) Log.v(TAG, "enableUsageLocal: mUsageEnabled=" + mUsageEnabled);
 
         if (mUsageEnabled) {
             return;
@@ -2501,26 +2690,21 @@
         mAwareMetrics.recordEnableUsage();
     }
 
-    private boolean disableUsageLocal(short transactionId) {
+    private void disableUsageLocal(short transactionId, boolean markAsAvailable) {
         if (VDBG) {
             Log.v(TAG, "disableUsageLocal: transactionId=" + transactionId + ", mUsageEnabled="
                     + mUsageEnabled);
         }
 
         if (!mUsageEnabled) {
-            return false;
+            return;
         }
-
         onAwareDownLocal();
 
-        mUsageEnabled = false;
-        boolean callDispatched = mWifiAwareNativeApi.disable(transactionId);
-
-        sendAwareStateChangedBroadcast(false);
-
+        mUsageEnabled = markAsAvailable;
+        deferDisableAware();
+        sendAwareStateChangedBroadcast(markAsAvailable);
         mAwareMetrics.recordDisableUsage();
-
-        return callDispatched;
     }
 
     private boolean initiateDataPathSetupLocal(short transactionId,
@@ -2588,6 +2772,7 @@
 
         if (completedCommand.arg1 == COMMAND_TYPE_CONNECT) {
             if (mCurrentAwareConfiguration == null) { // enabled (as opposed to re-configured)
+                queryCapabilities();
                 createAllDataPathInterfaces();
             }
 
@@ -2965,13 +3150,14 @@
         }
     }
 
-    private void onInitiateDataPathResponseSuccessLocal(Message command, int ndpId) {
+    private boolean onInitiateDataPathResponseSuccessLocal(Message command, int ndpId) {
         if (VDBG) {
             Log.v(TAG, "onInitiateDataPathResponseSuccessLocal: command=" + command + ", ndpId="
                     + ndpId);
         }
 
-        mDataPathMgr.onDataPathInitiateSuccess((WifiAwareNetworkSpecifier) command.obj, ndpId);
+        return mDataPathMgr
+                .onDataPathInitiateSuccess((WifiAwareNetworkSpecifier) command.obj, ndpId);
     }
 
     private void onInitiateDataPathResponseFailLocal(Message command, int reason) {
@@ -3060,6 +3246,22 @@
                 rangingIndication, rangeMm);
     }
 
+    private void onMatchExpiredLocal(int pubSubId, int requestorInstanceId) {
+        if (VDBG) {
+            Log.v(TAG,
+                    "onMatchExpiredNotification: pubSubId=" + pubSubId
+                            + ", requestorInstanceId=" + requestorInstanceId);
+        }
+
+        Pair<WifiAwareClientState, WifiAwareDiscoverySessionState> data =
+                getClientSessionForPubSubId(pubSubId);
+        if (data == null) {
+            Log.e(TAG, "onMatch: no session found for pubSubId=" + pubSubId);
+            return;
+        }
+        data.second.onMatchExpired(requestorInstanceId);
+    }
+
     private void onSessionTerminatedLocal(int pubSubId, boolean isPublish, int reason) {
         if (VDBG) {
             Log.v(TAG, "onSessionTerminatedLocal: pubSubId=" + pubSubId + ", isPublish=" + isPublish
@@ -3130,7 +3332,7 @@
         mSm.onAwareDownCleanupSendQueueState();
         mDataPathMgr.onAwareDownCleanupDataPaths();
         mCurrentDiscoveryInterfaceMac = ALL_ZERO_MAC;
-        deleteAllDataPathInterfaces();
+        mDataPathMgr.deleteAllInterfaces();
     }
 
     /*
@@ -3243,6 +3445,18 @@
         return builder.build();
     }
 
+    private WorkSource createMergedRequestorWs() {
+        if (mDbg) {
+            Log.v(TAG, "createMergedRequestorWs(): mClients=[" + mClients + "]");
+        }
+        WorkSource requestorWs = new WorkSource();
+        for (int i = 0; i < mClients.size(); ++i) {
+            WifiAwareClientState clientState = mClients.valueAt(i);
+            requestorWs.add(new WorkSource(clientState.getUid(), clientState.getCallingPackage()));
+        }
+        return requestorWs;
+    }
+
     private boolean doesAnyClientNeedIdentityChangeNotifications() {
         for (int i = 0; i < mClients.size(); ++i) {
             if (mClients.valueAt(i).getNotifyIdentityChange()) {
diff --git a/service/java/com/android/server/wifi/coex/CoexManager.java b/service/java/com/android/server/wifi/coex/CoexManager.java
new file mode 100644
index 0000000..6168784
--- /dev/null
+++ b/service/java/com/android/server/wifi/coex/CoexManager.java
@@ -0,0 +1,830 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.coex;
+
+import static android.net.wifi.CoexUnsafeChannel.POWER_CAP_NONE;
+import static android.net.wifi.WifiManager.COEX_RESTRICTION_SOFTAP;
+import static android.net.wifi.WifiManager.COEX_RESTRICTION_WIFI_AWARE;
+import static android.net.wifi.WifiManager.COEX_RESTRICTION_WIFI_DIRECT;
+import static android.net.wifi.WifiScanner.WIFI_BAND_24_GHZ;
+import static android.net.wifi.WifiScanner.WIFI_BAND_5_GHZ;
+import static android.telephony.TelephonyManager.NETWORK_TYPE_LTE;
+import static android.telephony.TelephonyManager.NETWORK_TYPE_NR;
+
+import static com.android.server.wifi.coex.CoexUtils.CHANNEL_SET_5_GHZ;
+import static com.android.server.wifi.coex.CoexUtils.CHANNEL_SET_5_GHZ_160_MHZ;
+import static com.android.server.wifi.coex.CoexUtils.CHANNEL_SET_5_GHZ_20_MHZ;
+import static com.android.server.wifi.coex.CoexUtils.CHANNEL_SET_5_GHZ_40_MHZ;
+import static com.android.server.wifi.coex.CoexUtils.CHANNEL_SET_5_GHZ_80_MHZ;
+import static com.android.server.wifi.coex.CoexUtils.NUM_24_GHZ_CHANNELS;
+import static com.android.server.wifi.coex.CoexUtils.get2gHarmonicCoexUnsafeChannels;
+import static com.android.server.wifi.coex.CoexUtils.get5gHarmonicCoexUnsafeChannels;
+import static com.android.server.wifi.coex.CoexUtils.getIntermodCoexUnsafeChannels;
+import static com.android.server.wifi.coex.CoexUtils.getNeighboringCoexUnsafeChannels;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.wifi.CoexUnsafeChannel;
+import android.net.wifi.ICoexCallback;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiManager.CoexRestriction;
+import android.os.Build;
+import android.os.Handler;
+import android.os.PersistableBundle;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.CarrierConfigManager;
+import android.telephony.PhysicalChannelConfig;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.util.Pair;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.HandlerExecutor;
+import com.android.server.wifi.WifiNative;
+import com.android.wifi.resources.R;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
+
+import javax.annotation.concurrent.NotThreadSafe;
+
+
+/**
+ * This class handles Wi-Fi/Cellular coexistence by dynamically generating a set of Wi-Fi channels
+ * that would cause interference to/receive interference from the active cellular channels. These
+ * Wi-Fi channels are represented by {@link CoexUnsafeChannel} and may be retrieved through
+ * {@link #getCoexUnsafeChannels()}.
+ *
+ * Clients may be notified of updates to the value of #getCoexUnsafeChannels by implementing an
+ * {@link CoexListener} and listening on
+ * {@link CoexListener#onCoexUnsafeChannelsChanged()}
+ *
+ * Note: This class is not thread-safe. It needs to be invoked from the main Wifi thread only.
+ */
+@NotThreadSafe
+@RequiresApi(Build.VERSION_CODES.S)
+public class CoexManager {
+    private static final String TAG = "WifiCoexManager";
+    private boolean mVerboseLoggingEnabled = false;
+
+    @NonNull
+    private final Context mContext;
+    @NonNull
+    private final WifiNative mWifiNative;
+    @NonNull
+    private final TelephonyManager mDefaultTelephonyManager;
+    @NonNull
+    private final SubscriptionManager mSubscriptionManager;
+    @NonNull
+    private final CarrierConfigManager mCarrierConfigManager;
+    @NonNull
+    private final Handler mCallbackHandler;
+
+    private final List<Pair<TelephonyManager, TelephonyCallback>> mTelephonyManagersAndCallbacks =
+            new ArrayList<>();
+
+    @VisibleForTesting
+    /* package */ class CoexOnSubscriptionsChangedListener
+            extends SubscriptionManager.OnSubscriptionsChangedListener {
+        @java.lang.Override
+        public void onSubscriptionsChanged() {
+            final List<SubscriptionInfo> subInfoList =
+                    mSubscriptionManager.getAvailableSubscriptionInfoList();
+            updateCarrierConfigs(subInfoList);
+            Set<Integer> newSubIds = Collections.emptySet();
+            if (subInfoList != null) {
+                newSubIds = subInfoList.stream()
+                        .map(SubscriptionInfo::getSubscriptionId)
+                        .collect(Collectors.toSet());
+            }
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "onSubscriptionsChanged called with subIds: " + newSubIds);
+            }
+            // Unregister callbacks for removed subscriptions
+            Iterator<Pair<TelephonyManager, TelephonyCallback>> iter =
+                    mTelephonyManagersAndCallbacks.iterator();
+            while (iter.hasNext()) {
+                Pair<TelephonyManager, TelephonyCallback> managerCallbackPair = iter.next();
+                final TelephonyManager telephonyManager = managerCallbackPair.first;
+                final TelephonyCallback telephonyCallback = managerCallbackPair.second;
+                final int subId = telephonyManager.getSubscriptionId();
+                final boolean subIdRemoved = !newSubIds.remove(subId);
+                if (subIdRemoved) {
+                    mCellChannelsPerSubId.remove(subId);
+                    telephonyManager.unregisterTelephonyCallback(telephonyCallback);
+                    iter.remove();
+                }
+            }
+            // Register callbacks for new subscriptions
+            for (int subId : newSubIds) {
+                final TelephonyManager telephonyManager =
+                        mDefaultTelephonyManager.createForSubscriptionId(subId);
+                if (telephonyManager == null) {
+                    Log.e(TAG, "Could not create TelephonyManager for subId: " + subId);
+                }
+                final TelephonyCallback callback = new CoexTelephonyCallback(subId);
+                telephonyManager.registerTelephonyCallback(
+                        new HandlerExecutor(mCallbackHandler), callback);
+                mTelephonyManagersAndCallbacks.add(new Pair<>(telephonyManager, callback));
+            }
+        }
+    }
+
+    @VisibleForTesting
+    /* package */ class CoexTelephonyCallback extends TelephonyCallback
+            implements TelephonyCallback.PhysicalChannelConfigListener {
+        private final int mSubId;
+
+        private CoexTelephonyCallback(int subId) {
+            super();
+            mSubId = subId;
+        }
+
+        @java.lang.Override
+        public void onPhysicalChannelConfigChanged(
+                @NonNull List<PhysicalChannelConfig> configs) {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "onPhysicalChannelConfigChanged for subId: " + mSubId
+                        + " called with configs: " + configs);
+            }
+            List<CoexUtils.CoexCellChannel> cellChannels = new ArrayList<>();
+            for (PhysicalChannelConfig config : configs) {
+                cellChannels.add(new CoexUtils.CoexCellChannel(config, mSubId));
+            }
+            if (cellChannels.equals(mCellChannelsPerSubId.get(mSubId))) {
+                // No change to cell channels, so no need to recalculate
+                return;
+            }
+            mCellChannelsPerSubId.put(mSubId, cellChannels);
+            if (mIsUsingMockCellChannels) {
+                return;
+            }
+            updateCoexUnsafeChannels(getCellChannelsForAllSubIds());
+        }
+    }
+
+    private final SparseBooleanArray mAvoid5gSoftApForLaaPerSubId = new SparseBooleanArray();
+    private final SparseBooleanArray mAvoid5gWifiDirectForLaaPerSubId = new SparseBooleanArray();
+    private BroadcastReceiver mCarrierConfigChangedReceiver = new BroadcastReceiver() {
+        @java.lang.Override
+        public void onReceive(Context context, Intent intent) {
+            if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED
+                    .equals(intent.getAction())) {
+                if (updateCarrierConfigs(mSubscriptionManager.getAvailableSubscriptionInfoList())) {
+                    updateCoexUnsafeChannels(getCellChannelsForAllSubIds());
+                }
+            }
+        }
+    };
+
+    @NonNull
+    private final SparseArray<List<CoexUtils.CoexCellChannel>> mCellChannelsPerSubId =
+            new SparseArray<>();
+    @NonNull
+    private final List<CoexUtils.CoexCellChannel> mMockCellChannels = new ArrayList<>();
+    private boolean mIsUsingMockCellChannels = false;
+
+    @NonNull
+    private final List<CoexUnsafeChannel> mCurrentCoexUnsafeChannels = new ArrayList<>();
+    private int mCoexRestrictions;
+
+    @NonNull
+    private final SparseArray<Entry> mLteTableEntriesByBand = new SparseArray<>();
+    @NonNull
+    private final SparseArray<Entry> mNrTableEntriesByBand = new SparseArray<>();
+
+    @NonNull
+    private final Set<CoexListener> mListeners = new HashSet<>();
+    @NonNull
+    private final RemoteCallbackList<ICoexCallback> mRemoteCallbackList =
+            new RemoteCallbackList<ICoexCallback>();
+
+    public CoexManager(@NonNull Context context,
+            @NonNull WifiNative wifiNative,
+            @NonNull TelephonyManager defaultTelephonyManager,
+            @NonNull SubscriptionManager subscriptionManager,
+            @NonNull CarrierConfigManager carrierConfigManager,
+            @NonNull Handler handler) {
+        mContext = context;
+        mWifiNative = wifiNative;
+        mDefaultTelephonyManager = defaultTelephonyManager;
+        mSubscriptionManager = subscriptionManager;
+        mCarrierConfigManager = carrierConfigManager;
+        mCallbackHandler = handler;
+        if (!mContext.getResources().getBoolean(R.bool.config_wifiDefaultCoexAlgorithmEnabled)) {
+            Log.i(TAG, "Default coex algorithm is disabled.");
+            return;
+        }
+        readTableFromXml();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        mContext.registerReceiver(mCarrierConfigChangedReceiver, filter, null, mCallbackHandler);
+        mSubscriptionManager.addOnSubscriptionsChangedListener(
+                new HandlerExecutor(mCallbackHandler), new CoexOnSubscriptionsChangedListener());
+    }
+
+    /**
+     * Returns the list of current {@link CoexUnsafeChannel} being used for Wi-Fi/Cellular coex
+     * channel avoidance supplied in {@link #setCoexUnsafeChannels(List, int)}.
+     *
+     * If any {@link CoexRestriction} flags are set in {@link #getCoexRestrictions()}, then the
+     * CoexUnsafeChannels should be totally avoided (i.e. not best effort) for the Wi-Fi modes
+     * specified by the flags.
+     *
+     * @return Set of current CoexUnsafeChannels.
+     */
+    @NonNull
+    public List<CoexUnsafeChannel> getCoexUnsafeChannels() {
+        return mCurrentCoexUnsafeChannels;
+    }
+
+    /**
+     * Returns the current coex restrictions being used for Wi-Fi/Cellular coex
+     * channel avoidance supplied in {@link #setCoexUnsafeChannels(List, int)}.
+     *
+     * @return int containing a bitwise-OR combination of {@link CoexRestriction}.
+     */
+    public int getCoexRestrictions() {
+        return mCoexRestrictions;
+    }
+
+    /**
+     * Sets the current CoexUnsafeChannels and coex restrictions returned by
+     * {@link #getCoexUnsafeChannels()} and {@link #getCoexRestrictions()} and notifies each
+     * listener with {@link CoexListener#onCoexUnsafeChannelsChanged()} and each
+     * remote callback with {@link ICoexCallback#onCoexUnsafeChannelsChanged()}.
+     *
+     * @param coexUnsafeChannels Set of CoexUnsafeChannels to return in
+     *                           {@link #getCoexUnsafeChannels()}
+     * @param coexRestrictions int to return in {@link #getCoexRestrictions()}
+     */
+    public void setCoexUnsafeChannels(@NonNull List<CoexUnsafeChannel> coexUnsafeChannels,
+            int coexRestrictions) {
+        if (coexUnsafeChannels == null) {
+            Log.e(TAG, "setCoexUnsafeChannels called with null unsafe channel set");
+            return;
+        }
+        if ((~(COEX_RESTRICTION_WIFI_DIRECT | COEX_RESTRICTION_SOFTAP
+                | COEX_RESTRICTION_WIFI_AWARE) & coexRestrictions) != 0) {
+            Log.e(TAG, "setCoexUnsafeChannels called with undefined restriction flags");
+            return;
+        }
+        mCurrentCoexUnsafeChannels.clear();
+        mCurrentCoexUnsafeChannels.addAll(coexUnsafeChannels);
+        mCoexRestrictions = coexRestrictions;
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "Current unsafe channels: " + mCurrentCoexUnsafeChannels
+                    + ", restrictions: " + mCoexRestrictions);
+        }
+        mWifiNative.setCoexUnsafeChannels(mCurrentCoexUnsafeChannels, mCoexRestrictions);
+        notifyListeners();
+        notifyRemoteCallbacks();
+    }
+
+    /**
+     * Registers a {@link CoexListener} to be notified with updates.
+     * @param listener CoexListener to be registered.
+     */
+    public void registerCoexListener(@NonNull CoexListener listener) {
+        if (listener == null) {
+            Log.e(TAG, "registerCoexListener called with null listener");
+            return;
+        }
+        mListeners.add(listener);
+    }
+
+    /**
+     * Unregisters a {@link CoexListener}.
+     * @param listener CoexListener to be unregistered.
+     */
+    public void unregisterCoexListener(@NonNull CoexListener listener) {
+        if (listener == null) {
+            Log.e(TAG, "unregisterCoexListener called with null listener");
+            return;
+        }
+        if (!mListeners.remove(listener)) {
+            Log.e(TAG, "unregisterCoexListener called on listener that was not registered: "
+                    + listener);
+        }
+    }
+
+    /**
+     * Registers a remote ICoexCallback from an external app and immediately notifies it.
+     * see {@link WifiManager#registerCoexCallback(Executor, WifiManager.CoexCallback)}
+     * @param callback ICoexCallback instance to register
+     */
+    public void registerRemoteCoexCallback(ICoexCallback callback) {
+        mRemoteCallbackList.register(callback);
+        try {
+            callback.onCoexUnsafeChannelsChanged(mCurrentCoexUnsafeChannels, mCoexRestrictions);
+        } catch (RemoteException e) {
+            Log.e(TAG, "onCoexUnsafeChannelsChanged: remote exception -- " + e);
+        }
+    }
+
+    /**
+     * Unregisters a remote ICoexCallback from an external app.
+     * see {@link WifiManager#unregisterCoexCallback(WifiManager.CoexCallback)}
+     * @param callback ICoexCallback instance to unregister
+     */
+    public void unregisterRemoteCoexCallback(ICoexCallback callback) {
+        mRemoteCallbackList.unregister(callback);
+    }
+
+    private void notifyListeners() {
+        for (CoexListener listener : mListeners) {
+            listener.onCoexUnsafeChannelsChanged();
+        }
+    }
+
+    private void notifyRemoteCallbacks() {
+        final int itemCount = mRemoteCallbackList.beginBroadcast();
+        for (int i = 0; i < itemCount; i++) {
+            try {
+                mRemoteCallbackList.getBroadcastItem(i)
+                        .onCoexUnsafeChannelsChanged(mCurrentCoexUnsafeChannels, mCoexRestrictions);
+            } catch (RemoteException e) {
+                Log.e(TAG, "onCoexUnsafeChannelsChanged: remote exception -- " + e);
+            }
+        }
+        mRemoteCallbackList.finishBroadcast();
+    }
+
+    /**
+     * Listener interface for internal Wi-Fi clients to listen to updates to
+     * {@link #getCoexUnsafeChannels()} and {@link #getCoexRestrictions()}
+     */
+    public interface CoexListener {
+        /**
+         * Called to notify the listener that the values of
+         * {@link CoexManager#getCoexUnsafeChannels()} and/or
+         * {@link CoexManager#getCoexRestrictions()} have changed and should be
+         * retrieved again.
+         */
+        void onCoexUnsafeChannelsChanged();
+    }
+
+    private void updateCoexUnsafeChannels(@NonNull List<CoexUtils.CoexCellChannel> cellChannels) {
+        if (cellChannels == null) {
+            Log.e(TAG, "updateCoexUnsafeChannels called with null cell channel list");
+            return;
+        }
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "updateCoexUnsafeChannels called with cell channels: " + cellChannels);
+        }
+        int numUnsafe2gChannels = 0;
+        int numUnsafe5gChannels = 0;
+        int default2gChannel = Integer.MAX_VALUE;
+        int default5gChannel = Integer.MAX_VALUE;
+        int coexRestrictions = 0;
+        Map<Pair<Integer, Integer>, CoexUnsafeChannel> coexUnsafeChannelsByBandChannelPair =
+                new HashMap<>();
+        // Gather all of the CoexUnsafeChannels calculated from each cell channel.
+        for (CoexUtils.CoexCellChannel cellChannel : cellChannels) {
+            final Entry entry;
+            switch (cellChannel.getRat()) {
+                case NETWORK_TYPE_LTE:
+                    entry = mLteTableEntriesByBand.get(cellChannel.getBand());
+                    break;
+                case NETWORK_TYPE_NR:
+                    entry = mNrTableEntriesByBand.get(cellChannel.getBand());
+                    break;
+                default:
+                    entry = null;
+            }
+            final List<CoexUnsafeChannel> currentBandUnsafeChannels = new ArrayList<>();
+            if (entry != null) {
+                final int powerCapDbm;
+                if (entry.hasPowerCapDbm()) {
+                    powerCapDbm = entry.getPowerCapDbm();
+                    if (mVerboseLoggingEnabled) {
+                        Log.v(TAG, cellChannel + " sets wifi power cap " + powerCapDbm);
+                    }
+                } else {
+                    powerCapDbm = POWER_CAP_NONE;
+                }
+                final Params params = entry.getParams();
+                final Override override = entry.getOverride();
+                if (params != null) {
+                    // Add all of the CoexUnsafeChannels calculated with the given parameters.
+                    final int downlinkFreqKhz = cellChannel.getDownlinkFreqKhz();
+                    final int downlinkBandwidthKhz = cellChannel.getDownlinkBandwidthKhz();
+                    final int uplinkFreqKhz = cellChannel.getUplinkFreqKhz();
+                    final int uplinkBandwidthKhz = cellChannel.getUplinkBandwidthKhz();
+                    final NeighborThresholds neighborThresholds = params.getNeighborThresholds();
+                    final HarmonicParams harmonicParams2g = params.getHarmonicParams2g();
+                    final HarmonicParams harmonicParams5g = params.getHarmonicParams5g();
+                    final IntermodParams intermodParams2g = params.getIntermodParams2g();
+                    final IntermodParams intermodParams5g = params.getIntermodParams2g();
+                    final DefaultChannels defaultChannels = params.getDefaultChannels();
+                    // Calculate interference from cell downlink.
+                    if (downlinkFreqKhz >= 0 && downlinkBandwidthKhz > 0) {
+                        if (neighborThresholds != null && neighborThresholds.hasCellVictimMhz()) {
+                            final List<CoexUnsafeChannel> neighboringChannels =
+                                    getNeighboringCoexUnsafeChannels(
+                                            downlinkFreqKhz,
+                                            downlinkBandwidthKhz,
+                                            neighborThresholds.getCellVictimMhz() * 1000,
+                                            powerCapDbm);
+                            if (!neighboringChannels.isEmpty()) {
+                                if (mVerboseLoggingEnabled) {
+                                    Log.v(TAG, cellChannel + " is neighboring victim of "
+                                            + neighboringChannels);
+                                }
+                                currentBandUnsafeChannels.addAll(neighboringChannels);
+                            }
+                        }
+                    }
+                    // Calculate interference from cell uplink
+                    if (uplinkFreqKhz >= 0 && uplinkBandwidthKhz > 0) {
+                        if (neighborThresholds != null && neighborThresholds.hasWifiVictimMhz()) {
+                            final List<CoexUnsafeChannel> neighboringChannels =
+                                    getNeighboringCoexUnsafeChannels(
+                                            uplinkFreqKhz,
+                                            uplinkBandwidthKhz,
+                                            neighborThresholds.getWifiVictimMhz() * 1000,
+                                            powerCapDbm);
+                            if (!neighboringChannels.isEmpty()) {
+                                if (mVerboseLoggingEnabled) {
+                                    Log.v(TAG, cellChannel + " is neighboring aggressor to "
+                                            + neighboringChannels);
+                                }
+                                currentBandUnsafeChannels.addAll(neighboringChannels);
+                            }
+                        }
+                        if (harmonicParams2g != null) {
+                            final List<CoexUnsafeChannel> harmonicChannels2g =
+                                    get2gHarmonicCoexUnsafeChannels(
+                                            uplinkFreqKhz,
+                                            uplinkBandwidthKhz,
+                                            harmonicParams2g.getN(),
+                                            harmonicParams2g.getOverlap(),
+                                            powerCapDbm);
+                            if (!harmonicChannels2g.isEmpty()) {
+                                if (mVerboseLoggingEnabled) {
+                                    Log.v(TAG, cellChannel + " has harmonic interference with "
+                                            + harmonicChannels2g);
+                                }
+                                currentBandUnsafeChannels.addAll(harmonicChannels2g);
+                            }
+                        }
+                        if (harmonicParams5g != null) {
+                            final List<CoexUnsafeChannel> harmonicChannels5g =
+                                    get5gHarmonicCoexUnsafeChannels(
+                                            uplinkFreqKhz,
+                                            uplinkBandwidthKhz,
+                                            harmonicParams5g.getN(),
+                                            harmonicParams5g.getOverlap(),
+                                            powerCapDbm);
+                            if (!harmonicChannels5g.isEmpty()) {
+                                if (mVerboseLoggingEnabled) {
+                                    Log.v(TAG, cellChannel + " has harmonic interference with "
+                                            + harmonicChannels5g);
+                                }
+                                currentBandUnsafeChannels.addAll(harmonicChannels5g);
+                            }
+                        }
+
+                        if (intermodParams2g != null) {
+                            for (CoexUtils.CoexCellChannel victimCellChannel : cellChannels) {
+                                if (victimCellChannel.getDownlinkFreqKhz() >= 0
+                                        && victimCellChannel.getDownlinkBandwidthKhz() > 0) {
+                                    final List<CoexUnsafeChannel> intermodChannels2g =
+                                            getIntermodCoexUnsafeChannels(
+                                                    uplinkFreqKhz,
+                                                    uplinkBandwidthKhz,
+                                                    victimCellChannel.getDownlinkFreqKhz(),
+                                                    victimCellChannel.getDownlinkBandwidthKhz(),
+                                                    intermodParams2g.getN(),
+                                                    intermodParams2g.getM(),
+                                                    intermodParams2g.getOverlap(),
+                                                    WIFI_BAND_24_GHZ,
+                                                    powerCapDbm);
+                                    if (!intermodChannels2g.isEmpty()) {
+                                        if (mVerboseLoggingEnabled) {
+                                            Log.v(TAG, cellChannel + " and " + intermodChannels2g
+                                                    + " have intermod interference on "
+                                                    + victimCellChannel);
+                                        }
+                                        currentBandUnsafeChannels.addAll(intermodChannels2g);
+                                    }
+                                }
+                            }
+                        }
+                        if (intermodParams5g != null) {
+                            for (CoexUtils.CoexCellChannel victimCellChannel : cellChannels) {
+                                if (victimCellChannel.getDownlinkFreqKhz() >= 0
+                                        && victimCellChannel.getDownlinkBandwidthKhz() > 0) {
+                                    final List<CoexUnsafeChannel> intermodChannels5g =
+                                            getIntermodCoexUnsafeChannels(
+                                                    uplinkFreqKhz,
+                                                    uplinkBandwidthKhz,
+                                                    victimCellChannel.getDownlinkFreqKhz(),
+                                                    victimCellChannel.getDownlinkBandwidthKhz(),
+                                                    intermodParams5g.getN(),
+                                                    intermodParams5g.getM(),
+                                                    intermodParams5g.getOverlap(),
+                                                    WIFI_BAND_5_GHZ,
+                                                    powerCapDbm);
+                                    if (!intermodChannels5g.isEmpty()) {
+                                        if (mVerboseLoggingEnabled) {
+                                            Log.v(TAG, cellChannel + " and " + intermodChannels5g
+                                                    + " have intermod interference on "
+                                                    + victimCellChannel);
+                                        }
+                                        currentBandUnsafeChannels.addAll(intermodChannels5g);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    // Collect the lowest number default channel for each band to extract from
+                    // calculated set of CoexUnsafeChannels later.
+                    if (defaultChannels != null) {
+                        if (defaultChannels.hasDefault2g()) {
+                            int channel = defaultChannels.getDefault2g();
+                            if (channel < default2gChannel) {
+                                default2gChannel = channel;
+                            }
+                        }
+                        if (defaultChannels.hasDefault5g()) {
+                            int channel = defaultChannels.getDefault5g();
+                            if (channel < default5gChannel) {
+                                default5gChannel = channel;
+                            }
+                        }
+                    }
+                } else if (override != null) {
+                    // Add all of the CoexUnsafeChannels defined by the override lists.
+                    final Override2g override2g = override.getOverride2g();
+                    if (override2g != null) {
+                        final List<Integer> channelList2g = override2g.getChannel();
+                        for (OverrideCategory2g category : override2g.getCategory()) {
+                            if (OverrideCategory2g.all.equals(category)) {
+                                for (int i = 1; i <= 14; i++) {
+                                    channelList2g.add(i);
+                                }
+                            }
+                        }
+                        if (!channelList2g.isEmpty()) {
+                            if (mVerboseLoggingEnabled) {
+                                Log.v(TAG, cellChannel + " sets override 2g channels "
+                                        + channelList2g);
+                            }
+                            for (int channel : channelList2g) {
+                                currentBandUnsafeChannels.add(new CoexUnsafeChannel(
+                                        WIFI_BAND_24_GHZ, channel, powerCapDbm));
+                            }
+                        }
+                    }
+                    final Override5g override5g = override.getOverride5g();
+                    if (override5g != null) {
+                        final List<Integer> channelList5g = override5g.getChannel();
+                        for (OverrideCategory5g category : override5g.getCategory()) {
+                            if (OverrideCategory5g._20Mhz.equals(category)) {
+                                channelList5g.addAll(CHANNEL_SET_5_GHZ_20_MHZ);
+                            } else if (OverrideCategory5g._40Mhz.equals(category)) {
+                                channelList5g.addAll(CHANNEL_SET_5_GHZ_40_MHZ);
+                            } else if (OverrideCategory5g._80Mhz.equals(category)) {
+                                channelList5g.addAll(CHANNEL_SET_5_GHZ_80_MHZ);
+                            } else if (OverrideCategory5g._160Mhz.equals(category)) {
+                                channelList5g.addAll(CHANNEL_SET_5_GHZ_160_MHZ);
+                            } else if (OverrideCategory5g.all.equals(category)) {
+                                channelList5g.addAll(CHANNEL_SET_5_GHZ);
+                            }
+                        }
+                        if (!channelList5g.isEmpty()) {
+                            if (mVerboseLoggingEnabled) {
+                                Log.v(TAG, cellChannel + " sets override 5g channels "
+                                        + channelList5g);
+                            }
+                            for (int channel : channelList5g) {
+                                currentBandUnsafeChannels.add(new CoexUnsafeChannel(
+                                        WIFI_BAND_5_GHZ, channel, powerCapDbm));
+                            }
+                        }
+                    }
+                }
+            }
+            // Set coex restrictions for LAA based on carrier config values.
+            if (cellChannel.getRat() == NETWORK_TYPE_LTE
+                    && cellChannel.getBand() == AccessNetworkConstants.EutranBand.BAND_46) {
+                final boolean avoid5gSoftAp =
+                        mAvoid5gSoftApForLaaPerSubId.get(cellChannel.getSubId());
+                final boolean avoid5gWifiDirect =
+                        mAvoid5gWifiDirectForLaaPerSubId.get(cellChannel.getSubId());
+                if (avoid5gSoftAp || avoid5gWifiDirect) {
+                    for (int channel : CHANNEL_SET_5_GHZ) {
+                        currentBandUnsafeChannels.add(
+                                new CoexUnsafeChannel(WIFI_BAND_5_GHZ, channel));
+                    }
+                    if (avoid5gSoftAp) {
+                        if (mVerboseLoggingEnabled) {
+                            Log.v(TAG, "Avoiding 5g softap due to LAA channel " + cellChannel);
+                        }
+                        coexRestrictions |= COEX_RESTRICTION_SOFTAP;
+                    }
+                    if (avoid5gWifiDirect) {
+                        if (mVerboseLoggingEnabled) {
+                            Log.v(TAG, "Avoiding 5g wifi direct due to LAA channel " + cellChannel);
+                        }
+                        coexRestrictions |= COEX_RESTRICTION_WIFI_DIRECT;
+                    }
+                }
+            }
+            // Add all of the CoexUnsafeChannels calculated from this cell channel to the total.
+            // If the total already contains a CoexUnsafeChannel for the same band and channel,
+            // keep the one that has the lower power cap.
+            for (CoexUnsafeChannel unsafeChannel : currentBandUnsafeChannels) {
+                final int band = unsafeChannel.getBand();
+                final int channel = unsafeChannel.getChannel();
+                final Pair<Integer, Integer> bandChannelPair = new Pair<>(band, channel);
+                final CoexUnsafeChannel existingUnsafeChannel =
+                        coexUnsafeChannelsByBandChannelPair.get(bandChannelPair);
+                if (existingUnsafeChannel != null) {
+                    if (unsafeChannel.getPowerCapDbm() == POWER_CAP_NONE) {
+                        continue;
+                    }
+                    final int existingPowerCapDbm = existingUnsafeChannel.getPowerCapDbm();
+                    if (existingPowerCapDbm != POWER_CAP_NONE
+                            && existingPowerCapDbm < unsafeChannel.getPowerCapDbm()) {
+                        continue;
+                    }
+                } else {
+                    // Count the number of unsafe channels for each band to determine if we need to
+                    // remove the default channels before returning.
+                    if (band == WIFI_BAND_24_GHZ) {
+                        numUnsafe2gChannels++;
+                    } else if (band == WIFI_BAND_5_GHZ) {
+                        numUnsafe5gChannels++;
+                    }
+                }
+                coexUnsafeChannelsByBandChannelPair.put(bandChannelPair, unsafeChannel);
+            }
+        }
+        // Omit the default channel from each band if the entire band is unsafe and there are
+        // no coex restrictions set.
+        if (coexRestrictions == 0) {
+            if (numUnsafe2gChannels == NUM_24_GHZ_CHANNELS) {
+                if (mVerboseLoggingEnabled) {
+                    Log.v(TAG, "Omitting default 2g channel " + default2gChannel
+                            + " from unsafe set.");
+                }
+                coexUnsafeChannelsByBandChannelPair.remove(
+                        new Pair<>(WIFI_BAND_24_GHZ, default2gChannel));
+            }
+            if (numUnsafe5gChannels == CHANNEL_SET_5_GHZ.size()) {
+                if (mVerboseLoggingEnabled) {
+                    Log.v(TAG, "Omitting default 5g channel " + default5gChannel
+                            + " from unsafe set.");
+                }
+                coexUnsafeChannelsByBandChannelPair.remove(
+                        new Pair<>(WIFI_BAND_5_GHZ, default5gChannel));
+            }
+        }
+        setCoexUnsafeChannels(
+                new ArrayList<>(coexUnsafeChannelsByBandChannelPair.values()), coexRestrictions);
+    }
+
+    /**
+     * Updates carrier config values and returns true if the values have changed, false otherwise.
+     */
+    private boolean updateCarrierConfigs(@Nullable List<SubscriptionInfo> subInfos) {
+        final SparseBooleanArray oldAvoid5gSoftAp = mAvoid5gSoftApForLaaPerSubId.clone();
+        final SparseBooleanArray oldAvoid5gWifiDirect = mAvoid5gWifiDirectForLaaPerSubId.clone();
+        mAvoid5gSoftApForLaaPerSubId.clear();
+        mAvoid5gWifiDirectForLaaPerSubId.clear();
+        if (subInfos != null) {
+            for (SubscriptionInfo subInfo : subInfos) {
+                final int subId = subInfo.getSubscriptionId();
+                PersistableBundle bundle = mCarrierConfigManager.getConfigForSubId(subId);
+                if (bundle != null) {
+                    mAvoid5gSoftApForLaaPerSubId.put(subId, bundle.getBoolean(
+                            CarrierConfigManager.Wifi.KEY_AVOID_5GHZ_SOFTAP_FOR_LAA_BOOL));
+                    mAvoid5gWifiDirectForLaaPerSubId.put(subId, bundle.getBoolean(
+                            CarrierConfigManager.Wifi.KEY_AVOID_5GHZ_WIFI_DIRECT_FOR_LAA_BOOL));
+                }
+            }
+        }
+        return !mAvoid5gSoftApForLaaPerSubId.equals(oldAvoid5gSoftAp)
+                || !mAvoid5gWifiDirectForLaaPerSubId.equals(oldAvoid5gWifiDirect);
+    }
+
+    /**
+     * Parses a coex table xml from the specified File and populates the table entry maps.
+     * Returns {@code true} if the file was found and read successfully, {@code false} otherwise.
+     */
+    @VisibleForTesting
+    boolean readTableFromXml() {
+        final String filepath = mContext.getResources().getString(
+                R.string.config_wifiCoexTableFilepath);
+        if (filepath == null) {
+            Log.e(TAG, "Coex table filepath was null");
+            return false;
+        }
+        final File file = new File(filepath);
+        try (InputStream str = new BufferedInputStream(new FileInputStream(file))) {
+            mLteTableEntriesByBand.clear();
+            mNrTableEntriesByBand.clear();
+            for (Entry entry : XmlParser.readTable(str).getEntry()) {
+                if (RatType.LTE.equals(entry.getRat())) {
+                    mLteTableEntriesByBand.put(entry.getBand(), entry);
+                } else if (RatType.NR.equals(entry.getRat())) {
+                    mNrTableEntriesByBand.put(entry.getBand(), entry);
+                }
+            }
+            Log.i(TAG, "Successfully read coex table from file");
+            return true;
+        } catch (FileNotFoundException e) {
+            Log.e(TAG, "No coex table file found at " + file);
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to read coex table file: " + e);
+        }
+        return false;
+    }
+
+    /**
+     * Sets the mock CoexCellChannels to use for coex calculations.
+     * @param cellChannels list of mock cell channels
+     */
+    public void setMockCellChannels(@NonNull List<CoexUtils.CoexCellChannel> cellChannels) {
+        mIsUsingMockCellChannels = true;
+        mMockCellChannels.clear();
+        mMockCellChannels.addAll(cellChannels);
+        updateCoexUnsafeChannels(mMockCellChannels);
+    }
+
+    /**
+     * Removes all added mock CoexCellChannels.
+     */
+    public void resetMockCellChannels() {
+        mIsUsingMockCellChannels = false;
+        mMockCellChannels.clear();
+        updateCoexUnsafeChannels(getCellChannelsForAllSubIds());
+    }
+
+    /**
+     * Returns all cell channels used for coex calculations.
+     */
+    public List<CoexUtils.CoexCellChannel> getCellChannels() {
+        if (mIsUsingMockCellChannels) {
+            return mMockCellChannels;
+        }
+        return getCellChannelsForAllSubIds();
+    }
+
+    private List<CoexUtils.CoexCellChannel> getCellChannelsForAllSubIds() {
+        final List<CoexUtils.CoexCellChannel> cellChannels = new ArrayList<>();
+        for (int i = 0, size = mCellChannelsPerSubId.size(); i < size; i++) {
+            cellChannels.addAll(mCellChannelsPerSubId.valueAt(i));
+        }
+        return cellChannels;
+    }
+
+    /**
+     * Enables verbose logging of the coex algorithm.
+     */
+    public void enableVerboseLogging(boolean verbose) {
+        mVerboseLoggingEnabled = verbose;
+    }
+}
diff --git a/service/java/com/android/server/wifi/coex/CoexUtils.java b/service/java/com/android/server/wifi/coex/CoexUtils.java
new file mode 100644
index 0000000..7dd2398
--- /dev/null
+++ b/service/java/com/android/server/wifi/coex/CoexUtils.java
@@ -0,0 +1,670 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.coex;
+
+import static android.net.wifi.ScanResult.UNSPECIFIED;
+import static android.net.wifi.WifiScanner.WIFI_BAND_24_GHZ;
+import static android.net.wifi.WifiScanner.WIFI_BAND_5_GHZ;
+
+import android.net.wifi.CoexUnsafeChannel;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiAnnotations;
+import android.os.Build;
+import android.telephony.Annotation;
+import android.telephony.PhysicalChannelConfig;
+import android.util.Log;
+import android.util.SparseIntArray;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * Class containing the unsafe channel algorithms and other utility methods for Wi-Fi/Cellular coex.
+ */
+@RequiresApi(Build.VERSION_CODES.S)
+public class CoexUtils {
+    public static final String TAG = "CoexUtils";
+
+    private static final int INVALID_CHANNEL = -1;
+    @VisibleForTesting
+    /* package */ static final int INVALID_BAND = -1;
+    @VisibleForTesting
+    /* package */ static final int INVALID_FREQ = -1;
+
+    public static final int NUM_24_GHZ_CHANNELS = 14;
+    public static final NavigableSet<Integer> CHANNEL_SET_5_GHZ_20_MHZ = create5g20MhzChannels();
+    public static final NavigableSet<Integer> CHANNEL_SET_5_GHZ_40_MHZ = create5g40MhzChannels();
+    public static final NavigableSet<Integer> CHANNEL_SET_5_GHZ_80_MHZ = create5g80MhzChannels();
+    public static final NavigableSet<Integer> CHANNEL_SET_5_GHZ_160_MHZ = create5g160MhzChannels();
+    public static final NavigableSet<Integer> CHANNEL_SET_5_GHZ = new TreeSet<>();
+    static {
+        CHANNEL_SET_5_GHZ.addAll(CHANNEL_SET_5_GHZ_20_MHZ);
+        CHANNEL_SET_5_GHZ.addAll(CHANNEL_SET_5_GHZ_40_MHZ);
+        CHANNEL_SET_5_GHZ.addAll(CHANNEL_SET_5_GHZ_80_MHZ);
+        CHANNEL_SET_5_GHZ.addAll(CHANNEL_SET_5_GHZ_160_MHZ);
+    }
+    private static final SparseIntArray DEPENDENT_MAP_5_GHZ = create5gDependentChannelMap();
+
+    private static NavigableSet<Integer> create5g20MhzChannels() {
+        NavigableSet<Integer> set = new TreeSet<>();
+        for (int chan = 32; chan <= 68; chan += 4) {
+            set.add(chan);
+        }
+        for (int chan = 96; chan <= 144; chan += 4) {
+            set.add(chan);
+        }
+        for (int chan = 149; chan <= 173; chan += 4) {
+            set.add(chan);
+        }
+        return set;
+    }
+
+    private static NavigableSet<Integer> create5g40MhzChannels() {
+        NavigableSet<Integer> set = new TreeSet<>();
+        set.add(34);
+        for (int chan = 38; chan <= 62; chan += 8) {
+            set.add(chan);
+        }
+        for (int chan = 102; chan <= 142; chan += 8) {
+            set.add(chan);
+        }
+        for (int chan = 151; chan <= 159; chan += 8) {
+            set.add(chan);
+        }
+        return set;
+    }
+
+    private static NavigableSet<Integer> create5g80MhzChannels() {
+        NavigableSet<Integer> set = new TreeSet<>();
+        set.add(42);
+        set.add(58);
+        set.add(106);
+        set.add(122);
+        set.add(138);
+        set.add(155);
+        return set;
+    }
+
+    private static NavigableSet<Integer> create5g160MhzChannels() {
+        NavigableSet<Integer> set = new TreeSet<>();
+        set.add(50);
+        set.add(114);
+        return set;
+    }
+
+    /**
+     * Creates a SparseIntArray map of 5GHz channel to the dependent channel that contains it,
+     * if it exists.
+     */
+    private static SparseIntArray create5gDependentChannelMap() {
+        SparseIntArray map = new SparseIntArray();
+        // Map 160Mhz channels with their dependency 80Mhz channels.
+        for (int chan : CHANNEL_SET_5_GHZ_160_MHZ) {
+            map.put(chan - 8, chan);
+            map.put(chan + 8, chan);
+        }
+        // Map 80Mhz channels with their dependency 40Mhz channels.
+        for (int chan : CHANNEL_SET_5_GHZ_80_MHZ) {
+            map.put(chan - 4, chan);
+            map.put(chan + 4, chan);
+        }
+        // Map 40Mhz channels with their dependency 20Mhz channels.
+        // Note channel 36 maps to both 34 and 38, but will only map to 38 in the dependent map.
+        for (int chan : CHANNEL_SET_5_GHZ_40_MHZ) {
+            map.put(chan - 2, chan);
+            map.put(chan + 2, chan);
+        }
+        return map;
+    }
+
+    // Channels to frequencies
+
+    /** Gets the upper or lower edge of a given channel */
+    private static int getChannelEdgeKhz(int channel, @WifiAnnotations.WifiBandBasic int band,
+            boolean lowerEdge) {
+        int centerFreqMhz = ScanResult.convertChannelToFrequencyMhzIfSupported(channel, band);
+        if (centerFreqMhz == UNSPECIFIED) {
+            return INVALID_FREQ;
+        }
+
+        int bandwidthOffsetMhz = 0;
+        if (band == WIFI_BAND_24_GHZ) {
+            bandwidthOffsetMhz = 11;
+        } else if (band == WIFI_BAND_5_GHZ) {
+            if (CHANNEL_SET_5_GHZ_20_MHZ.contains(channel)) {
+                bandwidthOffsetMhz = 10;
+            } else if (CHANNEL_SET_5_GHZ_40_MHZ.contains(channel)) {
+                bandwidthOffsetMhz = 20;
+            } else if (CHANNEL_SET_5_GHZ_80_MHZ.contains(channel)) {
+                bandwidthOffsetMhz = 40;
+            } else {
+                bandwidthOffsetMhz = 80;
+            }
+        }
+
+        if (lowerEdge) {
+            bandwidthOffsetMhz = -bandwidthOffsetMhz;
+        }
+        return (centerFreqMhz + bandwidthOffsetMhz) * 1_000;
+    }
+
+    /** Gets the lower frequency of a given channel */
+    @VisibleForTesting
+    /* package */ static int getLowerFreqKhz(int channel, @WifiAnnotations.WifiBandBasic int band) {
+        return getChannelEdgeKhz(channel, band, true);
+    }
+
+    /** Gets the upper frequency of a given channel */
+    @VisibleForTesting
+    /* package */ static int getUpperFreqKhz(int channel, @WifiAnnotations.WifiBandBasic int band) {
+        return getChannelEdgeKhz(channel, band, false);
+    }
+
+    // Frequencies to channels
+
+    /**
+     * Gets the highest 2.4GHz Wi-Fi channel overlapping the upper edge of an interference
+     * frequency approaching from below the band.
+     *
+     * Returns INVALID_CHANNEL if there is no overlap.
+     */
+    private static int get2gHighestOverlapChannel(int upperEdgeKhz) {
+        final int band = WIFI_BAND_24_GHZ;
+        if (upperEdgeKhz > getLowerFreqKhz(14, band)) {
+            return 14;
+        }
+        if (upperEdgeKhz > getLowerFreqKhz(13, band)) {
+            return 13;
+        }
+        final int chan1LowerFreqKhz = getLowerFreqKhz(1, band);
+        if (upperEdgeKhz > chan1LowerFreqKhz) {
+            return getOffsetChannel(1, upperEdgeKhz - chan1LowerFreqKhz, 1);
+        }
+        // Edge does not overlap the band.
+        return INVALID_CHANNEL;
+    }
+
+    /**
+     * Gets the lowest 2.4GHz Wi-Fi channel overlapping the lower edge of an interference
+     * frequency approaching from above the band.
+     *
+     * Returns INVALID_CHANNEL if there is no overlap.
+     */
+    private static int get2gLowestOverlapChannel(int lowerEdgeKhz) {
+        final int band = WIFI_BAND_24_GHZ;
+        if (lowerEdgeKhz < getUpperFreqKhz(1, band)) {
+            return 1;
+        }
+        final int chan13UpperFreqKhz = getUpperFreqKhz(13, band);
+        if (lowerEdgeKhz < chan13UpperFreqKhz) {
+            return getOffsetChannel(13, lowerEdgeKhz - chan13UpperFreqKhz, 1);
+        }
+        if (lowerEdgeKhz < getUpperFreqKhz(14, band)) {
+            return 14;
+        }
+        // Edge does not overlap the band.
+        return INVALID_CHANNEL;
+    }
+
+    /**
+     * Gets the highest 5GHz Wi-Fi channel overlapping the upper edge of an interference
+     * frequency approaching from below the band.
+     *
+     * Returns INVALID_CHANNEL if there is no overlap.
+     */
+    private static int get5gHighestOverlap20MhzChannel(int upperEdgeKhz) {
+        final int band = WIFI_BAND_5_GHZ;
+        // 149 to 173
+        if (upperEdgeKhz > getLowerFreqKhz(173, band)) {
+            return 173;
+        }
+        final int chan149LowerFreqKhz = getLowerFreqKhz(149, band);
+        if (upperEdgeKhz > chan149LowerFreqKhz) {
+            return getOffsetChannel(149, upperEdgeKhz - chan149LowerFreqKhz, 4);
+        }
+        // 96 to 144
+        if (upperEdgeKhz > getLowerFreqKhz(144, band)) {
+            return 144;
+        }
+        final int chan96LowerFreqKhz = getLowerFreqKhz(96, band);
+        if (upperEdgeKhz > chan96LowerFreqKhz) {
+            return getOffsetChannel(96, upperEdgeKhz - chan96LowerFreqKhz, 4);
+        }
+        // 32 to 68
+        if (upperEdgeKhz > getLowerFreqKhz(68, band)) {
+            return 68;
+        }
+        final int chan32LowerFreqKhz = getLowerFreqKhz(32, band);
+        if (upperEdgeKhz > chan32LowerFreqKhz) {
+            return getOffsetChannel(32, upperEdgeKhz - chan32LowerFreqKhz, 4);
+        }
+        // Edge does not overlap the band.
+        return INVALID_CHANNEL;
+    }
+
+    /**
+     * Gets the lowest 5GHz Wi-Fi channel overlapping the lower edge of an interference
+     * frequency approaching from above the band.
+     *
+     * Returns INVALID_CHANNEL if there is no overlap.
+     */
+    private static int get5gLowestOverlap20MhzChannel(int lowerEdgeKhz) {
+        final int band = WIFI_BAND_5_GHZ;
+        // 32 to 68
+        if (lowerEdgeKhz < getUpperFreqKhz(32, band)) {
+            return 32;
+        }
+        final int chan68UpperFreqKhz = getUpperFreqKhz(68, band);
+        if (lowerEdgeKhz < chan68UpperFreqKhz) {
+            return getOffsetChannel(68, lowerEdgeKhz - chan68UpperFreqKhz, 4);
+        }
+        // 96 to 144
+        if (lowerEdgeKhz < getUpperFreqKhz(96, band)) {
+            return 96;
+        }
+        final int chan144UpperFreqKhz = getUpperFreqKhz(144, band);
+        if (lowerEdgeKhz < chan144UpperFreqKhz) {
+            return getOffsetChannel(144, lowerEdgeKhz - chan144UpperFreqKhz, 4);
+        }
+        // 149 to 173
+        if (lowerEdgeKhz < getUpperFreqKhz(149, band)) {
+            return 149;
+        }
+        final int chan173UpperFreqKhz = getUpperFreqKhz(173, band);
+        if (lowerEdgeKhz < chan173UpperFreqKhz) {
+            return getOffsetChannel(173, lowerEdgeKhz - chan173UpperFreqKhz, 4);
+        }
+        // Edge does not overlap the band.
+        return INVALID_CHANNEL;
+    }
+
+    /**
+     * Returns the furthest channel located a given frequency offset away from a start channel
+     * counting by a given channel step size. A positive frequency offset will give a channel
+     * above the start, and a negative frequency offset will give a channel below the start.
+     *
+     * @param startChannel Channel to start from
+     * @param offsetKhz Offset distance in Khz
+     * @param channelStepSize Step size to count channels by
+     * @return The channel that lies the given offset away from the start channel
+     */
+    @VisibleForTesting
+    /* package */ static int getOffsetChannel(
+            int startChannel, int offsetKhz, int channelStepSize) {
+        // Each channel number is always separated by 5Mhz.
+        int channelSpacingKhz = 5_000;
+        int stepsToOffset = offsetKhz / (channelSpacingKhz * channelStepSize);
+        // Offset lands directly channel edge; use previous channel based on offset direction.
+        if (offsetKhz % (channelSpacingKhz * channelStepSize) == 0) {
+            if (offsetKhz > 0) {
+                stepsToOffset--;
+            } else if (offsetKhz < 0) {
+                stepsToOffset++;
+            }
+        }
+        return startChannel + (stepsToOffset * channelStepSize);
+    }
+
+    /**
+     * Returns the percent overlap (0 to 100) of an aggressor frequency range over a victim
+     * frequency range,
+     */
+    private static int getOverlapPercent(int aggressorLowerKhz, int aggressorUpperKhz,
+            int victimLowerKhz, int victimUpperKhz) {
+        final int victimBandwidthKhz = victimUpperKhz - victimLowerKhz;
+        int overlapWidthKhz = Math.min(aggressorUpperKhz, victimUpperKhz)
+                - Math.max(aggressorLowerKhz, victimLowerKhz);
+        if (overlapWidthKhz < 0) {
+            overlapWidthKhz = 0;
+        }
+        if (victimBandwidthKhz == 0) {
+            return 0;
+        }
+        return overlapWidthKhz * 100 / victimBandwidthKhz;
+    }
+
+    /**
+     * Returns the CoexUnsafeChannels for the given cell channel and threshold.
+     */
+    public static List<CoexUnsafeChannel> getNeighboringCoexUnsafeChannels(
+            int cellFreqKhz, int cellBandwidthKhz, int thresholdKhz, int powerCapDbm) {
+        List<CoexUnsafeChannel> coexUnsafeChannels = new ArrayList<>();
+        final int unsafeLowerKhz = cellFreqKhz - (cellBandwidthKhz / 2) - thresholdKhz;
+        final int unsafeUpperKhz = cellFreqKhz + (cellBandwidthKhz / 2) + thresholdKhz;
+
+        // 2.4Ghz
+        final int lowest2gChannel = get2gLowestOverlapChannel(unsafeLowerKhz);
+        final int highest2gChannel = get2gHighestOverlapChannel(unsafeUpperKhz);
+        // If the interference has a valid overlap over the 2.4GHz band, mark every channel
+        // in the inclusive range of the lowest and highest overlapped channels.
+        if (lowest2gChannel != INVALID_CHANNEL && highest2gChannel != INVALID_CHANNEL
+                && lowest2gChannel <= highest2gChannel) {
+            for (int channel = lowest2gChannel; channel <= highest2gChannel; channel++) {
+                coexUnsafeChannels.add(
+                        new CoexUnsafeChannel(WIFI_BAND_24_GHZ, channel, powerCapDbm));
+            }
+        }
+
+        // 5Ghz
+        final int highest5gChannel = get5gHighestOverlap20MhzChannel(unsafeUpperKhz);
+        final int lowest5gChannel = get5gLowestOverlap20MhzChannel(unsafeLowerKhz);
+        // If the interference has a valid overlap over the 5GHz band, mark every channel
+        // in the inclusive range of the lowest and highest overlapped channels.
+        if (lowest5gChannel != INVALID_CHANNEL && highest5gChannel != INVALID_CHANNEL
+                && lowest5gChannel <= highest5gChannel) {
+            final Set<Integer> overlapped5g20MhzChannels = CHANNEL_SET_5_GHZ_20_MHZ.subSet(
+                    lowest5gChannel, true,
+                    highest5gChannel, true);
+            final Set<Integer> seen = new HashSet<>();
+            // Mark overlapped 20Mhz channels and their dependents as unsafe
+            for (int channel : overlapped5g20MhzChannels) {
+                while (channel != 0) {
+                    if (!seen.add(channel)) {
+                        // Dependent channel was already marked unsafe by another dependency channel
+                        break;
+                    }
+                    coexUnsafeChannels.add(
+                            new CoexUnsafeChannel(WIFI_BAND_5_GHZ, channel, powerCapDbm));
+                    // Go to each dependent 40, 80, 160Mhz channel and mark them as unsafe.
+                    // If a dependent doesn't exist, channel will be set to 0 and the loop ends.
+                    channel = DEPENDENT_MAP_5_GHZ.get(channel, 0);
+                }
+            }
+            // 36 should also map to 34, but only maps to 38 in the dependent channel map.
+            if (overlapped5g20MhzChannels.contains(36) && !seen.contains(34)) {
+                coexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 34, powerCapDbm));
+            }
+        }
+
+        return coexUnsafeChannels;
+    }
+
+    /**
+     * Returns the 2.4GHz UnsafeChannels affected by the harmonic interference from a given uplink
+     * cell channel.
+     */
+    public static List<CoexUnsafeChannel> get2gHarmonicCoexUnsafeChannels(
+            int ulFreqKhz, int ulBandwidthKhz, int harmonicDegree, int overlapPercentThreshold,
+            int powerCapDbm) {
+        List<CoexUnsafeChannel> coexUnsafeChannels = new ArrayList<>();
+        final int unsafeLowerKhz = (ulFreqKhz - (ulBandwidthKhz / 2)) * harmonicDegree;
+        final int unsafeUpperKhz = (ulFreqKhz + (ulBandwidthKhz / 2)) * harmonicDegree;
+
+        int lowest2gChannel = get2gLowestOverlapChannel(unsafeLowerKhz);
+        int highest2gChannel = get2gHighestOverlapChannel(unsafeUpperKhz);
+        if (lowest2gChannel != INVALID_CHANNEL && highest2gChannel != INVALID_CHANNEL
+                && lowest2gChannel <= highest2gChannel) {
+            // Find lowest channel at max overlap, or invalid channel 15 if the threshold is not met
+            while (lowest2gChannel <= 14 && getOverlapPercent(unsafeLowerKhz, unsafeUpperKhz,
+                    getLowerFreqKhz(lowest2gChannel, WIFI_BAND_24_GHZ),
+                    getUpperFreqKhz(lowest2gChannel, WIFI_BAND_24_GHZ))
+                    < overlapPercentThreshold) {
+                lowest2gChannel++;
+            }
+            // Find highest channel at max overlap, or invalid channel 0 if the threshold is not met
+            while (highest2gChannel >= 1 && getOverlapPercent(unsafeLowerKhz, unsafeUpperKhz,
+                    getLowerFreqKhz(highest2gChannel, WIFI_BAND_24_GHZ),
+                    getUpperFreqKhz(highest2gChannel, WIFI_BAND_24_GHZ))
+                    < overlapPercentThreshold) {
+                highest2gChannel--;
+            }
+            // Mark every channel in between as unsafe
+            for (int channel = lowest2gChannel; channel <= highest2gChannel; channel++) {
+                coexUnsafeChannels.add(
+                        new CoexUnsafeChannel(WIFI_BAND_24_GHZ, channel, powerCapDbm));
+            }
+        }
+        return coexUnsafeChannels;
+    }
+
+
+    /**
+     * Returns the 5GHz CoexUnsafeChannels affected by the harmonic interference from a given uplink
+     * cell channel.
+     */
+    public static List<CoexUnsafeChannel> get5gHarmonicCoexUnsafeChannels(
+            int ulFreqKhz, int ulBandwidthKhz, int harmonicDegree, int overlapPercentThreshold,
+            int powerCapDbm) {
+        List<CoexUnsafeChannel> coexUnsafeChannels = new ArrayList<>();
+        final int unsafeLowerKhz = (ulFreqKhz - (ulBandwidthKhz / 2)) * harmonicDegree;
+        final int unsafeUpperKhz = (ulFreqKhz + (ulBandwidthKhz / 2)) * harmonicDegree;
+
+        final int lowest5gChannel = get5gLowestOverlap20MhzChannel(unsafeLowerKhz);
+        final int highest5gChannel = get5gHighestOverlap20MhzChannel(unsafeUpperKhz);
+        if (lowest5gChannel != INVALID_CHANNEL && highest5gChannel != INVALID_CHANNEL
+                && lowest5gChannel <= highest5gChannel) {
+            Map<Integer, Integer> overlapPercents = new HashMap<>();
+            // Find lowest 20MHz overlap channel
+            overlapPercents.put(lowest5gChannel, getOverlapPercent(unsafeLowerKhz, unsafeUpperKhz,
+                    getLowerFreqKhz(lowest5gChannel, WIFI_BAND_5_GHZ),
+                    getUpperFreqKhz(lowest5gChannel, WIFI_BAND_5_GHZ)));
+            // Find highest 2MHz overlap channel
+            overlapPercents.put(highest5gChannel, getOverlapPercent(unsafeLowerKhz, unsafeUpperKhz,
+                    getLowerFreqKhz(highest5gChannel, WIFI_BAND_5_GHZ),
+                    getUpperFreqKhz(highest5gChannel, WIFI_BAND_5_GHZ)));
+            // Every channel in between should be at 100 percent overlap
+            for (int channel : CHANNEL_SET_5_GHZ_20_MHZ.subSet(
+                    lowest5gChannel, false, highest5gChannel, false)) {
+                overlapPercents.put(channel, 100);
+            }
+            // Iterate through each group of 20Mhz, 40Mhz, 80Mhz, 160Mhz channels, and add to
+            // unsafe channel set if the pre-calculated overlap percent meets the threshold.
+            while (!overlapPercents.isEmpty()) {
+                Map<Integer, Integer> dependentOverlaps = new HashMap<>();
+                for (int channel : overlapPercents.keySet()) {
+                    int overlapPercent = overlapPercents.get(channel);
+                    // Add channel to unsafe channel set if overlap percent meets threshold.
+                    if (overlapPercent >= overlapPercentThreshold) {
+                        coexUnsafeChannels.add(
+                                new CoexUnsafeChannel(WIFI_BAND_5_GHZ, channel, powerCapDbm));
+                    }
+                    // Pre-calculate the dependent channel overlap for the next iteration by adding
+                    // half of each dependency channel percent.
+                    final int dependentChannel =  DEPENDENT_MAP_5_GHZ.get(channel, 0);
+                    if (dependentChannel != 0) {
+                        dependentOverlaps.put(dependentChannel, overlapPercent / 2
+                                + dependentOverlaps.getOrDefault(dependentChannel, 0));
+                    }
+                    // 36 should also map to 34, but only maps to 38 in the dependent map.
+                    if (channel == 36) {
+                        dependentOverlaps.put(34, overlapPercent / 2
+                                + dependentOverlaps.getOrDefault(34, 0));
+                    }
+                }
+                // Set the next dependent group to iterate over until there are no more dependents.
+                overlapPercents = dependentOverlaps;
+            }
+        }
+        return coexUnsafeChannels;
+    }
+
+    /**
+     * Returns CoexUnsafeChannels of a given band affected by the intermod interference from a given
+     * uplink and downlink cell channel.
+     */
+    public static List<CoexUnsafeChannel> getIntermodCoexUnsafeChannels(
+            int ulFreqKhz, int ulBandwidthKhz, int dlFreqKhz, int dlBandwidthKhz,
+            int n, int m, int overlapPercentThreshold, @WifiAnnotations.WifiBandBasic int band,
+            int powerCapDbm) {
+        List<CoexUnsafeChannel> coexUnsafeChannels = new ArrayList<>();
+        final int ulLowerKhz = (ulFreqKhz - (ulBandwidthKhz / 2));
+        final int ulUpperKhz = (ulFreqKhz + (ulBandwidthKhz / 2));
+        final int dlLowerKhz = (dlFreqKhz - (dlBandwidthKhz / 2));
+        final int dlUpperKhz = (dlFreqKhz + (dlBandwidthKhz / 2));
+
+        Set<Integer> channelSet = new HashSet<>();
+        if (band == WIFI_BAND_24_GHZ) {
+            for (int channel = 1; channel <= 14; channel++) {
+                channelSet.add(channel);
+            }
+        } else if (band == WIFI_BAND_5_GHZ) {
+            channelSet.addAll(CHANNEL_SET_5_GHZ);
+        }
+
+        for (int channel : channelSet) {
+            final int wifiLowerKhz = getLowerFreqKhz(channel, band);
+            final int wifiUpperKhz = getUpperFreqKhz(channel, band);
+            final int intermodLowerKhz = Math.min(n * ulLowerKhz, n * ulUpperKhz)
+                    + Math.min(m * wifiLowerKhz, m * wifiUpperKhz);
+            final int intermodUpperKhz = Math.max(n * ulLowerKhz, n * ulUpperKhz)
+                    + Math.max(m * wifiLowerKhz, m * wifiUpperKhz);
+            if (getOverlapPercent(intermodLowerKhz, intermodUpperKhz, dlLowerKhz, dlUpperKhz)
+                    >= overlapPercentThreshold) {
+                coexUnsafeChannels.add(new CoexUnsafeChannel(band, channel, powerCapDbm));
+            }
+        }
+
+        return coexUnsafeChannels;
+    }
+
+    /**
+     * Data structure class mirroring cell channel information from PhysicalChannelConfig used for
+     * coex calculations.
+     */
+    public static class CoexCellChannel {
+        private final @Annotation.NetworkType int mRat;
+        private final int mBand;
+        private final int mDownlinkFreqKhz;
+        private final int mDownlinkBandwidthKhz;
+        private final int mUplinkFreqKhz;
+        private final int mUplinkBandwidthKhz;
+        private final int mSubId;
+
+        public CoexCellChannel(@Annotation.NetworkType int rat, int band,
+                int downlinkFreqKhz, int downlinkBandwidthKhz,
+                int uplinkFreqKhz, int uplinkBandwidthKhz, int subId) {
+            if (band < 1 || band > 261) {
+                Log.wtf(TAG, "Band is " + band + " but should be a value from 1 to 261");
+            }
+            if (downlinkFreqKhz < 0 && downlinkFreqKhz != PhysicalChannelConfig.FREQUENCY_UNKNOWN) {
+                Log.wtf(TAG, "Downlink frequency is " + downlinkFreqKhz + " but should be >= 0"
+                        + " or PhysicalChannelConfig.FREQUENCY_UNKNOWN: "
+                        + PhysicalChannelConfig.FREQUENCY_UNKNOWN);
+            }
+            if (downlinkBandwidthKhz <= 0
+                    && downlinkBandwidthKhz != PhysicalChannelConfig.CELL_BANDWIDTH_UNKNOWN) {
+                Log.wtf(TAG, "Downlink bandwidth is " + downlinkBandwidthKhz + " but should be > 0"
+                        + " or PhysicalChannelConfig.CELL_BANDWIDTH_UNKNOWN: "
+                        + PhysicalChannelConfig.CELL_BANDWIDTH_UNKNOWN);
+            }
+            if (uplinkFreqKhz < 0 && uplinkFreqKhz != PhysicalChannelConfig.FREQUENCY_UNKNOWN) {
+                Log.wtf(TAG, "Uplink frequency is " + uplinkFreqKhz + " but should be >= 0"
+                        + " or PhysicalChannelConfig.FREQUENCY_UNKNOWN: "
+                        + PhysicalChannelConfig.FREQUENCY_UNKNOWN);
+            }
+            if (uplinkBandwidthKhz <= 0
+                    && uplinkBandwidthKhz != PhysicalChannelConfig.CELL_BANDWIDTH_UNKNOWN) {
+                Log.wtf(TAG, "Uplink bandwidth is " + uplinkBandwidthKhz + " but should be > 0"
+                        + " or PhysicalChannelConfig.CELL_BANDWIDTH_UNKNOWN: "
+                        + PhysicalChannelConfig.CELL_BANDWIDTH_UNKNOWN);
+            }
+            mRat = rat;
+            mBand = band;
+            mDownlinkFreqKhz = downlinkFreqKhz;
+            mDownlinkBandwidthKhz = downlinkBandwidthKhz;
+            mUplinkFreqKhz = uplinkFreqKhz;
+            mUplinkBandwidthKhz = uplinkBandwidthKhz;
+            mSubId = subId;
+        }
+
+        /**
+         * Constructor to extract coex cell channel info from PhysicalChannelConfig
+         */
+        public CoexCellChannel(
+                @android.annotation.NonNull PhysicalChannelConfig config, int subId) {
+            this(config.getNetworkType(),
+                    config.getBand(),
+                    config.getDownlinkFrequencyKhz(),
+                    config.getCellBandwidthDownlinkKhz(),
+                    config.getUplinkFrequencyKhz(),
+                    config.getCellBandwidthUplinkKhz(),
+                    subId);
+        }
+
+        public @Annotation.NetworkType int getRat() {
+            return mRat;
+        }
+
+        public int getBand() {
+            return mBand;
+        }
+
+        public int getDownlinkFreqKhz() {
+            return mDownlinkFreqKhz;
+        }
+
+        public int getDownlinkBandwidthKhz() {
+            return mDownlinkBandwidthKhz;
+        }
+
+        public int getUplinkFreqKhz() {
+            return mUplinkFreqKhz;
+        }
+
+        public int getUplinkBandwidthKhz() {
+            return mUplinkBandwidthKhz;
+        }
+
+        public int getSubId() {
+            return mSubId;
+        }
+
+        @java.lang.Override
+        public String toString() {
+            return "CoexCellChannel{"
+                    + "rat=" + mRat
+                    + ", band=" + mBand
+                    + ", dlFreqKhz=" + mDownlinkFreqKhz
+                    + ", dlBandwidthKhz=" + mDownlinkBandwidthKhz
+                    + ", ulFreqKhz=" + mUplinkFreqKhz
+                    + ", ulBandwidthKhz=" + mUplinkBandwidthKhz
+                    + ", subId=" + mSubId
+                    + '}';
+        }
+
+        @java.lang.Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof CoexCellChannel)) return false;
+            CoexCellChannel that = (CoexCellChannel) o;
+            return getRat() == that.getRat() && getBand() == that.getBand()
+                    && getDownlinkFreqKhz() == that.getDownlinkFreqKhz()
+                    && getDownlinkBandwidthKhz() == that.getDownlinkBandwidthKhz()
+                    && getUplinkFreqKhz() == that.getUplinkFreqKhz()
+                    && getUplinkBandwidthKhz() == that.getUplinkBandwidthKhz()
+                    && getSubId() == that.getSubId();
+        }
+
+        @java.lang.Override
+        public int hashCode() {
+            return Objects.hash(getRat(), getBand(), getDownlinkFreqKhz(),
+                    getDownlinkBandwidthKhz(),
+                    getUplinkFreqKhz(), getUplinkBandwidthKhz(), getSubId());
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/ANQPData.java b/service/java/com/android/server/wifi/hotspot2/ANQPData.java
index 26286a8..b65b790 100644
--- a/service/java/com/android/server/wifi/hotspot2/ANQPData.java
+++ b/service/java/com/android/server/wifi/hotspot2/ANQPData.java
@@ -20,6 +20,7 @@
 import com.android.server.wifi.Clock;
 import com.android.server.wifi.hotspot2.anqp.ANQPElement;
 import com.android.server.wifi.hotspot2.anqp.Constants;
+import com.android.server.wifi.hotspot2.anqp.HSWanMetricsElement;
 
 import java.util.Collections;
 import java.util.HashMap;
@@ -33,18 +34,45 @@
      * Entry lifetime.
      */
     @VisibleForTesting
-    public static final long DATA_LIFETIME_MILLISECONDS = 3600000L;
+    public static final long DATA_LIFETIME_MILLISECONDS = 3_600_000L; // One hour
+    public static final long DATA_SHORT_LIFETIME_MILLISECONDS = 600_000L; // Ten minutes
 
     private final Clock mClock;
     private final Map<Constants.ANQPElementType, ANQPElement> mANQPElements;
-    private final long mExpiryTime;
+    private long mExpiryTime;
 
     public ANQPData(Clock clock, Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
         mClock = clock;
         mANQPElements = new HashMap<>();
+        long dataLifetime = DATA_LIFETIME_MILLISECONDS;
+
         if (anqpElements != null) {
             mANQPElements.putAll(anqpElements);
+
+            HSWanMetricsElement hsWanMetricsElement =
+                    (HSWanMetricsElement) anqpElements.get(Constants.ANQPElementType.HSWANMetrics);
+
+            if (hsWanMetricsElement != null) {
+                // If the WAN Metrics ANQP-element is initialized, and signals that the AP is at
+                // capacity or its link status is down, then send a new ANQP-request sooner, because
+                // its status may change.
+                if (hsWanMetricsElement.isElementInitialized()
+                        && (hsWanMetricsElement.getStatus() != HSWanMetricsElement.LINK_STATUS_UP
+                        || hsWanMetricsElement.isAtCapacity())) {
+                    dataLifetime = DATA_SHORT_LIFETIME_MILLISECONDS;
+                }
+            }
         }
+        mExpiryTime = mClock.getElapsedSinceBootMillis() + dataLifetime;
+    }
+
+    /**
+     * Update an entry with post association ANQP elelemtns
+     *
+     * @param anqpElements ANQP elements to add
+     */
+    public void update(Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
+        mANQPElements.putAll(anqpElements);
         mExpiryTime = mClock.getElapsedSinceBootMillis() + DATA_LIFETIME_MILLISECONDS;
     }
 
diff --git a/service/java/com/android/server/wifi/hotspot2/ANQPRequestManager.java b/service/java/com/android/server/wifi/hotspot2/ANQPRequestManager.java
index 2634fa8..071cbd4 100644
--- a/service/java/com/android/server/wifi/hotspot2/ANQPRequestManager.java
+++ b/service/java/com/android/server/wifi/hotspot2/ANQPRequestManager.java
@@ -138,6 +138,22 @@
     }
 
     /**
+     * Request Venue URL ANQP-element from the specified AP post connection.
+     *
+     * @param bssid The BSSID of the AP
+     * @param anqpNetworkKey The unique network key associated with this request
+     * @return true if a request was sent successfully
+     */
+    public boolean requestVenueUrlAnqpElement(long bssid, ANQPNetworkKey anqpNetworkKey) {
+        if (!mPasspointHandler.requestVenueUrlAnqp(bssid)) {
+            return false;
+        }
+
+        mPendingQueries.put(bssid, anqpNetworkKey);
+        return true;
+    }
+
+    /**
      * Notification of the completion of an ANQP request.
      *
      * @param bssid The BSSID of the AP
diff --git a/service/java/com/android/server/wifi/hotspot2/AnqpCache.java b/service/java/com/android/server/wifi/hotspot2/AnqpCache.java
index 9a3a1fb..b8a5b53 100644
--- a/service/java/com/android/server/wifi/hotspot2/AnqpCache.java
+++ b/service/java/com/android/server/wifi/hotspot2/AnqpCache.java
@@ -59,6 +59,24 @@
     }
 
     /**
+     * Add or update additional ANQP elements to an ANQP entry associated with a given key. This
+     * method is useful for Venue URL or any other ANQP element which is queried after a connection
+     * is made.
+     *
+     * @param key The key that's associated with the entry
+     * @param anqpElements The additional ANQP elements from the AP, post connection
+     */
+    public void addOrUpdateEntry(ANQPNetworkKey key,
+            Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
+        if (!mANQPCache.containsKey(key)) {
+            // Create a new entry
+            addEntry(key, anqpElements);
+            return;
+        }
+        mANQPCache.get(key).update(anqpElements);
+    }
+
+    /**
      * Get the ANQP data associated with the given AP.
      *
      * @param key The key that's associated with the entry
diff --git a/service/java/com/android/server/wifi/hotspot2/NetworkDetail.java b/service/java/com/android/server/wifi/hotspot2/NetworkDetail.java
index 752cebf..22e8554 100644
--- a/service/java/com/android/server/wifi/hotspot2/NetworkDetail.java
+++ b/service/java/com/android/server/wifi/hotspot2/NetworkDetail.java
@@ -301,7 +301,7 @@
         //set up channel info
         mPrimaryFreq = freq;
         int channelWidth = ScanResult.UNSPECIFIED;
-        int centerFreq0 = 0;
+        int centerFreq0 = mPrimaryFreq;
         int centerFreq1 = 0;
 
         // First check if HE Operation IE is present
@@ -338,6 +338,12 @@
                 centerFreq0 = htOperation.getCenterFreq0(mPrimaryFreq);
             }
         }
+
+        if (channelWidth == ScanResult.UNSPECIFIED) {
+            // Failed to obtain channel info from HE, VHT, HT IEs (possibly a 802.11a/b/g legacy AP)
+            channelWidth = ScanResult.CHANNEL_WIDTH_20MHZ;
+        }
+
         mChannelWidth = channelWidth;
         mCenterfreq0 = centerFreq0;
         mCenterfreq1 = centerFreq1;
diff --git a/service/java/com/android/server/wifi/hotspot2/OsuNetworkConnection.java b/service/java/com/android/server/wifi/hotspot2/OsuNetworkConnection.java
index b3bab64..6fb51a1 100644
--- a/service/java/com/android/server/wifi/hotspot2/OsuNetworkConnection.java
+++ b/service/java/com/android/server/wifi/hotspot2/OsuNetworkConnection.java
@@ -191,11 +191,10 @@
         config.providerFriendlyName = friendlyName;
 
         if (TextUtils.isEmpty(nai)) {
-            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+            config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN);
         } else {
             // Setup OSEN connection with Unauthenticated user TLS and WFA Root certs
-            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.OSEN);
-            config.allowedProtocols.set(WifiConfiguration.Protocol.OSEN);
+            config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OSEN);
             config.enterpriseConfig.setDomainSuffixMatch(nai);
             config.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.UNAUTH_TLS);
             config.enterpriseConfig.setCaPath(WfaKeyStore.DEFAULT_WFA_CERT_DIR);
@@ -233,10 +232,10 @@
     /**
      * Method to update logging level in this class
      *
-     * @param verbose more than 0 enables verbose logging
+     * @param verbose enables verbose logging
      */
-    public void enableVerboseLogging(int verbose) {
-        mVerboseLoggingEnabled = verbose > 0 ? true : false;
+    public void enableVerboseLogging(boolean verbose) {
+        mVerboseLoggingEnabled = verbose;
     }
 
     private class ConnectivityCallbacks extends ConnectivityManager.NetworkCallback {
diff --git a/service/java/com/android/server/wifi/hotspot2/OsuServerConnection.java b/service/java/com/android/server/wifi/hotspot2/OsuServerConnection.java
index f9c9df1..31d8ba4 100644
--- a/service/java/com/android/server/wifi/hotspot2/OsuServerConnection.java
+++ b/service/java/com/android/server/wifi/hotspot2/OsuServerConnection.java
@@ -158,10 +158,10 @@
     /**
      * Enables verbose logging
      *
-     * @param verbose a value greater than zero enables verbose logging
+     * @param verbose enables verbose logging
      */
-    public void enableVerboseLogging(int verbose) {
-        mVerboseLoggingEnabled = verbose > 0 ? true : false;
+    public void enableVerboseLogging(boolean verbose) {
+        mVerboseLoggingEnabled = verbose;
     }
 
     /**
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointConfigUserStoreData.java b/service/java/com/android/server/wifi/hotspot2/PasspointConfigUserStoreData.java
index 8400bb5..f642196 100644
--- a/service/java/com/android/server/wifi/hotspot2/PasspointConfigUserStoreData.java
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointConfigUserStoreData.java
@@ -21,6 +21,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.server.wifi.Clock;
 import com.android.server.wifi.WifiCarrierInfoManager;
 import com.android.server.wifi.WifiConfigStore;
 import com.android.server.wifi.WifiKeyStore;
@@ -75,10 +76,13 @@
     private static final String XML_TAG_HAS_EVER_CONNECTED = "HasEverConnected";
     private static final String XML_TAG_IS_FROM_SUGGESTION = "IsFromSuggestion";
     private static final String XML_TAG_IS_TRUSTED = "IsTrusted";
+    private static final String XML_TAG_CONNECT_CHOICE = "ConnectChoice";
+    private static final String XML_TAG_CONNECT_CHOICE_RSSI = "ConnectChoiceRssi";
 
     private final WifiKeyStore mKeyStore;
     private final WifiCarrierInfoManager mWifiCarrierInfoManager;
     private final DataSource mDataSource;
+    private final Clock mClock;
 
     /**
      * Interface define the data source for the Passpoint configuration store data.
@@ -100,10 +104,11 @@
     }
 
     PasspointConfigUserStoreData(WifiKeyStore keyStore,
-            WifiCarrierInfoManager wifiCarrierInfoManager, DataSource dataSource) {
+            WifiCarrierInfoManager wifiCarrierInfoManager, DataSource dataSource, Clock clock) {
         mKeyStore = keyStore;
         mWifiCarrierInfoManager = wifiCarrierInfoManager;
         mDataSource = dataSource;
+        mClock = clock;
     }
 
     @Override
@@ -204,6 +209,8 @@
         XmlUtil.writeNextValue(out, XML_TAG_HAS_EVER_CONNECTED, provider.getHasEverConnected());
         XmlUtil.writeNextValue(out, XML_TAG_IS_FROM_SUGGESTION, provider.isFromSuggestion());
         XmlUtil.writeNextValue(out, XML_TAG_IS_TRUSTED, provider.isTrusted());
+        XmlUtil.writeNextValue(out, XML_TAG_CONNECT_CHOICE, provider.getConnectChoice());
+        XmlUtil.writeNextValue(out, XML_TAG_CONNECT_CHOICE_RSSI, provider.getConnectChoiceRssi());
         if (provider.getConfig() != null) {
             XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_PASSPOINT_CONFIGURATION);
             PasspointXmlUtils.serializePasspointConfiguration(out, provider.getConfig());
@@ -279,6 +286,8 @@
         boolean shared = false;
         boolean isTrusted = true;
         PasspointConfiguration config = null;
+        String connectChoice = null;
+        int connectChoiceRssi = 0;
         while (XmlUtil.nextElementWithin(in, outerTagDepth)) {
             if (in.getAttributeValue(null, "name") != null) {
                 // Value elements.
@@ -317,6 +326,12 @@
                     case XML_TAG_IS_TRUSTED:
                         isTrusted = (boolean) value;
                         break;
+                    case XML_TAG_CONNECT_CHOICE:
+                        connectChoice = (String) value;
+                        break;
+                    case XML_TAG_CONNECT_CHOICE_RSSI:
+                        connectChoiceRssi = (int) value;
+                        break;
                     default:
                         Log.w(TAG, "Ignoring unknown value name found " + name[0]);
                         break;
@@ -352,7 +367,8 @@
                 mWifiCarrierInfoManager,
                 providerId, creatorUid, packageName, isFromSuggestion, caCertificateAliases,
                 clientPrivateKeyAndCertificateAlias, remediationCaCertificateAlias,
-                hasEverConnected, shared);
+                hasEverConnected, shared, mClock);
+        provider.setUserConnectChoice(connectChoice, connectChoiceRssi);
         if (isFromSuggestion) {
             provider.setTrusted(isTrusted);
         }
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointEventHandler.java b/service/java/com/android/server/wifi/hotspot2/PasspointEventHandler.java
index 3815c30..0d82947 100644
--- a/service/java/com/android/server/wifi/hotspot2/PasspointEventHandler.java
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointEventHandler.java
@@ -19,7 +19,7 @@
 import android.util.Log;
 import android.util.Pair;
 
-import com.android.server.wifi.WifiNative;
+import com.android.server.wifi.WifiInjector;
 import com.android.server.wifi.hotspot2.anqp.ANQPElement;
 import com.android.server.wifi.hotspot2.anqp.Constants;
 
@@ -34,7 +34,7 @@
  * event notifications.
  */
 public class PasspointEventHandler {
-    private final WifiNative mSupplicantHook;
+    private final WifiInjector mWifiInjector;
     private final Callbacks mCallbacks;
 
     /**
@@ -66,8 +66,8 @@
         void onWnmFrameReceived(WnmData data);
     }
 
-    public PasspointEventHandler(WifiNative supplicantHook, Callbacks callbacks) {
-        mSupplicantHook = supplicantHook;
+    public PasspointEventHandler(WifiInjector wifiInjector, Callbacks callbacks) {
+        mWifiInjector = wifiInjector;
         mCallbacks = callbacks;
     }
 
@@ -80,8 +80,7 @@
     public boolean requestANQP(long bssid, List<Constants.ANQPElementType> elements) {
         Pair<Set<Integer>, Set<Integer>> querySets = buildAnqpIdSet(elements);
         if (bssid == 0 || querySets == null) return false;
-        if (!mSupplicantHook.requestAnqp(
-                mSupplicantHook.getClientInterfaceName(),
+        if (!mWifiInjector.getActiveModeWarden().getPrimaryClientModeManager().requestAnqp(
                 Utils.macToString(bssid), querySets.first, querySets.second)) {
             Log.d(Utils.hs2LogTag(getClass()), "ANQP failed on " + Utils.macToString(bssid));
             return false;
@@ -91,6 +90,17 @@
     }
 
     /**
+     * Request the Venue URL ANQP element from the specified AP |bssid|.
+     * @param bssid BSSID of the AP
+     * @return true if request is sent successfully, false otherwise
+     */
+    public boolean requestVenueUrlAnqp(long bssid) {
+        if (bssid == 0) return false;
+        return mWifiInjector.getActiveModeWarden().getPrimaryClientModeManager()
+                .requestVenueUrlAnqp(Utils.macToString(bssid));
+    }
+
+    /**
      * Request a passpoint icon file |filename| from the specified AP |bssid|.
      * @param bssid BSSID of the AP
      * @param fileName name of the icon file
@@ -98,8 +108,8 @@
      */
     public boolean requestIcon(long bssid, String fileName) {
         if (bssid == 0 || fileName == null) return false;
-        return mSupplicantHook.requestIcon(
-                mSupplicantHook.getClientInterfaceName(), Utils.macToString(bssid), fileName);
+        return mWifiInjector.getActiveModeWarden().getPrimaryClientModeManager()
+                .requestIcon(Utils.macToString(bssid), fileName);
     }
 
     /**
@@ -128,8 +138,7 @@
 
     /**
      * Invoked when a Wireless Network Management (WNM) frame is received.
-     * TODO(zqiu): currently WNM frame notification is through WifiMonitor,
-     * this shouldn't be needed once we switch over to wificond for WNM frame monitoring.
+     *
      * @param data WNM frame data
      */
     public void notifyWnmFrameReceived(WnmData data) {
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointManager.java b/service/java/com/android/server/wifi/hotspot2/PasspointManager.java
index d398759..8d5a1d7 100644
--- a/service/java/com/android/server/wifi/hotspot2/PasspointManager.java
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointManager.java
@@ -23,6 +23,7 @@
 import static java.security.cert.PKIXReason.NO_TRUST_ANCHOR;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.net.MacAddress;
@@ -41,6 +42,8 @@
 import android.util.Log;
 import android.util.Pair;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.Clock;
 import com.android.server.wifi.MacAddressUtil;
 import com.android.server.wifi.NetworkUpdateResult;
@@ -54,13 +57,17 @@
 import com.android.server.wifi.hotspot2.anqp.ANQPElement;
 import com.android.server.wifi.hotspot2.anqp.Constants;
 import com.android.server.wifi.hotspot2.anqp.HSOsuProvidersElement;
+import com.android.server.wifi.hotspot2.anqp.I18Name;
 import com.android.server.wifi.hotspot2.anqp.OsuProviderInfo;
+import com.android.server.wifi.hotspot2.anqp.VenueNameElement;
+import com.android.server.wifi.hotspot2.anqp.VenueUrlElement;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.UserActionEvent;
 import com.android.server.wifi.util.InformationElementUtil;
 import com.android.server.wifi.util.WifiPermissionsUtil;
 
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.net.URL;
 import java.security.GeneralSecurityException;
 import java.security.KeyStore;
 import java.security.cert.CertPath;
@@ -75,6 +82,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -123,6 +131,7 @@
     private final AppOpsManager mAppOps;
     private final WifiCarrierInfoManager mWifiCarrierInfoManager;
     private final MacAddressUtil mMacAddressUtil;
+    private final Clock mClock;
     private final WifiPermissionsUtil mWifiPermissionsUtil;
 
     /**
@@ -145,7 +154,11 @@
                 Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
             if (mVerboseLoggingEnabled) {
                 Log.d(TAG, "ANQP response received from BSSID "
-                        + Utils.macToString(bssid));
+                        + Utils.macToString(bssid) + " - List of ANQP elements:");
+                int i = 0;
+                for (Constants.ANQPElementType type : anqpElements.keySet()) {
+                    Log.d(TAG, "#" + i++ + ": " + type);
+                }
             }
             // Notify request manager for the completion of a request.
             ANQPNetworkKey anqpKey =
@@ -156,8 +169,13 @@
                 return;
             }
 
+            if (anqpElements.containsKey(Constants.ANQPElementType.ANQPVenueUrl)) {
+                // Venue URL ANQP is requested and received only after the network is connected
+                mWifiMetrics.incrementTotalNumberOfPasspointConnectionsWithVenueUrl();
+            }
+
             // Add new entry to the cache.
-            mAnqpCache.addEntry(anqpKey, anqpElements);
+            mAnqpCache.addOrUpdateEntry(anqpKey, anqpElements);
         }
 
         @Override
@@ -189,7 +207,7 @@
         public void setProviders(List<PasspointProvider> providers) {
             mProviders.clear();
             for (PasspointProvider provider : providers) {
-                provider.enableVerboseLogging(mVerboseLoggingEnabled ? 1 : 0);
+                provider.enableVerboseLogging(mVerboseLoggingEnabled);
                 mProviders.put(provider.getConfig().getUniqueId(), provider);
                 if (provider.getPackageName() != null) {
                     startTrackingAppOpsChange(provider.getPackageName(),
@@ -251,6 +269,44 @@
         }
     }
 
+    private class OnNetworkUpdateListener implements
+            WifiConfigManager.OnNetworkUpdateListener {
+        @Override
+        public void onConnectChoiceSet(@NonNull List<WifiConfiguration> networks,
+                String choiceKey, int rssi) {
+            onUserConnectChoiceSet(networks, choiceKey, rssi);
+        }
+        @Override
+        public void onConnectChoiceRemoved(String choiceKey) {
+            onUserConnectChoiceRemove(choiceKey);
+        }
+
+    }
+
+    private void onUserConnectChoiceRemove(String choiceKey) {
+        mProviders.values().stream()
+                .filter(provider -> TextUtils.equals(provider.getConnectChoice(), choiceKey))
+                .forEach(provider -> {
+                    provider.setUserConnectChoice(null, 0);
+                });
+        mWifiConfigManager.saveToStore(true);
+    }
+
+    private void onUserConnectChoiceSet(List<WifiConfiguration> networks, String choiceKey,
+            int rssi) {
+        for (WifiConfiguration config : networks) {
+            PasspointProvider provider = mProviders.get(config.getProfileKey());
+            if (provider != null) {
+                provider.setUserConnectChoice(choiceKey, rssi);
+            }
+        }
+        PasspointProvider provider = mProviders.get(choiceKey);
+        if (provider != null) {
+            provider.setUserConnectChoice(null, 0);
+        }
+        mWifiConfigManager.saveToStore(true);
+    }
+
     /**
      * Remove all Passpoint profiles installed by the app that has been disabled or uninstalled.
      *
@@ -262,7 +318,6 @@
                 packageName).entrySet()) {
             String uniqueId = entry.getValue().getConfig().getUniqueId();
             removeProvider(Process.WIFI_UID /* ignored */, true, uniqueId, null);
-            disconnectIfPasspointNetwork(uniqueId);
         }
     }
 
@@ -292,19 +347,6 @@
         mAppOps.stopWatchingMode(appOpsChangedListener);
     }
 
-    private void disconnectIfPasspointNetwork(String uniqueId) {
-        WifiConfiguration currentConfiguration =
-                mWifiInjector.getClientModeImpl().getCurrentWifiConfiguration();
-        if (currentConfiguration == null) return;
-        if (currentConfiguration.isPasspoint() && TextUtils.equals(currentConfiguration.getKey(),
-                uniqueId)) {
-            Log.i(TAG, "Disconnect current Passpoint network for FQDN: "
-                    + currentConfiguration.FQDN + " and ID: " + uniqueId
-                    + " because the profile was removed");
-            mWifiInjector.getClientModeImpl().disconnectCommand();
-        }
-    }
-
     public PasspointManager(Context context, WifiInjector wifiInjector, Handler handler,
             WifiNative wifiNative, WifiKeyStore keyStore, Clock clock,
             PasspointObjectFactory objectFactory, WifiConfigManager wifiConfigManager,
@@ -313,7 +355,7 @@
             WifiCarrierInfoManager wifiCarrierInfoManager,
             MacAddressUtil macAddressUtil,
             WifiPermissionsUtil wifiPermissionsUtil) {
-        mPasspointEventHandler = objectFactory.makePasspointEventHandler(wifiNative,
+        mPasspointEventHandler = objectFactory.makePasspointEventHandler(wifiInjector,
                 new CallbackHandler(context));
         mWifiInjector = wifiInjector;
         mHandler = handler;
@@ -327,7 +369,7 @@
         mProviderIndex = 0;
         mWifiCarrierInfoManager = wifiCarrierInfoManager;
         wifiConfigStore.registerStoreData(objectFactory.makePasspointConfigUserStoreData(
-                mKeyStore, mWifiCarrierInfoManager, new UserDataSourceHandler()));
+                mKeyStore, mWifiCarrierInfoManager, new UserDataSourceHandler(), clock));
         wifiConfigStore.registerStoreData(objectFactory.makePasspointConfigSharedStoreData(
                 new SharedDataSourceHandler()));
         mPasspointProvisioner = objectFactory.makePasspointProvisioner(context, wifiNative,
@@ -335,6 +377,9 @@
         mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
         sPasspointManager = this;
         mMacAddressUtil = macAddressUtil;
+        mClock = clock;
+        mWifiConfigManager.addOnNetworkUpdateListener(
+                new PasspointManager.OnNetworkUpdateListener());
         mWifiPermissionsUtil = wifiPermissionsUtil;
     }
 
@@ -349,10 +394,10 @@
 
     /**
      * Enable verbose logging
-     * @param verbose more than 0 enables verbose logging
+     * @param verbose enables verbose logging
      */
-    public void enableVerboseLogging(int verbose) {
-        mVerboseLoggingEnabled = (verbose > 0) ? true : false;
+    public void enableVerboseLogging(boolean verbose) {
+        mVerboseLoggingEnabled = verbose;
         mPasspointProvisioner.enableVerboseLogging(verbose);
         for (PasspointProvider provider : mProviders.values()) {
             provider.enableVerboseLogging(verbose);
@@ -362,7 +407,7 @@
     private void updateWifiConfigInWcmIfPresent(
             WifiConfiguration newConfig, int uid, String packageName, boolean isFromSuggestion) {
         WifiConfiguration configInWcm =
-                mWifiConfigManager.getConfiguredNetwork(newConfig.getKey());
+                mWifiConfigManager.getConfiguredNetwork(newConfig.getProfileKey());
         if (configInWcm == null) return;
         // suggestion != saved
         if (isFromSuggestion != configInWcm.fromWifiNetworkSuggestion) return;
@@ -424,7 +469,8 @@
         mWifiCarrierInfoManager.tryUpdateCarrierIdForPasspoint(config);
         // Create a provider and install the necessary certificates and keys.
         PasspointProvider newProvider = mObjectFactory.makePasspointProvider(config, mKeyStore,
-                mWifiCarrierInfoManager, mProviderIndex++, uid, packageName, isFromSuggestion);
+                mWifiCarrierInfoManager, mProviderIndex++, uid, packageName, isFromSuggestion,
+                mClock);
         newProvider.setTrusted(isTrusted);
 
         boolean metricsNoRootCa = false;
@@ -474,17 +520,20 @@
                     + " and unique ID: " + config.getUniqueId());
             old.uninstallCertsAndKeys();
             mProviders.remove(config.getUniqueId());
+            // Keep the user connect choice and AnonymousIdentity
+            newProvider.setUserConnectChoice(old.getConnectChoice(), old.getConnectChoiceRssi());
+            newProvider.setAnonymousIdentity(old.getAnonymousIdentity());
             // New profile changes the credential, remove the related WifiConfig.
             if (!old.equals(newProvider)) {
                 mWifiConfigManager.removePasspointConfiguredNetwork(
-                        newProvider.getWifiConfig().getKey());
+                        newProvider.getWifiConfig().getProfileKey());
             } else {
                 // If there is a config cached in WifiConfigManager, update it with new info.
                 updateWifiConfigInWcmIfPresent(
                         newProvider.getWifiConfig(), uid, packageName, isFromSuggestion);
             }
         }
-        newProvider.enableVerboseLogging(mVerboseLoggingEnabled ? 1 : 0);
+        newProvider.enableVerboseLogging(mVerboseLoggingEnabled);
         mProviders.put(config.getUniqueId(), newProvider);
         mWifiConfigManager.saveToStore(true /* forceWrite */);
         if (!isFromSuggestion && newProvider.getPackageName() != null) {
@@ -502,6 +551,9 @@
         if (metricsSubscriptionExpiration) {
             mWifiMetrics.incrementNumPasspointProviderWithSubscriptionExpiration();
         }
+        if (SdkLevel.isAtLeastS() && config.getDecoratedIdentityPrefix() != null) {
+            mWifiMetrics.incrementTotalNumberOfPasspointProfilesWithDecoratedIdentity();
+        }
         mWifiMetrics.incrementNumPasspointProviderInstallSuccess();
         return true;
     }
@@ -521,9 +573,10 @@
         String packageName = provider.getPackageName();
         // Remove any configs corresponding to the profile in WifiConfigManager.
         mWifiConfigManager.removePasspointConfiguredNetwork(
-                provider.getWifiConfig().getKey());
+                provider.getWifiConfig().getProfileKey());
         String uniqueId = provider.getConfig().getUniqueId();
         mProviders.remove(uniqueId);
+        mWifiConfigManager.removeConnectChoiceFromAllNetworks(uniqueId);
         mWifiConfigManager.saveToStore(true /* forceWrite */);
 
         // Stop monitoring the package if there is no Passpoint profile installed by the package
@@ -614,7 +667,11 @@
                                 ? UserActionEvent.EVENT_CONFIGURE_AUTO_CONNECT_ON
                                 : UserActionEvent.EVENT_CONFIGURE_AUTO_CONNECT_OFF,
                         provider.isFromSuggestion(), true);
+                // Update WifiConfigManager if changed.
+                updateWifiConfigInWcmIfPresent(provider.getWifiConfig(), provider.getCreatorUid(),
+                        provider.getPackageName(), provider.isFromSuggestion());
             }
+
             mWifiConfigManager.saveToStore(true);
             return true;
         }
@@ -630,6 +687,10 @@
                                     ? UserActionEvent.EVENT_CONFIGURE_AUTO_CONNECT_ON
                                     : UserActionEvent.EVENT_CONFIGURE_AUTO_CONNECT_OFF,
                             provider.isFromSuggestion(), true);
+                    // Update WifiConfigManager if changed.
+                    updateWifiConfigInWcmIfPresent(provider.getWifiConfig(),
+                            provider.getCreatorUid(), provider.getPackageName(),
+                            provider.isFromSuggestion());
                 }
                 found = true;
             }
@@ -660,7 +721,7 @@
                                     : UserActionEvent.EVENT_CONFIGURE_MAC_RANDOMIZATION_OFF,
                             provider.isFromSuggestion(), true);
                     mWifiConfigManager.removePasspointConfiguredNetwork(
-                            provider.getWifiConfig().getKey());
+                            provider.getWifiConfig().getProfileKey());
                 }
                 found = true;
             }
@@ -850,7 +911,7 @@
                         + anqpEntry.getElements().get(Constants.ANQPElementType.ANQPDomName));
             }
             PasspointMatch matchStatus = provider.match(anqpEntry.getElements(),
-                    roamingConsortium);
+                    roamingConsortium, scanResult);
             if (matchStatus == PasspointMatch.HomeProvider
                     || matchStatus == PasspointMatch.RoamingProvider) {
                 allMatches.add(Pair.create(provider, matchStatus));
@@ -921,8 +982,6 @@
 
     /**
      * Notify the reception of a Wireless Network Management (WNM) frame.
-     * TODO(zqiu): currently the notification is done through WifiMonitor,
-     * will no longer be the case once we switch over to use wificond.
      */
     public void receivedWnmFrame(WnmData data) {
         mPasspointEventHandler.notifyWnmFrameReceived(data);
@@ -991,16 +1050,10 @@
                     type = WifiManager.PASSPOINT_ROAMING_NETWORK;
                 }
                 Map<Integer, List<ScanResult>> scanResultsPerNetworkType =
-                        configs.get(config.getKey());
-                if (scanResultsPerNetworkType == null) {
-                    scanResultsPerNetworkType = new HashMap<>();
-                    configs.put(config.getKey(), scanResultsPerNetworkType);
-                }
-                List<ScanResult> matchingScanResults = scanResultsPerNetworkType.get(type);
-                if (matchingScanResults == null) {
-                    matchingScanResults = new ArrayList<>();
-                    scanResultsPerNetworkType.put(type, matchingScanResults);
-                }
+                        configs.computeIfAbsent(config.getProfileKey(),
+                                k -> new HashMap<>());
+                List<ScanResult> matchingScanResults = scanResultsPerNetworkType.computeIfAbsent(
+                        type, k -> new ArrayList<>());
                 matchingScanResults.add(scanResult);
             }
         }
@@ -1093,8 +1146,14 @@
     }
 
     /**
-     * Returns the corresponding wifi configurations for given a list Passpoint profile unique
-     * identifiers.
+     * Returns the corresponding wifi configurations from {@link WifiConfigManager} for given a list
+     * of Passpoint profile unique identifiers.
+     *
+     * Note: Not all matched Passpoint profile's WifiConfiguration will be returned, only the ones
+     * already be added into the {@link WifiConfigManager} will be returned. As the returns of this
+     * method is expected to show in Wifi Picker or use with
+     * {@link WifiManager#connect(int, WifiManager.ActionListener)} API, each WifiConfiguration must
+     * have a valid network Id.
      *
      * An empty list will be returned when no match is found.
      *
@@ -1114,14 +1173,9 @@
                 continue;
             }
             WifiConfiguration config = provider.getWifiConfig();
-            if (mWifiConfigManager.shouldUseAggressiveRandomization(config)) {
-                config.setRandomizedMacAddress(MacAddress.fromString(DEFAULT_MAC_ADDRESS));
-            } else {
-                MacAddress result = mMacAddressUtil.calculatePersistentMac(config.getKey(),
-                        mMacAddressUtil.obtainMacRandHashFunction(Process.WIFI_UID));
-                if (result != null) {
-                    config.setRandomizedMacAddress(result);
-                }
+            config = mWifiConfigManager.getConfiguredNetwork(config.getProfileKey());
+            if (config == null) {
+                continue;
             }
             // If the Passpoint configuration is from a suggestion, check if the app shares this
             // suggestion with the user.
@@ -1130,6 +1184,15 @@
                     .isPasspointSuggestionSharedWithUser(config)) {
                 continue;
             }
+            if (mWifiConfigManager.shouldUseEnhancedRandomization(config)) {
+                config.setRandomizedMacAddress(MacAddress.fromString(DEFAULT_MAC_ADDRESS));
+            } else {
+                MacAddress result = mMacAddressUtil.calculatePersistentMac(config.getNetworkKey(),
+                        mMacAddressUtil.obtainMacRandHashFunction(Process.WIFI_UID));
+                if (result != null) {
+                    config.setRandomizedMacAddress(result);
+                }
+            }
             configs.add(config);
         }
         return configs;
@@ -1231,8 +1294,8 @@
                 mWifiCarrierInfoManager,
                 mProviderIndex++, wifiConfig.creatorUid, null, false,
                 Arrays.asList(enterpriseConfig.getCaCertificateAlias()),
-                enterpriseConfig.getClientCertificateAlias(), null, false, false);
-        provider.enableVerboseLogging(mVerboseLoggingEnabled ? 1 : 0);
+                enterpriseConfig.getClientCertificateAlias(), null, false, false, mClock);
+        provider.enableVerboseLogging(mVerboseLoggingEnabled);
         mProviders.put(passpointConfig.getUniqueId(), provider);
         return true;
     }
@@ -1283,11 +1346,12 @@
             @NonNull PasspointConfiguration passpointConfiguration,
             @NonNull List<ScanResult> scanResults) {
         PasspointProvider provider = mObjectFactory.makePasspointProvider(passpointConfiguration,
-                null, mWifiCarrierInfoManager, 0, 0, null, false);
+                null, mWifiCarrierInfoManager, 0, 0, null, false, mClock);
         List<ScanResult> filteredScanResults = new ArrayList<>();
         for (ScanResult scanResult : scanResults) {
             PasspointMatch matchInfo = provider.match(getANQPElements(scanResult),
-                    InformationElementUtil.getRoamingConsortiumIE(scanResult.informationElements));
+                    InformationElementUtil.getRoamingConsortiumIE(scanResult.informationElements),
+                    scanResult);
             if (matchInfo == PasspointMatch.HomeProvider
                     || matchInfo == PasspointMatch.RoamingProvider) {
                 filteredScanResults.add(scanResult);
@@ -1312,6 +1376,27 @@
     public void clearAnqpRequestsAndFlushCache() {
         mAnqpRequestManager.clear();
         mAnqpCache.flush();
+        mProviders.values().stream().forEach(p -> p.clearProviderBlock());
+    }
+
+    private PKIXParameters mInjectedPKIXParameters;
+    private boolean mUseInjectedPKIX = false;
+
+
+    /**
+     * Used to speedup unit test.
+     */
+    @VisibleForTesting
+    public void injectPKIXParameters(PKIXParameters params) {
+        mInjectedPKIXParameters = params;
+    }
+
+    /**
+     * Used to speedup unit test.
+     */
+    @VisibleForTesting
+    public void setUseInjectedPKIX(boolean value) {
+        mUseInjectedPKIX = value;
     }
 
     /**
@@ -1328,10 +1413,194 @@
         CertPathValidator validator =
                 CertPathValidator.getInstance(CertPathValidator.getDefaultType());
         CertPath path = factory.generateCertPath(Arrays.asList(caCert));
-        KeyStore ks = KeyStore.getInstance("AndroidCAStore");
-        ks.load(null, null);
-        PKIXParameters params = new PKIXParameters(ks);
-        params.setRevocationEnabled(false);
+        PKIXParameters params;
+        if (mUseInjectedPKIX) {
+            params = mInjectedPKIXParameters;
+        } else {
+            KeyStore ks = KeyStore.getInstance("AndroidCAStore");
+            ks.load(null, null);
+            params = new PKIXParameters(ks);
+            params.setRevocationEnabled(false);
+        }
         validator.validate(path, params);
     }
+
+    /**
+     * Request the Venue URL ANQP-element from the AP post connection
+     *
+     * @param scanResult Scan result associated to the requested AP
+     */
+    public void requestVenueUrlAnqpElement(@NonNull ScanResult scanResult) {
+        long bssid;
+        try {
+            bssid = Utils.parseMac(scanResult.BSSID);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID);
+            return;
+        }
+        InformationElementUtil.Vsa vsa = InformationElementUtil.getHS2VendorSpecificIE(
+                scanResult.informationElements);
+        ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(scanResult.SSID, bssid, scanResult.hessid,
+                vsa.anqpDomainID);
+        // TODO(haishalom@): Should we limit to R3 only? vsa.hsRelease > NetworkDetail.HSRelease.R2
+        // I am seeing R2's that respond to Venue URL request, so may keep it this way.
+        // APs that do not support this ANQP request simply ignore it.
+        mAnqpRequestManager.requestVenueUrlAnqpElement(bssid, anqpKey);
+    }
+
+    /**
+     * Get the Venue URL associated to the scan result, matched to the system language. If no
+     * Venue URL matches the system language, then entry number one is returned, which is considered
+     * to be the venue's default language.
+     *
+     * @param scanResult Scan result
+     * @return The Venue URL associated to the scan result or null if not found
+     */
+    @Nullable
+    public URL getVenueUrl(@NonNull ScanResult scanResult) {
+        long bssid;
+        try {
+            bssid = Utils.parseMac(scanResult.BSSID);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID);
+            return null;
+        }
+        InformationElementUtil.Vsa vsa = InformationElementUtil.getHS2VendorSpecificIE(
+                scanResult.informationElements);
+        ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(scanResult.SSID, bssid, scanResult.hessid,
+                vsa.anqpDomainID);
+        ANQPData anqpEntry = mAnqpCache.getEntry(anqpKey);
+        if (anqpEntry == null) {
+            return null;
+        }
+        VenueUrlElement venueUrlElement = (VenueUrlElement)
+                anqpEntry.getElements().get(Constants.ANQPElementType.ANQPVenueUrl);
+        if (venueUrlElement == null || venueUrlElement.getVenueUrls().isEmpty()) {
+            return null; // No Venue URL
+        }
+        VenueNameElement venueNameElement = (VenueNameElement)
+                anqpEntry.getElements().get(Constants.ANQPElementType.ANQPVenueName);
+        if (venueNameElement == null
+                || venueUrlElement.getVenueUrls().size() != venueNameElement.getNames().size()) {
+            Log.w(TAG, "Venue name list size mismatches the Venue URL list size");
+            return null; // No match between Venue names Venue URLs
+        }
+
+        // Find the Venue URL that matches the system language. Venue URLs are ordered by venue
+        // names.
+        Locale locale = Locale.getDefault();
+        URL venueUrl = null;
+        int index = 1;
+        for (I18Name venueName : venueNameElement.getNames()) {
+            if (venueName.getLanguage().equals(locale.getISO3Language())) {
+                venueUrl = venueUrlElement.getVenueUrls().get(index);
+                break;
+            }
+            index++;
+        }
+
+        // If no venue URL for the system language is available, use entry number one
+        if (venueUrl == null) {
+            venueUrl = venueUrlElement.getVenueUrls().get(1);
+        }
+
+        if (mVerboseLoggingEnabled) {
+            Log.d(TAG, "Venue URL to display (language = " + locale.getDisplayLanguage()
+                    + "): " + (venueUrl != null ? venueUrl : "None"));
+        }
+        return venueUrl;
+    }
+
+    /**
+     * Handle Deauthentication Imminent WNM-Notification event
+     *
+     * @param event Deauthentication Imminent WNM-Notification data
+     * @param config Configuration of the currently connected network
+     */
+    public void handleDeauthImminentEvent(WnmData event, WifiConfiguration config) {
+        if (event == null || config == null) {
+            return;
+        }
+
+        blockProvider(config.getProfileKey(), event.getBssid(), event.isEss(),
+                event.getDelay());
+        mWifiMetrics.incrementPasspointDeauthImminentScope(event.isEss());
+    }
+
+    /**
+     * Block a specific provider from network selection
+     *
+     * @param passpointUniqueId The unique ID of the Passpoint network
+     * @param bssid BSSID of the AP
+     * @param isEss Block the ESS or the BSS
+     * @param delay Delay in seconds
+     */
+    private void blockProvider(String passpointUniqueId, long bssid, boolean isEss, int delay) {
+        PasspointProvider provider = mProviders.get(passpointUniqueId);
+        if (provider != null) {
+            provider.blockBssOrEss(bssid, isEss, delay);
+        }
+    }
+
+    /**
+     * Store the AnonymousIdentity for passpoint after connection.
+     */
+    public void setAnonymousIdentity(WifiConfiguration configuration) {
+        if (!configuration.isPasspoint()) {
+            return;
+        }
+        PasspointProvider provider = mProviders.get(configuration.getProfileKey());
+        if (provider != null) {
+            provider.setAnonymousIdentity(configuration.enterpriseConfig.getAnonymousIdentity());
+            mWifiConfigManager.saveToStore(true);
+        }
+    }
+
+    /**
+     * Resets all sim networks state.
+     */
+    public void resetSimPasspointNetwork() {
+        mProviders.values().stream().forEach(p -> p.setAnonymousIdentity(null));
+        mWifiConfigManager.saveToStore(true);
+    }
+
+    /**
+     * Handle Terms & Conditions acceptance required WNM-Notification event
+     *
+     * @param event Terms & Conditions WNM-Notification data
+     * @param config Configuration of the currently connected Passpoint network
+     *
+     * @return The Terms & conditions URL if it is valid, null otherwise
+     */
+    public URL handleTermsAndConditionsEvent(WnmData event, WifiConfiguration config) {
+        if (event == null || config == null || !config.isPasspoint()) {
+            return null;
+        }
+        final int oneHourInSeconds = 60 * 60;
+        final int twentyFourHoursInSeconds = 24 * 60 * 60;
+        final URL termsAndConditionsUrl;
+        try {
+            termsAndConditionsUrl = new URL(event.getUrl());
+        } catch (java.net.MalformedURLException e) {
+            Log.e(TAG, "Malformed Terms and Conditions URL: " + event.getUrl()
+                    + " from BSSID: " + Utils.macToString(event.getBssid()));
+
+            // Block this provider for an hour, this unlikely issue may be resolved shortly
+            blockProvider(config.getProfileKey(), event.getBssid(), true, oneHourInSeconds);
+            return null;
+        }
+        // Reject URLs that are not HTTPS
+        if (!TextUtils.equals(termsAndConditionsUrl.getProtocol(), "https")) {
+            Log.e(TAG, "Non-HTTPS Terms and Conditions URL rejected: " + termsAndConditionsUrl
+                    + " from BSSID: " + Utils.macToString(event.getBssid()));
+
+            // Block this provider for 24 hours, it is unlikely to be changed
+            blockProvider(config.getProfileKey(), event.getBssid(), true,
+                    twentyFourHoursInSeconds);
+            return null;
+        }
+        Log.i(TAG, "Captive network, Terms and Conditions URL: " + termsAndConditionsUrl
+                + " from BSSID: " + Utils.macToString(event.getBssid()));
+        return termsAndConditionsUrl;
+    }
 }
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointNetworkNominateHelper.java b/service/java/com/android/server/wifi/hotspot2/PasspointNetworkNominateHelper.java
index bf2e460..a4d7e83 100644
--- a/service/java/com/android/server/wifi/hotspot2/PasspointNetworkNominateHelper.java
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointNetworkNominateHelper.java
@@ -87,8 +87,7 @@
                 // If scanDetail is not Passpoint network, ignore.
                 continue;
             }
-            if (!scanDetail.getNetworkDetail().isInternet()
-                    || isApWanLinkStatusDown(scanDetail)) {
+            if (isApWanLinkStatusDown(scanDetail)) {
                 // If scanDetail has no internet connection, ignore.
                 mLocalLog.log("Ignoring no internet connection Passpoint AP: "
                         + WifiNetworkSelector.toScanId(scanDetail.getScanResult()));
@@ -117,7 +116,13 @@
         if (wm == null) {
             return false;
         }
-        return wm.getStatus() != HSWanMetricsElement.LINK_STATUS_UP || wm.isCapped();
+
+        // Check if the WAN Metrics ANQP element is initialized with values other than 0's
+        if (!wm.isElementInitialized()) {
+            // WAN Metrics ANQP element is not initialized in this network. Ignore it.
+            return false;
+        }
+        return wm.getStatus() != HSWanMetricsElement.LINK_STATUS_UP || wm.isAtCapacity();
     }
 
     /**
@@ -160,6 +165,10 @@
                 if (config == null) {
                     continue;
                 }
+                if (mWifiConfigManager.isNonCarrierMergedNetworkTemporarilyDisabled(config)) {
+                    mLocalLog.log("Ignoring non-carrier-merged SSID: " + config.FQDN);
+                    continue;
+                }
                 if (mWifiConfigManager.isNetworkTemporarilyDisabledByUser(config.FQDN)) {
                     mLocalLog.log("Ignoring user disabled FQDN: " + config.FQDN);
                     continue;
@@ -186,7 +195,7 @@
             config.meteredHint = true;
         }
         WifiConfiguration existingNetwork = mWifiConfigManager.getConfiguredNetwork(
-                config.getKey());
+                config.getProfileKey());
         if (existingNetwork != null) {
             WifiConfiguration.NetworkSelectionStatus status =
                     existingNetwork.getNetworkSelectionStatus();
@@ -211,7 +220,7 @@
         mWifiConfigManager.allowAutojoin(result.getNetworkId(), config.allowAutojoin);
         mWifiConfigManager.enableNetwork(result.getNetworkId(), false, Process.WIFI_UID, null);
         mWifiConfigManager.setNetworkCandidateScanResult(result.getNetworkId(),
-                candidate.mScanDetail.getScanResult(), 0);
+                candidate.mScanDetail.getScanResult(), 0, null);
         mWifiConfigManager.updateScanDetailForNetwork(
                 result.getNetworkId(), candidate.mScanDetail);
         return mWifiConfigManager.getConfiguredNetwork(result.getNetworkId());
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointObjectFactory.java b/service/java/com/android/server/wifi/hotspot2/PasspointObjectFactory.java
index 2ce1b1a..7ad464d 100644
--- a/service/java/com/android/server/wifi/hotspot2/PasspointObjectFactory.java
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointObjectFactory.java
@@ -21,6 +21,7 @@
 
 import com.android.server.wifi.Clock;
 import com.android.server.wifi.WifiCarrierInfoManager;
+import com.android.server.wifi.WifiInjector;
 import com.android.server.wifi.WifiKeyStore;
 import com.android.server.wifi.WifiMetrics;
 import com.android.server.wifi.WifiNative;
@@ -44,25 +45,29 @@
      * @param callbacks Instance of {@link PasspointEventHandler.Callbacks}
      * @return {@link PasspointEventHandler}
      */
-    public PasspointEventHandler makePasspointEventHandler(WifiNative wifiNative,
+    public PasspointEventHandler makePasspointEventHandler(WifiInjector wifiInjector,
             PasspointEventHandler.Callbacks callbacks) {
-        return new PasspointEventHandler(wifiNative, callbacks);
+        return new PasspointEventHandler(wifiInjector, callbacks);
     }
 
     /**
      * Create a PasspointProvider instance.
      *
-     * @param keyStore Instance of {@link WifiKeyStore}
      * @param config Configuration for the provider
+     * @param keyStore Instance of {@link WifiKeyStore}
+     * @param wifiCarrierInfoManager Instance of {@link WifiCarrierInfoManager}
      * @param providerId Unique identifier for the provider
+     * @param creatorUid Creator UID
      * @param packageName Package name of app adding/updating the {@code config}
+     * @param isFromSuggestion True if originated from a suggestion
+     * @param clock Instance of {@link Clock}
      * @return {@link PasspointProvider}
      */
     public PasspointProvider makePasspointProvider(PasspointConfiguration config,
             WifiKeyStore keyStore, WifiCarrierInfoManager wifiCarrierInfoManager, long providerId,
-            int creatorUid, String packageName, boolean isFromSuggestion) {
+            int creatorUid, String packageName, boolean isFromSuggestion, Clock clock) {
         return new PasspointProvider(config, keyStore, wifiCarrierInfoManager, providerId,
-                creatorUid, packageName, isFromSuggestion);
+                creatorUid, packageName, isFromSuggestion, clock);
     }
 
     /**
@@ -71,12 +76,14 @@
      * @param keyStore Instance of {@link WifiKeyStore}
      * @param wifiCarrierInfoManager Instance of {@link WifiCarrierInfoManager}
      * @param dataSource Passpoint configuration data source
+     * @param clock Instance of {@link Clock}
      * @return {@link PasspointConfigUserStoreData}
      */
     public PasspointConfigUserStoreData makePasspointConfigUserStoreData(WifiKeyStore keyStore,
             WifiCarrierInfoManager wifiCarrierInfoManager,
-            PasspointConfigUserStoreData.DataSource dataSource) {
-        return new PasspointConfigUserStoreData(keyStore, wifiCarrierInfoManager, dataSource);
+            PasspointConfigUserStoreData.DataSource dataSource, Clock clock) {
+        return new PasspointConfigUserStoreData(keyStore, wifiCarrierInfoManager, dataSource,
+                clock);
     }
 
     /**
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointProvider.java b/service/java/com/android/server/wifi/hotspot2/PasspointProvider.java
index 8e7364d..8ca032d 100644
--- a/service/java/com/android/server/wifi/hotspot2/PasspointProvider.java
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointProvider.java
@@ -18,8 +18,12 @@
 
 import static android.net.wifi.WifiConfiguration.MeteredOverride;
 
+import static com.android.server.wifi.MboOceConstants.DEFAULT_BLOCKLIST_DURATION_MS;
+
 import android.annotation.Nullable;
 import android.net.wifi.EAPConstants;
+import android.net.wifi.ScanResult;
+import android.net.wifi.SecurityParams;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.hotspot2.PasspointConfiguration;
@@ -27,12 +31,15 @@
 import android.net.wifi.hotspot2.pps.Credential.SimCredential;
 import android.net.wifi.hotspot2.pps.Credential.UserCredential;
 import android.net.wifi.hotspot2.pps.HomeSp;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Base64;
 import android.util.Log;
 import android.util.Pair;
 
+import com.android.modules.utils.build.SdkLevel;
+import com.android.server.wifi.Clock;
 import com.android.server.wifi.IMSIParameter;
 import com.android.server.wifi.WifiCarrierInfoManager;
 import com.android.server.wifi.WifiKeyStore;
@@ -107,18 +114,25 @@
     private boolean mIsTrusted;
     private boolean mVerboseLoggingEnabled;
 
+    private final Clock mClock;
+    private long mReauthDelay = 0;
+    private List<String> mBlockedBssids = new ArrayList<>();
+    private String mAnonymousIdentity = null;
+    private String mConnectChoice = null;
+    private int mConnectChoiceRssi = 0;
+
     public PasspointProvider(PasspointConfiguration config, WifiKeyStore keyStore,
             WifiCarrierInfoManager wifiCarrierInfoManager, long providerId, int creatorUid,
-            String packageName, boolean isFromSuggestion) {
+            String packageName, boolean isFromSuggestion, Clock clock) {
         this(config, keyStore, wifiCarrierInfoManager, providerId, creatorUid, packageName,
-                isFromSuggestion, null, null, null, false, false);
+                isFromSuggestion, null, null, null, false, false, clock);
     }
 
     public PasspointProvider(PasspointConfiguration config, WifiKeyStore keyStore,
             WifiCarrierInfoManager wifiCarrierInfoManager, long providerId, int creatorUid,
             String packageName, boolean isFromSuggestion, List<String> caCertificateAliases,
             String clientPrivateKeyAndCertificateAlias, String remediationCaCertificateAlias,
-            boolean hasEverConnected, boolean isShared) {
+            boolean hasEverConnected, boolean isShared, Clock clock) {
         // Maintain a copy of the configuration to avoid it being updated by others.
         mConfig = new PasspointConfiguration(config);
         mKeyStore = keyStore;
@@ -133,6 +147,7 @@
         mIsFromSuggestion = isFromSuggestion;
         mWifiCarrierInfoManager = wifiCarrierInfoManager;
         mIsTrusted = true;
+        mClock = clock;
 
         // Setup EAP method and authentication parameter based on the credential.
         if (mConfig.getCredential().getUserCredential() != null) {
@@ -168,6 +183,17 @@
         return mIsTrusted;
     }
 
+    /**
+     * Set Anonymous Identity for passpoint network.
+     */
+    public void setAnonymousIdentity(String anonymousIdentity) {
+        mAnonymousIdentity = anonymousIdentity;
+    }
+
+    public String getAnonymousIdentity() {
+        return mAnonymousIdentity;
+    }
+
     public PasspointConfiguration getConfig() {
         // Return a copy of the configuration to avoid it being updated by others.
         return new PasspointConfiguration(mConfig);
@@ -363,9 +389,12 @@
 
     private @Nullable String getMatchingSimImsi() {
         String matchingSIMImsi = null;
-        if (mConfig.getCarrierId() != TelephonyManager.UNKNOWN_CARRIER_ID) {
+        if (mConfig.getSubscriptionId() != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
             matchingSIMImsi = mWifiCarrierInfoManager
-                    .getMatchingImsi(mConfig.getCarrierId());
+                    .getMatchingImsiBySubId(mConfig.getSubscriptionId());
+        } else if (mConfig.getCarrierId() != TelephonyManager.UNKNOWN_CARRIER_ID) {
+            matchingSIMImsi = mWifiCarrierInfoManager.getMatchingImsiBySubId(
+                    mWifiCarrierInfoManager.getMatchingSubId(mConfig.getCarrierId()));
         } else {
             // Get the IMSI and carrier ID of SIM card which match with the IMSI prefix from
             // passpoint profile
@@ -385,10 +414,20 @@
      *
      * @param anqpElements ANQP elements from the AP
      * @param roamingConsortiumFromAp Roaming Consortium information element from the AP
+     * @param scanResult Latest Scan result
      * @return {@link PasspointMatch}
      */
     public PasspointMatch match(Map<ANQPElementType, ANQPElement> anqpElements,
-            RoamingConsortium roamingConsortiumFromAp) {
+            RoamingConsortium roamingConsortiumFromAp, ScanResult scanResult) {
+        if (isProviderBlocked(scanResult)) {
+            if (mVerboseLoggingEnabled) {
+                Log.d(TAG, "Provider " + mConfig.getServiceFriendlyName()
+                        + " is blocked because reauthentication delay duration is still in"
+                        + " progess");
+            }
+            return PasspointMatch.None;
+        }
+
         // If the profile requires a SIM credential, make sure that the installed SIM matches
         String matchingSimImsi = null;
         if (mConfig.getCredential().getSimCredential() != null) {
@@ -458,6 +497,14 @@
      */
     public WifiConfiguration getWifiConfig() {
         WifiConfiguration wifiConfig = new WifiConfiguration();
+
+        List<SecurityParams> paramsList = Arrays.asList(
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_PASSPOINT_R1_R2),
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_PASSPOINT_R3));
+        wifiConfig.setSecurityParams(paramsList);
+
         wifiConfig.FQDN = mConfig.getHomeSp().getFqdn();
         wifiConfig.setPasspointUniqueId(mConfig.getUniqueId());
         if (mConfig.getHomeSp().getRoamingConsortiumOis() != null) {
@@ -474,16 +521,18 @@
             }
         }
         wifiConfig.providerFriendlyName = mConfig.getHomeSp().getFriendlyName();
-        wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
-        wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
         int carrierId = mConfig.getCarrierId();
         if (carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) {
             carrierId = mBestGuessCarrierId;
         }
         wifiConfig.carrierId = carrierId;
-
-        // Set RSN only to tell wpa_supplicant that this network is for Passpoint.
-        wifiConfig.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
+        wifiConfig.subscriptionId =
+                mConfig.getSubscriptionId() == SubscriptionManager.INVALID_SUBSCRIPTION_ID
+                        ? mWifiCarrierInfoManager.getMatchingSubId(carrierId)
+                        : mConfig.getSubscriptionId();
+        wifiConfig.carrierMerged = mConfig.isCarrierMerged();
+        wifiConfig.oemPaid = mConfig.isOemPaid();
+        wifiConfig.oemPrivate = mConfig.isOemPrivate();
 
         WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
         enterpriseConfig.setRealm(mConfig.getCredential().getRealm());
@@ -498,6 +547,7 @@
         } else {
             buildEnterpriseConfigForSimCredential(enterpriseConfig,
                     mConfig.getCredential().getSimCredential());
+            enterpriseConfig.setAnonymousIdentity(mAnonymousIdentity);
         }
         // If AAA server trusted names are specified, use it to replace HOME SP FQDN
         // and use system CA regardless of provisioned CA certificate.
@@ -506,6 +556,9 @@
                     String.join(";", mConfig.getAaaServerTrustedNames()));
             enterpriseConfig.setCaPath(SYSTEM_CA_STORE_PATH);
         }
+        if (SdkLevel.isAtLeastS()) {
+            enterpriseConfig.setDecoratedIdentityPrefix(mConfig.getDecoratedIdentityPrefix());
+        }
         wifiConfig.enterpriseConfig = enterpriseConfig;
         // PPS MO Credential/CheckAAAServerCertStatus node contains a flag which indicates
         // if the mobile device needs to check the AAA server certificate's revocation status
@@ -522,11 +575,18 @@
         wifiConfig.creatorUid = mCreatorUid;
         wifiConfig.trusted = mIsTrusted;
         if (mConfig.isMacRandomizationEnabled()) {
-            wifiConfig.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_PERSISTENT;
+            if (mConfig.isEnhancedMacRandomizationEnabled()) {
+                wifiConfig.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_NON_PERSISTENT;
+            } else {
+                wifiConfig.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_PERSISTENT;
+            }
         } else {
             wifiConfig.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_NONE;
         }
         wifiConfig.meteredOverride = mConfig.getMeteredOverride();
+        wifiConfig.getNetworkSelectionStatus().setConnectChoice(mConnectChoice);
+        wifiConfig.getNetworkSelectionStatus().setConnectChoiceRssi(mConnectChoiceRssi);
+
         return wifiConfig;
     }
 
@@ -638,6 +698,22 @@
         builder.append("Shared: ").append(mIsShared).append("\n");
         builder.append("Suggestion: ").append(mIsFromSuggestion).append("\n");
         builder.append("Trusted: ").append(mIsTrusted).append("\n");
+        builder.append("UserConnectChoice: ").append(mConnectChoice).append("\n");
+        if (mReauthDelay != 0 && mClock.getElapsedSinceBootMillis() < mReauthDelay) {
+            builder.append("Reauth delay remaining (seconds): ")
+                    .append((mReauthDelay - mClock.getElapsedSinceBootMillis()) / 1000)
+                    .append("\n");
+            if (mBlockedBssids.isEmpty()) {
+                builder.append("ESS is blocked").append("\n");
+            } else {
+                builder.append("List of blocked BSSIDs:").append("\n");
+                for (String bssid : mBlockedBssids) {
+                    builder.append(bssid).append("\n");
+                }
+            }
+        } else {
+            builder.append("Provider is not blocked").append("\n");
+        }
 
         if (mPackageName != null) {
             builder.append("PackageName: ").append(mPackageName).append("\n");
@@ -1001,9 +1077,94 @@
 
     /**
      * Enable verbose logging
-     * @param verbose more than 0 enables verbose logging
+     * @param verbose enables verbose logging
      */
-    public void enableVerboseLogging(int verbose) {
-        mVerboseLoggingEnabled = (verbose > 0) ? true : false;
+    public void enableVerboseLogging(boolean verbose) {
+        mVerboseLoggingEnabled = verbose;
+    }
+
+    /**
+     * Block a BSS or ESS following a Deauthentication-Imminent WNM-Notification
+     *
+     * @param bssid BSSID of the source AP
+     * @param isEss true: Block ESS, false: Block BSS
+     * @param delayInSeconds Delay duration in seconds
+     */
+    public void blockBssOrEss(long bssid, boolean isEss, int delayInSeconds) {
+        if (delayInSeconds < 0 || bssid == 0) {
+            return;
+        }
+
+        mReauthDelay = mClock.getElapsedSinceBootMillis();
+        if (delayInSeconds == 0) {
+            // Section 3.2.1.2 in the specification defines that a Re-Auth Delay field
+            // value of 0 means the delay value is chosen by the mobile device.
+            mReauthDelay += DEFAULT_BLOCKLIST_DURATION_MS;
+        } else {
+            mReauthDelay += (delayInSeconds * 1000);
+        }
+        if (isEss) {
+            // Deauth-imminent for the entire ESS, do not try to reauthenticate until the delay
+            // is over. Clear the list of blocked BSSIDs.
+            mBlockedBssids.clear();
+        } else {
+            // Add this MAC address to the list of blocked BSSIDs.
+            mBlockedBssids.add(Utils.macToString(bssid));
+        }
+    }
+
+    /**
+     * Clear a block from a Passpoint provider. Used when Wi-Fi state is cleared, for example,
+     * when turning Wi-Fi off.
+     */
+    public void clearProviderBlock() {
+        mReauthDelay = 0;
+        mBlockedBssids.clear();
+    }
+
+    /**
+     * Checks if this provider is blocked or if there are any BSSes blocked
+     *
+     * @param scanResult Latest scan result
+     * @return true if blocked, false otherwise
+     */
+    private boolean isProviderBlocked(ScanResult scanResult) {
+        if (mReauthDelay == 0) {
+            return false;
+        }
+
+        if (mClock.getElapsedSinceBootMillis() >= mReauthDelay) {
+            // Provider was blocked, but the delay duration have passed
+            mReauthDelay = 0;
+            mBlockedBssids.clear();
+            return false;
+        }
+
+        // Empty means the entire ESS is blocked
+        if (mBlockedBssids.isEmpty() || mBlockedBssids.contains(scanResult.BSSID)) {
+            return true;
+        }
+
+        // Trying to associate to another BSS in the ESS
+        return false;
+    }
+
+    /**
+     * Set the user connect choice on the passpoint network.
+     * @param choice The {@link WifiConfiguration#getProfileKey()} of the user connect
+     *               network.
+     * @param rssi The signal strength of the network.
+     */
+    public void setUserConnectChoice(String choice, int rssi) {
+        mConnectChoice = choice;
+        mConnectChoiceRssi = rssi;
+    }
+
+    public String getConnectChoice() {
+        return mConnectChoice;
+    }
+
+    public int getConnectChoiceRssi() {
+        return mConnectChoiceRssi;
     }
 }
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java b/service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java
index ad8480a..49105dd 100644
--- a/service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java
@@ -132,12 +132,12 @@
     /**
      * Enable verbose logging to help debug failures
      *
-     * @param level integer indicating verbose logging enabled if > 0
+     * @param verbose enables verbose logging.
      */
-    public void enableVerboseLogging(int level) {
-        mVerboseLoggingEnabled = (level > 0) ? true : false;
-        mOsuNetworkConnection.enableVerboseLogging(level);
-        mOsuServerConnection.enableVerboseLogging(level);
+    public void enableVerboseLogging(boolean verbose) {
+        mVerboseLoggingEnabled = verbose;
+        mOsuNetworkConnection.enableVerboseLogging(verbose);
+        mOsuServerConnection.enableVerboseLogging(verbose);
     }
 
     /**
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointXmlUtils.java b/service/java/com/android/server/wifi/hotspot2/PasspointXmlUtils.java
index f50192c..d123ee8 100644
--- a/service/java/com/android/server/wifi/hotspot2/PasspointXmlUtils.java
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointXmlUtils.java
@@ -22,6 +22,7 @@
 import android.net.wifi.hotspot2.pps.Policy;
 import android.net.wifi.hotspot2.pps.UpdateParameter;
 
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.util.XmlUtil;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -110,9 +111,14 @@
     private static final String XML_TAG_USAGE_LIMIT_DATA_LIMIT = "UsageLimitDataLimit";
     private static final String XML_TAG_USAGE_LIMIT_TIME_LIMIT = "UsageLimitTimeLimit";
     private static final String XML_TAG_CARRIER_ID = "CarrierId";
+    private static final String XML_TAG_SUBSCRIPTION_ID = "SubscriptionId";
     private static final String XML_TAG_IS_AUTO_JOIN = "AutoJoinEnabled";
     private static final String XML_TAG_IS_MAC_RANDOMIZATION_ENABLED = "IsMacRandomizationEnabled";
     private static final String XML_TAG_METERED_OVERRIDE = "MeteredOverride";
+    private static final String XML_TAG_IS_CARRIER_MERGED = "IsCarrierMerged";
+    private static final String XML_TAG_IS_OEM_PAID = "IsOemPaid";
+    private static final String XML_TAG_IS_OEM_PRIVATE = "IsOemPrivate";
+    private static final String XML_TAG_DECORATED_IDENTITY_PREFIX = "DecoratedIdentityPrefix";
 
     /**
      * Serialize a {@link PasspointConfiguration} to the output stream as a XML block.
@@ -150,10 +156,18 @@
                     config.getServiceFriendlyNames());
         }
         XmlUtil.writeNextValue(out, XML_TAG_CARRIER_ID, config.getCarrierId());
+        XmlUtil.writeNextValue(out, XML_TAG_SUBSCRIPTION_ID, config.getSubscriptionId());
         XmlUtil.writeNextValue(out, XML_TAG_IS_AUTO_JOIN, config.isAutojoinEnabled());
         XmlUtil.writeNextValue(out, XML_TAG_IS_MAC_RANDOMIZATION_ENABLED,
                 config.isMacRandomizationEnabled());
         XmlUtil.writeNextValue(out, XML_TAG_METERED_OVERRIDE, config.getMeteredOverride());
+        XmlUtil.writeNextValue(out, XML_TAG_IS_CARRIER_MERGED, config.isCarrierMerged());
+        XmlUtil.writeNextValue(out, XML_TAG_IS_OEM_PAID, config.isOemPaid());
+        XmlUtil.writeNextValue(out, XML_TAG_IS_OEM_PRIVATE, config.isOemPrivate());
+        if (SdkLevel.isAtLeastS()) {
+            XmlUtil.writeNextValue(out, XML_TAG_DECORATED_IDENTITY_PREFIX,
+                    config.getDecoratedIdentityPrefix());
+        }
     }
 
     /**
@@ -210,6 +224,9 @@
                     case XML_TAG_CARRIER_ID:
                         config.setCarrierId((int) value);
                         break;
+                    case XML_TAG_SUBSCRIPTION_ID:
+                        config.setSubscriptionId((int) value);
+                        break;
                     case XML_TAG_IS_AUTO_JOIN:
                         config.setAutojoinEnabled((boolean) value);
                         break;
@@ -219,6 +236,20 @@
                     case XML_TAG_METERED_OVERRIDE:
                         config.setMeteredOverride((int) value);
                         break;
+                    case XML_TAG_IS_CARRIER_MERGED:
+                        config.setCarrierMerged((boolean) value);
+                        break;
+                    case XML_TAG_IS_OEM_PAID:
+                        config.setOemPaid((boolean) value);
+                        break;
+                    case XML_TAG_IS_OEM_PRIVATE:
+                        config.setOemPrivate((boolean) value);
+                        break;
+                    case XML_TAG_DECORATED_IDENTITY_PREFIX:
+                        if (SdkLevel.isAtLeastS()) {
+                            config.setDecoratedIdentityPrefix((String) value);
+                        }
+                        break;
                     default:
                         throw new XmlPullParserException("Unknown value under "
                                 + "PasspointConfiguration: " + in.getName());
diff --git a/service/java/com/android/server/wifi/hotspot2/WnmData.java b/service/java/com/android/server/wifi/hotspot2/WnmData.java
index 97a7d11..ad89b71 100644
--- a/service/java/com/android/server/wifi/hotspot2/WnmData.java
+++ b/service/java/com/android/server/wifi/hotspot2/WnmData.java
@@ -16,58 +16,145 @@
 
 package com.android.server.wifi.hotspot2;
 
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * This class carries the payload of a Hotspot 2.0 Wireless Network Management (WNM) frame,
  * described in the Hotspot 2.0 spec, section 3.2.
  */
 public class WnmData {
     public static final int ESS = 1;   // HS2.0 spec section 3.2.1.2, table 4
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {
+            HS20_REMEDIATION_EVENT,
+            HS20_DEAUTH_IMMINENT_EVENT,
+            HS20_TERMS_AND_CONDITIONS_ACCEPTANCE_REQUIRED_EVENT})
+    public @interface WmnEventType {}
+    public static final int HS20_REMEDIATION_EVENT = 0;
+    public static final int HS20_DEAUTH_IMMINENT_EVENT = 1;
+    public static final int HS20_TERMS_AND_CONDITIONS_ACCEPTANCE_REQUIRED_EVENT = 2;
+    public static final int UNDEFINED = -1;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {
+            OMA_DM,
+            SOAP_XML_SPP})
+    public @interface OsuMethod {}
+    public static final int OMA_DM = 0;
+    public static final int SOAP_XML_SPP = 1;
 
     private final long mBssid;
     private final String mUrl;
-    private final boolean mDeauthEvent;
+    private final @WmnEventType int mEventType;
     private final int mMethod;
     private final boolean mEss;
     private final int mDelay;
 
-    public WnmData(long bssid, String url, int method) {
+    private WnmData(@WmnEventType int eventType, long bssid, String url, @OsuMethod int method,
+            boolean ess, int delay) {
         mBssid = bssid;
         mUrl = url;
         mMethod = method;
-        mEss = false;
-        mDelay = -1;
-        mDeauthEvent = false;
-    }
-
-    public WnmData(long bssid, String url, boolean ess, int delay) {
-        mBssid = bssid;
-        mUrl = url;
         mEss = ess;
         mDelay = delay;
-        mMethod = -1;
-        mDeauthEvent = true;
+        mEventType = eventType;
     }
 
+    /**
+     * Create a Passpoint Remediation WNM-Notification
+     *
+     * @param bssid BSSID of the source AP
+     * @param url URL of the remediation server
+     * @param method OSU method. Refer to section 4.8.1.3 of the Passpoint spec
+     *
+     * @return a WnmData object
+     */
+    public static WnmData createRemediationEvent(long bssid, String url, @OsuMethod int method) {
+        return new WnmData(HS20_REMEDIATION_EVENT, bssid, url, method, false, UNDEFINED);
+    }
+
+    /**
+     * Create a Passpoint Deauth-Imminent WNM-Notification
+     *
+     * @param bssid BSSID of the source AP
+     * @param url URL of the remediation server
+     * @param ess A flag to indicate if the event applies to the ESS or only to the BSS
+     *
+     * @return a WnmData object
+     */
+    public static WnmData createDeauthImminentEvent(long bssid, String url, boolean ess,
+            int delay) {
+        return new WnmData(HS20_DEAUTH_IMMINENT_EVENT, bssid, url, UNDEFINED, ess, delay);
+    }
+
+    /**
+     * Create a Passpoint Terms & Conditions acceptance required WNM-Notification
+     *
+     * @param bssid BSSID of the source AP
+     * @param url URL of the remediation server
+     *
+     * @return a WnmData object
+     */
+    public static WnmData createTermsAndConditionsAccetanceRequiredEvent(long bssid, String url) {
+        return new WnmData(HS20_TERMS_AND_CONDITIONS_ACCEPTANCE_REQUIRED_EVENT, bssid, url,
+                UNDEFINED, false, UNDEFINED);
+    }
+
+    /**
+     * Get the BSSID of the source AP
+     *
+     * @return The BSSID of the source AP
+     */
     public long getBssid() {
         return mBssid;
     }
 
+    /**
+     * Get the URL associated to the WNM-Notification
+     *
+     * @return The URL associated to the WNM-Notification
+     */
     public String getUrl() {
         return mUrl;
     }
 
-    public boolean isDeauthEvent() {
-        return mDeauthEvent;
+    /**
+     * Get event type
+     *
+     * @return The WNM-Notification event type
+     */
+    @WmnEventType
+    public int getEventType() {
+        return mEventType;
     }
 
+    /**
+     * Get the OSU method associated to the Remediation WNM-Notification
+     *
+     * @return The OSU method supported by the server
+     */
+    @OsuMethod
     public int getMethod() {
         return mMethod;
     }
 
+    /**
+     * Get the ESS flag associated to the Deauth-Imminent WNM-Notification
+     *
+     * @return true if notification is for the entire ESS, false for the BSS only
+     */
     public boolean isEss() {
         return mEss;
     }
 
+    /**
+     * Get the delay in seconds associated to the Deauth-Imminent WNM-Notification
+     *
+     * @return The delay in seconds that a mobile device shall wait before attempting reassociation
+     */
     public int getDelay() {
         return mDelay;
     }
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/ANQPParser.java b/service/java/com/android/server/wifi/hotspot2/anqp/ANQPParser.java
index 89bcfcb..cf1b915 100644
--- a/service/java/com/android/server/wifi/hotspot2/anqp/ANQPParser.java
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/ANQPParser.java
@@ -72,6 +72,8 @@
                 return DomainNameElement.parse(payload);
             case ANQPVendorSpec:
                 return parseVendorSpecificElement(payload);
+            case ANQPVenueUrl:
+                return VenueUrlElement.parse(payload);
             default:
                 throw new ProtocolException("Unknown element ID: " + infoID);
         }
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/Constants.java b/service/java/com/android/server/wifi/hotspot2/anqp/Constants.java
index c45b3c5..420105c 100644
--- a/service/java/com/android/server/wifi/hotspot2/anqp/Constants.java
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/Constants.java
@@ -1,9 +1,5 @@
 package com.android.server.wifi.hotspot2.anqp;
 
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.charset.Charset;
 import java.util.Collection;
 import java.util.EnumMap;
 import java.util.HashMap;
@@ -40,6 +36,7 @@
     public static final int ANQP_3GPP_NETWORK = 264;
     public static final int ANQP_DOM_NAME = 268;
     public static final int ANQP_VENDOR_SPEC = 56797;
+    public static final int ANQP_VENUE_URL = 277;
 
     public static final int HS_QUERY_LIST = 1;
     public static final int HS_FRIENDLY_NAME = 3;
@@ -59,6 +56,7 @@
         ANQP3GPPNetwork,
         ANQPDomName,
         ANQPVendorSpec,
+        ANQPVenueUrl,
         HSQueryList,
         HSFriendlyName,
         HSWANMetrics,
@@ -85,6 +83,7 @@
         sAnqpMap.put(ANQP_3GPP_NETWORK, ANQPElementType.ANQP3GPPNetwork);
         sAnqpMap.put(ANQP_DOM_NAME, ANQPElementType.ANQPDomName);
         sAnqpMap.put(ANQP_VENDOR_SPEC, ANQPElementType.ANQPVendorSpec);
+        sAnqpMap.put(ANQP_VENUE_URL, ANQPElementType.ANQPVenueUrl);
 
         sHs20Map.put(HS_QUERY_LIST, ANQPElementType.HSQueryList);
         sHs20Map.put(HS_FRIENDLY_NAME, ANQPElementType.HSFriendlyName);
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/HSWanMetricsElement.java b/service/java/com/android/server/wifi/hotspot2/anqp/HSWanMetricsElement.java
index b55fefb..12ace55 100644
--- a/service/java/com/android/server/wifi/hotspot2/anqp/HSWanMetricsElement.java
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/HSWanMetricsElement.java
@@ -58,7 +58,7 @@
 
     private final int mStatus;
     private final boolean mSymmetric;
-    private final boolean mCapped;
+    private final boolean mAtCapacity;
     private final long mDownlinkSpeed;
     private final long mUplinkSpeed;
     private final int mDownlinkLoad;
@@ -66,12 +66,12 @@
     private final int mLMD;     // Load Measurement Duration.
 
     @VisibleForTesting
-    public HSWanMetricsElement(int status, boolean symmetric, boolean capped, long downlinkSpeed,
-            long uplinkSpeed, int downlinkLoad, int uplinkLoad, int lmd) {
+    public HSWanMetricsElement(int status, boolean symmetric, boolean atCapacity,
+            long downlinkSpeed, long uplinkSpeed, int downlinkLoad, int uplinkLoad, int lmd) {
         super(Constants.ANQPElementType.HSWANMetrics);
         mStatus = status;
         mSymmetric = symmetric;
-        mCapped = capped;
+        mAtCapacity = atCapacity;
         mDownlinkSpeed = downlinkSpeed;
         mUplinkSpeed = uplinkSpeed;
         mDownlinkLoad = downlinkLoad;
@@ -115,8 +115,8 @@
         return mSymmetric;
     }
 
-    public boolean isCapped() {
-        return mCapped;
+    public boolean isAtCapacity() {
+        return mAtCapacity;
     }
 
     public long getDownlinkSpeed() {
@@ -139,6 +139,21 @@
         return mLMD;
     }
 
+    /**
+     * Check if the WAN Metrics ANQP-element contains values other than all 0's
+     *
+     * @return true if element contains non-0 values, false otherwise
+     */
+    public boolean isElementInitialized() {
+        // Check if the WAN Metrics ANQP element is initialized with values other than 0's
+        if (mStatus == LINK_STATUS_RESERVED && !mAtCapacity && !mSymmetric && mDownlinkLoad == 0
+                && mDownlinkSpeed == 0 && mUplinkLoad == 0 && mUplinkSpeed == 0 && mLMD == 0) {
+            // WAN Metrics ANQP element is not initialized in this network. Ignore it.
+            return false;
+        }
+        return true;
+    }
+
     @Override
     public boolean equals(Object thatObject) {
         if (this == thatObject) {
@@ -150,7 +165,7 @@
         HSWanMetricsElement that = (HSWanMetricsElement) thatObject;
         return mStatus == that.mStatus
                 && mSymmetric == that.mSymmetric
-                && mCapped == that.mCapped
+                && mAtCapacity == that.mAtCapacity
                 && mDownlinkSpeed == that.mDownlinkSpeed
                 && mUplinkSpeed == that.mUplinkSpeed
                 && mDownlinkLoad == that.mDownlinkLoad
@@ -166,9 +181,9 @@
 
     @Override
     public String toString() {
-        return String.format("HSWanMetrics{mStatus=%s, mSymmetric=%s, mCapped=%s, " +
-                "mDlSpeed=%d, mUlSpeed=%d, mDlLoad=%f, mUlLoad=%f, mLMD=%d}",
-                mStatus, mSymmetric, mCapped,
+        return String.format("HSWanMetrics{mStatus=%s, mSymmetric=%s, mAtCapacity=%s, "
+                        + "mDlSpeed=%d, mUlSpeed=%d, mDlLoad=%f, mUlLoad=%f, mLMD=%d}",
+                mStatus, mSymmetric, mAtCapacity,
                 mDownlinkSpeed, mUplinkSpeed,
                 mDownlinkLoad * 100.0 / MAX_LOAD,
                 mUplinkLoad * 100.0 / MAX_LOAD,
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/VenueUrlElement.java b/service/java/com/android/server/wifi/hotspot2/anqp/VenueUrlElement.java
new file mode 100644
index 0000000..cbe2b4e
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/VenueUrlElement.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.hotspot2.anqp;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wifi.ByteBufferReader;
+
+import java.net.ProtocolException;
+import java.net.URL;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * The Venue URL ANQP Element, IEEE802.11-2016 section 9.4.5.20.
+ *
+ * Format:
+ *
+ * | Venue URL Duples |
+ *      variable
+ *
+ * Venue URL Duple:
+ * | Length | Venue Number | Venue URL Duple #1 (optional) | ...
+ *     1          1                 variable
+ */
+public class VenueUrlElement extends ANQPElement {
+    private final Map<Integer, URL> mVenueUrls;
+    private static final String TAG = "VenueUrlElement";
+
+    @VisibleForTesting
+    public VenueUrlElement(Map<Integer, URL> venueUrls) {
+        super(Constants.ANQPElementType.ANQPVenueUrl);
+        mVenueUrls = venueUrls;
+    }
+
+    /**
+     * Parse a VenueUrlElement from the given buffer.
+     *
+     * @param payload The byte buffer to read from
+     * @return {@link VenueUrlElement}
+     * @throws BufferUnderflowException
+     * @throws ProtocolException
+     */
+    public static VenueUrlElement parse(ByteBuffer payload)
+            throws ProtocolException, BufferUnderflowException {
+        Map<Integer, URL> venueUrls = new HashMap<>();
+        while (payload.remaining() >= 2) {
+            int length = payload.get() & 0xFF;
+            int venueNumber = payload.get() & 0xFF;
+            if (venueNumber == 0) {
+                // In case no Venue Name Tuple subfield was returned in the Venue Name ANQP-element
+                break;
+            }
+            String parsedUrl = ByteBufferReader.readString(payload, length - 1,
+                    StandardCharsets.UTF_8);
+            URL url;
+            try {
+                url = new URL(parsedUrl);
+            } catch (java.net.MalformedURLException e) {
+                Log.e(TAG, "Malformed venue URL: " + parsedUrl + " at index " + venueNumber);
+                throw new ProtocolException("Malformed venue URL: " + parsedUrl + " at index "
+                        + venueNumber);
+            }
+            // Reject URLs that are not HTTPS
+            String protocol = url.getProtocol();
+            if (!TextUtils.equals(protocol, "https")) {
+                Log.w(TAG, "Non-HTTPS Venue URL dropped: " + url);
+            } else {
+                venueUrls.put(Integer.valueOf(venueNumber), url);
+            }
+        }
+
+        return new VenueUrlElement(venueUrls);
+    }
+
+    public Map<Integer, URL> getVenueUrls() {
+        return mVenueUrls;
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof VenueUrlElement)) {
+            return false;
+        }
+        VenueUrlElement that = (VenueUrlElement) thatObject;
+        return mVenueUrls.equals(that.mVenueUrls);
+    }
+
+    @Override
+    public int hashCode() {
+        return mVenueUrls.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return "VenueUrl{ mUrlList=" + mVenueUrls + "}";
+    }
+
+}
diff --git a/service/java/com/android/server/wifi/p2p/SupplicantP2pIfaceCallback.java b/service/java/com/android/server/wifi/p2p/SupplicantP2pIfaceCallbackImpl.java
similarity index 97%
rename from service/java/com/android/server/wifi/p2p/SupplicantP2pIfaceCallback.java
rename to service/java/com/android/server/wifi/p2p/SupplicantP2pIfaceCallbackImpl.java
index ea74678..21b0998 100644
--- a/service/java/com/android/server/wifi/p2p/SupplicantP2pIfaceCallback.java
+++ b/service/java/com/android/server/wifi/p2p/SupplicantP2pIfaceCallbackImpl.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wifi.p2p;
 
+import android.annotation.NonNull;
 import android.hardware.wifi.supplicant.V1_0.ISupplicantP2pIfaceCallback;
 import android.hardware.wifi.supplicant.V1_0.WpsConfigMethods;
 import android.net.wifi.WpsInfo;
@@ -37,14 +38,18 @@
 /**
  * Class used for processing all P2P callbacks.
  */
-public class SupplicantP2pIfaceCallback extends ISupplicantP2pIfaceCallback.Stub {
-    private static final String TAG = "SupplicantP2pIfaceCallback";
+public class SupplicantP2pIfaceCallbackImpl extends ISupplicantP2pIfaceCallback.Stub {
+    private static final String TAG = "SupplicantP2pIfaceCallbackImpl";
     private static boolean sVerboseLoggingEnabled = true;
 
+    private final SupplicantP2pIfaceHal mP2pIfaceHal;
     private final String mInterface;
     private final WifiP2pMonitor mMonitor;
 
-    public SupplicantP2pIfaceCallback(String iface, WifiP2pMonitor monitor) {
+    public SupplicantP2pIfaceCallbackImpl(
+            @NonNull SupplicantP2pIfaceHal p2pIfaceHal,
+            @NonNull String iface, @NonNull WifiP2pMonitor monitor) {
+        mP2pIfaceHal = p2pIfaceHal;
         mInterface = iface;
         mMonitor = monitor;
     }
diff --git a/service/java/com/android/server/wifi/p2p/SupplicantP2pIfaceCallbackV1_4Impl.java b/service/java/com/android/server/wifi/p2p/SupplicantP2pIfaceCallbackV1_4Impl.java
new file mode 100644
index 0000000..676f8fd
--- /dev/null
+++ b/service/java/com/android/server/wifi/p2p/SupplicantP2pIfaceCallbackV1_4Impl.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.p2p;
+
+import android.annotation.NonNull;
+import android.net.wifi.p2p.WifiP2pDevice;
+import android.net.wifi.p2p.WifiP2pWfdInfo;
+import android.util.Log;
+
+import com.android.server.wifi.util.NativeUtil;
+
+import java.util.ArrayList;
+
+/**
+ * Class used for processing all P2P callbacks.
+ */
+public class SupplicantP2pIfaceCallbackV1_4Impl
+        extends android.hardware.wifi.supplicant.V1_4.ISupplicantP2pIfaceCallback.Stub {
+    private static final String TAG = "SupplicantP2pIfaceCallbackV1_4Impl";
+    private static boolean sVerboseLoggingEnabled = true;
+
+    private final SupplicantP2pIfaceHal mP2pIfaceHal;
+    private final String mInterface;
+    private final WifiP2pMonitor mMonitor;
+    private final SupplicantP2pIfaceHal.SupplicantP2pIfaceCallback mCallbackV10;
+
+    public SupplicantP2pIfaceCallbackV1_4Impl(
+            @NonNull SupplicantP2pIfaceHal p2pIfaceHal,
+            @NonNull String iface, @NonNull WifiP2pMonitor monitor) {
+        mP2pIfaceHal = p2pIfaceHal;
+        mInterface = iface;
+        mMonitor = monitor;
+        // Create an older callback for function delegation,
+        // and it would cascadingly create older one.
+        mCallbackV10 = mP2pIfaceHal.new SupplicantP2pIfaceCallback(mInterface);
+    }
+
+    /**
+     * Enable verbose logging for all sub modules.
+     */
+    public static void enableVerboseLogging(int verbose) {
+        sVerboseLoggingEnabled = verbose > 0;
+    }
+
+    protected static void logd(String s) {
+        if (sVerboseLoggingEnabled) Log.d(TAG, s);
+    }
+
+    /**
+     * Used to indicate that a new network has been added.
+     *
+     * @param networkId Network ID allocated to the corresponding network.
+     */
+    public void onNetworkAdded(int networkId) {
+        mCallbackV10.onNetworkAdded(networkId);
+    }
+
+    /**
+     * Used to indicate that a network has been removed.
+     *
+     * @param networkId Network ID allocated to the corresponding network.
+     */
+    public void onNetworkRemoved(int networkId) {
+        mCallbackV10.onNetworkRemoved(networkId);
+    }
+
+    /**
+     * Used to indicate that a P2P device has been found.
+     *
+     * @param srcAddress MAC address of the device found. This must either
+     *        be the P2P device address or the P2P interface address.
+     * @param p2pDeviceAddress P2P device address.
+     * @param primaryDeviceType Type of device. Refer to section B.1 of Wifi P2P
+     *        Technical specification v1.2.
+     * @param deviceName Name of the device.
+     * @param configMethods Mask of WPS configuration methods supported by the
+     *        device.
+     * @param deviceCapabilities Refer to section 4.1.4 of Wifi P2P Technical
+     *        specification v1.2.
+     * @param groupCapabilities Refer to section 4.1.4 of Wifi P2P Technical
+     *        specification v1.2.
+     * @param wfdDeviceInfo WFD device info as described in section 5.1.2 of WFD
+     *        technical specification v1.0.0.
+     */
+    public void onDeviceFound(byte[] srcAddress, byte[] p2pDeviceAddress, byte[] primaryDeviceType,
+            String deviceName, short configMethods, byte deviceCapabilities, int groupCapabilities,
+            byte[] wfdDeviceInfo) {
+        mCallbackV10.onDeviceFound(srcAddress, p2pDeviceAddress, primaryDeviceType,
+                deviceName, configMethods, deviceCapabilities, groupCapabilities, wfdDeviceInfo);
+    }
+
+    /**
+     * Used to indicate that a P2P device has been lost.
+     *
+     * @param p2pDeviceAddress P2P device address.
+     */
+    public void onDeviceLost(byte[] p2pDeviceAddress) {
+        mCallbackV10.onDeviceLost(p2pDeviceAddress);
+    }
+
+    /**
+     * Used to indicate the termination of P2P find operation.
+     */
+    public void onFindStopped() {
+        mCallbackV10.onFindStopped();
+    }
+
+    /**
+     * Used to indicate the reception of a P2P Group Owner negotiation request.
+     *
+     * @param srcAddress MAC address of the device that initiated the GO
+     *        negotiation request.
+     * @param passwordId Type of password.
+     */
+    public void onGoNegotiationRequest(byte[] srcAddress, short passwordId) {
+        mCallbackV10.onGoNegotiationRequest(srcAddress, passwordId);
+    }
+
+    /**
+     * Used to indicate the completion of a P2P Group Owner negotiation request.
+     *
+     * @param status Status of the GO negotiation.
+     */
+    public void onGoNegotiationCompleted(int status) {
+        mCallbackV10.onGoNegotiationCompleted(status);
+    }
+
+    /**
+     * Used to indicate a successful formation of a P2P group.
+     */
+    public void onGroupFormationSuccess() {
+        mCallbackV10.onGroupFormationSuccess();
+    }
+
+    /**
+     * Used to indicate a failure to form a P2P group.
+     *
+     * @param failureReason Failure reason string for debug purposes.
+     */
+    public void onGroupFormationFailure(String failureReason) {
+        mCallbackV10.onGroupFormationFailure(failureReason);
+    }
+
+    /**
+     * Used to indicate the start of a P2P group.
+     *
+     * @param groupIfName Interface name of the group. (For ex: p2p-p2p0-1)
+     * @param isGo Whether this device is owner of the group.
+     * @param ssid SSID of the group.
+     * @param frequency Frequency on which this group is created.
+     * @param psk PSK used to secure the group.
+     * @param passphrase PSK passphrase used to secure the group.
+     * @param goDeviceAddress MAC Address of the owner of this group.
+     * @param isPersistent Whether this group is persisted or not.
+     */
+    public void onGroupStarted(String groupIfName, boolean isGo, ArrayList<Byte> ssid,
+            int frequency, byte[] psk, String passphrase, byte[] goDeviceAddress,
+            boolean isPersistent) {
+        mCallbackV10.onGroupStarted(groupIfName, isGo,
+                ssid, frequency, psk, passphrase, goDeviceAddress, isPersistent);
+    }
+
+    /**
+     * Used to indicate the removal of a P2P group.
+     *
+     * @param groupIfName Interface name of the group. (For ex: p2p-p2p0-1)
+     * @param isGo Whether this device is owner of the group.
+     */
+    public void onGroupRemoved(String groupIfName, boolean isGo) {
+        mCallbackV10.onGroupRemoved(groupIfName, isGo);
+    }
+
+    /**
+     * Used to indicate the reception of a P2P invitation.
+     *
+     * @param srcAddress MAC address of the device that sent the invitation.
+     * @param goDeviceAddress MAC Address of the owner of this group.
+     * @param bssid Bssid of the group.
+     * @param persistentNetworkId Persistent network Id of the group.
+     * @param operatingFrequency Frequency on which the invitation was received.
+     */
+    public void onInvitationReceived(byte[] srcAddress, byte[] goDeviceAddress,
+            byte[] bssid, int persistentNetworkId, int operatingFrequency) {
+        mCallbackV10.onInvitationReceived(srcAddress, goDeviceAddress, bssid,
+                persistentNetworkId, operatingFrequency);
+    }
+
+    /**
+     * Used to indicate the result of the P2P invitation request.
+     *
+     * @param bssid Bssid of the group.
+     * @param status Status of the invitation.
+     */
+    public void onInvitationResult(byte[] bssid, int status) {
+        mCallbackV10.onInvitationResult(bssid, status);
+    }
+
+    /**
+     * Used to indicate the completion of a P2P provision discovery request.
+     *
+     * @param p2pDeviceAddress P2P device address.
+     * @param isRequest Whether we received or sent the provision discovery.
+     * @param status Status of the provision discovery (SupplicantStatusCode).
+     * @param configMethods Mask of WPS configuration methods supported.
+     *                      Only one configMethod bit should be set per call.
+     * @param generatedPin 8 digit pin generated.
+     */
+    public void onProvisionDiscoveryCompleted(byte[] p2pDeviceAddress, boolean isRequest,
+            byte status, short configMethods, String generatedPin) {
+        mCallbackV10.onProvisionDiscoveryCompleted(p2pDeviceAddress, isRequest,
+                status, configMethods, generatedPin);
+    }
+
+    /**
+     * Used to indicate the reception of a P2P service discovery response.
+     *
+     * @param srcAddress MAC address of the device that sent the service discovery.
+     * @param updateIndicator Service update indicator. Refer to section 3.1.3 of
+     *        Wifi P2P Technical specification v1.2.
+     * @param tlvs Refer to section 3.1.3.1 of Wifi P2P Technical specification v1.2.
+     */
+    public void onServiceDiscoveryResponse(byte[] srcAddress, short updateIndicator,
+            ArrayList<Byte> tlvs) {
+        mCallbackV10.onServiceDiscoveryResponse(srcAddress, updateIndicator, tlvs);
+    }
+
+    /**
+     * Used to indicate when a STA device is connected to this device.
+     *
+     * @param srcAddress MAC address of the device that was authorized.
+     * @param p2pDeviceAddress P2P device address.
+     */
+    public void onStaAuthorized(byte[] srcAddress, byte[] p2pDeviceAddress) {
+        mCallbackV10.onStaAuthorized(srcAddress, p2pDeviceAddress);
+    }
+
+    /**
+     * Used to indicate when a STA device is disconnected from this device.
+     *
+     * @param srcAddress MAC address of the device that was deauthorized.
+     * @param p2pDeviceAddress P2P device address.
+     */
+    public void onStaDeauthorized(byte[] srcAddress, byte[] p2pDeviceAddress) {
+        mCallbackV10.onStaDeauthorized(srcAddress, p2pDeviceAddress);
+    }
+
+    /**
+     * Used to indicate that a P2P WFD R2 device has been found.
+     *
+     * @param srcAddress MAC address of the device found. This must either
+     *        be the P2P device address or the P2P interface address.
+     * @param p2pDeviceAddress P2P device address.
+     * @param primaryDeviceType Type of device. Refer to section B.1 of Wifi P2P
+     *        Technical specification v1.2.
+     * @param deviceName Name of the device.
+     * @param configMethods Mask of WPS configuration methods supported by the
+     *        device.
+     * @param deviceCapabilities Refer to section 4.1.4 of Wifi P2P Technical
+     *        specification v1.2.
+     * @param groupCapabilities Refer to section 4.1.4 of Wifi P2P Technical
+     *        specification v1.2.
+     * @param wfdDeviceInfo WFD device info as described in section 5.1.2 of WFD
+     *        technical specification v1.0.0.
+     * @param wfdR2DeviceInfo WFD R2 device info as described in section 5.1.12 of WFD
+     *        technical specification v2.1.
+     */
+    public void onR2DeviceFound(byte[] srcAddress, byte[] p2pDeviceAddress,
+            byte[] primaryDeviceType, String deviceName, short configMethods,
+            byte deviceCapabilities, int groupCapabilities, byte[] wfdDeviceInfo,
+            byte[] wfdR2DeviceInfo) {
+        WifiP2pDevice device = new WifiP2pDevice();
+        device.deviceName = deviceName;
+        if (deviceName == null) {
+            Log.e(TAG, "Missing device name.");
+            return;
+        }
+
+        try {
+            device.deviceAddress = NativeUtil.macAddressFromByteArray(p2pDeviceAddress);
+        } catch (Exception e) {
+            Log.e(TAG, "Could not decode device address.", e);
+            return;
+        }
+
+        try {
+            device.primaryDeviceType = NativeUtil.wpsDevTypeStringFromByteArray(primaryDeviceType);
+        } catch (Exception e) {
+            Log.e(TAG, "Could not encode device primary type.", e);
+            return;
+        }
+
+        device.deviceCapability = deviceCapabilities;
+        device.groupCapability = groupCapabilities;
+        device.wpsConfigMethodsSupported = configMethods;
+        device.status = WifiP2pDevice.AVAILABLE;
+
+        if (wfdDeviceInfo != null && wfdDeviceInfo.length >= 6) {
+            device.wfdInfo = new WifiP2pWfdInfo(
+                    ((wfdDeviceInfo[0] & 0xFF) << 8) + (wfdDeviceInfo[1] & 0xFF),
+                    ((wfdDeviceInfo[2] & 0xFF) << 8) + (wfdDeviceInfo[3] & 0xFF),
+                    ((wfdDeviceInfo[4] & 0xFF) << 8) + (wfdDeviceInfo[5] & 0xFF));
+        }
+        if (wfdR2DeviceInfo != null && wfdR2DeviceInfo.length >= 2) {
+            device.wfdInfo.setR2DeviceInfo(
+                    ((wfdR2DeviceInfo[0] & 0xFF) << 8) + (wfdR2DeviceInfo[1] & 0xFF));
+        }
+
+        logd("R2 Device discovered on " + mInterface + ": "
+                + device + " R2 Info:" + wfdR2DeviceInfo);
+        mMonitor.broadcastP2pDeviceFound(mInterface, device);
+    }
+
+}
+
diff --git a/service/java/com/android/server/wifi/p2p/SupplicantP2pIfaceHal.java b/service/java/com/android/server/wifi/p2p/SupplicantP2pIfaceHal.java
index dca3a60..baf7346 100644
--- a/service/java/com/android/server/wifi/p2p/SupplicantP2pIfaceHal.java
+++ b/service/java/com/android/server/wifi/p2p/SupplicantP2pIfaceHal.java
@@ -29,6 +29,8 @@
 import android.hardware.wifi.supplicant.V1_0.WpsConfigMethods;
 import android.hidl.manager.V1_0.IServiceManager;
 import android.hidl.manager.V1_0.IServiceNotification;
+import android.net.wifi.CoexUnsafeChannel;
+import android.net.wifi.ScanResult;
 import android.net.wifi.WpsInfo;
 import android.net.wifi.p2p.WifiP2pConfig;
 import android.net.wifi.p2p.WifiP2pDevice;
@@ -41,6 +43,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.util.ArrayUtils;
 import com.android.server.wifi.util.NativeUtil;
 
@@ -112,7 +115,7 @@
             };
 
     private final WifiP2pMonitor mMonitor;
-    private SupplicantP2pIfaceCallback mCallback = null;
+    private ISupplicantP2pIfaceCallback mCallback = null;
 
     public SupplicantP2pIfaceHal(WifiP2pMonitor monitor) {
         mMonitor = monitor;
@@ -140,6 +143,7 @@
     public static void enableVerboseLogging(int verbose) {
         sVerboseLoggingEnabled = verbose > 0;
         SupplicantP2pIfaceCallback.enableVerboseLogging(verbose);
+        SupplicantP2pIfaceCallbackV1_4.enableVerboseLogging(verbose);
     }
 
     /**
@@ -262,17 +266,43 @@
                 return false;
             }
             if (mISupplicantP2pIface != null && mMonitor != null) {
-                mCallback = new SupplicantP2pIfaceCallback(ifaceName, mMonitor);
-                if (!registerCallback(mCallback)) {
-                    Log.e(TAG, "Callback registration failed. Initialization incomplete.");
-                    return false;
+                if (null != getP2pIfaceMockableV1_4()) {
+                    android.hardware.wifi.supplicant.V1_4.ISupplicantP2pIfaceCallback callback =
+                            new SupplicantP2pIfaceCallbackV1_4(ifaceName);
+                    if (!registerCallbackV1_4(callback)) {
+                        Log.e(TAG, "Callback registration failed. Initialization incomplete.");
+                        return false;
+                    }
+                    mCallback = callback;
+                } else {
+                    mCallback = new SupplicantP2pIfaceCallback(ifaceName);
+                    if (!registerCallback(mCallback)) {
+                        Log.e(TAG, "Callback registration failed. Initialization incomplete.");
+                        return false;
+                    }
                 }
             }
             return true;
         }
     }
 
+    protected class SupplicantP2pIfaceCallback extends SupplicantP2pIfaceCallbackImpl {
+        SupplicantP2pIfaceCallback(@NonNull String ifaceName) {
+            super(SupplicantP2pIfaceHal.this, ifaceName, mMonitor);
+        }
+    }
+
+    protected class SupplicantP2pIfaceCallbackV1_4 extends SupplicantP2pIfaceCallbackV1_4Impl {
+        SupplicantP2pIfaceCallbackV1_4(@NonNull String ifaceName) {
+            super(SupplicantP2pIfaceHal.this, ifaceName, mMonitor);
+        }
+    }
+
     private ISupplicantIface getIfaceV1_0(@NonNull String ifaceName) {
+        if (null == mISupplicant) {
+            Log.e(TAG, "Can't call getIface: ISupplicant is null");
+            return null;
+        }
         /** List all supplicant Ifaces */
         final ArrayList<ISupplicant.IfaceInfo> supplicantIfaces = new ArrayList();
         try {
@@ -464,6 +494,13 @@
                 mISupplicantP2pIface);
     }
 
+    protected android.hardware.wifi.supplicant.V1_4.ISupplicantP2pIface
+            getP2pIfaceMockableV1_4() {
+        if (mISupplicantP2pIface == null) return null;
+        return android.hardware.wifi.supplicant.V1_4.ISupplicantP2pIface.castFrom(
+                mISupplicantP2pIface);
+    }
+
     protected ISupplicantP2pNetwork getP2pNetworkMockable(ISupplicantNetwork network) {
         return ISupplicantP2pNetwork.asInterface(network.asBinder());
     }
@@ -488,13 +525,15 @@
         if (sVerboseLoggingEnabled) Log.d(TAG, s);
     }
 
-    protected static void logCompletion(String operation, SupplicantStatus status) {
-        if (status == null) {
-            Log.w(TAG, operation + " failed: no status code returned.");
-        } else if (status.code == SupplicantStatusCode.SUCCESS) {
+    protected static void logw(String s) {
+        Log.w(TAG, s);
+    }
+
+    protected static <S> void logCompletion(String operation, int code, String debugMessage) {
+        if (code == SupplicantStatusCode.SUCCESS) {
             logd(operation + " completed successfully.");
         } else {
-            Log.w(TAG, operation + " failed: " + status.code + " (" + status.debugMessage + ")");
+            Log.w(TAG, operation + " failed: " + code + " (" + debugMessage + ")");
         }
     }
 
@@ -527,6 +566,23 @@
         }
     }
 
+    /**
+     * Returns SupplicantP2pIface on success, logs failure to call methodStr
+     * and returns false otherwise
+     */
+    private android.hardware.wifi.supplicant.V1_4.ISupplicantP2pIface
+            getSupplicantP2pIfaceAndLogFailureV1_4(String method) {
+        synchronized (mLock) {
+            android.hardware.wifi.supplicant.V1_4.ISupplicantP2pIface p2pIfaceV12 =
+                    getP2pIfaceMockableV1_4();
+            if (p2pIfaceV12 == null) {
+                Log.e(TAG, "Can't call " + method + ": ISupplicantP2pIface is null");
+                return null;
+            }
+            return p2pIfaceV12;
+        }
+    }
+
     private int wpsInfoToConfigMethod(int info) {
         switch (info) {
             case WpsInfo.PBC:
@@ -597,6 +653,36 @@
 
 
     /**
+     * Register for callbacks from this interface.
+     *
+     * These callbacks are invoked for events that are specific to this interface.
+     * Registration of multiple callback objects is supported. These objects must
+     * be automatically deleted when the corresponding client process is dead or
+     * if this interface is removed.
+     *
+     * @param receiver An instance of the |ISupplicantP2pIfaceCallback| HIDL
+     *        interface object.
+     * @return boolean value indicating whether operation was successful.
+     */
+    public boolean registerCallbackV1_4(
+            android.hardware.wifi.supplicant.V1_4.ISupplicantP2pIfaceCallback receiver) {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("registerCallbackV1_4")) return false;
+            if (null == getP2pIfaceMockableV1_4()) return false;
+            SupplicantResultV1_4<Void> result =
+                    new SupplicantResultV1_4("registerCallbackV1_4()");
+            try {
+                result.setResult(getP2pIfaceMockableV1_4().registerCallback_1_4(receiver));
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+            return result.isSuccess();
+        }
+    }
+
+
+    /**
      * Initiate a P2P service discovery with a (optional) timeout.
      *
      * @param timeout Max time to be spent is peforming discovery.
@@ -1363,63 +1449,86 @@
 
 
     /**
-     * Set P2P Listen channel and operating chanel.
+     * Set P2P Listen channel.
      *
      * @param listenChannel Wifi channel. eg, 1, 6, 11.
-     * @param operatingChannel Wifi channel. eg, 1, 6, 11.
      *
      * @return true, if operation was successful.
      */
-    public boolean setListenChannel(int listenChannel, int operatingChannel) {
+    public boolean setListenChannel(int listenChannel) {
         synchronized (mLock) {
             if (!checkSupplicantP2pIfaceAndLogFailure("setListenChannel")) return false;
 
-            if (listenChannel >= 1 && listenChannel <= 11) {
-                SupplicantResult<Void> result = new SupplicantResult(
-                        "setListenChannel(" + listenChannel + ", " + DEFAULT_OPERATING_CLASS + ")");
-                try {
-                    result.setResult(mISupplicantP2pIface.setListenChannel(
-                            listenChannel, DEFAULT_OPERATING_CLASS));
-                } catch (RemoteException e) {
-                    Log.e(TAG, "ISupplicantP2pIface exception: " + e);
-                    supplicantServiceDiedHandler();
-                }
-                if (!result.isSuccess()) {
-                    return false;
-                }
-            } else if (listenChannel != 0) {
-                // listenChannel == 0 does not set any listen channel.
+            // There is no original channel recorded in supplicant, so just return true.
+            if (0 == listenChannel) return true;
+
+            // Using channels other than 1, 6, and 11 would result in discovery issue.
+            if (listenChannel != 1 && listenChannel != 6 && listenChannel != 11) {
                 return false;
             }
 
-            if (operatingChannel >= 0 && operatingChannel <= 165) {
-                ArrayList<ISupplicantP2pIface.FreqRange> ranges = new ArrayList<>();
-                // operatingChannel == 0 enables all freqs.
-                if (operatingChannel >= 1 && operatingChannel <= 165) {
-                    int freq = (operatingChannel <= 14 ? 2407 : 5000) + operatingChannel * 5;
-                    ISupplicantP2pIface.FreqRange range1 =  new ISupplicantP2pIface.FreqRange();
-                    range1.min = 1000;
-                    range1.max = freq - 5;
-                    ISupplicantP2pIface.FreqRange range2 =  new ISupplicantP2pIface.FreqRange();
-                    range2.min = freq + 5;
-                    range2.max = 6000;
-                    ranges.add(range1);
-                    ranges.add(range2);
-                }
-                SupplicantResult<Void> result = new SupplicantResult(
-                        "setDisallowedFrequencies(" + ranges + ")");
-                try {
-                    result.setResult(mISupplicantP2pIface.setDisallowedFrequencies(ranges));
-                } catch (RemoteException e) {
-                    Log.e(TAG, "ISupplicantP2pIface exception: " + e);
-                    supplicantServiceDiedHandler();
-                }
-                return result.isSuccess();
+            SupplicantResult<Void> result = new SupplicantResult(
+                    "setListenChannel(" + listenChannel + ", " + DEFAULT_OPERATING_CLASS + ")");
+            try {
+                result.setResult(mISupplicantP2pIface.setListenChannel(
+                        listenChannel, DEFAULT_OPERATING_CLASS));
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
             }
-            return false;
+            return result.isSuccess();
         }
     }
 
+    /**
+     * Set P2P operating channel.
+     *
+     * @param operatingChannel the desired operating channel.
+     * @param unsafeChannels channels which p2p cannot use.
+     *
+     * @return true, if operation was successful.
+     */
+    public boolean setOperatingChannel(int operatingChannel,
+            @NonNull List<CoexUnsafeChannel> unsafeChannels) {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("setOperatingChannel")) return false;
+            if (null == unsafeChannels) return false;
+
+            ArrayList<ISupplicantP2pIface.FreqRange> ranges = new ArrayList<>();
+            if (operatingChannel >= 1 && operatingChannel <= 165) {
+                int freq = (operatingChannel <= 14 ? 2407 : 5000) + operatingChannel * 5;
+                ISupplicantP2pIface.FreqRange range1 =  new ISupplicantP2pIface.FreqRange();
+                range1.min = 1000;
+                range1.max = freq - 5;
+                ISupplicantP2pIface.FreqRange range2 =  new ISupplicantP2pIface.FreqRange();
+                range2.min = freq + 5;
+                range2.max = 6000;
+                ranges.add(range1);
+                ranges.add(range2);
+            }
+            if (SdkLevel.isAtLeastS()) {
+                for (CoexUnsafeChannel cuc: unsafeChannels) {
+                    int centerFreq = ScanResult.convertChannelToFrequencyMhzIfSupported(
+                            cuc.getChannel(), cuc.getBand());
+                    ISupplicantP2pIface.FreqRange range = new ISupplicantP2pIface.FreqRange();
+                    // The range boundaries are inclusive in native frequency inclusion check.
+                    // Minusing one to avoid affecting neighbors.
+                    range.min = centerFreq - 5 - 1;
+                    range.max = centerFreq + 5 - 1;
+                    ranges.add(range);
+                }
+            }
+            SupplicantResult<Void> result = new SupplicantResult(
+                    "setDisallowedFrequencies(" + ranges + ")");
+            try {
+                result.setResult(mISupplicantP2pIface.setDisallowedFrequencies(ranges));
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+            return result.isSuccess();
+        }
+    }
 
     /**
      * This command can be used to add a upnp/bonjour service.
@@ -2388,6 +2497,42 @@
         }
     }
 
+    /**
+     * Set Wifi Display R2 device info.
+     *
+     * @param info WFD R2 device info as described in section 5.1.12 of WFD technical
+     *        specification v2.1.
+     * @return true, if operation was successful.
+     */
+    public boolean setWfdR2DeviceInfo(String info) {
+        synchronized (mLock) {
+            if (info == null) {
+                Log.e(TAG, "Cannot parse null WFD info string.");
+                return false;
+            }
+            byte[] wfdR2Info = null;
+            try {
+                wfdR2Info = NativeUtil.hexStringToByteArray(info);
+            } catch (Exception e) {
+                Log.e(TAG, "Could not parse WFD R2 Device Info string.");
+                return false;
+            }
+
+            android.hardware.wifi.supplicant.V1_4.ISupplicantP2pIface ifaceV14 =
+                    getSupplicantP2pIfaceAndLogFailureV1_4("setWfdR2DeviceInfo");
+            if (ifaceV14 == null) return false;
+            SupplicantResultV1_4<Void> result = new SupplicantResultV1_4(
+                    "setWfdR2DeviceInfo(" + info + ")");
+            try {
+                result.setResult(ifaceV14.setWfdR2DeviceInfo(wfdR2Info));
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+            return result.isSuccess();
+        }
+    }
+
 
     /**
      * Converts the Wps config method string to the equivalent enum value.
@@ -2434,39 +2579,84 @@
      * Primary purpose is to allow callback lambdas to provide results
      * to parent methods.
      */
-    private static class SupplicantResult<E> {
+    private static class SupplicantResultBase<S, E> {
         private String mMethodName;
-        private SupplicantStatus mStatus;
+        private S mStatus;
         private E mValue;
 
-        SupplicantResult(String methodName) {
+        SupplicantResultBase(String methodName) {
             mMethodName = methodName;
             mStatus = null;
             mValue = null;
             logd("entering " + mMethodName);
         }
 
-        public void setResult(SupplicantStatus status, E value) {
-            logCompletion(mMethodName, status);
+        public void setResult(S status, E value) {
+            if (status == null) {
+                logw(mMethodName + " failed: no status code returned.");
+            } else {
+                logCompletion(mMethodName, getCode(status), getDebugMessage(status));
+            }
             logd("leaving " + mMethodName + " with result = " + value);
             mStatus = status;
             mValue = value;
         }
 
-        public void setResult(SupplicantStatus status) {
-            logCompletion(mMethodName, status);
+        public void setResult(S status) {
+            if (status == null) {
+                logw(mMethodName + " failed: no status code returned.");
+            } else {
+                logCompletion(mMethodName, getCode(status), getDebugMessage(status));
+            }
             logd("leaving " + mMethodName);
             mStatus = status;
         }
 
         public boolean isSuccess() {
             return (mStatus != null
-                    && (mStatus.code == SupplicantStatusCode.SUCCESS
-                    || mStatus.code == SupplicantStatusCode.FAILURE_IFACE_EXISTS));
+                    && (getCode(mStatus) == SupplicantStatusCode.SUCCESS
+                    || getCode(mStatus) == SupplicantStatusCode.FAILURE_IFACE_EXISTS));
         }
 
         public E getResult() {
             return (isSuccess() ? mValue : null);
         }
+
+        protected int getCode(Object obj) {
+            SupplicantStatus status = (SupplicantStatus) obj;
+            return status.code;
+        }
+
+        protected String getDebugMessage(Object obj) {
+            SupplicantStatus status = (SupplicantStatus) obj;
+            return status.debugMessage;
+        }
+    }
+
+    private static class SupplicantResult<E>
+            extends SupplicantResultBase<SupplicantStatus, E> {
+        SupplicantResult(String iface) {
+            super(iface);
+        }
+    }
+
+    private static class SupplicantResultV1_4<E>
+            extends SupplicantResultBase<
+                    android.hardware.wifi.supplicant.V1_4.SupplicantStatus, E> {
+        SupplicantResultV1_4(String iface) {
+            super(iface);
+        }
+
+        protected int getCode(Object obj) {
+            android.hardware.wifi.supplicant.V1_4.SupplicantStatus status =
+                    (android.hardware.wifi.supplicant.V1_4.SupplicantStatus) obj;
+            return status.code;
+        }
+
+        protected String getDebugMessage(Object obj) {
+            android.hardware.wifi.supplicant.V1_4.SupplicantStatus status =
+                    (android.hardware.wifi.supplicant.V1_4.SupplicantStatus) obj;
+            return status.debugMessage;
+        }
     }
 }
diff --git a/service/java/com/android/server/wifi/p2p/WifiP2pMonitor.java b/service/java/com/android/server/wifi/p2p/WifiP2pMonitor.java
index cbf33b4..a0a2667 100644
--- a/service/java/com/android/server/wifi/p2p/WifiP2pMonitor.java
+++ b/service/java/com/android/server/wifi/p2p/WifiP2pMonitor.java
@@ -29,7 +29,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Protocol;
-import com.android.server.wifi.WifiInjector;
 import com.android.server.wifi.p2p.WifiP2pServiceImpl.P2pStatus;
 
 import java.util.HashMap;
@@ -40,8 +39,6 @@
 /**
  * Listens for events from the wpa_supplicant, and passes them on
  * to the {@link WifiP2pServiceImpl} for handling.
- *
- * @hide
  */
 public class WifiP2pMonitor {
     private static final String TAG = "WifiP2pMonitor";
@@ -79,13 +76,7 @@
     public static final int AP_STA_CONNECTED_EVENT               = BASE + 42;
 
 
-    private final WifiInjector mWifiInjector;
     private boolean mVerboseLoggingEnabled = false;
-    private boolean mConnected = false;
-
-    public WifiP2pMonitor(WifiInjector wifiInjector) {
-        mWifiInjector = wifiInjector;
-    }
 
     /**
      * Enable verbose logging for all sub modules.
@@ -134,12 +125,6 @@
         mMonitoringMap.put(iface, enabled);
     }
 
-    private void setMonitoringNone() {
-        for (String iface : mMonitoringMap.keySet()) {
-            setMonitoring(iface, false);
-        }
-    }
-
     /**
      * Start Monitoring for wpa_supplicant events.
      *
@@ -165,16 +150,6 @@
     }
 
     /**
-     * Stop Monitoring for wpa_supplicant events.
-     *
-     * TODO: Add unit tests for these once we remove the legacy code.
-     */
-    public synchronized void stopAllMonitoring() {
-        mConnected = false;
-        setMonitoringNone();
-    }
-
-    /**
      * Similar functions to Handler#sendMessage that send the message to the registered handler
      * for the given interface and message what.
      * All of these should be called with the WifiMonitor class lock
diff --git a/service/java/com/android/server/wifi/p2p/WifiP2pNative.java b/service/java/com/android/server/wifi/p2p/WifiP2pNative.java
index 300b7a1..f26d532 100644
--- a/service/java/com/android/server/wifi/p2p/WifiP2pNative.java
+++ b/service/java/com/android/server/wifi/p2p/WifiP2pNative.java
@@ -19,70 +19,42 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.hardware.wifi.V1_0.IWifiP2pIface;
-import android.hardware.wifi.V1_0.IfaceType;
+import android.net.wifi.CoexUnsafeChannel;
 import android.net.wifi.nl80211.WifiNl80211Manager;
 import android.net.wifi.p2p.WifiP2pConfig;
 import android.net.wifi.p2p.WifiP2pGroup;
 import android.net.wifi.p2p.WifiP2pGroupList;
 import android.net.wifi.p2p.nsd.WifiP2pServiceInfo;
 import android.os.Handler;
+import android.os.WorkSource;
 import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.server.wifi.HalDeviceManager;
 import com.android.server.wifi.PropertyService;
-import com.android.server.wifi.WifiInjector;
 import com.android.server.wifi.WifiNative;
 import com.android.server.wifi.WifiVendorHal;
 
+import java.util.List;
 import java.util.Set;
 
 /**
  * Native calls for bring up/shut down of the supplicant daemon and for
  * sending requests to the supplicant daemon
- *
- * {@hide}
  */
 public class WifiP2pNative {
     private static final String TAG = "WifiP2pNative";
     private boolean mVerboseLoggingEnabled = false;
     private final SupplicantP2pIfaceHal mSupplicantP2pIfaceHal;
-    private final WifiInjector mWifiInjector;
+    private final WifiNative mWifiNative;
+    private final WifiNl80211Manager mWifiNl80211Manager;
     private final HalDeviceManager mHalDeviceManager;
     private final PropertyService mPropertyService;
     private final WifiVendorHal mWifiVendorHal;
     private IWifiP2pIface mIWifiP2pIface;
-    private InterfaceAvailableListenerInternal mInterfaceAvailableListener;
     private InterfaceDestroyedListenerInternal mInterfaceDestroyedListener;
 
     // Internal callback registered to HalDeviceManager.
-    private class InterfaceAvailableListenerInternal implements
-            HalDeviceManager.InterfaceAvailableForRequestListener {
-        private final HalDeviceManager.InterfaceAvailableForRequestListener mExternalListener;
-
-        InterfaceAvailableListenerInternal(
-                HalDeviceManager.InterfaceAvailableForRequestListener externalListener) {
-            mExternalListener = externalListener;
-        }
-
-        @Override
-        public void onAvailabilityChanged(boolean isAvailable) {
-            Log.d(TAG, "P2P InterfaceAvailableListener " + isAvailable);
-            // We need another level of abstraction here. When a P2P interface is created,
-            // we should mask the availability change callback from WifiP2pService.
-            // This is because when the P2P interface is created, we'll get a callback
-            // indicating that we can no longer create a new P2P interface. We don't need to
-            // propagate this internal state to WifiP2pServiceImpl.
-            if (mIWifiP2pIface != null && !isAvailable) {
-                Log.i(TAG, "Masking interface non-availability callback because "
-                        + "we created a P2P iface");
-                return;
-            }
-            mExternalListener.onAvailabilityChanged(isAvailable);
-        }
-    }
-
-    // Internal callback registered to HalDeviceManager.
     private class InterfaceDestroyedListenerInternal implements
             HalDeviceManager.InterfaceDestroyedListener {
         private final HalDeviceManager.InterfaceDestroyedListener mExternalListener;
@@ -114,10 +86,15 @@
         }
     }
 
-    public WifiP2pNative(WifiInjector wifiInjector, WifiVendorHal wifiVendorHal,
-            SupplicantP2pIfaceHal p2pIfaceHal, HalDeviceManager halDeviceManager,
+    public WifiP2pNative(
+            WifiNl80211Manager wifiNl80211Manager,
+            WifiNative wifiNative,
+            WifiVendorHal wifiVendorHal,
+            SupplicantP2pIfaceHal p2pIfaceHal,
+            HalDeviceManager halDeviceManager,
             PropertyService propertyService) {
-        mWifiInjector = wifiInjector;
+        mWifiNative = wifiNative;
+        mWifiNl80211Manager = wifiNl80211Manager;
         mWifiVendorHal = wifiVendorHal;
         mSupplicantP2pIfaceHal = p2pIfaceHal;
         mHalDeviceManager = halDeviceManager;
@@ -180,10 +157,10 @@
      * For devices which do not the support the HAL, this will bypass HalDeviceManager &
      * teardown any existing iface.
      */
-    private String createP2pIface(Handler handler) {
+    private String createP2pIface(Handler handler, WorkSource requestorWs) {
         if (mHalDeviceManager.isSupported()) {
             mIWifiP2pIface = mHalDeviceManager
-                                .createP2pIface(mInterfaceDestroyedListener, handler);
+                                .createP2pIface(mInterfaceDestroyedListener, handler, requestorWs);
             if (mIWifiP2pIface == null) {
                 Log.e(TAG, "Failed to create P2p iface in HalDeviceManager");
                 return null;
@@ -202,43 +179,20 @@
     }
 
     /**
-     * Register for an interface available callbacks from HalDeviceManager.
-     *
-     * @param listener callback to be invoked when the interface is available/not available.
-     */
-    public void registerInterfaceAvailableListener(
-            @NonNull HalDeviceManager.InterfaceAvailableForRequestListener listener,
-            Handler handler) {
-        mInterfaceAvailableListener = new InterfaceAvailableListenerInternal(listener);
-        // The interface available callbacks are cleared on every HAL stop, so need to
-        // re-register these callbacks on every start.
-        mHalDeviceManager.registerStatusListener(() -> {
-            if (mHalDeviceManager.isStarted()) {
-                Log.i(TAG, "Registering for interface available listener");
-                mHalDeviceManager.registerInterfaceAvailableForRequestListener(
-                        IfaceType.P2P, mInterfaceAvailableListener, handler);
-            }
-        }, handler);
-        if (mHalDeviceManager.isStarted()) {
-            mHalDeviceManager.registerInterfaceAvailableForRequestListener(
-                    IfaceType.P2P, mInterfaceAvailableListener, handler);
-        }
-    }
-
-    /**
      * Setup Interface for P2p mode.
      *
      * @param destroyedListener Listener to be invoked when the interface is destroyed.
      * @param handler Handler to be used for invoking the destroyedListener.
+     * @param requestorWs Worksource to attribute the request to.
      */
     public String setupInterface(
             @NonNull HalDeviceManager.InterfaceDestroyedListener destroyedListener,
-            Handler handler) {
+            @NonNull Handler handler, @NonNull WorkSource requestorWs) {
         Log.d(TAG, "Setup P2P interface");
         if (mIWifiP2pIface == null) {
             mInterfaceDestroyedListener =
                     new InterfaceDestroyedListenerInternal(destroyedListener);
-            String ifaceName = createP2pIface(handler);
+            String ifaceName = createP2pIface(handler, requestorWs);
             if (ifaceName == null) {
                 Log.e(TAG, "Failed to create P2p iface");
                 return null;
@@ -283,6 +237,19 @@
     }
 
     /**
+     * Replace requestorWs in-place when iface is already enabled.
+     */
+    public boolean replaceRequestorWs(WorkSource requestorWs) {
+        if (mHalDeviceManager.isSupported()) {
+            if (mIWifiP2pIface == null) return false;
+            return mHalDeviceManager.replaceRequestorWs(mIWifiP2pIface, requestorWs);
+        } else {
+            Log.i(TAG, "HAL (HIDL) is not supported. Ignore replace requestorWs");
+            return true;
+        }
+    }
+
+    /**
      * Set WPS device name.
      *
      * @param name String to be set.
@@ -495,13 +462,26 @@
      * operating class (180).
      *
      * @param lc Wifi channel. eg, 1, 6, 11.
-     * @param oc Operating Class indicates the channel set of the AP
-     *        indicated by this BSSID
      *
      * @return true, if operation was successful.
      */
-    public boolean p2pSetChannel(int lc, int oc) {
-        return mSupplicantP2pIfaceHal.setListenChannel(lc, oc);
+    public boolean p2pSetListenChannel(int lc) {
+        return mSupplicantP2pIfaceHal.setListenChannel(lc);
+    }
+
+    /**
+     * Set P2P operating channel.
+     *
+     * @param oc Wifi channel, eg, 1, 6, 11.
+     * @param unsafeChannels channels are not allowed.
+     * @return true if operation was successful.
+     */
+    public boolean p2pSetOperatingChannel(int oc, @NonNull List<CoexUnsafeChannel> unsafeChannels) {
+        if (null == unsafeChannels) {
+            Log.wtf(TAG, "unsafeChannels is null.");
+            return false;
+        }
+        return mSupplicantP2pIfaceHal.setOperatingChannel(oc, unsafeChannels);
     }
 
     /**
@@ -615,12 +595,10 @@
     private void abortWifiRunningScanIfNeeded(boolean isJoin) {
         if (!isJoin) return;
 
-        WifiNl80211Manager wifiCondManager = mWifiInjector.getWifiCondManager();
-        WifiNative wifiNative = mWifiInjector.getWifiNative();
-        Set<String> wifiClientInterfaces = wifiNative.getClientInterfaceNames();
+        Set<String> wifiClientInterfaces = mWifiNative.getClientInterfaceNames();
 
         for (String interfaceName: wifiClientInterfaces) {
-            wifiCondManager.abortScan(interfaceName);
+            mWifiNl80211Manager.abortScan(interfaceName);
         }
     }
 
@@ -865,4 +843,15 @@
     public long getSupportedFeatureSet(@NonNull String ifaceName) {
         return mWifiVendorHal.getSupportedFeatureSet(ifaceName);
     }
+
+    /**
+     * Set Wifi Display R2 device info.
+     *
+     * @param hex WFD device info as described in section 5.1.12 of WFD technical
+     *        specification v2.1.0.
+     * @return true, if operation was successful.
+     */
+    public boolean setWfdR2DeviceInfo(String hex) {
+        return mSupplicantP2pIfaceHal.setWfdR2DeviceInfo(hex);
+    }
 }
diff --git a/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java b/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
index eb2a89c..6410af2 100644
--- a/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
+++ b/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
@@ -44,6 +44,7 @@
 import android.net.ip.IpClientCallbacks;
 import android.net.ip.IpClientUtil;
 import android.net.shared.ProvisioningConfiguration;
+import android.net.wifi.CoexUnsafeChannel;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
@@ -73,8 +74,10 @@
 import android.os.Messenger;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.WorkSource;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
@@ -92,12 +95,17 @@
 import com.android.internal.util.Protocol;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.android.internal.util.WakeupMessage;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.FrameworkFacade;
+import com.android.server.wifi.WifiGlobals;
 import com.android.server.wifi.WifiInjector;
 import com.android.server.wifi.WifiLog;
 import com.android.server.wifi.WifiSettingsConfigStore;
+import com.android.server.wifi.coex.CoexManager;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.P2pConnectionEvent;
 import com.android.server.wifi.util.NetdWrapper;
+import com.android.server.wifi.util.StringUtil;
 import com.android.server.wifi.util.WifiAsyncChannel;
 import com.android.server.wifi.util.WifiHandler;
 import com.android.server.wifi.util.WifiPermissionsUtil;
@@ -118,6 +126,8 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
 
 /**
  * WifiP2pService includes a state machine to perform Wi-Fi p2p operations. Applications
@@ -131,11 +141,31 @@
  */
 public class WifiP2pServiceImpl extends IWifiP2pManager.Stub {
     private static final String TAG = "WifiP2pService";
+    @VisibleForTesting
+    public static final String P2P_IDLE_SHUTDOWN_MESSAGE_TIMEOUT_TAG = TAG
+            + " Idle Shutdown Message Timeout";
     private boolean mVerboseLoggingEnabled = false;
     private static final String NETWORKTYPE = "WIFI_P2P";
     @VisibleForTesting
+    static final String DEFAULT_DEVICE_NAME_PREFIX = "Android_";
+    // The maxinum length of the device name is 32 bytes, see
+    // Section 4.1.15 in Wi-Fi Direct Specification v1 and
+    // Section 12 in Wi-Fi Protected Setup Specification v2.
+    @VisibleForTesting
+    static final int DEVICE_NAME_LENGTH_MAX = 32;
+    @VisibleForTesting
+    static final int DEVICE_NAME_POSTFIX_LENGTH_MIN = 4;
+    @VisibleForTesting
+    static final int DEVICE_NAME_PREFIX_LENGTH_MAX =
+            DEVICE_NAME_LENGTH_MAX - DEVICE_NAME_POSTFIX_LENGTH_MIN;
+    @VisibleForTesting
     static final int DEFAULT_GROUP_OWNER_INTENT = 6;
 
+    @VisibleForTesting
+    // It requires to over "DISCOVER_TIMEOUT_S(120)" or "GROUP_CREATING_WAIT_TIME_MS(120)".
+    // Otherwise it will cause interface down before function timeout.
+    static final long P2P_INTERFACE_IDLE_SHUTDOWN_TIMEOUT_MS = 150_000;
+
     private Context mContext;
 
     NetdWrapper mNetdWrapper;
@@ -152,6 +182,9 @@
     private FrameworkFacade mFrameworkFacade;
     private WifiSettingsConfigStore mSettingsConfigStore;
     private WifiP2pMetrics mWifiP2pMetrics;
+    // This will only be null if SdkLevel is not at least S
+    @Nullable private CoexManager mCoexManager;
+    private WifiGlobals mWifiGlobals;
 
     private static final Boolean JOIN_GROUP = true;
     private static final Boolean FORM_GROUP = false;
@@ -231,6 +264,8 @@
     public static final int ENABLE_P2P                      =   BASE + 16;
     public static final int DISABLE_P2P                     =   BASE + 17;
     public static final int REMOVE_CLIENT_INFO              =   BASE + 18;
+    // idle shutdown message
+    public static final int CMD_P2P_IDLE_SHUTDOWN           =   BASE + 19;
 
     // Messages for interaction with IpClient.
     private static final int IPC_PRE_DHCP_ACTION            =   BASE + 30;
@@ -241,9 +276,16 @@
 
     private static final int GROUP_OWNER_TETHER_READY       =   BASE + 35;
 
+    private static final int UPDATE_P2P_DISALLOWED_CHANNELS =   BASE + 36;
+
     public static final int ENABLED                         = 1;
     public static final int DISABLED                        = 0;
 
+    private static final int P2P_CONNECT_TRIGGER_GROUP_NEG_REQ      = 1;
+    private static final int P2P_CONNECT_TRIGGER_INVITATION_REQ     = 2;
+    private static final int P2P_CONNECT_TRIGGER_OTHER              = 3;
+
+
     private final boolean mP2pSupported;
 
     private final WifiP2pDevice mThisDevice = new WifiP2pDevice();
@@ -272,7 +314,7 @@
     private boolean mTemporarilyDisconnectedWifi = false;
 
     // The transaction Id of service discovery request
-    private byte mServiceTransactionId = 0;
+    private int mServiceTransactionId = 0;
 
     // Service discovery request ID of wpa_supplicant.
     // null means it's not set yet.
@@ -291,6 +333,10 @@
     // latter from leaking to apps
     private static final String ANONYMIZED_DEVICE_ADDRESS = "02:00:00:00:00:00";
 
+    // Idle shut down
+    @VisibleForTesting
+    public WakeupMessage mP2pIdleShutdownMessage;
+
     /**
      * Error code definition.
      * see the Table.8 in the WiFi Direct specification for the detail.
@@ -458,21 +504,24 @@
     }
 
     private class DeathHandlerData {
-        DeathHandlerData(DeathRecipient dr, Messenger m) {
+        DeathHandlerData(DeathRecipient dr, Messenger m, WorkSource ws) {
             mDeathRecipient = dr;
             mMessenger = m;
+            mWorkSource = ws;
         }
 
         @Override
         public String toString() {
-            return "deathRecipient=" + mDeathRecipient + ", messenger=" + mMessenger;
+            return "deathRecipient=" + mDeathRecipient + ", messenger=" + mMessenger
+                    + ", worksource=" + mWorkSource;
         }
 
-        DeathRecipient mDeathRecipient;
-        Messenger mMessenger;
+        final DeathRecipient mDeathRecipient;
+        final Messenger mMessenger;
+        final WorkSource mWorkSource;
     }
     private Object mLock = new Object();
-    private final Map<IBinder, DeathHandlerData> mDeathDataByBinder = new HashMap<>();
+    private final Map<IBinder, DeathHandlerData> mDeathDataByBinder = new ConcurrentHashMap<>();
 
     public WifiP2pServiceImpl(Context context, WifiInjector wifiInjector) {
         mContext = context;
@@ -481,6 +530,8 @@
         mFrameworkFacade = mWifiInjector.getFrameworkFacade();
         mSettingsConfigStore = mWifiInjector.getSettingsConfigStore();
         mWifiP2pMetrics = mWifiInjector.getWifiP2pMetrics();
+        mCoexManager = mWifiInjector.getCoexManager();
+        mWifiGlobals = mWifiInjector.getWifiGlobals();
 
         mDetailedState = NetworkInfo.DetailedState.IDLE;
 
@@ -539,7 +590,7 @@
         mIpClientStartIndex++;
         if (mIpClient != null) {
             try {
-                mIpClient.stop();
+                mIpClient.shutdown();
             } catch (RemoteException e) {
                 e.rethrowFromSystemServer();
             }
@@ -614,7 +665,7 @@
      * an AsyncChannel communication with WifiP2pService
      */
     @Override
-    public Messenger getMessenger(final IBinder binder) {
+    public Messenger getMessenger(final IBinder binder, final String packageName) {
         enforceAccessPermission();
         enforceChangePermission();
 
@@ -630,15 +681,21 @@
                 close(binder);
             };
 
+            WorkSource ws = packageName != null
+                    ? new WorkSource(Binder.getCallingUid(), packageName)
+                    : new WorkSource(Binder.getCallingUid());
             try {
                 binder.linkToDeath(dr, 0);
-                mDeathDataByBinder.put(binder, new DeathHandlerData(dr, messenger));
+                mDeathDataByBinder.put(binder, new DeathHandlerData(dr, messenger, ws));
             } catch (RemoteException e) {
                 Log.e(TAG, "Error on linkToDeath: e=" + e);
                 // fall-through here - won't clean up
             }
-            mP2pStateMachine.sendMessage(ENABLE_P2P);
-
+            // If p2p is already on, send ENABLE_P2P to merge the new worksource.
+            // If p2p is off, the first one activates P2P will merge all worksources.
+            if (!mP2pStateMachine.isP2pDisabled()) {
+                mP2pStateMachine.sendMessage(ENABLE_P2P);
+            }
             return messenger;
         }
     }
@@ -780,6 +837,10 @@
         private final WifiP2pDeviceList mPeers = new WifiP2pDeviceList();
         private String mInterfaceName;
 
+        private List<CoexUnsafeChannel> mCoexUnsafeChannels = new ArrayList<>();
+        private int mUserListenChannel = 0;
+        private int mUserOperatingChannel = 0;
+
         // During a connection, supplicant can tell us that a device was lost. From a supplicant's
         // perspective, the discovery stops during connection and it purges device since it does
         // not get latest updates about the device without being in discovery state.
@@ -799,8 +860,6 @@
                 });
         private final WifiP2pInfo mWifiP2pInfo = new WifiP2pInfo();
         private WifiP2pGroup mGroup;
-        // Is the HAL (HIDL) interface available for use.
-        private boolean mIsHalInterfaceAvailable = false;
         // Is wifi on or off.
         private boolean mIsWifiEnabled = false;
 
@@ -839,6 +898,12 @@
             setLogOnlyTransitions(true);
 
             if (p2pSupported) {
+                // Init p2p idle shutdown message
+                mP2pIdleShutdownMessage = new WakeupMessage(mContext,
+                                  this.getHandler(),
+                                  P2P_IDLE_SHUTDOWN_MESSAGE_TIMEOUT_TAG,
+                                  CMD_P2P_IDLE_SHUTDOWN);
+
                 // Register for wifi on/off broadcasts
                 mContext.registerReceiver(new BroadcastReceiver() {
                     @Override
@@ -847,7 +912,6 @@
                                 WifiManager.WIFI_STATE_UNKNOWN);
                         if (wifistate == WifiManager.WIFI_STATE_ENABLED) {
                             mIsWifiEnabled = true;
-                            checkAndReEnableP2p();
                         } else {
                             mIsWifiEnabled = false;
                             // Teardown P2P if it's up already.
@@ -887,19 +951,65 @@
                         }
                     }
                 }, new IntentFilter(TetheringManager.ACTION_TETHER_STATE_CHANGED));
-                // Register for interface availability from HalDeviceManager
-                mWifiNative.registerInterfaceAvailableListener((boolean isAvailable) -> {
-                    mIsHalInterfaceAvailable = isAvailable;
-                    checkAndSendP2pStateChangedBroadcast();
-                }, getHandler());
-
                 mSettingsConfigStore.registerChangeListener(
                         WIFI_VERBOSE_LOGGING_ENABLED,
                         (key, newValue) -> enableVerboseLogging(newValue),
                         getHandler());
+                if (SdkLevel.isAtLeastS()) {
+                    mCoexManager.registerCoexListener(new CoexManager.CoexListener() {
+                        @Override
+                        public void onCoexUnsafeChannelsChanged() {
+                            checkCoexUnsafeChannels();
+                        }
+                    });
+                }
             }
         }
 
+        boolean isP2pDisabled() {
+            return getCurrentState() == mP2pDisabledState;
+        }
+
+        void scheduleIdleShutdown() {
+            if (mP2pIdleShutdownMessage != null) {
+                mP2pIdleShutdownMessage.cancel();
+                mP2pIdleShutdownMessage.schedule(SystemClock.elapsedRealtime()
+                        + P2P_INTERFACE_IDLE_SHUTDOWN_TIMEOUT_MS);
+                if (mVerboseLoggingEnabled) {
+                    Log.d(TAG, "IdleShutDown message (re)scheduled in "
+                            + (P2P_INTERFACE_IDLE_SHUTDOWN_TIMEOUT_MS / 1000) + "s");
+                }
+            }
+            mP2pStateMachine.getHandler().removeMessages(CMD_P2P_IDLE_SHUTDOWN);
+        }
+
+        void cancelIdleShutdown() {
+            if (mP2pIdleShutdownMessage != null) {
+                mP2pIdleShutdownMessage.cancel();
+                if (mVerboseLoggingEnabled) {
+                    Log.d(TAG, "IdleShutDown message canceled");
+                }
+            }
+            mP2pStateMachine.getHandler().removeMessages(CMD_P2P_IDLE_SHUTDOWN);
+        }
+
+        void checkCoexUnsafeChannels() {
+            List<CoexUnsafeChannel> unsafeChannels = null;
+
+            // If WIFI DIRECT bit is not set, pass null to clear unsafe channels.
+            if (SdkLevel.isAtLeastS()
+                    && (mCoexManager.getCoexRestrictions()
+                    & WifiManager.COEX_RESTRICTION_WIFI_DIRECT) != 0) {
+                unsafeChannels = mCoexManager.getCoexUnsafeChannels();
+                Log.d(TAG, "UnsafeChannels: "
+                        + unsafeChannels.stream()
+                                .map(Object::toString)
+                                .collect(Collectors.joining(",")));
+            }
+
+            sendMessage(UPDATE_P2P_DISALLOWED_CHANNELS, unsafeChannels);
+        }
+
         /**
          * Enable verbose logging for all sub modules.
          */
@@ -958,6 +1068,14 @@
             mWifiMonitor.startMonitoring(mInterfaceName);
         }
 
+        private WorkSource createMergedRequestorWs() {
+            WorkSource requestorWs = new WorkSource();
+            for (DeathHandlerData deathHandlerData : mDeathDataByBinder.values()) {
+                requestorWs.add(deathHandlerData.mWorkSource);
+            }
+            return requestorWs;
+        }
+
         class DefaultState extends State {
             @Override
             public boolean processMessage(Message message) {
@@ -1107,7 +1225,7 @@
                         break;
                     case WifiP2pManager.REQUEST_P2P_STATE:
                         replyToMessage(message, WifiP2pManager.RESPONSE_P2P_STATE,
-                                (mIsWifiEnabled && isHalInterfaceAvailable())
+                                mIsWifiEnabled
                                 ? WifiP2pManager.WIFI_P2P_STATE_ENABLED
                                 : WifiP2pManager.WIFI_P2P_STATE_DISABLED);
                         break;
@@ -1155,6 +1273,7 @@
                     case IPC_PROVISIONING_SUCCESS:
                     case IPC_PROVISIONING_FAILURE:
                     case GROUP_OWNER_TETHER_READY:
+                    case UPDATE_P2P_DISALLOWED_CHANNELS:
                     case WifiP2pMonitor.P2P_PROV_DISC_FAILURE_EVENT:
                     case SET_MIRACAST_MODE:
                     case WifiP2pManager.START_LISTEN:
@@ -1410,32 +1529,38 @@
                 }
             }
 
+            private boolean setupInterface() {
+                if (!mIsWifiEnabled) {
+                    Log.e(TAG, "Ignore P2P enable since wifi is " + mIsWifiEnabled);
+                    return false;
+                }
+                WorkSource requestorWs = createMergedRequestorWs();
+                mInterfaceName = mWifiNative.setupInterface((String ifaceName) -> {
+                    sendMessage(DISABLE_P2P);
+                    checkAndSendP2pStateChangedBroadcast();
+                }, getHandler(), requestorWs);
+                if (mInterfaceName == null) {
+                    Log.e(TAG, "Failed to setup interface for P2P");
+                    return false;
+                }
+                setupInterfaceFeatures(mInterfaceName);
+                try {
+                    mNetdWrapper.setInterfaceUp(mInterfaceName);
+                } catch (IllegalStateException ie) {
+                    loge("Unable to change interface settings: " + ie);
+                }
+                registerForWifiMonitorEvents();
+                return true;
+            }
+
             @Override
             public boolean processMessage(Message message) {
                 if (mVerboseLoggingEnabled) logd(getName() + message.toString());
                 switch (message.what) {
                     case ENABLE_P2P:
-                        if (!mIsWifiEnabled) {
-                            Log.e(TAG, "Ignore P2P enable since wifi is " + mIsWifiEnabled);
-                            break;
+                        if (setupInterface()) {
+                            transitionTo(mInactiveState);
                         }
-                        mInterfaceName = mWifiNative.setupInterface((String ifaceName) -> {
-                            mIsHalInterfaceAvailable = false;
-                            sendMessage(DISABLE_P2P);
-                            checkAndSendP2pStateChangedBroadcast();
-                        }, getHandler());
-                        if (mInterfaceName == null) {
-                            Log.e(TAG, "Failed to setup interface for P2P");
-                            break;
-                        }
-                        setupInterfaceFeatures(mInterfaceName);
-                        try {
-                            mNetdWrapper.setInterfaceUp(mInterfaceName);
-                        } catch (IllegalStateException ie) {
-                            loge("Unable to change interface settings: " + ie);
-                        }
-                        registerForWifiMonitorEvents();
-                        transitionTo(mInactiveState);
                         break;
                     case REMOVE_CLIENT_INFO:
                         if (!(message.obj instanceof IBinder)) {
@@ -1452,7 +1577,33 @@
                         }
                         break;
                     default:
-                        return NOT_HANDLED;
+                        // only handle commands from clients and only commands
+                        // which require P2P to be active.
+                        if (message.what < Protocol.BASE_WIFI_P2P_MANAGER
+                                || Protocol.BASE_WIFI_P2P_SERVICE <= message.what
+                                || message.what == WifiP2pManager.UPDATE_CHANNEL_INFO) {
+                            return NOT_HANDLED;
+                        }
+                        // If P2P is not ready, it might be disabled due
+                        // to another interface, ex. turn on softap from
+                        // the quicksettings.
+                        // As the new priority scheme, the foreground app
+                        // might be able to use P2P, so just try to enable
+                        // it.
+                        // Check & re-enable P2P if needed.
+                        // P2P interface will be created if all of the below are true:
+                        // a) Wifi is enabled.
+                        // b) There is at least 1 client app which invoked initialize().
+                        if (mVerboseLoggingEnabled) {
+                            Log.d(TAG, "Wifi enabled=" + mIsWifiEnabled + ", Number of clients="
+                                    + mDeathDataByBinder.size());
+                        }
+                        if (!mIsWifiEnabled) return NOT_HANDLED;
+                        if (mDeathDataByBinder.isEmpty()) return NOT_HANDLED;
+                        if (!setupInterface()) return NOT_HANDLED;
+                        deferMessage(message);
+                        transitionTo(mInactiveState);
+                        break;
                 }
                 return HANDLED;
             }
@@ -1467,6 +1618,8 @@
                     factoryReset(Process.SYSTEM_UID);
                 }
 
+                checkCoexUnsafeChannels();
+
                 sendP2pConnectionChangedBroadcast();
                 initializeP2pSettings();
             }
@@ -1480,7 +1633,9 @@
                         transitionTo(mP2pDisabledState);
                         break;
                     case ENABLE_P2P:
-                        // Nothing to do
+                        if (!mWifiNative.replaceRequestorWs(createMergedRequestorWs())) {
+                            Log.e(TAG, "Failed to replace requestorWs");
+                        }
                         break;
                     case DISABLE_P2P:
                         if (mPeers.clear()) {
@@ -1501,6 +1656,9 @@
                         // clear client info and remove it from list
                         clearClientInfo(mClientChannelList.get(b));
                         mClientChannelList.remove(b);
+                        if (!mWifiNative.replaceRequestorWs(createMergedRequestorWs())) {
+                            Log.e(TAG, "Failed to replace requestorWs");
+                        }
                         break;
                     case WifiP2pManager.SET_DEVICE_NAME:
                     {
@@ -1547,7 +1705,9 @@
                         }
                         if (!blocked && mDiscoveryPostponed) {
                             mDiscoveryPostponed = false;
-                            mWifiNative.p2pFind(DISCOVER_TIMEOUT_S);
+                            if (mWifiNative.p2pFind(DISCOVER_TIMEOUT_S)) {
+                                sendP2pDiscoveryChangedBroadcast(true);
+                            }
                         }
                         if (blocked && mWifiChannel != null) {
                             mWifiChannel.replyToMessage(message, message.arg2);
@@ -1612,6 +1772,7 @@
                             break;
                         }
                         if (mWifiNative.p2pFind(DISCOVER_TIMEOUT_S)) {
+                            sendP2pDiscoveryChangedBroadcast(true);
                             mWifiP2pMetrics.incrementServiceScans();
                             replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_SUCCEEDED);
                         } else {
@@ -1763,13 +1924,14 @@
                                     WifiP2pManager.ERROR);
                             break;
                         }
-                        Bundle p2pChannels = (Bundle) message.obj;
-                        int lc = p2pChannels.getInt("lc", 0);
-                        int oc = p2pChannels.getInt("oc", 0);
-                        if (mVerboseLoggingEnabled) {
-                            logd(getName() + " set listen and operating channel");
+                        if (message.obj == null) {
+                            Log.e(TAG, "Illegal arguments(s)");
+                            break;
                         }
-                        if (mWifiNative.p2pSetChannel(lc, oc)) {
+                        Bundle p2pChannels = (Bundle) message.obj;
+                        mUserListenChannel = p2pChannels.getInt("lc", 0);
+                        mUserOperatingChannel = p2pChannels.getInt("oc", 0);
+                        if (updateP2pChannels()) {
                             replyToMessage(message, WifiP2pManager.SET_CHANNEL_SUCCEEDED);
                         } else {
                             replyToMessage(message, WifiP2pManager.SET_CHANNEL_FAILED);
@@ -1789,6 +1951,13 @@
                         replyToMessage(message, WifiP2pManager.RESPONSE_GET_HANDOVER_MESSAGE,
                                 selectBundle);
                         break;
+                    case UPDATE_P2P_DISALLOWED_CHANNELS:
+                        mCoexUnsafeChannels.clear();
+                        if (null != message.obj) {
+                            mCoexUnsafeChannels.addAll((List<CoexUnsafeChannel>) message.obj);
+                        }
+                        updateP2pChannels();
+                        break;
                     default:
                         return NOT_HANDLED;
                 }
@@ -1798,6 +1967,9 @@
             @Override
             public void exit() {
                 sendP2pDiscoveryChangedBroadcast(false);
+                mUserListenChannel = 0;
+                mUserOperatingChannel = 0;
+                mCoexUnsafeChannels.clear();
             }
         }
 
@@ -1806,11 +1978,24 @@
             public void enter() {
                 if (mVerboseLoggingEnabled) logd(getName());
                 mSavedPeerConfig.invalidate();
+                mDetailedState = NetworkInfo.DetailedState.IDLE;
+                scheduleIdleShutdown();
+            }
+
+            @Override
+            public void exit() {
+                cancelIdleShutdown();
             }
 
             @Override
             public boolean processMessage(Message message) {
                 if (mVerboseLoggingEnabled) logd(getName() + message.toString());
+                // Re-schedule the shutdown timer since we got the new operation.
+                // only handle commands from clients.
+                if (message.what > Protocol.BASE_WIFI_P2P_MANAGER
+                        && message.what < Protocol.BASE_WIFI_P2P_SERVICE) {
+                    scheduleIdleShutdown();
+                }
                 switch (message.what) {
                     case WifiP2pManager.CONNECT:
                         if (!mWifiPermissionsUtil.checkCanAccessWifiDirect(
@@ -1846,7 +2031,7 @@
                             } else {
                                 mAutonomousGroup = false;
                                 mWifiNative.p2pStopFind();
-                                if (reinvokePersistentGroup(config)) {
+                                if (reinvokePersistentGroup(config, false)) {
                                     mWifiP2pMetrics.startConnectionEvent(
                                             P2pConnectionEvent.CONNECTION_REINVOKE,
                                             config);
@@ -1880,6 +2065,10 @@
                                     WifiP2pManager.ERROR);
                         }
                         break;
+                    case CMD_P2P_IDLE_SHUTDOWN:
+                        Log.d(TAG, "IdleShutDown message received");
+                        sendMessage(DISABLE_P2P);
+                        break;
                     case WifiP2pMonitor.P2P_GO_NEGOTIATION_REQUEST_EVENT:
                         config = (WifiP2pConfig) message.obj;
                         if (isConfigInvalid(config)) {
@@ -2094,12 +2283,9 @@
                             break;
                         }
                         Bundle p2pChannels = (Bundle) message.obj;
-                        int lc = p2pChannels.getInt("lc", 0);
-                        int oc = p2pChannels.getInt("oc", 0);
-                        if (mVerboseLoggingEnabled) {
-                            logd(getName() + " set listen and operating channel");
-                        }
-                        if (mWifiNative.p2pSetChannel(lc, oc)) {
+                        mUserListenChannel = p2pChannels.getInt("lc", 0);
+                        mUserOperatingChannel = p2pChannels.getInt("oc", 0);
+                        if (updateP2pChannels()) {
                             replyToMessage(message, WifiP2pManager.SET_CHANNEL_SUCCEEDED);
                         } else {
                             replyToMessage(message, WifiP2pManager.SET_CHANNEL_FAILED);
@@ -2232,7 +2418,8 @@
                 switch (message.what) {
                     case PEER_CONNECTION_USER_ACCEPT:
                         mWifiNative.p2pStopFind();
-                        p2pConnectWithPinDisplay(mSavedPeerConfig);
+                        p2pConnectWithPinDisplay(mSavedPeerConfig,
+                                                 P2P_CONNECT_TRIGGER_GROUP_NEG_REQ);
                         mPeers.updateStatus(mSavedPeerConfig.deviceAddress, WifiP2pDevice.INVITED);
                         sendPeersChangedBroadcast();
                         transitionTo(mGroupNegotiationState);
@@ -2276,9 +2463,10 @@
                 switch (message.what) {
                     case PEER_CONNECTION_USER_ACCEPT:
                         mWifiNative.p2pStopFind();
-                        if (!reinvokePersistentGroup(mSavedPeerConfig)) {
+                        if (!reinvokePersistentGroup(mSavedPeerConfig, true)) {
                             // Do negotiation when persistence fails
-                            p2pConnectWithPinDisplay(mSavedPeerConfig);
+                            p2pConnectWithPinDisplay(mSavedPeerConfig,
+                                                     P2P_CONNECT_TRIGGER_INVITATION_REQ);
                         }
                         mPeers.updateStatus(mSavedPeerConfig.deviceAddress, WifiP2pDevice.INVITED);
                         sendPeersChangedBroadcast();
@@ -2328,7 +2516,7 @@
                         }
                         if (mSavedPeerConfig.wps.setup == WpsInfo.PBC) {
                             if (mVerboseLoggingEnabled) logd("Found a match " + mSavedPeerConfig);
-                            p2pConnectWithPinDisplay(mSavedPeerConfig);
+                            p2pConnectWithPinDisplay(mSavedPeerConfig, P2P_CONNECT_TRIGGER_OTHER);
                             transitionTo(mGroupNegotiationState);
                         }
                         break;
@@ -2347,7 +2535,8 @@
                             if (mVerboseLoggingEnabled) logd("Found a match " + mSavedPeerConfig);
                             // we already have the pin
                             if (!TextUtils.isEmpty(mSavedPeerConfig.wps.pin)) {
-                                p2pConnectWithPinDisplay(mSavedPeerConfig);
+                                p2pConnectWithPinDisplay(mSavedPeerConfig,
+                                                         P2P_CONNECT_TRIGGER_OTHER);
                                 transitionTo(mGroupNegotiationState);
                             } else {
                                 mJoinExistingGroup = false;
@@ -2372,7 +2561,7 @@
                         if (mSavedPeerConfig.wps.setup == WpsInfo.DISPLAY) {
                             if (mVerboseLoggingEnabled) logd("Found a match " + mSavedPeerConfig);
                             mSavedPeerConfig.wps.pin = provDisc.pin;
-                            p2pConnectWithPinDisplay(mSavedPeerConfig);
+                            p2pConnectWithPinDisplay(mSavedPeerConfig, P2P_CONNECT_TRIGGER_OTHER);
                             notifyInvitationSent(provDisc.pin, device.deviceAddress);
                             transitionTo(mGroupNegotiationState);
                         }
@@ -2447,11 +2636,8 @@
                             // As a result, P2P sends a unicast intent to tether service to trigger
                             // the whole flow before entering GroupCreatedState.
                             setWifiP2pInfoOnGroupFormation(null);
-                            String tetheringServicePackage = findTetheringServicePackage();
-                            if (!TextUtils.isEmpty(tetheringServicePackage)) {
-                                sendP2pTetherRequestBroadcast(tetheringServicePackage);
-                            } else {
-                                loge("No valid tethering service, remove " + mGroup);
+                            if (!sendP2pTetherRequestBroadcast()) {
+                                loge("Cannot start tethering, remove " + mGroup);
                                 mWifiNative.p2pGroupRemove(mGroup.getInterface());
                             }
                             break;
@@ -2527,7 +2713,7 @@
 
                             // Reinvocation has failed, try group negotiation
                             mSavedPeerConfig.netId = WifiP2pGroup.NETWORK_ID_PERSISTENT;
-                            p2pConnectWithPinDisplay(mSavedPeerConfig);
+                            p2pConnectWithPinDisplay(mSavedPeerConfig, P2P_CONNECT_TRIGGER_OTHER);
                         } else if (status == P2pStatus.INFORMATION_IS_CURRENTLY_UNAVAILABLE) {
 
                             // Devices setting persistent_reconnect to 0 in wpa_supplicant
@@ -2535,7 +2721,7 @@
                             // "information is currently unavailable" error.
                             // So, try another way to connect for interoperability.
                             mSavedPeerConfig.netId = WifiP2pGroup.NETWORK_ID_PERSISTENT;
-                            p2pConnectWithPinDisplay(mSavedPeerConfig);
+                            p2pConnectWithPinDisplay(mSavedPeerConfig, P2P_CONNECT_TRIGGER_OTHER);
                         } else if (status == P2pStatus.NO_COMMON_CHANNEL) {
                             transitionTo(mFrequencyConflictState);
                         } else {
@@ -2573,7 +2759,7 @@
                 logd("Notify frequency conflict");
                 Resources r = mContext.getResources();
 
-                AlertDialog dialog = new AlertDialog.Builder(mContext)
+                AlertDialog dialog = mFrameworkFacade.makeAlertDialogBuilder(mContext)
                         .setMessage(r.getString(R.string.wifi_p2p_frequency_conflict_message,
                             getDeviceName(mSavedPeerConfig.deviceAddress)))
                         .setPositiveButton(r.getString(R.string.dlg_ok), new OnClickListener() {
@@ -2776,7 +2962,6 @@
                             logd("mDhcpResultsParcelable: " + mDhcpResultsParcelable);
                         }
                         setWifiP2pInfoOnGroupFormation(mDhcpResultsParcelable.serverAddress);
-                        sendP2pConnectionChangedBroadcast();
                         try {
                             final String ifname = mGroup.getInterface();
                             if (mDhcpResultsParcelable != null) {
@@ -2787,6 +2972,7 @@
                         } catch (Exception e) {
                             loge("Failed to add iface to local network " + e);
                         }
+                        sendP2pConnectionChangedBroadcast();
                         break;
                     case IPC_PROVISIONING_SUCCESS:
                         break;
@@ -2946,6 +3132,23 @@
                     case WifiP2pMonitor.P2P_GROUP_STARTED_EVENT:
                         loge("Duplicate group creation event notice, ignore");
                         break;
+                    case WifiP2pManager.CANCEL_CONNECT:
+                        mWifiNative.p2pCancelConnect();
+                        mWifiP2pMetrics.endConnectionEvent(
+                                P2pConnectionEvent.CLF_CANCEL);
+
+                        ArrayList<WifiP2pDevice> invitingPeers = new ArrayList<>();
+                        mPeers.getDeviceList().forEach(dev -> {
+                            if (dev.status == WifiP2pDevice.INVITED) {
+                                invitingPeers.add(dev);
+                            }
+                        });
+                        if (mPeers.remove(new WifiP2pDeviceList(invitingPeers))) {
+                            sendPeersChangedBroadcast();
+                        }
+
+                        replyToMessage(message, WifiP2pManager.CANCEL_CONNECT_SUCCEEDED);
+                        break;
                     default:
                         return NOT_HANDLED;
                 }
@@ -2962,6 +3165,12 @@
                 resetWifiP2pInfo();
                 mDetailedState = NetworkInfo.DetailedState.DISCONNECTED;
                 sendP2pConnectionChangedBroadcast();
+                // When location mode is off, tethering service cannot receive regular
+                // p2p connection changed event to stop tethering, send a
+                // dedicated update to stop it.
+                if (!mWifiPermissionsUtil.isLocationModeEnabled()) {
+                    sendP2pTetherRequestBroadcast();
+                }
             }
         }
 
@@ -3042,32 +3251,9 @@
             pw.println();
         }
 
-        // Check & re-enable P2P if needed.
-        // P2P interface will be created if all of the below are true:
-        // a) Wifi is enabled.
-        // b) HAL (HIDL) interface is available.
-        // c) There is atleast 1 client app which invoked initialize().
-        private void checkAndReEnableP2p() {
-            boolean isHalInterfaceAvailable = isHalInterfaceAvailable();
-            Log.d(TAG, "Wifi enabled=" + mIsWifiEnabled + ", P2P Interface availability="
-                    + isHalInterfaceAvailable + ", Number of clients="
-                    + mDeathDataByBinder.size());
-            if (mIsWifiEnabled && isHalInterfaceAvailable
-                    && !mDeathDataByBinder.isEmpty()) {
-                sendMessage(ENABLE_P2P);
-            }
-        }
-
-        // Ignore judgement if the device do not support HAL (HIDL) interface
-        private boolean isHalInterfaceAvailable() {
-            return mWifiNative.isHalInterfaceSupported() ? mIsHalInterfaceAvailable : true;
-        }
-
         private void checkAndSendP2pStateChangedBroadcast() {
-            boolean isHalInterfaceAvailable = isHalInterfaceAvailable();
-            Log.d(TAG, "Wifi enabled=" + mIsWifiEnabled + ", P2P Interface availability="
-                    + isHalInterfaceAvailable);
-            sendP2pStateChangedBroadcast(mIsWifiEnabled && isHalInterfaceAvailable);
+            Log.d(TAG, "Wifi enabled=" + mIsWifiEnabled);
+            sendP2pStateChangedBroadcast(mIsWifiEnabled);
         }
 
         private void sendP2pStateChangedBroadcast(boolean enabled) {
@@ -3168,8 +3354,11 @@
             return null;
         }
 
-        private void sendP2pTetherRequestBroadcast(String tetheringServicePackage) {
-            if (mVerboseLoggingEnabled) logd("sending p2p tether request broadcast");
+        private boolean sendP2pTetherRequestBroadcast() {
+            String tetheringServicePackage = findTetheringServicePackage();
+            if (TextUtils.isEmpty(tetheringServicePackage)) return false;
+            Log.i(TAG, "sending p2p tether request broadcast to "
+                    + tetheringServicePackage);
 
             Intent intent = new Intent(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
             intent.setPackage(tetheringServicePackage);
@@ -3179,7 +3368,10 @@
             intent.putExtra(WifiP2pManager.EXTRA_NETWORK_INFO, makeNetworkInfo());
             intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP, eraseOwnDeviceAddress(mGroup));
 
-            sendBroadcastMultiplePermissions(intent);
+            Context context = mContext.createContextAsUser(UserHandle.ALL, 0);
+            context.sendBroadcastWithMultiplePermissions(
+                    intent, RECEIVER_PERMISSIONS_FOR_BROADCAST);
+            return true;
         }
 
         private void sendP2pPersistentGroupsChangedBroadcast() {
@@ -3208,7 +3400,7 @@
             addRowToDialog(group, R.string.wifi_p2p_to_message, getDeviceName(peerAddress));
             addRowToDialog(group, R.string.wifi_p2p_show_pin_message, pin);
 
-            AlertDialog dialog = new AlertDialog.Builder(mContext)
+            AlertDialog dialog = mFrameworkFacade.makeAlertDialogBuilder(mContext)
                     .setTitle(r.getString(R.string.wifi_p2p_invitation_sent_title))
                     .setView(textEntryView)
                     .setPositiveButton(r.getString(R.string.ok), null)
@@ -3229,7 +3421,7 @@
             addRowToDialog(group, R.string.wifi_p2p_to_message, getDeviceName(peerAddress));
             addRowToDialog(group, R.string.wifi_p2p_show_pin_message, pin);
 
-            AlertDialog dialog = new AlertDialog.Builder(mContext)
+            AlertDialog dialog = mFrameworkFacade.makeAlertDialogBuilder(mContext)
                     .setTitle(r.getString(R.string.wifi_p2p_invitation_sent_title))
                     .setView(textEntryView)
                     .setPositiveButton(r.getString(R.string.accept), new OnClickListener() {
@@ -3257,7 +3449,7 @@
 
             final EditText pin = (EditText) textEntryView.findViewById(R.id.wifi_p2p_wps_pin);
 
-            AlertDialog dialog = new AlertDialog.Builder(mContext)
+            AlertDialog dialog = mFrameworkFacade.makeAlertDialogBuilder(mContext)
                     .setTitle(r.getString(R.string.wifi_p2p_invitation_to_connect_title))
                     .setView(textEntryView)
                     .setPositiveButton(r.getString(R.string.accept), new OnClickListener() {
@@ -3534,7 +3726,7 @@
          * Start a p2p group negotiation and display pin if necessary
          * @param config for the peer
          */
-        private void p2pConnectWithPinDisplay(WifiP2pConfig config) {
+        private void p2pConnectWithPinDisplay(WifiP2pConfig config, int triggerType) {
             if (config == null) {
                 Log.e(TAG, "Illegal argument(s)");
                 return;
@@ -3545,7 +3737,21 @@
                 return;
             }
             config.groupOwnerIntent = selectGroupOwnerIntentIfNecessary(config);
-            String pin = mWifiNative.p2pConnect(config, dev.isGroupOwner());
+            boolean action;
+            if (triggerType == P2P_CONNECT_TRIGGER_GROUP_NEG_REQ) {
+                // If this is called from the GO negotiation path, the sender initiated
+                // a group negotiation.
+                action = FORM_GROUP;
+            } else if (triggerType == P2P_CONNECT_TRIGGER_INVITATION_REQ) {
+                // The group owner won't report it is a Group Owner always.
+                // If this is called from the invitation path, the sender should be in
+                // a group, and the target should be a group owner.
+                action = JOIN_GROUP;
+            } else {
+                action = dev.isGroupOwner() ? JOIN_GROUP : FORM_GROUP;
+            }
+
+            String pin = mWifiNative.p2pConnect(config, action);
             try {
                 Integer.parseInt(pin);
                 notifyInvitationSent(pin, config.deviceAddress);
@@ -3560,7 +3766,7 @@
          * @param config for the peer
          * @return true on success, false on failure
          */
-        private boolean reinvokePersistentGroup(WifiP2pConfig config) {
+        private boolean reinvokePersistentGroup(WifiP2pConfig config, boolean isInvited) {
             if (config == null) {
                 Log.e(TAG, "Illegal argument(s)");
                 return false;
@@ -3570,7 +3776,10 @@
                 Log.e(TAG, "Invalid device");
                 return false;
             }
-            boolean join = dev.isGroupOwner();
+            // The group owner won't report it is a Group Owner always.
+            // If this is called from the invitation path, the sender should be in
+            // a group, and the target should be a group owner.
+            boolean join = dev.isGroupOwner() || isInvited;
             String ssid = mWifiNative.p2pGetSsid(dev.deviceAddress);
             if (mVerboseLoggingEnabled) logd("target ssid is " + ssid + " join:" + join);
 
@@ -3582,6 +3791,9 @@
                 join = false;
             } else if (join) {
                 int netId = mGroups.getNetworkId(dev.deviceAddress, ssid);
+                if (isInvited && netId < 0) {
+                    netId = mGroups.getNetworkId(dev.deviceAddress);
+                }
                 if (netId >= 0) {
                     // Skip WPS and start 4way handshake immediately.
                     if (!mWifiNative.p2pGroupAdd(netId)) {
@@ -3758,14 +3970,38 @@
 
         private String getPersistedDeviceName() {
             String deviceName = mSettingsConfigStore.get(WIFI_P2P_DEVICE_NAME);
-            if (deviceName == null) {
+            if (null != deviceName) return deviceName;
+
+            String prefix = mWifiGlobals.getWifiP2pDeviceNamePrefix();
+            if (DEVICE_NAME_PREFIX_LENGTH_MAX < prefix.getBytes(StandardCharsets.UTF_8).length
+                    || 0 == prefix.getBytes(StandardCharsets.UTF_8).length) {
+                logw("The length of default device name prefix is invalid"
+                        + ", fallback to default name.");
+                prefix = DEFAULT_DEVICE_NAME_PREFIX;
+            }
+            // The length of remaining bytes is at least {@link #DEVICE_NAME_POSTFIX_LENGTH_MIN}.
+            int remainingBytes =
+                    DEVICE_NAME_LENGTH_MAX - prefix.getBytes(StandardCharsets.UTF_8).length;
+
+            int numDigits = mWifiGlobals.getWifiP2pDeviceNamePostfixNumDigits();
+            if (numDigits > remainingBytes) {
+                logw("The postfix length exceeds the remaining byte number"
+                        + ", use the smaller one.");
+                numDigits = remainingBytes;
+            }
+
+            String postfix;
+            if (numDigits >= DEVICE_NAME_POSTFIX_LENGTH_MIN) {
+                postfix = StringUtil.generateRandomNumberString(numDigits);
+            } else {
                 // We use the 4 digits of the ANDROID_ID to have a friendly
                 // default that has low likelihood of collision with a peer
                 String id = mFrameworkFacade.getSecureStringSetting(mContext,
                         Settings.Secure.ANDROID_ID);
-                return "Android_" + id.substring(0, 4);
+                postfix = id.substring(0, 4);
             }
-            return deviceName;
+            logd("the default device name: " + prefix + postfix);
+            return prefix + postfix;
         }
 
         private boolean setAndPersistDeviceName(String devName) {
@@ -3785,26 +4021,34 @@
         }
 
         private boolean setWfdInfo(WifiP2pWfdInfo wfdInfo) {
+            final boolean enabled = wfdInfo.isEnabled();
             boolean success;
-
-            if (!wfdInfo.isEnabled()) {
-                success = mWifiNative.setWfdEnable(false);
-            } else {
-                success =
-                    mWifiNative.setWfdEnable(true)
-                    && mWifiNative.setWfdDeviceInfo(wfdInfo.getDeviceInfoHex());
-            }
-
-            if (!success) {
-                loge("Failed to set wfd properties");
+            if (!mWifiNative.setWfdEnable(enabled)) {
+                loge("Failed to set wfd enable: " + enabled);
                 return false;
             }
 
+            if (enabled) {
+                if (!mWifiNative.setWfdDeviceInfo(wfdInfo.getDeviceInfoHex())) {
+                    loge("Failed to set wfd properties");
+                    return false;
+                }
+                if (!setWfdR2InfoIfNecessary(wfdInfo)) {
+                    loge("Failed to set wfd r2 properties");
+                    return false;
+                }
+            }
             mThisDevice.wfdInfo = wfdInfo;
             sendThisDeviceChangedBroadcast();
             return true;
         }
 
+        private boolean setWfdR2InfoIfNecessary(WifiP2pWfdInfo wfdInfo) {
+            if (!SdkLevel.isAtLeastS()) return true;
+            if (!wfdInfo.isR2Supported()) return true;
+            return mWifiNative.setWfdR2DeviceInfo(wfdInfo.getR2DeviceInfoHex());
+        }
+
         private void initializeP2pSettings() {
             mThisDevice.deviceName = getPersistedDeviceName();
             mThisDevice.primaryDeviceType = mContext.getResources().getString(
@@ -3836,6 +4080,14 @@
         }
 
         private void handleGroupCreationFailure() {
+            // A group is formed, but the tethering request is not proceed.
+            if (null != mGroup) {
+                // Clear any timeout that was set. This is essential for devices
+                // that reuse the main p2p interface for a created group.
+                mWifiNative.setP2pGroupIdle(mGroup.getInterface(), 0);
+                mWifiNative.p2pGroupRemove(mGroup.getInterface());
+                mGroup = null;
+            }
             resetWifiP2pInfo();
             mDetailedState = NetworkInfo.DetailedState.FAILED;
             sendP2pConnectionChangedBroadcast();
@@ -4004,9 +4256,9 @@
             }
 
             ++mServiceTransactionId;
-            //The Wi-Fi p2p spec says transaction id should be non-zero
-            if (mServiceTransactionId == 0) ++mServiceTransactionId;
-            req.setTransactionId(mServiceTransactionId);
+            // The Wi-Fi p2p spec says transaction id should be 1 byte and non-zero.
+            if (mServiceTransactionId == 256) mServiceTransactionId = 1;
+            req.setTransactionId((mServiceTransactionId));
             clientInfo.mReqList.put(mServiceTransactionId, req);
 
             if (mServiceDiscReqId == null) {
@@ -4353,6 +4605,23 @@
             return intent;
         }
 
+        private boolean updateP2pChannels() {
+            Log.d(TAG, "Set P2P listen channel to " + mUserListenChannel);
+            if (!mWifiNative.p2pSetListenChannel(mUserListenChannel)) {
+                Log.e(TAG, "Cannot set listen channel.");
+                return false;
+            }
+
+            Log.d(TAG, "Set P2P operating channel to " + mUserOperatingChannel
+                    + ", unsafe channels: "
+                    + mCoexUnsafeChannels.stream()
+                            .map(Object::toString).collect(Collectors.joining(",")));
+            if (!mWifiNative.p2pSetOperatingChannel(mUserOperatingChannel, mCoexUnsafeChannels)) {
+                Log.e(TAG, "Cannot set operate channel.");
+                return false;
+            }
+            return true;
+        }
     }
 
     /**
diff --git a/service/java/com/android/server/wifi/rtt/RttNative.java b/service/java/com/android/server/wifi/rtt/RttNative.java
index 4b15241..3c0541b 100644
--- a/service/java/com/android/server/wifi/rtt/RttNative.java
+++ b/service/java/com/android/server/wifi/rtt/RttNative.java
@@ -388,13 +388,6 @@
         // Skipping any configurations which have an error (printing out a message).
         // The caller will only get results for valid configurations.
         for (ResponderConfig responder: request.mRttPeers) {
-            if (!isCalledFromPrivilegedContext) {
-                if (!responder.supports80211mc) {
-                    Log.e(TAG, "Invalid responder: does not support 802.11mc");
-                    continue;
-                }
-            }
-
             RttConfig config = new RttConfig();
 
             System.arraycopy(responder.macAddress.toByteArray(), 0, config.addr, 0,
@@ -422,7 +415,7 @@
                     config.mustRequestLcr = false;
                     config.burstPeriod = 0;
                     config.numBurst = 0;
-                    config.numFramesPerBurst = 5;
+                    config.numFramesPerBurst = request.mRttBurstSize;
                     config.numRetriesPerRttFrame = 0; // irrelevant for 2-sided RTT
                     config.numRetriesPerFtmr = 3;
                     config.burstDuration = 9;
@@ -431,7 +424,7 @@
                     config.mustRequestLcr = true;
                     config.burstPeriod = 0;
                     config.numBurst = 0;
-                    config.numFramesPerBurst = 8;
+                    config.numFramesPerBurst = request.mRttBurstSize;
                     config.numRetriesPerRttFrame = (config.type == RttType.TWO_SIDED ? 0 : 3);
                     config.numRetriesPerFtmr = 3;
                     config.burstDuration = 9;
@@ -477,12 +470,6 @@
         // Skipping any configurations which have an error (printing out a message).
         // The caller will only get results for valid configurations.
         for (ResponderConfig responder: request.mRttPeers) {
-            if (!isCalledFromPrivilegedContext) {
-                if (!responder.supports80211mc) {
-                    Log.e(TAG, "Invalid responder: does not support 802.11mc");
-                    continue;
-                }
-            }
 
             android.hardware.wifi.V1_4.RttConfig config =
                     new android.hardware.wifi.V1_4.RttConfig();
@@ -511,7 +498,7 @@
                     config.mustRequestLcr = false;
                     config.burstPeriod = 0;
                     config.numBurst = 0;
-                    config.numFramesPerBurst = 5;
+                    config.numFramesPerBurst = request.mRttBurstSize;
                     config.numRetriesPerRttFrame = 0; // irrelevant for 2-sided RTT
                     config.numRetriesPerFtmr = 3;
                     config.burstDuration = 9;
@@ -520,7 +507,7 @@
                     config.mustRequestLcr = true;
                     config.burstPeriod = 0;
                     config.numBurst = 0;
-                    config.numFramesPerBurst = 8;
+                    config.numFramesPerBurst = request.mRttBurstSize;
                     config.numRetriesPerRttFrame = (config.type == RttType.TWO_SIDED ? 0 : 3);
                     config.numRetriesPerFtmr = 3;
                     config.burstDuration = 9;
@@ -779,7 +766,8 @@
                     rttResult.distanceInMm, rttResult.distanceSdInMm,
                     rttResult.rssi / -2, rttResult.numberPerBurstPeer,
                     rttResult.successNumber, lci, lcr, responderLocation,
-                    rttResult.timeStampInUs / CONVERSION_US_TO_MS));
+                    rttResult.timeStampInUs / CONVERSION_US_TO_MS,
+                    rttResult.type == RttType.TWO_SIDED));
         }
         return rangingResults;
     }
@@ -815,7 +803,8 @@
                     rttResult.distanceInMm, rttResult.distanceSdInMm,
                     rttResult.rssi / -2, rttResult.numberPerBurstPeer,
                     rttResult.successNumber, lci, lcr, responderLocation,
-                    rttResult.timeStampInUs / CONVERSION_US_TO_MS));
+                    rttResult.timeStampInUs / CONVERSION_US_TO_MS,
+                    rttResult.type == RttType.TWO_SIDED));
         }
         return rangingResults;
     }
diff --git a/service/java/com/android/server/wifi/rtt/RttServiceImpl.java b/service/java/com/android/server/wifi/rtt/RttServiceImpl.java
index 3122a94..c3267cc 100644
--- a/service/java/com/android/server/wifi/rtt/RttServiceImpl.java
+++ b/service/java/com/android/server/wifi/rtt/RttServiceImpl.java
@@ -1114,16 +1114,11 @@
                     }
 
                     int errorCode = RangingResult.STATUS_FAIL;
-                    if (!isCalledFromPrivilegedContext) {
-                        if (!peer.supports80211mc) {
-                            errorCode = RangingResult.STATUS_RESPONDER_DOES_NOT_SUPPORT_IEEE80211MC;
-                        }
-                    }
 
                     if (peer.peerHandle == null) {
                         finalResults.add(
                                 new RangingResult(errorCode, peer.macAddress, 0, 0, 0, 0, 0, null,
-                                        null, null, 0));
+                                        null, null, 0, false));
                     } else {
                         finalResults.add(
                                 new RangingResult(errorCode, peer.peerHandle, 0, 0, 0, 0, 0, null,
@@ -1148,32 +1143,32 @@
                         finalResults.add(new RangingResult(
                                 status,
                                 peer.macAddress,
-                                resultForRequest.getDistanceMm(),
-                                resultForRequest.getDistanceStdDevMm(),
-                                resultForRequest.getRssi(),
-                                resultForRequest.getNumAttemptedMeasurements(),
-                                resultForRequest.getNumSuccessfulMeasurements(),
+                                resultForRequest.mDistanceMm,
+                                resultForRequest.mDistanceStdDevMm,
+                                resultForRequest.mRssi,
+                                resultForRequest.mNumAttemptedMeasurements,
+                                resultForRequest.mNumSuccessfulMeasurements,
                                 lci,
                                 lcr,
                                 responderLocation,
-                                resultForRequest.getRangingTimestampMillis()));
+                                resultForRequest.mTimestamp,
+                                resultForRequest.mIs80211mcMeasurement));
                     } else {
                         finalResults.add(new RangingResult(
                                 status,
                                 peer.peerHandle,
-                                resultForRequest.getDistanceMm(),
-                                resultForRequest.getDistanceStdDevMm(),
-                                resultForRequest.getRssi(),
-                                resultForRequest.getNumAttemptedMeasurements(),
-                                resultForRequest.getNumSuccessfulMeasurements(),
+                                resultForRequest.mDistanceMm,
+                                resultForRequest.mDistanceStdDevMm,
+                                resultForRequest.mRssi,
+                                resultForRequest.mNumAttemptedMeasurements,
+                                resultForRequest.mNumSuccessfulMeasurements,
                                 lci,
                                 lcr,
                                 responderLocation,
-                                resultForRequest.getRangingTimestampMillis()));
+                                resultForRequest.mTimestamp));
                     }
                 }
             }
-
             return finalResults;
         }
 
diff --git a/service/java/com/android/server/wifi/scanner/ChannelHelper.java b/service/java/com/android/server/wifi/scanner/ChannelHelper.java
index a7e50a6..04d11e7 100644
--- a/service/java/com/android/server/wifi/scanner/ChannelHelper.java
+++ b/service/java/com/android/server/wifi/scanner/ChannelHelper.java
@@ -19,6 +19,7 @@
 import android.net.wifi.WifiScanner;
 import android.util.ArraySet;
 
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.WifiNative;
 
 import java.util.Set;
@@ -137,6 +138,10 @@
          * an empty set if an entire Band if specified or if the list is empty.
          */
         public abstract Set<Integer> getChannelSet();
+        /**
+         * Add 6Ghz Preferred Scanning Channels into the current channel collection.
+         */
+        public abstract void add6GhzPscChannels();
 
         /**
          * Add all channels in the ScanSetting to the collection
@@ -146,9 +151,18 @@
                 for (int j = 0; j < scanSettings.channels.length; ++j) {
                     addChannel(scanSettings.channels[j].frequency);
                 }
-            } else {
-                addBand(scanSettings.band);
+                return;
             }
+            if (SdkLevel.isAtLeastS()) {
+                if (scanSettings.is6GhzPscOnlyEnabled() && is6GhzBandIncluded(scanSettings.band)) {
+                    // Modify the band to exclude 6Ghz since not all 6Ghz channels will be added.
+                    int band = scanSettings.band & (~WifiScanner.WIFI_BAND_6_GHZ);
+                    addBand(band);
+                    add6GhzPscChannels();
+                    return;
+                }
+            }
+            addBand(scanSettings.band);
         }
 
         /**
@@ -311,6 +325,13 @@
     }
 
     /**
+     * Returns whether WIFI_BAND_6_GHZ is included in the input band.
+     */
+    public static boolean is6GhzBandIncluded(int band) {
+        return (band & WifiScanner.WIFI_BAND_6_GHZ) != 0;
+    }
+
+    /**
      * Converts a WifiScanner.WIFI_BAND_* constant to a meaningful String
      */
     public static String bandToString(int band) {
@@ -340,6 +361,10 @@
         }
         band &= ~WifiScanner.WIFI_BAND_6_GHZ;
 
+        if ((band & WifiScanner.WIFI_BAND_60_GHZ) != 0) {
+            sj.add("60Ghz");
+        }
+        band &= ~WifiScanner.WIFI_BAND_60_GHZ;
         if (band != 0) {
             return "Invalid band";
         }
diff --git a/service/java/com/android/server/wifi/scanner/KnownBandsChannelHelper.java b/service/java/com/android/server/wifi/scanner/KnownBandsChannelHelper.java
index 2de70d5..936b914 100644
--- a/service/java/com/android/server/wifi/scanner/KnownBandsChannelHelper.java
+++ b/service/java/com/android/server/wifi/scanner/KnownBandsChannelHelper.java
@@ -19,12 +19,14 @@
 import static android.net.wifi.WifiScanner.WIFI_BAND_24_GHZ;
 import static android.net.wifi.WifiScanner.WIFI_BAND_5_GHZ;
 import static android.net.wifi.WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY;
+import static android.net.wifi.WifiScanner.WIFI_BAND_60_GHZ;
 import static android.net.wifi.WifiScanner.WIFI_BAND_6_GHZ;
 import static android.net.wifi.WifiScanner.WIFI_BAND_ALL;
 import static android.net.wifi.WifiScanner.WIFI_BAND_COUNT;
 import static android.net.wifi.WifiScanner.WIFI_BAND_INDEX_24_GHZ;
 import static android.net.wifi.WifiScanner.WIFI_BAND_INDEX_5_GHZ;
 import static android.net.wifi.WifiScanner.WIFI_BAND_INDEX_5_GHZ_DFS_ONLY;
+import static android.net.wifi.WifiScanner.WIFI_BAND_INDEX_60_GHZ;
 import static android.net.wifi.WifiScanner.WIFI_BAND_INDEX_6_GHZ;
 import static android.net.wifi.WifiScanner.WIFI_BAND_UNSPECIFIED;
 
@@ -35,8 +37,11 @@
 import android.util.ArraySet;
 
 import com.android.server.wifi.WifiNative;
+import com.android.server.wifi.proto.WifiStatsLog;
 
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -61,9 +66,13 @@
     private WifiScanner.ChannelSpec[][] mBandsToChannels;
 
     protected void setBandChannels(int[] channels2G, int[] channels5G, int[] channelsDfs,
-            int[] channels6G) {
+            int[] channels6G, int[] channels60G) {
         mBandsToChannels = new WifiScanner.ChannelSpec[WIFI_BAND_COUNT][];
 
+        for (int i = 0; i < WIFI_BAND_COUNT; i++) {
+            mBandsToChannels[i] = NO_CHANNELS;
+        }
+
         if (channels2G.length != 0) {
             mBandsToChannels[WIFI_BAND_INDEX_24_GHZ] =
                     new WifiScanner.ChannelSpec[channels2G.length];
@@ -95,6 +104,14 @@
         } else {
             mBandsToChannels[WIFI_BAND_INDEX_6_GHZ] = NO_CHANNELS;
         }
+
+        if (channels60G.length != 0) {
+            mBandsToChannels[WIFI_BAND_INDEX_60_GHZ] =
+                    new WifiScanner.ChannelSpec[channels60G.length];
+            copyChannels(mBandsToChannels[WIFI_BAND_INDEX_60_GHZ], channels60G);
+        } else {
+            mBandsToChannels[WIFI_BAND_INDEX_60_GHZ] = NO_CHANNELS;
+        }
     }
 
     private static void copyChannels(
@@ -111,15 +128,14 @@
             return null;
         }
 
-        WifiScanner.ChannelSpec[][] channels = new WifiScanner.ChannelSpec[WIFI_BAND_COUNT][];
+        List<WifiScanner.ChannelSpec[]> channelList = new ArrayList<>();
         for (@WifiBandIndex int index = 0; index < WIFI_BAND_COUNT; index++) {
-            if ((band & (1 << index)) != 0) {
-                channels[index] = mBandsToChannels[index];
-            } else {
-                channels[index] = NO_CHANNELS;
+            if ((band & (1 << index)) != 0 && mBandsToChannels[index].length > 0) {
+                channelList.add(mBandsToChannels[index]);
             }
         }
-        return channels;
+
+        return channelList.toArray(new WifiScanner.ChannelSpec[0][0]);
     }
 
     @Override
@@ -179,6 +195,8 @@
             }
         } else if (ScanResult.is6GHz(frequency)) {
             return WIFI_BAND_6_GHZ;
+        } else if (ScanResult.is60GHz(frequency)) {
+            return WIFI_BAND_60_GHZ;
         } else {
             return WIFI_BAND_UNSPECIFIED;
         }
@@ -194,6 +212,8 @@
                 return WIFI_BAND_INDEX_5_GHZ_DFS_ONLY;
             case WIFI_BAND_6_GHZ:
                 return WIFI_BAND_INDEX_6_GHZ;
+            case WIFI_BAND_60_GHZ:
+                return WIFI_BAND_INDEX_60_GHZ;
             default:
                 return -1;
         }
@@ -226,6 +246,38 @@
     }
 
     /**
+     * Convert Wifi channel frequency to a bucketed band value.
+     *
+     * @param frequency Frequency (e.g. 2417)
+     * @return WifiBandBucket enum value (e.g. BAND_2G)
+     */
+    public static int getBand(int frequency) {
+        int band = WifiStatsLog.WIFI_HEALTH_STAT_REPORTED__BAND__UNKNOWN;
+
+        if (ScanResult.is24GHz(frequency)) {
+            band = WifiStatsLog.WIFI_HEALTH_STAT_REPORTED__BAND__BAND_2G;
+        } else if (ScanResult.is5GHz(frequency)) {
+            if (frequency <= BAND_5_GHZ_LOW_END_FREQ) {
+                band = WifiStatsLog.WIFI_HEALTH_STAT_REPORTED__BAND__BAND_5G_LOW;
+            } else if (frequency <= BAND_5_GHZ_MID_END_FREQ) {
+                band = WifiStatsLog.WIFI_HEALTH_STAT_REPORTED__BAND__BAND_5G_MIDDLE;
+            } else {
+                band = WifiStatsLog.WIFI_HEALTH_STAT_REPORTED__BAND__BAND_5G_HIGH;
+            }
+        } else if (ScanResult.is6GHz(frequency)) {
+            if (frequency <= BAND_6_GHZ_LOW_END_FREQ) {
+                band = WifiStatsLog.WIFI_HEALTH_STAT_REPORTED__BAND__BAND_6G_LOW;
+            } else if (frequency <= BAND_6_GHZ_MID_END_FREQ) {
+                band = WifiStatsLog.WIFI_HEALTH_STAT_REPORTED__BAND__BAND_6G_MIDDLE;
+            } else {
+                band = WifiStatsLog.WIFI_HEALTH_STAT_REPORTED__BAND__BAND_6G_HIGH;
+            }
+        }
+
+        return band;
+    }
+
+    /**
      * ChannelCollection that merges channels so that the optimal schedule will be generated.
      * When the max channels value is satisfied this implementation will always create a channel
      * list that includes no more than the added channels.
@@ -271,6 +323,7 @@
         @Override
         public boolean containsBand(int band) {
             WifiScanner.ChannelSpec[][] bandChannels = getAvailableScanChannels(band);
+
             for (int i = 0; i < bandChannels.length; ++i) {
                 for (int j = 0; j < bandChannels[i].length; ++j) {
                     if (!mChannels.contains(bandChannels[i][j].frequency)) {
@@ -349,9 +402,22 @@
         }
 
         @Override
+        public void add6GhzPscChannels() {
+            Set<Integer> missingChannels = getMissingChannelsFromBand(WIFI_BAND_6_GHZ);
+            if (missingChannels.isEmpty()) {
+                return;
+            }
+            for (int freq : missingChannels) {
+                if (ScanResult.is6GHzPsc(freq)) {
+                    mChannels.add(freq);
+                    mAllBands |= WIFI_BAND_6_GHZ;
+                }
+            }
+        }
+
+        @Override
         public void fillBucketSettings(WifiNative.BucketSettings bucketSettings, int maxChannels) {
-            if ((mChannels.size() > maxChannels || mAllBands == mExactBands)
-                    && mAllBands != 0) {
+            if ((mChannels.size() > maxChannels || mAllBands == mExactBands) && mAllBands != 0) {
                 bucketSettings.band = mAllBands;
                 bucketSettings.num_channels = 0;
                 bucketSettings.channels = null;
@@ -382,7 +448,6 @@
     }
 
     @Override
-
     public KnownBandsChannelCollection createChannelCollection() {
         return new KnownBandsChannelCollection();
     }
diff --git a/service/java/com/android/server/wifi/scanner/PresetKnownBandsChannelHelper.java b/service/java/com/android/server/wifi/scanner/PresetKnownBandsChannelHelper.java
index e1ddd91..fa2c62d 100644
--- a/service/java/com/android/server/wifi/scanner/PresetKnownBandsChannelHelper.java
+++ b/service/java/com/android/server/wifi/scanner/PresetKnownBandsChannelHelper.java
@@ -21,7 +21,7 @@
  */
 public class PresetKnownBandsChannelHelper extends KnownBandsChannelHelper {
     public PresetKnownBandsChannelHelper(int[] channels2G, int[] channels5G, int[] channelsDfs,
-            int[] channels6G) {
-        setBandChannels(channels2G, channels5G, channelsDfs, channels6G);
+            int[] channels6G, int[] channels60G) {
+        setBandChannels(channels2G, channels5G, channelsDfs, channels6G, channels60G);
     }
 }
diff --git a/service/java/com/android/server/wifi/scanner/ScanScheduleUtil.java b/service/java/com/android/server/wifi/scanner/ScanScheduleUtil.java
index ab127af..a090ecd 100644
--- a/service/java/com/android/server/wifi/scanner/ScanScheduleUtil.java
+++ b/service/java/com/android/server/wifi/scanner/ScanScheduleUtil.java
@@ -176,7 +176,7 @@
                     filteredScanDatas.add(new ScanData(scanData.getId(),
                                     scanData.getFlags(),
                                     0,
-                                    scanData.getBandScanned(),
+                                    scanData.getScannedBandsInternal(),
                                     filteredResults.toArray(
                                             new ScanResult[filteredResults.size()])));
                 }
diff --git a/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java b/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
index b4c67b7..4340925 100644
--- a/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
+++ b/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
@@ -27,6 +27,7 @@
 import android.net.wifi.IWifiScanner;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiAnnotations;
+import android.net.wifi.WifiManager;
 import android.net.wifi.WifiScanner;
 import android.net.wifi.WifiScanner.ChannelSpec;
 import android.net.wifi.WifiScanner.PnoSettings;
@@ -54,6 +55,7 @@
 import com.android.internal.util.Protocol;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.ClientModeImpl;
 import com.android.server.wifi.Clock;
 import com.android.server.wifi.FrameworkFacade;
@@ -66,6 +68,7 @@
 import com.android.server.wifi.proto.nano.WifiMetricsProto;
 import com.android.server.wifi.scanner.ChannelHelper.ChannelCollection;
 import com.android.server.wifi.util.ArrayUtils;
+import com.android.server.wifi.util.LastCallerInfoManager;
 import com.android.server.wifi.util.ScanResultUtil;
 import com.android.server.wifi.util.WifiHandler;
 import com.android.server.wifi.util.WifiPermissionsUtil;
@@ -187,8 +190,15 @@
         if (msg.what != WifiScanner.CMD_START_SINGLE_SCAN) return false;
         if (!(msg.obj instanceof Bundle)) return false;
         Bundle bundle = (Bundle) msg.obj;
-        ScanSettings scanSettings = bundle.getParcelable(WifiScanner.SCAN_PARAMS_SCAN_SETTINGS_KEY);
-        return scanSettings.ignoreLocationSettings;
+        try {
+            ScanSettings scanSettings =
+                    bundle.getParcelable(WifiScanner.SCAN_PARAMS_SCAN_SETTINGS_KEY);
+            if (scanSettings == null) return false;
+            return scanSettings.ignoreLocationSettings;
+        } catch (BadParcelableException e) {
+            Log.wtf(TAG, "Failed to get parcelable params", e);
+            return false;
+        }
     }
 
     // Check if we should hide this request from app-ops if this is a single scan request.
@@ -328,6 +338,8 @@
                     mBackgroundScanStateMachine.sendMessage(Message.obtain(msg));
                     mSingleScanStateMachine.sendMessage(Message.obtain(msg));
                     mPnoScanStateMachine.sendMessage(Message.obtain(msg));
+                    mLastCallerInfoManager.put(LastCallerInfoManager.SCANNING_ENABLED, msg.arg1,
+                            msg.sendingUid, msg.arg2, (String) msg.obj, true);
                     break;
                 case WifiScanner.CMD_DISABLE:
                     Log.i(TAG, "Received a request to disable scanning, UID = " + msg.sendingUid);
@@ -335,6 +347,8 @@
                     mBackgroundScanStateMachine.sendMessage(Message.obtain(msg));
                     mSingleScanStateMachine.sendMessage(Message.obtain(msg));
                     mPnoScanStateMachine.sendMessage(Message.obtain(msg));
+                    mLastCallerInfoManager.put(LastCallerInfoManager.SCANNING_ENABLED, msg.arg1,
+                            msg.sendingUid, msg.arg2, (String) msg.obj, false);
                     break;
                 case WifiScanner.CMD_START_BACKGROUND_SCAN:
                 case WifiScanner.CMD_STOP_BACKGROUND_SCAN:
@@ -399,6 +413,8 @@
     private final FrameworkFacade mFrameworkFacade;
     private final WifiPermissionsUtil mWifiPermissionsUtil;
     private final WifiNative mWifiNative;
+    private final WifiManager mWifiManager;
+    private final LastCallerInfoManager mLastCallerInfoManager;
 
     WifiScanningServiceImpl(Context context, Looper looper,
             WifiScannerImpl.WifiScannerImplFactory scannerImplFactory,
@@ -417,7 +433,11 @@
         mFrameworkFacade = wifiInjector.getFrameworkFacade();
         mWifiPermissionsUtil = wifiInjector.getWifiPermissionsUtil();
         mWifiNative = wifiInjector.getWifiNative();
+        // Wifi service is always started before other wifi services. So, there is no problem
+        // obtaining WifiManager in the constructor here.
+        mWifiManager = mContext.getSystemService(WifiManager.class);
         mPreviousSchedule = null;
+        mLastCallerInfoManager = wifiInjector.getLastCallerInfoManager();
     }
 
     public void startService() {
@@ -628,6 +648,21 @@
          */
         @VisibleForTesting
         public static final int CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS = 180 * 1000;
+        /**
+         * Alarm Tag to use for the delayed indication of emergency scan end.
+         */
+        @VisibleForTesting
+        public static final String EMERGENCY_SCAN_END_INDICATION_ALARM_TAG =
+                TAG + "EmergencyScanEnd";
+        /**
+         * Alarm timeout to use for the delayed indication of emergency scan end.
+         */
+        private static final int EMERGENCY_SCAN_END_INDICATION_DELAY_MILLIS = 15_000;
+        /**
+         * Alarm listener to use for the delayed indication of emergency scan end.
+         */
+        private final AlarmManager.OnAlarmListener mEmergencyScanEndIndicationListener =
+                () -> mWifiManager.setEmergencyScanRequestInProgress(false);
 
         private final DefaultState mDefaultState = new DefaultState();
         private final DriverStartedState mDriverStartedState = new DriverStartedState();
@@ -685,7 +720,10 @@
                  */
                 @Override
                 public void onScanStatus(int event) {
-                    if (DBG) localLog("onScanStatus event received, event=" + event);
+                    if (DBG) {
+                        localLog("onScanStatus event received, event=" + event
+                                + ", iface=" + mImplIfaceName);
+                    }
                     switch (event) {
                         case WifiNative.WIFI_SCAN_RESULTS_AVAILABLE:
                         case WifiNative.WIFI_SCAN_THRESHOLD_NUM_SCANS:
@@ -706,7 +744,7 @@
                  */
                 @Override
                 public void onFullScanResult(ScanResult fullScanResult, int bucketsScanned) {
-                    if (DBG) localLog("onFullScanResult received");
+                    if (DBG) localLog("onFullScanResult received on iface " + mImplIfaceName);
                     reportFullScanResultForImpl(mImplIfaceName, fullScanResult, bucketsScanned);
                 }
 
@@ -744,6 +782,7 @@
                             scanSettings, new ScanEventHandler(ifaceName));
                     if (!success) {
                         Log.e(TAG, "Failed to start single scan on " + ifaceName);
+                        mStatusPerImpl.put(ifaceName, STATUS_FAILED);
                         continue;
                     }
                     mStatusPerImpl.put(ifaceName, STATUS_PENDING);
@@ -759,6 +798,10 @@
             public @Nullable ScanData getLatestSingleScanResults() {
                 ScanData consolidatedScanData = null;
                 for (WifiScannerImpl impl : mScannerImpls.values()) {
+                    Integer ifaceStatus = mStatusPerImpl.get(impl.getIfaceName());
+                    if (ifaceStatus == null || ifaceStatus != STATUS_SUCCEEDED) {
+                        continue;
+                    }
                     ScanData scanData = impl.getLatestSingleScanResults();
                     if (consolidatedScanData == null) {
                         consolidatedScanData = new ScanData(scanData);
@@ -809,6 +852,84 @@
             }
         }
 
+        /**
+         * Helper method to handle the scan start message.
+         */
+        private void handleScanStartMessage(ClientInfo ci, Message msg) {
+            int handler = msg.arg2;
+            Bundle scanParams = (Bundle) msg.obj;
+            if (scanParams == null) {
+                logCallback("singleScanInvalidRequest",  ci, handler, "null params");
+                replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "params null");
+                return;
+            }
+            ScanSettings scanSettings = null;
+            WorkSource workSource = null;
+            try {
+                scanSettings =
+                        scanParams.getParcelable(
+                                WifiScanner.SCAN_PARAMS_SCAN_SETTINGS_KEY);
+                workSource =
+                        scanParams.getParcelable(
+                                WifiScanner.SCAN_PARAMS_WORK_SOURCE_KEY);
+            } catch (BadParcelableException e) {
+                Log.wtf(TAG, "Failed to get parcelable params", e);
+                logCallback("singleScanInvalidRequest",  ci, handler,
+                        "bad parcel params");
+                replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST,
+                        "bad parcel params");
+                return;
+            }
+            if (validateScanRequest(ci, handler, scanSettings)) {
+                if (getCurrentState() == mDefaultState && !scanSettings.ignoreLocationSettings) {
+                    // Reject regular scan requests if scanning is disabled.
+                    replyFailed(msg, WifiScanner.REASON_UNSPECIFIED, "not available");
+                    return;
+                }
+                mWifiMetrics.incrementOneshotScanCount();
+                if ((scanSettings.band & WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY) != 0) {
+                    mWifiMetrics.incrementOneshotScanWithDfsCount();
+                }
+                logScanRequest("addSingleScanRequest", ci, handler, workSource,
+                        scanSettings, null);
+                replySucceeded(msg);
+
+                if (scanSettings.ignoreLocationSettings) {
+                    // Inform wifi manager that an emergency scan is in progress (regardless of
+                    // whether scanning is currently enabled or not). This ensures that
+                    // the wifi chip remains on for the duration of this scan.
+                    mWifiManager.setEmergencyScanRequestInProgress(true);
+                }
+
+                if (getCurrentState() == mScanningState) {
+                    // If there is an active scan that will fulfill the scan request then
+                    // mark this request as an active scan, otherwise mark it pending.
+                    if (activeScanSatisfies(scanSettings)) {
+                        mActiveScans.addRequest(ci, handler, workSource, scanSettings);
+                    } else {
+                        mPendingScans.addRequest(ci, handler, workSource, scanSettings);
+                    }
+                } else if (getCurrentState() == mIdleState) {
+                    // If were not currently scanning then try to start a scan. Otherwise
+                    // this scan will be scheduled when transitioning back to IdleState
+                    // after finishing the current scan.
+                    mPendingScans.addRequest(ci, handler, workSource, scanSettings);
+                    tryToStartNewScan();
+                } else if (getCurrentState() == mDefaultState) {
+                    // If scanning is disabled and the request is for emergency purposes
+                    // (checked above), add to pending list. this scan will be scheduled when
+                    // transitioning to IdleState when wifi manager enables scanning as a part of
+                    // processing WifiManager.setEmergencyScanRequestInProgress(true)
+                    mPendingScans.addRequest(ci, handler, workSource, scanSettings);
+                }
+            } else {
+                logCallback("singleScanInvalidRequest",  ci, handler, "bad request");
+                replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request");
+                mWifiMetrics.incrementScanReturnEntry(
+                        WifiMetricsProto.WifiLog.SCAN_FAILURE_INVALID_CONFIGURATION, 1);
+            }
+        }
+
         class DefaultState extends State {
             @Override
             public void enter() {
@@ -817,6 +938,8 @@
             }
             @Override
             public boolean processMessage(Message msg) {
+                ClientInfo ci = mClients.get(msg.replyTo);
+
                 switch (msg.what) {
                     case WifiScanner.CMD_ENABLE:
                         if (mScannerImpls.isEmpty()) {
@@ -830,8 +953,10 @@
                         transitionTo(mDefaultState);
                         return HANDLED;
                     case WifiScanner.CMD_START_SINGLE_SCAN:
+                        handleScanStartMessage(ci, msg);
+                        return HANDLED;
                     case WifiScanner.CMD_STOP_SINGLE_SCAN:
-                        replyFailed(msg, WifiScanner.REASON_UNSPECIFIED, "not available");
+                        removeSingleScanRequest(ci, msg.arg2);
                         return HANDLED;
                     case CMD_SCAN_RESULTS_AVAILABLE:
                         if (DBG) localLog("ignored scan results available event");
@@ -887,71 +1012,10 @@
 
             @Override
             public boolean processMessage(Message msg) {
-                ClientInfo ci = mClients.get(msg.replyTo);
-
                 switch (msg.what) {
                     case WifiScanner.CMD_ENABLE:
                         // Ignore if we're already in driver loaded state.
                         return HANDLED;
-                    case WifiScanner.CMD_START_SINGLE_SCAN:
-                        int handler = msg.arg2;
-                        Bundle scanParams = (Bundle) msg.obj;
-                        if (scanParams == null) {
-                            logCallback("singleScanInvalidRequest",  ci, handler, "null params");
-                            replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "params null");
-                            return HANDLED;
-                        }
-                        ScanSettings scanSettings = null;
-                        WorkSource workSource = null;
-                        try {
-                            scanSettings =
-                                    scanParams.getParcelable(
-                                            WifiScanner.SCAN_PARAMS_SCAN_SETTINGS_KEY);
-                            workSource =
-                                    scanParams.getParcelable(
-                                            WifiScanner.SCAN_PARAMS_WORK_SOURCE_KEY);
-                        } catch (BadParcelableException e) {
-                            Log.e(TAG, "Failed to get parcelable params", e);
-                            logCallback("singleScanInvalidRequest",  ci, handler,
-                                    "bad parcel params");
-                            replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST,
-                                    "bad parcel params");
-                            return HANDLED;
-                        }
-                        if (validateScanRequest(ci, handler, scanSettings)) {
-                            mWifiMetrics.incrementOneshotScanCount();
-                            if ((scanSettings.band & WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY) != 0) {
-                                mWifiMetrics.incrementOneshotScanWithDfsCount();
-                            }
-                            logScanRequest("addSingleScanRequest", ci, handler, workSource,
-                                    scanSettings, null);
-                            replySucceeded(msg);
-
-                            // If there is an active scan that will fulfill the scan request then
-                            // mark this request as an active scan, otherwise mark it pending.
-                            // If were not currently scanning then try to start a scan. Otherwise
-                            // this scan will be scheduled when transitioning back to IdleState
-                            // after finishing the current scan.
-                            if (getCurrentState() == mScanningState) {
-                                if (activeScanSatisfies(scanSettings)) {
-                                    mActiveScans.addRequest(ci, handler, workSource, scanSettings);
-                                } else {
-                                    mPendingScans.addRequest(ci, handler, workSource, scanSettings);
-                                }
-                            } else {
-                                mPendingScans.addRequest(ci, handler, workSource, scanSettings);
-                                tryToStartNewScan();
-                            }
-                        } else {
-                            logCallback("singleScanInvalidRequest",  ci, handler, "bad request");
-                            replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request");
-                            mWifiMetrics.incrementScanReturnEntry(
-                                    WifiMetricsProto.WifiLog.SCAN_FAILURE_INVALID_CONFIGURATION, 1);
-                        }
-                        return HANDLED;
-                    case WifiScanner.CMD_STOP_SINGLE_SCAN:
-                        removeSingleScanRequest(ci, msg.arg2);
-                        return HANDLED;
                     default:
                         return NOT_HANDLED;
                 }
@@ -1009,11 +1073,7 @@
                         ScanData latestScanResults =
                                 mScannerImplsTracker.getLatestSingleScanResults();
                         if (latestScanResults != null) {
-                            mWifiMetrics.incrementScanReturnEntry(
-                                    WifiMetricsProto.WifiLog.SCAN_SUCCESS,
-                                    mActiveScans.size());
-                            reportScanResults(latestScanResults);
-                            mActiveScans.clear();
+                            handleScanResults(latestScanResults);
                         } else {
                             Log.e(TAG, "latest scan results null unexpectedly");
                         }
@@ -1025,6 +1085,8 @@
                     case CMD_SCAN_FAILED:
                         mWifiMetrics.incrementScanReturnEntry(
                                 WifiMetricsProto.WifiLog.SCAN_UNKNOWN, mActiveScans.size());
+                        mWifiMetrics.getScanMetrics().logScanFailed(
+                                WifiMetrics.ScanMetrics.SCAN_TYPE_SINGLE);
                         sendOpFailedToAllAndClear(mActiveScans, WifiScanner.REASON_UNSPECIFIED,
                                 "Scan failed");
                         transitionTo(mIdleState);
@@ -1084,7 +1146,7 @@
                 case WifiScanner.SCAN_TYPE_HIGH_ACCURACY:
                     return true;
                 default:
-                    // This should never happen becuase we've validated the incoming type in
+                    // This should never happen because we've validated the incoming type in
                     // |validateScanType|.
                     throw new IllegalArgumentException("Invalid scan type "
                         + mActiveScanSettings.scanType);
@@ -1101,12 +1163,29 @@
                 case WifiScanner.SCAN_TYPE_HIGH_ACCURACY:
                     return existingScanType;
                 default:
-                    // This should never happen becuase we've validated the incoming type in
+                    // This should never happen because we've validated the incoming type in
                     // |validateScanType|.
                     throw new IllegalArgumentException("Invalid scan type " + existingScanType);
             }
         }
 
+        private boolean mergeRnrSetting(boolean enable6GhzRnr, ScanSettings scanSettings) {
+            if (!SdkLevel.isAtLeastS()) {
+                return false;
+            }
+            if (enable6GhzRnr) {
+                return true;
+            }
+            int rnrSetting = scanSettings.getRnrSetting();
+            if (rnrSetting == WifiScanner.WIFI_RNR_ENABLED) {
+                return true;
+            }
+            if (rnrSetting == WifiScanner.WIFI_RNR_ENABLED_IF_WIFI_BAND_6_GHZ_SCANNED) {
+                return ChannelHelper.is6GhzBandIncluded(scanSettings.band);
+            }
+            return false;
+        }
+
         boolean activeScanSatisfies(ScanSettings settings) {
             if (mActiveScanSettings == null) {
                 return false;
@@ -1186,6 +1265,7 @@
             List<WifiNative.HiddenNetwork> hiddenNetworkList = new ArrayList<>();
             for (RequestInfo<ScanSettings> entry : mPendingScans) {
                 settings.scanType = mergeScanTypes(settings.scanType, entry.settings.type);
+                settings.enable6GhzRnr = mergeRnrSetting(settings.enable6GhzRnr, entry.settings);
                 channels.addChannels(entry.settings);
                 for (ScanSettings.HiddenNetwork srcNetwork : entry.settings.hiddenNetworks) {
                     WifiNative.HiddenNetwork hiddenNetwork = new WifiNative.HiddenNetwork();
@@ -1196,7 +1276,13 @@
                         != 0) {
                     bucketSettings.report_events |= WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT;
                 }
+
+                if (entry.clientInfo != null) {
+                    mWifiMetrics.getScanMetrics().setClientUid(entry.clientInfo.mUid);
+                }
+                mWifiMetrics.getScanMetrics().setWorkSource(entry.workSource);
             }
+
             if (hiddenNetworkList.size() > 0) {
                 settings.hiddenNetworks = new WifiNative.HiddenNetwork[hiddenNetworkList.size()];
                 int numHiddenNetworks = 0;
@@ -1206,9 +1292,12 @@
             }
 
             channels.fillBucketSettings(bucketSettings, Integer.MAX_VALUE);
-
             settings.buckets = new WifiNative.BucketSettings[] {bucketSettings};
+
             if (mScannerImplsTracker.startSingleScan(settings)) {
+                mWifiMetrics.getScanMetrics().logScanStarted(
+                        WifiMetrics.ScanMetrics.SCAN_TYPE_SINGLE);
+
                 // store the active scan settings
                 mActiveScanSettings = settings;
                 // swap pending and active scan requests
@@ -1221,6 +1310,9 @@
             } else {
                 mWifiMetrics.incrementScanReturnEntry(
                         WifiMetricsProto.WifiLog.SCAN_UNKNOWN, mPendingScans.size());
+                mWifiMetrics.getScanMetrics().logScanFailedToStart(
+                        WifiMetrics.ScanMetrics.SCAN_TYPE_SINGLE);
+
                 // notify and cancel failed scans
                 sendOpFailedToAllAndClear(mPendingScans, WifiScanner.REASON_UNSPECIFIED,
                         "Failed to start single scan");
@@ -1279,12 +1371,32 @@
                         describeForLog(allResults));
                 entry.reportEvent(WifiScanner.CMD_SCAN_RESULT, 0, parcelableAllResults);
             }
+        }
 
+        void handleScanResults(@NonNull ScanData results) {
+            mWifiMetrics.getScanMetrics().logScanSucceeded(
+                    WifiMetrics.ScanMetrics.SCAN_TYPE_SINGLE, results.getResults().length);
+            mWifiMetrics.incrementScanReturnEntry(
+                    WifiMetricsProto.WifiLog.SCAN_SUCCESS, mActiveScans.size());
+            reportScanResults(results);
             // Cache full band (with DFS or not) scan results.
-            if (WifiScanner.isFullBandScan(results.getBandScanned(), true)) {
+            if (WifiScanner.isFullBandScan(results.getScannedBandsInternal(), true)) {
                 mCachedScanResults.clear();
                 mCachedScanResults.addAll(Arrays.asList(results.getResults()));
             }
+            if (mActiveScans.stream().anyMatch(rI -> rI.settings.ignoreLocationSettings)) {
+                // We were processing an emergency scan, post an alarm to inform WifiManager the
+                // end of that scan processing. If another scan is processed before the alarm fires,
+                // this timer is restarted (AlarmManager.set() using the same listener resets the
+                // timer). This delayed indication of emergency scan end prevents
+                // quick wifi toggle on/off if there is a burst of emergency scans when wifi is off.
+                mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                        mClock.getElapsedSinceBootMillis()
+                                + EMERGENCY_SCAN_END_INDICATION_DELAY_MILLIS,
+                        EMERGENCY_SCAN_END_INDICATION_ALARM_TAG,
+                        mEmergencyScanEndIndicationListener, getHandler());
+            }
+            mActiveScans.clear();
         }
 
         List<ScanResult> getCachedScanResultsAsList() {
@@ -1508,7 +1620,12 @@
                         replySucceeded(msg);
                         break;
                     case CMD_SCAN_RESULTS_AVAILABLE:
-                        reportScanResults(mScannerImpl.getLatestBatchedScanResults(true));
+                        WifiScanner.ScanData[] results = mScannerImpl.getLatestBatchedScanResults(
+                                true);
+                        mWifiMetrics.getScanMetrics().logScanSucceeded(
+                                WifiMetrics.ScanMetrics.SCAN_TYPE_BACKGROUND,
+                                results != null ? results.length : 0);
+                        reportScanResults(results);
                         break;
                     case CMD_FULL_SCAN_RESULTS:
                         reportFullScanResult((ScanResult) msg.obj, /* bucketsScanned */ msg.arg2);
@@ -1518,6 +1635,8 @@
                         transitionTo(mPausedState);
                         break;
                     case CMD_SCAN_FAILED:
+                        mWifiMetrics.getScanMetrics().logScanFailed(
+                                WifiMetrics.ScanMetrics.SCAN_TYPE_BACKGROUND);
                         Log.e(TAG, "WifiScanner background scan gave CMD_SCAN_FAILED");
                         sendBackgroundScanFailedToAllAndClear(
                                 WifiScanner.REASON_UNSPECIFIED, "Background Scan failed");
@@ -1552,11 +1671,11 @@
 
         private boolean addBackgroundScanRequest(ClientInfo ci, int handler,
                 ScanSettings settings, WorkSource workSource) {
-            // check the input
             if (ci == null) {
                 Log.d(TAG, "Failing scan request ClientInfo not found " + handler);
                 return false;
             }
+
             if (settings.periodInMs < WifiScanner.MIN_SCAN_PERIOD_MS) {
                 loge("Failing scan request because periodInMs is " + settings.periodInMs
                         + ", min scan period is: " + WifiScanner.MIN_SCAN_PERIOD_MS);
@@ -1602,6 +1721,8 @@
             }
 
             logScanRequest("addBackgroundScanRequest", ci, handler, null, settings, null);
+            mWifiMetrics.getScanMetrics().setClientUid(ci.mUid);
+            mWifiMetrics.getScanMetrics().setWorkSource(workSource);
             mActiveBackgroundScans.addRequest(ci, handler, workSource, settings);
 
             if (updateSchedule()) {
@@ -1653,6 +1774,8 @@
                         Log.d(TAG, "scan restarted with " + schedule.num_buckets
                                 + " bucket(s) and base period: " + schedule.base_period_ms);
                     }
+                    mWifiMetrics.getScanMetrics().logScanStarted(
+                            WifiMetrics.ScanMetrics.SCAN_TYPE_BACKGROUND);
                     return true;
                 } else {
                     mPreviousSchedule = null;
@@ -1666,6 +1789,8 @@
                                 + "[" + bucket.report_events + "]: "
                                 + ChannelHelper.toString(bucket));
                     }
+                    mWifiMetrics.getScanMetrics().logScanFailedToStart(
+                            WifiMetrics.ScanMetrics.SCAN_TYPE_BACKGROUND);
                     return false;
                 }
             }
@@ -2046,9 +2171,14 @@
                                     "bad parcel params");
                             return HANDLED;
                         }
+
                         if (addHwPnoScanRequest(ci, msg.arg2, scanSettings, pnoSettings)) {
+                            mWifiMetrics.getScanMetrics().logPnoScanEvent(
+                                    WifiMetrics.ScanMetrics.PNO_SCAN_STATE_STARTED);
                             replySucceeded(msg);
                         } else {
+                            mWifiMetrics.getScanMetrics().logPnoScanEvent(
+                                    WifiMetrics.ScanMetrics.PNO_SCAN_STATE_FAILED_TO_START);
                             replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request");
                             transitionTo(mStartedState);
                         }
@@ -2059,6 +2189,9 @@
                         break;
                     case CMD_PNO_NETWORK_FOUND:
                         ScanResult[] scanResults = ((ScanResult[]) msg.obj);
+                        mWifiMetrics.getScanMetrics().logPnoScanEvent(
+                                WifiMetrics.ScanMetrics.PNO_SCAN_STATE_COMPLETED_NETWORK_FOUND);
+
                         if (isSingleScanNeeded(scanResults)) {
                             ScanSettings activeScanSettings = getScanSettings();
                             if (activeScanSettings == null) {
@@ -2075,6 +2208,8 @@
                         }
                         break;
                     case CMD_PNO_SCAN_FAILED:
+                        mWifiMetrics.getScanMetrics().logPnoScanEvent(
+                                WifiMetrics.ScanMetrics.PNO_SCAN_STATE_FAILED);
                         sendPnoScanFailedToAllAndClear(
                                 WifiScanner.REASON_UNSPECIFIED, "pno scan failed");
                         transitionTo(mStartedState);
@@ -2235,6 +2370,7 @@
                         WifiScanner.CMD_START_SINGLE_SCAN, settings,
                         ClientModeImpl.WIFI_WORK_SOURCE);
             }
+            mWifiMetrics.getScanMetrics().setWorkSource(ClientModeImpl.WIFI_WORK_SOURCE);
         }
 
         /**
@@ -2551,9 +2687,10 @@
         if (mSingleScanStateMachine != null) {
             mSingleScanStateMachine.dump(fd, pw, args);
             pw.println();
-            pw.println("Latest scan results:");
             List<ScanResult> scanResults = mSingleScanStateMachine.getCachedScanResultsAsList();
             long nowMs = mClock.getElapsedSinceBootMillis();
+            Log.d(TAG, "Latest scan results nowMs = " + nowMs);
+            pw.println("Latest scan results:");
             ScanResultUtil.dumpScanResults(pw, scanResults, nowMs);
             pw.println();
         }
@@ -2620,7 +2757,7 @@
             case WifiScanner.SCAN_TYPE_HIGH_ACCURACY:
                 return "HIGH ACCURACY";
             default:
-                // This should never happen becuase we've validated the incoming type in
+                // This should never happen because we've validated the incoming type in
                 // |validateScanType|.
                 throw new IllegalArgumentException("Invalid scan type " + type);
         }
@@ -2628,22 +2765,25 @@
 
     static String describeTo(StringBuilder sb, ScanSettings scanSettings) {
         sb.append("ScanSettings { ")
-          .append(" type:").append(getScanTypeString(scanSettings.type))
-          .append(" band:").append(ChannelHelper.bandToString(scanSettings.band))
-          .append(" ignoreLocationSettings:").append(scanSettings.ignoreLocationSettings)
-          .append(" period:").append(scanSettings.periodInMs)
-          .append(" reportEvents:").append(scanSettings.reportEvents)
-          .append(" numBssidsPerScan:").append(scanSettings.numBssidsPerScan)
-          .append(" maxScansToCache:").append(scanSettings.maxScansToCache)
-          .append(" channels:[ ");
+                .append(" type:").append(getScanTypeString(scanSettings.type))
+                .append(" band:").append(ChannelHelper.bandToString(scanSettings.band))
+                .append(" ignoreLocationSettings:").append(scanSettings.ignoreLocationSettings)
+                .append(" period:").append(scanSettings.periodInMs)
+                .append(" reportEvents:").append(scanSettings.reportEvents)
+                .append(" numBssidsPerScan:").append(scanSettings.numBssidsPerScan)
+                .append(" maxScansToCache:").append(scanSettings.maxScansToCache)
+                .append(" rnrSetting:").append(
+                        SdkLevel.isAtLeastS() ? scanSettings.getRnrSetting() : "Not supported")
+                .append(" 6GhzPscOnlyEnabled:").append(
+                        SdkLevel.isAtLeastS() ? scanSettings.is6GhzPscOnlyEnabled()
+                                : "Not supported")
+                .append(" channels:[ ");
         if (scanSettings.channels != null) {
             for (int i = 0; i < scanSettings.channels.length; i++) {
-                sb.append(scanSettings.channels[i].frequency)
-                  .append(" ");
+                sb.append(scanSettings.channels[i].frequency).append(" ");
             }
         }
-        sb.append(" ] ")
-          .append(" } ");
+        sb.append(" ] ").append(" } ");
         return sb.toString();
     }
 
diff --git a/service/java/com/android/server/wifi/scanner/WificondChannelHelper.java b/service/java/com/android/server/wifi/scanner/WificondChannelHelper.java
index b52ce7e..927d04e 100644
--- a/service/java/com/android/server/wifi/scanner/WificondChannelHelper.java
+++ b/service/java/com/android/server/wifi/scanner/WificondChannelHelper.java
@@ -33,7 +33,7 @@
     public WificondChannelHelper(WifiNative wifiNative) {
         mWifiNative = wifiNative;
         final int[] emptyFreqList = new int[0];
-        setBandChannels(emptyFreqList, emptyFreqList, emptyFreqList, emptyFreqList);
+        setBandChannels(emptyFreqList, emptyFreqList, emptyFreqList, emptyFreqList, emptyFreqList);
         updateChannels();
     }
 
@@ -50,13 +50,16 @@
         int[] channels6G =
                 mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_6_GHZ);
         if (channels6G == null) Log.e(TAG, "Failed to get channels for 6GHz band");
+        int[] channels60G =
+                mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_60_GHZ);
+        if (channels60G == null) Log.e(TAG, "Failed to get channels for 60GHz band");
 
         if (channels24G == null || channels5G == null || channelsDfs == null
-                || channels6G == null) {
+                || channels6G == null || channels60G == null) {
             Log.e(TAG, "Failed to get all channels for band, not updating band channel lists");
         } else if (channels24G.length > 0 || channels5G.length > 0 || channelsDfs.length > 0
-                || channels6G.length > 0) {
-            setBandChannels(channels24G, channels5G, channelsDfs, channels6G);
+                || channels6G.length > 0 || channels60G.length > 0) {
+            setBandChannels(channels24G, channels5G, channelsDfs, channels6G, channels60G);
         } else {
             Log.e(TAG, "Got zero length for all channel lists");
         }
diff --git a/service/java/com/android/server/wifi/scanner/WificondScannerImpl.java b/service/java/com/android/server/wifi/scanner/WificondScannerImpl.java
index 66e0cfb..9879ca8 100644
--- a/service/java/com/android/server/wifi/scanner/WificondScannerImpl.java
+++ b/service/java/com/android/server/wifi/scanner/WificondScannerImpl.java
@@ -115,6 +115,8 @@
     @Override
     public void cleanup() {
         synchronized (mSettingsLock) {
+            cancelScanTimeout();
+            reportScanFailure();
             stopHwPnoScan();
             mLastScanSettings = null; // finally clear any active scan
             mLastPnoScanSettings = null; // finally clear any active scan
@@ -177,15 +179,16 @@
                 }
             }
             mLastScanSettings = new LastScanSettings(
-                        mClock.getElapsedSinceBootMillis(),
-                        reportFullResults, allFreqs, eventHandler);
+                    mClock.getElapsedSinceBootNanos(),
+                    reportFullResults, allFreqs, eventHandler);
 
             boolean success = false;
-            Set<Integer> freqs;
+            Set<Integer> freqs = Collections.emptySet();
             if (!allFreqs.isEmpty()) {
                 freqs = allFreqs.getScanFreqs();
                 success = mWifiNative.scan(
-                        getIfaceName(), settings.scanType, freqs, hiddenNetworkSSIDSet);
+                        getIfaceName(), settings.scanType, freqs, hiddenNetworkSSIDSet,
+                        settings.enable6GhzRnr);
                 if (!success) {
                     Log.e(TAG, "Failed to start scan, freqs=" + freqs);
                 }
@@ -196,7 +199,8 @@
             }
             if (success) {
                 if (DBG) {
-                    Log.d(TAG, "Starting wifi scan for freqs=" + freqs);
+                    Log.d(TAG, "Starting wifi scan for freqs=" + freqs
+                            + " on iface " + getIfaceName());
                 }
 
                 mScanTimeoutListener = new AlarmManager.OnAlarmListener() {
@@ -210,11 +214,7 @@
                         TIMEOUT_ALARM_TAG, mScanTimeoutListener, mEventHandler);
             } else {
                 // indicate scan failure async
-                mEventHandler.post(new Runnable() {
-                        @Override public void run() {
-                            reportScanFailure();
-                        }
-                    });
+                mEventHandler.post(() -> reportScanFailure());
             }
 
             return true;
@@ -321,8 +321,8 @@
             int numFilteredScanResults = 0;
             for (int i = 0; i < mNativePnoScanResults.size(); ++i) {
                 ScanResult result = mNativePnoScanResults.get(i).getScanResult();
-                long timestamp_ms = result.timestamp / 1000; // convert us -> ms
-                if (timestamp_ms > mLastPnoScanSettings.startTime) {
+                // nanoseconds -> microseconds
+                if (result.timestamp >= mLastPnoScanSettings.startTimeNanos / 1_000) {
                     hwPnoScanResults.add(result);
                 } else {
                     numFilteredScanResults++;
@@ -344,15 +344,15 @@
     /**
      * Return one of the WIFI_BAND_# values that was scanned for in this scan.
      */
-    private static int getBandScanned(ChannelCollection channelCollection) {
-        int bandScanned = WifiScanner.WIFI_BAND_UNSPECIFIED;
+    private static int getScannedBandsInternal(ChannelCollection channelCollection) {
+        int bandsScanned = WifiScanner.WIFI_BAND_UNSPECIFIED;
 
         for (@WifiBandIndex int i = 0; i < WifiScanner.WIFI_BAND_COUNT; i++) {
             if (channelCollection.containsBand(1 << i)) {
-                bandScanned |= 1 << i;
+                bandsScanned |= 1 << i;
             }
         }
-        return bandScanned;
+        return bandsScanned;
     }
 
     private void pollLatestScanData() {
@@ -367,8 +367,8 @@
             int numFilteredScanResults = 0;
             for (int i = 0; i < mNativeScanResults.size(); ++i) {
                 ScanResult result = mNativeScanResults.get(i).getScanResult();
-                long timestamp_ms = result.timestamp / 1000; // convert us -> ms
-                if (timestamp_ms > mLastScanSettings.startTime) {
+                // nanoseconds -> microseconds
+                if (result.timestamp >= mLastScanSettings.startTimeNanos / 1_000) {
                     if (mLastScanSettings.singleScanFreqs.containsChannel(
                                     result.frequency)) {
                         singleScanResults.add(result);
@@ -391,7 +391,7 @@
                 }
                 Collections.sort(singleScanResults, SCAN_RESULT_SORT_COMPARATOR);
                 mLatestSingleScanResult = new WifiScanner.ScanData(0, 0, 0,
-                        getBandScanned(mLastScanSettings.singleScanFreqs),
+                        getScannedBandsInternal(mLastScanSettings.singleScanFreqs),
                         singleScanResults.toArray(new ScanResult[singleScanResults.size()]));
                 mLastScanSettings.singleScanEventHandler
                         .onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
@@ -438,8 +438,8 @@
             }
 
             mLastPnoScanSettings = new LastPnoScanSettings(
-                        mClock.getElapsedSinceBootMillis(),
-                        settings.networkList, eventHandler);
+                    mClock.getElapsedSinceBootNanos(),
+                    settings.networkList, eventHandler);
 
             if (!startHwPnoScan(settings)) {
                 Log.e(TAG, "Failed to start PNO scan");
@@ -473,6 +473,7 @@
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         synchronized (mSettingsLock) {
             long nowMs = mClock.getElapsedSinceBootMillis();
+            Log.d(TAG, "Latest native scan results nowMs = " + nowMs);
             pw.println("Latest native scan results:");
             if (mNativeScanResults != null) {
                 List<ScanResult> scanResults = mNativeScanResults.stream().map(r -> {
@@ -501,17 +502,17 @@
     }
 
     private static class LastScanSettings {
-        LastScanSettings(long startTime,
+        LastScanSettings(long startTimeNanos,
                 boolean reportSingleScanFullResults,
                 ChannelCollection singleScanFreqs,
                 WifiNative.ScanEventHandler singleScanEventHandler) {
-            this.startTime = startTime;
+            this.startTimeNanos = startTimeNanos;
             this.reportSingleScanFullResults = reportSingleScanFullResults;
             this.singleScanFreqs = singleScanFreqs;
             this.singleScanEventHandler = singleScanEventHandler;
         }
 
-        public long startTime;
+        public long startTimeNanos;
         public boolean reportSingleScanFullResults;
         public ChannelCollection singleScanFreqs;
         public WifiNative.ScanEventHandler singleScanEventHandler;
@@ -519,15 +520,15 @@
     }
 
     private static class LastPnoScanSettings {
-        LastPnoScanSettings(long startTime,
+        LastPnoScanSettings(long startTimeNanos,
                 WifiNative.PnoNetwork[] pnoNetworkList,
                 WifiNative.PnoEventHandler pnoScanEventHandler) {
-            this.startTime = startTime;
+            this.startTimeNanos = startTimeNanos;
             this.pnoNetworkList = pnoNetworkList;
             this.pnoScanEventHandler = pnoScanEventHandler;
         }
 
-        public long startTime;
+        public long startTimeNanos;
         public WifiNative.PnoNetwork[] pnoNetworkList;
         public WifiNative.PnoEventHandler pnoScanEventHandler;
 
diff --git a/service/java/com/android/server/wifi/util/ActionListenerWrapper.java b/service/java/com/android/server/wifi/util/ActionListenerWrapper.java
new file mode 100644
index 0000000..274413d
--- /dev/null
+++ b/service/java/com/android/server/wifi/util/ActionListenerWrapper.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.util;
+
+import android.annotation.Nullable;
+import android.net.wifi.IActionListener;
+import android.os.RemoteException;
+
+/**
+ * Simple wrapper around {@link IActionListener} that triggers onSuccess/onFailure
+ * on a best effort basis.
+ */
+public class ActionListenerWrapper {
+    private final IActionListener mListener;
+
+    public ActionListenerWrapper(@Nullable IActionListener listener) {
+        mListener = listener;
+    }
+
+    /** Trigger the failure callback with a reason if the listener is non-null. */
+    public void sendFailure(int reason) {
+        if (mListener != null) {
+            try {
+                mListener.onFailure(reason);
+            } catch (RemoteException e) {
+                // no-op (client may be dead, nothing to be done)
+            }
+        }
+    }
+
+    /** Trigger the success callback with a reason if the listener is non-null. */
+    public void sendSuccess() {
+        if (mListener != null) {
+            try {
+                mListener.onSuccess();
+            } catch (RemoteException e) {
+                // no-op (client may be dead, nothing to be done)
+            }
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/util/ApConfigUtil.java b/service/java/com/android/server/wifi/util/ApConfigUtil.java
index 1e8cc24..d09e798 100644
--- a/service/java/com/android/server/wifi/util/ApConfigUtil.java
+++ b/service/java/com/android/server/wifi/util/ApConfigUtil.java
@@ -20,22 +20,34 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Resources;
+import android.net.wifi.CoexUnsafeChannel;
 import android.net.wifi.ScanResult;
 import android.net.wifi.SoftApCapability;
 import android.net.wifi.SoftApConfiguration;
 import android.net.wifi.SoftApConfiguration.BandType;
+import android.net.wifi.SoftApInfo;
+import android.net.wifi.WifiClient;
 import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
 import android.net.wifi.WifiScanner;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseArray;
 
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.WifiNative;
+import com.android.server.wifi.coex.CoexManager;
 import com.android.wifi.resources.R;
 
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Random;
+import java.util.Set;
+
 
 /**
  * Provide utility functions for updating soft AP related configuration.
@@ -43,6 +55,7 @@
 public class ApConfigUtil {
     private static final String TAG = "ApConfigUtil";
 
+    public static final int INVALID_VALUE_FOR_BAND_OR_CHANNEL = -1;
     public static final int DEFAULT_AP_BAND = SoftApConfiguration.BAND_2GHZ;
     public static final int DEFAULT_AP_CHANNEL = 6;
     public static final int HIGHEST_2G_AP_CHANNEL = 14;
@@ -103,6 +116,8 @@
                 return WifiScanner.WIFI_BAND_5_GHZ;
             case SoftApConfiguration.BAND_6GHZ:
                 return WifiScanner.WIFI_BAND_6_GHZ;
+            case SoftApConfiguration.BAND_60GHZ:
+                return WifiScanner.WIFI_BAND_60_GHZ;
             default:
                 return WifiScanner.WIFI_BAND_UNSPECIFIED;
         }
@@ -116,7 +131,7 @@
      * @return center frequency in Mhz of the channel, -1 if no match
      */
     public static int convertChannelToFrequency(int channel, @BandType int band) {
-        return ScanResult.convertChannelToFrequencyMhz(channel,
+        return ScanResult.convertChannelToFrequencyMhzIfSupported(channel,
                 apConfig2wifiScannerBand(band));
     }
 
@@ -133,6 +148,8 @@
             return SoftApConfiguration.BAND_5GHZ;
         } else if (ScanResult.is6GHz(frequency)) {
             return SoftApConfiguration.BAND_6GHZ;
+        } else if (ScanResult.is60GHz(frequency)) {
+            return SoftApConfiguration.BAND_60GHZ;
         }
 
         return -1;
@@ -158,10 +175,26 @@
     }
 
     /**
+     * Add 2.4Ghz to target band when 2.4Ghz SoftAp supported.
+     *
+     * @param targetBand The band is needed to add 2.4G.
+     * @return The band includes 2.4Ghz when 2.4G SoftAp supported.
+     */
+    public static @BandType int append24GToBandIf24GSupported(@BandType int targetBand,
+            Context context) {
+        if (isBandSupported(SoftApConfiguration.BAND_2GHZ, context)) {
+            return targetBand | SoftApConfiguration.BAND_2GHZ;
+        }
+        return targetBand;
+    }
+
+    /**
      * Checks if band is a valid combination of {link  SoftApConfiguration#BandType} values
      */
     public static boolean isBandValid(@BandType int band) {
-        return ((band != 0) && ((band & ~SoftApConfiguration.BAND_ANY) == 0));
+        int bandAny = SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ
+                | SoftApConfiguration.BAND_6GHZ | SoftApConfiguration.BAND_60GHZ;
+        return ((band != 0) && ((band & ~bandAny) == 0));
     }
 
     /**
@@ -184,6 +217,46 @@
         return ((band & (band - 1)) != 0);
     }
 
+
+    /**
+     * Checks whether or not band configuration is supported.
+     * @param apBand a combination of the bands
+     * @param context the caller context used to get value from resource file.
+     * @return true if band is supported, false otherwise
+     */
+    public static boolean isBandSupported(@BandType int apBand, Context context) {
+        if (!isBandValid(apBand)) {
+            Log.e(TAG, "Invalid SoftAp band. ");
+            return false;
+        }
+
+        if (containsBand(apBand, SoftApConfiguration.BAND_2GHZ)
+                && !isSoftAp24GhzSupported(context)) {
+            Log.e(TAG, "Can not start softAp with 2GHz band, not supported.");
+            return false;
+        }
+
+        if (containsBand(apBand, SoftApConfiguration.BAND_5GHZ)
+                && !isSoftAp5GhzSupported(context)) {
+            Log.e(TAG, "Can not start softAp with 5GHz band, not supported.");
+            return false;
+        }
+
+        if (containsBand(apBand, SoftApConfiguration.BAND_6GHZ)
+                && !isSoftAp6GhzSupported(context)) {
+            Log.e(TAG, "Can not start softAp with 6GHz band, not supported.");
+            return false;
+        }
+
+        if (containsBand(apBand, SoftApConfiguration.BAND_60GHZ)
+                && !isSoftAp60GhzSupported(context)) {
+            Log.e(TAG, "Can not start softAp with 6GHz band, not supported.");
+            return false;
+        }
+
+        return true;
+    }
+
     /**
      * Convert string to channel list
      * Format of the list is a comma separated channel numbers, or range of channel numbers
@@ -230,15 +303,35 @@
     }
 
     /**
-     * Get channel frequencies for band that are allowed by both regulatory and OEM configuration
+     * Returns the unsafe channels frequency from coex module.
+     *
+     * @param coexManager reference used to get unsafe channels to avoid for coex.
+     */
+    @NonNull
+    public static Set<Integer> getUnsafeChannelFreqsFromCoex(@NonNull CoexManager coexManager) {
+        Set<Integer> unsafeFreqs = new HashSet<>();
+        if (SdkLevel.isAtLeastS()) {
+            for (CoexUnsafeChannel unsafeChannel : coexManager.getCoexUnsafeChannels()) {
+                unsafeFreqs.add(ScanResult.convertChannelToFrequencyMhzIfSupported(
+                        unsafeChannel.getChannel(), unsafeChannel.getBand()));
+            }
+        }
+        return unsafeFreqs;
+    }
+
+    /**
+     * Get channels or frequencies for band that are allowed by both regulatory
+     * and OEM configuration.
      *
      * @param band to get channels for
      * @param wifiNative reference used to get regulatory restrictionsimport java.util.Arrays;
      * @param resources used to get OEM restrictions
+     * @param inFrequencyMHz true to convert channel to frequency.
      * @return A list of frequencies that are allowed, null on error.
      */
     public static List<Integer> getAvailableChannelFreqsForBand(
-            @BandType int band, WifiNative wifiNative, Resources resources) {
+            @BandType int band, WifiNative wifiNative, Resources resources,
+            boolean inFrequencyMHz) {
         if (!isBandValid(band) || isMultiband(band)) {
             return null;
         }
@@ -261,6 +354,11 @@
                         R.string.config_wifiSoftap6gChannelList));
                 scannerBand = WifiScanner.WIFI_BAND_6_GHZ;
                 break;
+            case SoftApConfiguration.BAND_60GHZ:
+                configuredList = convertStringToChannelList(resources.getString(
+                        R.string.config_wifiSoftap60gChannelList));
+                scannerBand = WifiScanner.WIFI_BAND_60_GHZ;
+                break;
             default:
                 return null;
         }
@@ -269,91 +367,187 @@
         int[] regulatoryArray = wifiNative.getChannelsForBand(scannerBand);
         List<Integer> regulatoryList = new ArrayList<Integer>();
         for (int freq : regulatoryArray) {
-            regulatoryList.add(freq);
+            if (inFrequencyMHz) {
+                regulatoryList.add(freq);
+            } else {
+                regulatoryList.add(ScanResult.convertFrequencyMhzToChannelIfSupported(freq));
+            }
         }
 
         if (configuredList == null || configuredList.isEmpty()) {
             return regulatoryList;
         }
-
         List<Integer> filteredList = new ArrayList<Integer>();
         // Otherwise, filter the configured list
         for (int channel : configuredList) {
-            int channelFreq = convertChannelToFrequency(channel, band);
-
-            if (regulatoryList.contains(channelFreq)) {
-                filteredList.add(channelFreq);
+            if (inFrequencyMHz) {
+                int channelFreq = convertChannelToFrequency(channel, band);
+                if (regulatoryList.contains(channelFreq)) {
+                    filteredList.add(channelFreq);
+                }
+            } else if (regulatoryList.contains(channel)) {
+                filteredList.add(channel);
             }
         }
         return filteredList;
     }
 
     /**
-     * Return a channel number for AP setup based on the frequency band.
+     * Return a channel frequency for AP setup based on the frequency band.
      * @param apBand one or combination of the values of SoftApConfiguration.BAND_*.
      * @param wifiNative reference used to collect regulatory restrictions.
+     * @param coexManager reference used to get unsafe channels to avoid for coex.
      * @param resources the resources to use to get configured allowed channels.
      * @return a valid channel frequency on success, -1 on failure.
      */
-    public static int chooseApChannel(int apBand, WifiNative wifiNative, Resources resources) {
+    public static int chooseApChannel(int apBand, @NonNull WifiNative wifiNative,
+            @NonNull CoexManager coexManager, @NonNull Resources resources) {
         if (!isBandValid(apBand)) {
             Log.e(TAG, "Invalid band: " + apBand);
             return -1;
         }
 
-        List<Integer> allowedFreqList = null;
-
-        if ((apBand & SoftApConfiguration.BAND_6GHZ) != 0) {
-            allowedFreqList = getAvailableChannelFreqsForBand(SoftApConfiguration.BAND_6GHZ,
-                    wifiNative, resources);
-            if (allowedFreqList != null && allowedFreqList.size() > 0) {
-                return allowedFreqList.get(sRandom.nextInt(allowedFreqList.size())).intValue();
+        Set<Integer> unsafeFreqs = new HashSet<>();
+        if (SdkLevel.isAtLeastS()) {
+            unsafeFreqs = getUnsafeChannelFreqsFromCoex(coexManager);
+        }
+        final int[] bandPreferences = new int[]{
+                SoftApConfiguration.BAND_60GHZ,
+                SoftApConfiguration.BAND_6GHZ,
+                SoftApConfiguration.BAND_5GHZ,
+                SoftApConfiguration.BAND_2GHZ};
+        int selectedUnsafeFreq = 0;
+        for (int band : bandPreferences) {
+            if ((apBand & band) == 0) {
+                continue;
+            }
+            final List<Integer> availableFreqs =
+                    getAvailableChannelFreqsForBand(band, wifiNative, resources, true);
+            if (availableFreqs == null || availableFreqs.isEmpty()) {
+                continue;
+            }
+            // Separate the available freqs by safe and unsafe.
+            List<Integer> availableSafeFreqs = new ArrayList<>();
+            List<Integer> availableUnsafeFreqs = new ArrayList<>();
+            for (int freq : availableFreqs) {
+                if (unsafeFreqs.contains(freq)) {
+                    availableUnsafeFreqs.add(freq);
+                } else {
+                    availableSafeFreqs.add(freq);
+                }
+            }
+            // If there are safe freqs available for this band, randomly select one.
+            if (!availableSafeFreqs.isEmpty()) {
+                return availableSafeFreqs.get(sRandom.nextInt(availableSafeFreqs.size()));
+            } else if (!availableUnsafeFreqs.isEmpty() && selectedUnsafeFreq == 0) {
+                // Save an unsafe freq from the first preferred band to fall back on later.
+                selectedUnsafeFreq = availableUnsafeFreqs.get(
+                        sRandom.nextInt(availableUnsafeFreqs.size()));
             }
         }
-
-        if ((apBand & SoftApConfiguration.BAND_5GHZ) != 0) {
-            allowedFreqList = getAvailableChannelFreqsForBand(SoftApConfiguration.BAND_5GHZ,
-                    wifiNative, resources);
-            if (allowedFreqList != null && allowedFreqList.size() > 0) {
-                return allowedFreqList.get(sRandom.nextInt(allowedFreqList.size())).intValue();
-            }
+        // If all available channels are soft unsafe, select a random one of the highest band.
+        boolean isHardUnsafe = false;
+        if (SdkLevel.isAtLeastS()) {
+            isHardUnsafe =
+                    (coexManager.getCoexRestrictions() & WifiManager.COEX_RESTRICTION_SOFTAP) != 0;
+        }
+        if (!isHardUnsafe && selectedUnsafeFreq != 0) {
+            return selectedUnsafeFreq;
         }
 
-        if ((apBand & SoftApConfiguration.BAND_2GHZ) != 0) {
-            allowedFreqList = getAvailableChannelFreqsForBand(SoftApConfiguration.BAND_2GHZ,
-                    wifiNative, resources);
-            if (allowedFreqList != null && allowedFreqList.size() > 0) {
-                return allowedFreqList.get(sRandom.nextInt(allowedFreqList.size())).intValue();
-            }
-        }
-
-        // If the default AP band is allowed, just use the default channel
+        // If all available channels are hard unsafe, select the default AP channel.
         if (containsBand(apBand, DEFAULT_AP_BAND)) {
-            Log.e(TAG, "Allowed channel list not specified, selecting default channel");
-            /* Use default channel. */
-            return convertChannelToFrequency(DEFAULT_AP_CHANNEL,
+            final int defaultChannelFreq = convertChannelToFrequency(DEFAULT_AP_CHANNEL,
                     DEFAULT_AP_BAND);
+            Log.e(TAG, "Allowed channel list not specified, selecting default channel");
+            if (isHardUnsafe && unsafeFreqs.contains(defaultChannelFreq)) {
+                Log.e(TAG, "Default channel is hard restricted due to coex");
+            }
+            return defaultChannelFreq;
         }
-
         Log.e(TAG, "No available channels");
         return -1;
     }
 
     /**
+     * Remove unavailable bands from the input band and return the resulting
+     * (remaining) available bands. Unavailable bands are those which don't have channels available.
+     *
+     * @param capability SoftApCapability which inidcates supported channel list.
+     * @param targetBand The target band which plan to enable
+     * @param coexManager reference to CoexManager
+     *
+     * @return the available band which removed the unsupported band.
+     *         0 when all of the band is not supported.
+     */
+    public static @BandType int removeUnavailableBands(SoftApCapability capability,
+            @NonNull int targetBand, CoexManager coexManager) {
+        int availableBand = targetBand;
+        for (int band : SoftApConfiguration.BAND_TYPES) {
+            Set<Integer> availableChannelFreqsList = new HashSet<>();
+            if ((targetBand & band) != 0) {
+                for (int channel : capability.getSupportedChannelList(band)) {
+                    availableChannelFreqsList.add(convertChannelToFrequency(channel, band));
+                }
+                // Only remove hard unsafe channels
+                if (SdkLevel.isAtLeastS()
+                        && (coexManager.getCoexRestrictions() & WifiManager.COEX_RESTRICTION_SOFTAP)
+                        != 0) {
+                    availableChannelFreqsList.removeAll(getUnsafeChannelFreqsFromCoex(coexManager));
+                }
+                if (availableChannelFreqsList.size() == 0) {
+                    availableBand &= ~band;
+                }
+            }
+        }
+        return availableBand;
+    }
+
+    /**
+     * Remove all unsupported bands from the input band and return the resulting
+     * (remaining) support bands. Unsupported bands are those which don't have channels available.
+     *
+     * @param context The caller context used to get value from resource file.
+     * @param band The target band which plan to enable
+     *
+     * @return the available band which removed the unsupported band.
+     *         0 when all of the band is not supported.
+     */
+    public static @BandType int removeUnsupportedBands(Context context,
+            @NonNull int band) {
+        int availableBand = band;
+        if (((band & SoftApConfiguration.BAND_2GHZ) != 0) && !isSoftAp24GhzSupported(context)) {
+            availableBand &= ~SoftApConfiguration.BAND_2GHZ;
+        }
+        if (((band & SoftApConfiguration.BAND_5GHZ) != 0) && !isSoftAp5GhzSupported(context)) {
+            availableBand &= ~SoftApConfiguration.BAND_5GHZ;
+        }
+        if (((band & SoftApConfiguration.BAND_6GHZ) != 0) && !isSoftAp6GhzSupported(context)) {
+            availableBand &= ~SoftApConfiguration.BAND_6GHZ;
+        }
+        if (((band & SoftApConfiguration.BAND_60GHZ) != 0) && !isSoftAp60GhzSupported(context)) {
+            availableBand &= ~SoftApConfiguration.BAND_60GHZ;
+        }
+        return availableBand;
+    }
+
+    /**
      * Update AP band and channel based on the provided country code and band.
      * This will also set
      * @param wifiNative reference to WifiNative
+     * @param coexManager reference to CoexManager
      * @param resources the resources to use to get configured allowed channels.
      * @param countryCode country code
      * @param config configuration to update
      * @return an integer result code
      */
     public static int updateApChannelConfig(WifiNative wifiNative,
-                                            Resources resources,
-                                            String countryCode,
-                                            SoftApConfiguration.Builder configBuilder,
-                                            SoftApConfiguration config,
-                                            boolean acsEnabled) {
+            @NonNull CoexManager coexManager,
+            Resources resources,
+            String countryCode,
+            SoftApConfiguration.Builder configBuilder,
+            SoftApConfiguration config,
+            boolean acsEnabled) {
         /* Use default band and channel for device without HAL. */
         if (!wifiNative.isHalStarted()) {
             configBuilder.setChannel(DEFAULT_AP_CHANNEL, DEFAULT_AP_BAND);
@@ -369,14 +563,15 @@
 
         /* Select a channel if it is not specified and ACS is not enabled */
         if ((config.getChannel() == 0) && !acsEnabled) {
-            int freq = chooseApChannel(config.getBand(), wifiNative, resources);
+            int freq = chooseApChannel(config.getBand(), wifiNative, coexManager, resources);
             if (freq == -1) {
                 /* We're not able to get channel from wificond. */
                 Log.e(TAG, "Failed to get available channel.");
                 return ERROR_NO_CHANNEL;
             }
             configBuilder.setChannel(
-                    ScanResult.convertFrequencyMhzToChannel(freq), convertFrequencyToBand(freq));
+                    ScanResult.convertFrequencyMhzToChannelIfSupported(freq),
+                    convertFrequencyToBand(freq));
         }
 
         return SUCCESS;
@@ -412,6 +607,9 @@
                 case WifiConfiguration.AP_BAND_5GHZ:
                     band = SoftApConfiguration.BAND_5GHZ;
                     break;
+                case WifiConfiguration.AP_BAND_60GHZ:
+                    band = SoftApConfiguration.BAND_60GHZ;
+                    break;
                 default:
                     // WifiConfiguration.AP_BAND_ANY means only 2GHz and 5GHz bands
                     band = SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ;
@@ -455,6 +653,32 @@
             Log.d(TAG, "Update Softap capability, add SAE feature support");
             features |= SoftApCapability.SOFTAP_FEATURE_WPA3_SAE;
         }
+
+        if (isMacCustomizationSupported(context)) {
+            Log.d(TAG, "Update Softap capability, add MAC customization support");
+            features |= SoftApCapability.SOFTAP_FEATURE_MAC_ADDRESS_CUSTOMIZATION;
+        }
+
+        if (isSoftAp24GhzSupported(context)) {
+            Log.d(TAG, "Update Softap capability, add 2.4G support");
+            features |= SoftApCapability.SOFTAP_FEATURE_BAND_24G_SUPPORTED;
+        }
+
+        if (isSoftAp5GhzSupported(context)) {
+            Log.d(TAG, "Update Softap capability, add 5G support");
+            features |= SoftApCapability.SOFTAP_FEATURE_BAND_5G_SUPPORTED;
+        }
+
+        if (isSoftAp6GhzSupported(context)) {
+            Log.d(TAG, "Update Softap capability, add 6G support");
+            features |= SoftApCapability.SOFTAP_FEATURE_BAND_6G_SUPPORTED;
+        }
+
+        if (isSoftAp60GhzSupported(context)) {
+            Log.d(TAG, "Update Softap capability, add 60G support");
+            features |= SoftApCapability.SOFTAP_FEATURE_BAND_60G_SUPPORTED;
+        }
+
         SoftApCapability capability = new SoftApCapability(features);
         int hardwareSupportedMaxClient = context.getResources().getInteger(
                 R.integer.config_wifiHardwareSoftapMaxClientCount);
@@ -467,7 +691,29 @@
     }
 
     /**
-     * Helper function to get hal support client force disconnect or not.
+     * Helper function to get device support AP MAC randomization or not.
+     *
+     * @param context the caller context used to get value from resource file.
+     * @return true if supported, false otherwise.
+     */
+    public static boolean isApMacRandomizationSupported(@NonNull Context context) {
+        return context.getResources().getBoolean(
+                    R.bool.config_wifi_ap_mac_randomization_supported);
+    }
+
+    /**
+     * Helper function to get HAL support bridged AP or not.
+     *
+     * @param context the caller context used to get value from resource file.
+     * @return true if supported, false otherwise.
+     */
+    public static boolean isBridgedModeSupported(@NonNull Context context) {
+        return SdkLevel.isAtLeastS() && context.getResources().getBoolean(
+                    R.bool.config_wifiBridgedSoftApSupported);
+    }
+
+    /**
+     * Helper function to get HAL support client force disconnect or not.
      *
      * @param context the caller context used to get value from resource file.
      * @return true if supported, false otherwise.
@@ -500,6 +746,77 @@
     }
 
     /**
+     * Helper function to get MAC Address customization or not.
+     *
+     * @param context the caller context used to get value from resource file.
+     * @return true if supported, false otherwise.
+     */
+    public static boolean isMacCustomizationSupported(@NonNull Context context) {
+        return context.getResources().getBoolean(
+                R.bool.config_wifiSoftapMacAddressCustomizationSupported);
+    }
+
+    /**
+     * Helper function to get whether or not 2.4G Soft AP support.
+     *
+     * @param context the caller context used to get value from resource file.
+     * @return true if supported, false otherwise.
+     */
+    public static boolean isSoftAp24GhzSupported(@NonNull Context context) {
+        return context.getResources().getBoolean(R.bool.config_wifi24ghzSupport)
+                && context.getResources().getBoolean(
+                R.bool.config_wifiSoftap24ghzSupported);
+    }
+
+    /**
+     * Helper function to get whether or not 5G Soft AP support.
+     *
+     * @param context the caller context used to get value from resource file.
+     * @return true if supported, false otherwise.
+     */
+    public static boolean isSoftAp5GhzSupported(@NonNull Context context) {
+        return context.getResources().getBoolean(R.bool.config_wifi5ghzSupport)
+                && context.getResources().getBoolean(
+                R.bool.config_wifiSoftap5ghzSupported);
+    }
+
+    /**
+     * Helper function to get whether or not 6G Soft AP support
+     *
+     * @param context the caller context used to get value from resource file.
+     * @return true if supported, false otherwise.
+     */
+    public static boolean isSoftAp6GhzSupported(@NonNull Context context) {
+        return context.getResources().getBoolean(R.bool.config_wifi6ghzSupport)
+                && context.getResources().getBoolean(
+                R.bool.config_wifiSoftap6ghzSupported);
+    }
+
+    /**
+     * Helper function to get whether or not 60G Soft AP support.
+     *
+     * @param context the caller context used to get value from resource file.
+     * @return true if supported, false otherwise.
+     */
+    public static boolean isSoftAp60GhzSupported(@NonNull Context context) {
+        return context.getResources().getBoolean(R.bool.config_wifi60ghzSupport)
+                && context.getResources().getBoolean(
+                R.bool.config_wifiSoftap60ghzSupported);
+    }
+
+    /**
+     * Helper function to get whether or not dynamic country code update is supported when Soft AP
+     * enabled.
+     *
+     * @param context the caller context used to get value from resource file.
+     * @return true if supported, false otherwise.
+     */
+    public static boolean isSoftApDynamicCountryCodeSupported(@NonNull Context context) {
+        return context.getResources().getBoolean(
+                R.bool.config_wifiSoftApDynamicCountryCodeUpdateSupported);
+    }
+
+    /**
      * Helper function for comparing two SoftApConfiguration.
      *
      * @param currentConfig the original configuration.
@@ -515,7 +832,9 @@
                 || !Objects.equals(currentConfig.getPassphrase(), newConfig.getPassphrase())
                 || currentConfig.isHiddenSsid() != newConfig.isHiddenSsid()
                 || currentConfig.getBand() != newConfig.getBand()
-                || currentConfig.getChannel() != newConfig.getChannel();
+                || currentConfig.getChannel() != newConfig.getChannel()
+                || (SdkLevel.isAtLeastS() && !currentConfig.getChannels().toString()
+                        .equals(newConfig.getChannels().toString()));
     }
 
 
@@ -543,6 +862,117 @@
             Log.d(TAG, "Error, SAE requires HAL support");
             return false;
         }
+
+        // The bands length should always 1 in R. Adding SdkLevel.isAtLeastS for lint check only.
+        if (config.getBands().length > 1 && SdkLevel.isAtLeastS()) {
+            int[] bands = config.getBands();
+            if ((bands[0] & SoftApConfiguration.BAND_6GHZ) != 0
+                    || (bands[0] & SoftApConfiguration.BAND_60GHZ) != 0
+                    || (bands[1] & SoftApConfiguration.BAND_6GHZ) != 0
+                    || (bands[1] & SoftApConfiguration.BAND_60GHZ) != 0) {
+                Log.d(TAG, "Error, dual APs doesn't support on 6GHz and 60GHz");
+                return false;
+            }
+            if (!capability.areFeaturesSupported(SoftApCapability.SOFTAP_FEATURE_ACS_OFFLOAD)
+                    && (config.getChannels().valueAt(0) == 0
+                    || config.getChannels().valueAt(1) == 0)) {
+                Log.d(TAG, "Error, dual APs requires HAL ACS support when channel isn't specified");
+                return false;
+            }
+        }
         return true;
     }
+
+
+    /**
+     * Check if need to provide freq range for ACS.
+     *
+     * @param band in SoftApConfiguration.BandType
+     * @return true when freq ranges is needed, otherwise false.
+     */
+    public static boolean isSendFreqRangesNeeded(@BandType int band, Context context) {
+        // Fist we check if one of the selected bands has restrictions in the overlay file.
+        // Note,
+        //   - We store the config string here for future use, hence we need to check all bands.
+        //   - If there is no OEM restriction, we store the full band
+        boolean retVal = false;
+        String channelList = "";
+        if ((band & SoftApConfiguration.BAND_2GHZ) != 0) {
+            channelList =
+                context.getResources().getString(R.string.config_wifiSoftap2gChannelList);
+            if (!TextUtils.isEmpty(channelList)) {
+                retVal = true;
+            }
+        }
+
+        if ((band & SoftApConfiguration.BAND_5GHZ) != 0) {
+            channelList =
+                context.getResources().getString(R.string.config_wifiSoftap5gChannelList);
+            if (!TextUtils.isEmpty(channelList)) {
+                retVal = true;
+            }
+        }
+
+        if ((band & SoftApConfiguration.BAND_6GHZ) != 0) {
+            channelList =
+                context.getResources().getString(R.string.config_wifiSoftap6gChannelList);
+            if (!TextUtils.isEmpty(channelList)) {
+                retVal = true;
+            }
+        }
+
+        // If any of the selected band has restriction in the overlay file, we return true.
+        if (retVal) {
+            return true;
+        }
+
+        // Next, if only one of 5G or 6G is selected, then we need freqList to separate them
+        // Since there is no other way.
+        if (((band & SoftApConfiguration.BAND_5GHZ) != 0)
+                && ((band & SoftApConfiguration.BAND_6GHZ) == 0)) {
+            return true;
+        }
+        if (((band & SoftApConfiguration.BAND_5GHZ) == 0)
+                && ((band & SoftApConfiguration.BAND_6GHZ) != 0)) {
+            return true;
+        }
+
+        // In all other cases, we don't need to set the freqList
+        return false;
+    }
+
+    /**
+     * Deep copy for object Map<String, SoftApInfo>
+     */
+    public static Map<String, SoftApInfo> deepCopyForSoftApInfoMap(
+            Map<String, SoftApInfo> originalMap) {
+        if (originalMap == null) {
+            return null;
+        }
+        Map<String, SoftApInfo> deepCopyMap = new HashMap<String, SoftApInfo>();
+        for (Map.Entry<String, SoftApInfo> entry: originalMap.entrySet()) {
+            deepCopyMap.put(entry.getKey(), new SoftApInfo(entry.getValue()));
+        }
+        return deepCopyMap;
+    }
+
+    /**
+     * Deep copy for object Map<String, List<WifiClient>>
+     */
+    public static Map<String, List<WifiClient>> deepCopyForWifiClientListMap(
+            Map<String, List<WifiClient>> originalMap) {
+        if (originalMap == null) {
+            return null;
+        }
+        Map<String, List<WifiClient>> deepCopyMap = new HashMap<String, List<WifiClient>>();
+        for (Map.Entry<String, List<WifiClient>> entry: originalMap.entrySet()) {
+            List<WifiClient> clients = new ArrayList<>();
+            for (WifiClient client : entry.getValue()) {
+                clients.add(new WifiClient(client.getMacAddress(),
+                        client.getApInstanceIdentifier()));
+            }
+            deepCopyMap.put(entry.getKey(), clients);
+        }
+        return deepCopyMap;
+    }
 }
diff --git a/service/java/com/android/server/wifi/util/ExternalCallbackTracker.java b/service/java/com/android/server/wifi/util/ExternalCallbackTracker.java
deleted file mode 100644
index 225e68a..0000000
--- a/service/java/com/android/server/wifi/util/ExternalCallbackTracker.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wifi.util;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import com.android.internal.util.Preconditions;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Holds a list of external app-provided binder callback objects and tracks the death
- * of the callback object.
- * @param <T> Callback object type.
- */
-public class ExternalCallbackTracker<T> {
-    private static final String TAG = "WifiExternalCallbackTracker";
-
-    /* Limit on number of registered callbacks to track and prevent potential memory leak */
-    private static final int NUM_CALLBACKS_WARN_LIMIT = 10;
-    private static final int NUM_CALLBACKS_WTF_LIMIT = 20;
-
-    /**
-     * Container for storing info about each external callback and tracks it's death.
-     */
-    private static class ExternalCallbackHolder<T> implements IBinder.DeathRecipient {
-        private final IBinder mBinder;
-        private final T mCallbackObject;
-        private final DeathCallback mDeathCallback;
-
-        /**
-         * Callback to be invoked on death of the app hosting the binder.
-         */
-        public interface DeathCallback {
-            /**
-             * Called when the corresponding app has died.
-             */
-            void onDeath();
-        }
-
-        private ExternalCallbackHolder(@NonNull IBinder binder, @NonNull T callbackObject,
-                                       @NonNull DeathCallback deathCallback) {
-            mBinder = Preconditions.checkNotNull(binder);
-            mCallbackObject = Preconditions.checkNotNull(callbackObject);
-            mDeathCallback = Preconditions.checkNotNull(deathCallback);
-        }
-
-        /**
-         * Static method to create a new {@link ExternalCallbackHolder} object and register for
-         * death notification of the associated binder.
-         * @return an instance of {@link ExternalCallbackHolder} if there are no failures, otherwise
-         * null.
-         */
-        public static <T> ExternalCallbackHolder<T> createAndLinkToDeath(
-                @NonNull IBinder binder, @NonNull T callbackObject,
-                @NonNull DeathCallback deathCallback) {
-            ExternalCallbackHolder<T> externalCallback =
-                    new ExternalCallbackHolder<>(binder, callbackObject, deathCallback);
-            try {
-                binder.linkToDeath(externalCallback, 0);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error on linkToDeath - " + e);
-                return null;
-            }
-            return externalCallback;
-        }
-
-        /**
-         * Unlinks this object from binder death.
-         */
-        public void reset() {
-            mBinder.unlinkToDeath(this, 0);
-        }
-
-        /**
-         * Retrieve the callback object.
-         */
-        public T getCallback() {
-            return mCallbackObject;
-        }
-
-        /**
-         * App hosting the binder has died.
-         */
-        @Override
-        public void binderDied() {
-            mDeathCallback.onDeath();
-            Log.d(TAG, "Binder died " + mBinder);
-        }
-    }
-
-    private final Map<Integer, ExternalCallbackHolder<T>> mCallbacks;
-    private final Handler mHandler;
-
-    public ExternalCallbackTracker(Handler handler) {
-        mHandler = handler;
-        mCallbacks = new HashMap<>();
-    }
-
-    /**
-     * Add a callback object to tracker.
-     * @return true on success, false on failure.
-     */
-    public boolean add(@NonNull IBinder binder, @NonNull T callbackObject, int callbackIdentifier) {
-        ExternalCallbackHolder<T> externalCallback = ExternalCallbackHolder.createAndLinkToDeath(
-                binder, callbackObject, () -> {
-                    mHandler.post(() -> {
-                        Log.d(TAG, "Remove external callback on death " + callbackIdentifier);
-                        remove(callbackIdentifier);
-                    });
-                });
-        if (externalCallback == null) return false;
-        if (mCallbacks.containsKey(callbackIdentifier)) {
-            Log.d(TAG, "Replacing callback " + callbackIdentifier);
-            remove(callbackIdentifier);
-        }
-        mCallbacks.put(callbackIdentifier, externalCallback);
-        if (mCallbacks.size() > NUM_CALLBACKS_WTF_LIMIT) {
-            Log.wtf(TAG, "Too many callbacks: " + mCallbacks.size());
-        } else if (mCallbacks.size() > NUM_CALLBACKS_WARN_LIMIT) {
-            Log.w(TAG, "Too many callbacks: " + mCallbacks.size());
-        }
-        return true;
-    }
-
-    /**
-     * Remove a callback object to tracker.
-     * @return Removed object instance on success, null on failure.
-     */
-    public @Nullable T remove(int callbackIdentifier) {
-        ExternalCallbackHolder<T> externalCallback = mCallbacks.remove(callbackIdentifier);
-        if (externalCallback == null) {
-            Log.w(TAG, "Unknown external callback " + callbackIdentifier);
-            return null;
-        }
-        externalCallback.reset();
-        return externalCallback.getCallback();
-    }
-
-    /**
-     * Retrieve all the callback objects in the tracker.
-     */
-    public List<T> getCallbacks() {
-        List<T> callbacks = new ArrayList<>();
-        for (ExternalCallbackHolder<T> externalCallback : mCallbacks.values()) {
-            callbacks.add(externalCallback.getCallback());
-        }
-        return callbacks;
-    }
-
-    /**
-     * Retrieve the number of callback objects in the tracker.
-     */
-    public int getNumCallbacks() {
-        return mCallbacks.size();
-    }
-
-    /**
-     * Remove all callbacks registered.
-     */
-    public void clear() {
-        for (ExternalCallbackHolder<T> externalCallback : mCallbacks.values()) {
-            externalCallback.reset();
-        }
-        mCallbacks.clear();
-    }
-}
diff --git a/service/java/com/android/server/wifi/util/InformationElementUtil.java b/service/java/com/android/server/wifi/util/InformationElementUtil.java
index a94c15f..92e5d35 100644
--- a/service/java/com/android/server/wifi/util/InformationElementUtil.java
+++ b/service/java/com/android/server/wifi/util/InformationElementUtil.java
@@ -280,7 +280,7 @@
             if (mCenterFreqIndex1 == 0 || mChannelMode == 0) {
                 return 0;
             } else {
-                return ScanResult.convertChannelToFrequencyMhz(mCenterFreqIndex1,
+                return ScanResult.convertChannelToFrequencyMhzIfSupported(mCenterFreqIndex1,
                         WifiScanner.WIFI_BAND_5_GHZ);
             }
         }
@@ -295,7 +295,7 @@
             if (mCenterFreqIndex2 == 0 || mChannelMode == 0) {
                 return 0;
             } else {
-                return ScanResult.convertChannelToFrequencyMhz(mCenterFreqIndex2,
+                return ScanResult.convertChannelToFrequencyMhzIfSupported(mCenterFreqIndex2,
                         WifiScanner.WIFI_BAND_5_GHZ);
             }
         }
@@ -393,7 +393,7 @@
          * Only applicable for 6GHz channels
          */
         public int getPrimaryFreq() {
-            return ScanResult.convertChannelToFrequencyMhz(mPrimaryChannel,
+            return ScanResult.convertChannelToFrequencyMhzIfSupported(mPrimaryChannel,
                         WifiScanner.WIFI_BAND_6_GHZ);
         }
 
@@ -406,7 +406,7 @@
                 if (mCenterFreqSeg0 == 0) {
                     return 0;
                 } else {
-                    return ScanResult.convertChannelToFrequencyMhz(mCenterFreqSeg0,
+                    return ScanResult.convertChannelToFrequencyMhzIfSupported(mCenterFreqSeg0,
                             WifiScanner.WIFI_BAND_6_GHZ);
                 }
             } else {
@@ -423,7 +423,7 @@
                 if (mCenterFreqSeg1 == 0) {
                     return 0;
                 } else {
-                    return ScanResult.convertChannelToFrequencyMhz(mCenterFreqSeg1,
+                    return ScanResult.convertChannelToFrequencyMhzIfSupported(mCenterFreqSeg1,
                             WifiScanner.WIFI_BAND_6_GHZ);
                 }
             } else {
@@ -930,15 +930,26 @@
         private static final int RSN_CIPHER_CCMP = 0x04ac0f00;
         private static final int RSN_CIPHER_NO_GROUP_ADDRESSED = 0x07ac0f00;
         private static final int RSN_CIPHER_GCMP_256 = 0x09ac0f00;
+        private static final int RSN_CIPHER_GCMP_128 = 0x08ac0f00;
+        private static final int RSN_CIPHER_BIP_GMAC_128 = 0x0bac0f00;
+        private static final int RSN_CIPHER_BIP_GMAC_256 = 0x0cac0f00;
+        private static final int RSN_CIPHER_BIP_CMAC_256 = 0x0dac0f00;
+
+        // RSN capability bit definition
+        private static final int RSN_CAP_MANAGEMENT_FRAME_PROTECTION_REQUIRED = 1 << 6;
+        private static final int RSN_CAP_MANAGEMENT_FRAME_PROTECTION_CAPABLE = 1 << 7;
 
         public List<Integer> protocol;
         public List<List<Integer>> keyManagement;
         public List<List<Integer>> pairwiseCipher;
         public List<Integer> groupCipher;
+        public List<Integer> groupManagementCipher;
         public boolean isESS;
         public boolean isIBSS;
         public boolean isPrivacy;
         public boolean isWPS;
+        public boolean isManagementFrameProtectionRequired;
+        public boolean isManagementFrameProtectionCapable;
 
         public Capabilities() {
         }
@@ -1029,7 +1040,7 @@
                             rsnKeyManagement.add(ScanResult.KEY_MGMT_FILS_SHA384);
                             break;
                         default:
-                            // do nothing
+                            rsnKeyManagement.add(ScanResult.KEY_MGMT_UNKNOWN);
                             break;
                     }
                 }
@@ -1038,6 +1049,28 @@
                     rsnKeyManagement.add(ScanResult.KEY_MGMT_EAP);
                 }
                 keyManagement.add(rsnKeyManagement);
+
+                // RSN capabilities (optional),
+                // see section 9.4.2.25 - RSNE - In IEEE Std 802.11-2016
+                if (buf.remaining() < 2) return;
+                int rsnCaps = buf.getShort();
+
+                if (buf.remaining() < 2) return;
+                // PMKID, it's not used, drop it if exists (optional).
+                int rsnPmkIdCount = buf.getShort();
+                for (int i = 0; i < rsnPmkIdCount; i++) {
+                    // Each PMKID element length in the PMKID List is 16 bytes
+                    byte[] tmp = new byte[16];
+                    buf.get(tmp);
+                }
+
+                // Group management cipher suite (optional).
+                if (buf.remaining() < 4) return;
+                groupManagementCipher.add(parseRsnCipher(buf.getInt()));
+                isManagementFrameProtectionRequired = !groupManagementCipher.isEmpty()
+                        && 0 != (RSN_CAP_MANAGEMENT_FRAME_PROTECTION_REQUIRED & rsnCaps);
+                isManagementFrameProtectionCapable = !groupManagementCipher.isEmpty()
+                        && 0 != (RSN_CAP_MANAGEMENT_FRAME_PROTECTION_CAPABLE & rsnCaps);
             } catch (BufferUnderflowException e) {
                 Log.e("IE_Capabilities", "Couldn't parse RSNE, buffer underflow");
             }
@@ -1070,6 +1103,14 @@
                     return ScanResult.CIPHER_GCMP_256;
                 case RSN_CIPHER_NO_GROUP_ADDRESSED:
                     return ScanResult.CIPHER_NO_GROUP_ADDRESSED;
+                case RSN_CIPHER_GCMP_128:
+                    return ScanResult.CIPHER_GCMP_128;
+                case RSN_CIPHER_BIP_GMAC_128:
+                    return ScanResult.CIPHER_BIP_GMAC_128;
+                case RSN_CIPHER_BIP_GMAC_256:
+                    return ScanResult.CIPHER_BIP_GMAC_256;
+                case RSN_CIPHER_BIP_CMAC_256:
+                    return ScanResult.CIPHER_BIP_CMAC_256;
                 default:
                     Log.w("IE_Capabilities", "Unknown RSN cipher suite: "
                             + Integer.toHexString(cipher));
@@ -1159,7 +1200,7 @@
                             wpaKeyManagement.add(ScanResult.KEY_MGMT_PSK);
                             break;
                         default:
-                            // do nothing
+                            wpaKeyManagement.add(ScanResult.KEY_MGMT_UNKNOWN);
                             break;
                     }
                 }
@@ -1180,20 +1221,35 @@
          * @param ies            -- Information Element array
          * @param beaconCap      -- 16-bit Beacon Capability Information field
          * @param isOweSupported -- Boolean flag to indicate if OWE is supported by the device
+         * @param freq           -- Frequency on which frame/beacon was transmitted.
+         *                          Some parsing may be affected such as DMG parameters in
+         *                          DMG (60GHz) beacon.
          */
 
-        public void from(InformationElement[] ies, int beaconCap, boolean isOweSupported) {
+        public void from(InformationElement[] ies, int beaconCap, boolean isOweSupported,
+                int freq) {
             protocol = new ArrayList<>();
             keyManagement = new ArrayList<>();
             groupCipher = new ArrayList<>();
             pairwiseCipher = new ArrayList<>();
+            groupManagementCipher = new ArrayList<>();
 
             if (ies == null) {
                 return;
             }
-            isESS = (beaconCap & NativeScanResult.BSS_CAPABILITY_ESS) != 0;
-            isIBSS = (beaconCap & NativeScanResult.BSS_CAPABILITY_IBSS) != 0;
             isPrivacy = (beaconCap & NativeScanResult.BSS_CAPABILITY_PRIVACY) != 0;
+            if (ScanResult.is60GHz(freq)) {
+                /* In DMG, bits 0 and 1 are parsed together, where ESS=0x3 and IBSS=0x1 */
+                if ((beaconCap & NativeScanResult.BSS_CAPABILITY_DMG_ESS)
+                        == NativeScanResult.BSS_CAPABILITY_DMG_ESS) {
+                    isESS = true;
+                } else if ((beaconCap & NativeScanResult.BSS_CAPABILITY_DMG_IBSS) != 0) {
+                    isIBSS = true;
+                }
+            } else {
+                isESS = (beaconCap & NativeScanResult.BSS_CAPABILITY_ESS) != 0;
+                isIBSS = (beaconCap & NativeScanResult.BSS_CAPABILITY_IBSS) != 0;
+            }
             for (InformationElement ie : ies) {
                 WifiNl80211Manager.OemSecurityType oemSecurityType =
                         WifiNl80211Manager.parseOemSecurityTypeElement(
@@ -1281,13 +1337,13 @@
                 case ScanResult.KEY_MGMT_PSK:
                     return "PSK";
                 case ScanResult.KEY_MGMT_EAP:
-                    return "EAP";
+                    return "EAP/SHA1";
                 case ScanResult.KEY_MGMT_FT_EAP:
                     return "FT/EAP";
                 case ScanResult.KEY_MGMT_FT_PSK:
                     return "FT/PSK";
                 case ScanResult.KEY_MGMT_EAP_SHA256:
-                    return "EAP-SHA256";
+                    return "EAP/SHA256";
                 case ScanResult.KEY_MGMT_PSK_SHA256:
                     return "PSK-SHA256";
                 case ScanResult.KEY_MGMT_OWE:
@@ -1362,6 +1418,14 @@
             if (isWPS) {
                 capabilities.append("[WPS]");
             }
+            if (!groupManagementCipher.isEmpty()) {
+                if (isManagementFrameProtectionRequired) {
+                    capabilities.append("[MFPR]");
+                }
+                if (isManagementFrameProtectionCapable) {
+                    capabilities.append("[MFPC]");
+                }
+            }
 
             return capabilities.toString();
         }
diff --git a/service/java/com/android/server/wifi/util/LastCallerInfoManager.java b/service/java/com/android/server/wifi/util/LastCallerInfoManager.java
new file mode 100644
index 0000000..0074a0c
--- /dev/null
+++ b/service/java/com/android/server/wifi/util/LastCallerInfoManager.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.util;
+
+import android.annotation.IntDef;
+import android.util.SparseArray;
+
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Manage multiple last caller info
+ */
+public class LastCallerInfoManager {
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {
+            SCANNING_ENABLED,
+            WIFI_ENABLED,
+            SOFT_AP,
+            TETHERED_HOTSPOT,
+            AUTOJOIN_GLOBAL})
+    public @interface ApiType {}
+    public static final int SCANNING_ENABLED = 1;
+    public static final int WIFI_ENABLED = 2;
+    public static final int SOFT_AP = 3;
+    public static final int TETHERED_HOTSPOT = 4;
+    public static final int AUTOJOIN_GLOBAL = 5;
+
+    private final SparseArray<LastCallerInfo> mLastCallerInfoMap = new SparseArray<>();
+
+    /**
+     * Store the last caller information for the API
+     */
+    public void put(@ApiType int apiName, int tid, int uid, int pid, String packageName,
+            boolean toggleState) {
+        synchronized (mLastCallerInfoMap) {
+            LastCallerInfo callerInfo = new LastCallerInfo(tid, uid, pid, packageName, toggleState);
+            mLastCallerInfoMap.put(apiName, callerInfo);
+        }
+    }
+
+    /**
+     * Convert int constant into API String name
+     */
+    private String convertApiName(@ApiType int key) {
+        switch (key) {
+            case SCANNING_ENABLED:
+                return "ScanningEnabled";
+            case WIFI_ENABLED:
+                return "WifiEnabled";
+            case SOFT_AP:
+                return "SoftAp";
+            case TETHERED_HOTSPOT:
+                return "TetheredHotspot";
+            case AUTOJOIN_GLOBAL:
+                return "AutojoinGlobal";
+            default:
+                return "Unknown";
+        }
+    }
+
+    /**
+     * Print the last caller info for the APIs tracked
+     */
+    public void dump(PrintWriter pw) {
+        pw.println("Dump of LastCallerInfoManager");
+        for (int i = 0; i < mLastCallerInfoMap.size(); i++) {
+            String apiName = convertApiName(mLastCallerInfoMap.keyAt(i));
+            String callerInfo = mLastCallerInfoMap.valueAt(i).toString();
+            pw.println(apiName + ": " + callerInfo);
+        }
+    }
+
+    /**
+     * Last caller info
+     */
+    public static class LastCallerInfo {
+        private int mTid;
+        private int mUid;
+        private int mPid;
+        private String mPackageName;
+        private boolean mToggleState;
+
+        public LastCallerInfo(int tid, int uid, int pid, String packageName, boolean toggleState) {
+            mTid = tid;
+            mUid = uid;
+            mPid = pid;
+            mPackageName = packageName;
+            mToggleState = toggleState;
+        }
+
+        /**
+         * Convert the last caller info into String format
+         */
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("tid=").append(mTid).append(" uid=").append(mUid)
+                    .append(" pid=").append(mPid).append(" packageName=").append(mPackageName)
+                    .append(" toggleState=").append(mToggleState);
+            return sb.toString();
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/util/Matrix.java b/service/java/com/android/server/wifi/util/Matrix.java
index afd9de6..99123a4 100644
--- a/service/java/com/android/server/wifi/util/Matrix.java
+++ b/service/java/com/android/server/wifi/util/Matrix.java
@@ -17,7 +17,7 @@
 package com.android.server.wifi.util;
 
 /**
- * Utility for doing basic matix calculations
+ * Utility for doing basic matrix calculations
  */
 public class Matrix {
     public final int n;
@@ -202,7 +202,7 @@
      */
     public Matrix dot(Matrix that, Matrix result) {
         if (!(this.n == result.n && this.m == that.n && that.m == result.m)) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("shape error" + this + that + result);
         }
         for (int i = 0; i < n; i++) {
             for (int j = 0; j < that.m; j++) {
diff --git a/service/java/com/android/server/wifi/util/MetricsUtils.java b/service/java/com/android/server/wifi/util/MetricsUtils.java
index 4c87aa2..da1a986 100644
--- a/service/java/com/android/server/wifi/util/MetricsUtils.java
+++ b/service/java/com/android/server/wifi/util/MetricsUtils.java
@@ -19,7 +19,7 @@
 import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
 import android.util.SparseIntArray;
 
-import com.android.server.wifi.BssidBlocklistMonitor;
+import com.android.server.wifi.WifiBlocklistMonitor;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.NetworkDisableReason;
 
 /**
@@ -220,34 +220,34 @@
     }
 
     /**
-     * Converts BssidBlocklistMonitor.FailureReason to
+     * Converts WifiBlocklistMonitor.FailureReason to
      * WifiMetricsProto.NetworkDisableReason.DisableReason
      */
     public static int convertBssidBlocklistReasonToWifiProtoEnum(int reason) {
         switch (reason) {
-            case BssidBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA:
+            case WifiBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA:
                 return NetworkDisableReason.REASON_AP_UNABLE_TO_HANDLE_NEW_STA;
-            case BssidBlocklistMonitor.REASON_NETWORK_VALIDATION_FAILURE:
+            case WifiBlocklistMonitor.REASON_NETWORK_VALIDATION_FAILURE:
                 return NetworkDisableReason.REASON_NETWORK_VALIDATION_FAILURE;
-            case BssidBlocklistMonitor.REASON_WRONG_PASSWORD:
+            case WifiBlocklistMonitor.REASON_WRONG_PASSWORD:
                 return NetworkDisableReason.REASON_WRONG_PASSWORD;
-            case BssidBlocklistMonitor.REASON_EAP_FAILURE:
+            case WifiBlocklistMonitor.REASON_EAP_FAILURE:
                 return NetworkDisableReason.REASON_EAP_FAILURE;
-            case BssidBlocklistMonitor.REASON_ASSOCIATION_REJECTION:
+            case WifiBlocklistMonitor.REASON_ASSOCIATION_REJECTION:
                 return NetworkDisableReason.REASON_ASSOCIATION_REJECTION;
-            case BssidBlocklistMonitor.REASON_ASSOCIATION_TIMEOUT:
+            case WifiBlocklistMonitor.REASON_ASSOCIATION_TIMEOUT:
                 return NetworkDisableReason.REASON_ASSOCIATION_TIMEOUT;
-            case BssidBlocklistMonitor.REASON_AUTHENTICATION_FAILURE:
+            case WifiBlocklistMonitor.REASON_AUTHENTICATION_FAILURE:
                 return NetworkDisableReason.REASON_AUTHENTICATION_FAILURE;
-            case BssidBlocklistMonitor.REASON_DHCP_FAILURE:
+            case WifiBlocklistMonitor.REASON_DHCP_FAILURE:
                 return NetworkDisableReason.REASON_DHCP_FAILURE;
-            case BssidBlocklistMonitor.REASON_ABNORMAL_DISCONNECT:
+            case WifiBlocklistMonitor.REASON_ABNORMAL_DISCONNECT:
                 return NetworkDisableReason.REASON_ABNORMAL_DISCONNECT;
-            case BssidBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_MBO_OCE:
+            case WifiBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_MBO_OCE:
                 return NetworkDisableReason.REASON_FRAMEWORK_DISCONNECT_MBO_OCE;
-            case BssidBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT:
+            case WifiBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT:
                 return NetworkDisableReason.REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT;
-            case BssidBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE:
+            case WifiBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE:
                 return NetworkDisableReason.REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE;
             default:
                 return NetworkDisableReason.REASON_UNKNOWN;
diff --git a/service/java/com/android/server/wifi/util/MissingCounterTimerLockList.java b/service/java/com/android/server/wifi/util/MissingCounterTimerLockList.java
index efcbc59..5417973 100644
--- a/service/java/com/android/server/wifi/util/MissingCounterTimerLockList.java
+++ b/service/java/com/android/server/wifi/util/MissingCounterTimerLockList.java
@@ -70,12 +70,13 @@
      * Add a object to lock with timer duration
      * @param entry object to lock.
      * @param duration duration of the timer.
+     * @param maxTimeoutDuration always remove this entry after this duration.
      */
-    public void add(@NonNull E entry, long duration) {
+    public void add(@NonNull E entry, long duration, long maxTimeoutDuration) {
         if (entry == null) {
             return;
         }
-        mEntries.put(entry, new LockListEntry(duration));
+        mEntries.put(entry, new LockListEntry(duration, maxTimeoutDuration));
     }
 
     /**
@@ -116,13 +117,17 @@
 
     class LockListEntry {
         private final long mExpiryMs;
+        private long mTimeFirstAddedMs;
+        private long mMaxDisableDurationMs;
         private long mStartTimeStamp;
         private int mCount;
 
-        LockListEntry(long expiryMs) {
+        LockListEntry(long expiryMs, long maxDisableDurationMs) {
             mCount = mConsecutiveMissingCountToTriggerTimer;
             mExpiryMs = expiryMs;
             mStartTimeStamp = mClock.getWallClockMillis();
+            mTimeFirstAddedMs = mStartTimeStamp;
+            mMaxDisableDurationMs = maxDisableDurationMs;
         }
 
         void onPresent() {
@@ -147,7 +152,12 @@
         }
 
         boolean isExpired() {
-            return mCount == 0 && mStartTimeStamp + mExpiryMs < mClock.getWallClockMillis();
+            long curTimeMs = mClock.getWallClockMillis();
+            boolean maxDisableDurationPassed = mTimeFirstAddedMs + mMaxDisableDurationMs
+                    < curTimeMs;
+            boolean absentForLongEnough = mCount == 0 && mStartTimeStamp + mExpiryMs
+                    < curTimeMs;
+            return maxDisableDurationPassed || absentForLongEnough;
         }
     }
 }
diff --git a/service/java/com/android/server/wifi/util/ScanResultUtil.java b/service/java/com/android/server/wifi/util/ScanResultUtil.java
index 4085045..b7e26ae 100644
--- a/service/java/com/android/server/wifi/util/ScanResultUtil.java
+++ b/service/java/com/android/server/wifi/util/ScanResultUtil.java
@@ -16,14 +16,19 @@
 
 package com.android.server.wifi.util;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.net.wifi.ScanResult;
+import android.net.wifi.SecurityParams;
 import android.net.wifi.WifiConfiguration;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.wifi.ScanDetail;
 import com.android.server.wifi.hotspot2.NetworkDetail;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.List;
 /**
  * Scan result utility for any {@link ScanResult} related operations.
@@ -33,6 +38,7 @@
  *   > Helper methods to identify the encryption of a ScanResult.
  */
 public class ScanResultUtil {
+    private static final String TAG = "ScanResultUtil";
     private ScanResultUtil() { /* not constructable */ }
 
     /**
@@ -40,8 +46,11 @@
      * result is filled in with the IEs from the beacon.
      */
     public static ScanDetail toScanDetail(ScanResult scanResult) {
+        ScanResult.InformationElement[] ieArray = (null != scanResult.informationElements)
+            ? scanResult.informationElements
+            : new ScanResult.InformationElement[0];
         NetworkDetail networkDetail = new NetworkDetail(scanResult.BSSID,
-                scanResult.informationElements, scanResult.anqpLines, scanResult.frequency);
+                ieArray, scanResult.anqpLines, scanResult.frequency);
         return new ScanDetail(scanResult, networkDetail);
     }
 
@@ -72,18 +81,123 @@
 
     /**
      * Helper method to check if the provided |scanResult| corresponds to a EAP network or not.
-     * This checks if the provided capabilities string contains EAP encryption type or not.
+     * This checks these conditions:
+     * - Enable EAP/SHA1, EAP/SHA256 AKM, FT/EAP, or EAP-FILS.
+     * - Not a WPA3 Enterprise only network.
+     * - Not a WPA3 Enterprise transition network.
      */
     public static boolean isScanResultForEapNetwork(ScanResult scanResult) {
-        return scanResult.capabilities.contains("EAP");
+        return (scanResult.capabilities.contains("EAP/SHA1")
+                        || scanResult.capabilities.contains("EAP/SHA256")
+                        || scanResult.capabilities.contains("FT/EAP")
+                        || scanResult.capabilities.contains("EAP-FILS"))
+                && !isScanResultForWpa3EnterpriseOnlyNetwork(scanResult)
+                && !isScanResultForWpa3EnterpriseTransitionNetwork(scanResult);
+    }
+
+    private static boolean isScanResultForPmfMandatoryNetwork(ScanResult scanResult) {
+        return scanResult.capabilities.contains("[MFPR]");
+    }
+
+    private static boolean isScanResultForPmfCapableNetwork(ScanResult scanResult) {
+        return scanResult.capabilities.contains("[MFPC]");
     }
 
     /**
-     * Helper method to check if the provided |scanResult| corresponds to a EAP network or not.
-     * This checks if the provided capabilities string contains EAP encryption type or not.
+     * Helper method to check if the provided |scanResult| corresponds to a Passpoint R1/R2
+     * network or not.
+     * Passpoint R1/R2 requirements:
+     * - WPA2 Enterprise network.
+     * - interworking bit is set.
+     * - HotSpot Release presents.
+     */
+    public static boolean isScanResultForPasspointR1R2Network(ScanResult scanResult) {
+        if (!isScanResultForEapNetwork(scanResult)) return false;
+
+        ScanDetail detail = toScanDetail(scanResult);
+        if (!detail.getNetworkDetail().isInterworking()) return false;
+        return null != detail.getNetworkDetail().getHSRelease();
+    }
+
+    /**
+     * Helper method to check if the provided |scanResult| corresponds to a Passpoint R3
+     * network or not.
+     * Passpoint R3 requirements:
+     * - Must be WPA2 Enterprise network, WPA3 Enterprise network,
+     *   or WPA3 Enterprise 192-bit mode network.
+     * - interworking bit is set.
+     * - HotSpot Release presents.
+     * - PMF is mandatory.
+     */
+    public static boolean isScanResultForPasspointR3Network(ScanResult scanResult) {
+        if (!isScanResultForEapNetwork(scanResult)
+                && !isScanResultForWpa3EnterpriseOnlyNetwork(scanResult)
+                && !isScanResultForEapSuiteBNetwork(scanResult)) {
+            return false;
+        }
+        if (!isScanResultForPmfMandatoryNetwork(scanResult)) return false;
+
+        ScanDetail detail = toScanDetail(scanResult);
+        if (!detail.getNetworkDetail().isInterworking()) return false;
+        return null != detail.getNetworkDetail().getHSRelease();
+    }
+
+    /**
+     * Helper method to check if the provided |scanResult| corresponds to
+     * a WPA3 Enterprise transition network or not.
+     *
+     * See Section 3.3 WPA3-Enterprise transition mode in WPA3 Specification
+     * - Enable at least EAP/SHA1 and EAP/SHA256 AKM suites.
+     * - Not enable WPA1 version 1, WEP, and TKIP.
+     * - Management Frame Protection Capable is set.
+     * - Management Frame Protection Required is not set.
+     */
+    public static boolean isScanResultForWpa3EnterpriseTransitionNetwork(ScanResult scanResult) {
+        return scanResult.capabilities.contains("EAP/SHA1")
+                && scanResult.capabilities.contains("EAP/SHA256")
+                && scanResult.capabilities.contains("RSN")
+                && !scanResult.capabilities.contains("WEP")
+                && !scanResult.capabilities.contains("TKIP")
+                && !isScanResultForPmfMandatoryNetwork(scanResult)
+                && isScanResultForPmfCapableNetwork(scanResult);
+    }
+
+    /**
+     * Helper method to check if the provided |scanResult| corresponds to
+     * a WPA3 Enterprise only network or not.
+     *
+     * See Section 3.2 WPA3-Enterprise only mode in WPA3 Specification
+     * - Enable at least EAP/SHA256 AKM suite.
+     * - Not enable EAP/SHA1 AKM suite.
+     * - Not enable WPA1 version 1, WEP, and TKIP.
+     * - Management Frame Protection Capable is set.
+     * - Management Frame Protection Required is set.
+     */
+    public static boolean isScanResultForWpa3EnterpriseOnlyNetwork(ScanResult scanResult) {
+        return scanResult.capabilities.contains("EAP/SHA256")
+                && !scanResult.capabilities.contains("EAP/SHA1")
+                && scanResult.capabilities.contains("RSN")
+                && !scanResult.capabilities.contains("WEP")
+                && !scanResult.capabilities.contains("TKIP")
+                && isScanResultForPmfMandatoryNetwork(scanResult)
+                && isScanResultForPmfCapableNetwork(scanResult);
+    }
+
+    /**
+     * Helper method to check if the provided |scanResult| corresponds to a WPA3-Enterprise 192-bit
+     * mode network or not.
+     * This checks if the provided capabilities comply these conditions:
+     * - Enable SUITE-B-192 AKM.
+     * - Not enable EAP/SHA1 AKM suite.
+     * - Not enable WPA1 version 1, WEP, and TKIP.
+     * - Management Frame Protection Required is set.
      */
     public static boolean isScanResultForEapSuiteBNetwork(ScanResult scanResult) {
-        return scanResult.capabilities.contains("SUITE-B-192");
+        return scanResult.capabilities.contains("SUITE_B_192")
+                && scanResult.capabilities.contains("RSN")
+                && !scanResult.capabilities.contains("WEP")
+                && !scanResult.capabilities.contains("TKIP")
+                && isScanResultForPmfMandatoryNetwork(scanResult);
     }
 
     /**
@@ -143,16 +257,27 @@
     }
 
     /**
+     *  Helper method to check if the provided |scanResult| corresponds to an unknown amk network.
+     *  This checks if the provided capabilities string contains ? or not.
+     */
+    public static boolean isScanResultForUnknownAkmNetwork(ScanResult scanResult) {
+        return scanResult.capabilities.contains("?");
+    }
+
+    /**
      * Helper method to check if the provided |scanResult| corresponds to an open network or not.
      * This checks if the provided capabilities string does not contain either of WEP, PSK, SAE
-     * or EAP encryption types or not.
+     * EAP, or unknown encryption types or not.
      */
     public static boolean isScanResultForOpenNetwork(ScanResult scanResult) {
         return (!(isScanResultForWepNetwork(scanResult) || isScanResultForPskNetwork(scanResult)
                 || isScanResultForEapNetwork(scanResult) || isScanResultForSaeNetwork(scanResult)
+                || isScanResultForWpa3EnterpriseTransitionNetwork(scanResult)
+                || isScanResultForWpa3EnterpriseOnlyNetwork(scanResult)
                 || isScanResultForWapiPskNetwork(scanResult)
                 || isScanResultForWapiCertNetwork(scanResult)
-                || isScanResultForEapSuiteBNetwork(scanResult)));
+                || isScanResultForEapSuiteBNetwork(scanResult)
+                || isScanResultForUnknownAkmNetwork(scanResult)));
     }
 
     /**
@@ -168,38 +293,109 @@
      * Creates a network configuration object using the provided |scanResult|.
      * This is used to create ephemeral network configurations.
      */
-    public static WifiConfiguration createNetworkFromScanResult(ScanResult scanResult) {
+    public static @Nullable WifiConfiguration createNetworkFromScanResult(ScanResult scanResult) {
         WifiConfiguration config = new WifiConfiguration();
         config.SSID = createQuotedSSID(scanResult.SSID);
-        setAllowedKeyManagementFromScanResult(scanResult, config);
+        List<SecurityParams> list = generateSecurityParamsListFromScanResult(scanResult);
+        if (list.isEmpty()) {
+            return null;
+        }
+        config.setSecurityParams(list);
         return config;
     }
 
     /**
-     * Sets the {@link WifiConfiguration#allowedKeyManagement} field on the given
-     * {@link WifiConfiguration} based on its corresponding {@link ScanResult}.
+     * Generate security params from the scan result.
+     * @param scanResult the scan result to be checked.
+     * @return a list of security params. If no known security params, return an empty list.
      */
-    public static void setAllowedKeyManagementFromScanResult(ScanResult scanResult,
-            WifiConfiguration config) {
-        if (isScanResultForSaeNetwork(scanResult)) {
-            config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
-        } else if (isScanResultForWapiPskNetwork(scanResult)) {
-            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WAPI_PSK);
-        } else if (isScanResultForWapiCertNetwork(scanResult)) {
-            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WAPI_CERT);
-        } else if (isScanResultForPskNetwork(scanResult)) {
-            config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
-        } else if (isScanResultForEapSuiteBNetwork(scanResult)) {
-            config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B);
-        } else if (isScanResultForEapNetwork(scanResult)) {
-            config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
-        } else if (isScanResultForWepNetwork(scanResult)) {
-            config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_WEP);
-        } else if (isScanResultForOweNetwork(scanResult)) {
-            config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OWE);
-        } else {
-            config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN);
+    public static @NonNull List<SecurityParams> generateSecurityParamsListFromScanResult(
+            ScanResult scanResult) {
+        List<SecurityParams> list = new ArrayList<>();
+
+        // Open network & its upgradable types
+        if (ScanResultUtil.isScanResultForOweTransitionNetwork(scanResult)) {
+            list.add(SecurityParams.createSecurityParamsBySecurityType(
+                    WifiConfiguration.SECURITY_TYPE_OPEN));
+            list.add(SecurityParams.createSecurityParamsBySecurityType(
+                    WifiConfiguration.SECURITY_TYPE_OWE));
+            return list;
+        } else if (ScanResultUtil.isScanResultForOweNetwork(scanResult)) {
+            list.add(SecurityParams.createSecurityParamsBySecurityType(
+                    WifiConfiguration.SECURITY_TYPE_OWE));
+            return list;
+        } else if (ScanResultUtil.isScanResultForOpenNetwork(scanResult)) {
+            list.add(SecurityParams.createSecurityParamsBySecurityType(
+                    WifiConfiguration.SECURITY_TYPE_OPEN));
+            return list;
         }
+
+        // WEP network which has no upgradable type
+        if (ScanResultUtil.isScanResultForWepNetwork(scanResult)) {
+            list.add(SecurityParams.createSecurityParamsBySecurityType(
+                    WifiConfiguration.SECURITY_TYPE_WEP));
+            return list;
+        }
+
+        // WAPI PSK network which has no upgradable type
+        if (ScanResultUtil.isScanResultForWapiPskNetwork(scanResult)) {
+            list.add(SecurityParams.createSecurityParamsBySecurityType(
+                    WifiConfiguration.SECURITY_TYPE_WAPI_PSK));
+            return list;
+        }
+
+        // WAPI CERT network which has no upgradable type
+        if (ScanResultUtil.isScanResultForWapiCertNetwork(scanResult)) {
+            list.add(SecurityParams.createSecurityParamsBySecurityType(
+                    WifiConfiguration.SECURITY_TYPE_WAPI_CERT));
+            return list;
+        }
+
+        // WPA2 personal network & its upgradable types
+        if (ScanResultUtil.isScanResultForPskNetwork(scanResult)
+                && ScanResultUtil.isScanResultForSaeNetwork(scanResult)) {
+            list.add(SecurityParams.createSecurityParamsBySecurityType(
+                    WifiConfiguration.SECURITY_TYPE_PSK));
+            list.add(SecurityParams.createSecurityParamsBySecurityType(
+                    WifiConfiguration.SECURITY_TYPE_SAE));
+            return list;
+        } else if (ScanResultUtil.isScanResultForPskNetwork(scanResult)) {
+            list.add(SecurityParams.createSecurityParamsBySecurityType(
+                    WifiConfiguration.SECURITY_TYPE_PSK));
+            return list;
+        } else if (ScanResultUtil.isScanResultForSaeNetwork(scanResult)) {
+            list.add(SecurityParams.createSecurityParamsBySecurityType(
+                    WifiConfiguration.SECURITY_TYPE_SAE));
+            return list;
+        }
+
+        // WPA3 Enterprise 192-bit mode, WPA2/WPA3 enterprise network & its upgradable types
+        if (ScanResultUtil.isScanResultForEapSuiteBNetwork(scanResult)) {
+            list.add(SecurityParams.createSecurityParamsBySecurityType(
+                    WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT));
+        } else if (ScanResultUtil.isScanResultForWpa3EnterpriseTransitionNetwork(scanResult)) {
+            list.add(SecurityParams.createSecurityParamsBySecurityType(
+                    WifiConfiguration.SECURITY_TYPE_EAP));
+            list.add(SecurityParams.createSecurityParamsBySecurityType(
+                    WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE));
+        } else if (ScanResultUtil.isScanResultForWpa3EnterpriseOnlyNetwork(scanResult)) {
+            list.add(SecurityParams.createSecurityParamsBySecurityType(
+                    WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE));
+        } else if (ScanResultUtil.isScanResultForEapNetwork(scanResult)) {
+            list.add(SecurityParams.createSecurityParamsBySecurityType(
+                    WifiConfiguration.SECURITY_TYPE_EAP));
+        }
+        // An Enterprise network might be a Passpoint network as well.
+        // R3 network might be also a valid R1/R2 network.
+        if (isScanResultForPasspointR1R2Network(scanResult)) {
+            list.add(SecurityParams.createSecurityParamsBySecurityType(
+                    WifiConfiguration.SECURITY_TYPE_PASSPOINT_R1_R2));
+        }
+        if (isScanResultForPasspointR3Network(scanResult)) {
+            list.add(SecurityParams.createSecurityParamsBySecurityType(
+                    WifiConfiguration.SECURITY_TYPE_PASSPOINT_R3));
+        }
+        return list;
     }
 
     /**
@@ -253,10 +449,12 @@
      */
     public static boolean validateScanResultList(List<ScanResult> scanResults) {
         if (scanResults == null || scanResults.isEmpty()) {
+            Log.w(TAG, "Empty or null ScanResult list");
             return false;
         }
         for (ScanResult scanResult : scanResults) {
             if (!validate(scanResult)) {
+                Log.w(TAG, "Invalid ScanResult: " + scanResult);
                 return false;
             }
         }
diff --git a/service/java/com/android/server/wifi/util/StateMachineObituary.java b/service/java/com/android/server/wifi/util/StateMachineObituary.java
new file mode 100644
index 0000000..ee41448
--- /dev/null
+++ b/service/java/com/android/server/wifi/util/StateMachineObituary.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.util;
+
+import com.android.internal.util.IState;
+import com.android.internal.util.StateMachine;
+import com.android.internal.util.StateMachine.LogRec;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Enough information about a StateMachine to reconstruct the dump() log. This is necessary
+ * because a StateMachine's logs are lost upon quitting
+ * ({@link StateMachine#quit()} or {@link StateMachine#quitNow()}).
+ *
+ * Note: most of StateMachine's internal variables (including LogRecs) are stored in the
+ * StateMachine's inner class SmHandler. StateMachine clears its reference to SmHandler
+ * after quitting, thereby losing all the LogRecs as well.
+ */
+public class StateMachineObituary {
+    private final String mName;
+    /**
+     * Total records processed, which may be greater than the size of mLogRecs since
+     * messages could be discarded.
+     */
+    private final int mTotalProcessedRecords;
+    private final List<String> mLogRecs = new ArrayList<>();
+    private final String mLastStateName;
+
+    public StateMachineObituary(StateMachine stateMachine) {
+        mName = stateMachine.getName();
+        // total number of LogRecs ever
+        mTotalProcessedRecords = stateMachine.getLogRecCount();
+        // number of records currently readable i.e. not yet discarded
+        int currentReadableRecords = stateMachine.getLogRecSize();
+        // LogRecs are mutable and StateMachine internally reuses LogRec instances instead of
+        // allocating new ones. Thus, convert the LogRecs to Strings.
+        for (int i = 0; i < currentReadableRecords; i++) {
+            LogRec logRec = stateMachine.getLogRec(i);
+            if (logRec != null) { // just in case
+                mLogRecs.add(logRec.toString());
+            }
+        }
+        final IState curState = stateMachine.getCurrentState();
+        mLastStateName = curState == null ? "<QUIT>" : curState.getName();
+    }
+
+    /** Dump the same information as {@link StateMachine#dump} when the StateMachine was active. */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println(mName + ":");
+        pw.println(" total records=" + mTotalProcessedRecords);
+        int i = 0;
+        for (String logRec : mLogRecs) {
+            pw.println(" rec[" + i + "]: " + logRec);
+            pw.flush();
+            i++;
+        }
+        pw.println("curState=" + mLastStateName);
+    }
+}
diff --git a/service/java/com/android/server/wifi/util/StringUtil.java b/service/java/com/android/server/wifi/util/StringUtil.java
index 813b85b..4279efe 100644
--- a/service/java/com/android/server/wifi/util/StringUtil.java
+++ b/service/java/com/android/server/wifi/util/StringUtil.java
@@ -16,6 +16,9 @@
 
 package com.android.server.wifi.util;
 
+import java.util.Random;
+import java.util.stream.Collectors;
+
 /** Basic string utilities */
 public class StringUtil {
     static final byte ASCII_PRINTABLE_MIN = ' ';
@@ -45,4 +48,13 @@
 
         return true;
     }
+
+    /** Returns a random number string. */
+    public static String generateRandomNumberString(int length) {
+        final String pool = "0123456789";
+        return new Random(System.currentTimeMillis())
+                .ints(length, 0, pool.length())
+                .mapToObj(i -> Character.toString(pool.charAt(i)))
+                .collect(Collectors.joining());
+    }
 }
diff --git a/service/java/com/android/server/wifi/util/WifiConfigStoreEncryptionUtil.java b/service/java/com/android/server/wifi/util/WifiConfigStoreEncryptionUtil.java
index ded3450..a1501e6 100644
--- a/service/java/com/android/server/wifi/util/WifiConfigStoreEncryptionUtil.java
+++ b/service/java/com/android/server/wifi/util/WifiConfigStoreEncryptionUtil.java
@@ -103,6 +103,8 @@
             reportException(e, "encrypt had a padding problem");
         } catch (IllegalBlockSizeException e) {
             reportException(e, "encrypt had an illegal block size");
+        } catch (Exception e) {
+            reportException(e, "exception caught");
         }
         return encryptedData;
     }
@@ -135,6 +137,8 @@
             reportException(e, "decrypt had an invalid key");
         } catch (InvalidAlgorithmParameterException e) {
             reportException(e, "decrypt had an invalid algorithm parameter");
+        } catch (Exception e) {
+            reportException(e, "exception caught");
         }
         return decryptedData;
     }
@@ -180,6 +184,8 @@
             reportException(e, "getOrCreateSecretKey had an unrecoverable entry exception.");
         } catch (ProviderException e) {
             reportException(e, "getOrCreateSecretKey had a provider exception.");
+        } catch (Exception e) {
+            reportException(e, "exception caught");
         }
         return secretKey;
     }
@@ -187,5 +193,4 @@
     private void reportException(Exception exception, String error) {
         Log.wtf(TAG, "An irrecoverable key store error was encountered: " + error, exception);
     }
-
 }
diff --git a/service/java/com/android/server/wifi/util/WifiPermissionsUtil.java b/service/java/com/android/server/wifi/util/WifiPermissionsUtil.java
index ebe7ea4..5845f74 100644
--- a/service/java/com/android/server/wifi/util/WifiPermissionsUtil.java
+++ b/service/java/com/android/server/wifi/util/WifiPermissionsUtil.java
@@ -22,6 +22,7 @@
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.location.LocationManager;
 import android.net.NetworkStack;
@@ -32,18 +33,25 @@
 import android.provider.Settings;
 import android.util.EventLog;
 import android.util.Log;
+import android.util.Pair;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.FrameworkFacade;
 import com.android.server.wifi.WifiInjector;
 import com.android.server.wifi.WifiLog;
 
+import java.util.Arrays;
+
 /**
  * A wifi permissions utility assessing permissions
  * for getting scan results by a package.
  */
 public class WifiPermissionsUtil {
     private static final String TAG = "WifiPermissionsUtil";
+
+    private static final int APP_INFO_FLAGS_SYSTEM_APP =
+            ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
     private final WifiPermissionsWrapper mWifiPermissionsWrapper;
     private final Context mContext;
     private final FrameworkFacade mFrameworkFacade;
@@ -53,6 +61,7 @@
     @GuardedBy("mLock")
     private LocationManager mLocationManager;
     private WifiLog mLog;
+    private boolean mVerboseLoggingEnabled;
 
     public WifiPermissionsUtil(WifiPermissionsWrapper wifiPermissionsWrapper,
             Context context, UserManager userManager, WifiInjector wifiInjector) {
@@ -96,20 +105,27 @@
     public boolean isTargetSdkLessThan(String packageName, int versionCode, int callingUid) {
         long ident = Binder.clearCallingIdentity();
         try {
-            if (mContext.getPackageManager().getApplicationInfoAsUser(
-                    packageName, 0,
-                    UserHandle.getUserHandleForUid(callingUid)).targetSdkVersion
-                    < versionCode) {
-                return true;
+            final int targetSdkVersion;
+            if (SdkLevel.isAtLeastS()) {
+                // >= S, use the lightweight API to just get the target SDK version.
+                Context userContext = createPackageContextAsUser(callingUid);
+                if (userContext == null) return false;
+                targetSdkVersion = userContext.getPackageManager().getTargetSdkVersion(packageName);
+            } else {
+                // < S, use the heavyweight API to get all package info.
+                targetSdkVersion = mContext.getPackageManager().getApplicationInfoAsUser(
+                        packageName, 0,
+                        UserHandle.getUserHandleForUid(callingUid)).targetSdkVersion;
             }
+            return targetSdkVersion < versionCode;
         } catch (PackageManager.NameNotFoundException e) {
             // In case of exception, assume unknown app (more strict checking)
             // Note: This case will never happen since checkPackage is
             // called to verify validity before checking App's version.
+            return false;
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
-        return false;
     }
 
     /**
@@ -136,6 +152,10 @@
         }
         if (mWifiPermissionsWrapper.getUidPermission(permissionType, uid)
                 == PackageManager.PERMISSION_DENIED) {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "checkCallersLocationPermission(" + pkgName + "): uid " + uid
+                        + " doesn't have permission " + permissionType);
+            }
             return false;
         }
 
@@ -144,11 +164,27 @@
         boolean isFineLocationAllowed = noteAppOpAllowed(
                 AppOpsManager.OPSTR_FINE_LOCATION, pkgName, featureId, uid, message);
         if (isFineLocationAllowed) {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "checkCallersLocationPermission(" + pkgName + "): ok because uid " + uid
+                        + " has app-op " + AppOpsManager.OPSTR_FINE_LOCATION);
+            }
             return true;
         }
         if (coarseForTargetSdkLessThanQ && isTargetSdkLessThanQ) {
-            return noteAppOpAllowed(AppOpsManager.OPSTR_COARSE_LOCATION, pkgName, featureId, uid,
-                    message);
+            boolean allowed = noteAppOpAllowed(AppOpsManager.OPSTR_COARSE_LOCATION, pkgName,
+                    featureId, uid, message);
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "checkCallersLocationPermission(" + pkgName + "): returning " + allowed
+                        + " because uid " + uid + (allowed ? "has" : "doesn't have") + " app-op "
+                        + AppOpsManager.OPSTR_COARSE_LOCATION);
+            }
+            return allowed;
+        }
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "checkCallersLocationPermission(" + pkgName + "): returning false for " + uid
+                    + ": coarseForTargetSdkLessThanQ=" + coarseForTargetSdkLessThanQ
+                    + ", isTargetSdkLessThanQ=" + isTargetSdkLessThanQ);
+
         }
         return false;
     }
@@ -200,7 +236,7 @@
      *
      * @param uid The uid of the package
      */
-    private boolean checkCallersHardwareLocationPermission(int uid) {
+    public boolean checkCallersHardwareLocationPermission(int uid) {
         return mWifiPermissionsWrapper.getUidPermission(Manifest.permission.LOCATION_HARDWARE, uid)
                 == PackageManager.PERMISSION_GRANTED;
     }
@@ -230,6 +266,10 @@
 
         // Location mode must be enabled
         if (!isLocationModeEnabled()) {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "enforceCanAccessScanResults(pkg=" + pkgName + ", uid=" + uid + "): "
+                        + "location is disabled");
+            }
             // Location mode is disabled, scan results cannot be returned
             throw new SecurityException("Location mode is disabled for the device");
         }
@@ -244,15 +284,30 @@
         // If neither caller or app has location access, there is no need to check
         // any other permissions. Deny access to scan results.
         if (!canCallingUidAccessLocation && !canAppPackageUseLocation) {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "enforceCanAccessScanResults(pkg=" + pkgName + ", uid=" + uid + "): "
+                        + "canCallingUidAccessLocation=" + canCallingUidAccessLocation
+                        + ", canAppPackageUseLocation=" + canAppPackageUseLocation);
+            }
             throw new SecurityException("UID " + uid + " has no location permission");
         }
         // Check if Wifi Scan request is an operation allowed for this App.
         if (!isScanAllowedbyApps(pkgName, featureId, uid)) {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "enforceCanAccessScanResults(pkg=" + pkgName + ", uid=" + uid + "): "
+                        + "doesn't have app-op " + AppOpsManager.OPSTR_WIFI_SCAN);
+            }
             throw new SecurityException("UID " + uid + " has no wifi scan permission");
         }
         // If the User or profile is current, permission is granted
         // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission.
-        if (!isCurrentProfile(uid) && !checkInteractAcrossUsersFull(uid)) {
+        boolean isCurrentProfile = isCurrentProfile(uid);
+        if (!isCurrentProfile && !checkInteractAcrossUsersFull(uid)) {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "enforceCanAccessScanResults(pkg=" + pkgName + ", uid=" + uid + "): "
+                        + "isCurrentProfile=" + isCurrentProfile
+                        + ", checkInteractAcrossUsersFull=" + checkInteractAcrossUsersFull(uid));
+            }
             throw new SecurityException("UID " + uid + " profile not permitted");
         }
     }
@@ -338,7 +393,7 @@
     }
 
     /**
-     * API to check to validate if a package name belongs to a UID. Throws SecurityException
+     * API to validate if a package name belongs to a UID. Throws SecurityException
      * if pkgName does not belongs to a UID
      *
      * @param pkgName package name of the application requesting access
@@ -531,7 +586,8 @@
         return devicePolicyManager;
     }
 
-    private DevicePolicyManager retrieveDevicePolicyManagerFromUserContext(int uid) {
+    @Nullable
+    private Context createPackageContextAsUser(int uid) {
         Context userContext = null;
         try {
             userContext = mContext.createPackageContextAsUser(mContext.getPackageName(), 0,
@@ -544,22 +600,20 @@
             Log.e(TAG, "Unable to retrieve user context for " + uid);
             return null;
         }
+        return userContext;
+    }
+
+    private DevicePolicyManager retrieveDevicePolicyManagerFromUserContext(int uid) {
+        Context userContext = createPackageContextAsUser(uid);
+        if (userContext == null) return null;
         return retrieveDevicePolicyManagerFromContext(userContext);
     }
 
-    /**
-     * Returns true if the |callingUid|/\callingPackage| is the device owner.
-     */
-    public boolean isDeviceOwner(int uid, @Nullable String packageName) {
-        // Cannot determine if the app is DO/PO if packageName is null. So, will return false to be
-        // safe.
-        if (packageName == null) {
-            Log.e(TAG, "isDeviceOwner: packageName is null, returning false");
-            return false;
-        }
+    @Nullable
+    private Pair<UserHandle, ComponentName> getDeviceOwner() {
         DevicePolicyManager devicePolicyManager =
                 retrieveDevicePolicyManagerFromContext(mContext);
-        if (devicePolicyManager == null) return false;
+        if (devicePolicyManager == null) return null;
         long ident = Binder.clearCallingIdentity();
         UserHandle deviceOwnerUser = null;
         ComponentName deviceOwnerComponent = null;
@@ -569,10 +623,64 @@
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
+        if (deviceOwnerUser == null || deviceOwnerComponent == null) return null;
+
+        if (deviceOwnerComponent.getPackageName() == null) {
+            // shouldn't happen
+            Log.wtf(TAG, "no package name on device owner component: " + deviceOwnerComponent);
+            return null;
+        }
+        return new Pair<>(deviceOwnerUser, deviceOwnerComponent);
+    }
+
+    /**
+     * Returns {@code true} if the calling {@code uid} and {@code packageName} is the device owner.
+     */
+    public boolean isDeviceOwner(int uid, @Nullable String packageName) {
+        // Cannot determine if the app is DO/PO if packageName is null. So, will return false to be
+        // safe.
+        if (packageName == null) {
+            Log.e(TAG, "isDeviceOwner: packageName is null, returning false");
+            return false;
+        }
+        Pair<UserHandle, ComponentName> deviceOwner = getDeviceOwner();
+        if (mVerboseLoggingEnabled) Log.v(TAG, "deviceOwner:" + deviceOwner);
+
         // no device owner
-        if (deviceOwnerUser == null || deviceOwnerComponent == null) return false;
-        return deviceOwnerUser.equals(UserHandle.getUserHandleForUid(uid))
-                && deviceOwnerComponent.getPackageName().equals(packageName);
+        if (deviceOwner == null) return false;
+
+        return deviceOwner.first.equals(UserHandle.getUserHandleForUid(uid))
+                && deviceOwner.second.getPackageName().equals(packageName);
+    }
+
+    /**
+     * Returns {@code true} if the calling {@code uid} is the device owner.
+     */
+    public boolean isDeviceOwner(int uid) {
+        Pair<UserHandle, ComponentName> deviceOwner = getDeviceOwner();
+
+        // no device owner
+        if (deviceOwner == null) return false;
+
+        // device owner belowngs to wrong user
+        if (!deviceOwner.first.equals(UserHandle.getUserHandleForUid(uid))) return false;
+
+        // finally, check uid
+        String deviceOwnerPackageName = deviceOwner.second.getPackageName();
+        String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "Packages for uid " + uid + ":" + Arrays.toString(packageNames));
+        }
+        if (packageNames == null) {
+            Log.w(TAG, "isDeviceOwner(): could not find packages for packageName="
+                    + deviceOwnerPackageName + " uid=" + uid);
+            return false;
+        }
+        for (String packageName : packageNames) {
+            if (deviceOwnerPackageName.equals(packageName)) return true;
+        }
+
+        return false;
     }
 
     /**
@@ -591,8 +699,25 @@
         return devicePolicyManager.isProfileOwnerApp(packageName);
     }
 
+    /** Helper method to check if the entity initiating the binder call is a system app. */
+    public boolean isSystem(String packageName, int uid) {
+        long ident = Binder.clearCallingIdentity();
+        try {
+            ApplicationInfo info = mContext.getPackageManager().getApplicationInfoAsUser(
+                    packageName, 0, UserHandle.getUserHandleForUid(uid));
+            return (info.flags & APP_INFO_FLAGS_SYSTEM_APP) != 0;
+        } catch (PackageManager.NameNotFoundException e) {
+            // In case of exception, assume unknown app (more strict checking)
+            // Note: This case will never happen since checkPackage is
+            // called to verify validity before checking App's version.
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+        return false;
+    }
+
     /**
-     * Check if the given UID belongs to the current foreground user. This is
+     * Checks if the given UID belongs to the current foreground or device owner user. This is
      * used to prevent apps running in background users from modifying network
      * configurations.
      * <p>
@@ -616,6 +741,13 @@
             EventLog.writeEvent(0x534e4554, "174749461", -1,
                     "Non foreground user trying to modify wifi configuration");
         }
-        return isCurrentProfile;
+        return isCurrentProfile || isDeviceOwner(uid);
+    }
+
+    /**
+     * Sets the verbose logging level.
+     */
+    public void enableVerboseLogging(boolean enabled) {
+        mVerboseLoggingEnabled = enabled;
     }
 }
diff --git a/service/java/com/android/server/wifi/util/WifiPermissionsWrapper.java b/service/java/com/android/server/wifi/util/WifiPermissionsWrapper.java
index 52ea1c0..42669fc 100644
--- a/service/java/com/android/server/wifi/util/WifiPermissionsWrapper.java
+++ b/service/java/com/android/server/wifi/util/WifiPermissionsWrapper.java
@@ -19,6 +19,8 @@
 import android.Manifest;
 import android.app.ActivityManager;
 import android.content.Context;
+import android.content.pm.PackageManager;
+import android.util.Log;
 
 /**
  * A wifi permissions dependency class to wrap around external
@@ -27,6 +29,7 @@
 public class WifiPermissionsWrapper {
     private static final String TAG = "WifiPermissionsWrapper";
     private final Context mContext;
+    private boolean mVerboseLoggingEnabled;
 
     public WifiPermissionsWrapper(Context context) {
         mContext = context;
@@ -44,7 +47,12 @@
      */
     public int getUidPermission(String permissionType, int uid) {
         // We don't care about pid, pass in -1
-        return mContext.checkPermission(permissionType, -1, uid);
+        int granted = mContext.checkPermission(permissionType, -1, uid);
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "getUidPermission(" + permissionType + ", " + uid + "): "
+                    + (granted == PackageManager.PERMISSION_GRANTED));
+        }
+        return granted;
     }
 
     /**
@@ -66,4 +74,11 @@
     public int getLocalMacAddressPermission(int uid) {
         return getUidPermission(Manifest.permission.LOCAL_MAC_ADDRESS, uid);
     }
+
+    /**
+     * Sets the verbose logging level.
+     */
+    public void enableVerboseLogging(boolean enabled) {
+        mVerboseLoggingEnabled = enabled;
+    }
 }
diff --git a/service/java/com/android/server/wifi/util/WorkSourceHelper.java b/service/java/com/android/server/wifi/util/WorkSourceHelper.java
new file mode 100644
index 0000000..d793ef9
--- /dev/null
+++ b/service/java/com/android/server/wifi/util/WorkSourceHelper.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.util;
+
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.WorkSource;
+import android.util.Log;
+
+/**
+ * Class for wrapping a WorkSource object and providing some (wifi specific) utility methods.
+ *
+ * This is primarily used in {@link com.android.server.wifi.HalDeviceManager} class.
+ */
+public class WorkSourceHelper {
+    private static final String TAG = "WorkSourceHelper";
+    private static final int APP_INFO_FLAGS_SYSTEM_APP =
+            ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
+    private final WorkSource mWorkSource;
+    private final WifiPermissionsUtil mWifiPermissionsUtil;
+    private final ActivityManager mActivityManager;
+    private final PackageManager mPackageManager;
+
+    public WorkSourceHelper(
+            @NonNull WorkSource workSource,
+            @NonNull WifiPermissionsUtil wifiPermissionsUtil,
+            @NonNull ActivityManager activityManager,
+            @NonNull PackageManager packageManager) {
+        mWorkSource = workSource;
+        mWifiPermissionsUtil = wifiPermissionsUtil;
+        mActivityManager = activityManager;
+        mPackageManager = packageManager;
+    }
+
+    @Override
+    public String toString() {
+        return mWorkSource.toString();
+    }
+
+    private boolean isPrivileged(int uid) {
+        return mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)
+                || mWifiPermissionsUtil.checkNetworkSetupWizardPermission(uid)
+                || mWifiPermissionsUtil.checkNetworkStackPermission(uid)
+                || mWifiPermissionsUtil.checkMainlineNetworkStackPermission(uid);
+    }
+
+    /**
+     * Returns whether any of the one or more worksource objects contains a privileged app
+     * request.
+     *
+     * Privileged = Request from an app with NETWORK_SETTINGS, NETWORK_SETUP_WIZARD or
+     * NETWORK_STACK permissions.
+     */
+    public boolean hasAnyPrivilegedAppRequest() {
+        for (int i = 0; i < mWorkSource.size(); i++) {
+            if (isPrivileged(mWorkSource.getUid(i))) return true;
+        }
+        return false;
+    }
+
+    private boolean isSystem(String packageName, int uid) {
+        // when checking ActiveModeWarden#INTERNAL_REQUESTOR_WS
+        if (packageName == null) {
+            return false;
+        }
+        try {
+            ApplicationInfo info = mPackageManager.getApplicationInfoAsUser(
+                    packageName, 0, UserHandle.getUserHandleForUid(uid));
+            return (info.flags & APP_INFO_FLAGS_SYSTEM_APP) != 0;
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(TAG, "Failed to retrieve app info for packageName=" + packageName + " uid=" + uid,
+                    e);
+            // In case of exception, assume unknown app (more strict checking)
+            // Note: This case will never happen since checkPackage is
+            // called to verify validity before checking App's version.
+            return false;
+        }
+    }
+
+    /**
+     * Returns whether any of the one or more worksource objects contains a system app
+     * request.
+     */
+    public boolean hasAnySystemAppRequest() {
+        for (int i = 0; i < mWorkSource.size(); i++) {
+            if (isSystem(mWorkSource.getPackageName(i), mWorkSource.getUid(i))) return true;
+        }
+        return false;
+    }
+
+    /**
+     * Check if the request comes from foreground app.
+     */
+    private boolean isForegroundApp(@NonNull String requestorPackageName) {
+        try {
+            return mActivityManager.getPackageImportance(requestorPackageName)
+                    <= IMPORTANCE_FOREGROUND;
+        } catch (SecurityException e) {
+            Log.e(TAG, "Failed to check the app state", e);
+            return false;
+        }
+    }
+
+    /**
+     * Returns whether any of the one or more worksource objects contains a foreground app
+     * request.
+     */
+    public boolean hasAnyForegroundAppRequest() {
+        for (int i = 0; i < mWorkSource.size(); i++) {
+            if (isForegroundApp(mWorkSource.getPackageName(i))) return true;
+        }
+        return false;
+    }
+
+    /**
+     * Check if the request comes from foreground service.
+     */
+    private boolean isForegroundService(@NonNull String requestorPackageName) {
+        try {
+            int importance = mActivityManager.getPackageImportance(requestorPackageName);
+            return IMPORTANCE_FOREGROUND < importance
+                    && importance <= IMPORTANCE_FOREGROUND_SERVICE;
+        } catch (SecurityException e) {
+            Log.e(TAG, "Failed to check the app state", e);
+            return false;
+        }
+    }
+
+    /**
+     * Returns whether any of the one or more worksource objects contains a foreground service
+     * request.
+     */
+    public boolean hasAnyForegroundServiceRequest() {
+        for (int i = 0; i < mWorkSource.size(); i++) {
+            if (isForegroundService(mWorkSource.getPackageName(i))) return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns whether any of the one or more worksource objects contains an internal
+     * (i.e uid = Process.WIFI_UID) request.
+     */
+    public boolean hasAnyInternalRequest() {
+        for (int i = 0; i < mWorkSource.size(); i++) {
+            if (mWorkSource.getUid(i) == Process.WIFI_UID) return true;
+        }
+        return false;
+    }
+}
diff --git a/service/java/com/android/server/wifi/util/XmlUtil.java b/service/java/com/android/server/wifi/util/XmlUtil.java
index eb204e7..dd3d6f6 100644
--- a/service/java/com/android/server/wifi/util/XmlUtil.java
+++ b/service/java/com/android/server/wifi/util/XmlUtil.java
@@ -28,12 +28,16 @@
 import android.net.RouteInfo;
 import android.net.StaticIpConfiguration;
 import android.net.Uri;
+import android.net.wifi.SecurityParams;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
 import android.net.wifi.WifiEnterpriseConfig;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
+import android.util.SparseIntArray;
+
+import com.android.modules.utils.build.SdkLevel;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -352,8 +356,21 @@
         public static final String XML_TAG_RANDOMIZED_MAC_ADDRESS = "RandomizedMacAddress";
         public static final String XML_TAG_MAC_RANDOMIZATION_SETTING = "MacRandomizationSetting";
         public static final String XML_TAG_CARRIER_ID = "CarrierId";
+        public static final String XML_TAG_SUBSCRIPTION_ID = "SubscriptionId";
         public static final String XML_TAG_IS_AUTO_JOIN = "AutoJoinEnabled";
+        public static final String XML_TAG_DELETION_PRIORITY = "DeletionPriority";
+        public static final String XML_TAG_NUM_REBOOTS_SINCE_LAST_USE = "NumRebootsSinceLastUse";
+
         public static final String XML_TAG_IS_TRUSTED = "Trusted";
+        public static final String XML_TAG_IS_OEM_PAID = "OemPaid";
+        public static final String XML_TAG_IS_OEM_PRIVATE = "OemPrivate";
+        public static final String XML_TAG_IS_CARRIER_MERGED = "CarrierMerged";
+        public static final String XML_TAG_SECURITY_PARAMS_LIST = "SecurityParamsList";
+        public static final String XML_TAG_SECURITY_PARAMS = "SecurityParams";
+        public static final String XML_TAG_SECURITY_TYPE = "SecurityType";
+        public static final String XML_TAG_SAE_IS_H2E_ONLY_MODE = "SaeIsH2eOnlyMode";
+        public static final String XML_TAG_SAE_IS_PK_ONLY_MODE = "SaeIsPkOnlyMode";
+        public static final String XML_TAG_IS_ADDED_BY_AUTO_UPGRADE = "IsAddedByAutoUpgrade";
         private static final String XML_TAG_IS_MOST_RECENTLY_CONNECTED = "IsMostRecentlyConnected";
 
         /**
@@ -412,6 +429,30 @@
             }
         }
 
+        private static void writeSecurityParamsListToXml(
+                XmlSerializer out, WifiConfiguration configuration)
+                throws XmlPullParserException, IOException {
+            XmlUtil.writeNextSectionStart(out, XML_TAG_SECURITY_PARAMS_LIST);
+            for (SecurityParams params: configuration.getSecurityParamsList()) {
+                XmlUtil.writeNextSectionStart(out, XML_TAG_SECURITY_PARAMS);
+                XmlUtil.writeNextValue(
+                        out, XML_TAG_SECURITY_TYPE,
+                        params.getSecurityType());
+                XmlUtil.writeNextValue(
+                        out, XML_TAG_SAE_IS_H2E_ONLY_MODE,
+                        params.isSaeH2eOnlyMode());
+                XmlUtil.writeNextValue(
+                        out, XML_TAG_SAE_IS_PK_ONLY_MODE,
+                        params.isSaePkOnlyMode());
+                XmlUtil.writeNextValue(
+                        out, XML_TAG_IS_ADDED_BY_AUTO_UPGRADE,
+                        params.isAddedByAutoUpgrade());
+                XmlUtil.writeNextSectionEnd(out, XML_TAG_SECURITY_PARAMS);
+            }
+
+            XmlUtil.writeNextSectionEnd(out, XML_TAG_SECURITY_PARAMS_LIST);
+        }
+
         /**
          * Write the Configuration data elements that are common for backup & config store to the
          * XML stream.
@@ -455,6 +496,13 @@
                     configuration.allowedSuiteBCiphers.toByteArray());
             XmlUtil.writeNextValue(out, XML_TAG_SHARED, configuration.shared);
             XmlUtil.writeNextValue(out, XML_TAG_IS_AUTO_JOIN, configuration.allowAutojoin);
+            XmlUtil.writeNextValue(
+                    out, XML_TAG_DELETION_PRIORITY,
+                    configuration.getDeletionPriority());
+            XmlUtil.writeNextValue(
+                    out, XML_TAG_NUM_REBOOTS_SINCE_LAST_USE,
+                    configuration.numRebootsSinceLastUse);
+            writeSecurityParamsListToXml(out, configuration);
         }
 
         /**
@@ -485,6 +533,10 @@
                 throws XmlPullParserException, IOException {
             writeCommonElementsToXml(out, configuration, encryptionUtil);
             XmlUtil.writeNextValue(out, XML_TAG_IS_TRUSTED, configuration.trusted);
+            XmlUtil.writeNextValue(out, XML_TAG_IS_OEM_PAID, configuration.oemPaid);
+            XmlUtil.writeNextValue(out, XML_TAG_IS_OEM_PRIVATE, configuration.oemPrivate);
+            XmlUtil.writeNextValue(out, XML_TAG_IS_CARRIER_MERGED,
+                    configuration.carrierMerged);
             XmlUtil.writeNextValue(out, XML_TAG_BSSID, configuration.BSSID);
             XmlUtil.writeNextValue(out, XML_TAG_STATUS, configuration.status);
             XmlUtil.writeNextValue(out, XML_TAG_FQDN, configuration.FQDN);
@@ -520,6 +572,7 @@
             XmlUtil.writeNextValue(out, XML_TAG_CARRIER_ID, configuration.carrierId);
             XmlUtil.writeNextValue(out, XML_TAG_IS_MOST_RECENTLY_CONNECTED,
                     configuration.isMostRecentlyConnected);
+            XmlUtil.writeNextValue(out, XML_TAG_SUBSCRIPTION_ID, configuration.subscriptionId);
         }
 
         /**
@@ -546,6 +599,63 @@
             }
         }
 
+        private static SecurityParams parseSecurityParamsFromXml(
+                XmlPullParser in, int outerTagDepth) throws XmlPullParserException, IOException {
+            SecurityParams params = null;
+            while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
+                String[] valueName = new String[1];
+                Object value = XmlUtil.readCurrentValue(in, valueName);
+                String tagName = valueName[0];
+                if (tagName == null) {
+                    throw new XmlPullParserException("Missing value name");
+                }
+                switch (tagName) {
+                    case WifiConfigurationXmlUtil.XML_TAG_SECURITY_TYPE:
+                        params = SecurityParams.createSecurityParamsBySecurityType((int) value);
+                        break;
+                    case WifiConfigurationXmlUtil.XML_TAG_SAE_IS_H2E_ONLY_MODE:
+                        if (null == params) {
+                            throw new XmlPullParserException("Missing security type.");
+                        }
+                        params.enableSaeH2eOnlyMode((boolean) value);
+                        break;
+                    case WifiConfigurationXmlUtil.XML_TAG_SAE_IS_PK_ONLY_MODE:
+                        if (null == params) {
+                            throw new XmlPullParserException("Missing security type.");
+                        }
+                        params.enableSaePkOnlyMode((boolean) value);
+                        break;
+                    case WifiConfigurationXmlUtil.XML_TAG_IS_ADDED_BY_AUTO_UPGRADE:
+                        if (null == params) {
+                            throw new XmlPullParserException("Missing security type.");
+                        }
+                        params.setIsAddedByAutoUpgrade((boolean) value);
+                        break;
+                }
+            }
+            return params;
+        }
+
+        private static void parseSecurityParamsListFromXml(
+                XmlPullParser in, int outerTagDepth,
+                WifiConfiguration configuration)
+                throws XmlPullParserException, IOException {
+            List<SecurityParams> paramsList = new ArrayList<>();
+            while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
+                switch (in.getName()) {
+                    case WifiConfigurationXmlUtil.XML_TAG_SECURITY_PARAMS:
+                        SecurityParams params = parseSecurityParamsFromXml(in, outerTagDepth + 1);
+                        if (params != null) {
+                            paramsList.add(params);
+                        }
+                        break;
+                }
+            }
+            if (!paramsList.isEmpty()) {
+                configuration.setSecurityParams(paramsList);
+            }
+        }
+
         /**
          * Parses the configuration data elements from the provided XML stream to a
          * WifiConfiguration object.
@@ -556,12 +666,13 @@
          * @param outerTagDepth depth of the outer tag in the XML document.
          * @param shouldExpectEncryptedCredentials Whether to expect encrypted credentials or not.
          * @param encryptionUtil Instance of {@link EncryptedDataXmlUtil}.
+         * @param fromSuggestion Is this WifiConfiguration created from a WifiNetworkSuggestion.
          * @return Pair<Config key, WifiConfiguration object> if parsing is successful,
          * null otherwise.
          */
         public static Pair<String, WifiConfiguration> parseFromXml(
                 XmlPullParser in, int outerTagDepth, boolean shouldExpectEncryptedCredentials,
-                @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
+                @Nullable WifiConfigStoreEncryptionUtil encryptionUtil, boolean fromSuggestion)
                 throws XmlPullParserException, IOException {
             WifiConfiguration configuration = new WifiConfiguration();
             String configKeyInData = null;
@@ -704,15 +815,32 @@
                         case XML_TAG_CARRIER_ID:
                             configuration.carrierId = (int) value;
                             break;
+                        case XML_TAG_SUBSCRIPTION_ID:
+                            configuration.subscriptionId = (int) value;
+                            break;
                         case XML_TAG_IS_AUTO_JOIN:
                             configuration.allowAutojoin = (boolean) value;
                             break;
+                        case XML_TAG_DELETION_PRIORITY:
+                            configuration.setDeletionPriority((int) value);
+                            break;
+                        case XML_TAG_NUM_REBOOTS_SINCE_LAST_USE:
+                            configuration.numRebootsSinceLastUse = (int) value;
+                            break;
                         case XML_TAG_IS_TRUSTED:
                             configuration.trusted = (boolean) value;
                             break;
+                        case XML_TAG_IS_OEM_PAID:
+                            configuration.oemPaid = (boolean) value;
+                            break;
+                        case XML_TAG_IS_OEM_PRIVATE:
+                            configuration.oemPrivate = (boolean) value;
+                            break;
                         case XML_TAG_IS_MOST_RECENTLY_CONNECTED:
                             configuration.isMostRecentlyConnected = (boolean) value;
                             break;
+                        case XML_TAG_IS_CARRIER_MERGED:
+                            configuration.carrierMerged = (boolean) value;
                         default:
                             Log.w(TAG, "Ignoring unknown value name found: " + valueName[0]);
                             break;
@@ -737,6 +865,9 @@
                                 configuration.preSharedKey = new String(preSharedKeyBytes);
                             }
                             break;
+                        case XML_TAG_SECURITY_PARAMS_LIST:
+                            parseSecurityParamsListFromXml(in, outerTagDepth + 1, configuration);
+                            break;
                         default:
                             Log.w(TAG, "Ignoring unknown tag found: " + tagName);
                             break;
@@ -746,6 +877,11 @@
             if (!macRandomizationSettingExists) {
                 configuration.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_NONE;
             }
+            if (configuration.macRandomizationSetting
+                    == WifiConfiguration.RANDOMIZATION_PERSISTENT && !fromSuggestion) {
+                configuration.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_AUTO;
+            }
+            configuration.convertLegacyFieldsToSecurityParamsIfNeeded();
             return Pair.create(configKeyInData, configuration);
         }
     }
@@ -994,7 +1130,7 @@
     }
 
     /**
-     * Utility class to serialize and deseriaize {@link NetworkSelectionStatus} object to XML &
+     * Utility class to serialize and deserialize {@link NetworkSelectionStatus} object to XML &
      * vice versa. This is used by {@link com.android.server.wifi.WifiConfigStore} module.
      */
     public static class NetworkSelectionStatusXmlUtil {
@@ -1006,6 +1142,9 @@
         public static final String XML_TAG_DISABLE_REASON = "DisableReason";
         public static final String XML_TAG_CONNECT_CHOICE = "ConnectChoice";
         public static final String XML_TAG_HAS_EVER_CONNECTED = "HasEverConnected";
+        public static final String XML_TAG_IS_CAPTIVE_PORTAL_NEVER_DETECTED =
+                "CaptivePortalNeverDetected";
+        public static final String XML_TAG_CONNECT_CHOICE_RSSI = "ConnectChoiceRssi";
 
         /**
          * Write the NetworkSelectionStatus data elements from the provided status to the XML
@@ -1022,8 +1161,12 @@
                     out, XML_TAG_DISABLE_REASON,
                     selectionStatus.getNetworkSelectionDisableReasonString());
             XmlUtil.writeNextValue(out, XML_TAG_CONNECT_CHOICE, selectionStatus.getConnectChoice());
+            XmlUtil.writeNextValue(out, XML_TAG_CONNECT_CHOICE_RSSI,
+                    selectionStatus.getConnectChoiceRssi());
             XmlUtil.writeNextValue(
                     out, XML_TAG_HAS_EVER_CONNECTED, selectionStatus.hasEverConnected());
+            XmlUtil.writeNextValue(out, XML_TAG_IS_CAPTIVE_PORTAL_NEVER_DETECTED,
+                    selectionStatus.hasNeverDetectedCaptivePortal());
         }
 
         /**
@@ -1039,6 +1182,9 @@
             NetworkSelectionStatus selectionStatus = new NetworkSelectionStatus();
             String statusString = "";
             String disableReasonString = "";
+            // Initialize hasNeverDetectedCaptivePortal to "false" for upgrading legacy configs
+            // which do not have the XML_TAG_IS_CAPTIVE_PORTAL_NEVER_DETECTED tag.
+            selectionStatus.setHasNeverDetectedCaptivePortal(false);
 
             // Loop through and parse out all the elements from the stream within this section.
             while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
@@ -1057,9 +1203,14 @@
                     case XML_TAG_CONNECT_CHOICE:
                         selectionStatus.setConnectChoice((String) value);
                         break;
+                    case XML_TAG_CONNECT_CHOICE_RSSI:
+                        selectionStatus.setConnectChoiceRssi((int) value);
+                        break;
                     case XML_TAG_HAS_EVER_CONNECTED:
                         selectionStatus.setHasEverConnected((boolean) value);
                         break;
+                    case XML_TAG_IS_CAPTIVE_PORTAL_NEVER_DETECTED:
+                        selectionStatus.setHasNeverDetectedCaptivePortal((boolean) value);
                     default:
                         Log.w(TAG, "Ignoring unknown value name found: " + valueName[0]);
                         break;
@@ -1083,6 +1234,10 @@
             }
             selectionStatus.setNetworkSelectionStatus(status);
             selectionStatus.setNetworkSelectionDisableReason(disableReason);
+            if (status == NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED) {
+                // Make the counter non-zero so that logging code works properly
+                selectionStatus.setDisableReasonCounter(disableReason, 1);
+            }
             return selectionStatus;
         }
     }
@@ -1114,6 +1269,10 @@
         public static final String XML_TAG_REALM = "Realm";
         public static final String XML_TAG_OCSP = "Ocsp";
         public static final String XML_TAG_WAPI_CERT_SUITE = "WapiCertSuite";
+        public static final String XML_TAG_APP_INSTALLED_ROOT_CA_CERT = "AppInstalledRootCaCert";
+        public static final String XML_TAG_APP_INSTALLED_PRIVATE_KEY = "AppInstalledPrivateKey";
+        public static final String XML_TAG_KEYCHAIN_KEY_ALIAS = "KeyChainAlias";
+        public static final String XML_TAG_DECORATED_IDENTITY_PREFIX = "DecoratedIdentityPrefix";
 
         /**
          * Write password key to the XML stream.
@@ -1187,6 +1346,16 @@
             XmlUtil.writeNextValue(out, XML_TAG_OCSP, enterpriseConfig.getOcsp());
             XmlUtil.writeNextValue(out,
                     XML_TAG_WAPI_CERT_SUITE, enterpriseConfig.getWapiCertSuite());
+            XmlUtil.writeNextValue(out, XML_TAG_APP_INSTALLED_ROOT_CA_CERT,
+                    enterpriseConfig.isAppInstalledCaCert());
+            XmlUtil.writeNextValue(out, XML_TAG_APP_INSTALLED_PRIVATE_KEY,
+                    enterpriseConfig.isAppInstalledDeviceKeyAndCert());
+            XmlUtil.writeNextValue(out, XML_TAG_KEYCHAIN_KEY_ALIAS,
+                    enterpriseConfig.getClientKeyPairAliasInternal());
+            if (SdkLevel.isAtLeastS()) {
+                XmlUtil.writeNextValue(out, XML_TAG_DECORATED_IDENTITY_PREFIX,
+                        enterpriseConfig.getDecoratedIdentityPrefix());
+            }
         }
 
         /**
@@ -1227,7 +1396,7 @@
                                     WifiEnterpriseConfig.PASSWORD_KEY, (String) value);
                             if (shouldExpectEncryptedCredentials
                                     && !TextUtils.isEmpty(enterpriseConfig.getFieldValue(
-                                            WifiEnterpriseConfig.PASSWORD_KEY))) {
+                                    WifiEnterpriseConfig.PASSWORD_KEY))) {
                                 // Indicates that encryption of password failed when it was last
                                 // written.
                                 Log.e(TAG, "password value not expected");
@@ -1287,6 +1456,22 @@
                         case XML_TAG_WAPI_CERT_SUITE:
                             enterpriseConfig.setWapiCertSuite((String) value);
                             break;
+                        case XML_TAG_APP_INSTALLED_ROOT_CA_CERT:
+                            enterpriseConfig.initIsAppInstalledCaCert((boolean) value);
+                            break;
+                        case XML_TAG_APP_INSTALLED_PRIVATE_KEY:
+                            enterpriseConfig.initIsAppInstalledDeviceKeyAndCert((boolean) value);
+                            break;
+                        case XML_TAG_KEYCHAIN_KEY_ALIAS:
+                            if (SdkLevel.isAtLeastS()) {
+                                enterpriseConfig.setClientKeyPairAlias((String) value);
+                            }
+                            break;
+                        case XML_TAG_DECORATED_IDENTITY_PREFIX:
+                            if (SdkLevel.isAtLeastS()) {
+                                enterpriseConfig.setDecoratedIdentityPrefix((String) value);
+                            }
+                            break;
                         default:
                             Log.w(TAG, "Ignoring unknown value name found: " + valueName[0]);
                             break;
@@ -1398,6 +1583,9 @@
          * List of XML tags corresponding to SoftApConfiguration object elements.
          */
         public static final String XML_TAG_CLIENT_MACADDRESS = "ClientMacAddress";
+        public static final String XML_TAG_BAND_CHANNEL = "BandChannel";
+        private static final String XML_TAG_BAND = "Band";
+        private static final String XML_TAG_CHANNEL = "Channel";
 
         /**
          * Parses the client list from the provided XML stream to a ArrayList object.
@@ -1443,6 +1631,65 @@
                 XmlUtil.writeNextValue(out, XML_TAG_CLIENT_MACADDRESS, mac.toString());
             }
         }
+
+
+        /**
+         * Parses the band and channel from the provided XML stream to a SparseIntArray object.
+         *
+         * @param in            XmlPullParser instance pointing to the XML stream.
+         * @param outerTagDepth depth of the outer tag in the XML document.
+         * @return SparseIntArray object if parsing is successful, null otherwise.
+         */
+        public static SparseIntArray parseChannelsFromXml(XmlPullParser in,
+                int outerTagDepth) throws XmlPullParserException, IOException,
+                IllegalArgumentException {
+            SparseIntArray channels = new SparseIntArray();
+            while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
+                int band = ApConfigUtil.INVALID_VALUE_FOR_BAND_OR_CHANNEL;
+                int channel = ApConfigUtil.INVALID_VALUE_FOR_BAND_OR_CHANNEL;
+                switch (in.getName()) {
+                    case XML_TAG_BAND_CHANNEL:
+                        while (!XmlUtil.isNextSectionEnd(in, outerTagDepth + 1)) {
+                            String[] valueName = new String[1];
+                            Object value = XmlUtil.readCurrentValue(in, valueName);
+                            if (valueName[0] == null) {
+                                throw new XmlPullParserException("Missing value name");
+                            }
+                            switch (valueName[0]) {
+                                case XML_TAG_BAND:
+                                    band = (int) value;
+                                    break;
+                                case XML_TAG_CHANNEL:
+                                    channel = (int) value;
+                                    break;
+                                default:
+                                    Log.e(TAG, "Unknown value name found: " + valueName[0]);
+                                    break;
+                            }
+                        }
+                        channels.put(band, channel);
+                        break;
+                }
+            }
+            return channels;
+        }
+
+        /**
+         * Write the SoftApConfiguration channels data elements
+         * from the provided SparseIntArray to the XML stream.
+         *
+         * @param out       XmlSerializer instance pointing to the XML stream.
+         * @param channels  SparseIntArray, which includes bands and channels, to be serialized.
+         */
+        public static void writeChannelsToXml(XmlSerializer out, SparseIntArray channels)
+                throws XmlPullParserException, IOException {
+            for (int i = 0; i < channels.size(); i++) {
+                XmlUtil.writeNextSectionStart(out, XML_TAG_BAND_CHANNEL);
+                XmlUtil.writeNextValue(out, XML_TAG_BAND, channels.keyAt(i));
+                XmlUtil.writeNextValue(out, XML_TAG_CHANNEL, channels.valueAt(i));
+                XmlUtil.writeNextSectionEnd(out, XML_TAG_BAND_CHANNEL);
+            }
+        }
     }
 }
 
diff --git a/service/lint-baseline-pre-jarjar.xml b/service/lint-baseline-pre-jarjar.xml
index 40c9c7f..05772d7 100644
--- a/service/lint-baseline-pre-jarjar.xml
+++ b/service/lint-baseline-pre-jarjar.xml
@@ -1,15 +1,3 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
-
-    <issue
-        id="NewApi"
-        message="Class requires API level 31 (current min is 30): `android.os.BugreportManager`"
-        errorLine1="        BugreportManager bugreportManager = mContext.getSystemService(BugreportManager.class);"
-        errorLine2="                                                                      ~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Wifi/service/java/com/android/server/wifi/WifiDiagnostics.java"
-            line="318"
-            column="71"/>
-    </issue>
-
 </issues>
diff --git a/service/proto/Android.bp b/service/proto/Android.bp
index c582c69..baa2bed 100644
--- a/service/proto/Android.bp
+++ b/service/proto/Android.bp
@@ -22,6 +22,7 @@
         type: "lite",
     },
     sdk_version: "system_current",
+    min_sdk_version: "30",
     srcs: ["src/scorecard.proto"],
 }
 
@@ -36,6 +37,7 @@
         ":system-messages-proto-src",
     ],
     sdk_version: "system_current",
+    min_sdk_version: "30",
     // Pin java_version until jarjar is certified to support later versions. http://b/72703434
     java_version: "1.8",
     target: {
diff --git a/service/proto/src/metrics.proto b/service/proto/src/metrics.proto
index 1c3b278..35db206 100644
--- a/service/proto/src/metrics.proto
+++ b/service/proto/src/metrics.proto
@@ -324,9 +324,9 @@
   // Histogram of "Connect to Network" notification user actions.
   repeated ConnectToNetworkNotificationAndActionCount connect_to_network_notification_action_count = 78;
 
-  // The number of SSIDs blacklisted from recommendation by the open network
+  // The number of SSIDs blocked from recommendation by the open network
   // notification recommender
-  optional int32 open_network_recommender_blacklist_size = 79;
+  optional int32 open_network_recommender_blocklist_size = 79;
 
   // Is the available network notification feature turned on
   optional bool is_wifi_networks_available_notification_on = 80;
@@ -581,7 +581,7 @@
   // Metrics collected by health monitor
   optional HealthMonitorMetrics health_monitor_metrics = 158;
 
-  // Metrics related to the BssidBlocklistMonitor
+  // Metrics related to the WifiBlocklistMonitor
   optional BssidBlocklistStats bssid_blocklist_stats = 159;
 
   // Connection duration under various health conditions
@@ -733,6 +733,40 @@
 
   // Histogram of Rx link speed at 6G high band
   repeated Int32Count rx_link_speed_count_6g_high = 207;
+
+  // Metrics about the first connection after boot.
+  optional FirstConnectAfterBootStats first_connect_after_boot_stats = 208;
+
+  // Metrics for Wifi to Wifi switches.
+  optional WifiToWifiSwitchStats wifi_to_wifi_switch_stats = 209;
+
+  // Bandwidth estimator stats.
+  optional BandwidthEstimatorStats bandwidth_estimator_stats = 210;
+
+  // Total number of scan results from 6GHz PSC band
+  optional int32 num_6g_psc_network_scan_results = 211;
+
+  // Total number of Passpoint connections with a venue URL
+  optional int32 total_number_of_passpoint_connections_with_venue_url = 212;
+
+  // Total number of Passpoint connections with a T&C URL
+  optional int32 total_number_of_passpoint_connections_with_terms_and_conditions_url = 213;
+
+  // Total number of user accepted and T&C process completed successfully
+  optional int32 total_number_of_passpoint_acceptance_of_terms_and_conditions = 214;
+
+  // Total number of Passpoint profiles with decorated identity prefix
+  optional int32 total_number_of_passpoint_profiles_with_decorated_identity = 215;
+
+  // Scope of Passpoint deauth-imminent notification: ESS or BSS
+  repeated Int32Count passpoint_deauth_imminent_scope = 216;
+
+  // Total number of steering requests from AP
+  optional int32 num_steering_request = 217;
+
+  // Histogram corresponding to the number of times recent connection failure status are reported
+  // per WifiConfiguration.RecentFailureReason
+  repeated Int32Count recent_failure_association_status = 218;
 }
 
 // Information that gets logged for every WiFi connection.
@@ -853,7 +887,7 @@
     TYPE_PHASE2_AKA = 6;
 
     // EAP-Authentication and Key Agreement Prime [RFC-5448]
-    TYPE_PHASE2_AKA_PRIME   = 7;
+    TYPE_PHASE2_AKA_PRIME = 7;
   }
 
   enum OcspType {
@@ -891,7 +925,7 @@
   // whether ipv6 is supported.
   optional bool supports_ipv6 = 7;
 
-  // If the router is a passpoint / hotspot 2.0 network
+  // If the router is a Passpoint / Hotspot 2.0 network
   optional bool passpoint = 8;
 
   // EAP method used by the enterprise network
@@ -911,6 +945,29 @@
 
   // Max Rx link speed (in Mbps) supported by current network (STA and AP)
   optional int32 max_supported_rx_link_speed_mbps = 14;
+
+  // If the Passpoint connection is provided by the Home provider or Roaming/visited network
+  optional bool is_passpoint_home_provider = 15;
+}
+
+enum ClientRole {
+  // default/Invalid role
+  ROLE_UNKNOWN = 0;
+
+  // STA created for scans only.
+  ROLE_CLIENT_SCAN_ONLY = 1;
+
+  // secondary STA used for make before break.
+  ROLE_CLIENT_SECONDARY_TRANSIENT = 2;
+
+  // secondary STA created for local connection (no internet connectivity).
+  ROLE_CLIENT_LOCAL_ONLY = 3;
+
+  // primary STA.
+  ROLE_CLIENT_PRIMARY = 4;
+
+  // Long lived secondary STA used for restricted use cases
+  ROLE_CLIENT_SECONDARY_LONG_LIVED = 5;
 }
 
 message ConnectionEvent {
@@ -977,6 +1034,9 @@
 
     // The reason code if the AP can no longer accept new clients.
     ASSOCIATION_REJECTION_AP_UNABLE_TO_HANDLE_NEW_STA = 5;
+
+    // The reason code if AP disconnects STA during the connecting state.
+    DISCONNECTION_NON_LOCAL = 6;
   }
 
   // Entity that recommended connecting to this network.
@@ -1050,7 +1110,10 @@
   }
 
   // Start time of the connection, in milliseconds since Unix epoch (1970-01-01).
-  optional int64 start_time_millis = 1;// [(datapol.semantic_type) = ST_TIMESTAMP];
+  optional int64 start_time_millis = 1 [deprecated=true]; // [(datapol.semantic_type) = ST_TIMESTAMP];
+
+  // Start time of the connection, in milliseconds since device boot
+  optional int64 start_time_since_boot_millis = 28 [default = 0];
 
   // Duration to connect.
   optional int32 duration_taken_to_connect_millis = 2;
@@ -1112,6 +1175,20 @@
 
   // Indicates if the profile used for the connection was provisioned by Passpoint OSU server
   optional bool is_osu_provisioned = 20;
+
+  // The wireless interface name e.g. "wlan0", "wlan1". Used to distinguish which event stream this
+  // ConnectionEvent belongs to when multiple interfaces are active concurrently.
+  optional string interface_name = 27;
+
+  // The role of the wireless interface at the start of the connection.
+  // Gives more information on what purpose this interface is used for.
+  optional ClientRole interface_role = 29;
+
+  // Indicates if the profile used for the connection is carrier merged.
+  optional bool is_carrier_merged = 30;
+
+  // Indicates if this was the first connection event after boot
+  optional bool is_first_connection_after_boot = 31;
 }
 
 // Number of occurrences of a specific RSSI poll rssi value
@@ -1343,6 +1420,34 @@
 
     // SIM was removed while using a SIM config
     DISCONNECT_RESET_SIM_NETWORKS = 6;
+
+    // The network being evaluated in Make-Before-Break was disconnected due to no internet.
+    DISCONNECT_MBB_NO_INTERNET = 7;
+
+    // The network has been removed from the database.
+    DISCONNECT_NETWORK_REMOVED = 8;
+
+    // The network has been marked as metered.
+    DISCONNECT_NETWORK_METERED = 9;
+
+    // The network has been disabled temporarily.
+    DISCONNECT_NETWORK_TEMPORARY_DISABLED = 10;
+
+    // The network has been disabled permanently.
+    DISCONNECT_NETWORK_PERMANENT_DISABLED = 11;
+
+    // Carrier offload for this network has been disabled.
+    DISCONNECT_CARRIER_OFFLOAD_DISABLED = 12;
+
+    // Disconnected due to an issue with passpoint terms and conditions URL.
+    DISCONNECT_PASSPOINT_TAC = 13;
+
+    // VCN policy is requesting to tear down the network.
+    DISCONNECT_VCN_REQUEST = 14;
+
+    // Connected to a network that's not found in the database.
+    DISCONNECT_UNKNOWN_NETWORK = 15;
+
   }
 
   // Authentication Failure reasons as reported through the API.
@@ -1472,8 +1577,16 @@
   // Whether cellular data network is available
   optional bool is_cellular_data_available = 25;
 
-  //Whether Adaptive Connectivity is enabled
+  // Whether Adaptive Connectivity is enabled
   optional bool is_adaptive_connectivity_enabled = 26;
+
+  // The wireless interface name e.g. "wlan0", "wlan1". Used to distinguish which event stream this
+  // StaEvent belongs to when multiple interfaces are active concurrently.
+  optional string interface_name = 27;
+
+  // The role of the wireless interface
+  // Gives more information on what purpose this interface is used for.
+  optional ClientRole interface_role = 28;
 }
 
 // Wi-Fi Aware metrics
@@ -1619,6 +1732,9 @@
   // enabled which did not trigger ranging
   optional int32 num_matches_without_ranging_for_ranging_enabled_subscribes = 49;
 
+  // Total number of different types of NDP requests
+  repeated NdpRequestTypeHistogramBucket histogram_ndp_request_type = 50;
+
   // Histogram bucket for Wi-Fi Aware logs. Range is [start, end)
   message HistogramBucket {
     // lower range of the bucket (inclusive)
@@ -1687,6 +1803,32 @@
     // number of samples in the bucket
     optional int32 count = 2;
   }
+
+  enum NdpRequestTypeEnum {
+    // Unknown Type
+    NETWORK_SPECIFIER_TYPE_UNKNOWN = 0;
+
+    // TYPE: in band, specific peer: role, client_id, session_id, peer_id, pmk/passphrase optional
+    NETWORK_SPECIFIER_TYPE_IB = 1;
+
+    // TYPE: in band, any peer: role, client_id, session_id, pmk/passphrase optional
+    NETWORK_SPECIFIER_TYPE_IB_ANY_PEER = 2;
+
+    //TYPE: out-of-band: role, client_id, peer_mac, pmk/passphrase optional
+    NETWORK_SPECIFIER_TYPE_OOB = 3;
+
+    //TYPE: out-of-band, any peer: role, client_id, pmk/passphrase optional
+    NETWORK_SPECIFIER_TYPE_OOB_ANY_PEER = 4;
+
+  }
+
+  message NdpRequestTypeHistogramBucket {
+    // request type defining the bucket
+    optional NdpRequestTypeEnum ndp_request_type = 1;
+    // number of samples in the bucket
+    optional int32 count = 2;
+
+  }
 }
 
 // Data point used to build 'Number of Connectable Network' histograms
@@ -1789,14 +1931,27 @@
   // Soft AP event Types
   enum SoftApEventType {
 
-    // Soft AP is Up and ready for use
+    // Soft AP in single AP mode is Up and ready for use.
+    // The sequence of the events in single AP mode:
+    // SOFT_AP_UP -> ... (ex:NUM_CLIENTS_CHANGED) -> SOFT_AP_DOWN
     SOFT_AP_UP = 0;
 
-    // Soft AP is Down
+    // Soft AP in single/dual AP mode is Down.
     SOFT_AP_DOWN = 1;
 
-    // Number of connected soft AP clients has changed
+    // Number of connected soft AP clients has changed.
     NUM_CLIENTS_CHANGED = 2;
+
+    // Soft AP in dual AP mode is Up and ready for use.
+    // There are two DUAL_AP_BOTH_INSTANCES_UP events in dual AP mode for
+    // storing each instance information.
+    // The sequence of the events in dual AP mode:
+    // DUAL_AP_BOTH_INSTANCES_UP -> DUAL_AP_BOTH_INSTANCES_UP
+    // ... (ex: DUAL_AP_ONE_INSTANCE_DOWN or NUM_CLIENTS_CHANGED) -> SOFT_AP_DOWN
+    DUAL_AP_BOTH_INSTANCES_UP = 3;
+
+    // One of the dual AP instance is Down.
+    DUAL_AP_ONE_INSTANCE_DOWN = 4;
   }
 
   // Soft AP channel bandwidth types
@@ -1817,6 +1972,20 @@
     BANDWIDTH_160 = 6;
   }
 
+  // The operational mode  of the Soft AP.
+  enum Generation {
+
+    WIFI_STANDARD_UNKNOWN = 0;
+
+    WIFI_STANDARD_LEGACY = 1;
+
+    WIFI_STANDARD_11N = 4;
+
+    WIFI_STANDARD_11AC = 5;
+
+    WIFI_STANDARD_11AX = 6;
+  }
+
   // Type of event being recorded
   optional SoftApEventType event_type = 1;
 
@@ -1846,6 +2015,17 @@
 
   // Indicates if user enabled the client_control
   optional bool client_control_is_enabled = 10;
+
+  // The operational mode of the AP
+  optional Generation generation = 11;
+
+  // Number of connected clients on current frequency if event_type is NUM_CLIENTS_CHANGED,
+  // otherwise zero. On dual AP, it may be different from num_connected_clients
+  // because there are two instances and num_connected_clients is used to record
+  // total number of the clients. But num_connected_clients_on_current_frequency
+  // is used to record the number of the clients on specific instance. (depends
+  // on channel_frequency)
+  optional int32 num_connected_clients_on_current_frequency = 12;
 }
 
 // Power stats for Wifi
@@ -2166,6 +2346,14 @@
   // Number of times connection is initiated in the high movement state
   // only relevant if high_movement_multiple_scans_feature_enabled=true
   optional int32 num_high_movement_connection_started = 4;
+
+  // Histogram corresponding to the number of times BSSIDs are blocked per
+  // WifiBlocklistMonitor.FailureReason.
+  repeated Int32Count bssid_blocklist_per_reason_count = 5;
+
+  // Histogram corresponding to the number of times WifiConfigurations are blocked per
+  // WifiConfiguration.NetworkSelectionStatus.NetworkSelectionDisableReason.
+  repeated Int32Count wifi_config_blocklist_per_reason_count = 6;
 }
 
 message WifiIsUnusableEvent {
@@ -2309,6 +2497,9 @@
   // Total time the wifi radio spent doing hotspot 2.0 scans and GAS exchange
   // in ms over the logging duration.
   optional int64 radio_hs20_scan_time_ms = 10;
+
+  // Radio stats from all the radios
+  repeated RadioStats radio_stats = 11;
 }
 
 message WifiUsabilityStatsEntry {
@@ -2463,6 +2654,183 @@
   // The device mobility state
   optional DeviceMobilityStatePnoScanStats.DeviceMobilityState
           device_mobility_state = 34;
+
+  // Duty cycle of the connection.
+  // if this connection is being served using time slicing on a radio with one or more interfaces
+  // (i.e MCC), then this field contains the duty cycle assigned to this interface in percent.
+  // If no concurrency or not using time slicing during concurrency (i.e SCC or DBS), set to 100.
+  optional int32 time_slice_duty_cycle_in_percent = 35;
+
+  // WME data packet contention time statistics for all four categories: BE, BK, VI, VO
+  repeated ContentionTimeStats contention_time_stats = 36;
+
+  // The channel utilization ratio (value) in the range of
+  // [BssLoad.MIN_CHANNEL_UTILIZATION, BssLoad.MAX_CHANNEL_UTILIZATION], where MIN_CHANNEL_UTILIZATION
+  // corresponds to ratio 0%, MAX_CHANNEL_UTILIZATION corresponds to ratio 100%,
+  // see {@link com.android.server.wifi.util.InformationElementUtil}
+  optional int32 channel_utilization_ratio = 37;
+
+  // Indicate whether current link layer throughput is sufficient, see {@link WifiDataStall#isThroughputSufficient()}.
+  optional bool is_throughput_sufficient = 38;
+
+  // Indicate whether Wi-Fi scoring is enabled by the user, see {@link WifiManager#setWifiScoringEnabled(boolean)}.
+  optional bool is_wifi_scoring_enabled = 39;
+
+  // Indicate whether Cellular data is available, see {@link WifiDataStall#isCellularDataAvailable()}.
+  optional bool is_cellular_data_available = 40;
+
+  // Rate statistics, including number of successful packets, retries, etc.,
+  // indexed by preamble, bandwidth, number of spatial streams, MCS.
+  repeated RateStats rate_stats = 41;
+
+  // Number of stations associated with current AP.
+  optional int32 sta_count = 42;
+
+  // Channel utilization at current AP in the range of [0, 255].
+  optional int32 channel_utilization = 43;
+
+  // Link layer radio stats for all the radios
+  repeated RadioStats radio_stats = 44;
+}
+
+message ContentionTimeStats {
+  enum AccessCategory {
+    // WME Best Effort Access Category
+    WME_ACCESS_CATEGORY_BE = 0;
+
+    // WME Background Access Category
+    WME_ACCESS_CATEGORY_BK = 1;
+
+    // WME Video Access Category
+    WME_ACCESS_CATEGORY_VI = 2;
+
+    // WME Voice Access Category
+    WME_ACCESS_CATEGORY_VO = 3;
+  }
+
+  // WME access category
+  optional AccessCategory access_category = 1;
+
+  // Data packet min contention time in microseconds
+  optional int64 contention_time_min_micros = 2;
+
+  // Data packet max contention time in microseconds
+  optional int64 contention_time_max_micros = 3;
+
+  // Data packet average contention time in microseconds
+  optional int64 contention_time_avg_micros = 4;
+
+  // Number of data packets used for contention statistics
+  optional int64 contention_num_samples = 5;
+}
+
+message RateStats {
+  enum WifiPreambleType {
+    // Preamble type for IEEE 802.11a/g, IEEE Std 802.11-2020, Section 17
+    WIFI_PREAMBLE_OFDM = 0;
+    // Preamble type for IEEE 802.11b, IEEE Std 802.11-2020, Section 16
+    WIFI_PREAMBLE_CCK = 1;
+    // Preamble type for IEEE 802.11n, IEEE Std 802.11-2020, Section 19
+    WIFI_PREAMBLE_HT = 2;
+    // Preamble type for IEEE 802.11ac, IEEE Std 802.11-2020, Section 21
+    WIFI_PREAMBLE_VHT = 3;
+    // Preamble type for IEEE 802.11ax, IEEE Std 802.11-2020, Section 27
+    WIFI_PREAMBLE_HE = 5;
+    // Invalid
+    WIFI_PREAMBLE_INVALID = -1;
+  }
+
+  enum WifiSpatialStreams {
+    // Single stream, 1x1
+    WIFI_SPATIAL_STREAMS_ONE = 0;
+    // Dual streams, 2x2
+    WIFI_SPATIAL_STREAMS_TWO = 1;
+    // Three streams, 3x3
+    WIFI_SPATIAL_STREAMS_THREE = 2;
+    // Four streams, 4x4
+    WIFI_SPATIAL_STREAMS_FOUR = 3;
+    // Invalid
+    WIFI_SPATIAL_STREAMS_INVALID = -1;
+  }
+
+  enum WifiChannelBandwidth {
+    // Channel bandwidth: 20MHz
+    WIFI_BANDWIDTH_20_MHZ = 0;
+    // Channel bandwidth: 40MHz
+    WIFI_BANDWIDTH_40_MHZ = 1;
+    // Channel bandwidth: 80MHz
+    WIFI_BANDWIDTH_80_MHZ = 2;
+    // Channel bandwidth: 160MHz
+    WIFI_BANDWIDTH_160_MHZ = 3;
+    // Channel bandwidth: 80MHz + 80MHz
+    WIFI_BANDWIDTH_80P80_MHZ = 4;
+    // Channel bandwidth: 5MHz
+    WIFI_BANDWIDTH_5_MHZ = 5;
+    // Channel bandwidth: 10MHz
+    WIFI_BANDWIDTH_10_MHZ = 6;
+    // Invalid channel bandwidth
+    WIFI_BANDWIDTH_INVALID = -1;
+  }
+
+  // Preamble information.
+  optional WifiPreambleType preamble = 1;
+
+  // Number of spatial streams.
+  optional WifiSpatialStreams nss = 2;
+
+  // Bandwidth information.
+  optional WifiChannelBandwidth bw = 3;
+
+  // MCS index. OFDM/CCK rate code would be as per IEEE std in the units of 0.5Mbps.
+  // HT/VHT/HE: it would be MCS index.
+  optional int32 rate_mcs_idx = 4;
+
+  // Bitrate in units of 100 Kbps.
+  optional int32 bit_rate_in_kbps = 5;
+
+  // Number of successfully transmitted data packets (ACK received).
+  optional int32 tx_mpdu = 6;
+
+  // Number of received data packets.
+  optional int32 rx_mpdu = 7;
+
+  // Number of data packet losses (no ACK).
+  optional int32 mpdu_lost = 8;
+
+  // Number of data packet retries.
+  optional int32 retries = 9;
+}
+
+message RadioStats {
+  // The Radio ID
+  optional int32 radio_id = 1;
+  // The total time the wifi radio is on in ms counted from the last radio chip reset
+  optional int64 total_radio_on_time_ms = 2;
+
+  // The total time the wifi radio is doing tx in ms counted from the last radio chip reset
+  optional int64 total_radio_tx_time_ms = 3;
+
+  // The total time the wifi radio is doing rx in ms counted from the last radio chip reset
+  optional int64 total_radio_rx_time_ms = 4;
+
+  // The total time spent on all types of scans in ms counted from the last radio chip reset
+  optional int64 total_scan_time_ms = 5;
+
+  // The total time spent on nan scans in ms counted from the last radio chip reset
+  optional int64 total_nan_scan_time_ms = 6;
+
+  // The total time spent on background scans in ms counted from the last radio chip reset
+  optional int64 total_background_scan_time_ms = 7;
+
+  // The total time spent on roam scans in ms counted from the last radio chip reset
+  optional int64 total_roam_scan_time_ms = 8;
+
+  // The total time spent on pno scans in ms counted from the last radio chip reset
+  optional int64 total_pno_scan_time_ms = 9;
+
+  // The total time spent on hotspot2.0 scans and GAS exchange in ms counted from the last radio
+  // chip reset
+  optional int64 total_hotspot_2_scan_time_ms = 10;
 }
 
 message WifiUsabilityStats {
@@ -2706,6 +3074,12 @@
   // Number of Enrollee-Responder incompatible configurations found in DPP R2 compatibility check.
   optional int32 num_dpp_r2_enrollee_responder_incompatible_configuration = 10;
 
+  // Number of Enrollee-Responder requests
+  optional int32 num_dpp_enrollee_responder_requests = 11;
+
+  // Number of Enrollee-Responder success
+  optional int32 num_dpp_enrollee_responder_success = 12;
+
   // Histogram bucket for Wi-Fi DPP configurator success
   message DppConfiguratorSuccessStatusHistogramBucket {
     // status type defining the bucket
@@ -2777,6 +3151,12 @@
 
     // Easy Connect R2 Failure event: Enrollee rejected the configuration.
     EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION = 12;
+
+    // Easy Connect Failure event: System failed to generate DPP URI.
+    EASY_CONNECT_EVENT_FAILURE_URI_GENERATION = 13;
+
+    // Easy Connect Failure event: Enrollee didn't scan the network's operating channel.
+    EASY_CONNECT_EVENT_FAILURE_ENROLLEE_FAILED_TO_SCAN_NETWORK_CHANNEL = 14;
   }
 }
 
@@ -2918,9 +3298,6 @@
   // Histogram of requests via this API surface to number of networks matched in scan results.
   repeated HistogramBucketInt32 network_match_size_histogram = 2;
 
-  // Number of successful network connection from this API.
-  optional int32 num_connect_success = 3;
-
   // Number of requests via this API surface that bypassed user approval.
   optional int32 num_user_approval_bypass = 4;
 
@@ -2929,6 +3306,32 @@
 
   // Number of unique apps using this API surface.
   optional int32 num_apps = 6;
+
+  // Number of connections on primary iface.
+  optional int32 num_connect_on_primary_iface = 7;
+
+  // Number of successful network connection from this API.
+  optional int32 num_connect_success_on_primary_iface = 3;
+
+  // Histogram of connection durations in seconds using this API surface.
+  repeated HistogramBucketInt32 connection_duration_sec_on_primary_iface_histogram = 8;
+
+  // Number of connections on secondary iface (STA + STA)
+  optional int32 num_connect_on_secondary_iface = 9;
+
+  // Number of connection success on secondary iface (STA + STA)
+  optional int32 num_connect_success_on_secondary_iface = 10;
+
+  // Histogram of connection durations in seconds on secondary iface using this API surface.
+  repeated HistogramBucketInt32 connection_duration_sec_on_secondary_iface_histogram = 11;
+
+  // Number of times there were concurrent connections (STA + STA):
+  // - on a primary iface (internet) and
+  // - on a secondary iface (using this API surface)
+  optional int32 num_concurrent_connection = 12;
+
+  // Histogram of concurrent connection durations in seconds using this API surface (STA + STA).
+  repeated HistogramBucketInt32 concurrent_connection_duration_sec_histogram = 13;
 }
 
 // NetworkSuggestion API metrics.
@@ -2968,6 +3371,16 @@
 
   // Number of user revoke app's permission from settings or disallowed from UI.
   optional int32 user_revoke_app_suggestion_permission = 6;
+
+  // Number of priority groups configured on the device, excluding the default priority group.
+  optional int32 num_priority_groups = 7;
+
+  // Number of times when a ScanResult matched more than one suggestions.
+  optional int32 num_multiple_suggestions = 8;
+
+  // Number of saved profiles which also have at least one suggestion (for the same network)
+  // provisioned on the device.
+  optional int32 num_saved_networks_with_configured_suggestion = 9;
 }
 
 // WifiLock metrics
@@ -3165,6 +3578,8 @@
     EVENT_CONFIGURE_ADAPTIVE_CONNECTIVITY_ON = 14;
     // User sets the adaptive connectivity to off
     EVENT_CONFIGURE_ADAPTIVE_CONNECTIVITY_OFF = 15;
+    // User restarts wifi subsystem
+    EVENT_RESTART_WIFI_SUB_SYSTEM = 16;
   }
 
   // The type of user action
@@ -3280,6 +3695,8 @@
   optional int32 cnt_disconnection_nonlocal = 5;
   // Number of networks with a large change of (or high) short disconnection rate
   optional int32 cnt_short_connection_nonlocal = 6;
+  // Number of networks with a large change of (or high) disconnection-during-connecting rate
+  optional int32 cnt_disconnection_nonlocal_connecting = 7;
 }
 
 // Metrics collected by health monitor
@@ -3309,6 +3726,9 @@
   optional int32 total_time_insufficient_throughput_ms = 2;
   // Total time with cellular data off
   optional int32 total_time_cellular_data_off_ms = 3;
+  // Total time with insufficient Wifi throughput and with cellular data on
+  // while wifi is the default network
+  optional int32 total_time_insufficient_throughput_default_wifi_ms = 4;
 }
 
 // Wi-Fi off metrics
@@ -3420,3 +3840,110 @@
   optional int32 num_connection_auth_failure = 2;
   optional int32 num_connection_non_auth_failure = 3;
 }
+
+message FirstConnectAfterBootStats {
+  message Attempt {
+    // The attempt completion timestamp, in milliseconds since boot.
+    optional int64 timestamp_since_boot_millis = 1;
+
+    // whether the attempt was successful.
+    optional bool is_success = 2;
+  }
+
+  // Info about whether Wifi was enabled at boot time.
+  // `is_success` is true if Wifi is toggled ON at boot, false otherwise.
+  // This field will never be null.
+  optional Attempt wifi_enabled_at_boot = 1;
+
+  // Info about the first network selection attempt for auto-join.
+  // `is_success` is true if a network candidate was found, false otherwise.
+  // This field is null if `wifi_enabled_at_boot.is_success` is false.
+  optional Attempt first_network_selection = 2;
+
+  // Info about the first successful L2 connection.
+  // `is_success` is true if the L2 connection was successfully established, false otherwise.
+  // This field is null if `first_network_selection.is_success` is false.
+  optional Attempt first_l2_connection = 3;
+
+  // Info about the first successful L3 connection.
+  // `is_success` is true if the L3 connection was successfully established, false otherwise.
+  // This field is null if `first_l2_connection.is_success` is false.
+  optional Attempt first_l3_connection = 4;
+}
+
+// Stats for Autojoin switches between Wifi networks. Does not include user/app-initiated switches.
+message WifiToWifiSwitchStats {
+  // Whether Make Before Break is supported by the hardware and enabled.
+  optional bool is_make_before_break_supported = 1;
+
+  // Number of times Wifi to Wifi switch was triggered. This includes Make Before Break and Break
+  // Before Make.
+  optional int32 wifi_to_wifi_switch_trigger_count = 2;
+
+  // Number of times Wifi to Wifi switch was triggered using Make Before Break (MBB).
+  // Note that MBB may not always be used for various reasons e.g. no additional iface available due
+  // to ongoing SoftAP, both old and new network have MAC randomization disabled, etc.
+  // TODO(b/180974604): also add metrics for the various reasons to not use MBB even when it is
+  //  supported by the device.
+  optional int32 make_before_break_trigger_count = 3;
+
+  // TODO(b/180974604): add metrics for L2 failure and L3 failure
+
+  // Number of times Make Before Break was aborted due to the new network not having internet.
+  optional int32 make_before_break_no_internet_count = 4;
+
+  // Number of times where, for some reason, Make Before Break resulted in the loss of the primary
+  // ClientModeManager, and we needed to recover by making one of the SECONDARY_TRANSIENT
+  // ClientModeManagers primary.
+  optional int32 make_before_break_recover_primary_count = 5;
+
+  // Number of times the new network in Make Before Break had its internet connection validated.
+  optional int32 make_before_break_internet_validated_count = 6;
+
+  // Number of times the new network was made primary in Make Before Break.
+  // TODO(b/180974604): add metrics for the various ways this could fail.
+  optional int32 make_before_break_success_count = 7;
+
+  // Number of times the old network in Make Before Break completed lingering and was disconnected.
+  optional int32 make_before_break_linger_completed_count = 8;
+
+  // Histogram of lingering duration caused by make before break in seconds.
+  repeated Int32Count make_before_break_linger_duration_seconds = 9;
+}
+
+// Network layer bandwidth estimator stats
+message BandwidthEstimatorStats {
+  // Bandwidth stats of each signal level
+  message PerLevel {
+    // Signal level derived by RssiUtil.calculateSignalLevel based on RSSI
+    optional uint32 signal_level = 1;
+    // Accumulated bandwidth sample count
+    optional uint32 count = 2;
+    // Average network layer bandwidth in kbps based on TrafficStats byte count and radio on time
+    // of link layer stats.
+    optional uint32 avg_bandwidth_kbps = 3;
+    // Error of layer 2 link bandwidth in percent
+    optional uint32 l2_error_percent = 4;
+    // Error of network layer bandwidth estimation in percent
+    optional uint32 bandwidth_est_error_percent = 5;
+  }
+
+  // Bandwidth stats of each link
+  message PerLink {
+    repeated PerLevel level = 1;
+  }
+
+  // Bandwidth stats of each band
+  message PerBand {
+    // Tx bandwidth stats
+    optional PerLink tx = 1;
+    // Rx bandwidth stats
+    optional PerLink rx = 2;
+  }
+
+  // Stats of 2g band
+  optional PerBand stats_2g = 1;
+  // Stats of above-2g band
+  optional PerBand stats_above_2g = 2;
+}
+
diff --git a/service/proto/src/scorecard.proto b/service/proto/src/scorecard.proto
index d351c56..a50d8b0 100644
--- a/service/proto/src/scorecard.proto
+++ b/service/proto/src/scorecard.proto
@@ -56,6 +56,8 @@
   reserved 3;
   repeated Signal event_stats = 4;     // Statistics taken at specific events
   reserved 5;
+  // Link bandwidth stats of all bands, links and signal levels
+  optional BandwidthStatsAll bandwidth_stats_all = 7;
 };
 
 // Describes the IEEE 802.11 security type
@@ -105,10 +107,37 @@
   // Compact signed encoding used here, because rssi values are negative.
   // The upper bound is not stored explicitly.
   optional sint64 low = 1;
-  // Number of occurences for this value or bucket.
+  // Number of occurrences for this value or bucket.
   optional int64 number = 2;
 }
 
+message BandwidthStatsAll {
+  // Stats of 2g band
+  optional BandwidthStatsAllLink stats_2g = 1;
+  // Stats of above-2g band
+  optional BandwidthStatsAllLink stats_above_2g = 2;
+}
+
+message BandwidthStatsAllLink {
+  // Tx bandwidth stats
+  optional BandwidthStatsAllLevel tx = 1;
+  // Rx bandwidth stats
+  optional BandwidthStatsAllLevel rx = 2;
+}
+
+// Bandwidth of all signal levels
+// The number of entries will match WifiScoreCard.NUM_SIGNAL_LEVEL
+message BandwidthStatsAllLevel {
+  repeated BandwidthStats level = 1;
+}
+
+message BandwidthStats {
+  // Accumulated bandwidth sample value
+  optional uint64 value = 1;
+  // Accumulated bandwidth sample count
+  optional uint32 count = 2;
+}
+
 // Events where statistics may be collected
 enum Event {
   SIGNAL_POLL = 1;
@@ -163,7 +192,8 @@
   optional ConnectionStats stats_prev_build = 4;
   // List of frequencies observed for this network from scan results, sorted by most recent first.
   repeated int32 frequencies = 5;
-
+  // Link bandwidth stats of all bands, links and signal levels
+  optional BandwidthStatsAll bandwidth_stats_all = 6;
 };
 
 message ConnectionStats {
@@ -183,9 +213,11 @@
   // Number of short connections caused by nonlocal disconnection at high RSSI
   // or at high Tx speed with a recent RSSI poll
   optional int32 num_short_connection_nonlocal = 7;
-  // Number of non-locally generated disconnections at high RSSI or Tx speed
-  // with a recent RSSI poll
+  // Number of non-local disconnections after L3 connection at high RSSI
+  // or Tx speed with a recent RSSI poll
   optional int32 num_disconnection_nonlocal = 8;
-  // Number of disconnections with a recent RSSI poll
+  // Number of disconnections after L3 connection with a recent RSSI poll
   optional int32 num_disconnection = 9;
+  // Number of non-local disconnections during the connecting state at high RSSI
+  optional int32 num_disconnection_nonlocal_connecting = 10;
 }
diff --git a/service/tests/mts/Android.bp b/service/tests/mts/Android.bp
index 3b5ba08..f29908a 100644
--- a/service/tests/mts/Android.bp
+++ b/service/tests/mts/Android.bp
@@ -23,6 +23,9 @@
     name: "MtsWifiTestCases",
     defaults: ["cts_defaults"],
 
+    min_sdk_version: "30",
+    target_sdk_version: "30",
+
     libs: [
         "org.apache.http.legacy",
         "android.test.base.stubs",
@@ -48,6 +51,9 @@
     // need access to system_current and test_current, so needs to be platform_apis: true
     platform_apis: true,
 
-    test_suites: [ "mts" ],
+    test_suites: [
+        "general-tests",
+        "mts-wifi",
+    ],
     test_config: "AndroidTest.xml",
 }
diff --git a/service/tests/mts/AndroidManifest.xml b/service/tests/mts/AndroidManifest.xml
index b8b025d..ae925ed 100644
--- a/service/tests/mts/AndroidManifest.xml
+++ b/service/tests/mts/AndroidManifest.xml
@@ -21,6 +21,8 @@
 
     <!-- Need DUMP permission to call `dumpsys wifi wifiMetricsProto` -->
     <uses-permission android:name="android.permission.DUMP" />
+    <!-- Need BATTERY_STATS permission to call `getWifiBatteryStats` during dump-->
+    <uses-permission android:name="android.permission.BATTERY_STATS" />
 
     <application android:usesCleartextTraffic="true">
         <uses-library android:name="android.test.runner" />
diff --git a/service/tests/mts/AndroidTest.xml b/service/tests/mts/AndroidTest.xml
index ee58786..b686b0a 100644
--- a/service/tests/mts/AndroidTest.xml
+++ b/service/tests/mts/AndroidTest.xml
@@ -19,6 +19,8 @@
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <option name="config-descriptor:metadata"
+            key="mainline-param" value="com.google.android.wifi.apex" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/service/tests/wifitests/Android.bp b/service/tests/wifitests/Android.bp
index 8f5db17..cedcb61 100644
--- a/service/tests/wifitests/Android.bp
+++ b/service/tests/wifitests/Android.bp
@@ -29,6 +29,7 @@
 
     static_libs: [
         "androidx.test.rules",
+        "collector-device-lib",
         "hamcrest-library",
         "mockito-target-extended-minus-junit4",
         "frameworks-base-testutils",
@@ -44,11 +45,13 @@
 
     jarjar_rules: ":wifi-jarjar-rules",
 
-    sdk_version: "core_platform", // tests can use @CorePlatformApi's
+    sdk_version: "core_current",
     libs: [
         // order matters: classes in framework-wifi are resolved before framework, meaning
         // @hide APIs in framework-wifi are resolved before @SystemApi stubs in framework
         "framework-wifi-pre-jarjar",
+        // for accessing connectivity hidden APIs
+        "framework-connectivity.impl",
         "framework",
 
         // if sdk_version="" this gets automatically included, but here we need to add manually.
@@ -61,28 +64,18 @@
         "ServiceWifiResources",
     ],
 
-    // These must be explicitly included because they are not normally accessible
-    // from apps.
+    // these are needed for Extended Mockito
     jni_libs: [
-        "libbase",
-        "libc++",
-        "ld-android",
-        "libdl_android",
-        "libcgrouprc",
-        "libcutils",
-        "libnativehelper",
-        "libprocessgroup",
-        "libutils",
-        "libvndksupport",
         "libdexmakerjvmtiagent",
         "libstaticjvmtiagent",
     ],
     compile_multilib: "both",
 
-    min_sdk_version: "29",
+    min_sdk_version: "30",
+    target_sdk_version: "30",
     privileged: true,
     test_suites: [
-        "device-tests",
+        "general-tests",
         "mts-wifi",
     ],
 
@@ -97,24 +90,27 @@
             "com.android.server.wifi.ActiveModeWarden",
             "com.android.server.wifi.ActiveModeWarden$*",
             "com.android.server.wifi.ActiveModeWarden.**",
+            "com.android.server.wifi.AdaptiveConnectivityEnabledSettingObserver",
+            "com.android.server.wifi.AdaptiveConnectivityEnabledSettingObserver$*",
+            "com.android.server.wifi.AdaptiveConnectivityEnabledSettingObserver.**",
             "com.android.server.wifi.AggressiveConnectedScore",
             "com.android.server.wifi.AggressiveConnectedScore$*",
             "com.android.server.wifi.AggressiveConnectedScore.**",
+            "com.android.server.wifi.AssocRejectEventInfo",
+            "com.android.server.wifi.AssocRejectEventInfo$*",
+            "com.android.server.wifi.AssocRejectEventInfo.**",
             "com.android.server.wifi.AvailableNetworkNotifier",
             "com.android.server.wifi.AvailableNetworkNotifier$*",
             "com.android.server.wifi.AvailableNetworkNotifier.**",
             "com.android.server.wifi.BackupManagerProxy",
             "com.android.server.wifi.BackupManagerProxy$*",
             "com.android.server.wifi.BackupManagerProxy.**",
-            "com.android.server.wifi.BaseWifiDiagnostics",
-            "com.android.server.wifi.BaseWifiDiagnostics$*",
-            "com.android.server.wifi.BaseWifiDiagnostics.**",
             "com.android.server.wifi.BaseWifiService",
             "com.android.server.wifi.BaseWifiService$*",
             "com.android.server.wifi.BaseWifiService.**",
-            "com.android.server.wifi.BssidBlocklistMonitor",
-            "com.android.server.wifi.BssidBlocklistMonitor$*",
-            "com.android.server.wifi.BssidBlocklistMonitor.**",
+            "com.android.server.wifi.WifiBlocklistMonitor",
+            "com.android.server.wifi.WifiBlocklistMonitor$*",
+            "com.android.server.wifi.WifiBlocklistMonitor.**",
             "com.android.server.wifi.BubbleFunScorer",
             "com.android.server.wifi.BubbleFunScorer$*",
             "com.android.server.wifi.BubbleFunScorer.**",
@@ -124,6 +120,9 @@
             "com.android.server.wifi.ByteBufferReader",
             "com.android.server.wifi.ByteBufferReader$*",
             "com.android.server.wifi.ByteBufferReader.**",
+            "com.android.server.wifi.ClientMode",
+            "com.android.server.wifi.ClientMode$*",
+            "com.android.server.wifi.ClientMode.**",
             "com.android.server.wifi.ClientModeImpl",
             "com.android.server.wifi.ClientModeImpl$*",
             "com.android.server.wifi.ClientModeImpl.**",
@@ -136,9 +135,15 @@
             "com.android.server.wifi.CompatibilityScorer",
             "com.android.server.wifi.CompatibilityScorer$*",
             "com.android.server.wifi.CompatibilityScorer.**",
+            "com.android.server.wifi.ConcreteClientModeManager",
+            "com.android.server.wifi.ConcreteClientModeManager$*",
+            "com.android.server.wifi.ConcreteClientModeManager.**",
             "com.android.server.wifi.ConfigurationMap",
             "com.android.server.wifi.ConfigurationMap$*",
             "com.android.server.wifi.ConfigurationMap.**",
+            "com.android.server.wifi.ConnectHelper",
+            "com.android.server.wifi.ConnectHelper$*",
+            "com.android.server.wifi.ConnectHelper.**",
             "com.android.server.wifi.ConnectToNetworkNotificationBuilder",
             "com.android.server.wifi.ConnectToNetworkNotificationBuilder$*",
             "com.android.server.wifi.ConnectToNetworkNotificationBuilder.**",
@@ -151,21 +156,21 @@
             "com.android.server.wifi.ConnectionFailureNotifier",
             "com.android.server.wifi.ConnectionFailureNotifier$*",
             "com.android.server.wifi.ConnectionFailureNotifier.**",
-            "com.android.server.wifi.DefaultModeManager",
-            "com.android.server.wifi.DefaultModeManager$*",
-            "com.android.server.wifi.DefaultModeManager.**",
+            "com.android.server.wifi.DefaultClientModeManager",
+            "com.android.server.wifi.DefaultClientModeManager$*",
+            "com.android.server.wifi.DefaultClientModeManager.**",
             "com.android.server.wifi.DeviceConfigFacade",
             "com.android.server.wifi.DeviceConfigFacade$*",
             "com.android.server.wifi.DeviceConfigFacade.**",
+            "com.android.server.wifi.DisconnectEventInfo",
+            "com.android.server.wifi.DisconnectEventInfo$*",
+            "com.android.server.wifi.DisconnectEventInfo.**",
             "com.android.server.wifi.DppManager",
             "com.android.server.wifi.DppManager$*",
             "com.android.server.wifi.DppManager.**",
             "com.android.server.wifi.DppMetrics",
             "com.android.server.wifi.DppMetrics$*",
             "com.android.server.wifi.DppMetrics.**",
-            "com.android.server.wifi.DummyLogMessage",
-            "com.android.server.wifi.DummyLogMessage$*",
-            "com.android.server.wifi.DummyLogMessage.**",
             "com.android.server.wifi.EapFailureNotifier",
             "com.android.server.wifi.EapFailureNotifier$*",
             "com.android.server.wifi.EapFailureNotifier.**",
@@ -187,9 +192,9 @@
             "com.android.server.wifi.IMSIParameter",
             "com.android.server.wifi.IMSIParameter$*",
             "com.android.server.wifi.IMSIParameter.**",
-            "com.android.server.wifi.ImsiPrivacyProtectionExemptionStoreData",
-            "com.android.server.wifi.ImsiPrivacyProtectionExemptionStoreData$*",
-            "com.android.server.wifi.ImsiPrivacyProtectionExemptionStoreData.**",
+            "com.android.server.wifi.WifiCarrierInfoStoreManagerData",
+            "com.android.server.wifi.WifiCarrierInfoStoreManagerData$*",
+            "com.android.server.wifi.WifiCarrierInfoStoreManagerData.**",
             "com.android.server.wifi.LastMileLogger",
             "com.android.server.wifi.LastMileLogger$*",
             "com.android.server.wifi.LastMileLogger.**",
@@ -235,6 +240,9 @@
             "com.android.server.wifi.NetworkUpdateResult",
             "com.android.server.wifi.NetworkUpdateResult$*",
             "com.android.server.wifi.NetworkUpdateResult.**",
+            "com.android.server.wifi.NoLogMessage",
+            "com.android.server.wifi.NoLogMessage$*",
+            "com.android.server.wifi.NoLogMessage.**",
             "com.android.server.wifi.OpenNetworkNotifier",
             "com.android.server.wifi.OpenNetworkNotifier$*",
             "com.android.server.wifi.OpenNetworkNotifier.**",
@@ -259,6 +267,9 @@
             "com.android.server.wifi.ScanDetailCache",
             "com.android.server.wifi.ScanDetailCache$*",
             "com.android.server.wifi.ScanDetailCache.**",
+            "com.android.server.wifi.ScanOnlyModeImpl",
+            "com.android.server.wifi.ScanOnlyModeImpl$*",
+            "com.android.server.wifi.ScanOnlyModeImpl.**",
             "com.android.server.wifi.ScanRequestProxy",
             "com.android.server.wifi.ScanRequestProxy$*",
             "com.android.server.wifi.ScanRequestProxy.**",
@@ -406,6 +417,9 @@
             "com.android.server.wifi.WifiDiagnostics",
             "com.android.server.wifi.WifiDiagnostics$*",
             "com.android.server.wifi.WifiDiagnostics.**",
+            "com.android.server.wifi.WifiGlobals",
+            "com.android.server.wifi.WifiGlobals$*",
+            "com.android.server.wifi.WifiGlobals.**",
             "com.android.server.wifi.WifiHealthMonitor",
             "com.android.server.wifi.WifiHealthMonitor$*",
             "com.android.server.wifi.WifiHealthMonitor.**",
@@ -454,6 +468,9 @@
             "com.android.server.wifi.WifiNetworkSuggestionsManager",
             "com.android.server.wifi.WifiNetworkSuggestionsManager$*",
             "com.android.server.wifi.WifiNetworkSuggestionsManager.**",
+            "com.android.server.wifi.WifiP2pConnection",
+            "com.android.server.wifi.WifiP2pConnection$*",
+            "com.android.server.wifi.WifiP2pConnection.**",
             "com.android.server.wifi.WifiPowerMetrics",
             "com.android.server.wifi.WifiPowerMetrics$*",
             "com.android.server.wifi.WifiPowerMetrics.**",
@@ -823,6 +840,9 @@
             "com.android.server.wifi.scanner.WificondScannerImpl",
             "com.android.server.wifi.scanner.WificondScannerImpl$*",
             "com.android.server.wifi.scanner.WificondScannerImpl.**",
+            "com.android.server.wifi.util.ActionListenerWrapper",
+            "com.android.server.wifi.util.ActionListenerWrapper$*",
+            "com.android.server.wifi.util.ActionListenerWrapper.**",
             "com.android.server.wifi.util.ApConfigUtil",
             "com.android.server.wifi.util.ApConfigUtil$*",
             "com.android.server.wifi.util.ApConfigUtil.**",
@@ -904,6 +924,9 @@
             "com.android.server.wifi.util.SettingsMigrationDataHolder",
             "com.android.server.wifi.util.SettingsMigrationDataHolder$*",
             "com.android.server.wifi.util.SettingsMigrationDataHolder.**",
+            "com.android.server.wifi.util.StateMachineObituary",
+            "com.android.server.wifi.util.StateMachineObituary$*",
+            "com.android.server.wifi.util.StateMachineObituary.**",
             "com.android.server.wifi.util.StringUtil",
             "com.android.server.wifi.util.StringUtil$*",
             "com.android.server.wifi.util.StringUtil.**",
@@ -925,6 +948,9 @@
             "com.android.server.wifi.util.WifiPermissionsWrapper",
             "com.android.server.wifi.util.WifiPermissionsWrapper$*",
             "com.android.server.wifi.util.WifiPermissionsWrapper.**",
+            "com.android.server.wifi.util.WorkSourceHelper",
+            "com.android.server.wifi.util.WorkSourceHelper$*",
+            "com.android.server.wifi.util.WorkSourceHelper.**",
             "com.android.server.wifi.util.WorkSourceUtil",
             "com.android.server.wifi.util.WorkSourceUtil$*",
             "com.android.server.wifi.util.WorkSourceUtil.**",
diff --git a/service/tests/wifitests/AndroidManifest.xml b/service/tests/wifitests/AndroidManifest.xml
index 31eaac9..151f4b5 100644
--- a/service/tests/wifitests/AndroidManifest.xml
+++ b/service/tests/wifitests/AndroidManifest.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!--
   ~ Copyright (C) 2015 The Android Open Source Project
   ~
@@ -17,27 +16,27 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.server.wifi.test">
+     package="com.android.server.wifi.test">
 
-    <application
-        android:debuggable="true"
-        android:largeHeap="true">
-        <uses-library android:name="android.test.runner" />
+    <application android:debuggable="true"
+         android:largeHeap="true">
+        <uses-library android:name="android.test.runner"/>
         <activity android:label="WifiTestDummyLabel"
-                  android:name="WifiTestDummyName">
+             android:name="WifiTestDummyName"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
     <instrumentation android:name="com.android.server.wifi.CustomTestRunner"
-        android:targetPackage="com.android.server.wifi.test"
-        android:label="Frameworks Wifi Tests">
+         android:targetPackage="com.android.server.wifi.test"
+         android:label="Frameworks Wifi Tests">
     </instrumentation>
 
-    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
-    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
 
 </manifest>
diff --git a/service/tests/wifitests/AndroidTest.xml b/service/tests/wifitests/AndroidTest.xml
index f904a44..931f5fc 100644
--- a/service/tests/wifitests/AndroidTest.xml
+++ b/service/tests/wifitests/AndroidTest.xml
@@ -20,6 +20,8 @@
 
     <option name="test-suite-tag" value="apct" />
     <option name="test-tag" value="FrameworksWifiTests" />
+    <option name="config-descriptor:metadata" key="mainline-param"
+            value="com.google.android.wifi.apex" />
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.server.wifi.test" />
         <option name="runner" value="com.android.server.wifi.CustomTestRunner" />
diff --git a/service/tests/wifitests/assets/coex_lte_27_harmonic.xml b/service/tests/wifitests/assets/coex_lte_27_harmonic.xml
new file mode 100644
index 0000000..9c046c3
--- /dev/null
+++ b/service/tests/wifitests/assets/coex_lte_27_harmonic.xml
@@ -0,0 +1,13 @@
+<table>
+    <entry>
+        <rat>LTE</rat>
+        <band>27</band>
+        <powerCapDbm>-50</powerCapDbm>
+        <params>
+            <harmonicParams2g>
+                <N>3</N>
+                <overlap>100</overlap>
+            </harmonicParams2g>
+        </params>
+    </entry>
+</table>
diff --git a/service/tests/wifitests/assets/coex_lte_40_neighboring.xml b/service/tests/wifitests/assets/coex_lte_40_neighboring.xml
new file mode 100644
index 0000000..a667a59
--- /dev/null
+++ b/service/tests/wifitests/assets/coex_lte_40_neighboring.xml
@@ -0,0 +1,17 @@
+<table>
+    <entry>
+        <rat>LTE</rat>
+        <band>40</band>
+        <powerCapDbm>-50</powerCapDbm>
+        <params>
+            <neighborThresholds>
+                <wifiVictimMhz>20</wifiVictimMhz>
+                <cellVictimMhz>20</cellVictimMhz>
+            </neighborThresholds>
+            <defaultChannels>
+                <default2g>6</default2g>
+                <default5g>36</default5g>
+            </defaultChannels>
+        </params>
+    </entry>
+</table>
diff --git a/service/tests/wifitests/assets/coex_lte_40_override.xml b/service/tests/wifitests/assets/coex_lte_40_override.xml
new file mode 100644
index 0000000..99ab912
--- /dev/null
+++ b/service/tests/wifitests/assets/coex_lte_40_override.xml
@@ -0,0 +1,18 @@
+<table>
+    <entry>
+        <rat>LTE</rat>
+        <band>40</band>
+        <override>
+            <override2g>
+                <channel>6</channel>
+                <channel>11</channel>
+            </override2g>
+            <override5g>
+                <category>20Mhz</category>
+                <category>40Mhz</category>
+                <channel>50</channel>
+                <channel>114</channel>
+            </override5g>
+        </override>
+    </entry>
+</table>
diff --git a/service/tests/wifitests/assets/coex_lte_46_neighboring.xml b/service/tests/wifitests/assets/coex_lte_46_neighboring.xml
new file mode 100644
index 0000000..5f061fb
--- /dev/null
+++ b/service/tests/wifitests/assets/coex_lte_46_neighboring.xml
@@ -0,0 +1,17 @@
+<table>
+    <entry>
+        <rat>LTE</rat>
+        <band>46</band>
+        <powerCapDbm>-50</powerCapDbm>
+        <params>
+            <neighborThresholds>
+                <wifiVictimMhz>20</wifiVictimMhz>
+                <cellVictimMhz>20</cellVictimMhz>
+            </neighborThresholds>
+            <defaultChannels>
+                <default2g>6</default2g>
+                <default5g>36</default5g>
+            </defaultChannels>
+        </params>
+    </entry>
+</table>
diff --git a/service/tests/wifitests/assets/coex_lte_7_intermod.xml b/service/tests/wifitests/assets/coex_lte_7_intermod.xml
new file mode 100644
index 0000000..5eb6ee1
--- /dev/null
+++ b/service/tests/wifitests/assets/coex_lte_7_intermod.xml
@@ -0,0 +1,14 @@
+<table>
+    <entry>
+        <rat>LTE</rat>
+        <band>7</band>
+        <powerCapDbm>-50</powerCapDbm>
+        <params>
+            <intermodParams2g>
+                <N>2</N>
+                <M>-1</M>
+                <overlap>100</overlap>
+            </intermodParams2g>
+        </params>
+    </entry>
+</table>
diff --git a/service/tests/wifitests/assets/coex_malformed.xml b/service/tests/wifitests/assets/coex_malformed.xml
new file mode 100644
index 0000000..74b55e6
--- /dev/null
+++ b/service/tests/wifitests/assets/coex_malformed.xml
@@ -0,0 +1,13 @@
+<table>
+    <entry>
+        <rat>LTE</rat>
+        <band>40</band>
+        <powerCapDbm>-50</powerCapDbm>
+        <params>
+            <neighborThresholds>
+                <wifiVictimMhz>20</wifiVictimMhz>
+                <cellVictimMhz>20</cellVictimMhz>
+            </neighborThresholds>
+        </params>
+    <!-- Entry is not closed -->
+</table>
diff --git a/service/tests/wifitests/coverage.sh b/service/tests/wifitests/coverage.sh
index b2b37c1..c0f7e38 100755
--- a/service/tests/wifitests/coverage.sh
+++ b/service/tests/wifitests/coverage.sh
@@ -66,7 +66,8 @@
   m FrameworksWifiTests jacoco-cli
 END_OF_BUILD_SCRIPT
 
-APK_NAME="$(find $BUILD_OUT_DIR/target -name FrameworksWifiTests.apk)"
+APK_NAME="$(find $BUILD_OUT_DIR/target/product -name FrameworksWifiTests.apk | \
+              grep -v /symbols/)"
 REPORTER_JAR="$(find $BUILD_OUT_DIR/host -name jacoco-cli.jar)"
 
 echo "Running tests and generating coverage report"
diff --git a/service/tests/wifitests/src/com/android/server/wifi/ActiveModeWardenTest.java b/service/tests/wifitests/src/com/android/server/wifi/ActiveModeWardenTest.java
index 2f13d09..1b32b8d 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/ActiveModeWardenTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/ActiveModeWardenTest.java
@@ -16,22 +16,31 @@
 
 package com.android.server.wifi;
 
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_LOCAL_ONLY;
 import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_PRIMARY;
 import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SCAN_ONLY;
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SECONDARY_LONG_LIVED;
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SECONDARY_TRANSIENT;
 import static com.android.server.wifi.ActiveModeManager.ROLE_SOFTAP_LOCAL_ONLY;
 import static com.android.server.wifi.ActiveModeManager.ROLE_SOFTAP_TETHERED;
+import static com.android.server.wifi.ActiveModeWarden.INTERNAL_REQUESTOR_WS;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.mockingDetails;
@@ -43,26 +52,38 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
+import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.res.Resources;
 import android.location.LocationManager;
+import android.net.wifi.ISubsystemRestartCallback;
+import android.net.wifi.IWifiConnectedNetworkScorer;
 import android.net.wifi.SoftApCapability;
 import android.net.wifi.SoftApConfiguration;
 import android.net.wifi.SoftApConfiguration.Builder;
 import android.net.wifi.SoftApInfo;
 import android.net.wifi.WifiClient;
+import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
 import android.os.BatteryStatsManager;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.WorkSource;
 import android.os.test.TestLooper;
+import android.telephony.TelephonyManager;
 import android.util.Log;
 
 import androidx.test.filters.SmallTest;
 
-import com.android.server.wifi.WifiNative.InterfaceAvailableForRequestListener;
-import com.android.server.wifi.util.GeneralUtil;
+import com.android.server.wifi.ActiveModeManager.ClientConnectivityRole;
+import com.android.server.wifi.ActiveModeManager.Listener;
+import com.android.server.wifi.ActiveModeManager.SoftApRole;
+import com.android.server.wifi.ActiveModeWarden.ExternalClientModeManagerRequestListener;
+import com.android.server.wifi.util.GeneralUtil.Mutable;
 import com.android.server.wifi.util.WifiPermissionsUtil;
 import com.android.wifi.resources.R;
 
@@ -71,15 +92,17 @@
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
 import java.io.ByteArrayOutputStream;
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
 /**
@@ -91,11 +114,21 @@
 
     private static final String ENABLED_STATE_STRING = "EnabledState";
     private static final String DISABLED_STATE_STRING = "DisabledState";
+    private static final String TEST_SSID_1 = "\"Ssid12345\"";
+    private static final String TEST_SSID_2 = "\"Ssid45678\"";
+    private static final String TEST_SSID_3 = "\"Ssid98765\"";
+    private static final String TEST_BSSID_1 = "01:12:23:34:45:56";
+    private static final String TEST_BSSID_2 = "10:21:32:43:54:65";
+    private static final String TEST_BSSID_3 = "11:22:33:44:55:66";
 
     private static final String WIFI_IFACE_NAME = "mockWlan";
+    private static final String WIFI_IFACE_NAME_1 = "mockWlan1";
     private static final int TEST_WIFI_RECOVERY_DELAY_MS = 2000;
     private static final int TEST_AP_FREQUENCY = 2412;
     private static final int TEST_AP_BANDWIDTH = SoftApInfo.CHANNEL_WIDTH_20MHZ;
+    private static final int TEST_UID = 435546654;
+    private static final String TEST_PACKAGE = "com.test";
+    private static final WorkSource TEST_WORKSOURCE = new WorkSource(TEST_UID, TEST_PACKAGE);
 
     TestLooper mLooper;
     @Mock WifiInjector mWifiInjector;
@@ -103,35 +136,41 @@
     @Mock Resources mResources;
     @Mock WifiNative mWifiNative;
     @Mock WifiApConfigStore mWifiApConfigStore;
-    @Mock ClientModeManager mClientModeManager;
+    @Mock ConcreteClientModeManager mClientModeManager;
     @Mock SoftApManager mSoftApManager;
-    @Mock DefaultModeManager mDefaultModeManager;
+    @Mock DefaultClientModeManager mDefaultClientModeManager;
     @Mock BatteryStatsManager mBatteryStats;
     @Mock SelfRecovery mSelfRecovery;
-    @Mock BaseWifiDiagnostics mWifiDiagnostics;
+    @Mock WifiDiagnostics mWifiDiagnostics;
     @Mock ScanRequestProxy mScanRequestProxy;
-    @Mock ClientModeImpl mClientModeImpl;
     @Mock FrameworkFacade mFacade;
     @Mock WifiSettingsStore mSettingsStore;
     @Mock WifiPermissionsUtil mWifiPermissionsUtil;
     @Mock SoftApCapability mSoftApCapability;
+    @Mock ActiveModeWarden.ModeChangeCallback mModeChangeCallback;
+    @Mock ActiveModeWarden.PrimaryClientModeManagerChangedCallback mPrimaryChangedCallback;
+    @Mock WifiMetrics mWifiMetrics;
+    @Mock ISubsystemRestartCallback mSubsystemRestartCallback;
+    @Mock ExternalScoreUpdateObserverProxy mExternalScoreUpdateObserverProxy;
+    @Mock DppManager mDppManager;
+    @Mock SarManager mSarManager;
+    @Mock HalDeviceManager mHalDeviceManager;
 
-    ActiveModeManager.Listener mClientListener;
-    ActiveModeManager.Listener mSoftApListener;
-    WifiManager.SoftApCallback mSoftApManagerCallback;
+    Listener<ConcreteClientModeManager> mClientListener;
+    Listener<SoftApManager> mSoftApListener;
+    WifiServiceImpl.SoftApCallbackInternal mSoftApManagerCallback;
     SoftApModeConfiguration mSoftApConfig;
-    @Mock WifiManager.SoftApCallback mSoftApStateMachineCallback;
-    @Mock WifiManager.SoftApCallback mLohsStateMachineCallback;
+    @Mock WifiServiceImpl.SoftApCallbackInternal mSoftApStateMachineCallback;
+    @Mock WifiServiceImpl.SoftApCallbackInternal mLohsStateMachineCallback;
     WifiNative.StatusListener mWifiNativeStatusListener;
     ActiveModeWarden mActiveModeWarden;
     private SoftApInfo mTestSoftApInfo;
 
     final ArgumentCaptor<WifiNative.StatusListener> mStatusListenerCaptor =
             ArgumentCaptor.forClass(WifiNative.StatusListener.class);
-    final ArgumentCaptor<InterfaceAvailableForRequestListener> mClientIfaceAvailableListener =
-            ArgumentCaptor.forClass(InterfaceAvailableForRequestListener.class);
-    final ArgumentCaptor<InterfaceAvailableForRequestListener> mSoftApIfaceAvailableListener =
-            ArgumentCaptor.forClass(InterfaceAvailableForRequestListener.class);
+
+    private BroadcastReceiver mEmergencyCallbackModeChangedBr;
+    private BroadcastReceiver mEmergencyCallStateChangedBr;
 
     /**
      * Set up the test environment.
@@ -144,7 +183,10 @@
         mLooper = new TestLooper();
 
         when(mWifiInjector.getScanRequestProxy()).thenReturn(mScanRequestProxy);
+        when(mWifiInjector.getSarManager()).thenReturn(mSarManager);
+        when(mWifiInjector.getHalDeviceManager()).thenReturn(mHalDeviceManager);
         when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_PRIMARY);
+        when(mClientModeManager.getInterfaceName()).thenReturn(WIFI_IFACE_NAME);
         when(mContext.getResources()).thenReturn(mResources);
         when(mSoftApManager.getRole()).thenReturn(ROLE_SOFTAP_TETHERED);
 
@@ -154,47 +196,70 @@
                 .thenReturn(TEST_WIFI_RECOVERY_DELAY_MS);
         when(mResources.getBoolean(R.bool.config_wifiScanHiddenNetworksScanOnlyMode))
                 .thenReturn(false);
+        when(mResources.getBoolean(R.bool.config_wifi_turn_off_during_emergency_call))
+                .thenReturn(true);
 
         when(mSettingsStore.isWifiToggleEnabled()).thenReturn(false);
         when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
         when(mSettingsStore.isScanAlwaysAvailable()).thenReturn(false);
         when(mWifiPermissionsUtil.isLocationModeEnabled()).thenReturn(true);
+        when(mFacade.getSettingsWorkSource(mContext)).thenReturn(TEST_WORKSOURCE);
 
         doAnswer(new Answer<ClientModeManager>() {
             public ClientModeManager answer(InvocationOnMock invocation) {
                 Object[] args = invocation.getArguments();
-                mClientListener = (ClientModeManager.Listener) args[0];
+                mClientListener = (Listener<ConcreteClientModeManager>) args[0];
                 return mClientModeManager;
             }
-        }).when(mWifiInjector).makeClientModeManager(any(ActiveModeManager.Listener.class));
+        }).when(mWifiInjector).makeClientModeManager(
+                any(Listener.class), any(), any(), anyBoolean());
         doAnswer(new Answer<SoftApManager>() {
             public SoftApManager answer(InvocationOnMock invocation) {
                 Object[] args = invocation.getArguments();
-                mSoftApListener = (ActiveModeManager.Listener) args[0];
-                mSoftApManagerCallback = (WifiManager.SoftApCallback) args[1];
+                mSoftApListener = (Listener<SoftApManager>) args[0];
+                mSoftApManagerCallback = (WifiServiceImpl.SoftApCallbackInternal) args[1];
                 mSoftApConfig = (SoftApModeConfiguration) args[2];
                 return mSoftApManager;
             }
-        }).when(mWifiInjector).makeSoftApManager(any(ActiveModeManager.Listener.class),
-                any(WifiManager.SoftApCallback.class), any());
+        }).when(mWifiInjector).makeSoftApManager(any(Listener.class),
+                any(WifiServiceImpl.SoftApCallbackInternal.class), any(), any(), any(),
+                anyBoolean());
+        when(mWifiNative.initialize()).thenReturn(true);
+        when(mWifiPermissionsUtil.isSystem(TEST_PACKAGE, TEST_UID)).thenReturn(true);
 
         mActiveModeWarden = createActiveModeWarden();
         mActiveModeWarden.start();
         mLooper.dispatchAll();
 
-        verify(mWifiNative).registerStatusListener(mStatusListenerCaptor.capture());
-        mWifiNativeStatusListener = mStatusListenerCaptor.getValue();
+        verify(mWifiMetrics).noteWifiEnabledDuringBoot(false);
 
-        verify(mWifiNative).registerClientInterfaceAvailabilityListener(
-                mClientIfaceAvailableListener.capture());
-        verify(mWifiNative).registerSoftApInterfaceAvailabilityListener(
-                mSoftApIfaceAvailableListener.capture());
+        verify(mWifiNative).registerStatusListener(mStatusListenerCaptor.capture());
+        verify(mWifiNative).initialize();
+        mWifiNativeStatusListener = mStatusListenerCaptor.getValue();
 
         mActiveModeWarden.registerSoftApCallback(mSoftApStateMachineCallback);
         mActiveModeWarden.registerLohsCallback(mLohsStateMachineCallback);
+        mActiveModeWarden.registerModeChangeCallback(mModeChangeCallback);
+        mActiveModeWarden.registerPrimaryClientModeManagerChangedCallback(mPrimaryChangedCallback);
+        when(mSubsystemRestartCallback.asBinder()).thenReturn(Mockito.mock(IBinder.class));
+        mActiveModeWarden.registerSubsystemRestartCallback(mSubsystemRestartCallback);
         mTestSoftApInfo = new SoftApInfo();
         mTestSoftApInfo.setFrequency(TEST_AP_FREQUENCY);
         mTestSoftApInfo.setBandwidth(TEST_AP_BANDWIDTH);
+
+        ArgumentCaptor<BroadcastReceiver> bcastRxCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mContext).registerReceiver(
+                bcastRxCaptor.capture(),
+                argThat(filter ->
+                        filter.hasAction(TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED)));
+        mEmergencyCallbackModeChangedBr = bcastRxCaptor.getValue();
+
+        verify(mContext).registerReceiver(
+                bcastRxCaptor.capture(),
+                argThat(filter ->
+                        filter.hasAction(TelephonyManager.ACTION_EMERGENCY_CALL_STATE_CHANGED)));
+        mEmergencyCallStateChangedBr = bcastRxCaptor.getValue();
     }
 
     private ActiveModeWarden createActiveModeWarden() {
@@ -202,14 +267,16 @@
                 mWifiInjector,
                 mLooper.getLooper(),
                 mWifiNative,
-                mDefaultModeManager,
+                mDefaultClientModeManager,
                 mBatteryStats,
                 mWifiDiagnostics,
                 mContext,
-                mClientModeImpl,
                 mSettingsStore,
                 mFacade,
-                mWifiPermissionsUtil);
+                mWifiPermissionsUtil,
+                mWifiMetrics,
+                mExternalScoreUpdateObserverProxy,
+                mDppManager);
         // SelfRecovery is created in WifiInjector after ActiveModeWarden, so getSelfRecovery()
         // returns null when constructing ActiveModeWarden.
         when(mWifiInjector.getSelfRecovery()).thenReturn(mSelfRecovery);
@@ -225,50 +292,91 @@
         mLooper.dispatchAll();
     }
 
+    private void emergencyCallbackModeChanged(boolean enabled) {
+        Intent intent = new Intent(TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
+        intent.putExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, enabled);
+        mEmergencyCallbackModeChangedBr.onReceive(mContext, intent);
+    }
+
+    private void emergencyCallStateChanged(boolean enabled) {
+        Intent intent = new Intent(TelephonyManager.ACTION_EMERGENCY_CALL_STATE_CHANGED);
+        intent.putExtra(TelephonyManager.EXTRA_PHONE_IN_EMERGENCY_CALL, enabled);
+        mEmergencyCallStateChangedBr.onReceive(mContext, intent);
+    }
+
+    private void enterClientModeActiveState() throws Exception {
+        enterClientModeActiveState(false);
+    }
+
     /**
      * Helper method to enter the EnabledState and set ClientModeManager in ConnectMode.
+     * @param isClientModeSwitch true if switching from another mode, false if creating a new one
      */
-    private void enterClientModeActiveState() throws Exception {
+    private void enterClientModeActiveState(boolean isClientModeSwitch) throws Exception {
         String fromState = mActiveModeWarden.getCurrentMode();
-        when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_PRIMARY);
         when(mSettingsStore.isWifiToggleEnabled()).thenReturn(true);
-        mActiveModeWarden.wifiToggled();
+        mActiveModeWarden.wifiToggled(TEST_WORKSOURCE);
         mLooper.dispatchAll();
-        mClientListener.onStarted();
+        when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_PRIMARY);
+        // ClientModeManager starts in SCAN_ONLY role.
+        mClientListener.onRoleChanged(mClientModeManager);
         mLooper.dispatchAll();
 
         assertInEnabledState();
-        verify(mClientModeManager).start();
-        verify(mClientModeManager).setRole(ROLE_CLIENT_PRIMARY);
+        if (!isClientModeSwitch) {
+            verify(mWifiInjector).makeClientModeManager(
+                    any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
+        } else {
+            verify(mClientModeManager).setRole(ROLE_CLIENT_PRIMARY, TEST_WORKSOURCE);
+        }
         verify(mScanRequestProxy).enableScanning(true, true);
         if (fromState.equals(DISABLED_STATE_STRING)) {
             verify(mBatteryStats).reportWifiOn();
         }
+        assertEquals(mClientModeManager, mActiveModeWarden.getPrimaryClientModeManager());
+        verify(mModeChangeCallback).onActiveModeManagerRoleChanged(mClientModeManager);
+    }
+
+    private void enterScanOnlyModeActiveState() throws Exception {
+        enterScanOnlyModeActiveState(false);
     }
 
     /**
      * Helper method to enter the EnabledState and set ClientModeManager in ScanOnlyMode.
      */
-    private void enterScanOnlyModeActiveState() throws Exception {
+    private void enterScanOnlyModeActiveState(boolean isClientModeSwitch) throws Exception {
         String fromState = mActiveModeWarden.getCurrentMode();
-        when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_SCAN_ONLY);
         when(mWifiPermissionsUtil.isLocationModeEnabled()).thenReturn(true);
         when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
         when(mSettingsStore.isScanAlwaysAvailable()).thenReturn(true);
         when(mSettingsStore.isWifiToggleEnabled()).thenReturn(false);
-        mActiveModeWarden.wifiToggled();
+        mActiveModeWarden.wifiToggled(TEST_WORKSOURCE);
         mLooper.dispatchAll();
-        mClientListener.onStarted();
-        mLooper.dispatchAll();
+        when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_SCAN_ONLY);
 
+        if (!isClientModeSwitch) {
+            mClientListener.onStarted(mClientModeManager);
+            mLooper.dispatchAll();
+            verify(mWifiInjector).makeClientModeManager(
+                    any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_SCAN_ONLY), anyBoolean());
+            verify(mModeChangeCallback).onActiveModeManagerAdded(mClientModeManager);
+        } else {
+            mClientListener.onRoleChanged(mClientModeManager);
+            mLooper.dispatchAll();
+            verify(mClientModeManager).setRole(ROLE_CLIENT_SCAN_ONLY, INTERNAL_REQUESTOR_WS);
+            // If switching from client mode back to scan only mode, role change would have been
+            // called once before when transitioning from scan only mode to client mode.
+            // Verify that it was called again.
+            verify(mModeChangeCallback, times(2))
+                    .onActiveModeManagerRoleChanged(mClientModeManager);
+        }
         assertInEnabledState();
-        verify(mClientModeManager).start();
-        verify(mClientModeManager).setRole(ROLE_CLIENT_SCAN_ONLY);
         verify(mScanRequestProxy).enableScanning(true, false);
         if (fromState.equals(DISABLED_STATE_STRING)) {
             verify(mBatteryStats).reportWifiOn();
         }
         verify(mBatteryStats).reportWifiState(BatteryStatsManager.WIFI_STATE_OFF_SCANNING, null);
+        assertEquals(mClientModeManager, mActiveModeWarden.getScanOnlyClientModeManager());
     }
 
     private void enterSoftApActiveMode() throws Exception {
@@ -277,6 +385,8 @@
                 mSoftApCapability));
     }
 
+    private int mTimesCreatedSoftApManager = 1;
+
     /**
      * Helper method to activate SoftApManager.
      *
@@ -284,21 +394,31 @@
      */
     private void enterSoftApActiveMode(SoftApModeConfiguration softApConfig) throws Exception {
         String fromState = mActiveModeWarden.getCurrentMode();
-        int softApRole = softApConfig.getTargetMode() == WifiManager.IFACE_IP_MODE_TETHERED
+        SoftApRole softApRole = softApConfig.getTargetMode() == WifiManager.IFACE_IP_MODE_TETHERED
                 ? ROLE_SOFTAP_TETHERED : ROLE_SOFTAP_LOCAL_ONLY;
-        when(mSoftApManager.getRole()).thenReturn(softApRole);
-        mActiveModeWarden.startSoftAp(softApConfig);
+        mActiveModeWarden.startSoftAp(softApConfig, TEST_WORKSOURCE);
         mLooper.dispatchAll();
-        mSoftApListener.onStarted();
+        when(mSoftApManager.getRole()).thenReturn(softApRole);
+        when(mSoftApManager.getSoftApModeConfiguration()).thenReturn(softApConfig);
+        mSoftApListener.onStarted(mSoftApManager);
         mLooper.dispatchAll();
 
         assertInEnabledState();
         assertThat(softApConfig).isEqualTo(mSoftApConfig);
-        verify(mSoftApManager).start();
-        verify(mSoftApManager).setRole(softApRole);
+        verify(mWifiInjector, times(mTimesCreatedSoftApManager)).makeSoftApManager(
+                any(), any(), any(), eq(TEST_WORKSOURCE), eq(softApRole), anyBoolean());
+        mTimesCreatedSoftApManager++;
         if (fromState.equals(DISABLED_STATE_STRING)) {
             verify(mBatteryStats).reportWifiOn();
         }
+        if (softApRole == ROLE_SOFTAP_TETHERED) {
+            assertEquals(mSoftApManager, mActiveModeWarden.getTetheredSoftApManager());
+            assertNull(mActiveModeWarden.getLocalOnlySoftApManager());
+        } else {
+            assertEquals(mSoftApManager, mActiveModeWarden.getLocalOnlySoftApManager());
+            assertNull(mActiveModeWarden.getTetheredSoftApManager());
+        }
+        verify(mModeChangeCallback).onActiveModeManagerAdded(mSoftApManager);
     }
 
     private void enterStaDisabledMode(boolean isSoftApModeManagerActive) {
@@ -306,11 +426,12 @@
         when(mSettingsStore.isWifiToggleEnabled()).thenReturn(false);
         when(mWifiPermissionsUtil.isLocationModeEnabled()).thenReturn(false);
         when(mSettingsStore.isScanAlwaysAvailable()).thenReturn(false);
-        mActiveModeWarden.wifiToggled();
+        mActiveModeWarden.wifiToggled(TEST_WORKSOURCE);
         mLooper.dispatchAll();
         if (mClientListener != null) {
-            mClientListener.onStopped();
+            mClientListener.onStopped(mClientModeManager);
             mLooper.dispatchAll();
+            verify(mModeChangeCallback).onActiveModeManagerRemoved(mClientModeManager);
         }
 
         if (isSoftApModeManagerActive) {
@@ -321,6 +442,8 @@
         if (fromState.equals(ENABLED_STATE_STRING)) {
             verify(mScanRequestProxy).enableScanning(false, false);
         }
+        // Ensure we return the default client mode manager when wifi is off.
+        assertEquals(mDefaultClientModeManager, mActiveModeWarden.getPrimaryClientModeManager());
     }
 
     private void shutdownWifi() {
@@ -343,6 +466,10 @@
         assertThat(mActiveModeWarden.isInEmergencyMode()).isTrue();
     }
 
+    private void assertNotInEmergencyMode() {
+        assertThat(mActiveModeWarden.isInEmergencyMode()).isFalse();
+    }
+
     /**
      * Counts the number of times a void method was called on a mock.
      *
@@ -481,14 +608,14 @@
         when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_SCAN_ONLY);
         when(mSettingsStore.isScanAlwaysAvailable()).thenReturn(true);
         when(mSettingsStore.isWifiToggleEnabled()).thenReturn(false);
-        mActiveModeWarden.wifiToggled();
+        mActiveModeWarden.wifiToggled(TEST_WORKSOURCE);
         mLooper.dispatchAll();
-        mClientListener.onStarted();
+        mClientListener.onStarted(mClientModeManager);
         mLooper.dispatchAll();
 
         assertInEnabledState();
-        verify(mClientModeManager).start();
-        verify(mClientModeManager).setRole(ROLE_CLIENT_SCAN_ONLY);
+        verify(mWifiInjector).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_SCAN_ONLY), anyBoolean());
         verify(mScanRequestProxy).enableScanning(true, true);
     }
 
@@ -522,7 +649,7 @@
         when(mSettingsStore.isScanAlwaysAvailable()).thenReturn(false);
         mActiveModeWarden.scanAlwaysModeChanged();
         mLooper.dispatchAll();
-        mClientListener.onStopped();
+        mClientListener.onStopped(mClientModeManager);
         mLooper.dispatchAll();
 
         verify(mClientModeManager).stop();
@@ -538,7 +665,7 @@
         enterSoftApActiveMode();
         enterScanOnlyModeActiveState();
 
-        reset(mDefaultModeManager);
+        reset(mDefaultClientModeManager);
         enterStaDisabledMode(true);
         verify(mSoftApManager, never()).stop();
         verify(mBatteryStats, never()).reportWifiOff();
@@ -552,10 +679,24 @@
         enterScanOnlyModeActiveState();
 
         reset(mBatteryStats, mScanRequestProxy);
-        enterClientModeActiveState();
+        enterClientModeActiveState(true);
         mLooper.dispatchAll();
-        verify(mClientModeManager).setRole(ROLE_CLIENT_PRIMARY);
-        assertInEnabledState();
+    }
+
+    /**
+     * Test that we can switch from the EnabledState (in ConnectMode) to another mode.
+     */
+    @Test
+    public void testSwitchModeWhenConnectModeActiveState() throws Exception {
+        enterClientModeActiveState();
+
+        verify(mPrimaryChangedCallback).onChange(null, mClientModeManager);
+
+        reset(mBatteryStats, mScanRequestProxy);
+        enterScanOnlyModeActiveState(true);
+        mLooper.dispatchAll();
+
+        verify(mPrimaryChangedCallback).onChange(mClientModeManager, null);
     }
 
     /**
@@ -564,13 +705,15 @@
     @Test
     public void testReenterClientModeActiveStateIsNop() throws Exception {
         enterClientModeActiveState();
-        verify(mClientModeManager, times(1)).start();
+        verify(mWifiInjector, times(1)).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
 
         when(mSettingsStore.isWifiToggleEnabled()).thenReturn(true);
-        mActiveModeWarden.wifiToggled();
+        mActiveModeWarden.wifiToggled(TEST_WORKSOURCE);
         mLooper.dispatchAll();
         // Should not start again.
-        verify(mClientModeManager, times(1)).start();
+        verify(mWifiInjector, times(1)).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
     }
 
     /**
@@ -597,11 +740,12 @@
     public void testEnterSoftApModeActiveWhenAlreadyInSoftApMode() throws Exception {
         enterSoftApActiveMode();
         // now inject failure through the SoftApManager.Listener
-        mSoftApListener.onStartFailure();
+        mSoftApListener.onStartFailure(mSoftApManager);
         mLooper.dispatchAll();
+        verify(mModeChangeCallback).onActiveModeManagerRemoved(mSoftApManager);
         assertInDisabledState();
         // clear the first call to start SoftApManager
-        reset(mSoftApManager, mBatteryStats);
+        reset(mSoftApManager, mBatteryStats, mModeChangeCallback);
 
         enterSoftApActiveMode();
     }
@@ -614,8 +758,9 @@
     public void testScanOnlyModeFailureWhenActive() throws Exception {
         enterScanOnlyModeActiveState();
         // now inject a failure through the ScanOnlyModeManager.Listener
-        mClientListener.onStartFailure();
+        mClientListener.onStartFailure(mClientModeManager);
         mLooper.dispatchAll();
+        verify(mModeChangeCallback).onActiveModeManagerRemoved(mClientModeManager);
         assertInDisabledState();
         verify(mBatteryStats).reportWifiOff();
     }
@@ -628,8 +773,9 @@
     public void testSoftApFailureWhenActive() throws Exception {
         enterSoftApActiveMode();
         // now inject failure through the SoftApManager.Listener
-        mSoftApListener.onStartFailure();
+        mSoftApListener.onStartFailure(mSoftApManager);
         mLooper.dispatchAll();
+        verify(mModeChangeCallback).onActiveModeManagerRemoved(mSoftApManager);
         verify(mBatteryStats).reportWifiOff();
     }
 
@@ -642,7 +788,7 @@
         enterScanOnlyModeActiveState();
 
         // now inject the stop message through the ScanOnlyModeManager.Listener
-        mClientListener.onStopped();
+        mClientListener.onStopped(mClientModeManager);
         mLooper.dispatchAll();
 
         assertInDisabledState();
@@ -657,8 +803,9 @@
         enterSoftApActiveMode();
         reset(mWifiNative);
         // now inject failure through the SoftApManager.Listener
-        mSoftApListener.onStartFailure();
+        mSoftApListener.onStartFailure(mSoftApManager);
         mLooper.dispatchAll();
+        verify(mModeChangeCallback).onActiveModeManagerRemoved(mSoftApManager);
         verify(mBatteryStats).reportWifiOff();
         verifyNoMoreInteractions(mWifiNative);
     }
@@ -670,7 +817,7 @@
     public void callsWifiServiceCallbackOnSoftApStateChanged() throws Exception {
         enterSoftApActiveMode();
 
-        mSoftApListener.onStarted();
+        mSoftApListener.onStarted(mSoftApManager);
         mSoftApManagerCallback.onStateChanged(WifiManager.WIFI_AP_STATE_ENABLED, 0);
         mLooper.dispatchAll();
 
@@ -686,38 +833,29 @@
         enterSoftApActiveMode(new SoftApModeConfiguration(
                 WifiManager.IFACE_IP_MODE_LOCAL_ONLY, null, mSoftApCapability));
 
-        mSoftApListener.onStarted();
+        mSoftApListener.onStarted(mSoftApManager);
         mSoftApManagerCallback.onStateChanged(WifiManager.WIFI_AP_STATE_ENABLED, 0);
         mLooper.dispatchAll();
 
         verify(mSoftApStateMachineCallback, never()).onStateChanged(anyInt(), anyInt());
-        verify(mSoftApStateMachineCallback, never()).onConnectedClientsChanged(any());
-        verify(mSoftApStateMachineCallback, never()).onInfoChanged(any());
+        verify(mSoftApStateMachineCallback, never()).onConnectedClientsOrInfoChanged(any(),
+                any(), anyBoolean());
     }
 
     /**
-     * Verifies that NumClientsChanged event is being passed from SoftApManager to WifiServiceImpl
+     * Verifies that ConnectedClientsOrInfoChanged event is being passed from SoftApManager
+     * to WifiServiceImpl
      */
     @Test
     public void callsWifiServiceCallbackOnSoftApConnectedClientsChanged() throws Exception {
-        final List<WifiClient> testClients = new ArrayList();
+        final Map<String, List<WifiClient>> testClients = new HashMap();
+        final Map<String, SoftApInfo> testInfos = new HashMap();
         enterSoftApActiveMode();
-        mSoftApManagerCallback.onConnectedClientsChanged(testClients);
+        mSoftApManagerCallback.onConnectedClientsOrInfoChanged(testInfos, testClients, false);
         mLooper.dispatchAll();
 
-        verify(mSoftApStateMachineCallback).onConnectedClientsChanged(testClients);
-    }
-
-    /**
-     * Verifies that SoftApInfoChanged event is being passed from SoftApManager to WifiServiceImpl
-     */
-    @Test
-    public void callsWifiServiceCallbackOnSoftApInfoChanged() throws Exception {
-        enterSoftApActiveMode();
-        mSoftApManagerCallback.onInfoChanged(mTestSoftApInfo);
-        mLooper.dispatchAll();
-
-        verify(mSoftApStateMachineCallback).onInfoChanged(mTestSoftApInfo);
+        verify(mSoftApStateMachineCallback).onConnectedClientsOrInfoChanged(
+                testInfos, testClients, false);
     }
 
     /**
@@ -728,7 +866,7 @@
     public void testScanOnlyModeStaysActiveOnEnabledUpdate() throws Exception {
         enterScanOnlyModeActiveState();
         // now inject success through the Listener
-        mClientListener.onStarted();
+        mClientListener.onStarted(mClientModeManager);
         mLooper.dispatchAll();
         assertInEnabledState();
         verify(mClientModeManager, never()).stop();
@@ -780,33 +918,35 @@
         doAnswer(new Answer<SoftApManager>() {
             public SoftApManager answer(InvocationOnMock invocation) {
                 Object[] args = invocation.getArguments();
-                mSoftApListener = (ActiveModeManager.Listener) args[0];
+                mSoftApListener = (Listener<SoftApManager>) args[0];
                 return mSoftApManager;
             }
-        }).when(mWifiInjector).makeSoftApManager(any(ActiveModeManager.Listener.class),
-                any(WifiManager.SoftApCallback.class), eq(softApConfig1));
+        }).when(mWifiInjector).makeSoftApManager(any(Listener.class),
+                any(WifiServiceImpl.SoftApCallbackInternal.class), eq(softApConfig1), any(), any(),
+                anyBoolean());
         // make a second softap manager
         SoftApManager softapManager = mock(SoftApManager.class);
-        GeneralUtil.Mutable<ActiveModeManager.Listener> softApListener =
-                new GeneralUtil.Mutable<>();
+        Mutable<Listener<SoftApManager>> softApListener =
+                new Mutable<>();
         doAnswer(new Answer<SoftApManager>() {
             public SoftApManager answer(InvocationOnMock invocation) {
                 Object[] args = invocation.getArguments();
-                softApListener.value = (ActiveModeManager.Listener) args[0];
+                softApListener.value = (Listener<SoftApManager>) args[0];
                 return softapManager;
             }
-        }).when(mWifiInjector).makeSoftApManager(any(ActiveModeManager.Listener.class),
-                any(WifiManager.SoftApCallback.class), eq(softApConfig2));
+        }).when(mWifiInjector).makeSoftApManager(any(Listener.class),
+                any(WifiServiceImpl.SoftApCallbackInternal.class), eq(softApConfig2), any(), any(),
+                anyBoolean());
 
-        mActiveModeWarden.startSoftAp(softApConfig1);
+        mActiveModeWarden.startSoftAp(softApConfig1, TEST_WORKSOURCE);
         mLooper.dispatchAll();
-        mSoftApListener.onStarted();
-        mActiveModeWarden.startSoftAp(softApConfig2);
+        mSoftApListener.onStarted(mSoftApManager);
+        mActiveModeWarden.startSoftAp(softApConfig2, TEST_WORKSOURCE);
         mLooper.dispatchAll();
-        softApListener.value.onStarted();
+        softApListener.value.onStarted(softapManager);
 
-        verify(mSoftApManager).start();
-        verify(softapManager).start();
+        verify(mWifiInjector, times(2)).makeSoftApManager(
+                any(), any(), any(), eq(TEST_WORKSOURCE), eq(ROLE_SOFTAP_TETHERED), anyBoolean());
         verify(mBatteryStats).reportWifiOn();
     }
 
@@ -827,7 +967,7 @@
     public void handleWifiNativeFailureDeviceNotShuttingDown() throws Exception {
         mWifiNativeStatusListener.onStatusChanged(false);
         mLooper.dispatchAll();
-        verify(mWifiDiagnostics).captureBugReportData(
+        verify(mWifiDiagnostics).triggerBugReportDataCapture(
                 WifiDiagnostics.REPORT_REASON_WIFINATIVE_FAILURE);
         verify(mSelfRecovery).trigger(eq(SelfRecovery.REASON_WIFINATIVE_FAILURE));
     }
@@ -840,7 +980,7 @@
         mActiveModeWarden.notifyShuttingDown();
         mWifiNativeStatusListener.onStatusChanged(false);
         mLooper.dispatchAll();
-        verify(mWifiDiagnostics, never()).captureBugReportData(
+        verify(mWifiDiagnostics, never()).triggerBugReportDataCapture(
                 WifiDiagnostics.REPORT_REASON_WIFINATIVE_FAILURE);
         verify(mSelfRecovery, never()).trigger(eq(SelfRecovery.REASON_WIFINATIVE_FAILURE));
     }
@@ -852,7 +992,7 @@
     public void handleWifiNativeStatusReady() throws Exception {
         mWifiNativeStatusListener.onStatusChanged(true);
         mLooper.dispatchAll();
-        verify(mWifiDiagnostics, never()).captureBugReportData(
+        verify(mWifiDiagnostics, never()).triggerBugReportDataCapture(
                 WifiDiagnostics.REPORT_REASON_WIFINATIVE_FAILURE);
         verify(mSelfRecovery, never()).trigger(eq(SelfRecovery.REASON_WIFINATIVE_FAILURE));
     }
@@ -864,7 +1004,7 @@
     public void shutdownWifiDoesNotCrashWhenClientModeExitsOnDestroyed() throws Exception {
         enterClientModeActiveState();
 
-        mClientListener.onStopped();
+        mClientListener.onStopped(mClientModeManager);
         mLooper.dispatchAll();
 
         shutdownWifi();
@@ -881,7 +1021,7 @@
 
         shutdownWifi();
 
-        mClientListener.onStopped();
+        mClientListener.onStopped(mClientModeManager);
         mLooper.dispatchAll();
 
         assertInDisabledState();
@@ -894,7 +1034,7 @@
     public void shutdownWifiDoesNotCrashWhenSoftApExitsOnDestroyed() throws Exception {
         enterSoftApActiveMode();
 
-        mSoftApListener.onStopped();
+        mSoftApListener.onStopped(mSoftApManager);
         mLooper.dispatchAll();
         mSoftApManagerCallback.onStateChanged(WifiManager.WIFI_AP_STATE_DISABLED, 0);
         mLooper.dispatchAll();
@@ -913,11 +1053,12 @@
 
         shutdownWifi();
 
-        mSoftApListener.onStopped();
+        mSoftApListener.onStopped(mSoftApManager);
         mSoftApManagerCallback.onStateChanged(WifiManager.WIFI_AP_STATE_DISABLED, 0);
         mLooper.dispatchAll();
 
         verify(mSoftApStateMachineCallback).onStateChanged(WifiManager.WIFI_AP_STATE_DISABLED, 0);
+        verify(mModeChangeCallback).onActiveModeManagerRemoved(mSoftApManager);
     }
 
     /**
@@ -956,7 +1097,7 @@
         SoftApModeConfiguration tetherConfig =
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mSoftApCapability);
-        SoftApConfiguration lohsConfigWC = WifiApConfigStore.generateLocalOnlyHotspotConfig(
+        SoftApConfiguration lohsConfigWC = mWifiApConfigStore.generateLocalOnlyHotspotConfig(
                 mContext, SoftApConfiguration.BAND_2GHZ, null);
         SoftApModeConfiguration lohsConfig =
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_LOCAL_ONLY, lohsConfigWC,
@@ -967,34 +1108,39 @@
         doAnswer(new Answer<SoftApManager>() {
             public SoftApManager answer(InvocationOnMock invocation) {
                 Object[] args = invocation.getArguments();
-                mSoftApListener = (ActiveModeManager.Listener) args[0];
+                mSoftApListener = (Listener<SoftApManager>) args[0];
                 return mSoftApManager;
             }
-        }).when(mWifiInjector).makeSoftApManager(any(ActiveModeManager.Listener.class),
-                any(WifiManager.SoftApCallback.class), eq(tetherConfig));
+        }).when(mWifiInjector).makeSoftApManager(any(Listener.class),
+                any(WifiServiceImpl.SoftApCallbackInternal.class), eq(tetherConfig),
+                eq(TEST_WORKSOURCE), eq(ROLE_SOFTAP_TETHERED), anyBoolean());
         // make a second softap manager
         SoftApManager lohsSoftapManager = mock(SoftApManager.class);
         when(lohsSoftapManager.getRole()).thenReturn(ROLE_SOFTAP_LOCAL_ONLY);
-        GeneralUtil.Mutable<ActiveModeManager.Listener> lohsSoftApListener =
-                new GeneralUtil.Mutable<>();
+        Mutable<Listener<SoftApManager>> lohsSoftApListener = new Mutable<>();
         doAnswer(new Answer<SoftApManager>() {
             public SoftApManager answer(InvocationOnMock invocation) {
                 Object[] args = invocation.getArguments();
-                lohsSoftApListener.value = (ActiveModeManager.Listener) args[0];
+                lohsSoftApListener.value = (Listener<SoftApManager>) args[0];
                 return lohsSoftapManager;
             }
-        }).when(mWifiInjector).makeSoftApManager(any(ActiveModeManager.Listener.class),
-                any(WifiManager.SoftApCallback.class), eq(lohsConfig));
+        }).when(mWifiInjector).makeSoftApManager(any(Listener.class),
+                any(WifiServiceImpl.SoftApCallbackInternal.class), eq(lohsConfig),
+                eq(TEST_WORKSOURCE), eq(ROLE_SOFTAP_LOCAL_ONLY), anyBoolean());
 
         // enable tethering and LOHS
-        mActiveModeWarden.startSoftAp(tetherConfig);
+        mActiveModeWarden.startSoftAp(tetherConfig, TEST_WORKSOURCE);
         mLooper.dispatchAll();
-        mSoftApListener.onStarted();
-        mActiveModeWarden.startSoftAp(lohsConfig);
+        mSoftApListener.onStarted(mSoftApManager);
+        mActiveModeWarden.startSoftAp(lohsConfig, TEST_WORKSOURCE);
         mLooper.dispatchAll();
-        lohsSoftApListener.value.onStarted();
-        verify(mSoftApManager).start();
-        verify(lohsSoftapManager).start();
+        lohsSoftApListener.value.onStarted(lohsSoftapManager);
+        verify(mWifiInjector).makeSoftApManager(any(Listener.class),
+                any(WifiServiceImpl.SoftApCallbackInternal.class), eq(tetherConfig),
+                eq(TEST_WORKSOURCE), eq(ROLE_SOFTAP_TETHERED), anyBoolean());
+        verify(mWifiInjector).makeSoftApManager(any(Listener.class),
+                any(WifiServiceImpl.SoftApCallbackInternal.class), eq(lohsConfig),
+                eq(TEST_WORKSOURCE), eq(ROLE_SOFTAP_LOCAL_ONLY), anyBoolean());
         verify(mBatteryStats).reportWifiOn();
 
         // disable tethering
@@ -1002,6 +1148,9 @@
         mLooper.dispatchAll();
         verify(mSoftApManager).stop();
         verify(lohsSoftapManager, never()).stop();
+
+        mSoftApListener.onStopped(mSoftApManager);
+        verify(mModeChangeCallback).onActiveModeManagerRemoved(mSoftApManager);
     }
 
     /**
@@ -1012,12 +1161,18 @@
         assertInDisabledState();
 
         when(mSettingsStore.isWifiToggleEnabled()).thenReturn(true);
-        mActiveModeWarden.wifiToggled();
+        mActiveModeWarden.wifiToggled(TEST_WORKSOURCE);
         mLooper.dispatchAll();
 
-        mClientListener.onStarted();
+        verify(mWifiInjector).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_PRIMARY),
+                anyBoolean());
+        mClientListener.onStarted(mClientModeManager);
         mLooper.dispatchAll();
 
+        // always set primary, even with single STA
+        verify(mWifiNative).setMultiStaPrimaryConnection(WIFI_IFACE_NAME);
+
         assertInEnabledState();
     }
 
@@ -1029,8 +1184,33 @@
         when(mSettingsStore.isScanAlwaysAvailable()).thenReturn(true);
         mActiveModeWarden.scanAlwaysModeChanged();
         mLooper.dispatchAll();
-        verify(mClientModeManager).start();
-        verify(mClientModeManager).setRole(ROLE_CLIENT_SCAN_ONLY);
+        verify(mWifiInjector).makeClientModeManager(
+                any(), eq(new WorkSource(Process.WIFI_UID)), eq(ROLE_CLIENT_SCAN_ONLY),
+                anyBoolean());
+        assertInEnabledState();
+        verify(mClientModeManager, never()).stop();
+    }
+
+    /**
+     * Test verifying that we ignore scan enable event when wifi is already enabled.
+     */
+    @Test
+    public void ignoreEnableScanModeWhenWifiEnabled() throws Exception {
+        // Turn on WIFI
+        assertInDisabledState();
+        when(mSettingsStore.isWifiToggleEnabled()).thenReturn(true);
+        mActiveModeWarden.wifiToggled(TEST_WORKSOURCE);
+        mLooper.dispatchAll();
+        mClientListener.onStarted(mClientModeManager);
+        mLooper.dispatchAll();
+        assertInEnabledState();
+
+        // Now toggle scan only change, should be ignored. We should send a role change
+        // again with PRIMARY & the cached requestorWs.
+        when(mSettingsStore.isScanAlwaysAvailable()).thenReturn(true);
+        mActiveModeWarden.scanAlwaysModeChanged();
+        mLooper.dispatchAll();
+        verify(mClientModeManager).setRole(ROLE_CLIENT_PRIMARY, TEST_WORKSOURCE);
         assertInEnabledState();
         verify(mClientModeManager, never()).stop();
     }
@@ -1060,10 +1240,12 @@
         mActiveModeWarden.start();
         mLooper.dispatchAll();
 
+        verify(mWifiMetrics).noteWifiEnabledDuringBoot(true);
+
         assertInEnabledState();
 
-        verify(mClientModeManager).start();
-        verify(mClientModeManager).setRole(ROLE_CLIENT_PRIMARY);
+        verify(mWifiInjector)
+                .makeClientModeManager(any(), any(), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
     }
 
     /**
@@ -1106,7 +1288,9 @@
 
         ArgumentCaptor<BroadcastReceiver> bcastRxCaptor =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
-        verify(mContext).registerReceiver(bcastRxCaptor.capture(), any(IntentFilter.class));
+        verify(mContext).registerReceiver(
+                bcastRxCaptor.capture(),
+                argThat(filter -> filter.hasAction(LocationManager.MODE_CHANGED_ACTION)));
         BroadcastReceiver broadcastReceiver = bcastRxCaptor.getValue();
 
         assertInDisabledState();
@@ -1134,12 +1318,14 @@
         mActiveModeWarden = createActiveModeWarden();
         mActiveModeWarden.start();
         mLooper.dispatchAll();
-        mClientListener.onStarted();
+        mClientListener.onStarted(mClientModeManager);
         mLooper.dispatchAll();
 
         ArgumentCaptor<BroadcastReceiver> bcastRxCaptor =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
-        verify(mContext).registerReceiver(bcastRxCaptor.capture(), any(IntentFilter.class));
+        verify(mContext).registerReceiver(
+                bcastRxCaptor.capture(),
+                argThat(filter -> filter.hasAction(LocationManager.MODE_CHANGED_ACTION)));
         BroadcastReceiver broadcastReceiver = bcastRxCaptor.getValue();
 
         assertInEnabledState();
@@ -1149,7 +1335,7 @@
         broadcastReceiver.onReceive(mContext, intent);
         mLooper.dispatchAll();
 
-        mClientListener.onStopped();
+        mClientListener.onStopped(mClientModeManager);
         mLooper.dispatchAll();
 
         assertInDisabledState();
@@ -1168,7 +1354,7 @@
 
         assertWifiShutDown(() -> {
             // test ecm changed
-            mActiveModeWarden.emergencyCallbackModeChanged(true);
+            emergencyCallbackModeChanged(true);
             mLooper.dispatchAll();
         });
     }
@@ -1186,7 +1372,7 @@
 
         assertEnteredEcmMode(() -> {
             // test ecm changed
-            mActiveModeWarden.emergencyCallbackModeChanged(true);
+            emergencyCallbackModeChanged(true);
             mLooper.dispatchAll();
         });
     }
@@ -1204,12 +1390,12 @@
 
         assertWifiShutDown(() -> {
             // test ecm changed
-            mActiveModeWarden.emergencyCallbackModeChanged(true);
+            emergencyCallbackModeChanged(true);
             mLooper.dispatchAll();
         });
 
         // test ecm changed
-        mActiveModeWarden.emergencyCallbackModeChanged(false);
+        emergencyCallbackModeChanged(false);
         mLooper.dispatchAll();
 
         assertInEnabledState();
@@ -1225,7 +1411,7 @@
         mActiveModeWarden.scanAlwaysModeChanged();
         mLooper.dispatchAll();
 
-        mClientListener.onStarted();
+        mClientListener.onStarted(mClientModeManager);
         mLooper.dispatchAll();
 
         assertInEnabledState();
@@ -1235,7 +1421,7 @@
 
         assertWifiShutDown(() -> {
             // test ecm changed
-            mActiveModeWarden.emergencyCallbackModeChanged(true);
+            emergencyCallbackModeChanged(true);
             mLooper.dispatchAll();
         });
     }
@@ -1257,7 +1443,7 @@
 
         assertEnteredEcmMode(() -> {
             // test ecm changed
-            mActiveModeWarden.emergencyCallbackModeChanged(true);
+            emergencyCallbackModeChanged(true);
             mLooper.dispatchAll();
         });
     }
@@ -1278,12 +1464,12 @@
 
         assertWifiShutDown(() -> {
             // test ecm changed
-            mActiveModeWarden.emergencyCallbackModeChanged(true);
+            emergencyCallbackModeChanged(true);
             mLooper.dispatchAll();
         });
 
         // test ecm changed
-        mActiveModeWarden.emergencyCallbackModeChanged(false);
+        emergencyCallbackModeChanged(false);
         mLooper.dispatchAll();
 
         assertInEnabledState();
@@ -1302,7 +1488,7 @@
 
         assertEnteredEcmMode(() -> {
             // test ecm changed
-            mActiveModeWarden.emergencyCallbackModeChanged(true);
+            emergencyCallbackModeChanged(true);
             mLooper.dispatchAll();
         });
     }
@@ -1319,7 +1505,7 @@
         when(mFacade.getConfigWiFiDisableInECBM(mContext)).thenReturn(false);
 
         // test ecm changed
-        mActiveModeWarden.emergencyCallbackModeChanged(true);
+        emergencyCallbackModeChanged(true);
         mLooper.dispatchAll();
 
         verify(mSoftApManager).stop();
@@ -1336,27 +1522,31 @@
         enterSoftApActiveMode();
 
         // verify Soft AP Manager started
-        verify(mSoftApManager).start();
+        verify(mWifiInjector).makeSoftApManager(
+                any(), any(), any(), eq(TEST_WORKSOURCE), eq(ROLE_SOFTAP_TETHERED), anyBoolean());
 
         // Test with WifiDisableInECBM turned on:
         when(mFacade.getConfigWiFiDisableInECBM(mContext)).thenReturn(true);
 
         assertEnteredEcmMode(() -> {
             // test ecm changed
-            mActiveModeWarden.emergencyCallbackModeChanged(true);
+            emergencyCallbackModeChanged(true);
             mLooper.dispatchAll();
-            mSoftApListener.onStopped();
+            mSoftApListener.onStopped(mSoftApManager);
             mLooper.dispatchAll();
         });
 
+        verify(mModeChangeCallback).onActiveModeManagerRemoved(mSoftApManager);
+
         // test ecm changed
-        mActiveModeWarden.emergencyCallbackModeChanged(false);
+        emergencyCallbackModeChanged(false);
         mLooper.dispatchAll();
 
         assertInDisabledState();
 
         // verify no additional calls to enable softap
-        verify(mSoftApManager).start();
+        verify(mWifiInjector).makeSoftApManager(
+                any(), any(), any(), eq(TEST_WORKSOURCE), eq(ROLE_SOFTAP_TETHERED), anyBoolean());
     }
 
     /**
@@ -1365,15 +1555,17 @@
     @Test
     public void testEcmOnFromDisabledMode() throws Exception {
         assertInDisabledState();
-        verify(mSoftApManager, never()).start();
-        verify(mClientModeManager, never()).start();
+        verify(mWifiInjector, never()).makeSoftApManager(
+                any(), any(), any(), any(), any(), anyBoolean());
+        verify(mWifiInjector, never()).makeClientModeManager(
+                any(), any(), any(), anyBoolean());
 
         // Test with WifiDisableInECBM turned on:
         when(mFacade.getConfigWiFiDisableInECBM(mContext)).thenReturn(true);
 
         assertEnteredEcmMode(() -> {
             // test ecm changed
-            mActiveModeWarden.emergencyCallbackModeChanged(true);
+            emergencyCallbackModeChanged(true);
             mLooper.dispatchAll();
         });
     }
@@ -1394,13 +1586,13 @@
 
         assertEnteredEcmMode(() -> {
             // test call state changed
-            mActiveModeWarden.emergencyCallStateChanged(true);
+            emergencyCallStateChanged(true);
             mLooper.dispatchAll();
-            mClientListener.onStopped();
+            mClientListener.onStopped(mClientModeManager);
             mLooper.dispatchAll();
         });
 
-        mActiveModeWarden.emergencyCallStateChanged(false);
+        emergencyCallStateChanged(false);
         mLooper.dispatchAll();
 
         assertInEnabledState();
@@ -1420,21 +1612,22 @@
         when(mFacade.getConfigWiFiDisableInECBM(mContext)).thenReturn(true);
 
         assertWifiShutDown(() -> {
-            mActiveModeWarden.emergencyCallStateChanged(true);
+            emergencyCallStateChanged(true);
             mLooper.dispatchAll();
-            mClientListener.onStopped();
+            mClientListener.onStopped(mClientModeManager);
             mLooper.dispatchAll();
         });
 
         assertWifiShutDown(() -> {
-            mActiveModeWarden.emergencyCallbackModeChanged(true);
+            emergencyCallbackModeChanged(true);
             mLooper.dispatchAll();
         }, 0); // does not cause another shutdown
 
         // client mode only started once so far
-        verify(mClientModeManager).start();
+        verify(mWifiInjector).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
 
-        mActiveModeWarden.emergencyCallStateChanged(false);
+        emergencyCallStateChanged(false);
         mLooper.dispatchAll();
 
         // stay in ecm, do not send an additional client mode trigger
@@ -1443,13 +1636,15 @@
         assertInDisabledState();
 
         // client mode still only started once
-        verify(mClientModeManager).start();
+        verify(mWifiInjector).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
 
-        mActiveModeWarden.emergencyCallbackModeChanged(false);
+        emergencyCallbackModeChanged(false);
         mLooper.dispatchAll();
 
         // now we can re-enable wifi
-        verify(mClientModeManager, times(2)).start();
+        verify(mWifiInjector, times(2)).makeClientModeManager(
+                any(), any(), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
         assertInEnabledState();
     }
 
@@ -1463,25 +1658,26 @@
         enableWifi();
 
         assertInEnabledState();
-        verify(mClientModeManager).start();
+        verify(mWifiInjector).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
 
         // Test with WifiDisableInECBM turned on:
         when(mFacade.getConfigWiFiDisableInECBM(mContext)).thenReturn(true);
 
         assertEnteredEcmMode(() -> {
-            mActiveModeWarden.emergencyCallbackModeChanged(true);
+            emergencyCallbackModeChanged(true);
             mLooper.dispatchAll();
-            mClientListener.onStopped();
+            mClientListener.onStopped(mClientModeManager);
             mLooper.dispatchAll();
         });
         assertInDisabledState();
 
         assertEnteredEcmMode(() -> {
-            mActiveModeWarden.emergencyCallStateChanged(true);
+            emergencyCallStateChanged(true);
             mLooper.dispatchAll();
         }, 0); // does not enter ECM state again
 
-        mActiveModeWarden.emergencyCallStateChanged(false);
+        emergencyCallStateChanged(false);
         mLooper.dispatchAll();
 
         // stay in ecm, do not send an additional client mode trigger
@@ -1490,13 +1686,15 @@
         assertInDisabledState();
 
         // client mode still only started once
-        verify(mClientModeManager).start();
+        verify(mWifiInjector).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
 
-        mActiveModeWarden.emergencyCallbackModeChanged(false);
+        emergencyCallbackModeChanged(false);
         mLooper.dispatchAll();
 
         // now we can re-enable wifi
-        verify(mClientModeManager, times(2)).start();
+        verify(mWifiInjector, times(2)).makeClientModeManager(
+                any(), any(), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
         assertInEnabledState();
     }
 
@@ -1511,25 +1709,26 @@
         enableWifi();
 
         assertInEnabledState();
-        verify(mClientModeManager).start();
+        verify(mWifiInjector).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
 
         // Test with WifiDisableInECBM turned on:
         when(mFacade.getConfigWiFiDisableInECBM(mContext)).thenReturn(true);
 
         assertEnteredEcmMode(() -> {
-            mActiveModeWarden.emergencyCallStateChanged(true);
+            emergencyCallStateChanged(true);
             mLooper.dispatchAll();
-            mClientListener.onStopped();
+            mClientListener.onStopped(mClientModeManager);
             mLooper.dispatchAll();
         });
         assertInDisabledState();
 
         assertEnteredEcmMode(() -> {
-            mActiveModeWarden.emergencyCallbackModeChanged(true);
+            emergencyCallbackModeChanged(true);
             mLooper.dispatchAll();
         }, 0); // still only 1 shutdown
 
-        mActiveModeWarden.emergencyCallbackModeChanged(false);
+        emergencyCallbackModeChanged(false);
         mLooper.dispatchAll();
 
         // stay in ecm, do not send an additional client mode trigger
@@ -1538,13 +1737,15 @@
         assertInDisabledState();
 
         // client mode still only started once
-        verify(mClientModeManager).start();
+        verify(mWifiInjector).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
 
-        mActiveModeWarden.emergencyCallStateChanged(false);
+        emergencyCallStateChanged(false);
         mLooper.dispatchAll();
 
         // now we can re-enable wifi
-        verify(mClientModeManager, times(2)).start();
+        verify(mWifiInjector, times(2)).makeClientModeManager(
+                any(), any(), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
         assertInEnabledState();
     }
 
@@ -1561,50 +1762,53 @@
 
         enableWifi();
 
-        verify(mClientModeManager).start();
+        verify(mWifiInjector).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
         assertInEnabledState();
 
         // Test with WifiDisableInECBM turned on:
         when(mFacade.getConfigWiFiDisableInECBM(mContext)).thenReturn(true);
 
         assertEnteredEcmMode(() -> {
-            mActiveModeWarden.emergencyCallbackModeChanged(true);
-            mActiveModeWarden.emergencyCallStateChanged(true);
-            mActiveModeWarden.emergencyCallStateChanged(true);
-            mActiveModeWarden.emergencyCallbackModeChanged(true);
-            mActiveModeWarden.emergencyCallbackModeChanged(true);
+            emergencyCallbackModeChanged(true);
+            emergencyCallStateChanged(true);
+            emergencyCallStateChanged(true);
+            emergencyCallbackModeChanged(true);
+            emergencyCallbackModeChanged(true);
             mLooper.dispatchAll();
-            mClientListener.onStopped();
+            mClientListener.onStopped(mClientModeManager);
             mLooper.dispatchAll();
         });
         assertInDisabledState();
 
         assertEnteredEcmMode(() -> {
-            mActiveModeWarden.emergencyCallbackModeChanged(false);
+            emergencyCallbackModeChanged(false);
             mLooper.dispatchAll();
-            mActiveModeWarden.emergencyCallbackModeChanged(false);
+            emergencyCallbackModeChanged(false);
             mLooper.dispatchAll();
-            mActiveModeWarden.emergencyCallbackModeChanged(false);
+            emergencyCallbackModeChanged(false);
             mLooper.dispatchAll();
-            mActiveModeWarden.emergencyCallbackModeChanged(false);
+            emergencyCallbackModeChanged(false);
             mLooper.dispatchAll();
         }, 0);
 
         // didn't enter client mode again
-        verify(mClientModeManager).start();
+        verify(mWifiInjector).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
         assertInDisabledState();
 
         // now we will exit ECM
-        mActiveModeWarden.emergencyCallStateChanged(false);
+        emergencyCallStateChanged(false);
         mLooper.dispatchAll();
 
         // now we can re-enable wifi
-        verify(mClientModeManager, times(2)).start();
+        verify(mWifiInjector, times(2)).makeClientModeManager(
+                any(), any(), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
         assertInEnabledState();
     }
 
     /**
-     * Toggling wifi when in ECM does not exit ecm mode and enable wifi
+     * Toggling wifi on when in ECM does not exit ecm mode and enable wifi
      */
     @Test
     public void testWifiDoesNotToggleOnWhenInEcm() throws Exception {
@@ -1614,16 +1818,71 @@
         when(mFacade.getConfigWiFiDisableInECBM(mContext)).thenReturn(true);
         // test ecm changed
         assertEnteredEcmMode(() -> {
-            mActiveModeWarden.emergencyCallbackModeChanged(true);
+            emergencyCallbackModeChanged(true);
             mLooper.dispatchAll();
         });
 
         // now toggle wifi and verify we do not start wifi
         when(mSettingsStore.isWifiToggleEnabled()).thenReturn(true);
-        mActiveModeWarden.wifiToggled();
+        mActiveModeWarden.wifiToggled(TEST_WORKSOURCE);
         mLooper.dispatchAll();
 
-        verify(mClientModeManager, never()).start();
+        verify(mWifiInjector, never()).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
+        assertInDisabledState();
+        assertInEmergencyMode();
+
+        // now we will exit ECM
+        emergencyCallbackModeChanged(false);
+        mLooper.dispatchAll();
+        assertNotInEmergencyMode();
+
+        // Wifi toggle on now takes effect
+        verify(mWifiInjector).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
+        assertInEnabledState();
+    }
+
+    /**
+     * Toggling wifi off when in ECM does not disable wifi when getConfigWiFiDisableInECBM is
+     * disabled.
+     */
+    @Test
+    public void testWifiDoesNotToggleOffWhenInEcmAndConfigDisabled() throws Exception {
+        enableWifi();
+        assertInEnabledState();
+        verify(mWifiInjector).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
+
+        // Test with WifiDisableInECBM turned off
+        when(mFacade.getConfigWiFiDisableInECBM(mContext)).thenReturn(false);
+        // test ecm changed
+        assertEnteredEcmMode(() -> {
+            emergencyCallbackModeChanged(true);
+            mLooper.dispatchAll();
+        });
+
+        // now toggle wifi and verify we do not start wifi
+        when(mSettingsStore.isWifiToggleEnabled()).thenReturn(false);
+        mActiveModeWarden.wifiToggled(TEST_WORKSOURCE);
+        mLooper.dispatchAll();
+
+        // still only called once
+        verify(mWifiInjector).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
+        verify(mClientModeManager, never()).stop();
+        assertInEnabledState();
+        assertInEmergencyMode();
+
+        // now we will exit ECM
+        emergencyCallbackModeChanged(false);
+        mLooper.dispatchAll();
+        assertNotInEmergencyMode();
+
+        // Wifi toggle off now takes effect
+        verify(mClientModeManager).stop();
+        mClientListener.onStopped(mClientModeManager);
+        mLooper.dispatchAll();
         assertInDisabledState();
     }
 
@@ -1644,7 +1903,7 @@
         when(mFacade.getConfigWiFiDisableInECBM(mContext)).thenReturn(true);
         assertEnteredEcmMode(() -> {
             // test ecm changed
-            mActiveModeWarden.emergencyCallbackModeChanged(true);
+            emergencyCallbackModeChanged(true);
             mLooper.dispatchAll();
         });
 
@@ -1654,7 +1913,8 @@
         mActiveModeWarden.scanAlwaysModeChanged();
         mLooper.dispatchAll();
 
-        verify(mClientModeManager, never()).start();
+        verify(mWifiInjector, never()).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
         assertInDisabledState();
     }
 
@@ -1670,17 +1930,37 @@
         when(mFacade.getConfigWiFiDisableInECBM(mContext)).thenReturn(true);
         assertEnteredEcmMode(() -> {
             // test ecm changed
-            mActiveModeWarden.emergencyCallbackModeChanged(true);
+            emergencyCallbackModeChanged(true);
             mLooper.dispatchAll();
         });
 
+        // try to start Soft AP
         mActiveModeWarden.startSoftAp(
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
-                mSoftApCapability));
+                mSoftApCapability), TEST_WORKSOURCE);
         mLooper.dispatchAll();
 
-        verify(mSoftApManager, never()).start();
+        verify(mWifiInjector, never())
+                .makeSoftApManager(any(), any(), any(), eq(TEST_WORKSOURCE), any(), anyBoolean());
         assertInDisabledState();
+
+        // verify triggered Soft AP failure callback
+        verify(mSoftApStateMachineCallback).onStateChanged(WifiManager.WIFI_AP_STATE_FAILED,
+                WifiManager.SAP_START_FAILURE_GENERAL);
+
+        // try to start LOHS
+        mActiveModeWarden.startSoftAp(
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_LOCAL_ONLY, null,
+                mSoftApCapability), TEST_WORKSOURCE);
+        mLooper.dispatchAll();
+
+        verify(mWifiInjector, never())
+                .makeSoftApManager(any(), any(), any(), eq(TEST_WORKSOURCE), any(), anyBoolean());
+        assertInDisabledState();
+
+        // verify triggered LOHS failure callback
+        verify(mLohsStateMachineCallback).onStateChanged(WifiManager.WIFI_AP_STATE_FAILED,
+                WifiManager.SAP_START_FAILURE_GENERAL);
     }
 
     /**
@@ -1694,7 +1974,7 @@
         when(mFacade.getConfigWiFiDisableInECBM(mContext)).thenReturn(true);
         assertEnteredEcmMode(() -> {
             // test ecm changed
-            mActiveModeWarden.emergencyCallbackModeChanged(true);
+            emergencyCallbackModeChanged(true);
             mLooper.dispatchAll();
         });
 
@@ -1716,9 +1996,10 @@
         // Turn on SoftAp.
         mActiveModeWarden.startSoftAp(
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
-                mSoftApCapability));
+                mSoftApCapability), TEST_WORKSOURCE);
         mLooper.dispatchAll();
-        verify(mSoftApManager).start();
+        verify(mWifiInjector)
+                .makeSoftApManager(any(), any(), any(), eq(TEST_WORKSOURCE), any(), anyBoolean());
 
         // Turn off SoftAp.
         mActiveModeWarden.stopSoftAp(WifiManager.IFACE_IP_MODE_UNSPECIFIED);
@@ -1739,14 +2020,14 @@
         when(mFacade.getConfigWiFiDisableInECBM(mContext)).thenReturn(true);
         assertEnteredEcmMode(() -> {
             // test ecm changed
-            mActiveModeWarden.emergencyCallbackModeChanged(true);
+            emergencyCallbackModeChanged(true);
             mLooper.dispatchAll();
-            mClientListener.onStopped();
+            mClientListener.onStopped(mClientModeManager);
             mLooper.dispatchAll();
         });
 
         // Spurious onStopped
-        mClientListener.onStopped();
+        mClientListener.onStopped(mClientModeManager);
         mLooper.dispatchAll();
 
         assertInDisabledState();
@@ -1764,14 +2045,14 @@
         when(mFacade.getConfigWiFiDisableInECBM(mContext)).thenReturn(true);
         assertEnteredEcmMode(() -> {
             // test ecm changed
-            mActiveModeWarden.emergencyCallbackModeChanged(true);
+            emergencyCallbackModeChanged(true);
             mLooper.dispatchAll();
-            mClientListener.onStopped();
+            mClientListener.onStopped(mClientModeManager);
             mLooper.dispatchAll();
         });
 
         // Spurious onStopped
-        mClientListener.onStopped();
+        mClientListener.onStopped(mClientModeManager);
         mLooper.dispatchAll();
 
         assertInDisabledState();
@@ -1788,21 +2069,25 @@
     public void testReturnToEnabledStateAfterAPModeShutdown() throws Exception {
         enableWifi();
         assertInEnabledState();
-        verify(mClientModeManager).start();
+        verify(mWifiInjector).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
 
         mActiveModeWarden.startSoftAp(
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
-                mSoftApCapability));
+                mSoftApCapability), TEST_WORKSOURCE);
         // add an "unexpected" sta mode stop to simulate a single interface device
-        mClientListener.onStopped();
+        mClientListener.onStopped(mClientModeManager);
         mLooper.dispatchAll();
+        verify(mModeChangeCallback).onActiveModeManagerRemoved(mClientModeManager);
 
         // Now stop the AP
-        mSoftApListener.onStopped();
+        mSoftApListener.onStopped(mSoftApManager);
         mLooper.dispatchAll();
+        verify(mModeChangeCallback).onActiveModeManagerRemoved(mSoftApManager);
 
         // We should re-enable client mode
-        verify(mClientModeManager, times(2)).start();
+        verify(mWifiInjector, times(2)).makeClientModeManager(
+                any(), any(), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
         assertInEnabledState();
     }
 
@@ -1820,37 +2105,48 @@
     public void testReturnToEnabledStateAfterWifiEnabledShutdown() throws Exception {
         enableWifi();
         assertInEnabledState();
-        verify(mClientModeManager).start();
+        verify(mWifiInjector).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
 
         mActiveModeWarden.startSoftAp(
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
-                mSoftApCapability));
+                mSoftApCapability), TEST_WORKSOURCE);
         mLooper.dispatchAll();
 
         when(mSettingsStore.isWifiToggleEnabled()).thenReturn(true);
-        mActiveModeWarden.wifiToggled();
-        mSoftApListener.onStopped();
+        mActiveModeWarden.wifiToggled(TEST_WORKSOURCE);
+        mSoftApListener.onStopped(mSoftApManager);
         mLooper.dispatchAll();
 
         // wasn't called again
-        verify(mClientModeManager).start();
+        verify(mWifiInjector).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
         assertInEnabledState();
     }
 
     @Test
     public void testRestartWifiStackInEnabledStateTriggersBugReport() throws Exception {
         enableWifi();
-        mActiveModeWarden.recoveryRestartWifi(SelfRecovery.REASON_WIFINATIVE_FAILURE);
+
+        // note: using a reason that will typical not start a bug report on purpose to guarantee
+        // that it is the flag and not the reason which controls it.
+        mActiveModeWarden.recoveryRestartWifi(SelfRecovery.REASON_LAST_RESORT_WATCHDOG, "some text",
+                true);
         mLooper.dispatchAll();
-        verify(mClientModeImpl).takeBugReport(anyString(), anyString());
+        verify(mWifiDiagnostics).takeBugReport(anyString(), anyString());
+        verify(mSubsystemRestartCallback).onSubsystemRestarting();
     }
 
     @Test
     public void testRestartWifiWatchdogDoesNotTriggerBugReport() throws Exception {
         enableWifi();
-        mActiveModeWarden.recoveryRestartWifi(SelfRecovery.REASON_LAST_RESORT_WATCHDOG);
+        // note: using a reason that will typical start a bug report on purpose to guarantee that
+        // it is the flag and not the reason which controls it.
+        mActiveModeWarden.recoveryRestartWifi(SelfRecovery.REASON_WIFINATIVE_FAILURE,
+                "anything", false);
         mLooper.dispatchAll();
-        verify(mClientModeImpl, never()).takeBugReport(anyString(), anyString());
+        verify(mWifiDiagnostics, never()).takeBugReport(anyString(), anyString());
+        verify(mSubsystemRestartCallback).onSubsystemRestarting();
     }
 
     /**
@@ -1863,9 +2159,10 @@
         mActiveModeWarden.recoveryDisableWifi();
         mLooper.dispatchAll();
         verify(mClientModeManager).stop();
-        mClientListener.onStopped();
+        mClientListener.onStopped(mClientModeManager);
         mLooper.dispatchAll();
         assertInDisabledState();
+        verify(mModeChangeCallback).onActiveModeManagerRemoved(mClientModeManager);
     }
 
     /**
@@ -1878,6 +2175,11 @@
             mActiveModeWarden.recoveryDisableWifi();
             mLooper.dispatchAll();
         });
+        mLooper.moveTimeForward(TEST_WIFI_RECOVERY_DELAY_MS + 10);
+        mLooper.dispatchAll();
+
+        // Ensure we did not restart wifi.
+        assertInDisabledState();
     }
 
     /**
@@ -1893,7 +2195,11 @@
     public void testRestartWifiStackInDisabledState() throws Exception {
         assertInDisabledState();
 
-        mActiveModeWarden.recoveryRestartWifi(SelfRecovery.REASON_WIFINATIVE_FAILURE);
+        mActiveModeWarden.recoveryRestartWifi(SelfRecovery.REASON_WIFINATIVE_FAILURE,
+                SelfRecovery.REASON_STRINGS[SelfRecovery.REASON_WIFINATIVE_FAILURE], true);
+        mLooper.dispatchAll();
+
+        mLooper.moveTimeForward(TEST_WIFI_RECOVERY_DELAY_MS + 10);
         mLooper.dispatchAll();
 
         assertInDisabledState();
@@ -1911,7 +2217,7 @@
      * ActiveModeWarden should enter SCAN_ONLY mode and the wifi driver should be started.
      */
     @Test
-    public void testRestartWifiStackInDisabledStateWithScanState() throws Exception {
+    public void testRestartWifiStackInStaScanEnabledState() throws Exception {
         assertInDisabledState();
 
         when(mSettingsStore.isScanAlwaysAvailable()).thenReturn(true);
@@ -1919,23 +2225,28 @@
         mLooper.dispatchAll();
 
         assertInEnabledState();
-        verify(mClientModeManager).start();
-        verify(mClientModeManager).setRole(ROLE_CLIENT_SCAN_ONLY);
+        verify(mWifiInjector).makeClientModeManager(
+                any(), eq(new WorkSource(Process.WIFI_UID)), eq(ROLE_CLIENT_SCAN_ONLY),
+                anyBoolean());
 
-        mActiveModeWarden.recoveryRestartWifi(SelfRecovery.REASON_WIFINATIVE_FAILURE);
+        mActiveModeWarden.recoveryRestartWifi(SelfRecovery.REASON_WIFINATIVE_FAILURE,
+                SelfRecovery.REASON_STRINGS[SelfRecovery.REASON_WIFINATIVE_FAILURE], true);
         mLooper.dispatchAll();
 
         verify(mClientModeManager).stop();
-        mClientListener.onStopped();
+        mClientListener.onStopped(mClientModeManager);
         mLooper.dispatchAll();
         assertInDisabledState();
+        verify(mModeChangeCallback).onActiveModeManagerRemoved(mClientModeManager);
 
         mLooper.moveTimeForward(TEST_WIFI_RECOVERY_DELAY_MS);
         mLooper.dispatchAll();
 
-        verify(mClientModeManager, times(2)).start();
-        verify(mClientModeManager, times(2)).setRole(ROLE_CLIENT_SCAN_ONLY);
+        verify(mWifiInjector, times(2)).makeClientModeManager(any(), any(), any(), anyBoolean());
         assertInEnabledState();
+
+        verify(mSubsystemRestartCallback).onSubsystemRestarting();
+        verify(mSubsystemRestartCallback).onSubsystemRestarted();
     }
 
     /**
@@ -1949,28 +2260,36 @@
      * ActiveModeWarden should enter CONNECT_MODE and the wifi driver should be started.
      */
     @Test
-    public void testRestartWifiStackInEnabledState() throws Exception {
+    public void testRestartWifiStackInStaConnectEnabledState() throws Exception {
         enableWifi();
         assertInEnabledState();
-        verify(mClientModeManager).start();
+        verify(mWifiInjector).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
 
         assertWifiShutDown(() -> {
-            mActiveModeWarden.recoveryRestartWifi(SelfRecovery.REASON_WIFINATIVE_FAILURE);
+            mActiveModeWarden.recoveryRestartWifi(SelfRecovery.REASON_WIFINATIVE_FAILURE,
+                    SelfRecovery.REASON_STRINGS[SelfRecovery.REASON_WIFINATIVE_FAILURE], true);
             mLooper.dispatchAll();
             // Complete the stop
-            mClientListener.onStopped();
+            mClientListener.onStopped(mClientModeManager);
             mLooper.dispatchAll();
         });
 
+        verify(mModeChangeCallback).onActiveModeManagerRemoved(mClientModeManager);
+
         // still only started once
-        verify(mClientModeManager).start();
+        verify(mWifiInjector).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
 
         mLooper.moveTimeForward(TEST_WIFI_RECOVERY_DELAY_MS);
         mLooper.dispatchAll();
 
         // started again
-        verify(mClientModeManager, times(2)).start();
+        verify(mWifiInjector, times(2)).makeClientModeManager(any(), any(), any(), anyBoolean());
         assertInEnabledState();
+
+        verify(mSubsystemRestartCallback).onSubsystemRestarting();
+        verify(mSubsystemRestartCallback).onSubsystemRestarted();
     }
 
     /**
@@ -1984,48 +2303,118 @@
     public void testRestartWifiStackDoesNotExitECMMode() throws Exception {
         enableWifi();
         assertInEnabledState();
-        verify(mClientModeManager).start();
-        verify(mClientModeManager).setRole(ROLE_CLIENT_PRIMARY);
+        verify(mWifiInjector).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_PRIMARY), eq(false));
 
         when(mFacade.getConfigWiFiDisableInECBM(mContext)).thenReturn(true);
         assertEnteredEcmMode(() -> {
-            mActiveModeWarden.emergencyCallStateChanged(true);
+            emergencyCallStateChanged(true);
             mLooper.dispatchAll();
-            mClientListener.onStopped();
+            mClientListener.onStopped(mClientModeManager);
             mLooper.dispatchAll();
         });
         assertInEmergencyMode();
         assertInDisabledState();
         verify(mClientModeManager).stop();
         verify(mClientModeManager, atLeastOnce()).getRole();
+        verify(mClientModeManager).clearWifiConnectedNetworkScorer();
+        verify(mModeChangeCallback).onActiveModeManagerRemoved(mClientModeManager);
 
-        mActiveModeWarden.recoveryRestartWifi(SelfRecovery.REASON_LAST_RESORT_WATCHDOG);
+        mActiveModeWarden.recoveryRestartWifi(SelfRecovery.REASON_LAST_RESORT_WATCHDOG,
+                SelfRecovery.REASON_STRINGS[SelfRecovery.REASON_LAST_RESORT_WATCHDOG], false);
         mLooper.dispatchAll();
 
-        verify(mClientModeManager).start(); // wasn't called again
+        // wasn't called again
+        verify(mWifiInjector).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
         assertInEmergencyMode();
         assertInDisabledState();
+
+        verify(mClientModeManager, atLeastOnce()).getInterfaceName();
         verifyNoMoreInteractions(mClientModeManager, mSoftApManager);
     }
 
     /**
-     * The command to trigger a WiFi reset should trigger a reset when in AP mode.
-     * Enter AP mode, send command to restart wifi.
-     * <p>
-     * Expected: The command to trigger a wifi reset should trigger wifi shutdown.
+     * The command to trigger a WiFi reset should trigger a wifi reset in SoftApManager through
+     * the ActiveModeWarden.shutdownWifi() call when in SAP enabled mode.
      */
     @Test
-    public void testRestartWifiStackFullyStopsWifi() throws Exception {
-        mActiveModeWarden.startSoftAp(new SoftApModeConfiguration(
-                WifiManager.IFACE_IP_MODE_LOCAL_ONLY, null, mSoftApCapability));
-        mLooper.dispatchAll();
-        verify(mSoftApManager).start();
-        verify(mSoftApManager).setRole(ROLE_SOFTAP_LOCAL_ONLY);
+    public void testRestartWifiStackInTetheredSoftApEnabledState() throws Exception {
+        enterSoftApActiveMode();
+        verify(mWifiInjector).makeSoftApManager(
+                any(), any(), any(), eq(TEST_WORKSOURCE), eq(ROLE_SOFTAP_TETHERED), anyBoolean());
 
         assertWifiShutDown(() -> {
-            mActiveModeWarden.recoveryRestartWifi(SelfRecovery.REASON_STA_IFACE_DOWN);
+            mActiveModeWarden.recoveryRestartWifi(SelfRecovery.REASON_WIFINATIVE_FAILURE,
+                    SelfRecovery.REASON_STRINGS[SelfRecovery.REASON_WIFINATIVE_FAILURE], true);
+            mLooper.dispatchAll();
+            // Complete the stop
+            mSoftApListener.onStopped(mSoftApManager);
             mLooper.dispatchAll();
         });
+
+        verify(mModeChangeCallback).onActiveModeManagerRemoved(mSoftApManager);
+
+        // still only started once
+        verify(mWifiInjector).makeSoftApManager(
+                any(), any(), any(), eq(TEST_WORKSOURCE), eq(ROLE_SOFTAP_TETHERED), anyBoolean());
+
+        mLooper.moveTimeForward(TEST_WIFI_RECOVERY_DELAY_MS);
+        mLooper.dispatchAll();
+
+        // started again
+        verify(mWifiInjector, times(2)).makeSoftApManager(
+                any(), any(), any(), any(), any(), anyBoolean());
+        assertInEnabledState();
+
+        verify(mSubsystemRestartCallback).onSubsystemRestarting();
+        verify(mSubsystemRestartCallback).onSubsystemRestarted();
+    }
+
+    /**
+     * The command to trigger a WiFi reset should trigger a wifi reset in SoftApManager &
+     * ClientModeManager through the ActiveModeWarden.shutdownWifi() call when in STA + SAP
+     * enabled mode.
+     */
+    @Test
+    public void testRestartWifiStackInTetheredSoftApAndStaConnectEnabledState() throws Exception {
+        enableWifi();
+        enterSoftApActiveMode();
+        verify(mWifiInjector).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
+        verify(mWifiInjector).makeSoftApManager(
+                any(), any(), any(), eq(TEST_WORKSOURCE), eq(ROLE_SOFTAP_TETHERED), anyBoolean());
+
+        assertWifiShutDown(() -> {
+            mActiveModeWarden.recoveryRestartWifi(SelfRecovery.REASON_WIFINATIVE_FAILURE,
+                    SelfRecovery.REASON_STRINGS[SelfRecovery.REASON_WIFINATIVE_FAILURE], true);
+            mLooper.dispatchAll();
+            // Complete the stop
+            mClientListener.onStopped(mClientModeManager);
+            mSoftApListener.onStopped(mSoftApManager);
+            mLooper.dispatchAll();
+        });
+
+        verify(mModeChangeCallback).onActiveModeManagerRemoved(mClientModeManager);
+        verify(mModeChangeCallback).onActiveModeManagerRemoved(mSoftApManager);
+
+        // still only started once
+        verify(mWifiInjector).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
+        verify(mWifiInjector).makeSoftApManager(
+                any(), any(), any(), eq(TEST_WORKSOURCE), eq(ROLE_SOFTAP_TETHERED), anyBoolean());
+
+        mLooper.moveTimeForward(TEST_WIFI_RECOVERY_DELAY_MS);
+        mLooper.dispatchAll();
+
+        // started again
+        verify(mWifiInjector, times(2)).makeClientModeManager(any(), any(), any(), anyBoolean());
+        verify(mWifiInjector, times(2)).makeSoftApManager(
+                any(), any(), any(), any(), any(), anyBoolean());
+        assertInEnabledState();
+
+        verify(mSubsystemRestartCallback).onSubsystemRestarting();
+        verify(mSubsystemRestartCallback).onSubsystemRestarted();
     }
 
     /**
@@ -2043,11 +2432,12 @@
         when(mSettingsStore.isScanAlwaysAvailable()).thenReturn(false);
         when(mSettingsStore.isAirplaneModeOn()).thenReturn(true);
 
-        mActiveModeWarden.wifiToggled();
+        mActiveModeWarden.wifiToggled(TEST_WORKSOURCE);
         mLooper.dispatchAll();
 
         assertInDisabledState();
-        verify(mClientModeManager, never()).start();
+        verify(mWifiInjector, never()).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), any(), anyBoolean());
     }
 
     /**
@@ -2065,12 +2455,12 @@
         when(mSettingsStore.isScanAlwaysAvailable()).thenReturn(true);
         when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
 
-        mActiveModeWarden.wifiToggled();
+        mActiveModeWarden.wifiToggled(TEST_WORKSOURCE);
         mLooper.dispatchAll();
 
         assertInEnabledState();
-        verify(mClientModeManager).start();
-        verify(mClientModeManager).setRole(ROLE_CLIENT_SCAN_ONLY);
+        verify(mWifiInjector).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_SCAN_ONLY), anyBoolean());
     }
 
     /**
@@ -2081,22 +2471,56 @@
     public void ecmDisablesWifi_exitEcm_restartWifi() throws Exception {
         enterClientModeActiveState();
 
-        verify(mClientModeManager).start();
+        verify(mWifiInjector).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
 
         when(mFacade.getConfigWiFiDisableInECBM(mContext)).thenReturn(true);
         assertEnteredEcmMode(() -> {
-            mActiveModeWarden.emergencyCallbackModeChanged(true);
+            emergencyCallbackModeChanged(true);
             mLooper.dispatchAll();
         });
         assertInEnabledState();
         verify(mClientModeManager).stop();
 
-        mActiveModeWarden.emergencyCallbackModeChanged(false);
+        mClientListener.onStopped(mClientModeManager);
+        mLooper.dispatchAll();
+        assertInDisabledState();
+
+        emergencyCallbackModeChanged(false);
         mLooper.dispatchAll();
 
-        assertThat(mActiveModeWarden.isInEmergencyMode()).isFalse();
+        assertNotInEmergencyMode();
         // client mode restarted
-        verify(mClientModeManager, times(2)).start();
+        verify(mWifiInjector, times(2)).makeClientModeManager(any(), any(), any(), anyBoolean());
+        assertInEnabledState();
+    }
+
+    /**
+     * Tests that if the carrier config to disable Wifi is not enabled during ECM, Wifi remains on
+     * during ECM, and nothing happens after exiting ECM.
+     */
+    @Test
+    public void ecmDoesNotDisableWifi_exitEcm_noOp() throws Exception {
+        enterClientModeActiveState();
+
+        verify(mWifiInjector).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
+
+        when(mFacade.getConfigWiFiDisableInECBM(mContext)).thenReturn(false);
+        assertEnteredEcmMode(() -> {
+            emergencyCallbackModeChanged(true);
+            mLooper.dispatchAll();
+        });
+        assertInEnabledState();
+        verify(mClientModeManager, never()).stop();
+
+        emergencyCallbackModeChanged(false);
+        mLooper.dispatchAll();
+
+        assertNotInEmergencyMode();
+        // client mode manager not started again
+        verify(mWifiInjector).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
         assertInEnabledState();
     }
 
@@ -2139,31 +2563,6 @@
     }
 
     @Test
-    public void interfaceAvailabilityListener() throws Exception {
-        assertFalse(mActiveModeWarden.canRequestMoreClientModeManagers());
-        assertFalse(mActiveModeWarden.canRequestMoreSoftApManagers());
-
-        assertNotNull(mClientIfaceAvailableListener.getValue());
-        assertNotNull(mSoftApIfaceAvailableListener.getValue());
-
-        mClientIfaceAvailableListener.getValue().onAvailabilityChanged(true);
-        mLooper.dispatchAll();
-        assertTrue(mActiveModeWarden.canRequestMoreClientModeManagers());
-
-        mSoftApIfaceAvailableListener.getValue().onAvailabilityChanged(true);
-        mLooper.dispatchAll();
-        assertTrue(mActiveModeWarden.canRequestMoreSoftApManagers());
-
-        mClientIfaceAvailableListener.getValue().onAvailabilityChanged(false);
-        mLooper.dispatchAll();
-        assertFalse(mActiveModeWarden.canRequestMoreClientModeManagers());
-
-        mSoftApIfaceAvailableListener.getValue().onAvailabilityChanged(false);
-        mLooper.dispatchAll();
-        assertFalse(mActiveModeWarden.canRequestMoreSoftApManagers());
-    }
-
-    @Test
     public void isStaApConcurrencySupported() throws Exception {
         when(mWifiNative.isStaApConcurrencySupported()).thenReturn(false);
         assertFalse(mActiveModeWarden.isStaApConcurrencySupported());
@@ -2173,6 +2572,795 @@
     }
 
     @Test
+    public void isStaStaConcurrencySupported() throws Exception {
+        // STA + STA not supported.
+        when(mWifiNative.isStaStaConcurrencySupported()).thenReturn(false);
+        assertFalse(mActiveModeWarden.isStaStaConcurrencySupportedForLocalOnlyConnections());
+        assertFalse(mActiveModeWarden.isStaStaConcurrencySupportedForMbb());
+        assertFalse(mActiveModeWarden.isStaStaConcurrencySupportedForRestrictedConnections());
+
+        // STA + STA supported, but no use-cases enabled.
+        when(mWifiNative.isStaStaConcurrencySupported()).thenReturn(true);
+        assertFalse(mActiveModeWarden.isStaStaConcurrencySupportedForLocalOnlyConnections());
+        assertFalse(mActiveModeWarden.isStaStaConcurrencySupportedForMbb());
+        assertFalse(mActiveModeWarden.isStaStaConcurrencySupportedForRestrictedConnections());
+
+        when(mResources.getBoolean(R.bool.config_wifiMultiStaLocalOnlyConcurrencyEnabled))
+                .thenReturn(true);
+        assertTrue(mActiveModeWarden.isStaStaConcurrencySupportedForLocalOnlyConnections());
+
+        when(mResources.getBoolean(
+                R.bool.config_wifiMultiStaNetworkSwitchingMakeBeforeBreakEnabled))
+                .thenReturn(true);
+        assertTrue(mActiveModeWarden.isStaStaConcurrencySupportedForMbb());
+
+        when(mResources.getBoolean(R.bool.config_wifiMultiStaRestrictedConcurrencyEnabled))
+                .thenReturn(true);
+        assertTrue(mActiveModeWarden.isStaStaConcurrencySupportedForRestrictedConnections());
+    }
+
+    private Listener<ConcreteClientModeManager> requestAdditionalClientModeManager(
+            ClientConnectivityRole additionaClientModeManagerRole,
+            ConcreteClientModeManager additionalClientModeManager,
+            ExternalClientModeManagerRequestListener externalRequestListener,
+            String ssid, String bssid)
+            throws Exception {
+        enterClientModeActiveState();
+
+        Mutable<Listener<ConcreteClientModeManager>> additionalClientListener =
+                new Mutable<>();
+
+        // Connected to ssid1/bssid1
+        WifiConfiguration config1 = new WifiConfiguration();
+        config1.SSID = TEST_SSID_1;
+        when(mClientModeManager.getConnectedWifiConfiguration()).thenReturn(config1);
+        when(mClientModeManager.getConnectedBssid()).thenReturn(TEST_BSSID_1);
+
+        doAnswer((invocation) -> {
+            Object[] args = invocation.getArguments();
+            additionalClientListener.value =
+                    (Listener<ConcreteClientModeManager>) args[0];
+            return additionalClientModeManager;
+        }).when(mWifiInjector).makeClientModeManager(
+                any(Listener.class), any(), any(), anyBoolean());
+        when(additionalClientModeManager.getInterfaceName()).thenReturn(WIFI_IFACE_NAME_1);
+        when(additionalClientModeManager.getRole()).thenReturn(additionaClientModeManagerRole);
+
+        // request for ssid2/bssid2
+        if (additionaClientModeManagerRole == ROLE_CLIENT_LOCAL_ONLY) {
+            mActiveModeWarden.requestLocalOnlyClientModeManager(
+                    externalRequestListener, TEST_WORKSOURCE, ssid, bssid);
+        } else if (additionaClientModeManagerRole == ROLE_CLIENT_SECONDARY_LONG_LIVED) {
+            mActiveModeWarden.requestSecondaryLongLivedClientModeManager(
+                    externalRequestListener, TEST_WORKSOURCE, ssid, bssid);
+        } else if (additionaClientModeManagerRole == ROLE_CLIENT_SECONDARY_TRANSIENT) {
+            mActiveModeWarden.requestSecondaryTransientClientModeManager(
+                    externalRequestListener, TEST_WORKSOURCE, ssid, bssid);
+        }
+        mLooper.dispatchAll();
+        verify(mWifiInjector)
+                .makeClientModeManager(any(), eq(TEST_WORKSOURCE),
+                        eq(additionaClientModeManagerRole), anyBoolean());
+        additionalClientListener.value.onStarted(additionalClientModeManager);
+        mLooper.dispatchAll();
+        // capture last use case set
+        ArgumentCaptor<Integer> useCaseCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mWifiNative, atLeastOnce()).setMultiStaUseCase(useCaseCaptor.capture());
+        int lastUseCaseSet = useCaseCaptor.getValue().intValue();
+        // Ensure the hardware is correctly configured for STA + STA
+        if (additionaClientModeManagerRole == ROLE_CLIENT_LOCAL_ONLY
+                || additionaClientModeManagerRole == ROLE_CLIENT_SECONDARY_LONG_LIVED) {
+            assertEquals(WifiNative.DUAL_STA_NON_TRANSIENT_UNBIASED, lastUseCaseSet);
+        } else if (additionaClientModeManagerRole == ROLE_CLIENT_SECONDARY_TRANSIENT) {
+            assertEquals(WifiNative.DUAL_STA_TRANSIENT_PREFER_PRIMARY, lastUseCaseSet);
+        }
+
+        // verify last set of primary connection is for WIFI_IFACE_NAME
+        ArgumentCaptor<String> ifaceNameCaptor = ArgumentCaptor.forClass(String.class);
+        verify(mWifiNative, atLeastOnce()).setMultiStaPrimaryConnection(ifaceNameCaptor.capture());
+        assertEquals(WIFI_IFACE_NAME, ifaceNameCaptor.getValue());
+
+        // Returns the new local only client mode manager.
+        ArgumentCaptor<ClientModeManager> requestedClientModeManager =
+                ArgumentCaptor.forClass(ClientModeManager.class);
+        verify(externalRequestListener).onAnswer(requestedClientModeManager.capture());
+        assertEquals(additionalClientModeManager, requestedClientModeManager.getValue());
+        // the additional CMM never became primary
+        verify(mPrimaryChangedCallback, never()).onChange(any(), eq(additionalClientModeManager));
+
+        return additionalClientListener.value;
+    }
+
+    private void requestRemoveAdditionalClientModeManager(
+            ClientConnectivityRole role) throws Exception {
+        ConcreteClientModeManager additionalClientModeManager =
+                mock(ConcreteClientModeManager.class);
+        ExternalClientModeManagerRequestListener externalRequestListener = mock(
+                ExternalClientModeManagerRequestListener.class);
+        Listener<ConcreteClientModeManager> additionalClientListener =
+                requestAdditionalClientModeManager(role, additionalClientModeManager,
+                        externalRequestListener, TEST_SSID_2, TEST_BSSID_2);
+
+        mActiveModeWarden.removeClientModeManager(additionalClientModeManager);
+        mLooper.dispatchAll();
+        verify(additionalClientModeManager).stop();
+        additionalClientListener.onStopped(additionalClientModeManager);
+        mLooper.dispatchAll();
+        verify(mModeChangeCallback).onActiveModeManagerRemoved(additionalClientModeManager);
+        // the additional CMM still never became primary
+        verify(mPrimaryChangedCallback, never()).onChange(any(), eq(additionalClientModeManager));
+    }
+
+    private void requestRemoveAdditionalClientModeManagerWhenNotAllowed(
+            ClientConnectivityRole role) throws Exception {
+        enterClientModeActiveState();
+
+        // Connected to ssid1/bssid1
+        WifiConfiguration config1 = new WifiConfiguration();
+        config1.SSID = TEST_SSID_1;
+        when(mClientModeManager.getConnectedWifiConfiguration()).thenReturn(config1);
+        when(mClientModeManager.getConnectedBssid()).thenReturn(TEST_BSSID_1);
+
+        ConcreteClientModeManager additionalClientModeManager =
+                mock(ConcreteClientModeManager.class);
+        Mutable<Listener<ConcreteClientModeManager>> additionalClientListener =
+                new Mutable<>();
+        doAnswer((invocation) -> {
+            Object[] args = invocation.getArguments();
+            additionalClientListener.value =
+                    (Listener<ConcreteClientModeManager>) args[0];
+            return additionalClientModeManager;
+        }).when(mWifiInjector).makeClientModeManager(
+                any(Listener.class), any(), any(), anyBoolean());
+        when(additionalClientModeManager.getInterfaceName()).thenReturn(WIFI_IFACE_NAME_1);
+        when(additionalClientModeManager.getRole()).thenReturn(role);
+
+        ExternalClientModeManagerRequestListener externalRequestListener = mock(
+                ExternalClientModeManagerRequestListener.class);
+        // request for ssid2/bssid2
+        if (role == ROLE_CLIENT_LOCAL_ONLY) {
+            mActiveModeWarden.requestLocalOnlyClientModeManager(
+                    externalRequestListener, TEST_WORKSOURCE, TEST_SSID_2, TEST_BSSID_2);
+        } else if (role == ROLE_CLIENT_SECONDARY_LONG_LIVED) {
+            mActiveModeWarden.requestSecondaryLongLivedClientModeManager(
+                    externalRequestListener, TEST_WORKSOURCE, TEST_SSID_2, TEST_BSSID_2);
+        } else if (role == ROLE_CLIENT_SECONDARY_TRANSIENT) {
+            mActiveModeWarden.requestSecondaryTransientClientModeManager(
+                    externalRequestListener, TEST_WORKSOURCE, TEST_SSID_2, TEST_BSSID_2);
+        }
+        mLooper.dispatchAll();
+        verifyNoMoreInteractions(additionalClientModeManager);
+        // Returns the existing primary client mode manager.
+        ArgumentCaptor<ClientModeManager> requestedClientModeManager =
+                ArgumentCaptor.forClass(ClientModeManager.class);
+        verify(externalRequestListener).onAnswer(requestedClientModeManager.capture());
+        assertEquals(mClientModeManager, requestedClientModeManager.getValue());
+
+        mActiveModeWarden.removeClientModeManager(requestedClientModeManager.getValue());
+        mLooper.dispatchAll();
+        verifyNoMoreInteractions(additionalClientModeManager);
+    }
+
+    private void requestAdditionalClientModeManagerWhenWifiIsOff(
+            ClientConnectivityRole role) throws Exception {
+        ExternalClientModeManagerRequestListener externalRequestListener = mock(
+                ExternalClientModeManagerRequestListener.class);
+        if (role == ROLE_CLIENT_LOCAL_ONLY) {
+            mActiveModeWarden.requestLocalOnlyClientModeManager(
+                    externalRequestListener, TEST_WORKSOURCE, TEST_SSID_1, TEST_BSSID_1);
+        } else if (role == ROLE_CLIENT_SECONDARY_LONG_LIVED) {
+            mActiveModeWarden.requestSecondaryLongLivedClientModeManager(
+                    externalRequestListener, TEST_WORKSOURCE, TEST_SSID_1, TEST_BSSID_1);
+        } else if (role == ROLE_CLIENT_SECONDARY_TRANSIENT) {
+            mActiveModeWarden.requestSecondaryTransientClientModeManager(
+                    externalRequestListener, TEST_WORKSOURCE, TEST_SSID_1, TEST_BSSID_1);
+        }
+        mLooper.dispatchAll();
+
+        verify(externalRequestListener).onAnswer(null);
+    }
+
+    public void requestAdditionalClientModeManagerWhenAlreadyPresent(
+            ClientConnectivityRole role) throws Exception {
+        ConcreteClientModeManager additionalClientModeManager =
+                mock(ConcreteClientModeManager.class);
+        ExternalClientModeManagerRequestListener externalRequestListener = mock(
+                ExternalClientModeManagerRequestListener.class);
+        requestAdditionalClientModeManager(role, additionalClientModeManager,
+                externalRequestListener, TEST_SSID_2, TEST_BSSID_2);
+
+        // set additional CMM connected to ssid2/bssid2
+        WifiConfiguration config2 = new WifiConfiguration();
+        config2.SSID = TEST_SSID_2;
+        when(additionalClientModeManager.getConnectedWifiConfiguration()).thenReturn(config2);
+        when(additionalClientModeManager.getConnectedBssid()).thenReturn(TEST_BSSID_2);
+
+        // request for ssid3/bssid3
+        // request for one more CMM (returns the existing one).
+        if (role == ROLE_CLIENT_LOCAL_ONLY) {
+            mActiveModeWarden.requestLocalOnlyClientModeManager(
+                    externalRequestListener, TEST_WORKSOURCE, TEST_SSID_3, TEST_BSSID_3);
+        } else if (role == ROLE_CLIENT_SECONDARY_LONG_LIVED) {
+            mActiveModeWarden.requestSecondaryLongLivedClientModeManager(
+                    externalRequestListener, TEST_WORKSOURCE, TEST_SSID_3, TEST_BSSID_3);
+        } else if (role == ROLE_CLIENT_SECONDARY_TRANSIENT) {
+            mActiveModeWarden.requestSecondaryTransientClientModeManager(
+                    externalRequestListener, TEST_WORKSOURCE, TEST_SSID_3, TEST_BSSID_3);
+        }
+        mLooper.dispatchAll();
+
+        // Don't make another client mode manager.
+        verify(mWifiInjector, times(1))
+                .makeClientModeManager(any(), any(), eq(role), anyBoolean());
+        // Returns the existing client mode manager.
+        ArgumentCaptor<ClientModeManager> requestedClientModeManager =
+                ArgumentCaptor.forClass(ClientModeManager.class);
+        verify(externalRequestListener, times(2)).onAnswer(requestedClientModeManager.capture());
+        assertEquals(additionalClientModeManager, requestedClientModeManager.getValue());
+    }
+
+    private void requestAdditionalClientModeManagerWhenConnectingToPrimaryBssid(
+            ClientConnectivityRole role) throws Exception {
+        enterClientModeActiveState();
+
+        // Connected to ssid1/bssid1
+        WifiConfiguration config1 = new WifiConfiguration();
+        config1.SSID = TEST_SSID_1;
+        when(mClientModeManager.getConnectedWifiConfiguration()).thenReturn(config1);
+        when(mClientModeManager.getConnectedBssid()).thenReturn(TEST_BSSID_1);
+
+        ConcreteClientModeManager additionalClientModeManager =
+                mock(ConcreteClientModeManager.class);
+        Mutable<Listener<ConcreteClientModeManager>> additionalClientListener =
+                new Mutable<>();
+        doAnswer((invocation) -> {
+            Object[] args = invocation.getArguments();
+            additionalClientListener.value =
+                    (Listener<ConcreteClientModeManager>) args[0];
+            return additionalClientModeManager;
+        }).when(mWifiInjector).makeClientModeManager(
+                any(Listener.class), any(), any(), anyBoolean());
+        when(additionalClientModeManager.getInterfaceName()).thenReturn(WIFI_IFACE_NAME_1);
+        when(additionalClientModeManager.getRole()).thenReturn(role);
+
+        ExternalClientModeManagerRequestListener externalRequestListener = mock(
+                ExternalClientModeManagerRequestListener.class);
+        // request for same ssid1/bssid1
+        if (role == ROLE_CLIENT_LOCAL_ONLY) {
+            mActiveModeWarden.requestLocalOnlyClientModeManager(
+                    externalRequestListener, TEST_WORKSOURCE, TEST_SSID_1, TEST_BSSID_1);
+        } else if (role == ROLE_CLIENT_SECONDARY_LONG_LIVED) {
+            mActiveModeWarden.requestSecondaryLongLivedClientModeManager(
+                    externalRequestListener, TEST_WORKSOURCE, TEST_SSID_1, TEST_BSSID_1);
+        } else if (role == ROLE_CLIENT_SECONDARY_TRANSIENT) {
+            mActiveModeWarden.requestSecondaryTransientClientModeManager(
+                    externalRequestListener, TEST_WORKSOURCE, TEST_SSID_1, TEST_BSSID_1);
+        }
+        mLooper.dispatchAll();
+        verifyNoMoreInteractions(additionalClientModeManager);
+        // Returns the existing primary client mode manager.
+        ArgumentCaptor<ClientModeManager> requestedClientModeManager =
+                ArgumentCaptor.forClass(ClientModeManager.class);
+        verify(externalRequestListener).onAnswer(requestedClientModeManager.capture());
+        assertEquals(mClientModeManager, requestedClientModeManager.getValue());
+    }
+
+    @Test
+    public void requestRemoveLocalOnlyClientModeManager() throws Exception {
+        // Ensure that we can create more client ifaces.
+        when(mWifiNative.isItPossibleToCreateStaIface(any())).thenReturn(true);
+        when(mResources.getBoolean(R.bool.config_wifiMultiStaLocalOnlyConcurrencyEnabled))
+                .thenReturn(true);
+        assertTrue(mActiveModeWarden.canRequestMoreClientModeManagersInRole(
+                TEST_WORKSOURCE, ROLE_CLIENT_LOCAL_ONLY));
+
+        requestRemoveAdditionalClientModeManager(ROLE_CLIENT_LOCAL_ONLY);
+    }
+
+    @Test
+    public void requestRemoveLocalOnlyClientModeManagerWhenStaStaNotSupported() throws Exception {
+        // Ensure that we cannot create more client ifaces.
+        when(mWifiNative.isItPossibleToCreateStaIface(any())).thenReturn(false);
+        assertFalse(mActiveModeWarden.canRequestMoreClientModeManagersInRole(
+                TEST_WORKSOURCE, ROLE_CLIENT_LOCAL_ONLY));
+        requestRemoveAdditionalClientModeManagerWhenNotAllowed(ROLE_CLIENT_LOCAL_ONLY);
+    }
+
+    @Test
+    public void requestRemoveLocalOnlyClientModeManagerWhenFeatureDisabled() throws Exception {
+        // Ensure that we can create more client ifaces.
+        when(mWifiNative.isItPossibleToCreateStaIface(any())).thenReturn(true);
+        when(mResources.getBoolean(R.bool.config_wifiMultiStaLocalOnlyConcurrencyEnabled))
+                .thenReturn(false);
+        assertFalse(mActiveModeWarden.canRequestMoreClientModeManagersInRole(
+                TEST_WORKSOURCE, ROLE_CLIENT_LOCAL_ONLY));
+        requestRemoveAdditionalClientModeManagerWhenNotAllowed(ROLE_CLIENT_LOCAL_ONLY);
+    }
+
+    @Test
+    public void requestLocalOnlyClientModeManagerWhenWifiIsOff() throws Exception {
+        // Ensure that we can create more client ifaces.
+        when(mWifiNative.isItPossibleToCreateStaIface(any())).thenReturn(true);
+        assertFalse(mActiveModeWarden.canRequestMoreClientModeManagersInRole(
+                TEST_WORKSOURCE, ROLE_CLIENT_LOCAL_ONLY));
+
+        requestAdditionalClientModeManagerWhenWifiIsOff(ROLE_CLIENT_LOCAL_ONLY);
+    }
+
+    @Test
+    public void requestLocalOnlyClientModeManagerWhenAlreadyPresent() throws Exception {
+        // Ensure that we can create more client ifaces.
+        when(mWifiNative.isItPossibleToCreateStaIface(any())).thenReturn(true);
+        when(mResources.getBoolean(R.bool.config_wifiMultiStaLocalOnlyConcurrencyEnabled))
+                .thenReturn(true);
+        assertTrue(mActiveModeWarden.canRequestMoreClientModeManagersInRole(
+                TEST_WORKSOURCE, ROLE_CLIENT_LOCAL_ONLY));
+
+        requestAdditionalClientModeManagerWhenAlreadyPresent(ROLE_CLIENT_LOCAL_ONLY);
+    }
+
+    @Test
+    public void requestLocalOnlyClientModeManagerWhenConnectingToPrimaryBssid() throws Exception {
+        // Ensure that we can create more client ifaces.
+        when(mWifiNative.isItPossibleToCreateStaIface(any())).thenReturn(true);
+        when(mResources.getBoolean(R.bool.config_wifiMultiStaLocalOnlyConcurrencyEnabled))
+                .thenReturn(true);
+        assertTrue(mActiveModeWarden.canRequestMoreClientModeManagersInRole(
+                TEST_WORKSOURCE, ROLE_CLIENT_LOCAL_ONLY));
+
+        requestAdditionalClientModeManagerWhenConnectingToPrimaryBssid(ROLE_CLIENT_LOCAL_ONLY);
+    }
+
+    @Test
+    public void requestRemoveLocalOnlyClientModeManagerWhenNotSystemAppAndTargetSdkLessThanS()
+            throws Exception {
+        // Ensure that we can create more client ifaces.
+        when(mWifiNative.isItPossibleToCreateStaIface(any())).thenReturn(true);
+        when(mResources.getBoolean(R.bool.config_wifiMultiStaLocalOnlyConcurrencyEnabled))
+                .thenReturn(true);
+        when(mWifiPermissionsUtil.isSystem(TEST_PACKAGE, TEST_UID)).thenReturn(false);
+        when(mWifiPermissionsUtil.isTargetSdkLessThan(
+                TEST_PACKAGE, Build.VERSION_CODES.S, TEST_UID))
+                .thenReturn(true);
+        assertFalse(mActiveModeWarden.canRequestMoreClientModeManagersInRole(
+                TEST_WORKSOURCE, ROLE_CLIENT_LOCAL_ONLY));
+        requestRemoveAdditionalClientModeManagerWhenNotAllowed(ROLE_CLIENT_LOCAL_ONLY);
+    }
+
+    @Test
+    public void requestRemoveLocalOnlyClientModeManagerWhenNotSystemAppAndTargetSdkEqualToS()
+            throws Exception {
+        // Ensure that we can create more client ifaces.
+        when(mWifiNative.isItPossibleToCreateStaIface(any())).thenReturn(true);
+        when(mResources.getBoolean(R.bool.config_wifiMultiStaLocalOnlyConcurrencyEnabled))
+                .thenReturn(true);
+        when(mWifiPermissionsUtil.isSystem(TEST_PACKAGE, TEST_UID)).thenReturn(false);
+        when(mWifiPermissionsUtil.isTargetSdkLessThan(
+                TEST_PACKAGE, Build.VERSION_CODES.S, TEST_UID))
+                .thenReturn(false);
+        assertTrue(mActiveModeWarden.canRequestMoreClientModeManagersInRole(
+                TEST_WORKSOURCE, ROLE_CLIENT_LOCAL_ONLY));
+        requestRemoveAdditionalClientModeManager(ROLE_CLIENT_LOCAL_ONLY);
+    }
+
+    @Test
+    public void requestRemoveSecondaryLongLivedClientModeManager() throws Exception {
+        // Ensure that we can create more client ifaces.
+        when(mWifiNative.isItPossibleToCreateStaIface(any())).thenReturn(true);
+        when(mResources.getBoolean(R.bool.config_wifiMultiStaRestrictedConcurrencyEnabled))
+                .thenReturn(true);
+        assertTrue(mActiveModeWarden.canRequestMoreClientModeManagersInRole(
+                TEST_WORKSOURCE, ROLE_CLIENT_SECONDARY_LONG_LIVED));
+
+        requestRemoveAdditionalClientModeManager(ROLE_CLIENT_SECONDARY_LONG_LIVED);
+    }
+
+    @Test
+    public void requestRemoveSecondaryLongLivedClientModeManagerWhenStaStaNotSupported()
+            throws Exception {
+        // Ensure that we cannot create more client ifaces.
+        when(mWifiNative.isItPossibleToCreateStaIface(any())).thenReturn(false);
+        assertFalse(mActiveModeWarden.canRequestMoreClientModeManagersInRole(
+                TEST_WORKSOURCE, ROLE_CLIENT_SECONDARY_LONG_LIVED));
+        requestRemoveAdditionalClientModeManagerWhenNotAllowed(ROLE_CLIENT_SECONDARY_LONG_LIVED);
+    }
+
+    @Test
+    public void requestRemoveSecondaryLongLivedClientModeManagerWhenFeatureDisabled()
+            throws Exception {
+        // Ensure that we can create more client ifaces.
+        when(mWifiNative.isItPossibleToCreateStaIface(any())).thenReturn(true);
+        when(mResources.getBoolean(R.bool.config_wifiMultiStaRestrictedConcurrencyEnabled))
+                .thenReturn(false);
+        assertFalse(mActiveModeWarden.canRequestMoreClientModeManagersInRole(
+                TEST_WORKSOURCE, ROLE_CLIENT_SECONDARY_LONG_LIVED));
+        requestRemoveAdditionalClientModeManagerWhenNotAllowed(ROLE_CLIENT_SECONDARY_LONG_LIVED);
+    }
+
+    @Test
+    public void requestSecondaryLongLivedClientModeManagerWhenWifiIsOff() throws Exception {
+        // Ensure that we can create more client ifaces.
+        when(mWifiNative.isItPossibleToCreateStaIface(any())).thenReturn(true);
+        when(mResources.getBoolean(R.bool.config_wifiMultiStaRestrictedConcurrencyEnabled))
+                .thenReturn(true);
+        assertTrue(mActiveModeWarden.canRequestMoreClientModeManagersInRole(
+                TEST_WORKSOURCE, ROLE_CLIENT_SECONDARY_LONG_LIVED));
+
+        requestAdditionalClientModeManagerWhenWifiIsOff(ROLE_CLIENT_SECONDARY_LONG_LIVED);
+    }
+
+    @Test
+    public void requestSecondaryLongLivedClientModeManagerWhenAlreadyPresent() throws Exception {
+        // Ensure that we can create more client ifaces.
+        when(mWifiNative.isItPossibleToCreateStaIface(any())).thenReturn(true);
+        when(mResources.getBoolean(R.bool.config_wifiMultiStaRestrictedConcurrencyEnabled))
+                .thenReturn(true);
+        assertTrue(mActiveModeWarden.canRequestMoreClientModeManagersInRole(
+                TEST_WORKSOURCE, ROLE_CLIENT_SECONDARY_LONG_LIVED));
+
+        requestAdditionalClientModeManagerWhenAlreadyPresent(ROLE_CLIENT_SECONDARY_LONG_LIVED);
+    }
+
+    @Test
+    public void requestSecondaryLongLivedClientModeManagerWhenConnectingToPrimaryBssid()
+            throws Exception {
+        // Ensure that we can create more client ifaces.
+        when(mWifiNative.isItPossibleToCreateStaIface(any())).thenReturn(true);
+        when(mResources.getBoolean(R.bool.config_wifiMultiStaRestrictedConcurrencyEnabled))
+                .thenReturn(true);
+        assertTrue(mActiveModeWarden.canRequestMoreClientModeManagersInRole(
+                TEST_WORKSOURCE, ROLE_CLIENT_SECONDARY_LONG_LIVED));
+
+        requestAdditionalClientModeManagerWhenConnectingToPrimaryBssid(
+                ROLE_CLIENT_SECONDARY_LONG_LIVED);
+    }
+
+    @Test
+    public void requestRemoveSecondaryTransientClientModeManager() throws Exception {
+        // Ensure that we can create more client ifaces.
+        when(mWifiNative.isItPossibleToCreateStaIface(any())).thenReturn(true);
+        when(mResources.getBoolean(
+                R.bool.config_wifiMultiStaNetworkSwitchingMakeBeforeBreakEnabled))
+                .thenReturn(true);
+        assertTrue(mActiveModeWarden.canRequestMoreClientModeManagersInRole(
+                TEST_WORKSOURCE, ROLE_CLIENT_SECONDARY_TRANSIENT));
+
+        requestRemoveAdditionalClientModeManager(ROLE_CLIENT_SECONDARY_TRANSIENT);
+    }
+
+    @Test
+    public void requestRemoveSecondaryTransientClientModeManagerWhenStaStaNotSupported()
+            throws Exception {
+        // Ensure that we cannot create more client ifaces.
+        when(mWifiNative.isItPossibleToCreateStaIface(any())).thenReturn(false);
+        assertFalse(mActiveModeWarden.canRequestMoreClientModeManagersInRole(
+                TEST_WORKSOURCE, ROLE_CLIENT_SECONDARY_TRANSIENT));
+        requestRemoveAdditionalClientModeManagerWhenNotAllowed(ROLE_CLIENT_SECONDARY_TRANSIENT);
+    }
+
+    @Test
+    public void requestRemoveSecondaryTransientClientModeManagerWhenFeatureDisabled()
+            throws Exception {
+        // Ensure that we can create more client ifaces.
+        when(mWifiNative.isItPossibleToCreateStaIface(any())).thenReturn(true);
+        when(mResources.getBoolean(
+                R.bool.config_wifiMultiStaNetworkSwitchingMakeBeforeBreakEnabled))
+                .thenReturn(false);
+        assertFalse(mActiveModeWarden.canRequestMoreClientModeManagersInRole(
+                TEST_WORKSOURCE, ROLE_CLIENT_SECONDARY_TRANSIENT));
+        requestRemoveAdditionalClientModeManagerWhenNotAllowed(ROLE_CLIENT_SECONDARY_TRANSIENT);
+    }
+
+    @Test
+    public void requestSecondaryTransientClientModeManagerWhenWifiIsOff() throws Exception {
+        // Ensure that we can create more client ifaces.
+        when(mWifiNative.isItPossibleToCreateStaIface(any())).thenReturn(true);
+        when(mResources.getBoolean(
+                R.bool.config_wifiMultiStaNetworkSwitchingMakeBeforeBreakEnabled))
+                .thenReturn(true);
+        assertTrue(mActiveModeWarden.canRequestMoreClientModeManagersInRole(
+                TEST_WORKSOURCE, ROLE_CLIENT_SECONDARY_TRANSIENT));
+
+        requestAdditionalClientModeManagerWhenWifiIsOff(ROLE_CLIENT_SECONDARY_TRANSIENT);
+    }
+
+    @Test
+    public void requestSecondaryTransientClientModeManagerWhenAlreadyPresent() throws Exception {
+        // Ensure that we can create more client ifaces.
+        when(mWifiNative.isItPossibleToCreateStaIface(any())).thenReturn(true);
+        when(mResources.getBoolean(
+                R.bool.config_wifiMultiStaNetworkSwitchingMakeBeforeBreakEnabled))
+                .thenReturn(true);
+        assertTrue(mActiveModeWarden.canRequestMoreClientModeManagersInRole(
+                TEST_WORKSOURCE, ROLE_CLIENT_SECONDARY_TRANSIENT));
+
+        requestAdditionalClientModeManagerWhenAlreadyPresent(ROLE_CLIENT_SECONDARY_TRANSIENT);
+    }
+
+    @Test
+    public void requestSecondaryTransientClientModeManagerWhenConnectingToPrimaryBssid()
+            throws Exception {
+        // Ensure that we can create more client ifaces.
+        when(mWifiNative.isItPossibleToCreateStaIface(any())).thenReturn(true);
+        when(mResources.getBoolean(
+                R.bool.config_wifiMultiStaNetworkSwitchingMakeBeforeBreakEnabled))
+                .thenReturn(true);
+        assertTrue(mActiveModeWarden.canRequestMoreClientModeManagersInRole(
+                TEST_WORKSOURCE, ROLE_CLIENT_SECONDARY_TRANSIENT));
+
+        requestAdditionalClientModeManagerWhenConnectingToPrimaryBssid(
+                ROLE_CLIENT_SECONDARY_TRANSIENT);
+    }
+
+    @Test
+    public void requestHighPrioSecondaryTransientClientModeManagerWhenConnectedToLocalOnlyBssid()
+            throws Exception {
+        // Ensure that we can create more client ifaces.
+        when(mWifiNative.isItPossibleToCreateStaIface(any())).thenReturn(true);
+        when(mResources.getBoolean(
+                R.bool.config_wifiMultiStaLocalOnlyConcurrencyEnabled))
+                .thenReturn(true);
+        when(mResources.getBoolean(
+                R.bool.config_wifiMultiStaNetworkSwitchingMakeBeforeBreakEnabled))
+                .thenReturn(true);
+        assertTrue(mActiveModeWarden.canRequestMoreClientModeManagersInRole(
+                TEST_WORKSOURCE, ROLE_CLIENT_LOCAL_ONLY));
+        assertTrue(mActiveModeWarden.canRequestMoreClientModeManagersInRole(
+                TEST_WORKSOURCE, ROLE_CLIENT_SECONDARY_TRANSIENT));
+
+        enterClientModeActiveState();
+
+        // Primary Connected to ssid1/bssid1
+        WifiConfiguration config1 = new WifiConfiguration();
+        config1.SSID = TEST_SSID_1;
+        when(mClientModeManager.getConnectedWifiConfiguration()).thenReturn(config1);
+        when(mClientModeManager.getConnectedBssid()).thenReturn(TEST_BSSID_1);
+
+        ConcreteClientModeManager additionalClientModeManager =
+                mock(ConcreteClientModeManager.class);
+        Mutable<Listener<ConcreteClientModeManager>> additionalClientListener1 =
+                new Mutable<>();
+        doAnswer((invocation) -> {
+            Object[] args = invocation.getArguments();
+            additionalClientListener1.value =
+                    (Listener<ConcreteClientModeManager>) args[0];
+            return additionalClientModeManager;
+        }).when(mWifiInjector).makeClientModeManager(
+                any(Listener.class), any(), eq(ROLE_CLIENT_LOCAL_ONLY),
+                anyBoolean());
+        when(additionalClientModeManager.getRole()).thenReturn(ROLE_CLIENT_LOCAL_ONLY);
+
+        ExternalClientModeManagerRequestListener externalRequestListener = mock(
+                ExternalClientModeManagerRequestListener.class);
+        // request for ssid2/bssid2
+        mActiveModeWarden.requestLocalOnlyClientModeManager(
+                externalRequestListener, TEST_WORKSOURCE, TEST_SSID_2, TEST_BSSID_2);
+        mLooper.dispatchAll();
+        verify(mWifiInjector).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_LOCAL_ONLY), anyBoolean());
+        additionalClientListener1.value.onStarted(additionalClientModeManager);
+        mLooper.dispatchAll();
+        // Returns the new client mode manager.
+        ArgumentCaptor<ClientModeManager> requestedClientModeManager =
+                ArgumentCaptor.forClass(ClientModeManager.class);
+        verify(externalRequestListener).onAnswer(requestedClientModeManager.capture());
+        assertEquals(additionalClientModeManager, requestedClientModeManager.getValue());
+
+        // set additional CMM connected to ssid2/bssid2
+        WifiConfiguration config2 = new WifiConfiguration();
+        config2.SSID = TEST_SSID_2;
+        when(additionalClientModeManager.getConnectedWifiConfiguration()).thenReturn(config2);
+        when(additionalClientModeManager.getConnectedBssid()).thenReturn(TEST_BSSID_2);
+
+        // request for same ssid2/bssid2 for a different role.
+        // request for one more CMM (should return the existing local only one).
+        mActiveModeWarden.requestSecondaryTransientClientModeManager(
+                externalRequestListener, TEST_WORKSOURCE, TEST_SSID_2, TEST_BSSID_2);
+        mLooper.dispatchAll();
+
+        // Don't make another client mode manager, but should switch role of existing client mode
+        // manager.
+        verify(mWifiInjector, never())
+                .makeClientModeManager(any(), any(), eq(ROLE_CLIENT_SECONDARY_TRANSIENT),
+                        anyBoolean());
+        ArgumentCaptor<Listener<ConcreteClientModeManager>>
+                additionalClientListener2 = ArgumentCaptor.forClass(
+                        Listener.class);
+        verify(additionalClientModeManager).setRole(eq(ROLE_CLIENT_SECONDARY_TRANSIENT),
+                eq(TEST_WORKSOURCE), additionalClientListener2.capture());
+
+        // Simulate completion of role switch.
+        additionalClientListener2.getValue().onRoleChanged(additionalClientModeManager);
+
+        // Returns the existing client mode manager.
+        verify(externalRequestListener, times(2)).onAnswer(requestedClientModeManager.capture());
+        assertEquals(additionalClientModeManager, requestedClientModeManager.getValue());
+    }
+
+    @Test
+    public void requestLowPrioSecondaryTransientClientModeManagerWhenConnectedToLocalOnlyBssid()
+            throws Exception {
+        // Ensure that we can create more client ifaces.
+        when(mWifiNative.isItPossibleToCreateStaIface(any())).thenReturn(true);
+        when(mResources.getBoolean(
+                R.bool.config_wifiMultiStaLocalOnlyConcurrencyEnabled))
+                .thenReturn(true);
+        when(mResources.getBoolean(
+                R.bool.config_wifiMultiStaNetworkSwitchingMakeBeforeBreakEnabled))
+                .thenReturn(true);
+        assertTrue(mActiveModeWarden.canRequestMoreClientModeManagersInRole(
+                TEST_WORKSOURCE, ROLE_CLIENT_LOCAL_ONLY));
+        assertTrue(mActiveModeWarden.canRequestMoreClientModeManagersInRole(
+                TEST_WORKSOURCE, ROLE_CLIENT_SECONDARY_TRANSIENT));
+
+        enterClientModeActiveState();
+
+        // Primary Connected to ssid1/bssid1
+        WifiConfiguration config1 = new WifiConfiguration();
+        config1.SSID = TEST_SSID_1;
+        when(mClientModeManager.getConnectedWifiConfiguration()).thenReturn(config1);
+        when(mClientModeManager.getConnectedBssid()).thenReturn(TEST_BSSID_1);
+
+        ConcreteClientModeManager additionalClientModeManager =
+                mock(ConcreteClientModeManager.class);
+        Mutable<Listener<ConcreteClientModeManager>> additionalClientListener1 =
+                new Mutable<>();
+        doAnswer((invocation) -> {
+            Object[] args = invocation.getArguments();
+            additionalClientListener1.value =
+                    (Listener<ConcreteClientModeManager>) args[0];
+            return additionalClientModeManager;
+        }).when(mWifiInjector).makeClientModeManager(
+                any(Listener.class), any(), eq(ROLE_CLIENT_LOCAL_ONLY),
+                anyBoolean());
+        when(additionalClientModeManager.getRole()).thenReturn(ROLE_CLIENT_LOCAL_ONLY);
+
+        ExternalClientModeManagerRequestListener externalRequestListener = mock(
+                ExternalClientModeManagerRequestListener.class);
+        // request for ssid2/bssid2
+        mActiveModeWarden.requestLocalOnlyClientModeManager(
+                externalRequestListener, TEST_WORKSOURCE, TEST_SSID_2, TEST_BSSID_2);
+        mLooper.dispatchAll();
+        verify(mWifiInjector).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_LOCAL_ONLY), anyBoolean());
+        additionalClientListener1.value.onStarted(additionalClientModeManager);
+        mLooper.dispatchAll();
+        // Returns the new client mode manager.
+        ArgumentCaptor<ClientModeManager> requestedClientModeManager =
+                ArgumentCaptor.forClass(ClientModeManager.class);
+        verify(externalRequestListener).onAnswer(requestedClientModeManager.capture());
+        assertEquals(additionalClientModeManager, requestedClientModeManager.getValue());
+
+        // set additional CMM connected to ssid2/bssid2
+        WifiConfiguration config2 = new WifiConfiguration();
+        config2.SSID = TEST_SSID_2;
+        when(additionalClientModeManager.getConnectedWifiConfiguration()).thenReturn(config2);
+        when(additionalClientModeManager.getConnectedBssid()).thenReturn(TEST_BSSID_2);
+
+        // Now, deny the creation of STA for the new request
+        when(mWifiNative.isItPossibleToCreateStaIface(any())).thenReturn(false);
+
+        // request for same ssid2/bssid2 for a different role.
+        // request for one more CMM (should return null).
+        mActiveModeWarden.requestSecondaryTransientClientModeManager(
+                externalRequestListener, TEST_WORKSOURCE, TEST_SSID_2, TEST_BSSID_2);
+        mLooper.dispatchAll();
+
+        // Don't make another client mode manager or change role
+        verify(mWifiInjector, never())
+                .makeClientModeManager(any(), any(), eq(ROLE_CLIENT_SECONDARY_TRANSIENT),
+                        anyBoolean());
+        verify(additionalClientModeManager, never()).setRole(eq(ROLE_CLIENT_SECONDARY_TRANSIENT),
+                eq(TEST_WORKSOURCE), any());
+
+        // Ensure the request is rejected.
+        verify(externalRequestListener, times(2)).onAnswer(requestedClientModeManager.capture());
+        assertNull(requestedClientModeManager.getValue());
+    }
+
+    @Test
+    public void requestSecondaryTransientClientModeManagerWhenDppInProgress()
+            throws Exception {
+        // Ensure that we can create more client ifaces.
+        when(mWifiNative.isItPossibleToCreateStaIface(any())).thenReturn(true);
+        when(mResources.getBoolean(
+                R.bool.config_wifiMultiStaNetworkSwitchingMakeBeforeBreakEnabled))
+                .thenReturn(true);
+        assertTrue(mActiveModeWarden.canRequestMoreClientModeManagersInRole(
+                TEST_WORKSOURCE, ROLE_CLIENT_SECONDARY_TRANSIENT));
+
+        // Create primary STA.
+        enterClientModeActiveState();
+
+        // Start DPP session
+        when(mDppManager.isSessionInProgress()).thenReturn(true);
+
+        // request secondary transient CMM creation.
+        ConcreteClientModeManager additionalClientModeManager =
+                mock(ConcreteClientModeManager.class);
+        Mutable<Listener<ConcreteClientModeManager>> additionalClientListener =
+                new Mutable<>();
+        doAnswer((invocation) -> {
+            Object[] args = invocation.getArguments();
+            additionalClientListener.value =
+                    (Listener<ConcreteClientModeManager>) args[0];
+            return additionalClientModeManager;
+        }).when(mWifiInjector).makeClientModeManager(
+                any(Listener.class), any(), eq(ROLE_CLIENT_SECONDARY_TRANSIENT),
+                anyBoolean());
+        when(additionalClientModeManager.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_TRANSIENT);
+
+        ExternalClientModeManagerRequestListener externalRequestListener = mock(
+                ExternalClientModeManagerRequestListener.class);
+        mActiveModeWarden.requestSecondaryTransientClientModeManager(
+                externalRequestListener, TEST_WORKSOURCE, TEST_SSID_2, TEST_BSSID_2);
+        mLooper.dispatchAll();
+
+        // verify that we did not create a secondary CMM.
+        verifyNoMoreInteractions(additionalClientModeManager);
+        // Returns the existing primary client mode manager.
+        ArgumentCaptor<ClientModeManager> requestedClientModeManager =
+                ArgumentCaptor.forClass(ClientModeManager.class);
+        verify(externalRequestListener).onAnswer(requestedClientModeManager.capture());
+        assertEquals(mClientModeManager, requestedClientModeManager.getValue());
+
+        // Stop ongoing DPP session.
+        when(mDppManager.isSessionInProgress()).thenReturn(false);
+
+        // request secondary transient CMM creation again, now it should be allowed.
+        mActiveModeWarden.requestSecondaryTransientClientModeManager(
+                externalRequestListener, TEST_WORKSOURCE, TEST_SSID_2, TEST_BSSID_2);
+        mLooper.dispatchAll();
+        verify(mWifiInjector)
+                .makeClientModeManager(any(), eq(TEST_WORKSOURCE),
+                        eq(ROLE_CLIENT_SECONDARY_TRANSIENT), anyBoolean());
+        additionalClientListener.value.onStarted(additionalClientModeManager);
+        mLooper.dispatchAll();
+        // Returns the new secondary client mode manager.
+        verify(externalRequestListener, times(2)).onAnswer(requestedClientModeManager.capture());
+        assertEquals(additionalClientModeManager, requestedClientModeManager.getValue());
+    }
+
+    @Test
+    public void configureHwOnMbbSwitch()
+            throws Exception {
+        // Ensure that we can create more client ifaces.
+        when(mWifiNative.isItPossibleToCreateStaIface(any())).thenReturn(true);
+        when(mResources.getBoolean(
+                R.bool.config_wifiMultiStaNetworkSwitchingMakeBeforeBreakEnabled))
+                .thenReturn(true);
+        assertTrue(mActiveModeWarden.canRequestMoreClientModeManagersInRole(
+                TEST_WORKSOURCE, ROLE_CLIENT_SECONDARY_TRANSIENT));
+
+        ConcreteClientModeManager additionalClientModeManager =
+                mock(ConcreteClientModeManager.class);
+        ExternalClientModeManagerRequestListener externalRequestListener = mock(
+                ExternalClientModeManagerRequestListener.class);
+        Listener<ConcreteClientModeManager> additionalClientListener =
+                requestAdditionalClientModeManager(ROLE_CLIENT_SECONDARY_TRANSIENT,
+                        additionalClientModeManager, externalRequestListener, TEST_SSID_2,
+                        TEST_BSSID_2);
+
+        // Now simulate the MBB role switch.
+        when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_TRANSIENT);
+        mClientListener.onRoleChanged(mClientModeManager);
+
+        when(additionalClientModeManager.getRole()).thenReturn(ROLE_CLIENT_PRIMARY);
+        additionalClientListener.onRoleChanged(additionalClientModeManager);
+
+        // verify last use case set is PREFER_PRIMARY
+        ArgumentCaptor<Integer> useCaseCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mWifiNative, atLeastOnce()).setMultiStaUseCase(useCaseCaptor.capture());
+        int lastUseCaseSet = useCaseCaptor.getValue().intValue();
+        assertEquals(WifiNative.DUAL_STA_TRANSIENT_PREFER_PRIMARY, lastUseCaseSet);
+
+        // verify last set of primary connection is for WIFI_IFACE_NAME_1
+        ArgumentCaptor<String> ifaceNameCaptor = ArgumentCaptor.forClass(String.class);
+        verify(mWifiNative, atLeastOnce()).setMultiStaPrimaryConnection(ifaceNameCaptor.capture());
+        assertEquals(WIFI_IFACE_NAME_1, ifaceNameCaptor.getValue());
+    }
+
+    @Test
     public void airplaneModeToggleOnDisablesWifi() throws Exception {
         enterClientModeActiveState();
         assertInEnabledState();
@@ -2183,7 +3371,7 @@
             mLooper.dispatchAll();
         });
 
-        mClientListener.onStopped();
+        mClientListener.onStopped(mClientModeManager);
         mLooper.dispatchAll();
         assertInDisabledState();
     }
@@ -2199,7 +3387,7 @@
             mLooper.dispatchAll();
         });
 
-        mSoftApListener.onStopped();
+        mSoftApListener.onStopped(mSoftApManager);
         mLooper.dispatchAll();
         assertInDisabledState();
     }
@@ -2220,18 +3408,17 @@
 
         // APM toggle off before the stop is complete.
         assertInEnabledState();
-        when(mClientModeManager.isStopping()).thenReturn(true);
         when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
         mActiveModeWarden.airplaneModeToggled();
         mLooper.dispatchAll();
 
-        mClientListener.onStopped();
+        mClientListener.onStopped(mClientModeManager);
         mLooper.dispatchAll();
 
-        verify(mClientModeManager, times(2)).start();
-        verify(mClientModeManager, times(2)).setRole(ROLE_CLIENT_PRIMARY);
+        verify(mWifiInjector, times(2)).makeClientModeManager(
+                any(), any(), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
 
-        mClientListener.onStarted();
+        mClientListener.onStarted(mClientModeManager);
         mLooper.dispatchAll();
 
         // We should be back to enabled state.
@@ -2254,7 +3441,6 @@
 
         // APM toggle off before the stop is complete.
         assertInEnabledState();
-        when(mClientModeManager.isStopping()).thenReturn(true);
         when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
         mActiveModeWarden.airplaneModeToggled();
         // This test is identical to
@@ -2262,13 +3448,13 @@
         // dispatchAll() here is removed. There could be a race between airplaneModeToggled and
         // mClientListener.onStopped(). See b/160105640#comment5.
 
-        mClientListener.onStopped();
+        mClientListener.onStopped(mClientModeManager);
         mLooper.dispatchAll();
 
-        verify(mClientModeManager, times(2)).start();
-        verify(mClientModeManager, times(2)).setRole(ROLE_CLIENT_PRIMARY);
+        verify(mWifiInjector, times(2)).makeClientModeManager(
+                any(), any(), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
 
-        mClientListener.onStarted();
+        mClientListener.onStarted(mClientModeManager);
         mLooper.dispatchAll();
 
         // We should be back to enabled state.
@@ -2292,28 +3478,485 @@
 
         // APM toggle off before the stop is complete.
         assertInEnabledState();
-        when(mClientModeManager.isStopping()).thenReturn(true);
-        when(mSoftApManager.isStopping()).thenReturn(true);
         when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
         mActiveModeWarden.airplaneModeToggled();
         mLooper.dispatchAll();
 
         // AP stopped, should not process APM toggle.
-        mSoftApListener.onStopped();
+        mSoftApListener.onStopped(mSoftApManager);
         mLooper.dispatchAll();
-        verify(mClientModeManager, times(1)).start();
-        verify(mClientModeManager, times(1)).setRole(ROLE_CLIENT_PRIMARY);
+        verify(mWifiInjector, times(1)).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
 
         // STA also stopped, should process APM toggle.
-        mClientListener.onStopped();
+        mClientListener.onStopped(mClientModeManager);
         mLooper.dispatchAll();
-        verify(mClientModeManager, times(2)).start();
-        verify(mClientModeManager, times(2)).setRole(ROLE_CLIENT_PRIMARY);
+        verify(mWifiInjector, times(2)).makeClientModeManager(
+                any(), any(), eq(ROLE_CLIENT_PRIMARY), anyBoolean());
 
-        mClientListener.onStarted();
+        mClientListener.onStarted(mClientModeManager);
         mLooper.dispatchAll();
 
         // We should be back to enabled state.
         assertInEnabledState();
     }
+
+    @Test
+    public void propagateVerboseLoggingFlagToClientModeManager() throws Exception {
+        mActiveModeWarden.enableVerboseLogging(true);
+        enterClientModeActiveState();
+        assertInEnabledState();
+        verify(mWifiInjector).makeClientModeManager(any(), any(), any(), eq(true));
+
+        mActiveModeWarden.enableVerboseLogging(false);
+        verify(mClientModeManager).enableVerboseLogging(false);
+    }
+
+    @Test
+    public void propagateConnectedWifiScorerToPrimaryClientModeManager() throws Exception {
+        IBinder iBinder = mock(IBinder.class);
+        IWifiConnectedNetworkScorer iScorer = mock(IWifiConnectedNetworkScorer.class);
+        mActiveModeWarden.setWifiConnectedNetworkScorer(iBinder, iScorer);
+        verify(iScorer).onSetScoreUpdateObserver(mExternalScoreUpdateObserverProxy);
+        enterClientModeActiveState();
+        assertInEnabledState();
+        verify(mClientModeManager).setWifiConnectedNetworkScorer(iBinder, iScorer);
+
+        mActiveModeWarden.clearWifiConnectedNetworkScorer();
+        verify(mClientModeManager).clearWifiConnectedNetworkScorer();
+
+        mActiveModeWarden.setWifiConnectedNetworkScorer(iBinder, iScorer);
+        verify(mClientModeManager, times(2)).setWifiConnectedNetworkScorer(iBinder, iScorer);
+    }
+
+    @Test
+    public void propagateConnectedWifiScorerToPrimaryClientModeManager_enterScanOnlyState()
+            throws Exception {
+        IBinder iBinder = mock(IBinder.class);
+        IWifiConnectedNetworkScorer iScorer = mock(IWifiConnectedNetworkScorer.class);
+        mActiveModeWarden.setWifiConnectedNetworkScorer(iBinder, iScorer);
+        verify(iScorer).onSetScoreUpdateObserver(mExternalScoreUpdateObserverProxy);
+        enterClientModeActiveState();
+        assertInEnabledState();
+        verify(mClientModeManager).setWifiConnectedNetworkScorer(iBinder, iScorer);
+
+        enterScanOnlyModeActiveState(true);
+
+        verify(mClientModeManager).clearWifiConnectedNetworkScorer();
+    }
+
+    @Test
+    public void handleWifiScorerSetScoreUpdateObserverFailure() throws Exception {
+        IBinder iBinder = mock(IBinder.class);
+        IWifiConnectedNetworkScorer iScorer = mock(IWifiConnectedNetworkScorer.class);
+        doThrow(new RemoteException()).when(iScorer).onSetScoreUpdateObserver(any());
+        mActiveModeWarden.setWifiConnectedNetworkScorer(iBinder, iScorer);
+        verify(iScorer).onSetScoreUpdateObserver(mExternalScoreUpdateObserverProxy);
+        enterClientModeActiveState();
+        assertInEnabledState();
+        // Ensure we did not propagate the scorer.
+        verify(mClientModeManager, never()).setWifiConnectedNetworkScorer(iBinder, iScorer);
+    }
+
+    /** Verify that the primary changed callback is triggered when entering client mode. */
+    @Test
+    public void testAddPrimaryClientModeManager() throws Exception {
+        enterClientModeActiveState();
+
+        verify(mPrimaryChangedCallback).onChange(null, mClientModeManager);
+    }
+
+    /** Verify the primary changed callback is not triggered when there is no primary. */
+    @Test
+    public void testNoAddPrimaryClientModeManager() throws Exception {
+        enterScanOnlyModeActiveState();
+
+        verify(mPrimaryChangedCallback, never()).onChange(any(), any());
+    }
+
+    /**
+     * Verify the primary changed callback is triggered when changing the primary from one
+     * ClientModeManager to another.
+     */
+    @Test
+    public void testSwitchPrimaryClientModeManager() throws Exception {
+        // Ensure that we can create more client ifaces.
+        when(mWifiNative.isItPossibleToCreateStaIface(any())).thenReturn(true);
+        when(mResources.getBoolean(
+                R.bool.config_wifiMultiStaNetworkSwitchingMakeBeforeBreakEnabled))
+                .thenReturn(true);
+        assertTrue(mActiveModeWarden.canRequestMoreClientModeManagersInRole(
+                TEST_WORKSOURCE, ROLE_CLIENT_SECONDARY_TRANSIENT));
+
+        enterClientModeActiveState();
+
+        verify(mPrimaryChangedCallback).onChange(null, mClientModeManager);
+
+        // Connected to ssid1/bssid1
+        WifiConfiguration config1 = new WifiConfiguration();
+        config1.SSID = TEST_SSID_1;
+        when(mClientModeManager.getConnectedWifiConfiguration()).thenReturn(config1);
+        when(mClientModeManager.getConnectedBssid()).thenReturn(TEST_BSSID_1);
+
+        ConcreteClientModeManager additionalClientModeManager =
+                mock(ConcreteClientModeManager.class);
+        Mutable<Listener<ConcreteClientModeManager>> additionalClientListener =
+                new Mutable<>();
+        doAnswer((invocation) -> {
+            Object[] args = invocation.getArguments();
+            additionalClientListener.value =
+                    (Listener<ConcreteClientModeManager>) args[0];
+            return additionalClientModeManager;
+        }).when(mWifiInjector).makeClientModeManager(
+                any(Listener.class), any(), eq(ROLE_CLIENT_SECONDARY_TRANSIENT),
+                anyBoolean());
+        when(additionalClientModeManager.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_TRANSIENT);
+
+        ExternalClientModeManagerRequestListener externalRequestListener = mock(
+                ExternalClientModeManagerRequestListener.class);
+        // request for ssid2/bssid2
+        mActiveModeWarden.requestSecondaryTransientClientModeManager(
+                externalRequestListener, TEST_WORKSOURCE, TEST_SSID_2, TEST_BSSID_2);
+        mLooper.dispatchAll();
+        verify(mWifiInjector).makeClientModeManager(
+                any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_SECONDARY_TRANSIENT), anyBoolean());
+        additionalClientListener.value.onStarted(additionalClientModeManager);
+        mLooper.dispatchAll();
+        // Returns the new client mode manager.
+        ArgumentCaptor<ClientModeManager> requestedClientModeManager =
+                ArgumentCaptor.forClass(ClientModeManager.class);
+        verify(externalRequestListener).onAnswer(requestedClientModeManager.capture());
+        assertEquals(additionalClientModeManager, requestedClientModeManager.getValue());
+
+        // primary didn't change yet
+        verify(mPrimaryChangedCallback, never()).onChange(any(), eq(additionalClientModeManager));
+
+        // change primary
+        when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_TRANSIENT);
+        mClientListener.onRoleChanged(mClientModeManager);
+        when(additionalClientModeManager.getRole()).thenReturn(ROLE_CLIENT_PRIMARY);
+        additionalClientListener.value.onRoleChanged(additionalClientModeManager);
+
+        // verify callback triggered
+        verify(mPrimaryChangedCallback).onChange(mClientModeManager, null);
+        verify(mPrimaryChangedCallback).onChange(null, additionalClientModeManager);
+    }
+
+    @Test
+    public void testRegisterPrimaryCmmChangedCallbackWhenConnectModeActiveState() throws Exception {
+        enterClientModeActiveState();
+
+        // register a new primary cmm change callback.
+        ActiveModeWarden.PrimaryClientModeManagerChangedCallback primarCmmCallback = mock(
+                ActiveModeWarden.PrimaryClientModeManagerChangedCallback.class);
+        mActiveModeWarden.registerPrimaryClientModeManagerChangedCallback(primarCmmCallback);
+        // Ensure we get the callback immediately.
+        verify(primarCmmCallback).onChange(null, mClientModeManager);
+    }
+
+    @Test
+    public void testGetCmmInRolesWithNullRoleInOneCmm() throws Exception {
+        enterClientModeActiveState();
+
+        // Ensure that we can create more client ifaces.
+        when(mWifiNative.isItPossibleToCreateStaIface(any())).thenReturn(true);
+        when(mResources.getBoolean(R.bool.config_wifiMultiStaLocalOnlyConcurrencyEnabled))
+                .thenReturn(true);
+
+        ConcreteClientModeManager additionalClientModeManager =
+                mock(ConcreteClientModeManager.class);
+        when(mWifiInjector.makeClientModeManager(
+                any(), any(), any(), anyBoolean())).thenReturn(additionalClientModeManager);
+
+        mActiveModeWarden.requestLocalOnlyClientModeManager(
+                mock(ExternalClientModeManagerRequestListener.class),
+                TEST_WORKSOURCE, TEST_SSID_2, TEST_BSSID_2);
+        mLooper.dispatchAll();
+
+        // No role set, should be ignored.
+        when(additionalClientModeManager.getRole()).thenReturn(null);
+        assertEquals(1, mActiveModeWarden.getClientModeManagersInRoles(
+                ROLE_CLIENT_PRIMARY, ROLE_CLIENT_LOCAL_ONLY).size());
+
+        // Role set, should be included.
+        when(additionalClientModeManager.getRole()).thenReturn(ROLE_CLIENT_LOCAL_ONLY);
+        assertEquals(2, mActiveModeWarden.getClientModeManagersInRoles(
+                ROLE_CLIENT_PRIMARY, ROLE_CLIENT_LOCAL_ONLY).size());
+    }
+
+    /**
+     * Helper method to enter the EnabledState and set ClientModeManager in ScanOnlyMode during
+     * emergency scan processing.
+     */
+    private void indicateStartOfEmergencyScan(
+            boolean hasAnyOtherStaToggleEnabled,
+            @Nullable ActiveModeManager.ClientRole expectedRole)
+            throws Exception {
+        String fromState = mActiveModeWarden.getCurrentMode();
+        mActiveModeWarden.setEmergencyScanRequestInProgress(true);
+        mLooper.dispatchAll();
+
+        if (!hasAnyOtherStaToggleEnabled) {
+            when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_SCAN_ONLY);
+            mClientListener.onStarted(mClientModeManager);
+            mLooper.dispatchAll();
+            verify(mWifiInjector).makeClientModeManager(
+                    any(), eq(TEST_WORKSOURCE), eq(ROLE_CLIENT_SCAN_ONLY), anyBoolean());
+            verify(mModeChangeCallback).onActiveModeManagerAdded(mClientModeManager);
+            verify(mScanRequestProxy).enableScanning(true, false);
+            verify(mBatteryStats).reportWifiOn();
+            verify(mBatteryStats).reportWifiState(
+                    BatteryStatsManager.WIFI_STATE_OFF_SCANNING, null);
+        } else {
+            verify(mClientModeManager).setRole(eq(expectedRole), any());
+            verify(mClientModeManager, never()).stop();
+            assertEquals(fromState, mActiveModeWarden.getCurrentMode());
+        }
+        assertInEnabledState();
+    }
+
+    private void indicateEndOfEmergencyScan(
+            boolean hasAnyOtherStaToggleEnabled,
+            @Nullable ActiveModeManager.ClientRole expectedRole) {
+        String fromState = mActiveModeWarden.getCurrentMode();
+        mActiveModeWarden.setEmergencyScanRequestInProgress(false);
+        mLooper.dispatchAll();
+        if (!hasAnyOtherStaToggleEnabled) {
+            mClientListener.onStopped(mClientModeManager);
+            mLooper.dispatchAll();
+            verify(mModeChangeCallback).onActiveModeManagerRemoved(mClientModeManager);
+            verify(mScanRequestProxy).enableScanning(false, false);
+            assertInDisabledState();
+        } else {
+            // Nothing changes.
+            verify(mClientModeManager).setRole(eq(expectedRole), any());
+            verify(mClientModeManager, never()).stop();
+            assertEquals(fromState, mActiveModeWarden.getCurrentMode());
+        }
+    }
+
+    @Test
+    public void testEmergencyScanWhenWifiDisabled() throws Exception {
+        // Wifi fully disabled.
+        when(mWifiPermissionsUtil.isLocationModeEnabled()).thenReturn(false);
+        when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
+        when(mSettingsStore.isScanAlwaysAvailable()).thenReturn(false);
+        when(mSettingsStore.isWifiToggleEnabled()).thenReturn(false);
+
+        indicateStartOfEmergencyScan(false, null);
+
+        // To reset setRole invocation above which is checked inside |indicateEndOfEmergencyScan|
+        clearInvocations(mClientModeManager);
+
+        indicateEndOfEmergencyScan(false, null);
+    }
+
+    @Test
+    public void testEmergencyScanWhenWifiEnabled() throws Exception {
+        // Wifi enabled.
+        enterClientModeActiveState();
+
+        reset(mBatteryStats, mScanRequestProxy, mModeChangeCallback);
+
+        indicateStartOfEmergencyScan(true, ROLE_CLIENT_PRIMARY);
+
+        // To reset setRole invocation above which is checked inside |indicateEndOfEmergencyScan|
+        clearInvocations(mClientModeManager);
+
+        indicateEndOfEmergencyScan(true, ROLE_CLIENT_PRIMARY);
+    }
+
+    @Test
+    public void testEmergencyScanWhenScanOnlyModeEnabled() throws Exception {
+        // Scan only enabled.
+        enterScanOnlyModeActiveState();
+
+        reset(mBatteryStats, mScanRequestProxy, mModeChangeCallback);
+
+        indicateStartOfEmergencyScan(true, ROLE_CLIENT_SCAN_ONLY);
+
+        // To reset setRole invocation above which is checked inside |indicateEndOfEmergencyScan|
+        clearInvocations(mClientModeManager);
+
+        indicateEndOfEmergencyScan(true, ROLE_CLIENT_SCAN_ONLY);
+    }
+
+    @Test
+    public void testEmergencyScanWhenEcmOnWithWifiDisableInEcbm() throws Exception {
+        // Wifi enabled.
+        enterClientModeActiveState();
+
+        reset(mBatteryStats, mScanRequestProxy, mModeChangeCallback);
+
+        // Test with WifiDisableInECBM turned on
+        when(mFacade.getConfigWiFiDisableInECBM(mContext)).thenReturn(true);
+
+        assertWifiShutDown(() -> {
+            // test ecm changed
+            emergencyCallbackModeChanged(true);
+            mLooper.dispatchAll();
+            // fully shutdown
+            mClientListener.onStopped(mClientModeManager);
+            mLooper.dispatchAll();
+        });
+        reset(mBatteryStats, mScanRequestProxy, mModeChangeCallback);
+
+        indicateStartOfEmergencyScan(false, null);
+
+        // To reset setRole invocation above which is checked inside |indicateEndOfEmergencyScan|
+        clearInvocations(mClientModeManager);
+
+        indicateEndOfEmergencyScan(false, null);
+    }
+
+    @Test
+    public void testEmergencyScanWhenEcmOnWithoutWifiDisableInEcbm() throws Exception {
+        // Wifi enabled.
+        enterClientModeActiveState();
+
+        reset(mBatteryStats, mScanRequestProxy, mModeChangeCallback);
+
+        // Test with WifiDisableInECBM turned off
+        when(mFacade.getConfigWiFiDisableInECBM(mContext)).thenReturn(false);
+
+        assertEnteredEcmMode(() -> {
+            // test ecm changed
+            emergencyCallbackModeChanged(true);
+            mLooper.dispatchAll();
+        });
+
+        indicateStartOfEmergencyScan(true, ROLE_CLIENT_PRIMARY);
+
+        // To reset setRole invocation above which is checked inside |indicateEndOfEmergencyScan|
+        clearInvocations(mClientModeManager);
+
+        indicateEndOfEmergencyScan(true, ROLE_CLIENT_PRIMARY);
+    }
+
+    @Test
+    public void testWifiDisableDuringEmergencyScan() throws Exception {
+        // Wifi enabled.
+        enterClientModeActiveState();
+
+        reset(mBatteryStats, mScanRequestProxy, mModeChangeCallback);
+
+        indicateStartOfEmergencyScan(true, ROLE_CLIENT_PRIMARY);
+
+        // Toggle off wifi
+        when(mSettingsStore.isWifiToggleEnabled()).thenReturn(false);
+        mActiveModeWarden.wifiToggled(TEST_WORKSOURCE);
+        mLooper.dispatchAll();
+
+        // Ensure that we switched the role to scan only state because of the emergency scan.
+        when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_SCAN_ONLY);
+        mClientListener.onRoleChanged(mClientModeManager);
+        mLooper.dispatchAll();
+        verify(mClientModeManager).setRole(ROLE_CLIENT_SCAN_ONLY, INTERNAL_REQUESTOR_WS);
+        verify(mClientModeManager, never()).stop();
+        assertInEnabledState();
+
+        // To reset setRole invocation above which is checked inside |indicateEndOfEmergencyScan|
+        clearInvocations(mClientModeManager);
+
+        indicateEndOfEmergencyScan(false, null);
+    }
+
+    @Test
+    public void testScanOnlyModeDisableDuringEmergencyScan() throws Exception {
+        // Scan only enabled.
+        enterScanOnlyModeActiveState();
+
+        reset(mBatteryStats, mScanRequestProxy, mModeChangeCallback);
+
+        indicateStartOfEmergencyScan(true, ROLE_CLIENT_SCAN_ONLY);
+
+        // To reset setRole invocation above which is checked below.
+        clearInvocations(mClientModeManager);
+
+        // Toggle off scan only mode
+        when(mWifiPermissionsUtil.isLocationModeEnabled()).thenReturn(false);
+        when(mSettingsStore.isScanAlwaysAvailable()).thenReturn(false);
+        mActiveModeWarden.scanAlwaysModeChanged();
+        mLooper.dispatchAll();
+
+        // Ensure that we remained in scan only state because of the emergency scan.
+        verify(mClientModeManager).setRole(eq(ROLE_CLIENT_SCAN_ONLY), any());
+        verify(mClientModeManager, never()).stop();
+        assertInEnabledState();
+
+        // To reset setRole invocation above which is checked inside |indicateEndOfEmergencyScan|
+        clearInvocations(mClientModeManager);
+
+        indicateEndOfEmergencyScan(false, null);
+    }
+
+    @Test
+    public void testEcmOffWithWifiDisabledStateDuringEmergencyScan() throws Exception {
+        // Wifi enabled.
+        enterClientModeActiveState();
+
+        reset(mBatteryStats, mScanRequestProxy, mModeChangeCallback);
+
+        // Test with WifiDisableInECBM turned on
+        when(mFacade.getConfigWiFiDisableInECBM(mContext)).thenReturn(true);
+
+        assertWifiShutDown(() -> {
+            // test ecm changed
+            emergencyCallbackModeChanged(true);
+            mLooper.dispatchAll();
+            // fully shutdown
+            mClientListener.onStopped(mClientModeManager);
+            mLooper.dispatchAll();
+        });
+        reset(mBatteryStats, mScanRequestProxy, mModeChangeCallback);
+
+        indicateStartOfEmergencyScan(false, null);
+
+        // Now turn off ECM
+        emergencyCallbackModeChanged(false);
+        mLooper.dispatchAll();
+
+        // Ensure we turned wifi back on.
+        verify(mClientModeManager).setRole(eq(ROLE_CLIENT_PRIMARY), any());
+        when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_PRIMARY);
+        mClientListener.onRoleChanged(mClientModeManager);
+        verify(mScanRequestProxy).enableScanning(true, true);
+        assertInEnabledState();
+
+        // To reset setRole invocation above which is checked inside |indicateEndOfEmergencyScan|
+        clearInvocations(mClientModeManager);
+
+        indicateEndOfEmergencyScan(true, ROLE_CLIENT_PRIMARY);
+    }
+
+    @Test
+    public void testEcmOffWithoutWifiDisabledStateDuringEmergencyScan() throws Exception {
+        // Wifi enabled.
+        enterClientModeActiveState();
+
+        reset(mBatteryStats, mScanRequestProxy, mModeChangeCallback);
+
+        // Test with WifiDisableInECBM turned off
+        when(mFacade.getConfigWiFiDisableInECBM(mContext)).thenReturn(false);
+
+        assertEnteredEcmMode(() -> {
+            // test ecm changed
+            emergencyCallbackModeChanged(true);
+            mLooper.dispatchAll();
+        });
+
+        // Now turn off ECM
+        emergencyCallbackModeChanged(false);
+        mLooper.dispatchAll();
+
+        // Ensure that we remained in connected state.
+        verify(mClientModeManager).setRole(eq(ROLE_CLIENT_PRIMARY), any());
+        verify(mClientModeManager, never()).stop();
+        assertInEnabledState();
+
+        // To reset setRole invocation above which is checked inside |indicateEndOfEmergencyScan|
+        clearInvocations(mClientModeManager);
+
+        indicateEndOfEmergencyScan(true, ROLE_CLIENT_PRIMARY);
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/AdaptiveConnectivityEnabledSettingObserverTest.java b/service/tests/wifitests/src/com/android/server/wifi/AdaptiveConnectivityEnabledSettingObserverTest.java
new file mode 100644
index 0000000..0cf1329
--- /dev/null
+++ b/service/tests/wifitests/src/com/android/server/wifi/AdaptiveConnectivityEnabledSettingObserverTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static com.android.server.wifi.AdaptiveConnectivityEnabledSettingObserver.SETTINGS_SECURE_ADAPTIVE_CONNECTIVITY_ENABLED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.test.TestLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.wifi.proto.nano.WifiMetricsProto;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+
+@SmallTest
+public class AdaptiveConnectivityEnabledSettingObserverTest extends WifiBaseTest {
+
+    private AdaptiveConnectivityEnabledSettingObserver mAdaptiveConnectivityEnabledSettingObserver;
+
+    private TestLooper mLooper;
+    @Mock private WifiMetrics mWifiMetrics;
+    @Mock private FrameworkFacade mFrameworkFacade;
+    @Mock private Context mContext;
+
+    @Captor private ArgumentCaptor<ContentObserver> mContentObserverCaptor;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mLooper = new TestLooper();
+        Handler handler = new Handler(mLooper.getLooper());
+
+        mAdaptiveConnectivityEnabledSettingObserver =
+                new AdaptiveConnectivityEnabledSettingObserver(
+                        handler, mWifiMetrics, mFrameworkFacade, mContext);
+
+        // True pre-initialization
+        assertThat(mAdaptiveConnectivityEnabledSettingObserver.get()).isTrue();
+    }
+
+    @Test
+    public void initiallyTrue_adaptiveConnectivityToggled_getCorrectValue() throws Exception {
+        // initialize to true
+        when(mFrameworkFacade.getSecureIntegerSetting(
+                eq(mContext), eq(SETTINGS_SECURE_ADAPTIVE_CONNECTIVITY_ENABLED), anyInt()))
+                .thenReturn(1);
+
+        mAdaptiveConnectivityEnabledSettingObserver.initialize();
+
+        assertThat(mAdaptiveConnectivityEnabledSettingObserver.get()).isTrue();
+        verify(mFrameworkFacade).registerContentObserver(
+                eq(mContext), any(), anyBoolean(), mContentObserverCaptor.capture());
+        verify(mWifiMetrics).setAdaptiveConnectivityState(true);
+
+        // set to false
+        when(mFrameworkFacade.getSecureIntegerSetting(eq(mContext),
+                eq(SETTINGS_SECURE_ADAPTIVE_CONNECTIVITY_ENABLED), anyInt()))
+                .thenReturn(0);
+
+        mContentObserverCaptor.getValue().onChange(false);
+        mLooper.dispatchAll();
+
+        assertThat(mAdaptiveConnectivityEnabledSettingObserver.get()).isFalse();
+        verify(mWifiMetrics).setAdaptiveConnectivityState(false);
+        verify(mWifiMetrics).logUserActionEvent(
+                WifiMetricsProto.UserActionEvent.EVENT_CONFIGURE_ADAPTIVE_CONNECTIVITY_OFF);
+
+        // set to true again
+        when(mFrameworkFacade.getSecureIntegerSetting(eq(mContext),
+                eq(SETTINGS_SECURE_ADAPTIVE_CONNECTIVITY_ENABLED), anyInt()))
+                .thenReturn(1);
+
+        mContentObserverCaptor.getValue().onChange(false);
+        mLooper.dispatchAll();
+
+        assertThat(mAdaptiveConnectivityEnabledSettingObserver.get()).isTrue();
+        verify(mWifiMetrics, times(2)).setAdaptiveConnectivityState(true);
+        verify(mWifiMetrics).logUserActionEvent(
+                WifiMetricsProto.UserActionEvent.EVENT_CONFIGURE_ADAPTIVE_CONNECTIVITY_ON);
+    }
+
+    @Test
+    public void initiallyFalse_adaptiveConnectivityToggled_getCorrectValue() throws Exception {
+        // initialize to false
+        when(mFrameworkFacade.getSecureIntegerSetting(eq(mContext),
+                eq(SETTINGS_SECURE_ADAPTIVE_CONNECTIVITY_ENABLED), anyInt()))
+                .thenReturn(0);
+
+        mAdaptiveConnectivityEnabledSettingObserver.initialize();
+
+        assertThat(mAdaptiveConnectivityEnabledSettingObserver.get()).isFalse();
+        verify(mFrameworkFacade).registerContentObserver(
+                eq(mContext), any(), anyBoolean(), mContentObserverCaptor.capture());
+        verify(mWifiMetrics).setAdaptiveConnectivityState(false);
+
+        // set to true
+        when(mFrameworkFacade.getSecureIntegerSetting(eq(mContext),
+                eq(SETTINGS_SECURE_ADAPTIVE_CONNECTIVITY_ENABLED), anyInt()))
+                .thenReturn(1);
+
+        mContentObserverCaptor.getValue().onChange(false);
+        mLooper.dispatchAll();
+
+        assertThat(mAdaptiveConnectivityEnabledSettingObserver.get()).isTrue();
+        verify(mWifiMetrics).setAdaptiveConnectivityState(true);
+        verify(mWifiMetrics).logUserActionEvent(
+                WifiMetricsProto.UserActionEvent.EVENT_CONFIGURE_ADAPTIVE_CONNECTIVITY_ON);
+
+        // set to false
+        when(mFrameworkFacade.getSecureIntegerSetting(eq(mContext),
+                eq(SETTINGS_SECURE_ADAPTIVE_CONNECTIVITY_ENABLED), anyInt()))
+                .thenReturn(0);
+
+        mContentObserverCaptor.getValue().onChange(false);
+        mLooper.dispatchAll();
+
+        assertThat(mAdaptiveConnectivityEnabledSettingObserver.get()).isFalse();
+        verify(mWifiMetrics, times(2)).setAdaptiveConnectivityState(false);
+        verify(mWifiMetrics).logUserActionEvent(
+                WifiMetricsProto.UserActionEvent.EVENT_CONFIGURE_ADAPTIVE_CONNECTIVITY_OFF);
+    }
+}
diff --git a/service/tests/wifitests/src/com/android/server/wifi/AvailableNetworkNotifierTest.java b/service/tests/wifitests/src/com/android/server/wifi/AvailableNetworkNotifierTest.java
new file mode 100644
index 0000000..3437a81
--- /dev/null
+++ b/service/tests/wifitests/src/com/android/server/wifi/AvailableNetworkNotifierTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static com.android.server.wifi.ConnectToNetworkNotificationBuilder.ACTION_CONNECT_TO_NETWORK;
+
+import static org.mockito.Mockito.*;
+
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.net.wifi.ScanResult;
+import android.net.wifi.ScanResult.InformationElement;
+import android.net.wifi.WifiSsid;
+import android.os.Looper;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.EapFailureNotifier}.
+ */
+@SmallTest
+public class AvailableNetworkNotifierTest extends WifiBaseTest {
+    private AvailableNetworkNotifier mAvailableNetworkNotifier;
+    @Mock WifiContext mContext;
+    @Mock Looper mLooper;
+    @Mock FrameworkFacade mFrameworkFacade;
+    @Mock Clock mClock;
+    @Mock WifiMetrics mWifiMetrics;
+    @Mock WifiConfigManager mWifiConfigManager;
+    @Mock WifiConfigStore mWifiConfigStore;
+    @Mock ConnectHelper mConnectHelper;
+    @Mock ConnectToNetworkNotificationBuilder mConnectToNetworkNotificationBuilder;
+    @Mock MakeBeforeBreakManager mMakeBeforeBreakManager;
+    @Mock WifiNotificationManager mWifiNotificationManager;
+
+    BroadcastReceiver mBroadcastReceiver;
+
+    /**
+     * Sets up for unit test
+     */
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mAvailableNetworkNotifier = new AvailableNetworkNotifier(
+                "AvailableNetworkNotifierTest",
+                "storeDataIdentifier",
+                "toggleSettingsName",
+                1, // notificationIdentifier
+                1, // nominatorId
+                mContext,
+                mLooper,
+                mFrameworkFacade,
+                mClock,
+                mWifiMetrics,
+                mWifiConfigManager,
+                mWifiConfigStore,
+                mConnectHelper,
+                mConnectToNetworkNotificationBuilder,
+                mMakeBeforeBreakManager,
+                mWifiNotificationManager);
+
+        ArgumentCaptor<BroadcastReceiver> captor = ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mContext).registerReceiver(captor.capture(), any(), any(), any());
+        mBroadcastReceiver = captor.getValue();
+    }
+
+    @After
+    public void cleanUp() throws Exception {
+        validateMockitoUsage();
+    }
+
+    /** Verify that connecting to an unknown akm network works normally.
+     */
+    @Test
+    public void testConnectToUnknownAkmNetwork() throws Exception {
+        mAvailableNetworkNotifier.mState =
+                AvailableNetworkNotifier.STATE_SHOWING_RECOMMENDATION_NOTIFICATION;
+        final String ssid = "UnknownAkm-Network";
+        final String caps = "[RSN-?-TKIP+CCMP][ESS][WPS]";
+        ScanResult result = new ScanResult(WifiSsid.createFromAsciiEncoded(ssid), ssid,
+                "ab:cd:01:ef:45:89", 1245, 0, caps, -78, 2450, 1025, 22, 33, 20, 0,
+                0, true);
+        InformationElement ie = new InformationElement();
+        ie.id = InformationElement.EID_SSID;
+        ie.bytes = ssid.getBytes(StandardCharsets.UTF_8);
+        result.informationElements = new InformationElement[] { ie };
+        mAvailableNetworkNotifier.mRecommendedNetwork = result;
+
+        Intent intent = new Intent();
+        intent.setAction(ACTION_CONNECT_TO_NETWORK);
+        mBroadcastReceiver.onReceive(mContext, intent);
+        verify(mWifiConfigManager, never()).addOrUpdateNetwork(any(), anyInt());
+    }
+}
diff --git a/service/tests/wifitests/src/com/android/server/wifi/BssidBlocklistMonitorTest.java b/service/tests/wifitests/src/com/android/server/wifi/BssidBlocklistMonitorTest.java
deleted file mode 100644
index 1007391..0000000
--- a/service/tests/wifitests/src/com/android/server/wifi/BssidBlocklistMonitorTest.java
+++ /dev/null
@@ -1,839 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wifi;
-
-import static org.junit.Assert.*;
-import static org.mockito.Mockito.*;
-import static org.mockito.Mockito.times;
-
-import android.content.Context;
-import android.net.wifi.ScanResult;
-import android.util.LocalLog;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.wifi.resources.R;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Unit tests for {@link com.android.server.wifi.BssidBlocklistMonitor}.
- */
-@SmallTest
-public class BssidBlocklistMonitorTest {
-    private static final int TEST_NUM_MAX_FIRMWARE_SUPPORT_BSSIDS = 3;
-    private static final String TEST_SSID_1 = "TestSSID1";
-    private static final String TEST_SSID_2 = "TestSSID2";
-    private static final String TEST_SSID_3 = "TestSSID3";
-    private static final String TEST_BSSID_1 = "0a:08:5c:67:89:00";
-    private static final String TEST_BSSID_2 = "0a:08:5c:67:89:01";
-    private static final String TEST_BSSID_3 = "0a:08:5c:67:89:02";
-    private static final int TEST_GOOD_RSSI = -50;
-    private static final int TEST_SUFFICIENT_RSSI = -67;
-    private static final int MIN_RSSI_DIFF_TO_UNBLOCK_BSSID = 5;
-    private static final int TEST_FRAMEWORK_BLOCK_REASON =
-            BssidBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_MBO_OCE;
-    private static final int TEST_L2_FAILURE = BssidBlocklistMonitor.REASON_ASSOCIATION_REJECTION;
-    private static final int TEST_DHCP_FAILURE = BssidBlocklistMonitor.REASON_DHCP_FAILURE;
-    private static final long BASE_BLOCKLIST_DURATION = TimeUnit.MINUTES.toMillis(5); // 5 minutes
-    private static final long BASE_CONNECTED_SCORE_BLOCKLIST_DURATION =
-            TimeUnit.SECONDS.toMillis(30);
-    private static final long ABNORMAL_DISCONNECT_TIME_WINDOW_MS = TimeUnit.SECONDS.toMillis(30);
-    private static final long ABNORMAL_DISCONNECT_RESET_TIME_MS = TimeUnit.HOURS.toMillis(3);
-    private static final int FAILURE_STREAK_CAP = 7;
-    private static final Map<Integer, Integer> BLOCK_REASON_TO_DISABLE_THRESHOLD_MAP =
-            Map.ofEntries(
-                    Map.entry(BssidBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA, 1),
-                    Map.entry(BssidBlocklistMonitor.REASON_NETWORK_VALIDATION_FAILURE, 1),
-                    Map.entry(BssidBlocklistMonitor.REASON_WRONG_PASSWORD, 1),
-                    Map.entry(BssidBlocklistMonitor.REASON_EAP_FAILURE, 1),
-                    Map.entry(BssidBlocklistMonitor.REASON_ASSOCIATION_REJECTION, 3),
-                    Map.entry(BssidBlocklistMonitor.REASON_ASSOCIATION_TIMEOUT, 3),
-                    Map.entry(BssidBlocklistMonitor.REASON_AUTHENTICATION_FAILURE, 3),
-                    Map.entry(BssidBlocklistMonitor.REASON_DHCP_FAILURE, 3),
-                    Map.entry(BssidBlocklistMonitor.REASON_ABNORMAL_DISCONNECT, 3),
-                    Map.entry(BssidBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_MBO_OCE, 1),
-                    Map.entry(BssidBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT, 1),
-                    Map.entry(BssidBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE, 1)
-            );
-    private static final int NUM_FAILURES_TO_BLOCKLIST =
-            BLOCK_REASON_TO_DISABLE_THRESHOLD_MAP.get(TEST_L2_FAILURE);
-
-    @Mock private Context mContext;
-    @Mock private WifiConnectivityHelper mWifiConnectivityHelper;
-    @Mock private WifiLastResortWatchdog mWifiLastResortWatchdog;
-    @Mock private Clock mClock;
-    @Mock private LocalLog mLocalLog;
-    @Mock private WifiScoreCard mWifiScoreCard;
-    @Mock private ScoringParams mScoringParams;
-
-    private MockResources mResources;
-    private BssidBlocklistMonitor mBssidBlocklistMonitor;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-
-        when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
-        when(mWifiConnectivityHelper.getMaxNumBlacklistBssid())
-                .thenReturn(TEST_NUM_MAX_FIRMWARE_SUPPORT_BSSIDS);
-        when(mScoringParams.getSufficientRssi(anyInt())).thenReturn(TEST_SUFFICIENT_RSSI);
-        mResources = new MockResources();
-        mResources.setInteger(R.integer.config_wifiBssidBlocklistMonitorBaseBlockDurationMs,
-                (int) BASE_BLOCKLIST_DURATION);
-        mResources.setInteger(
-                R.integer.config_wifiBssidBlocklistMonitorConnectedScoreBaseBlockDurationMs,
-                (int) BASE_CONNECTED_SCORE_BLOCKLIST_DURATION);
-        mResources.setInteger(R.integer.config_wifiBssidBlocklistMonitorFailureStreakCap,
-                FAILURE_STREAK_CAP);
-        mResources.setInteger(R.integer.config_wifiBssidBlocklistAbnormalDisconnectTimeWindowMs,
-                (int) ABNORMAL_DISCONNECT_TIME_WINDOW_MS);
-        mResources.setInteger(
-                R.integer.config_wifiBssidBlocklistMonitorApUnableToHandleNewStaThreshold,
-                BLOCK_REASON_TO_DISABLE_THRESHOLD_MAP.get(
-                        BssidBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA));
-        mResources.setInteger(
-                R.integer.config_wifiBssidBlocklistMonitorNetworkValidationFailureThreshold,
-                BLOCK_REASON_TO_DISABLE_THRESHOLD_MAP.get(
-                        BssidBlocklistMonitor.REASON_NETWORK_VALIDATION_FAILURE));
-        mResources.setInteger(R.integer.config_wifiBssidBlocklistMonitorWrongPasswordThreshold,
-                BLOCK_REASON_TO_DISABLE_THRESHOLD_MAP.get(
-                        BssidBlocklistMonitor.REASON_WRONG_PASSWORD));
-        mResources.setInteger(R.integer.config_wifiBssidBlocklistMonitorEapFailureThreshold,
-                BLOCK_REASON_TO_DISABLE_THRESHOLD_MAP.get(
-                        BssidBlocklistMonitor.REASON_EAP_FAILURE));
-        mResources.setInteger(
-                R.integer.config_wifiBssidBlocklistMonitorAssociationRejectionThreshold,
-                BLOCK_REASON_TO_DISABLE_THRESHOLD_MAP.get(
-                        BssidBlocklistMonitor.REASON_ASSOCIATION_REJECTION));
-        mResources.setInteger(
-                R.integer.config_wifiBssidBlocklistMonitorAssociationTimeoutThreshold,
-                BLOCK_REASON_TO_DISABLE_THRESHOLD_MAP.get(
-                        BssidBlocklistMonitor.REASON_ASSOCIATION_TIMEOUT));
-        mResources.setInteger(
-                R.integer.config_wifiBssidBlocklistMonitorAuthenticationFailureThreshold,
-                BLOCK_REASON_TO_DISABLE_THRESHOLD_MAP.get(
-                        BssidBlocklistMonitor.REASON_AUTHENTICATION_FAILURE));
-        mResources.setInteger(R.integer.config_wifiBssidBlocklistMonitorDhcpFailureThreshold,
-                BLOCK_REASON_TO_DISABLE_THRESHOLD_MAP.get(
-                        BssidBlocklistMonitor.REASON_DHCP_FAILURE));
-        mResources.setInteger(
-                R.integer.config_wifiBssidBlocklistMonitorAbnormalDisconnectThreshold,
-                BLOCK_REASON_TO_DISABLE_THRESHOLD_MAP.get(
-                        BssidBlocklistMonitor.REASON_ABNORMAL_DISCONNECT));
-
-        when(mContext.getResources()).thenReturn(mResources);
-        mBssidBlocklistMonitor = new BssidBlocklistMonitor(mContext, mWifiConnectivityHelper,
-                mWifiLastResortWatchdog, mClock, mLocalLog, mWifiScoreCard, mScoringParams);
-    }
-
-    private void verifyAddTestBssidToBlocklist() {
-        mBssidBlocklistMonitor.handleBssidConnectionFailure(
-                TEST_BSSID_1, TEST_SSID_1,
-                BssidBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA, TEST_GOOD_RSSI);
-        assertTrue(mBssidBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
-    }
-
-    // Verify adding 2 BSSID for SSID_1 and 1 BSSID for SSID_2 to the blocklist.
-    private void verifyAddMultipleBssidsToBlocklist() {
-        when(mClock.getWallClockMillis()).thenReturn(0L);
-        mBssidBlocklistMonitor.handleBssidConnectionFailure(TEST_BSSID_1,
-                TEST_SSID_1, BssidBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA,
-                TEST_GOOD_RSSI);
-        when(mClock.getWallClockMillis()).thenReturn(1L);
-        mBssidBlocklistMonitor.handleBssidConnectionFailure(TEST_BSSID_2,
-                TEST_SSID_1, BssidBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA,
-                TEST_GOOD_RSSI);
-        mBssidBlocklistMonitor.handleBssidConnectionFailure(TEST_BSSID_3,
-                TEST_SSID_2, BssidBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA,
-                TEST_GOOD_RSSI);
-
-        // Verify that we have 3 BSSIDs in the blocklist.
-        Set<String> bssidList = mBssidBlocklistMonitor.updateAndGetBssidBlocklist();
-        assertEquals(3, bssidList.size());
-        assertTrue(bssidList.contains(TEST_BSSID_1));
-        assertTrue(bssidList.contains(TEST_BSSID_2));
-        assertTrue(bssidList.contains(TEST_BSSID_3));
-    }
-
-    private void handleBssidConnectionFailureMultipleTimes(String bssid, int reason, int times) {
-        handleBssidConnectionFailureMultipleTimes(bssid, TEST_SSID_1, reason, times);
-    }
-
-    private void handleBssidConnectionFailureMultipleTimes(String bssid, String ssid, int reason,
-            int times) {
-        for (int i = 0; i < times; i++) {
-            mBssidBlocklistMonitor.handleBssidConnectionFailure(bssid, ssid, reason,
-                    TEST_GOOD_RSSI);
-        }
-    }
-
-    /**
-     * Verify updateAndGetNumBlockedBssidsForSsid returns the correct number of blocked BSSIDs.
-     */
-    @Test
-    public void testUpdateAndGetNumBlockedBssidsForSsid() {
-        verifyAddMultipleBssidsToBlocklist();
-        assertEquals(2, mBssidBlocklistMonitor.updateAndGetNumBlockedBssidsForSsid(TEST_SSID_1));
-        assertEquals(1, mBssidBlocklistMonitor.updateAndGetNumBlockedBssidsForSsid(TEST_SSID_2));
-    }
-
-    /**
-     * Verify that updateAndGetBssidBlocklist removes expired blocklist entries and clears
-     * all failure counters for those networks.
-     */
-    @Test
-    public void testBssidIsRemovedFromBlocklistAfterTimeout() {
-        verifyAddTestBssidToBlocklist();
-        // Verify TEST_BSSID_1 is not removed from the blocklist until sufficient time have passed.
-        when(mClock.getWallClockMillis()).thenReturn(BASE_BLOCKLIST_DURATION);
-        assertTrue(mBssidBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
-
-        // Verify that TEST_BSSID_1 is removed from the blocklist after the timeout duration.
-        // By default there is no blocklist streak so the timeout duration is simply
-        // BASE_BLOCKLIST_DURATION
-        when(mClock.getWallClockMillis()).thenReturn(BASE_BLOCKLIST_DURATION + 1);
-        assertEquals(0, mBssidBlocklistMonitor.updateAndGetBssidBlocklist().size());
-    }
-
-    /**
-     * Verify that updateAndGetBssidBlocklist(ssid) updates firmware roaming configuration
-     * if a BSSID that belongs to the ssid is removed from blocklist.
-     */
-    @Test
-    public void testBssidRemovalUpdatesFirmwareConfiguration() {
-        verifyAddTestBssidToBlocklist();
-        when(mClock.getWallClockMillis()).thenReturn(BASE_BLOCKLIST_DURATION + 1);
-        assertEquals(0, mBssidBlocklistMonitor
-                .updateAndGetBssidBlocklistForSsid(TEST_SSID_1).size());
-        verify(mWifiConnectivityHelper).setFirmwareRoamingConfiguration(eq(new ArrayList<>()),
-                eq(new ArrayList<>()));
-    }
-
-    /**
-     * Verify that updateAndGetBssidBlocklist(ssid) does not update firmware roaming configuration
-     * if there are no BSSIDs belonging to the ssid removed from blocklist.
-     */
-    @Test
-    public void testBssidRemovalNotUpdateFirmwareConfiguration() {
-        verifyAddTestBssidToBlocklist();
-        when(mClock.getWallClockMillis()).thenReturn(BASE_BLOCKLIST_DURATION + 1);
-        assertEquals(0, mBssidBlocklistMonitor
-                .updateAndGetBssidBlocklistForSsid(TEST_SSID_2).size());
-        verify(mWifiConnectivityHelper, never()).setFirmwareRoamingConfiguration(
-                eq(new ArrayList<>()), eq(new ArrayList<>()));
-    }
-
-    /**
-     * Verify that when adding a AP that had already been failing (therefore has a blocklist
-     * streak), we are setting the blocklist duration using an exponential backoff technique.
-     */
-    @Test
-    public void testBssidIsRemoveFromBlocklistAfterTimoutExponentialBackoff() {
-        verifyAddTestBssidToBlocklist();
-        int multiplier = 2;
-        long duration = 0;
-        for (int i = 1; i <= FAILURE_STREAK_CAP; i++) {
-            when(mWifiScoreCard.getBssidBlocklistStreak(anyString(), anyString(), anyInt()))
-                    .thenReturn(i);
-            when(mClock.getWallClockMillis()).thenReturn(0L);
-            verifyAddTestBssidToBlocklist();
-
-            // calculate the expected blocklist duration then verify that timeout happens
-            // exactly after the duration.
-            duration = multiplier * BASE_BLOCKLIST_DURATION;
-            when(mClock.getWallClockMillis()).thenReturn(duration);
-            assertTrue(mBssidBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
-            when(mClock.getWallClockMillis()).thenReturn(duration + 1);
-            assertEquals(0, mBssidBlocklistMonitor.updateAndGetBssidBlocklist().size());
-
-            multiplier *= 2;
-        }
-
-        // finally verify that the timout is capped by the FAILURE_STREAK_CAP
-        when(mWifiScoreCard.getBssidBlocklistStreak(anyString(), anyString(), anyInt()))
-                .thenReturn(FAILURE_STREAK_CAP + 1);
-        when(mClock.getWallClockMillis()).thenReturn(0L);
-        verifyAddTestBssidToBlocklist();
-        when(mClock.getWallClockMillis()).thenReturn(duration);
-        assertTrue(mBssidBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
-        when(mClock.getWallClockMillis()).thenReturn(duration + 1);
-        assertEquals(0, mBssidBlocklistMonitor.updateAndGetBssidBlocklist().size());
-    }
-
-    /**
-     * Verify that consecutive failures will add a BSSID to blocklist.
-     */
-    @Test
-    public void testRepeatedConnectionFailuresAddToBlocklist() {
-        // First verify that n-1 failrues does not add the BSSID to blocklist
-        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_L2_FAILURE,
-                NUM_FAILURES_TO_BLOCKLIST - 1);
-        assertEquals(0, mBssidBlocklistMonitor.updateAndGetBssidBlocklist().size());
-
-        // Simulate a long time passing to make sure failure counters are not being cleared through
-        // some time based check
-        when(mClock.getWallClockMillis()).thenReturn(10 * BASE_BLOCKLIST_DURATION);
-        assertEquals(0, mBssidBlocklistMonitor.updateAndGetBssidBlocklist().size());
-
-        // Verify that 1 more failure will add the BSSID to blacklist.
-        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_L2_FAILURE, 1);
-        assertTrue(mBssidBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
-    }
-
-    /**
-     * Verify that only abnormal disconnects that happened in a window of time right after
-     * connection gets counted in the BssidBlocklistMonitor.
-     */
-    @Test
-    public void testAbnormalDisconnectRecencyCheck() {
-        // does some setup so that 1 failure is enough to add the BSSID to blocklist.
-        when(mWifiScoreCard.getBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1,
-                BssidBlocklistMonitor.REASON_ABNORMAL_DISCONNECT)).thenReturn(1);
-
-        // simulate an abnormal disconnect coming in after the allowed window of time
-        when(mWifiScoreCard.getBssidConnectionTimestampMs(TEST_SSID_1, TEST_BSSID_1))
-                .thenReturn(0L);
-        when(mClock.getWallClockMillis()).thenReturn(ABNORMAL_DISCONNECT_TIME_WINDOW_MS + 1);
-        assertFalse(mBssidBlocklistMonitor.handleBssidConnectionFailure(TEST_BSSID_1, TEST_SSID_1,
-                BssidBlocklistMonitor.REASON_ABNORMAL_DISCONNECT, TEST_GOOD_RSSI));
-        verify(mWifiScoreCard, never()).incrementBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1,
-                BssidBlocklistMonitor.REASON_ABNORMAL_DISCONNECT);
-
-        // simulate another abnormal disconnect within the time window and verify the BSSID is
-        // added to blocklist.
-        when(mClock.getWallClockMillis()).thenReturn(ABNORMAL_DISCONNECT_TIME_WINDOW_MS);
-        assertTrue(mBssidBlocklistMonitor.handleBssidConnectionFailure(TEST_BSSID_1, TEST_SSID_1,
-                BssidBlocklistMonitor.REASON_ABNORMAL_DISCONNECT, TEST_GOOD_RSSI));
-        verify(mWifiScoreCard).incrementBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1,
-                BssidBlocklistMonitor.REASON_ABNORMAL_DISCONNECT);
-    }
-
-    /**
-     * Verify that when the BSSID blocklist streak is greater or equal to 1, then we block a
-     * BSSID on a single failure regardless of failure type.
-     */
-    @Test
-    public void testBlocklistStreakExpeditesAddingToBlocklist() {
-        when(mWifiScoreCard.getBssidBlocklistStreak(anyString(), anyString(), anyInt()))
-                .thenReturn(1);
-        assertTrue(mBssidBlocklistMonitor.handleBssidConnectionFailure(
-                TEST_BSSID_1, TEST_SSID_1, TEST_L2_FAILURE, TEST_GOOD_RSSI));
-        assertTrue(mBssidBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
-    }
-
-    /**
-     * Verify that onSuccessfulConnection resets L2 related failure counts.
-     */
-    @Test
-    public void testL2FailureCountIsResetAfterSuccessfulConnection() {
-        // First simulate getting a particular L2 failure n-1 times
-        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_L2_FAILURE,
-                NUM_FAILURES_TO_BLOCKLIST - 1);
-
-        // Verify that a connection success event will clear the failure count.
-        mBssidBlocklistMonitor.handleBssidConnectionSuccess(TEST_BSSID_1, TEST_SSID_1);
-        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_L2_FAILURE,
-                NUM_FAILURES_TO_BLOCKLIST - 1);
-
-        // Verify we have not blocklisted anything yet because the failure count was cleared.
-        assertEquals(0, mBssidBlocklistMonitor.updateAndGetBssidBlocklist().size());
-
-        // Verify that TEST_BSSID_1 is added to blocklist after 1 more failure.
-        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_L2_FAILURE, 1);
-        assertTrue(mBssidBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
-    }
-
-    /**
-     * Verify that handleDhcpProvisioningSuccess resets DHCP failure counts.
-     */
-    @Test
-    public void testL3FailureCountIsResetAfterDhcpConfiguration() {
-        // First simulate getting an DHCP failure n-1 times.
-        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_DHCP_FAILURE,
-                NUM_FAILURES_TO_BLOCKLIST - 1);
-
-        // Verify that a dhcp provisioning success event will clear appropirate failure counts.
-        mBssidBlocklistMonitor.handleDhcpProvisioningSuccess(TEST_BSSID_1, TEST_SSID_1);
-        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_DHCP_FAILURE,
-                NUM_FAILURES_TO_BLOCKLIST - 1);
-
-        // Verify we have not blocklisted anything yet because the failure count was cleared.
-        assertEquals(0, mBssidBlocklistMonitor.updateAndGetBssidBlocklist().size());
-
-        // Verify that TEST_BSSID_1 is added to blocklist after 1 more failure.
-        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_DHCP_FAILURE, 1);
-        assertTrue(mBssidBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
-    }
-
-    /**
-     * Verify that handleBssidConnectionSuccess resets appropriate blocklist streak counts, and
-     * notifies WifiScorecard of the successful connection.
-     */
-    @Test
-    public void testNetworkConnectionResetsBlocklistStreak() {
-        when(mClock.getWallClockMillis()).thenReturn(ABNORMAL_DISCONNECT_RESET_TIME_MS + 1);
-        mBssidBlocklistMonitor.handleBssidConnectionSuccess(TEST_BSSID_1, TEST_SSID_1);
-        verify(mWifiScoreCard).resetBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1,
-                BssidBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA);
-        verify(mWifiScoreCard).resetBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1,
-                BssidBlocklistMonitor.REASON_WRONG_PASSWORD);
-        verify(mWifiScoreCard).resetBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1,
-                BssidBlocklistMonitor.REASON_EAP_FAILURE);
-        verify(mWifiScoreCard).resetBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1,
-                BssidBlocklistMonitor.REASON_ASSOCIATION_REJECTION);
-        verify(mWifiScoreCard).resetBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1,
-                BssidBlocklistMonitor.REASON_ASSOCIATION_TIMEOUT);
-        verify(mWifiScoreCard).resetBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1,
-                BssidBlocklistMonitor.REASON_AUTHENTICATION_FAILURE);
-        verify(mWifiScoreCard).setBssidConnectionTimestampMs(TEST_SSID_1, TEST_BSSID_1,
-                ABNORMAL_DISCONNECT_RESET_TIME_MS + 1);
-        verify(mWifiScoreCard).resetBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1,
-                BssidBlocklistMonitor.REASON_ABNORMAL_DISCONNECT);
-        verify(mWifiScoreCard).resetBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1,
-                BssidBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE);
-    }
-
-    /**
-     * Verify that the abnormal disconnect streak is not reset if insufficient time has passed.
-     */
-    @Test
-    public void testNetworkConnectionNotResetAbnormalDisconnectStreak() {
-        when(mClock.getWallClockMillis()).thenReturn(ABNORMAL_DISCONNECT_RESET_TIME_MS);
-        mBssidBlocklistMonitor.handleBssidConnectionSuccess(TEST_BSSID_1, TEST_SSID_1);
-        verify(mWifiScoreCard).setBssidConnectionTimestampMs(TEST_SSID_1, TEST_BSSID_1,
-                ABNORMAL_DISCONNECT_RESET_TIME_MS);
-        verify(mWifiScoreCard, never()).resetBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1,
-                BssidBlocklistMonitor.REASON_ABNORMAL_DISCONNECT);
-    }
-
-    /**
-     * Verify that the streak count for REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE is not reset
-     * if insufficient time has passed.
-     */
-    @Test
-    public void testNetworkConnectionNotResetConnectedScoreStreak() {
-        when(mClock.getWallClockMillis()).thenReturn(ABNORMAL_DISCONNECT_RESET_TIME_MS);
-        mBssidBlocklistMonitor.handleBssidConnectionSuccess(TEST_BSSID_1, TEST_SSID_1);
-        verify(mWifiScoreCard).setBssidConnectionTimestampMs(TEST_SSID_1, TEST_BSSID_1,
-                ABNORMAL_DISCONNECT_RESET_TIME_MS);
-        verify(mWifiScoreCard, never()).resetBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1,
-                BssidBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE);
-    }
-
-    /**
-     * Verify that handleDhcpProvisioningSuccess resets appropriate blocklist streak counts.
-     */
-    @Test
-    public void testDhcpProvisioningResetsBlocklistStreak() {
-        mBssidBlocklistMonitor.handleDhcpProvisioningSuccess(TEST_BSSID_1, TEST_SSID_1);
-        verify(mWifiScoreCard).resetBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1,
-                BssidBlocklistMonitor.REASON_DHCP_FAILURE);
-    }
-
-    /**
-     * Verify that handleNetworkValidationSuccess resets appropriate blocklist streak counts
-     * and removes the BSSID from blocklist.
-     */
-    @Test
-    public void testNetworkValidationResetsBlocklistStreak() {
-        verifyAddTestBssidToBlocklist();
-        mBssidBlocklistMonitor.handleNetworkValidationSuccess(TEST_BSSID_1, TEST_SSID_1);
-        verify(mWifiScoreCard).resetBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1,
-                BssidBlocklistMonitor.REASON_NETWORK_VALIDATION_FAILURE);
-        assertEquals(0, mBssidBlocklistMonitor.updateAndGetBssidBlocklist().size());
-    }
-
-    /**
-     * Verify that L3 failure counts are not affected when L2 failure counts are reset.
-     */
-    @Test
-    public void testL3FailureCountIsNotResetByConnectionSuccess() {
-        // First simulate getting an L3 failure n-1 times.
-        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_DHCP_FAILURE,
-                NUM_FAILURES_TO_BLOCKLIST - 1);
-        assertEquals(0, mBssidBlocklistMonitor.updateAndGetBssidBlocklist().size());
-
-        // Verify that the failure counter is not cleared by |handleBssidConnectionSuccess|.
-        mBssidBlocklistMonitor.handleBssidConnectionSuccess(TEST_BSSID_1, TEST_SSID_1);
-        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_DHCP_FAILURE, 1);
-        assertEquals(1, mBssidBlocklistMonitor.updateAndGetBssidBlocklist().size());
-    }
-
-    /**
-     * Verify that the blocklist streak is incremented after adding a BSSID to blocklist.
-     * And then verify the blocklist streak is not reset by a regular timeout.
-     */
-    @Test
-    public void testIncrementingBlocklistStreakCount() {
-        for (Map.Entry<Integer, Integer> entry : BLOCK_REASON_TO_DISABLE_THRESHOLD_MAP.entrySet()) {
-            int reason = entry.getKey();
-            int threshold = entry.getValue();
-            when(mClock.getWallClockMillis()).thenReturn(0L);
-            handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_SSID_1, reason, threshold);
-
-            // verify that the blocklist streak is incremented
-            verify(mWifiScoreCard).incrementBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1, reason);
-
-            // Verify that TEST_BSSID_1 is removed from the blocklist after the timeout duration.
-            when(mClock.getWallClockMillis()).thenReturn(BASE_BLOCKLIST_DURATION + 1);
-            assertEquals(0, mBssidBlocklistMonitor.updateAndGetBssidBlocklist().size());
-
-            // But the blocklist streak count is not cleared
-            verify(mWifiScoreCard, never()).resetBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1,
-                    reason);
-        }
-    }
-
-    /**
-     * Verify that when a failure signal is received for a BSSID with different SSID from before,
-     * then the failure counts are reset.
-     */
-    @Test
-    public void testFailureCountIsResetIfSsidChanges() {
-        // First simulate getting a particular L2 failure n-1 times
-        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_L2_FAILURE,
-                NUM_FAILURES_TO_BLOCKLIST - 1);
-
-        // Verify that when the SSID changes, the failure counts are cleared.
-        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_SSID_2, TEST_L2_FAILURE,
-                NUM_FAILURES_TO_BLOCKLIST - 1);
-
-        // Verify we have not blocklisted anything yet because the failure count was cleared.
-        assertEquals(0, mBssidBlocklistMonitor.updateAndGetBssidBlocklist().size());
-
-        // Verify that TEST_BSSID_1 is added to blocklist after 1 more failure.
-        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_SSID_2, TEST_L2_FAILURE, 1);
-        assertTrue(mBssidBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
-    }
-
-    /**
-     * Verify that a BSSID is not added to blocklist as long as
-     * mWifiLastResortWatchdog.shouldIgnoreBssidUpdate is returning true, for failure reasons
-     * that are also being tracked by the watchdog.
-     */
-    @Test
-    public void testWatchdogIsGivenChanceToTrigger() {
-        // Verify that |shouldIgnoreBssidUpdate| can prevent a BSSID from being added to blocklist.
-        when(mWifiLastResortWatchdog.shouldIgnoreBssidUpdate(anyString())).thenReturn(true);
-        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_L2_FAILURE,
-                NUM_FAILURES_TO_BLOCKLIST * 2);
-        assertEquals(0, mBssidBlocklistMonitor.updateAndGetBssidBlocklist().size());
-
-        // Verify that after watchdog is okay with blocking a BSSID, it gets blocked after 1
-        // more failure.
-        when(mWifiLastResortWatchdog.shouldIgnoreBssidUpdate(anyString())).thenReturn(false);
-        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_L2_FAILURE, 1);
-        assertTrue(mBssidBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
-    }
-
-    /**
-     * Verify that non device related errors, and errors that are not monitored by the watchdog
-     * bypasses the watchdog check.
-     */
-    @Test
-    public void testUnrelatedErrorsBypassWatchdogCheck() {
-        when(mWifiLastResortWatchdog.shouldIgnoreBssidUpdate(anyString())).thenReturn(true);
-        verifyAddTestBssidToBlocklist();
-        verify(mWifiLastResortWatchdog, never()).shouldIgnoreBssidUpdate(anyString());
-    }
-
-    /**
-     * Verify that we are correctly filtering by SSID when sending a blocklist down to firmware.
-     */
-    @Test
-    public void testSendBlocklistToFirmwareFilterBySsid() {
-        verifyAddMultipleBssidsToBlocklist();
-
-        // Verify we are sending 2 BSSIDs down to the firmware for SSID_1.
-        ArrayList<String> blocklist1 = new ArrayList<>();
-        blocklist1.add(TEST_BSSID_2);
-        blocklist1.add(TEST_BSSID_1);
-        mBssidBlocklistMonitor.updateFirmwareRoamingConfiguration(TEST_SSID_1);
-        verify(mWifiConnectivityHelper).setFirmwareRoamingConfiguration(eq(blocklist1),
-                eq(new ArrayList<>()));
-
-        // Verify we are sending 1 BSSID down to the firmware for SSID_2.
-        ArrayList<String> blocklist2 = new ArrayList<>();
-        blocklist2.add(TEST_BSSID_3);
-        mBssidBlocklistMonitor.updateFirmwareRoamingConfiguration(TEST_SSID_2);
-        verify(mWifiConnectivityHelper).setFirmwareRoamingConfiguration(eq(blocklist2),
-                eq(new ArrayList<>()));
-
-        // Verify we are not sending any BSSIDs down to the firmware since there does not
-        // exists any BSSIDs for TEST_SSID_3 in the blocklist.
-        mBssidBlocklistMonitor.updateFirmwareRoamingConfiguration(TEST_SSID_3);
-        verify(mWifiConnectivityHelper).setFirmwareRoamingConfiguration(eq(new ArrayList<>()),
-                eq(new ArrayList<>()));
-    }
-
-    /**
-     * Verify that when sending the blocklist down to firmware, the list is sorted by latest
-     * end time first.
-     * Also verify that when there are more blocklisted BSSIDs than the allowed limit by the
-     * firmware, the list sent down is trimmed.
-     */
-    @Test
-    public void testMostRecentBlocklistEntriesAreSentToFirmware() {
-        // Add BSSIDs to blocklist
-        String bssid = "0a:08:5c:67:89:0";
-        for (int i = 0; i < 10; i++) {
-            when(mClock.getWallClockMillis()).thenReturn((long) i);
-            mBssidBlocklistMonitor.handleBssidConnectionFailure(bssid + i,
-                    TEST_SSID_1, BssidBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA,
-                    TEST_GOOD_RSSI);
-
-            // This will build a List of BSSIDs starting from the latest added ones that is at
-            // most size |TEST_NUM_MAX_FIRMWARE_SUPPORT_BSSIDS|.
-            // Then verify that the blocklist is sent down in this sorted order.
-            ArrayList<String> blocklist = new ArrayList<>();
-            for (int j = i; j > i - TEST_NUM_MAX_FIRMWARE_SUPPORT_BSSIDS; j--) {
-                if (j < 0) {
-                    break;
-                }
-                blocklist.add(bssid + j);
-            }
-            mBssidBlocklistMonitor.updateFirmwareRoamingConfiguration(TEST_SSID_1);
-            verify(mWifiConnectivityHelper).setFirmwareRoamingConfiguration(eq(blocklist),
-                    eq(new ArrayList<>()));
-        }
-        assertEquals(10, mBssidBlocklistMonitor.updateAndGetBssidBlocklist().size());
-    }
-
-    /**
-     * Verifies that when firmware roaming is disabled, the blocklist does not get plumbed to
-     * hardware, but the blocklist should still accessible by the framework.
-     */
-    @Test
-    public void testFirmwareRoamingDisabled() {
-        when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(false);
-        verifyAddTestBssidToBlocklist();
-
-        mBssidBlocklistMonitor.updateFirmwareRoamingConfiguration(TEST_SSID_1);
-        verify(mWifiConnectivityHelper, never()).setFirmwareRoamingConfiguration(any(), any());
-    }
-
-    /**
-     * Verify that clearBssidBlocklist resets internal state.
-     */
-    @Test
-    public void testClearBssidBlocklist() {
-        verifyAddTestBssidToBlocklist();
-        mBssidBlocklistMonitor.clearBssidBlocklist();
-        assertEquals(0, mBssidBlocklistMonitor.updateAndGetBssidBlocklist().size());
-
-    }
-
-    /**
-     * Verify that the BssidStatusHistoryLoggerSize is capped.
-     */
-    @Test
-    public void testBssidStatusHistoryLoggerSize() {
-        int bssidStatusHistoryLoggerSize = 30;
-        for (int i = 0; i < bssidStatusHistoryLoggerSize; i++) {
-            verifyAddTestBssidToBlocklist();
-            mBssidBlocklistMonitor.clearBssidBlocklist();
-            assertEquals(i + 1, mBssidBlocklistMonitor.getBssidStatusHistoryLoggerSize());
-        }
-        verifyAddTestBssidToBlocklist();
-        mBssidBlocklistMonitor.clearBssidBlocklist();
-        assertEquals(bssidStatusHistoryLoggerSize,
-                mBssidBlocklistMonitor.getBssidStatusHistoryLoggerSize());
-    }
-
-    /**
-     * Verify that clearBssidBlocklistForSsid removes all BSSIDs for that network from the
-     * blocklist.
-     */
-    @Test
-    public void testClearBssidBlocklistForSsid() {
-        verifyAddMultipleBssidsToBlocklist();
-
-        // Clear the blocklist for SSID 1.
-        mBssidBlocklistMonitor.clearBssidBlocklistForSsid(TEST_SSID_1);
-
-        // Verify that the blocklist is deleted for SSID 1 and the BSSID for SSID 2 still remains.
-        Set<String> bssidList = mBssidBlocklistMonitor.updateAndGetBssidBlocklist();
-        assertEquals(1, bssidList.size());
-        assertTrue(bssidList.contains(TEST_BSSID_3));
-        verify(mWifiScoreCard, never()).resetBssidBlocklistStreakForSsid(TEST_SSID_1);
-    }
-
-    /**
-     * Verify that handleNetworkRemoved removes all BSSIDs for that network from the blocklist
-     * and also reset the blocklist streak count from WifiScoreCard.
-     */
-    @Test
-    public void testHandleNetworkRemovedResetsState() {
-        verifyAddMultipleBssidsToBlocklist();
-
-        // Clear the blocklist for SSID 1.
-        mBssidBlocklistMonitor.handleNetworkRemoved(TEST_SSID_1);
-
-        // Verify that the blocklist is deleted for SSID 1 and the BSSID for SSID 2 still remains.
-        Set<String> bssidList = mBssidBlocklistMonitor.updateAndGetBssidBlocklist();
-        assertEquals(1, bssidList.size());
-        assertTrue(bssidList.contains(TEST_BSSID_3));
-        verify(mWifiScoreCard).resetBssidBlocklistStreakForSsid(TEST_SSID_1);
-    }
-
-    /**
-     * Verify that |blockBssidForDurationMs| adds a BSSID to blocklist for the specified duration.
-     */
-    @Test
-    public void testBlockBssidForDurationMs() {
-        when(mClock.getWallClockMillis()).thenReturn(0L);
-        long testDuration = 5500L;
-        mBssidBlocklistMonitor.blockBssidForDurationMs(TEST_BSSID_1, TEST_SSID_1, testDuration,
-                TEST_FRAMEWORK_BLOCK_REASON, TEST_GOOD_RSSI);
-        assertEquals(1, mBssidBlocklistMonitor.updateAndGetBssidBlocklist().size());
-
-        // Verify that the BSSID is removed from blocklist by clearBssidBlocklistForSsid
-        mBssidBlocklistMonitor.clearBssidBlocklistForSsid(TEST_SSID_1);
-        assertEquals(0, mBssidBlocklistMonitor.updateAndGetBssidBlocklist().size());
-        assertEquals(1, mBssidBlocklistMonitor.getBssidStatusHistoryLoggerSize());
-
-        // Add the BSSID to blocklist again.
-        mBssidBlocklistMonitor.blockBssidForDurationMs(TEST_BSSID_1, TEST_SSID_1, testDuration,
-                TEST_FRAMEWORK_BLOCK_REASON, TEST_GOOD_RSSI);
-        assertEquals(1, mBssidBlocklistMonitor.updateAndGetBssidBlocklist().size());
-
-        // Verify that the BSSID is removed from blocklist once the specified duration is over.
-        when(mClock.getWallClockMillis()).thenReturn(testDuration + 1);
-        assertEquals(0, mBssidBlocklistMonitor.updateAndGetBssidBlocklist().size());
-        assertEquals(2, mBssidBlocklistMonitor.getBssidStatusHistoryLoggerSize());
-    }
-
-    /**
-     * Verify that invalid inputs are handled and result in no-op.
-     */
-    @Test
-    public void testBlockBssidForDurationMsInvalidInputs() {
-        // test invalid BSSID
-        when(mClock.getWallClockMillis()).thenReturn(0L);
-        long testDuration = 5500L;
-        mBssidBlocklistMonitor.blockBssidForDurationMs(null, TEST_SSID_1, testDuration,
-                TEST_FRAMEWORK_BLOCK_REASON, TEST_GOOD_RSSI);
-        assertEquals(0, mBssidBlocklistMonitor.updateAndGetBssidBlocklist().size());
-
-        // test invalid SSID
-        mBssidBlocklistMonitor.blockBssidForDurationMs(TEST_BSSID_1, null, testDuration,
-                TEST_FRAMEWORK_BLOCK_REASON, TEST_GOOD_RSSI);
-        assertEquals(0, mBssidBlocklistMonitor.updateAndGetBssidBlocklist().size());
-
-        // test invalid duration
-        mBssidBlocklistMonitor.blockBssidForDurationMs(TEST_BSSID_1, TEST_SSID_1, -1,
-                TEST_FRAMEWORK_BLOCK_REASON, TEST_GOOD_RSSI);
-        assertEquals(0, mBssidBlocklistMonitor.updateAndGetBssidBlocklist().size());
-    }
-
-    private void simulateRssiUpdate(String bssid, int rssi) {
-        ScanDetail scanDetail = mock(ScanDetail.class);
-        ScanResult scanResult = mock(ScanResult.class);
-        scanResult.BSSID = bssid;
-        scanResult.level = rssi;
-        when(scanDetail.getScanResult()).thenReturn(scanResult);
-        List<ScanDetail> scanDetails = new ArrayList<>();
-        scanDetails.add(scanDetail);
-        mBssidBlocklistMonitor.tryEnablingBlockedBssids(scanDetails);
-    }
-
-    /**
-     * Verify that if the RSSI is low when the BSSID is blocked, a RSSI improvement will remove
-     * the BSSID from blocklist.
-     */
-    @Test
-    public void testUnblockBssidAfterRssiImproves() {
-        when(mClock.getWallClockMillis()).thenReturn(0L);
-        // verify TEST_BSSID_1 is blocked
-        mBssidBlocklistMonitor.handleBssidConnectionFailure(
-                TEST_BSSID_1, TEST_SSID_1, BssidBlocklistMonitor.REASON_EAP_FAILURE,
-                TEST_SUFFICIENT_RSSI - MIN_RSSI_DIFF_TO_UNBLOCK_BSSID);
-        assertTrue(mBssidBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
-
-        // verify the blocklist is not cleared when the rssi improvement is not large enough.
-        simulateRssiUpdate(TEST_BSSID_1, TEST_SUFFICIENT_RSSI - 1);
-        assertTrue(mBssidBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
-
-        // verify TEST_BSSID_1 is removed from the blocklist after RSSI improves
-        simulateRssiUpdate(TEST_BSSID_1, TEST_SUFFICIENT_RSSI);
-        assertEquals(0, mBssidBlocklistMonitor.updateAndGetBssidBlocklist().size());
-    }
-
-    /**
-     * Verify that if the RSSI is already good when the BSSID is blocked, a RSSI improvement will
-     * not remove the BSSID from blocklist.
-     */
-    @Test
-    public void testBssidNotUnblockedIfRssiAlreadyGood() {
-        when(mClock.getWallClockMillis()).thenReturn(0L);
-        // verify TEST_BSSID_1 is blocked
-        mBssidBlocklistMonitor.handleBssidConnectionFailure(
-                TEST_BSSID_1, TEST_SSID_1, BssidBlocklistMonitor.REASON_EAP_FAILURE,
-                TEST_SUFFICIENT_RSSI);
-        assertTrue(mBssidBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
-
-        // verify TEST_BSSID_1 is not removed from blocklist
-        simulateRssiUpdate(TEST_BSSID_1, TEST_GOOD_RSSI);
-        assertTrue(mBssidBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
-    }
-
-    /**
-     * Verify that the logic to unblock BSSIDs after RSSI improvement does not apply for some
-     * failure reasons.
-     */
-    @Test
-    public void testRssiImprovementNotUnblockBssidForSomeFailureReasons() {
-        when(mClock.getWallClockMillis()).thenReturn(0L);
-        mBssidBlocklistMonitor.handleBssidConnectionFailure(
-                TEST_BSSID_1, TEST_SSID_1, BssidBlocklistMonitor.REASON_WRONG_PASSWORD,
-                TEST_SUFFICIENT_RSSI - MIN_RSSI_DIFF_TO_UNBLOCK_BSSID);
-        assertTrue(mBssidBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
-
-        simulateRssiUpdate(TEST_BSSID_1, TEST_SUFFICIENT_RSSI);
-        assertTrue(mBssidBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
-    }
-
-    /**
-     * Verify the failure reasons for all blocked BSSIDs are retrieved.
-     */
-    @Test
-    public void testGetFailureReasonsForSsid() {
-        // Null input should not crash
-        mBssidBlocklistMonitor.getFailureReasonsForSsid(null).size();
-        assertEquals(0, mBssidBlocklistMonitor.getFailureReasonsForSsid(TEST_SSID_1).size());
-        mBssidBlocklistMonitor.blockBssidForDurationMs(TEST_BSSID_1, TEST_SSID_1, 1000,
-                BssidBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA, TEST_GOOD_RSSI);
-        mBssidBlocklistMonitor.blockBssidForDurationMs(TEST_BSSID_2, TEST_SSID_1, 1000,
-                BssidBlocklistMonitor.REASON_ABNORMAL_DISCONNECT, TEST_GOOD_RSSI);
-
-        assertEquals(2, mBssidBlocklistMonitor.getFailureReasonsForSsid(TEST_SSID_1).size());
-        assertTrue(mBssidBlocklistMonitor.getFailureReasonsForSsid(TEST_SSID_1)
-                .contains(BssidBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA));
-        assertTrue(mBssidBlocklistMonitor.getFailureReasonsForSsid(TEST_SSID_1)
-                .contains(BssidBlocklistMonitor.REASON_ABNORMAL_DISCONNECT));
-    }
-}
diff --git a/service/tests/wifitests/src/com/android/server/wifi/CandidateScorerTest.java b/service/tests/wifitests/src/com/android/server/wifi/CandidateScorerTest.java
index 7b45848..0238900 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/CandidateScorerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/CandidateScorerTest.java
@@ -310,6 +310,93 @@
     }
 
     /**
+     * Prefer not oem paid suggestion over privileged oem paid suggestion even though the OEM paid
+     * network has better t'put & RSSI.
+     */
+    @Test
+    public void testPreferNotOemPaidOverOemPaid() throws Exception {
+        if (mExpectedExpId != ThroughputScorer.THROUGHPUT_SCORER_DEFAULT_EXPID) return;
+        assertThat(evaluate(mCandidate1.setScanRssi(-71)
+                        .setPredictedThroughputMbps(100)
+                        .setEphemeral(true)
+                        .setOemPaid(false)),
+                greaterThan(evaluate(mCandidate2.setScanRssi(-40)
+                        .setEphemeral(true)
+                        .setPredictedThroughputMbps(1000)
+                        .setOemPaid(true))));
+    }
+
+    /**
+     * Prefer not oem paid metered suggestion over privileged oem paid suggestion even though the
+     * OEM paid network has better t'put & RSSI.
+     */
+    @Test
+    public void testPreferNotOemPaidMeteredOverOemPaid() throws Exception {
+        if (mExpectedExpId != ThroughputScorer.THROUGHPUT_SCORER_DEFAULT_EXPID) return;
+        assertThat(evaluate(mCandidate1.setScanRssi(-71)
+                        .setPredictedThroughputMbps(100)
+                        .setEphemeral(true)
+                        .setMetered(true)
+                        .setOemPaid(false)),
+                greaterThan(evaluate(mCandidate2.setScanRssi(-40)
+                        .setEphemeral(true)
+                        .setPredictedThroughputMbps(1000)
+                        .setOemPaid(true))));
+    }
+
+    /**
+     * Prefer not oem paid suggestion over privileged oem private suggestion even though the OEM
+     * paid network has better t'put & RSSI.
+     */
+    @Test
+    public void testPreferNotOemPrivateOverOemPrivate() throws Exception {
+        if (mExpectedExpId != ThroughputScorer.THROUGHPUT_SCORER_DEFAULT_EXPID) return;
+        assertThat(evaluate(mCandidate1.setScanRssi(-71)
+                        .setPredictedThroughputMbps(100)
+                        .setEphemeral(true)
+                        .setOemPrivate(false)),
+                greaterThan(evaluate(mCandidate2.setScanRssi(-40)
+                        .setEphemeral(true)
+                        .setPredictedThroughputMbps(1000)
+                        .setOemPaid(true))));
+    }
+
+    /**
+     * Prefer not oem paid metered suggestion over privileged oem private suggestion even though the
+     * OEM paid network has better t'put & RSSI.
+     */
+    @Test
+    public void testPreferNotOemPrivateMeteredOverOemPrivate() throws Exception {
+        if (mExpectedExpId != ThroughputScorer.THROUGHPUT_SCORER_DEFAULT_EXPID) return;
+        assertThat(evaluate(mCandidate1.setScanRssi(-71)
+                        .setPredictedThroughputMbps(100)
+                        .setEphemeral(true)
+                        .setMetered(true)
+                        .setOemPrivate(false)),
+                greaterThan(evaluate(mCandidate2.setScanRssi(-40)
+                        .setEphemeral(true)
+                        .setPredictedThroughputMbps(1000)
+                        .setOemPaid(true))));
+    }
+
+    /**
+     * Prefer oem paid suggestion over oem private suggestion even though the
+     * OEM private network has better t'put & RSSI.
+     */
+    @Test
+    public void testPreferOemPaidOverOemPrivate() throws Exception {
+        if (mExpectedExpId != ThroughputScorer.THROUGHPUT_SCORER_DEFAULT_EXPID) return;
+        assertThat(evaluate(mCandidate1.setScanRssi(-71)
+                        .setPredictedThroughputMbps(100)
+                        .setEphemeral(true)
+                        .setOemPaid(true)),
+                greaterThan(evaluate(mCandidate2.setScanRssi(-40)
+                        .setPredictedThroughputMbps(1000)
+                        .setEphemeral(true)
+                        .setOemPrivate(true))));
+    }
+
+    /**
      * Prefer carrier untrusted over other untrusted.
      */
     @Test
@@ -324,4 +411,35 @@
                                                 .setTrusted(false))));
     }
 
+    /**
+     * Verify that the ThroughputScorer prefers a current network that has internet over a
+     * candidate that has no internet.
+     */
+    @Test
+    public void testPreferCurrentNetworkWithInternetOverNetworkWithNoInternet() throws Exception {
+        if (mExpectedExpId == ThroughputScorer.THROUGHPUT_SCORER_DEFAULT_EXPID) {
+            // First verify that when evaluated separately, mCandidate2 has a higher score due
+            // to it having better RSSI and throughput.
+            mCandidate1.setScanRssi(-77)
+                    .setPredictedThroughputMbps(30)
+                    .setCurrentNetwork(true)
+                    .setNoInternetAccess(false);
+            mCandidate2.setScanRssi(-40)
+                    .setPredictedThroughputMbps(100)
+                    .setCurrentNetwork(false)
+                    .setNoInternetAccess(true)
+                    .setNoInternetAccessExpected(false);
+            double score1 = evaluate(mCandidate1);
+            assertThat(evaluate(mCandidate2), greaterThan(score1));
+
+            // Then verify that when evaluated together, mCandidate1 wins because it is the current
+            // network and has internet
+            List<Candidate> candidates = new ArrayList<>();
+            candidates.add(mCandidate1);
+            candidates.add(mCandidate2);
+            ScoredCandidate choice = mCandidateScorer.scoreCandidates(candidates);
+            assertEquals(score1, choice.value, TOL);
+        }
+    }
+
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java b/service/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java
index baa5ca6..f31b8c4 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java
@@ -16,13 +16,22 @@
 
 package com.android.server.wifi;
 
+import static android.content.Intent.ACTION_SCREEN_OFF;
+import static android.content.Intent.ACTION_SCREEN_ON;
+import static android.net.NetworkInfo.DetailedState.CONNECTED;
+import static android.net.NetworkInfo.DetailedState.CONNECTING;
+import static android.net.NetworkInfo.DetailedState.OBTAINING_IPADDR;
 import static android.net.wifi.WifiConfiguration.METERED_OVERRIDE_METERED;
 import static android.net.wifi.WifiConfiguration.METERED_OVERRIDE_NONE;
 import static android.net.wifi.WifiConfiguration.METERED_OVERRIDE_NOT_METERED;
 import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.DISABLED_NONE;
 import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.DISABLED_NO_INTERNET_TEMPORARY;
 
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_PRIMARY;
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SECONDARY_TRANSIENT;
 import static com.android.server.wifi.ClientModeImpl.CMD_PRE_DHCP_ACTION;
+import static com.android.server.wifi.ClientModeImpl.WIFI_WORK_SOURCE;
+import static com.android.server.wifi.WifiSettingsConfigStore.WIFI_STA_FACTORY_MAC_ADDRESS;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -31,6 +40,7 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyByte;
@@ -40,6 +50,7 @@
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.argThat;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
@@ -54,18 +65,20 @@
 import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.withSettings;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.test.MockAnswerUtil.AnswerWithArguments;
 import android.app.test.TestAlarmManager;
-import android.bluetooth.BluetoothAdapter;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.hardware.wifi.supplicant.V1_0.ISupplicantStaIfaceCallback;
-import android.net.ConnectivityManager;
+import android.hardware.wifi.supplicant.V1_4.ISupplicantStaIfaceCallback.AssociationRejectionData;
+import android.hardware.wifi.supplicant.V1_4.ISupplicantStaIfaceCallback.MboAssocDisallowedReasonCode;
+import android.net.CaptivePortalData;
 import android.net.DhcpResultsParcelable;
-import android.net.INetworkAgent;
-import android.net.INetworkAgentRegistry;
 import android.net.InetAddresses;
 import android.net.Layer2InformationParcelable;
 import android.net.Layer2PacketParcelable;
@@ -77,10 +90,14 @@
 import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
+import android.net.NetworkProvider;
 import android.net.NetworkSpecifier;
 import android.net.StaticIpConfiguration;
+import android.net.Uri;
 import android.net.ip.IIpClient;
 import android.net.ip.IpClientCallbacks;
+import android.net.vcn.VcnManager;
+import android.net.vcn.VcnNetworkPolicyResult;
 import android.net.wifi.IActionListener;
 import android.net.wifi.ScanResult;
 import android.net.wifi.SupplicantState;
@@ -90,7 +107,7 @@
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiNetworkAgentSpecifier;
-import android.net.wifi.WifiScanner;
+import android.net.wifi.WifiNetworkSpecifier;
 import android.net.wifi.WifiSsid;
 import android.net.wifi.hotspot2.IProvisioningCallback;
 import android.net.wifi.hotspot2.OsuProvider;
@@ -101,20 +118,13 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.IPowerManager;
-import android.os.IThermalService;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Messenger;
 import android.os.PowerManager;
 import android.os.Process;
-import android.os.RemoteException;
-import android.os.UserManager;
 import android.os.test.TestLooper;
 import android.provider.Settings;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.test.mock.MockContentProvider;
 import android.test.mock.MockContentResolver;
@@ -128,17 +138,23 @@
 import com.android.internal.util.IState;
 import com.android.internal.util.StateMachine;
 import com.android.modules.utils.build.SdkLevel;
+import com.android.server.wifi.ClientMode.LinkProbeCallback;
+import com.android.server.wifi.ClientModeManagerBroadcastQueue.QueuedBroadcast;
+import com.android.server.wifi.WifiNative.ConnectionCapabilities;
+import com.android.server.wifi.WifiScoreCard.PerNetwork;
 import com.android.server.wifi.hotspot2.NetworkDetail;
 import com.android.server.wifi.hotspot2.PasspointManager;
 import com.android.server.wifi.hotspot2.PasspointProvisioningTestUtil;
+import com.android.server.wifi.hotspot2.WnmData;
 import com.android.server.wifi.proto.nano.WifiMetricsProto;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.StaEvent;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.WifiIsUnusableEvent;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.WifiUsabilityStats;
+import com.android.server.wifi.util.ActionListenerWrapper;
+import com.android.server.wifi.util.NativeUtil;
 import com.android.server.wifi.util.RssiUtilTest;
 import com.android.server.wifi.util.ScanResultUtil;
 import com.android.server.wifi.util.WifiPermissionsUtil;
-import com.android.server.wifi.util.WifiPermissionsWrapper;
 import com.android.wifi.resources.R;
 
 import org.junit.After;
@@ -146,6 +162,7 @@
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatcher;
+import org.mockito.Captor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -153,16 +170,19 @@
 import org.mockito.quality.Strictness;
 
 import java.io.ByteArrayOutputStream;
+import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.net.URL;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.BitSet;
 import java.util.Collections;
-import java.util.List;
+import java.util.Random;
+import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.function.Consumer;
 
@@ -181,6 +201,7 @@
                     : ClientModeImpl.NUM_LOG_RECS_VERBOSE);
     private static final int FRAMEWORK_NETWORK_ID = 0;
     private static final int PASSPOINT_NETWORK_ID = 1;
+    private static final int OTHER_NETWORK_ID = 47;
     private static final int TEST_RSSI = -54;
     private static final int TEST_NETWORK_ID = 54;
     private static final int WPS_SUPPLICANT_NETWORK_ID = 5;
@@ -205,15 +226,33 @@
     private static final int DATA_SUBID = 1;
     private static final int CARRIER_ID_1 = 100;
 
+    private static final long TEST_BSSID = 0x112233445566L;
+    private static final int TEST_DELAY_IN_SECONDS = 300;
+
+    private static final int DEFINED_ERROR_CODE = 32764;
+    private static final String TEST_TERMS_AND_CONDITIONS_URL =
+            "https://policies.google.com/terms?hl=en-US";
+    private static final String VENUE_URL =
+            "https://www.android.com/android-11/";
+
     private long mBinderToken;
     private MockitoSession mSession;
+    private TestNetworkParams mTestNetworkParams = new TestNetworkParams();
+
+    /**
+     * Helper class for setting the default parameters of the WifiConfiguration that gets used
+     * in connect().
+     */
+    class TestNetworkParams {
+        public boolean hasEverConnected = false;
+    }
 
     private static <T> T mockWithInterfaces(Class<T> class1, Class<?>... interfaces) {
         return mock(class1, withSettings().extraInterfaces(interfaces));
     }
 
     private void enableDebugLogs() {
-        mCmi.enableVerboseLogging(1);
+        mCmi.enableVerboseLogging(true);
     }
 
     private FrameworkFacade getFrameworkFacade() throws Exception {
@@ -246,17 +285,16 @@
                 });
         when(context.getContentResolver()).thenReturn(mockContentResolver);
 
-        when(context.getSystemService(Context.POWER_SERVICE)).thenReturn(
-                new PowerManager(context, mock(IPowerManager.class), mock(IThermalService.class),
-                    new Handler()));
+        when(context.getSystemService(Context.POWER_SERVICE)).thenReturn(mPowerManager);
+        when(context.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
+        when(mPowerManager.isInteractive()).thenReturn(true);
+        when(mPowerManager.newWakeLock(anyInt(), anyString())).thenReturn(
+                mock(PowerManager.WakeLock.class));
 
         mAlarmManager = new TestAlarmManager();
         when(context.getSystemService(Context.ALARM_SERVICE)).thenReturn(
                 mAlarmManager.getAlarmManager());
 
-        when(context.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(
-                mConnectivityManager);
-
         when(context.getOpPackageName()).thenReturn(OP_PACKAGE_NAME);
 
         when(context.getSystemService(ActivityManager.class)).thenReturn(
@@ -317,9 +355,9 @@
 
     private static ScanDetail getGoogleGuestScanDetail(int rssi, String bssid, int freq) {
         ScanResult.InformationElement[] ie = new ScanResult.InformationElement[1];
-        ie[0] = ScanResults.generateSsidIe(sSSID);
-        NetworkDetail nd = new NetworkDetail(sBSSID, ie, new ArrayList<String>(), sFreq);
-        ScanDetail detail = new ScanDetail(nd, sWifiSsid, bssid, "", rssi, freq,
+        ie[0] = ScanResults.generateSsidIe(TEST_SSID);
+        NetworkDetail nd = new NetworkDetail(TEST_BSSID_STR, ie, new ArrayList<String>(), sFreq);
+        ScanDetail detail = new ScanDetail(nd, TEST_WIFI_SSID, bssid, "", rssi, freq,
                 Long.MAX_VALUE, /* needed so that scan results aren't rejected because
                                    there older than scan start */
                 ie, new ArrayList<String>(), ScanResults.generateIERawDatafromScanResultIE(ie));
@@ -331,7 +369,7 @@
         ScanResults sr = ScanResults.create(0, 2412, 2437, 2462, 5180, 5220, 5745, 5825);
         ArrayList<ScanDetail> list = sr.getScanDetailArrayList();
 
-        list.add(getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq));
+        list.add(getGoogleGuestScanDetail(TEST_RSSI, TEST_BSSID_STR, sFreq));
         return list;
     }
 
@@ -345,11 +383,14 @@
         mIpClientCallback.onProvisioningFailure(new LinkProperties());
     }
 
-    static final String   sSSID = "\"GoogleGuest\"";
-    static final String   SSID_NO_QUOTE = sSSID.replace("\"", "");
-    static final WifiSsid sWifiSsid = WifiSsid.createFromAsciiEncoded(SSID_NO_QUOTE);
-    static final String   sBSSID = "01:02:03:04:05:06";
-    static final String   sBSSID1 = "02:01:04:03:06:05";
+    static final String   TEST_SSID = "\"GoogleGuest\"";
+    static final String   SSID_NO_QUOTE = TEST_SSID.replace("\"", "");
+    static final WifiSsid TEST_WIFI_SSID = WifiSsid.createFromAsciiEncoded(SSID_NO_QUOTE);
+    static final String   TEST_SSID1 = "\"RandomSsid1\"";
+    static final String   SSID_NO_QUOTE1 = TEST_SSID1.replace("\"", "");
+    static final WifiSsid TEST_WIFI_SSID1 = WifiSsid.createFromAsciiEncoded(SSID_NO_QUOTE1);
+    static final String   TEST_BSSID_STR = "01:02:03:04:05:06";
+    static final String   TEST_BSSID_STR1 = "02:01:04:03:06:05";
     static final int      sFreq = 2437;
     static final int      sFreq1 = 5240;
     static final String   WIFI_IFACE_NAME = "mockWlan";
@@ -359,8 +400,6 @@
     HandlerThread mWifiCoreThread;
     HandlerThread mP2pThread;
     HandlerThread mSyncThread;
-    AsyncChannel  mCmiAsyncChannel;
-    INetworkAgent  mNetworkAgentBinder;
     TestAlarmManager mAlarmManager;
     MockWifiMonitor mWifiMonitor;
     TestLooper mLooper;
@@ -370,30 +409,25 @@
     IpClientCallbacks mIpClientCallback;
     OsuProvider mOsuProvider;
     WifiConfiguration mConnectedNetwork;
-    WifiCarrierInfoManager mWifiCarrierInfoManager;
+    ExtendedWifiInfo mWifiInfo;
+    ConnectionCapabilities mConnectionCapabilities = new ConnectionCapabilities();
 
-    @Mock WifiScanner mWifiScanner;
+    @Mock WifiNetworkAgent mWifiNetworkAgent;
     @Mock SupplicantStateTracker mSupplicantStateTracker;
     @Mock WifiMetrics mWifiMetrics;
-    @Mock UserManager mUserManager;
-    @Mock BackupManagerProxy mBackupManagerProxy;
-    @Mock WifiCountryCode mCountryCode;
     @Mock WifiInjector mWifiInjector;
     @Mock WifiLastResortWatchdog mWifiLastResortWatchdog;
-    @Mock BssidBlocklistMonitor mBssidBlocklistMonitor;
-    @Mock PropertyService mPropertyService;
-    @Mock BuildProperties mBuildProperties;
-    @Mock IBinder mPackageManagerBinder;
-    @Mock SarManager mSarManager;
+    @Mock WifiBlocklistMonitor mWifiBlocklistMonitor;
     @Mock WifiConfigManager mWifiConfigManager;
     @Mock WifiNative mWifiNative;
     @Mock WifiScoreCard mWifiScoreCard;
+    @Mock PerNetwork mPerNetwork;
+    @Mock WifiScoreCard.NetworkConnectionStats mPerNetworkRecentStats;
     @Mock WifiHealthMonitor mWifiHealthMonitor;
     @Mock WifiTrafficPoller mWifiTrafficPoller;
     @Mock WifiConnectivityManager mWifiConnectivityManager;
     @Mock WifiStateTracker mWifiStateTracker;
     @Mock PasspointManager mPasspointManager;
-    @Mock SelfRecovery mSelfRecovery;
     @Mock WifiPermissionsUtil mWifiPermissionsUtil;
     @Mock IIpClient mIpClient;
     @Mock TelephonyManager mTelephonyManager;
@@ -401,24 +435,20 @@
     @Mock WrongPasswordNotifier mWrongPasswordNotifier;
     @Mock Clock mClock;
     @Mock ScanDetailCache mScanDetailCache;
-    @Mock BaseWifiDiagnostics mWifiDiagnostics;
-    @Mock ConnectivityManager mConnectivityManager;
+    @Mock WifiDiagnostics mWifiDiagnostics;
     @Mock IProvisioningCallback mProvisioningCallback;
-    @Mock HandlerThread mWifiHandlerThread;
-    @Mock WifiPermissionsWrapper mWifiPermissionsWrapper;
     @Mock WakeupController mWakeupController;
     @Mock WifiDataStall mWifiDataStall;
     @Mock WifiNetworkFactory mWifiNetworkFactory;
     @Mock UntrustedWifiNetworkFactory mUntrustedWifiNetworkFactory;
+    @Mock OemWifiNetworkFactory mOemWifiNetworkFactory;
     @Mock WifiNetworkSuggestionsManager mWifiNetworkSuggestionsManager;
     @Mock LinkProbeManager mLinkProbeManager;
     @Mock PackageManager mPackageManager;
     @Mock WifiLockManager mWifiLockManager;
     @Mock AsyncChannel mNullAsyncChannel;
-    @Mock INetworkAgentRegistry mNetworkAgentRegistry;
     @Mock BatteryStatsManager mBatteryStatsManager;
     @Mock MboOceController mMboOceController;
-    @Mock SubscriptionManager mSubscriptionManager;
     @Mock ConnectionFailureNotifier mConnectionFailureNotifier;
     @Mock EapFailureNotifier mEapFailureNotifier;
     @Mock SimRequiredNotifier mSimRequiredNotifier;
@@ -426,11 +456,57 @@
     @Mock ScanRequestProxy mScanRequestProxy;
     @Mock DeviceConfigFacade mDeviceConfigFacade;
     @Mock Network mNetwork;
+    @Mock ConcreteClientModeManager mClientModeManager;
+    @Mock WifiScoreReport mWifiScoreReport;
+    @Mock PowerManager mPowerManager;
+    @Mock WifiP2pConnection mWifiP2pConnection;
+    @Mock WifiGlobals mWifiGlobals;
+    @Mock LinkProbeCallback mLinkProbeCallback;
+    @Mock ClientModeImplMonitor mCmiMonitor;
+    @Mock ClientModeManagerBroadcastQueue mBroadcastQueue;
+    @Mock WifiNetworkSelector mWifiNetworkSelector;
+    @Mock ActiveModeWarden mActiveModeWarden;
+    @Mock ClientModeManager mPrimaryClientModeManager;
+    @Mock WifiSettingsConfigStore mSettingsConfigStore;
+    @Mock Uri mMockUri;
+    @Mock WifiCarrierInfoManager mWifiCarrierInfoManager;
 
-    final ArgumentCaptor<WifiConfigManager.OnNetworkUpdateListener> mConfigUpdateListenerCaptor =
-            ArgumentCaptor.forClass(WifiConfigManager.OnNetworkUpdateListener.class);
+    @Captor ArgumentCaptor<WifiConfigManager.OnNetworkUpdateListener> mConfigUpdateListenerCaptor;
+    @Captor ArgumentCaptor<WifiNetworkAgent.Callback> mWifiNetworkAgentCallbackCaptor;
+    @Captor ArgumentCaptor<WifiCarrierInfoManager.OnCarrierOffloadDisabledListener>
+            mOffloadDisabledListenerArgumentCaptor = ArgumentCaptor.forClass(
+                    WifiCarrierInfoManager.OnCarrierOffloadDisabledListener.class);
+    @Captor ArgumentCaptor<BroadcastReceiver> mScreenStateBroadcastReceiverCaptor;
 
-    public ClientModeImplTest() throws Exception {
+    private void setUpWifiNative() throws Exception {
+        when(mWifiNative.getStaFactoryMacAddress(WIFI_IFACE_NAME)).thenReturn(
+                TEST_GLOBAL_MAC_ADDRESS);
+        doAnswer(new AnswerWithArguments() {
+            public void answer(WifiSettingsConfigStore.Key<String> key, Object macAddress) {
+                when(mSettingsConfigStore.get(WIFI_STA_FACTORY_MAC_ADDRESS))
+                        .thenReturn((String) macAddress);
+            }
+        }).when(mSettingsConfigStore).put(eq(WIFI_STA_FACTORY_MAC_ADDRESS), any(String.class));
+        when(mWifiNative.getMacAddress(WIFI_IFACE_NAME))
+                .thenReturn(TEST_GLOBAL_MAC_ADDRESS.toString());
+        ConnectionCapabilities cap = new ConnectionCapabilities();
+        cap.wifiStandard = ScanResult.WIFI_STANDARD_11AC;
+        when(mWifiNative.getConnectionCapabilities(WIFI_IFACE_NAME))
+                .thenReturn(mConnectionCapabilities);
+        when(mWifiNative.setStaMacAddress(eq(WIFI_IFACE_NAME), anyObject()))
+                .then(new AnswerWithArguments() {
+                    public boolean answer(String iface, MacAddress mac) {
+                        when(mWifiNative.getMacAddress(iface)).thenReturn(mac.toString());
+                        return true;
+                    }
+                });
+        when(mWifiNative.connectToNetwork(any(), any())).thenReturn(true);
+    }
+
+    /** Reset verify() counters on WifiNative, and restore when() mocks on mWifiNative */
+    private void resetWifiNative() throws Exception {
+        reset(mWifiNative);
+        setUpWifiNative();
     }
 
     @Before
@@ -444,117 +520,82 @@
 
         /** uncomment this to enable logs from ClientModeImpls */
         // enableDebugLogs();
-        mWifiMonitor = new MockWifiMonitor();
-        when(mWifiInjector.getWifiMetrics()).thenReturn(mWifiMetrics);
-        when(mWifiInjector.getClock()).thenReturn(new Clock());
-        when(mWifiInjector.getWifiLastResortWatchdog()).thenReturn(mWifiLastResortWatchdog);
-        when(mWifiInjector.getPropertyService()).thenReturn(mPropertyService);
-        when(mWifiInjector.getBuildProperties()).thenReturn(mBuildProperties);
-        when(mWifiInjector.getWifiBackupRestore()).thenReturn(mock(WifiBackupRestore.class));
-        when(mWifiInjector.getWifiDiagnostics()).thenReturn(mWifiDiagnostics);
-        when(mWifiInjector.getWifiConfigManager()).thenReturn(mWifiConfigManager);
-        when(mWifiInjector.getWifiScanner()).thenReturn(mWifiScanner);
-        when(mWifiInjector.makeWifiConnectivityManager(any()))
-                .thenReturn(mWifiConnectivityManager);
-        when(mWifiInjector.getPasspointManager()).thenReturn(mPasspointManager);
-        when(mWifiInjector.getWifiStateTracker()).thenReturn(mWifiStateTracker);
-        when(mWifiInjector.getWifiMonitor()).thenReturn(mWifiMonitor);
-        when(mWifiInjector.getWifiNative()).thenReturn(mWifiNative);
-        when(mWifiInjector.getWifiTrafficPoller()).thenReturn(mWifiTrafficPoller);
-        when(mWifiInjector.getSelfRecovery()).thenReturn(mSelfRecovery);
-        when(mWifiInjector.getWifiPermissionsUtil()).thenReturn(mWifiPermissionsUtil);
-        when(mWifiInjector.makeTelephonyManager()).thenReturn(mTelephonyManager);
+        mWifiMonitor = spy(new MockWifiMonitor());
         when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mDataTelephonyManager);
-        when(mWifiInjector.getClock()).thenReturn(mClock);
-        when(mWifiHandlerThread.getLooper()).thenReturn(mLooper.getLooper());
-        when(mWifiInjector.getWifiHandlerThread()).thenReturn(mWifiHandlerThread);
-        when(mWifiInjector.getWifiPermissionsWrapper()).thenReturn(mWifiPermissionsWrapper);
-        when(mWifiInjector.getWakeupController()).thenReturn(mWakeupController);
-        when(mWifiInjector.getScoringParams()).thenReturn(new ScoringParams());
-        when(mWifiInjector.getWifiDataStall()).thenReturn(mWifiDataStall);
-        when(mWifiInjector.makeWifiNetworkFactory(any(), any())).thenReturn(mWifiNetworkFactory);
-        when(mWifiInjector.makeUntrustedWifiNetworkFactory(any(), any()))
-                .thenReturn(mUntrustedWifiNetworkFactory);
-        when(mWifiInjector.getWifiNetworkSuggestionsManager())
-                .thenReturn(mWifiNetworkSuggestionsManager);
-        when(mWifiInjector.getWifiScoreCard()).thenReturn(mWifiScoreCard);
-        when(mWifiInjector.getWifiHealthMonitor()).thenReturn(mWifiHealthMonitor);
-        when(mWifiInjector.getWifiLockManager()).thenReturn(mWifiLockManager);
-        when(mWifiInjector.getWifiThreadRunner())
-                .thenReturn(new WifiThreadRunner(new Handler(mLooper.getLooper())));
-        when(mWifiInjector.makeConnectionFailureNotifier(any()))
-                .thenReturn(mConnectionFailureNotifier);
-        when(mWifiInjector.getBssidBlocklistMonitor()).thenReturn(mBssidBlocklistMonitor);
-        when(mWifiInjector.getThroughputPredictor()).thenReturn(mThroughputPredictor);
-        when(mWifiInjector.getScanRequestProxy()).thenReturn(mScanRequestProxy);
-        when(mWifiInjector.getDeviceConfigFacade()).thenReturn(mDeviceConfigFacade);
-        when(mWifiNetworkFactory.getSpecificNetworkRequestUidAndPackageName(any()))
+        when(mWifiNetworkFactory.getSpecificNetworkRequestUidAndPackageName(any(), any()))
                 .thenReturn(Pair.create(Process.INVALID_UID, ""));
-        when(mWifiNative.initialize()).thenReturn(true);
-        when(mWifiNative.getFactoryMacAddress(WIFI_IFACE_NAME)).thenReturn(
-                TEST_GLOBAL_MAC_ADDRESS);
-        when(mWifiNative.getMacAddress(WIFI_IFACE_NAME))
-                .thenReturn(TEST_GLOBAL_MAC_ADDRESS.toString());
-        WifiNative.ConnectionCapabilities cap = new WifiNative.ConnectionCapabilities();
-        cap.wifiStandard = ScanResult.WIFI_STANDARD_11AC;
-        when(mWifiNative.getConnectionCapabilities(WIFI_IFACE_NAME)).thenReturn(cap);
-        when(mWifiNative.setMacAddress(eq(WIFI_IFACE_NAME), anyObject()))
-                .then(new AnswerWithArguments() {
-                    public boolean answer(String iface, MacAddress mac) {
-                        when(mWifiNative.getMacAddress(iface)).thenReturn(mac.toString());
-                        return true;
-                    }
-                });
+        setUpWifiNative();
         doAnswer(new AnswerWithArguments() {
             public MacAddress answer(
                     WifiConfiguration config) {
                 return config.getRandomizedMacAddress();
             }
         }).when(mWifiConfigManager).getRandomizedMacAndUpdateIfNeeded(any());
-        when(mWifiNative.connectToNetwork(any(), any())).thenReturn(true);
 
+        mTestNetworkParams = new TestNetworkParams();
         when(mWifiNetworkFactory.hasConnectionRequests()).thenReturn(true);
         when(mUntrustedWifiNetworkFactory.hasConnectionRequests()).thenReturn(true);
+        when(mOemWifiNetworkFactory.hasConnectionRequests()).thenReturn(true);
 
         mFrameworkFacade = getFrameworkFacade();
         mContext = getContext();
+        mWifiInfo = new ExtendedWifiInfo(mWifiGlobals, WIFI_IFACE_NAME);
 
+        when(mWifiGlobals.isConnectedMacRandomizationEnabled()).thenReturn(true);
         mResources = getMockResources();
-        mResources.setBoolean(R.bool.config_wifi_connected_mac_randomization_supported, true);
         mResources.setIntArray(R.array.config_wifiRssiLevelThresholds,
                 RssiUtilTest.RSSI_THRESHOLDS);
-        mResources.setInteger(R.integer.config_wifiPollRssiIntervalMilliseconds, 3000);
+        mResources.setInteger(R.integer.config_wifiLinkBandwidthUpdateThresholdPercent, 25);
+        mResources.setInteger(R.integer.config_wifiClientModeImplNumLogRecs, 100);
+        mResources.setBoolean(R.bool.config_wifiEnableLinkedNetworkRoaming, true);
         when(mContext.getResources()).thenReturn(mResources);
-        when(mContext.createContextAsUser(any(), anyInt())).thenReturn(mContext);
+
+        when(mWifiGlobals.getPollRssiIntervalMillis()).thenReturn(3000);
+        when(mWifiGlobals.getIpReachabilityDisconnectEnabled()).thenReturn(true);
 
         when(mFrameworkFacade.getIntegerSetting(mContext,
                 Settings.Global.WIFI_FREQUENCY_BAND,
                 WifiManager.WIFI_FREQUENCY_BAND_AUTO)).thenReturn(
                 WifiManager.WIFI_FREQUENCY_BAND_AUTO);
         when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(true);
-        when(mWifiPermissionsWrapper.getLocalMacAddressPermission(anyInt()))
-                .thenReturn(PackageManager.PERMISSION_DENIED);
         doAnswer(inv -> {
             mIpClientCallback.onQuit();
             return null;
         }).when(mIpClient).shutdown();
-        when(mConnectivityManager.registerNetworkAgent(any(), any(), any(), any(), any(), any(),
-                anyInt())).thenReturn(mNetwork);
-        List<SubscriptionInfo> subList = Arrays.asList(mock(SubscriptionInfo.class));
-        when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(subList);
-        when(mSubscriptionManager.getActiveSubscriptionIdList())
-                .thenReturn(new int[]{DATA_SUBID});
+        when(mWifiNetworkAgent.getNetwork()).thenReturn(mNetwork);
 
-        WifiCarrierInfoManager tu = new WifiCarrierInfoManager(mTelephonyManager,
-                mSubscriptionManager, mWifiInjector, mock(FrameworkFacade.class),
-                mock(WifiContext.class), mock(WifiConfigStore.class), mock(Handler.class),
-                mWifiMetrics);
-        mWifiCarrierInfoManager = spy(tu);
         // static mocking
         mSession = ExtendedMockito.mockitoSession().strictness(Strictness.LENIENT)
+                .mockStatic(WifiInjector.class, withSettings().lenient())
                 .spyStatic(MacAddress.class)
                 .startMocking();
+        when(WifiInjector.getInstance()).thenReturn(mWifiInjector);
+
+        when(mWifiInjector.getActiveModeWarden()).thenReturn(mActiveModeWarden);
+        when(mActiveModeWarden.getPrimaryClientModeManager()).thenReturn(mPrimaryClientModeManager);
+        when(mPrimaryClientModeManager.getSupportedFeatures()).thenReturn(
+                WifiManager.WIFI_FEATURE_WPA3_SAE | WifiManager.WIFI_FEATURE_OWE);
+        when(mWifiInjector.getWifiGlobals()).thenReturn(mWifiGlobals);
+        when(mWifiGlobals.isWpa3SaeUpgradeEnabled()).thenReturn(true);
+        when(mWifiGlobals.isOweUpgradeEnabled()).thenReturn(true);
+        when(mWifiGlobals.getClientModeImplNumLogRecs()).thenReturn(100);
+        when(mWifiInjector.makeWifiNetworkAgent(any(), any(), any(), any(), any()))
+                .thenAnswer(new AnswerWithArguments() {
+                    public WifiNetworkAgent answer(
+                            @NonNull NetworkCapabilities nc,
+                            @NonNull LinkProperties linkProperties,
+                            @NonNull NetworkAgentConfig naConfig,
+                            @Nullable NetworkProvider provider,
+                            @NonNull WifiNetworkAgent.Callback callback) {
+                        when(mWifiNetworkAgent.getCallback()).thenReturn(callback);
+                        return mWifiNetworkAgent;
+                    }
+                });
+        when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_PRIMARY);
+
         initializeCmi();
+        // Retrieve factory MAC address on first bootup.
+        verify(mWifiNative).getStaFactoryMacAddress(WIFI_IFACE_NAME);
 
         mOsuProvider = PasspointProvisioningTestUtil.generateOsuProvider(true);
         mConnectedNetwork = spy(WifiConfigurationTestUtil.createOpenNetwork());
@@ -567,10 +608,23 @@
                 DeviceConfigFacade.DEFAULT_OVERLAPPING_CONNECTION_DURATION_THRESHOLD_MS);
         when(mWifiScoreCard.detectAbnormalConnectionFailure(anyString()))
                 .thenReturn(WifiHealthMonitor.REASON_NO_FAILURE);
-        when(mWifiScoreCard.detectAbnormalDisconnection())
+        when(mWifiScoreCard.detectAbnormalDisconnection(any()))
                 .thenReturn(WifiHealthMonitor.REASON_NO_FAILURE);
+        when(mPerNetwork.getRecentStats()).thenReturn(mPerNetworkRecentStats);
+        when(mWifiScoreCard.lookupNetwork(any())).thenReturn(mPerNetwork);
         when(mThroughputPredictor.predictMaxTxThroughput(any())).thenReturn(90);
         when(mThroughputPredictor.predictMaxRxThroughput(any())).thenReturn(80);
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(boolean shouldReduceNetworkScore) {
+                mCmi.setShouldReduceNetworkScore(shouldReduceNetworkScore);
+            }
+        }).when(mClientModeManager).setShouldReduceNetworkScore(anyBoolean());
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ClientModeManager manager, QueuedBroadcast broadcast) {
+                broadcast.send();
+            }
+        }).when(mBroadcastQueue).queueOrSendBroadcast(any(), any());
     }
 
     private void registerAsyncChannel(Consumer<AsyncChannel> consumer, Messenger messenger,
@@ -611,32 +665,46 @@
     }
 
     private void initializeCmi() throws Exception {
-        mCmi = new ClientModeImpl(mContext, mFrameworkFacade, mLooper.getLooper(),
-                mUserManager, mWifiInjector, mBackupManagerProxy, mCountryCode, mWifiNative,
-                mWrongPasswordNotifier, mSarManager, mWifiTrafficPoller, mLinkProbeManager,
-                mBatteryStatsManager, mSupplicantStateTracker, mMboOceController,
-                mWifiCarrierInfoManager, mEapFailureNotifier, mSimRequiredNotifier);
-        mCmi.start();
-        mWifiCoreThread = getCmiHandlerThread(mCmi);
+        mCmi = new ClientModeImpl(mContext, mWifiMetrics, mClock, mWifiScoreCard, mWifiStateTracker,
+                mWifiPermissionsUtil, mWifiConfigManager, mPasspointManager,
+                mWifiMonitor, mWifiDiagnostics, mWifiDataStall,
+                new ScoringParams(), new WifiThreadRunner(new Handler(mLooper.getLooper())),
+                mWifiNetworkSuggestionsManager, mWifiHealthMonitor, mThroughputPredictor,
+                mDeviceConfigFacade, mScanRequestProxy, mWifiInfo, mWifiConnectivityManager,
+                mWifiBlocklistMonitor, mConnectionFailureNotifier,
+                WifiInjector.REGULAR_NETWORK_CAPABILITIES_FILTER, mWifiNetworkFactory,
+                mUntrustedWifiNetworkFactory, mOemWifiNetworkFactory,
+                mWifiLastResortWatchdog, mWakeupController,
+                mWifiLockManager, mFrameworkFacade, mLooper.getLooper(),
+                mWifiNative, mWrongPasswordNotifier, mWifiTrafficPoller, mLinkProbeManager,
+                1, mBatteryStatsManager, mSupplicantStateTracker, mMboOceController,
+                mWifiCarrierInfoManager, mEapFailureNotifier, mSimRequiredNotifier,
+                mWifiScoreReport, mWifiP2pConnection, mWifiGlobals,
+                WIFI_IFACE_NAME, mClientModeManager, mCmiMonitor, mBroadcastQueue,
+                mWifiNetworkSelector, mTelephonyManager, mWifiInjector, mSettingsConfigStore,
+                false);
 
-        registerAsyncChannel((x) -> {
-            mCmiAsyncChannel = x;
-        }, mCmi.getMessenger());
+        mWifiCoreThread = getCmiHandlerThread(mCmi);
 
         mBinderToken = Binder.clearCallingIdentity();
 
-        /* Send the BOOT_COMPLETED message to setup some CMI state. */
-        mCmi.handleBootCompleted();
-        mLooper.dispatchAll();
-
-        verify(mWifiNetworkFactory, atLeastOnce()).register();
-        verify(mUntrustedWifiNetworkFactory, atLeastOnce()).register();
         verify(mWifiConfigManager, atLeastOnce()).addOnNetworkUpdateListener(
                 mConfigUpdateListenerCaptor.capture());
         assertNotNull(mConfigUpdateListenerCaptor.getValue());
 
-        mCmi.initialize();
+        verify(mWifiCarrierInfoManager, atLeastOnce()).addOnCarrierOffloadDisabledListener(
+                mOffloadDisabledListenerArgumentCaptor.capture());
+        assertNotNull(mOffloadDisabledListenerArgumentCaptor.getValue());
+
+        mCmi.enableVerboseLogging(true);
         mLooper.dispatchAll();
+
+        verify(mWifiLastResortWatchdog, atLeastOnce()).clearAllFailureCounts();
+        assertEquals("DisconnectedState", getCurrentState().getName());
+
+        verify(mContext, atLeastOnce()).registerReceiver(
+                mScreenStateBroadcastReceiverCaptor.capture(),
+                argThat(f -> f.hasAction(ACTION_SCREEN_ON) && f.hasAction(ACTION_SCREEN_OFF)));
     }
 
     @After
@@ -650,49 +718,10 @@
         mWifiCoreThread = null;
         mP2pThread = null;
         mSyncThread = null;
-        mCmiAsyncChannel = null;
         mCmi = null;
-        mSession.finishMocking();
-    }
-
-    @Test
-    public void createNew() throws Exception {
-        assertEquals("DefaultState", getCurrentState().getName());
-
-        mCmi.handleBootCompleted();
-        mLooper.dispatchAll();
-        assertEquals("DefaultState", getCurrentState().getName());
-    }
-
-    @Test
-    public void loadComponentsInStaMode() throws Exception {
-        startSupplicantAndDispatchMessages();
-        assertEquals("DisconnectedState", getCurrentState().getName());
-    }
-
-    @Test
-    public void checkInitialStateStickyWhenDisabledMode() throws Exception {
-        mLooper.dispatchAll();
-        assertEquals("DefaultState", getCurrentState().getName());
-        assertEquals(ClientModeImpl.DISABLED_MODE, mCmi.getOperationalModeForTest());
-
-        mCmi.setOperationalMode(ClientModeImpl.DISABLED_MODE, null);
-        mLooper.dispatchAll();
-        assertEquals(ClientModeImpl.DISABLED_MODE, mCmi.getOperationalModeForTest());
-        assertEquals("DefaultState", getCurrentState().getName());
-    }
-
-    @Test
-    public void shouldStartSupplicantWhenConnectModeRequested() throws Exception {
-        // The first time we start out in DefaultState, we sit around here.
-        mLooper.dispatchAll();
-        assertEquals("DefaultState", getCurrentState().getName());
-        assertEquals(ClientModeImpl.DISABLED_MODE, mCmi.getOperationalModeForTest());
-
-        // But if someone tells us to enter connect mode, we start up supplicant
-        mCmi.setOperationalMode(ClientModeImpl.CONNECT_MODE, WIFI_IFACE_NAME);
-        mLooper.dispatchAll();
-        assertEquals("DisconnectedState", getCurrentState().getName());
+        if (mSession != null) {
+            mSession.finishMocking();
+        }
     }
 
     /**
@@ -700,94 +729,47 @@
      */
     @Test
     public void checkIsWifiEnabledForModeChanges() throws Exception {
-        // Check initial state
+        // now disable client mode and verify the reported wifi state
+        mCmi.stop();
         mLooper.dispatchAll();
-        assertEquals("DefaultState", getCurrentState().getName());
-        assertEquals(WifiManager.WIFI_STATE_DISABLED, mCmi.syncGetWifiState());
-
-        // switch to connect mode and verify wifi is reported as enabled
-        startSupplicantAndDispatchMessages();
-
-        assertEquals("DisconnectedState", getCurrentState().getName());
-        assertEquals(ClientModeImpl.CONNECT_MODE, mCmi.getOperationalModeForTest());
-        assertEquals(WifiManager.WIFI_STATE_ENABLED, mCmi.syncGetWifiState());
-        assertEquals("enabled", mCmi.syncGetWifiStateByName());
-
-        // now disable wifi and verify the reported wifi state
-        mCmi.setWifiStateForApiCalls(WifiManager.WIFI_STATE_DISABLED);
-        mCmi.setOperationalMode(ClientModeImpl.DISABLED_MODE, null);
-        mLooper.dispatchAll();
-        assertEquals(ClientModeImpl.DISABLED_MODE, mCmi.getOperationalModeForTest());
-        assertEquals("DefaultState", getCurrentState().getName());
-        assertEquals(WifiManager.WIFI_STATE_DISABLED, mCmi.syncGetWifiState());
-        assertEquals("disabled", mCmi.syncGetWifiStateByName());
-        verify(mContext, never()).sendStickyBroadcastAsUser(
-                (Intent) argThat(new WifiEnablingStateIntentMatcher()), any());
+        verify(mContext, never())
+                .sendStickyBroadcastAsUser(argThat(new WifiEnablingStateIntentMatcher()), any());
     }
 
-    private class WifiEnablingStateIntentMatcher implements ArgumentMatcher<Intent> {
+    private static class WifiEnablingStateIntentMatcher implements ArgumentMatcher<Intent> {
         @Override
         public boolean matches(Intent intent) {
-            if (WifiManager.WIFI_STATE_CHANGED_ACTION != intent.getAction()) {
-                // not the correct type
-                return false;
-            }
-            return WifiManager.WIFI_STATE_ENABLING
-                    == intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
-                                          WifiManager.WIFI_STATE_DISABLED);
+            return WifiManager.WIFI_STATE_CHANGED_ACTION.equals(intent.getAction())
+                    && WifiManager.WIFI_STATE_ENABLING
+                            == intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
+                                    WifiManager.WIFI_STATE_DISABLED);
         }
     }
 
-    private void canForgetNetwork() throws Exception {
-        when(mWifiConfigManager.removeNetwork(eq(0), anyInt(), any())).thenReturn(true);
-        IActionListener connectActionListener = mock(IActionListener.class);
-        mCmi.forget(0, mock(Binder.class), connectActionListener, 0, Binder.getCallingUid());
-        mLooper.dispatchAll();
-        verify(connectActionListener).onSuccess();
-        verify(mWifiConfigManager).removeNetwork(anyInt(), anyInt(), any());
-    }
-
-    /**
-     * Verifies that configs can be removed when not in client mode.
-     */
-    @Test
-    public void canForgetNetworkConfigWhenWifiDisabled() throws Exception {
-        canForgetNetwork();
-    }
-
-    /**
-     * Verifies that configs can be forgotten when in client mode.
-     */
-    @Test
-    public void canForgetNetworkConfigInClientMode() throws Exception {
-        initializeAndAddNetworkAndVerifySuccess();
-        canForgetNetwork();
+    private class NetworkStateChangedIntentMatcher implements ArgumentMatcher<Intent> {
+        private final NetworkInfo.DetailedState mState;
+        NetworkStateChangedIntentMatcher(NetworkInfo.DetailedState state) {
+            mState = state;
+        }
+        @Override
+        public boolean matches(Intent intent) {
+            if (WifiManager.NETWORK_STATE_CHANGED_ACTION != intent.getAction()) {
+                // not the correct type
+                return false;
+            }
+            NetworkInfo networkInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
+            return networkInfo.getDetailedState() == mState;
+        }
     }
 
     private void canSaveNetworkConfig() throws Exception {
-        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
-
-        int networkId = TEST_NETWORK_ID;
-        when(mWifiConfigManager.addOrUpdateNetwork(any(WifiConfiguration.class), anyInt()))
-                .thenReturn(new NetworkUpdateResult(networkId));
-        when(mWifiConfigManager.enableNetwork(eq(networkId), eq(false), anyInt(), any()))
-                .thenReturn(true);
-
         IActionListener connectActionListener = mock(IActionListener.class);
-        mCmi.save(config, mock(Binder.class), connectActionListener, 0, Binder.getCallingUid());
+        mCmi.saveNetwork(
+                new NetworkUpdateResult(TEST_NETWORK_ID),
+                new ActionListenerWrapper(connectActionListener),
+                Binder.getCallingUid());
         mLooper.dispatchAll();
         verify(connectActionListener).onSuccess();
-
-        verify(mWifiConfigManager).addOrUpdateNetwork(any(WifiConfiguration.class), anyInt());
-        verify(mWifiConfigManager).enableNetwork(eq(networkId), eq(false), anyInt(), any());
-    }
-
-    /**
-     * Verifies that configs can be saved when not in client mode.
-     */
-    @Test
-    public void canSaveNetworkConfigWhenWifiDisabled() throws Exception {
-        canSaveNetworkConfig();
     }
 
     /**
@@ -795,88 +777,20 @@
      */
     @Test
     public void canSaveNetworkConfigInClientMode() throws Exception {
-        loadComponentsInStaMode();
         canSaveNetworkConfig();
     }
 
-    /**
-     * Verifies that null configs are rejected in SAVE_NETWORK message.
-     */
-    @Test
-    public void saveNetworkConfigFailsWithNullConfig() throws Exception {
-        IActionListener connectActionListener = mock(IActionListener.class);
-        mCmi.save(null, mock(Binder.class), connectActionListener, 0, Binder.getCallingUid());
-        mLooper.dispatchAll();
-        verify(connectActionListener).onFailure(WifiManager.ERROR);
-
-        verify(mWifiConfigManager, never())
-                .addOrUpdateNetwork(any(WifiConfiguration.class), anyInt());
-        verify(mWifiConfigManager, never())
-                .enableNetwork(anyInt(), anyBoolean(), anyInt(), any());
-    }
-
-    /**
-     * Verifies that configs save fails when the addition of network fails.
-     */
-    @Test
-    public void saveNetworkConfigFailsWithConfigAddFailure() throws Exception {
-        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
-
-        when(mWifiConfigManager.addOrUpdateNetwork(any(WifiConfiguration.class), anyInt()))
-                .thenReturn(new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID));
-
-        IActionListener connectActionListener = mock(IActionListener.class);
-        mCmi.save(config, mock(Binder.class), connectActionListener, 0, Binder.getCallingUid());
-        mLooper.dispatchAll();
-        verify(connectActionListener).onFailure(WifiManager.ERROR);
-
-        verify(mWifiConfigManager).addOrUpdateNetwork(any(WifiConfiguration.class), anyInt());
-        verify(mWifiConfigManager, never())
-                .enableNetwork(anyInt(), anyBoolean(), anyInt(), any());
-    }
-
-    /**
-     * Verifies that configs save fails when the enable of network fails.
-     */
-    @Test
-    public void saveNetworkConfigFailsWithConfigEnableFailure() throws Exception {
-        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
-
-        int networkId = 5;
-        when(mWifiConfigManager.addOrUpdateNetwork(any(WifiConfiguration.class), anyInt()))
-                .thenReturn(new NetworkUpdateResult(networkId));
-        when(mWifiConfigManager.enableNetwork(eq(networkId), eq(false), anyInt(), any()))
-                .thenReturn(false);
-
-        IActionListener connectActionListener = mock(IActionListener.class);
-        mCmi.save(config, mock(Binder.class), connectActionListener, 0, Binder.getCallingUid());
-        mLooper.dispatchAll();
-        verify(connectActionListener).onFailure(WifiManager.ERROR);
-
-        verify(mWifiConfigManager).addOrUpdateNetwork(any(WifiConfiguration.class), anyInt());
-        verify(mWifiConfigManager).enableNetwork(eq(networkId), eq(false), anyInt(), any());
-    }
-
-    /**
-     * Helper method to move through startup states.
-     */
-    private void startSupplicantAndDispatchMessages() throws Exception {
-        mCmi.setWifiStateForApiCalls(WifiManager.WIFI_STATE_ENABLED);
-        mCmi.setOperationalMode(ClientModeImpl.CONNECT_MODE, WIFI_IFACE_NAME);
-
-        mLooper.dispatchAll();
-
-        verify(mWifiLastResortWatchdog, atLeastOnce()).clearAllFailureCounts();
-
-        assertEquals("DisconnectedState", getCurrentState().getName());
+    private WifiConfiguration createTestNetwork(boolean isHidden) {
+        WifiConfiguration config = new WifiConfiguration();
+        config.networkId = FRAMEWORK_NETWORK_ID;
+        config.SSID = TEST_SSID;
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+        config.hiddenSSID = isHidden;
+        return config;
     }
 
     private void initializeMocksForAddedNetwork(boolean isHidden) throws Exception {
-        WifiConfiguration config = new WifiConfiguration();
-        config.networkId = FRAMEWORK_NETWORK_ID;
-        config.SSID = sSSID;
-        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
-        config.hiddenSSID = isHidden;
+        WifiConfiguration config = createTestNetwork(isHidden);
 
         when(mWifiConfigManager.getSavedNetworks(anyInt())).thenReturn(Arrays.asList(config));
         when(mWifiConfigManager.getConfiguredNetwork(0)).thenReturn(config);
@@ -888,16 +802,10 @@
     }
 
     private void initializeAndAddNetworkAndVerifySuccess(boolean isHidden) throws Exception {
-        loadComponentsInStaMode();
         initializeMocksForAddedNetwork(isHidden);
     }
 
     private void setupAndStartConnectSequence(WifiConfiguration config) throws Exception {
-        when(mWifiConfigManager.enableNetwork(
-                eq(config.networkId), eq(true), anyInt(), any()))
-                .thenReturn(true);
-        when(mWifiConfigManager.updateLastConnectUid(eq(config.networkId), anyInt()))
-                .thenReturn(true);
         when(mWifiConfigManager.getConfiguredNetwork(eq(config.networkId)))
                 .thenReturn(config);
         when(mWifiConfigManager.getConfiguredNetworkWithoutMasking(
@@ -906,26 +814,23 @@
         verify(mWifiNative).removeAllNetworks(WIFI_IFACE_NAME);
 
         IActionListener connectActionListener = mock(IActionListener.class);
-        mCmi.connect(null, config.networkId, mock(Binder.class), connectActionListener, 0,
+        mCmi.connectNetwork(
+                new NetworkUpdateResult(config.networkId),
+                new ActionListenerWrapper(connectActionListener),
                 Binder.getCallingUid());
         mLooper.dispatchAll();
-        verify(mWifiConfigManager).userEnabledNetwork(config.networkId);
         verify(connectActionListener).onSuccess();
     }
 
     private void validateSuccessfulConnectSequence(WifiConfiguration config) {
-        verify(mWifiConfigManager).enableNetwork(
-                eq(config.networkId), eq(true), anyInt(), any());
-        verify(mWifiConnectivityManager).setUserConnectChoice(eq(config.networkId));
         verify(mWifiConnectivityManager).prepareForForcedConnection(eq(config.networkId));
         verify(mWifiConfigManager).getConfiguredNetworkWithoutMasking(eq(config.networkId));
         verify(mWifiNative).connectToNetwork(eq(WIFI_IFACE_NAME), eq(config));
+        verify(mCmiMonitor).onConnectionStart(mClientModeManager);
+        assertEquals("L2ConnectingState", mCmi.getCurrentState().getName());
     }
 
     private void validateFailureConnectSequence(WifiConfiguration config) {
-        verify(mWifiConfigManager).enableNetwork(
-                eq(config.networkId), eq(true), anyInt(), any());
-        verify(mWifiConnectivityManager).setUserConnectChoice(eq(config.networkId));
         verify(mWifiConnectivityManager).prepareForForcedConnection(eq(config.networkId));
         verify(mWifiConfigManager, never())
                 .getConfiguredNetworkWithoutMasking(eq(config.networkId));
@@ -941,13 +846,18 @@
      */
     @Test
     public void triggerConnect() throws Exception {
-        loadComponentsInStaMode();
         WifiConfiguration config = mConnectedNetwork;
         config.networkId = FRAMEWORK_NETWORK_ID;
         config.setRandomizedMacAddress(TEST_LOCAL_MAC_ADDRESS);
-        config.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_PERSISTENT;
+        config.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_AUTO;
+        config.getNetworkSelectionStatus().setHasEverConnected(mTestNetworkParams.hasEverConnected);
+        assertEquals(null, config.getNetworkSelectionStatus().getCandidateSecurityParams());
         setupAndStartConnectSequence(config);
         validateSuccessfulConnectSequence(config);
+        assertEquals(config.getSecurityParamsList().stream()
+                        .filter(WifiConfigurationUtil::isSecurityParamsValid)
+                        .findFirst().orElse(null),
+                config.getNetworkSelectionStatus().getCandidateSecurityParams());
     }
 
     /**
@@ -959,15 +869,11 @@
      */
     @Test
     public void triggerConnectFromNonSettingsApp() throws Exception {
-        loadComponentsInStaMode();
         WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
         config.networkId = FRAMEWORK_NETWORK_ID;
         when(mWifiPermissionsUtil.checkNetworkSettingsPermission(Process.myUid()))
                 .thenReturn(false);
         setupAndStartConnectSequence(config);
-        verify(mWifiConfigManager).enableNetwork(
-                eq(config.networkId), eq(true), anyInt(), any());
-        verify(mWifiConnectivityManager, never()).setUserConnectChoice(eq(config.networkId));
         verify(mWifiConnectivityManager).prepareForForcedConnection(eq(config.networkId));
         verify(mWifiConfigManager).getConfiguredNetworkWithoutMasking(eq(config.networkId));
         verify(mWifiNative).connectToNetwork(eq(WIFI_IFACE_NAME), eq(config));
@@ -982,10 +888,10 @@
      */
     @Test
     public void triggerConnectWithNoNetworkRequest() throws Exception {
-        loadComponentsInStaMode();
         // Remove the network requests.
         when(mWifiNetworkFactory.hasConnectionRequests()).thenReturn(false);
         when(mUntrustedWifiNetworkFactory.hasConnectionRequests()).thenReturn(false);
+        when(mOemWifiNetworkFactory.hasConnectionRequests()).thenReturn(false);
 
         WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
         config.networkId = FRAMEWORK_NETWORK_ID;
@@ -997,30 +903,79 @@
      * Tests the entire successful network connection flow.
      */
     @Test
-    public void connect() throws Exception {
+    public void testConnect() throws Exception {
+        connect(null);
+    }
+
+    private void connect() throws Exception {
+        connect(null);
+    }
+
+    /**
+     * Simulate a connect
+     *
+     * @param wnmDataForTermsAndConditions Use null unless it is required to simulate a terms and
+     *                                     conditions acceptance notification from Passpoint
+     * @throws Exception
+     */
+    private void connect(WnmData wnmDataForTermsAndConditions) throws Exception {
+        assertNull(mCmi.getConnectingWifiConfiguration());
+        assertNull(mCmi.getConnectedWifiConfiguration());
+
         triggerConnect();
+
+        assertNotNull(mCmi.getConnectingWifiConfiguration());
+        assertNull(mCmi.getConnectedWifiConfiguration());
+
         WifiConfiguration config = mWifiConfigManager.getConfiguredNetwork(FRAMEWORK_NETWORK_ID);
         config.carrierId = CARRIER_ID_1;
         when(mWifiConfigManager.getScanDetailCacheForNetwork(FRAMEWORK_NETWORK_ID))
                 .thenReturn(mScanDetailCache);
 
-        when(mScanDetailCache.getScanDetail(sBSSID)).thenReturn(
-                getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq));
-        when(mScanDetailCache.getScanResult(sBSSID)).thenReturn(
-                getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq).getScanResult());
+        when(mScanDetailCache.getScanDetail(TEST_BSSID_STR)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, TEST_BSSID_STR, sFreq));
+        when(mScanDetailCache.getScanResult(TEST_BSSID_STR)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, TEST_BSSID_STR, sFreq).getScanResult());
+        ScanResult scanResult = new ScanResult(WifiSsid.createFromAsciiEncoded(sFilsSsid),
+                sFilsSsid, TEST_BSSID_STR, 1245, 0, "", -78, 2412, 1025, 22, 33, 20, 0, 0, true);
+        ScanResult.InformationElement ie = createIE(ScanResult.InformationElement.EID_SSID,
+                sFilsSsid.getBytes(StandardCharsets.UTF_8));
+        scanResult.informationElements = new ScanResult.InformationElement[]{ie};
+        when(mScanRequestProxy.getScanResult(eq(TEST_BSSID_STR))).thenReturn(scanResult);
 
         mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
-                new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.ASSOCIATED));
+                new StateChangeResult(0, TEST_WIFI_SSID, TEST_BSSID_STR,
+                        SupplicantState.ASSOCIATED));
         mLooper.dispatchAll();
 
-        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, sBSSID);
+        WifiSsid wifiSsid = WifiSsid.createFromByteArray(
+                NativeUtil.byteArrayFromArrayList(NativeUtil.decodeSsid(mConnectedNetwork.SSID)));
+        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT,
+                new NetworkConnectionEventInfo(0, wifiSsid, TEST_BSSID_STR, false));
         mLooper.dispatchAll();
 
+        verify(mWifiMetrics).noteFirstL2ConnectionAfterBoot(true);
+
+        // L2 connected, but not L3 connected yet. So, still "Connecting"...
+        assertNotNull(mCmi.getConnectingWifiConfiguration());
+        assertNull(mCmi.getConnectedWifiConfiguration());
+
         mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
-                new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.COMPLETED));
+                new StateChangeResult(0, TEST_WIFI_SSID, TEST_BSSID_STR,
+                        SupplicantState.COMPLETED));
         mLooper.dispatchAll();
 
-        assertEquals("ObtainingIpState", getCurrentState().getName());
+        assertEquals("L3ProvisioningState", getCurrentState().getName());
+        verify(mContext).sendStickyBroadcastAsUser(
+                argThat(new NetworkStateChangedIntentMatcher(CONNECTING)), any());
+        verify(mContext).sendStickyBroadcastAsUser(
+                argThat(new NetworkStateChangedIntentMatcher(OBTAINING_IPADDR)), any());
+
+        if (wnmDataForTermsAndConditions != null) {
+            mCmi.sendMessage(WifiMonitor.HS20_TERMS_AND_CONDITIONS_ACCEPTANCE_REQUIRED_EVENT,
+                    0, 0, wnmDataForTermsAndConditions);
+            mLooper.dispatchAll();
+        }
 
         DhcpResultsParcelable dhcpResults = new DhcpResultsParcelable();
         dhcpResults.baseConfiguration = new StaticIpConfiguration();
@@ -1033,63 +988,129 @@
         injectDhcpSuccess(dhcpResults);
         mLooper.dispatchAll();
 
+        assertNull(mCmi.getConnectingWifiConfiguration());
+        assertNotNull(mCmi.getConnectedWifiConfiguration());
+
         // Verify WifiMetrics logging for metered metrics based on DHCP results
         verify(mWifiMetrics).addMeteredStat(any(), anyBoolean());
-        WifiInfo wifiInfo = mCmi.getWifiInfo();
+        WifiInfo wifiInfo = mWifiInfo;
         assertNotNull(wifiInfo);
-        assertEquals(sBSSID, wifiInfo.getBSSID());
+        assertEquals(TEST_BSSID_STR, wifiInfo.getBSSID());
         assertEquals(sFreq, wifiInfo.getFrequency());
-        assertTrue(sWifiSsid.equals(wifiInfo.getWifiSsid()));
-        assertNull(wifiInfo.getPasspointProviderFriendlyName());
+        assertEquals(TEST_WIFI_SSID, wifiInfo.getWifiSsid());
+        assertNotEquals(WifiInfo.DEFAULT_MAC_ADDRESS, wifiInfo.getMacAddress());
+        assertEquals(mConnectedNetwork.getDefaultSecurityParams().getSecurityType(),
+                mWifiInfo.getCurrentSecurityType());
+        if (wifiInfo.isPasspointAp()) {
+            assertEquals(wifiInfo.getPasspointProviderFriendlyName(),
+                    WifiConfigurationTestUtil.TEST_PROVIDER_FRIENDLY_NAME);
+        } else {
+            assertNull(wifiInfo.getPasspointProviderFriendlyName());
+        }
+        assertEquals(Arrays.asList(scanResult.informationElements),
+                    wifiInfo.getInformationElements());
         expectRegisterNetworkAgent((na) -> {
+            if (!mConnectedNetwork.carrierMerged) {
+                assertNull(na.subscriberId);
+            }
         }, (nc) -> {
                 if (SdkLevel.isAtLeastS()) {
                     WifiInfo wifiInfoFromTi = (WifiInfo) nc.getTransportInfo();
-                    assertEquals(sBSSID, wifiInfoFromTi.getBSSID());
+                    assertEquals(TEST_BSSID_STR, wifiInfoFromTi.getBSSID());
                     assertEquals(sFreq, wifiInfoFromTi.getFrequency());
-                    assertTrue(sWifiSsid.equals(wifiInfoFromTi.getWifiSsid()));
-                    assertNull(wifiInfoFromTi.getPasspointProviderFriendlyName());
+                    assertEquals(TEST_WIFI_SSID, wifiInfoFromTi.getWifiSsid());
+                    if (wifiInfo.isPasspointAp()) {
+                        assertEquals(wifiInfoFromTi.getPasspointProviderFriendlyName(),
+                                WifiConfigurationTestUtil.TEST_PROVIDER_FRIENDLY_NAME);
+                    } else {
+                        assertNull(wifiInfoFromTi.getPasspointProviderFriendlyName());
+                    }
                 }
             });
         // Ensure the connection stats for the network is updated.
-        verify(mWifiConfigManager).updateNetworkAfterConnect(FRAMEWORK_NETWORK_ID);
+        verify(mWifiConfigManager).updateNetworkAfterConnect(eq(FRAMEWORK_NETWORK_ID),
+                anyBoolean(), anyInt());
         verify(mWifiConfigManager).updateRandomizedMacExpireTime(any(), anyLong());
+        verify(mContext).sendStickyBroadcastAsUser(
+                argThat(new NetworkStateChangedIntentMatcher(CONNECTED)), any());
 
         // Anonymous Identity is not set.
         assertEquals("", mConnectedNetwork.enterpriseConfig.getAnonymousIdentity());
-        verify(mWifiStateTracker).updateState(eq(WifiStateTracker.CONNECTED));
-        assertEquals("ConnectedState", getCurrentState().getName());
+        verify(mWifiStateTracker).updateState(WIFI_IFACE_NAME, WifiStateTracker.CONNECTED);
+        assertEquals("L3ConnectedState", getCurrentState().getName());
         verify(mWifiMetrics).incrementNumOfCarrierWifiConnectionSuccess();
-        verify(mWifiLockManager).updateWifiClientConnected(true);
+        verify(mWifiLockManager).updateWifiClientConnected(mClientModeManager, true);
         verify(mWifiNative).getConnectionCapabilities(any());
         verify(mThroughputPredictor).predictMaxTxThroughput(any());
-        verify(mWifiMetrics).setConnectionMaxSupportedLinkSpeedMbps(90, 80);
-        verify(mWifiDataStall).setConnectionCapabilities(any());
+        verify(mWifiMetrics).setConnectionMaxSupportedLinkSpeedMbps(WIFI_IFACE_NAME, 90, 80);
         assertEquals(90, wifiInfo.getMaxSupportedTxLinkSpeedMbps());
+        verify(mWifiMetrics).noteFirstL3ConnectionAfterBoot(true);
     }
 
     private void setupEapSimConnection() throws Exception {
         mConnectedNetwork = spy(WifiConfigurationTestUtil.createEapNetwork(
                 WifiEnterpriseConfig.Eap.SIM, WifiEnterpriseConfig.Phase2.NONE));
         mConnectedNetwork.carrierId = CARRIER_ID_1;
-        doReturn(DATA_SUBID).when(mWifiCarrierInfoManager)
-                .getBestMatchSubscriptionId(any(WifiConfiguration.class));
-        when(mDataTelephonyManager.getSimOperator()).thenReturn("123456");
-        when(mDataTelephonyManager.getSimState()).thenReturn(TelephonyManager.SIM_STATE_READY);
+        when(mWifiCarrierInfoManager.getBestMatchSubscriptionId(any(WifiConfiguration.class)))
+                .thenReturn(DATA_SUBID);
+        when(mWifiCarrierInfoManager.isSimReady(DATA_SUBID)).thenReturn(true);
         mConnectedNetwork.enterpriseConfig.setAnonymousIdentity("");
 
         triggerConnect();
 
         when(mWifiConfigManager.getScanDetailCacheForNetwork(FRAMEWORK_NETWORK_ID))
                 .thenReturn(mScanDetailCache);
-        when(mScanDetailCache.getScanDetail(sBSSID)).thenReturn(
-                getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq));
-        when(mScanDetailCache.getScanResult(sBSSID)).thenReturn(
-                getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq).getScanResult());
+        when(mScanDetailCache.getScanDetail(TEST_BSSID_STR)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, TEST_BSSID_STR, sFreq));
+        when(mScanDetailCache.getScanResult(TEST_BSSID_STR)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, TEST_BSSID_STR, sFreq).getScanResult());
 
-        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, sBSSID);
+        WifiSsid wifiSsid = WifiSsid.createFromByteArray(
+                NativeUtil.byteArrayFromArrayList(NativeUtil.decodeSsid(mConnectedNetwork.SSID)));
+        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT,
+                new NetworkConnectionEventInfo(0, wifiSsid, TEST_BSSID_STR, false));
         mLooper.dispatchAll();
-        assertEquals("ObtainingIpState", getCurrentState().getName());
+        assertEquals("L3ProvisioningState", getCurrentState().getName());
+    }
+
+    /**
+     * Test when a roam occurs simultaneously with another connection attempt.
+     * The roam's NETWORK_CONNECTION_EVENT should be ignored, only the new network's
+     * NETWORK_CONNECTION_EVENT should be acted upon.
+     */
+    @Test
+    public void roamRaceWithConnect() throws Exception {
+        connect();
+
+        initializeAndAddNetworkAndVerifySuccess();
+
+        // connect to new network
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+        config.networkId = OTHER_NETWORK_ID;
+        setupAndStartConnectSequence(config);
+
+        // in L2ConnectingState
+        assertEquals("L2ConnectingState", getCurrentState().getName());
+
+        // send NETWORK_CONNECTION_EVENT for previous network ID
+        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT,
+                new NetworkConnectionEventInfo(
+                        FRAMEWORK_NETWORK_ID, TEST_WIFI_SSID, TEST_BSSID_STR, false));
+        mLooper.dispatchAll();
+
+        // should ignore it, stay in L2ConnectingState
+        assertEquals("L2ConnectingState", getCurrentState().getName());
+
+        // send expected new network SSID
+        WifiSsid wifiSsid = WifiSsid.createFromByteArray(
+                NativeUtil.byteArrayFromArrayList(NativeUtil.decodeSsid(config.SSID)));
+        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT,
+                new NetworkConnectionEventInfo(
+                        OTHER_NETWORK_ID, wifiSsid, TEST_BSSID_STR1, false));
+        mLooper.dispatchAll();
+
+        // then move to next state
+        assertEquals("L3ProvisioningState", getCurrentState().getName());
     }
 
     /**
@@ -1099,14 +1120,13 @@
     @Test
     public void testResetSimWhenNonConnectedSimRemoved() throws Exception {
         setupEapSimConnection();
-        doReturn(true).when(mWifiCarrierInfoManager).isSimPresent(eq(DATA_SUBID));
+        doReturn(true).when(mWifiCarrierInfoManager).isSimReady(eq(DATA_SUBID));
         mCmi.sendMessage(ClientModeImpl.CMD_RESET_SIM_NETWORKS,
                 ClientModeImpl.RESET_SIM_REASON_SIM_REMOVED);
         mLooper.dispatchAll();
 
         verify(mSimRequiredNotifier, never()).showSimRequiredNotification(any(), any());
-
-        assertEquals("ObtainingIpState", getCurrentState().getName());
+        assertEquals("L3ProvisioningState", getCurrentState().getName());
     }
 
     /**
@@ -1116,13 +1136,13 @@
     @Test
     public void testResetSimWhenConnectedSimRemoved() throws Exception {
         setupEapSimConnection();
-        doReturn(false).when(mWifiCarrierInfoManager).isSimPresent(eq(DATA_SUBID));
+        doReturn(false).when(mWifiCarrierInfoManager).isSimReady(eq(DATA_SUBID));
         mCmi.sendMessage(ClientModeImpl.CMD_RESET_SIM_NETWORKS,
                 ClientModeImpl.RESET_SIM_REASON_SIM_REMOVED);
         mLooper.dispatchAll();
 
         verify(mSimRequiredNotifier).showSimRequiredNotification(any(), any());
-        assertEquals("DisconnectingState", getCurrentState().getName());
+        verify(mWifiNative, times(2)).removeAllNetworks(WIFI_IFACE_NAME);
     }
 
     /**
@@ -1132,14 +1152,14 @@
     @Test
     public void testResetSimWhenConnectedSimRemovedAfterNetworkRemoval() throws Exception {
         setupEapSimConnection();
-        doReturn(false).when(mWifiCarrierInfoManager).isSimPresent(eq(DATA_SUBID));
+        doReturn(false).when(mWifiCarrierInfoManager).isSimReady(eq(DATA_SUBID));
         when(mWifiConfigManager.getConfiguredNetwork(anyInt())).thenReturn(null);
         mCmi.sendMessage(ClientModeImpl.CMD_RESET_SIM_NETWORKS,
                 ClientModeImpl.RESET_SIM_REASON_SIM_REMOVED);
         mLooper.dispatchAll();
 
         verify(mSimRequiredNotifier, never()).showSimRequiredNotification(any(), any());
-        assertEquals("ObtainingIpState", getCurrentState().getName());
+        assertEquals("L3ProvisioningState", getCurrentState().getName());
     }
 
     /**
@@ -1153,7 +1173,9 @@
                 ClientModeImpl.RESET_SIM_REASON_DEFAULT_DATA_SIM_CHANGED);
         mLooper.dispatchAll();
 
-        assertEquals("DisconnectingState", getCurrentState().getName());
+        verify(mWifiNative, times(2)).removeAllNetworks(WIFI_IFACE_NAME);
+        verify(mWifiMetrics).logStaEvent(anyString(), eq(StaEvent.TYPE_FRAMEWORK_DISCONNECT),
+                eq(StaEvent.DISCONNECT_RESET_SIM_NETWORKS));
     }
 
     /**
@@ -1164,16 +1186,15 @@
     public void testSetAnonymousIdentityWhenConnectionIsEstablishedNoPseudonym() throws Exception {
         mConnectedNetwork = spy(WifiConfigurationTestUtil.createEapNetwork(
                 WifiEnterpriseConfig.Eap.SIM, WifiEnterpriseConfig.Phase2.NONE));
-        when(mDataTelephonyManager.getSimOperator()).thenReturn("123456");
-        when(mDataTelephonyManager.getSimState()).thenReturn(TelephonyManager.SIM_STATE_READY);
         mConnectedNetwork.enterpriseConfig.setAnonymousIdentity("");
-
         String expectedAnonymousIdentity = "anonymous@wlan.mnc456.mcc123.3gppnetwork.org";
-        MockitoSession mockSession = ExtendedMockito.mockitoSession()
-                .mockStatic(SubscriptionManager.class)
-                .startMocking();
-        when(SubscriptionManager.getDefaultDataSubscriptionId()).thenReturn(DATA_SUBID);
-        doReturn(true).when(mWifiCarrierInfoManager).isImsiEncryptionInfoAvailable(anyInt());
+
+        when(mWifiCarrierInfoManager.getBestMatchSubscriptionId(any(WifiConfiguration.class)))
+                .thenReturn(DATA_SUBID);
+        when(mWifiCarrierInfoManager.isSimReady(DATA_SUBID)).thenReturn(true);
+        when(mWifiCarrierInfoManager.isImsiEncryptionInfoAvailable(anyInt())).thenReturn(true);
+        when(mWifiCarrierInfoManager.getAnonymousIdentityWith3GppRealm(any()))
+                .thenReturn(expectedAnonymousIdentity);
 
         // Initial value should be "not set"
         assertEquals("", mConnectedNetwork.enterpriseConfig.getAnonymousIdentity());
@@ -1186,14 +1207,17 @@
 
         when(mWifiConfigManager.getScanDetailCacheForNetwork(FRAMEWORK_NETWORK_ID))
                 .thenReturn(mScanDetailCache);
-        when(mScanDetailCache.getScanDetail(sBSSID)).thenReturn(
-                getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq));
-        when(mScanDetailCache.getScanResult(sBSSID)).thenReturn(
-                getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq).getScanResult());
+        when(mScanDetailCache.getScanDetail(TEST_BSSID_STR)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, TEST_BSSID_STR, sFreq));
+        when(mScanDetailCache.getScanResult(TEST_BSSID_STR)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, TEST_BSSID_STR, sFreq).getScanResult());
         when(mWifiNative.getEapAnonymousIdentity(anyString()))
                 .thenReturn(expectedAnonymousIdentity);
 
-        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, sBSSID);
+        WifiSsid wifiSsid = WifiSsid.createFromByteArray(
+                NativeUtil.byteArrayFromArrayList(NativeUtil.decodeSsid(mConnectedNetwork.SSID)));
+        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT,
+                new NetworkConnectionEventInfo(0, wifiSsid, TEST_BSSID_STR, false));
         mLooper.dispatchAll();
 
         verify(mWifiNative).getEapAnonymousIdentity(any());
@@ -1206,7 +1230,6 @@
         // trigger "add or update network" operation. The test needs to be updated to account for
         // this change.
         verify(mWifiConfigManager).addOrUpdateNetwork(any(), anyInt());
-        mockSession.finishMocking();
     }
 
     /**
@@ -1219,17 +1242,16 @@
             throws Exception {
         mConnectedNetwork = spy(WifiConfigurationTestUtil.createEapNetwork(
                 WifiEnterpriseConfig.Eap.SIM, WifiEnterpriseConfig.Phase2.NONE));
-        when(mDataTelephonyManager.getSimOperator()).thenReturn("123456");
-        when(mDataTelephonyManager.getSimState()).thenReturn(TelephonyManager.SIM_STATE_READY);
         mConnectedNetwork.enterpriseConfig.setAnonymousIdentity("");
-
         String expectedAnonymousIdentity = "anonymous@wlan.mnc456.mcc123.3gppnetwork.org";
         String pseudonym = "83bcca9384fca@wlan.mnc456.mcc123.3gppnetwork.org";
-        MockitoSession mockSession = ExtendedMockito.mockitoSession()
-                .mockStatic(SubscriptionManager.class)
-                .startMocking();
-        when(SubscriptionManager.getDefaultDataSubscriptionId()).thenReturn(DATA_SUBID);
-        doReturn(true).when(mWifiCarrierInfoManager).isImsiEncryptionInfoAvailable(anyInt());
+
+        when(mWifiCarrierInfoManager.getBestMatchSubscriptionId(any(WifiConfiguration.class)))
+                .thenReturn(DATA_SUBID);
+        when(mWifiCarrierInfoManager.isSimReady(DATA_SUBID)).thenReturn(true);
+        when(mWifiCarrierInfoManager.isImsiEncryptionInfoAvailable(anyInt())).thenReturn(true);
+        when(mWifiCarrierInfoManager.getAnonymousIdentityWith3GppRealm(any()))
+                .thenReturn(expectedAnonymousIdentity);
 
         triggerConnect();
 
@@ -1239,14 +1261,17 @@
 
         when(mWifiConfigManager.getScanDetailCacheForNetwork(FRAMEWORK_NETWORK_ID))
                 .thenReturn(mScanDetailCache);
-        when(mScanDetailCache.getScanDetail(sBSSID)).thenReturn(
-                getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq));
-        when(mScanDetailCache.getScanResult(sBSSID)).thenReturn(
-                getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq).getScanResult());
+        when(mScanDetailCache.getScanDetail(TEST_BSSID_STR)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, TEST_BSSID_STR, sFreq));
+        when(mScanDetailCache.getScanResult(TEST_BSSID_STR)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, TEST_BSSID_STR, sFreq).getScanResult());
         when(mWifiNative.getEapAnonymousIdentity(anyString()))
                 .thenReturn(pseudonym);
 
-        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, sBSSID);
+        WifiSsid wifiSsid = WifiSsid.createFromByteArray(
+                NativeUtil.byteArrayFromArrayList(NativeUtil.decodeSsid(mConnectedNetwork.SSID)));
+        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT,
+                new NetworkConnectionEventInfo(0, wifiSsid, TEST_BSSID_STR, false));
         mLooper.dispatchAll();
 
         verify(mWifiNative).getEapAnonymousIdentity(any());
@@ -1259,7 +1284,6 @@
         // trigger "add or update network" operation. The test needs to be updated to account for
         // this change.
         verify(mWifiConfigManager).addOrUpdateNetwork(any(), anyInt());
-        mockSession.finishMocking();
     }
 
     /**
@@ -1272,35 +1296,38 @@
             throws Exception {
         mConnectedNetwork = spy(WifiConfigurationTestUtil.createEapNetwork(
                 WifiEnterpriseConfig.Eap.SIM, WifiEnterpriseConfig.Phase2.NONE));
-        when(mDataTelephonyManager.getSimOperator()).thenReturn("123456");
-        when(mDataTelephonyManager.getSimState()).thenReturn(TelephonyManager.SIM_STATE_READY);
         mConnectedNetwork.enterpriseConfig.setAnonymousIdentity("");
-
-        String realm = "wlan.mnc456.mcc123.3gppnetwork.org";
-        String expectedAnonymousIdentity = "anonymous";
         String pseudonym = "83bcca9384fca";
-        MockitoSession mockSession = ExtendedMockito.mockitoSession()
-                .mockStatic(SubscriptionManager.class)
-                .startMocking();
-        when(SubscriptionManager.getDefaultDataSubscriptionId()).thenReturn(DATA_SUBID);
-        doReturn(true).when(mWifiCarrierInfoManager).isImsiEncryptionInfoAvailable(anyInt());
+        String realm = "wlan.mnc456.mcc123.3gppnetwork.org";
+        String expectedAnonymousIdentity = "anonymous@wlan.mnc456.mcc123.3gppnetwork.org";
 
+        when(mWifiCarrierInfoManager.getBestMatchSubscriptionId(any(WifiConfiguration.class)))
+                .thenReturn(DATA_SUBID);
+        when(mWifiCarrierInfoManager.isSimReady(DATA_SUBID)).thenReturn(true);
+        when(mWifiCarrierInfoManager.isImsiEncryptionInfoAvailable(anyInt())).thenReturn(true);
+        when(mWifiCarrierInfoManager.getAnonymousIdentityWith3GppRealm(any()))
+                .thenReturn(expectedAnonymousIdentity);
+        doAnswer(invocation -> { return invocation.getArgument(1) + "@" + realm; })
+                .when(mWifiCarrierInfoManager).decoratePseudonymWith3GppRealm(any(), anyString());
         triggerConnect();
 
         // CMD_START_CONNECT should have set anonymousIdentity to anonymous@<realm>
-        assertEquals(expectedAnonymousIdentity + "@" + realm,
+        assertEquals(expectedAnonymousIdentity,
                 mConnectedNetwork.enterpriseConfig.getAnonymousIdentity());
 
         when(mWifiConfigManager.getScanDetailCacheForNetwork(FRAMEWORK_NETWORK_ID))
                 .thenReturn(mScanDetailCache);
-        when(mScanDetailCache.getScanDetail(sBSSID)).thenReturn(
-                getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq));
-        when(mScanDetailCache.getScanResult(sBSSID)).thenReturn(
-                getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq).getScanResult());
+        when(mScanDetailCache.getScanDetail(TEST_BSSID_STR)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, TEST_BSSID_STR, sFreq));
+        when(mScanDetailCache.getScanResult(TEST_BSSID_STR)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, TEST_BSSID_STR, sFreq).getScanResult());
         when(mWifiNative.getEapAnonymousIdentity(anyString()))
                 .thenReturn(pseudonym);
 
-        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, sBSSID);
+        WifiSsid wifiSsid = WifiSsid.createFromByteArray(
+                NativeUtil.byteArrayFromArrayList(NativeUtil.decodeSsid(mConnectedNetwork.SSID)));
+        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT,
+                new NetworkConnectionEventInfo(0, wifiSsid, TEST_BSSID_STR, false));
         mLooper.dispatchAll();
 
         verify(mWifiNative).getEapAnonymousIdentity(any());
@@ -1313,28 +1340,138 @@
         // trigger "add or update network" operation. The test needs to be updated to account for
         // this change.
         verify(mWifiConfigManager).addOrUpdateNetwork(any(), anyInt());
-        mockSession.finishMocking();
+    }
+
+    /**
+     * Tests anonymous identity will be set to suggestion network.
+     */
+    @Test
+    public void testSetAnonymousIdentityWhenConnectionIsEstablishedWithPseudonymForSuggestion()
+            throws Exception {
+        mConnectedNetwork = spy(WifiConfigurationTestUtil.createEapNetwork(
+                WifiEnterpriseConfig.Eap.SIM, WifiEnterpriseConfig.Phase2.NONE));
+        mConnectedNetwork.enterpriseConfig.setAnonymousIdentity("");
+        mConnectedNetwork.fromWifiNetworkSuggestion = true;
+        String expectedAnonymousIdentity = "anonymous@wlan.mnc456.mcc123.3gppnetwork.org";
+        String pseudonym = "83bcca9384fca@wlan.mnc456.mcc123.3gppnetwork.org";
+
+        when(mWifiCarrierInfoManager.getBestMatchSubscriptionId(any(WifiConfiguration.class)))
+                .thenReturn(DATA_SUBID);
+        when(mWifiCarrierInfoManager.isSimReady(DATA_SUBID)).thenReturn(true);
+        when(mWifiCarrierInfoManager.isImsiEncryptionInfoAvailable(anyInt())).thenReturn(true);
+        when(mWifiCarrierInfoManager.getAnonymousIdentityWith3GppRealm(any()))
+                .thenReturn(expectedAnonymousIdentity);
+
+        triggerConnect();
+
+        // CMD_START_CONNECT should have set anonymousIdentity to anonymous@<realm>
+        assertEquals(expectedAnonymousIdentity,
+                mConnectedNetwork.enterpriseConfig.getAnonymousIdentity());
+
+        when(mWifiConfigManager.getScanDetailCacheForNetwork(FRAMEWORK_NETWORK_ID))
+                .thenReturn(mScanDetailCache);
+        when(mScanDetailCache.getScanDetail(TEST_BSSID_STR)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, TEST_BSSID_STR, sFreq));
+        when(mScanDetailCache.getScanResult(TEST_BSSID_STR)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, TEST_BSSID_STR, sFreq).getScanResult());
+        when(mWifiNative.getEapAnonymousIdentity(anyString()))
+                .thenReturn(pseudonym);
+
+        WifiSsid wifiSsid = WifiSsid.createFromByteArray(
+                NativeUtil.byteArrayFromArrayList(NativeUtil.decodeSsid(mConnectedNetwork.SSID)));
+        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT,
+                new NetworkConnectionEventInfo(0, wifiSsid, TEST_BSSID_STR, false));
+        mLooper.dispatchAll();
+
+        verify(mWifiNative).getEapAnonymousIdentity(any());
+        assertEquals(pseudonym,
+                mConnectedNetwork.enterpriseConfig.getAnonymousIdentity());
+        // Verify that WifiConfigManager#addOrUpdateNetwork() was called if there we received a
+        // real pseudonym to be stored. i.e. Encrypted IMSI will be used once, followed by
+        // pseudonym usage in all subsequent connections.
+        // Note: This test will fail if future logic will have additional conditions that would
+        // trigger "add or update network" operation. The test needs to be updated to account for
+        // this change.
+        verify(mWifiConfigManager).addOrUpdateNetwork(any(), anyInt());
+        verify(mWifiNetworkSuggestionsManager).setAnonymousIdentity(any());
+    }
+
+    /**
+     * Tests anonymous identity will be set to passpoint network.
+     */
+    @Test
+    public void testSetAnonymousIdentityWhenConnectionIsEstablishedWithPseudonymForPasspoint()
+            throws Exception {
+        mConnectedNetwork = spy(WifiConfigurationTestUtil.createEapNetwork(
+                WifiEnterpriseConfig.Eap.SIM, WifiEnterpriseConfig.Phase2.NONE));
+        mConnectedNetwork.enterpriseConfig.setAnonymousIdentity("");
+        mConnectedNetwork.FQDN = WifiConfigurationTestUtil.TEST_FQDN;
+        mConnectedNetwork.providerFriendlyName =
+                WifiConfigurationTestUtil.TEST_PROVIDER_FRIENDLY_NAME;
+        mConnectedNetwork.setPasspointUniqueId(WifiConfigurationTestUtil.TEST_FQDN + "_"
+                + WifiConfigurationTestUtil.TEST_FQDN.hashCode());
+        String expectedAnonymousIdentity = "anonymous@wlan.mnc456.mcc123.3gppnetwork.org";
+        String pseudonym = "83bcca9384fca@wlan.mnc456.mcc123.3gppnetwork.org";
+
+        when(mWifiCarrierInfoManager.getBestMatchSubscriptionId(any(WifiConfiguration.class)))
+                .thenReturn(DATA_SUBID);
+        when(mWifiCarrierInfoManager.isSimReady(DATA_SUBID)).thenReturn(true);
+        when(mWifiCarrierInfoManager.isImsiEncryptionInfoAvailable(anyInt())).thenReturn(true);
+        when(mWifiCarrierInfoManager.getAnonymousIdentityWith3GppRealm(any()))
+                .thenReturn(expectedAnonymousIdentity);
+
+        triggerConnect();
+
+        // CMD_START_CONNECT should have set anonymousIdentity to anonymous@<realm>
+        assertEquals(expectedAnonymousIdentity,
+                mConnectedNetwork.enterpriseConfig.getAnonymousIdentity());
+
+        when(mWifiConfigManager.getScanDetailCacheForNetwork(FRAMEWORK_NETWORK_ID))
+                .thenReturn(mScanDetailCache);
+        when(mScanDetailCache.getScanDetail(TEST_BSSID_STR)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, TEST_BSSID_STR, sFreq));
+        when(mScanDetailCache.getScanResult(TEST_BSSID_STR)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, TEST_BSSID_STR, sFreq).getScanResult());
+        when(mWifiNative.getEapAnonymousIdentity(anyString()))
+                .thenReturn(pseudonym);
+
+        WifiSsid wifiSsid = WifiSsid.createFromByteArray(
+                NativeUtil.byteArrayFromArrayList(NativeUtil.decodeSsid(mConnectedNetwork.SSID)));
+        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT,
+                new NetworkConnectionEventInfo(0, wifiSsid, TEST_BSSID_STR, false));
+        mLooper.dispatchAll();
+
+        verify(mWifiNative).getEapAnonymousIdentity(any());
+        assertEquals(pseudonym,
+                mConnectedNetwork.enterpriseConfig.getAnonymousIdentity());
+        // Verify that WifiConfigManager#addOrUpdateNetwork() was called if there we received a
+        // real pseudonym to be stored. i.e. Encrypted IMSI will be used once, followed by
+        // pseudonym usage in all subsequent connections.
+        // Note: This test will fail if future logic will have additional conditions that would
+        // trigger "add or update network" operation. The test needs to be updated to account for
+        // this change.
+        verify(mWifiConfigManager).addOrUpdateNetwork(any(), anyInt());
+        verify(mPasspointManager).setAnonymousIdentity(any());
     }
     /**
      * Tests the Passpoint information is set in WifiInfo for Passpoint AP connection.
      */
     @Test
     public void connectPasspointAp() throws Exception {
-        loadComponentsInStaMode();
         WifiConfiguration config = spy(WifiConfigurationTestUtil.createPasspointNetwork());
-        config.SSID = sWifiSsid.toString();
-        config.BSSID = sBSSID;
+        config.SSID = TEST_WIFI_SSID.toString();
+        config.BSSID = TEST_BSSID_STR;
         config.networkId = FRAMEWORK_NETWORK_ID;
-        config.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_PERSISTENT;
+        config.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_AUTO;
         setupAndStartConnectSequence(config);
         validateSuccessfulConnectSequence(config);
 
         mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
-                new StateChangeResult(FRAMEWORK_NETWORK_ID, sWifiSsid, sBSSID,
+                new StateChangeResult(FRAMEWORK_NETWORK_ID, TEST_WIFI_SSID, TEST_BSSID_STR,
                         SupplicantState.ASSOCIATING));
         mLooper.dispatchAll();
 
-        WifiInfo wifiInfo = mCmi.getWifiInfo();
+        WifiInfo wifiInfo = mWifiInfo;
         assertNotNull(wifiInfo);
         assertEquals(WifiConfigurationTestUtil.TEST_FQDN, wifiInfo.getPasspointFqdn());
         assertEquals(WifiConfigurationTestUtil.TEST_PROVIDER_FRIENDLY_NAME,
@@ -1348,26 +1485,25 @@
      */
     @Test
     public void testResetWifiInfoPasspointFields() throws Exception {
-        loadComponentsInStaMode();
         WifiConfiguration config = spy(WifiConfigurationTestUtil.createPasspointNetwork());
-        config.SSID = sWifiSsid.toString();
-        config.BSSID = sBSSID;
+        config.SSID = TEST_WIFI_SSID.toString();
+        config.BSSID = TEST_BSSID_STR;
         config.networkId = PASSPOINT_NETWORK_ID;
-        config.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_PERSISTENT;
+        config.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_AUTO;
         setupAndStartConnectSequence(config);
         validateSuccessfulConnectSequence(config);
 
         mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
-                new StateChangeResult(PASSPOINT_NETWORK_ID, sWifiSsid, sBSSID,
+                new StateChangeResult(PASSPOINT_NETWORK_ID, TEST_WIFI_SSID, TEST_BSSID_STR,
                         SupplicantState.ASSOCIATING));
         mLooper.dispatchAll();
 
         mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
-                new StateChangeResult(FRAMEWORK_NETWORK_ID, sWifiSsid, sBSSID,
+                new StateChangeResult(FRAMEWORK_NETWORK_ID, TEST_WIFI_SSID, TEST_BSSID_STR,
                         SupplicantState.ASSOCIATING));
         mLooper.dispatchAll();
 
-        WifiInfo wifiInfo = mCmi.getWifiInfo();
+        WifiInfo wifiInfo = mWifiInfo;
         assertNotNull(wifiInfo);
         assertNull(wifiInfo.getPasspointFqdn());
         assertNull(wifiInfo.getPasspointProviderFriendlyName());
@@ -1378,23 +1514,22 @@
      */
     @Test
     public void connectOsuAp() throws Exception {
-        loadComponentsInStaMode();
         WifiConfiguration osuConfig = spy(WifiConfigurationTestUtil.createEphemeralNetwork());
-        osuConfig.SSID = sWifiSsid.toString();
-        osuConfig.BSSID = sBSSID;
+        osuConfig.SSID = TEST_WIFI_SSID.toString();
+        osuConfig.BSSID = TEST_BSSID_STR;
         osuConfig.osu = true;
         osuConfig.networkId = FRAMEWORK_NETWORK_ID;
         osuConfig.providerFriendlyName = WifiConfigurationTestUtil.TEST_PROVIDER_FRIENDLY_NAME;
-        osuConfig.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_PERSISTENT;
+        osuConfig.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_AUTO;
         setupAndStartConnectSequence(osuConfig);
         validateSuccessfulConnectSequence(osuConfig);
 
         mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
-                new StateChangeResult(FRAMEWORK_NETWORK_ID, sWifiSsid, sBSSID,
+                new StateChangeResult(FRAMEWORK_NETWORK_ID, TEST_WIFI_SSID, TEST_BSSID_STR,
                         SupplicantState.ASSOCIATING));
         mLooper.dispatchAll();
 
-        WifiInfo wifiInfo = mCmi.getWifiInfo();
+        WifiInfo wifiInfo = mWifiInfo;
         assertNotNull(wifiInfo);
         assertTrue(wifiInfo.isOsuAp());
         assertEquals(WifiConfigurationTestUtil.TEST_PROVIDER_FRIENDLY_NAME,
@@ -1408,28 +1543,27 @@
      */
     @Test
     public void testResetWifiInfoOsuFields() throws Exception {
-        loadComponentsInStaMode();
         WifiConfiguration osuConfig = spy(WifiConfigurationTestUtil.createEphemeralNetwork());
-        osuConfig.SSID = sWifiSsid.toString();
-        osuConfig.BSSID = sBSSID;
+        osuConfig.SSID = TEST_WIFI_SSID.toString();
+        osuConfig.BSSID = TEST_BSSID_STR;
         osuConfig.osu = true;
         osuConfig.networkId = PASSPOINT_NETWORK_ID;
         osuConfig.providerFriendlyName = WifiConfigurationTestUtil.TEST_PROVIDER_FRIENDLY_NAME;
-        osuConfig.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_PERSISTENT;
+        osuConfig.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_AUTO;
         setupAndStartConnectSequence(osuConfig);
         validateSuccessfulConnectSequence(osuConfig);
 
         mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
-                new StateChangeResult(PASSPOINT_NETWORK_ID, sWifiSsid, sBSSID,
+                new StateChangeResult(PASSPOINT_NETWORK_ID, TEST_WIFI_SSID, TEST_BSSID_STR,
                         SupplicantState.ASSOCIATING));
         mLooper.dispatchAll();
 
         mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
-                new StateChangeResult(FRAMEWORK_NETWORK_ID, sWifiSsid, sBSSID,
+                new StateChangeResult(FRAMEWORK_NETWORK_ID, TEST_WIFI_SSID, TEST_BSSID_STR,
                         SupplicantState.ASSOCIATING));
         mLooper.dispatchAll();
 
-        WifiInfo wifiInfo = mCmi.getWifiInfo();
+        WifiInfo wifiInfo = mWifiInfo;
         assertNotNull(wifiInfo);
         assertFalse(wifiInfo.isOsuAp());
     }
@@ -1441,9 +1575,9 @@
     public void verifyWifiStateTrackerUpdatedWhenDisabled() throws Exception {
         connect();
 
-        mCmi.setOperationalMode(ClientModeImpl.DISABLED_MODE, null);
+        mCmi.stop();
         mLooper.dispatchAll();
-        verify(mWifiStateTracker).updateState(eq(WifiStateTracker.DISCONNECTED));
+        verify(mWifiStateTracker).updateState(WIFI_IFACE_NAME, WifiStateTracker.DISCONNECTED);
     }
 
     /**
@@ -1461,6 +1595,7 @@
         // Remove the network requests.
         when(mWifiNetworkFactory.hasConnectionRequests()).thenReturn(false);
         when(mUntrustedWifiNetworkFactory.hasConnectionRequests()).thenReturn(false);
+        when(mOemWifiNetworkFactory.hasConnectionRequests()).thenReturn(false);
 
         WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
         config.networkId = FRAMEWORK_NETWORK_ID + 1;
@@ -1486,36 +1621,18 @@
         // Remove the network requests.
         when(mWifiNetworkFactory.hasConnectionRequests()).thenReturn(false);
         when(mUntrustedWifiNetworkFactory.hasConnectionRequests()).thenReturn(false);
+        when(mOemWifiNetworkFactory.hasConnectionRequests()).thenReturn(false);
 
         when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(false);
 
         WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
         config.networkId = FRAMEWORK_NETWORK_ID + 1;
         setupAndStartConnectSequence(config);
-        verify(mWifiConfigManager).enableNetwork(
-                eq(config.networkId), eq(true), anyInt(), any());
-        verify(mWifiConnectivityManager, never()).setUserConnectChoice(eq(config.networkId));
         verify(mWifiConnectivityManager).prepareForForcedConnection(eq(config.networkId));
         verify(mWifiConfigManager, never())
                 .getConfiguredNetworkWithoutMasking(eq(config.networkId));
         verify(mWifiNative, never()).connectToNetwork(eq(WIFI_IFACE_NAME), eq(config));
-        verify(mWifiPermissionsUtil, times(4)).checkNetworkSettingsPermission(anyInt());
-    }
-
-    @Test
-    public void enableWithInvalidNetworkId() throws Exception {
-        initializeAndAddNetworkAndVerifySuccess();
-        when(mWifiConfigManager.getConfiguredNetwork(eq(0))).thenReturn(null);
-
-        verify(mWifiNative).removeAllNetworks(WIFI_IFACE_NAME);
-
-        IActionListener connectActionListener = mock(IActionListener.class);
-        mCmi.connect(null, 0, mock(Binder.class), connectActionListener, 0, Binder.getCallingUid());
-        mLooper.dispatchAll();
-        verify(connectActionListener).onFailure(anyInt());
-
-        verify(mWifiConfigManager, never()).enableNetwork(eq(0), eq(true), anyInt(), any());
-        verify(mWifiConfigManager, never()).updateLastConnectUid(eq(0), anyInt());
+        verify(mWifiPermissionsUtil, times(2)).checkNetworkSettingsPermission(anyInt());
     }
 
     /**
@@ -1531,7 +1648,9 @@
 
         // try to reconnect
         IActionListener connectActionListener = mock(IActionListener.class);
-        mCmi.connect(null, FRAMEWORK_NETWORK_ID, mock(Binder.class), connectActionListener, 0,
+        mCmi.connectNetwork(
+                new NetworkUpdateResult(FRAMEWORK_NETWORK_ID),
+                new ActionListenerWrapper(connectActionListener),
                 Binder.getCallingUid());
         mLooper.dispatchAll();
         verify(connectActionListener).onSuccess();
@@ -1552,13 +1671,12 @@
         connect();
 
         // try to reconnect
-        WifiConfiguration config = new WifiConfiguration();
-        config.networkId = FRAMEWORK_NETWORK_ID;
-        when(mWifiConfigManager.addOrUpdateNetwork(eq(config), anyInt()))
-                .thenReturn(new NetworkUpdateResult(FRAMEWORK_NETWORK_ID));
         IActionListener connectActionListener = mock(IActionListener.class);
-        mCmi.connect(config, WifiConfiguration.INVALID_NETWORK_ID, mock(Binder.class),
-                connectActionListener, 0, Binder.getCallingUid());
+        int callingUid = Binder.getCallingUid();
+        mCmi.connectNetwork(
+                new NetworkUpdateResult(FRAMEWORK_NETWORK_ID),
+                new ActionListenerWrapper(connectActionListener),
+                callingUid);
         mLooper.dispatchAll();
         verify(connectActionListener).onSuccess();
 
@@ -1580,7 +1698,9 @@
 
         // 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,
+        mCmi.connectNetwork(
+                new NetworkUpdateResult(FRAMEWORK_NETWORK_ID),
+                new ActionListenerWrapper(connectActionListener),
                 Binder.getCallingUid());
         mLooper.dispatchAll();
         verify(connectActionListener).onSuccess();
@@ -1603,16 +1723,17 @@
 
         // 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);
+        NetworkUpdateResult networkUpdateResult = new NetworkUpdateResult(
+                FRAMEWORK_NETWORK_ID,
+                false /* ip */,
+                false /* proxy */,
+                true /* credential */,
+                false /* isNewNetwork */);
         IActionListener connectActionListener = mock(IActionListener.class);
-        mCmi.connect(config, WifiConfiguration.INVALID_NETWORK_ID, mock(Binder.class),
-                connectActionListener, 0, Binder.getCallingUid());
+        mCmi.connectNetwork(
+                networkUpdateResult,
+                new ActionListenerWrapper(connectActionListener),
+                Binder.getCallingUid());
         mLooper.dispatchAll();
         verify(connectActionListener).onSuccess();
 
@@ -1633,12 +1754,15 @@
         triggerConnect();
 
         // fail the connection.
-        mCmi.sendMessage(WifiMonitor.ASSOCIATION_REJECTION_EVENT, 0,
-                ISupplicantStaIfaceCallback.StatusCode.AP_UNABLE_TO_HANDLE_NEW_STA, sBSSID);
+        mCmi.sendMessage(WifiMonitor.ASSOCIATION_REJECTION_EVENT,
+                new AssocRejectEventInfo(mConnectedNetwork.SSID, TEST_BSSID_STR,
+                        ISupplicantStaIfaceCallback.StatusCode.AP_UNABLE_TO_HANDLE_NEW_STA, false));
         mLooper.dispatchAll();
 
         IActionListener connectActionListener = mock(IActionListener.class);
-        mCmi.connect(null, FRAMEWORK_NETWORK_ID, mock(Binder.class), connectActionListener, 0,
+        mCmi.connectNetwork(
+                new NetworkUpdateResult(FRAMEWORK_NETWORK_ID),
+                new ActionListenerWrapper(connectActionListener),
                 Binder.getCallingUid());
         mLooper.dispatchAll();
         verify(connectActionListener).onSuccess();
@@ -1660,11 +1784,15 @@
         triggerConnect();
 
         // fail the connection.
-        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, FRAMEWORK_NETWORK_ID, 0, sBSSID);
+        DisconnectEventInfo disconnectEventInfo =
+                new DisconnectEventInfo(mConnectedNetwork.SSID, TEST_BSSID_STR, 0, false);
+        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, disconnectEventInfo);
         mLooper.dispatchAll();
 
         IActionListener connectActionListener = mock(IActionListener.class);
-        mCmi.connect(null, FRAMEWORK_NETWORK_ID, mock(Binder.class), connectActionListener, 0,
+        mCmi.connectNetwork(
+                new NetworkUpdateResult(FRAMEWORK_NETWORK_ID),
+                new ActionListenerWrapper(connectActionListener),
                 Binder.getCallingUid());
         mLooper.dispatchAll();
         verify(connectActionListener).onSuccess();
@@ -1683,37 +1811,83 @@
 
         verify(mWifiNative).removeAllNetworks(WIFI_IFACE_NAME);
 
-        IActionListener connectActionListener = mock(IActionListener.class);
-        mCmi.connect(null, 0, mock(Binder.class), connectActionListener, 0, Binder.getCallingUid());
-        mLooper.dispatchAll();
-        verify(connectActionListener).onSuccess();
+        startConnectSuccess();
 
-        verify(mWifiConfigManager).enableNetwork(eq(0), eq(true), anyInt(), any());
-
-        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, sBSSID);
+        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT,
+                new NetworkConnectionEventInfo(0, TEST_WIFI_SSID, TEST_BSSID_STR, false));
         mLooper.dispatchAll();
 
         mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
-                new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.COMPLETED));
+                new StateChangeResult(0, TEST_WIFI_SSID, TEST_BSSID_STR,
+                        SupplicantState.COMPLETED));
         mLooper.dispatchAll();
 
-        assertEquals("ObtainingIpState", getCurrentState().getName());
-        reset(mWifiNative);
+        assertEquals("L3ProvisioningState", getCurrentState().getName());
 
         // Connect to a different network
-        WifiConfiguration config = new WifiConfiguration();
-        config.networkId = TEST_NETWORK_ID;
-        when(mWifiConfigManager.getConfiguredNetwork(TEST_NETWORK_ID)).thenReturn(config);
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+        config.networkId = FRAMEWORK_NETWORK_ID + 1;
+        setupAndStartConnectSequence(config);
+        validateSuccessfulConnectSequence(config);
 
-        mCmi.connect(null, TEST_NETWORK_ID, mock(Binder.class), null, 0, Binder.getCallingUid());
+        // Disconnection from previous network.
+        DisconnectEventInfo disconnectEventInfo =
+                new DisconnectEventInfo(TEST_SSID, TEST_BSSID_STR, 0, false);
+        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, disconnectEventInfo);
+        mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
+                new StateChangeResult(0, TEST_WIFI_SSID, TEST_BSSID_STR,
+                        SupplicantState.DISCONNECTED));
+        mLooper.dispatchAll();
+
+        // Ensure we don't end the new connection event.
+        verify(mWifiMetrics, never()).endConnectionEvent(
+                any(), eq(WifiMetrics.ConnectionEvent.FAILURE_NETWORK_DISCONNECTION),
+                anyInt(), anyInt(), anyInt());
+        verify(mWifiConnectivityManager).prepareForForcedConnection(FRAMEWORK_NETWORK_ID + 1);
+    }
+
+    /**
+     * If there is a network removal while still connecting to it, the connection
+     * should be aborted.
+     */
+    @Test
+    public void networkRemovalWhileObtainingIp() throws Exception {
+        initializeAndAddNetworkAndVerifySuccess();
+
+        verify(mWifiNative).removeAllNetworks(WIFI_IFACE_NAME);
+
+        startConnectSuccess();
+
+        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT,
+                new NetworkConnectionEventInfo(0, TEST_WIFI_SSID, TEST_BSSID_STR, false));
         mLooper.dispatchAll();
 
         mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
-                new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.DISCONNECTED));
+                new StateChangeResult(0, TEST_WIFI_SSID, TEST_BSSID_STR,
+                        SupplicantState.COMPLETED));
         mLooper.dispatchAll();
 
-        verify(mWifiConnectivityManager).prepareForForcedConnection(eq(config.networkId));
+        assertEquals("L3ProvisioningState", getCurrentState().getName());
+        reset(mWifiNative);
 
+        // Simulate the target network removal & the disconnect trigger.
+        WifiConfiguration removedNetwork = new WifiConfiguration();
+        removedNetwork.networkId = FRAMEWORK_NETWORK_ID;
+        mConfigUpdateListenerCaptor.getValue().onNetworkRemoved(removedNetwork);
+        mLooper.dispatchAll();
+
+        verify(mWifiNative).removeNetworkCachedData(FRAMEWORK_NETWORK_ID);
+        verify(mWifiNative).disconnect(WIFI_IFACE_NAME);
+        verify(mWifiMetrics).logStaEvent(anyString(), eq(StaEvent.TYPE_FRAMEWORK_DISCONNECT),
+                eq(StaEvent.DISCONNECT_NETWORK_REMOVED));
+
+        when(mWifiConfigManager.getConfiguredNetwork(FRAMEWORK_NETWORK_ID)).thenReturn(null);
+        DisconnectEventInfo disconnectEventInfo =
+                new DisconnectEventInfo(TEST_SSID, TEST_BSSID_STR, 0, false);
+        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, disconnectEventInfo);
+        mLooper.dispatchAll();
+
+        assertEquals("DisconnectedState", getCurrentState().getName());
     }
 
     /**
@@ -1728,7 +1902,9 @@
         when(mWifiConfigManager.getConfiguredNetwork(TEST_NETWORK_ID)).thenReturn(config);
 
         IActionListener connectActionListener = mock(IActionListener.class);
-        mCmi.connect(null, TEST_NETWORK_ID, mock(Binder.class), connectActionListener, 0,
+        mCmi.connectNetwork(
+                new NetworkUpdateResult(TEST_NETWORK_ID),
+                new ActionListenerWrapper(connectActionListener),
                 Process.SYSTEM_UID);
         mLooper.dispatchAll();
         verify(connectActionListener).onSuccess();
@@ -1737,51 +1913,66 @@
                 WifiMetricsProto.ConnectionEvent.NOMINATOR_MANUAL);
     }
 
+    private void startConnectSuccess() throws Exception {
+        startConnectSuccess(FRAMEWORK_NETWORK_ID);
+    }
+
+    private void startConnectSuccess(int networkId) throws Exception {
+        IActionListener connectActionListener = mock(IActionListener.class);
+        mCmi.connectNetwork(
+                new NetworkUpdateResult(networkId),
+                new ActionListenerWrapper(connectActionListener),
+                Binder.getCallingUid());
+        mLooper.dispatchAll();
+        verify(connectActionListener).onSuccess();
+    }
+
     @Test
     public void testDhcpFailure() throws Exception {
         initializeAndAddNetworkAndVerifySuccess();
 
-        IActionListener connectActionListener = mock(IActionListener.class);
-        mCmi.connect(null, 0, mock(Binder.class), connectActionListener, 0, Binder.getCallingUid());
-        mLooper.dispatchAll();
-        verify(connectActionListener).onSuccess();
-
-        verify(mWifiConfigManager).enableNetwork(eq(0), eq(true), anyInt(), any());
+        startConnectSuccess();
 
         mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
-                new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.ASSOCIATED));
+                new StateChangeResult(0, TEST_WIFI_SSID, TEST_BSSID_STR,
+                        SupplicantState.ASSOCIATED));
         mLooper.dispatchAll();
 
-        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, sBSSID);
+        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT,
+                new NetworkConnectionEventInfo(0, TEST_WIFI_SSID, TEST_BSSID_STR, false));
         mLooper.dispatchAll();
-        verify(mBssidBlocklistMonitor).handleBssidConnectionSuccess(sBSSID, sSSID);
+        verify(mWifiBlocklistMonitor).handleBssidConnectionSuccess(TEST_BSSID_STR, TEST_SSID);
 
         mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
-                new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.COMPLETED));
+                new StateChangeResult(0, TEST_WIFI_SSID, TEST_BSSID_STR,
+                        SupplicantState.COMPLETED));
         mLooper.dispatchAll();
 
-        assertEquals("ObtainingIpState", getCurrentState().getName());
+        assertEquals("L3ProvisioningState", getCurrentState().getName());
         injectDhcpFailure();
         mLooper.dispatchAll();
 
-        assertEquals("DisconnectingState", getCurrentState().getName());
+        verify(mWifiNative).disconnect(WIFI_IFACE_NAME);
         // Verify this is not counted as a IP renewal failure
         verify(mWifiMetrics, never()).incrementIpRenewalFailure();
         // Verifies that WifiLastResortWatchdog be notified
         // by DHCP failure
         verify(mWifiLastResortWatchdog, times(2)).noteConnectionFailureAndTriggerIfNeeded(
-                sSSID, sBSSID, WifiLastResortWatchdog.FAILURE_CODE_DHCP);
-        verify(mBssidBlocklistMonitor, times(2)).handleBssidConnectionFailure(eq(sBSSID),
-                eq(sSSID), eq(BssidBlocklistMonitor.REASON_DHCP_FAILURE), anyInt());
-        verify(mBssidBlocklistMonitor, times(2)).handleBssidConnectionFailure(eq(sBSSID), eq(sSSID),
-                eq(BssidBlocklistMonitor.REASON_DHCP_FAILURE), anyInt());
-        verify(mBssidBlocklistMonitor, never()).handleDhcpProvisioningSuccess(sBSSID, sSSID);
-        verify(mBssidBlocklistMonitor, never()).handleNetworkValidationSuccess(sBSSID, sSSID);
+                eq(TEST_SSID), eq(TEST_BSSID_STR),
+                eq(WifiLastResortWatchdog.FAILURE_CODE_DHCP), anyBoolean());
+        verify(mWifiBlocklistMonitor, times(2)).handleBssidConnectionFailure(eq(TEST_BSSID_STR),
+                eq(TEST_SSID), eq(WifiBlocklistMonitor.REASON_DHCP_FAILURE), anyInt());
+        verify(mWifiBlocklistMonitor, times(2)).handleBssidConnectionFailure(eq(TEST_BSSID_STR),
+                eq(TEST_SSID), eq(WifiBlocklistMonitor.REASON_DHCP_FAILURE), anyInt());
+        verify(mWifiBlocklistMonitor, never()).handleDhcpProvisioningSuccess(
+                TEST_BSSID_STR, TEST_SSID);
+        verify(mWifiBlocklistMonitor, never()).handleNetworkValidationSuccess(
+                TEST_BSSID_STR, TEST_SSID);
     }
 
     /**
      * Verify that a IP renewal failure is logged when IP provisioning fail in the
-     * ConnectedState.
+     * L3ConnectedState.
      */
     @Test
     public void testDhcpRenewalMetrics() throws Exception {
@@ -1801,19 +1992,17 @@
     public void testWrongPasswordWithPreviouslyConnected() throws Exception {
         initializeAndAddNetworkAndVerifySuccess();
 
-        IActionListener connectActionListener = mock(IActionListener.class);
-        mCmi.connect(null, 0, mock(Binder.class), connectActionListener, 0, Binder.getCallingUid());
-        mLooper.dispatchAll();
-        verify(connectActionListener).onSuccess();
+        startConnectSuccess();
 
-        verify(mWifiConfigManager).enableNetwork(eq(0), eq(true), anyInt(), any());
-
-        WifiConfiguration config = new WifiConfiguration();
+        WifiConfiguration config = createTestNetwork(false);
         config.getNetworkSelectionStatus().setHasEverConnected(true);
         when(mWifiConfigManager.getConfiguredNetwork(anyInt())).thenReturn(config);
 
         mCmi.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT,
                 WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD);
+        DisconnectEventInfo disconnectEventInfo =
+                new DisconnectEventInfo(TEST_SSID, TEST_BSSID_STR, 0, false);
+        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, disconnectEventInfo);
         mLooper.dispatchAll();
 
         verify(mWrongPasswordNotifier, never()).onWrongPasswordError(anyString());
@@ -1824,6 +2013,36 @@
     }
 
     /**
+     * It is observed sometimes the WifiMonitor.NETWORK_DISCONNECTION_EVENT is observed before the
+     * actual connection failure messages while making a connection.
+     * The test make sure that make sure that the connection event is ended properly in the above
+     * case.
+     */
+    @Test
+    public void testDisconnectionEventInL2ConnectingStateEndsConnectionEvent() throws Exception {
+        initializeAndAddNetworkAndVerifySuccess();
+
+        startConnectSuccess();
+
+        WifiConfiguration config = createTestNetwork(false);
+        config.getNetworkSelectionStatus().setHasEverConnected(true);
+        when(mWifiConfigManager.getConfiguredNetwork(anyInt())).thenReturn(config);
+
+        DisconnectEventInfo disconnectEventInfo =
+                new DisconnectEventInfo(TEST_SSID, TEST_BSSID_STR, 0, false);
+        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, disconnectEventInfo);
+        mLooper.dispatchAll();
+
+        verify(mWifiMetrics).endConnectionEvent(
+                any(), eq(WifiMetrics.ConnectionEvent.FAILURE_NETWORK_DISCONNECTION),
+                anyInt(), anyInt(), anyInt());
+        verify(mWifiConnectivityManager).handleConnectionAttemptEnded(
+                any(), anyInt(), any(), any());
+        assertEquals(WifiInfo.SECURITY_TYPE_UNKNOWN, mWifiInfo.getCurrentSecurityType());
+        assertEquals("DisconnectedState", getCurrentState().getName());
+    }
+
+    /**
      * Verify that the network selection status will be updated with DISABLED_BY_WRONG_PASSWORD
      * when wrong password authentication failure is detected and the network has never been
      * connected.
@@ -1832,24 +2051,22 @@
     public void testWrongPasswordWithNeverConnected() throws Exception {
         initializeAndAddNetworkAndVerifySuccess();
 
-        IActionListener connectActionListener = mock(IActionListener.class);
-        mCmi.connect(null, 0, mock(Binder.class), connectActionListener, 0, Binder.getCallingUid());
-        mLooper.dispatchAll();
-        verify(connectActionListener).onSuccess();
-
-        verify(mWifiConfigManager).enableNetwork(eq(0), eq(true), anyInt(), any());
+        startConnectSuccess();
 
         WifiConfiguration config = new WifiConfiguration();
-        config.SSID = sSSID;
+        config.SSID = TEST_SSID;
         config.getNetworkSelectionStatus().setHasEverConnected(false);
         config.carrierId = CARRIER_ID_1;
         when(mWifiConfigManager.getConfiguredNetwork(anyInt())).thenReturn(config);
 
         mCmi.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT,
                 WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD);
+        DisconnectEventInfo disconnectEventInfo =
+                new DisconnectEventInfo(TEST_SSID, TEST_BSSID_STR, 0, false);
+        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, disconnectEventInfo);
         mLooper.dispatchAll();
 
-        verify(mWrongPasswordNotifier).onWrongPasswordError(eq(sSSID));
+        verify(mWrongPasswordNotifier).onWrongPasswordError(eq(TEST_SSID));
         verify(mWifiConfigManager).updateNetworkSelectionStatus(anyInt(),
                 eq(WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD));
         verify(mWifiMetrics).incrementNumOfCarrierWifiConnectionAuthFailure();
@@ -1857,32 +2074,6 @@
     }
 
     /**
-     * Verify that the network selection status will be updated with DISABLED_BY_WRONG_PASSWORD
-     * when wrong password authentication failure is detected and the network is unknown.
-     */
-    @Test
-    public void testWrongPasswordWithNullNetwork() throws Exception {
-        initializeAndAddNetworkAndVerifySuccess();
-
-        IActionListener connectActionListener = mock(IActionListener.class);
-        mCmi.connect(null, 0, mock(Binder.class), connectActionListener, 0, Binder.getCallingUid());
-        mLooper.dispatchAll();
-        verify(connectActionListener).onSuccess();
-
-        verify(mWifiConfigManager).enableNetwork(eq(0), eq(true), anyInt(), any());
-
-        when(mWifiConfigManager.getConfiguredNetwork(anyInt())).thenReturn(null);
-
-        mCmi.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT,
-                WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD);
-        mLooper.dispatchAll();
-
-        verify(mWifiConfigManager).updateNetworkSelectionStatus(anyInt(),
-                eq(WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD));
-        assertEquals("DisconnectedState", getCurrentState().getName());
-    }
-
-    /**
      * Verify that the function resetCarrierKeysForImsiEncryption() in TelephonyManager
      * is called when a Authentication failure is detected with a vendor specific EAP Error
      * of certification expired while using EAP-SIM
@@ -1890,27 +2081,18 @@
      */
     @Test
     public void testEapSimErrorVendorSpecific() throws Exception {
-        when(mWifiMetrics.startConnectionEvent(any(), anyString(), anyInt())).thenReturn(80000);
+        when(mWifiMetrics.startConnectionEvent(any(), any(), anyString(), anyInt()))
+                .thenReturn(80000);
         initializeAndAddNetworkAndVerifySuccess();
 
-        IActionListener connectActionListener = mock(IActionListener.class);
-        mCmi.connect(null, 0, mock(Binder.class), connectActionListener, 0, Binder.getCallingUid());
-        mLooper.dispatchAll();
-        verify(connectActionListener).onSuccess();
-
-        verify(mWifiConfigManager).enableNetwork(eq(0), eq(true), anyInt(), any());
+        startConnectSuccess();
 
         WifiConfiguration config = new WifiConfiguration();
-        config.SSID = sSSID;
+        config.SSID = TEST_SSID;
         config.getNetworkSelectionStatus().setHasEverConnected(true);
         config.allowedKeyManagement.set(KeyMgmt.IEEE8021X);
         config.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.SIM);
         when(mWifiConfigManager.getConfiguredNetwork(anyInt())).thenReturn(config);
-        MockitoSession mockSession = ExtendedMockito.mockitoSession()
-                .mockStatic(SubscriptionManager.class)
-                .startMocking();
-        when(SubscriptionManager.getDefaultDataSubscriptionId()).thenReturn(DATA_SUBID);
-        when(SubscriptionManager.isValidSubscriptionId(anyInt())).thenReturn(true);
         when(mWifiScoreCard.detectAbnormalConnectionFailure(anyString()))
                 .thenReturn(WifiHealthMonitor.REASON_AUTH_FAILURE);
 
@@ -1920,9 +2102,8 @@
         mLooper.dispatchAll();
 
         verify(mEapFailureNotifier).onEapFailure(
-                WifiNative.EAP_SIM_VENDOR_SPECIFIC_CERT_EXPIRED, config);
-        verify(mDataTelephonyManager).resetCarrierKeysForImsiEncryption();
-        mockSession.finishMocking();
+                WifiNative.EAP_SIM_VENDOR_SPECIFIC_CERT_EXPIRED, config, true);
+        verify(mWifiCarrierInfoManager).resetCarrierKeysForImsiEncryption(any());
         verify(mDeviceConfigFacade).isAbnormalConnectionFailureBugreportEnabled();
         verify(mWifiScoreCard).detectAbnormalConnectionFailure(anyString());
         verify(mWifiDiagnostics, times(2)).takeBugReport(anyString(), anyString());
@@ -1937,12 +2118,7 @@
     public void testEapTlsErrorVendorSpecific() throws Exception {
         initializeAndAddNetworkAndVerifySuccess();
 
-        IActionListener connectActionListener = mock(IActionListener.class);
-        mCmi.connect(null, 0, mock(Binder.class), connectActionListener, 0, Binder.getCallingUid());
-        mLooper.dispatchAll();
-        verify(connectActionListener).onSuccess();
-
-        verify(mWifiConfigManager).enableNetwork(eq(0), eq(true), anyInt(), any());
+        startConnectSuccess();
 
         WifiConfiguration config = new WifiConfiguration();
         config.getNetworkSelectionStatus().setHasEverConnected(true);
@@ -1955,7 +2131,7 @@
                 WifiNative.EAP_SIM_VENDOR_SPECIFIC_CERT_EXPIRED);
         mLooper.dispatchAll();
 
-        verify(mDataTelephonyManager, never()).resetCarrierKeysForImsiEncryption();
+        verify(mWifiCarrierInfoManager, never()).resetCarrierKeysForImsiEncryption(any());
     }
 
     /**
@@ -1966,12 +2142,7 @@
     public void testEapSimNoSubscribedError() throws Exception {
         initializeAndAddNetworkAndVerifySuccess();
 
-        IActionListener connectActionListener = mock(IActionListener.class);
-        mCmi.connect(null, 0, mock(Binder.class), connectActionListener, 0, Binder.getCallingUid());
-        mLooper.dispatchAll();
-        verify(connectActionListener).onSuccess();
-
-        verify(mWifiConfigManager).enableNetwork(eq(0), eq(true), anyInt(), any());
+        startConnectSuccess();
 
         when(mWifiConfigManager.getConfiguredNetwork(anyInt())).thenReturn(null);
 
@@ -1989,18 +2160,16 @@
     public void testBadNetworkEvent() throws Exception {
         initializeAndAddNetworkAndVerifySuccess();
 
-        IActionListener connectActionListener = mock(IActionListener.class);
-        mCmi.connect(null, 0, mock(Binder.class), connectActionListener, 0, Binder.getCallingUid());
-        mLooper.dispatchAll();
-        verify(connectActionListener).onSuccess();
+        startConnectSuccess();
 
-        verify(mWifiConfigManager).enableNetwork(eq(0), eq(true), anyInt(), any());
-
-        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, 0, 0, sBSSID);
+        DisconnectEventInfo disconnectEventInfo =
+                new DisconnectEventInfo(TEST_SSID, TEST_BSSID_STR, 0, false);
+        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, disconnectEventInfo);
         mLooper.dispatchAll();
 
         mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
-                new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.COMPLETED));
+                new StateChangeResult(0, TEST_WIFI_SSID, TEST_BSSID_STR,
+                        SupplicantState.COMPLETED));
         mLooper.dispatchAll();
 
         assertEquals("DisconnectedState", getCurrentState().getName());
@@ -2010,8 +2179,6 @@
 
     @Test
     public void getWhatToString() throws Exception {
-        assertEquals("CMD_CHANNEL_HALF_CONNECTED", mCmi.getWhatToString(
-                AsyncChannel.CMD_CHANNEL_HALF_CONNECTED));
         assertEquals("CMD_PRE_DHCP_ACTION", mCmi.getWhatToString(CMD_PRE_DHCP_ACTION));
         assertEquals("CMD_IP_REACHABILITY_LOST", mCmi.getWhatToString(
                 ClientModeImpl.CMD_IP_REACHABILITY_LOST));
@@ -2019,24 +2186,86 @@
 
     @Test
     public void disconnect() throws Exception {
-        when(mWifiScoreCard.detectAbnormalDisconnection())
+        when(mWifiScoreCard.detectAbnormalDisconnection(any()))
                 .thenReturn(WifiHealthMonitor.REASON_SHORT_CONNECTION_NONLOCAL);
         InOrder inOrderWifiLockManager = inOrder(mWifiLockManager);
         connect();
-        inOrderWifiLockManager.verify(mWifiLockManager).updateWifiClientConnected(true);
+        inOrderWifiLockManager.verify(mWifiLockManager)
+                .updateWifiClientConnected(mClientModeManager, true);
 
-        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, -1, 3, sBSSID);
+        DisconnectEventInfo disconnectEventInfo =
+                new DisconnectEventInfo(mConnectedNetwork.SSID, TEST_BSSID_STR, 0, false);
+        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, disconnectEventInfo);
         mLooper.dispatchAll();
         mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
-                new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.DISCONNECTED));
+                new StateChangeResult(0, WifiSsid.createFromAsciiEncoded(mConnectedNetwork.SSID),
+                        TEST_BSSID_STR, SupplicantState.DISCONNECTED));
         mLooper.dispatchAll();
 
-        verify(mWifiStateTracker).updateState(eq(WifiStateTracker.DISCONNECTED));
-        verify(mWifiNetworkSuggestionsManager).handleDisconnect(any(), any());
+        verify(mWifiStateTracker).updateState(WIFI_IFACE_NAME, WifiStateTracker.DISCONNECTED);
         assertEquals("DisconnectedState", getCurrentState().getName());
-        inOrderWifiLockManager.verify(mWifiLockManager).updateWifiClientConnected(false);
-        verify(mWifiScoreCard).detectAbnormalDisconnection();
+        verify(mCmiMonitor).onConnectionEnd(mClientModeManager);
+        inOrderWifiLockManager.verify(mWifiLockManager)
+                .updateWifiClientConnected(mClientModeManager, false);
+        verify(mWifiScoreCard).detectAbnormalDisconnection(WIFI_IFACE_NAME);
         verify(mWifiDiagnostics).takeBugReport(anyString(), anyString());
+        verify(mWifiNative).disableNetwork(WIFI_IFACE_NAME);
+        // Set MAC address thrice - once at bootup, once for new connection, once for disconnect.
+        verify(mWifiNative, times(3)).setStaMacAddress(eq(WIFI_IFACE_NAME), any());
+        // ClientModeManager should only be stopped when in lingering mode
+        verify(mClientModeManager, never()).stop();
+    }
+
+    @Test
+    public void secondaryRoleCmmDisconnected_stopsClientModeManager() throws Exception {
+        // Owning ClientModeManager has role SECONDARY_TRANSIENT
+        when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_TRANSIENT);
+
+        connect();
+
+        // ClientModeManager never stopped
+        verify(mClientModeManager, never()).stop();
+
+        // Disconnected from network
+        DisconnectEventInfo disconnectEventInfo =
+                new DisconnectEventInfo(mConnectedNetwork.SSID, TEST_BSSID_STR, 0, false);
+        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, disconnectEventInfo);
+        mLooper.dispatchAll();
+        mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
+                new StateChangeResult(0, WifiSsid.createFromAsciiEncoded(mConnectedNetwork.SSID),
+                        TEST_BSSID_STR, SupplicantState.DISCONNECTED));
+        mLooper.dispatchAll();
+
+        assertEquals("DisconnectedState", getCurrentState().getName());
+
+        // Since in lingering mode, disconnect => stop ClientModeManager
+        verify(mClientModeManager).stop();
+    }
+
+    @Test
+    public void primaryCmmDisconnected_doesntStopsClientModeManager() throws Exception {
+        // Owning ClientModeManager is primary
+        when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_PRIMARY);
+
+        connect();
+
+        // ClientModeManager never stopped
+        verify(mClientModeManager, never()).stop();
+
+        // Disconnected from network
+        DisconnectEventInfo disconnectEventInfo =
+                new DisconnectEventInfo(mConnectedNetwork.SSID, TEST_BSSID_STR, 0, false);
+        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, disconnectEventInfo);
+        mLooper.dispatchAll();
+        mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
+                new StateChangeResult(0, WifiSsid.createFromAsciiEncoded(mConnectedNetwork.SSID),
+                        TEST_BSSID_STR, SupplicantState.DISCONNECTED));
+        mLooper.dispatchAll();
+
+        assertEquals("DisconnectedState", getCurrentState().getName());
+
+        // Since primary => don't stop ClientModeManager
+        verify(mClientModeManager, never()).stop();
     }
 
     /**
@@ -2049,7 +2278,8 @@
     @Test
     public void setHasEverConnectedTrueOnConnect() throws Exception {
         connect();
-        verify(mWifiConfigManager, atLeastOnce()).updateNetworkAfterConnect(0);
+        verify(mWifiConfigManager, atLeastOnce()).updateNetworkAfterConnect(eq(0), eq(false),
+                anyInt());
     }
 
     /**
@@ -2061,7 +2291,7 @@
     @Test
     public void connectionFailureDoesNotSetHasEverConnectedTrue() throws Exception {
         testDhcpFailure();
-        verify(mWifiConfigManager, never()).updateNetworkAfterConnect(0);
+        verify(mWifiConfigManager, never()).updateNetworkAfterConnect(eq(0), eq(false), anyInt());
     }
 
     @Test
@@ -2071,7 +2301,7 @@
 
     @Test
     public void verboseLogRecSizeIsGreaterThanNormalSize() {
-        assertTrue(LOG_REC_LIMIT_IN_VERBOSE_MODE > ClientModeImpl.NUM_LOG_RECS_NORMAL);
+        assertTrue(LOG_REC_LIMIT_IN_VERBOSE_MODE > mWifiGlobals.getClientModeImplNumLogRecs());
     }
 
     /**
@@ -2079,7 +2309,8 @@
      */
     @Test
     public void normalLogRecSizeIsUsedByDefault() {
-        assertEquals(ClientModeImpl.NUM_LOG_RECS_NORMAL, mCmi.getLogRecMaxSize());
+        mCmi.enableVerboseLogging(false);
+        assertEquals(mWifiGlobals.getClientModeImplNumLogRecs(), mCmi.getLogRecMaxSize());
     }
 
     /**
@@ -2087,7 +2318,7 @@
      */
     @Test
     public void enablingVerboseLoggingUpdatesLogRecSize() {
-        mCmi.enableVerboseLogging(1);
+        mCmi.enableVerboseLogging(true);
         assertEquals(LOG_REC_LIMIT_IN_VERBOSE_MODE, mCmi.getLogRecMaxSize());
     }
 
@@ -2097,15 +2328,15 @@
         mLooper.dispatchAll();
         assertTrue(mCmi.getLogRecSize() >= 1);
 
-        mCmi.enableVerboseLogging(0);
+        mCmi.enableVerboseLogging(false);
         assertEquals(0, mCmi.getLogRecSize());
     }
 
     @Test
     public void disablingVerboseLoggingUpdatesLogRecSize() {
-        mCmi.enableVerboseLogging(1);
-        mCmi.enableVerboseLogging(0);
-        assertEquals(ClientModeImpl.NUM_LOG_RECS_NORMAL, mCmi.getLogRecMaxSize());
+        mCmi.enableVerboseLogging(true);
+        mCmi.enableVerboseLogging(false);
+        assertEquals(mWifiGlobals.getClientModeImplNumLogRecs(), mCmi.getLogRecMaxSize());
     }
 
     @Test
@@ -2122,6 +2353,7 @@
 
     @Test
     public void logRecsExcludeRssiPollCommandByDefault() {
+        mCmi.enableVerboseLogging(false);
         mCmi.sendMessage(ClientModeImpl.CMD_RSSI_POLL);
         mLooper.dispatchAll();
         assertEquals(0, mCmi.copyLogRecs()
@@ -2132,7 +2364,7 @@
 
     @Test
     public void logRecsIncludeRssiPollCommandWhenVerboseLoggingIsEnabled() {
-        mCmi.enableVerboseLogging(1);
+        mCmi.enableVerboseLogging(true);
         mCmi.sendMessage(ClientModeImpl.CMD_RSSI_POLL);
         mLooper.dispatchAll();
         assertEquals(1, mCmi.copyLogRecs()
@@ -2147,92 +2379,62 @@
      */
     @Test
     public void syncStartSubscriptionProvisioningInClientMode() throws Exception {
-        loadComponentsInStaMode();
         when(mPasspointManager.startSubscriptionProvisioning(anyInt(),
                 any(OsuProvider.class), any(IProvisioningCallback.class))).thenReturn(true);
         mLooper.startAutoDispatch();
         assertTrue(mCmi.syncStartSubscriptionProvisioning(
-                OTHER_USER_UID, mOsuProvider, mProvisioningCallback, mCmiAsyncChannel));
+                OTHER_USER_UID, mOsuProvider, mProvisioningCallback));
         verify(mPasspointManager).startSubscriptionProvisioning(OTHER_USER_UID, mOsuProvider,
                 mProvisioningCallback);
         mLooper.stopAutoDispatch();
     }
 
-    /**
-     * Verify that syncStartSubscriptionProvisioning will be a no-op and return false before
-     * SUPPLICANT_START command is received by the CMI.
-     */
     @Test
-    public void syncStartSubscriptionProvisioningBeforeSupplicantOrAPStart() throws Exception {
+    public void testSyncGetCurrentNetwork() throws Exception {
+        // syncGetCurrentNetwork() returns null when disconnected
         mLooper.startAutoDispatch();
-        assertFalse(mCmi.syncStartSubscriptionProvisioning(
-                OTHER_USER_UID, mOsuProvider, mProvisioningCallback, mCmiAsyncChannel));
+        assertNull(mCmi.syncGetCurrentNetwork());
         mLooper.stopAutoDispatch();
-        verify(mPasspointManager, never()).startSubscriptionProvisioning(
-                anyInt(), any(OsuProvider.class), any(IProvisioningCallback.class));
-    }
 
-    /**
-     * Verify that syncStartSubscriptionProvisioning will be a no-op and return false when not in
-     * client mode.
-     */
-    @Test
-    public void syncStartSubscriptionProvisioningNoOpWifiDisabled() throws Exception {
+        connect();
+
+        // syncGetCurrentNetwork() returns non-null Network when connected
         mLooper.startAutoDispatch();
-        assertFalse(mCmi.syncStartSubscriptionProvisioning(
-                OTHER_USER_UID, mOsuProvider, mProvisioningCallback, mCmiAsyncChannel));
+        assertEquals(mNetwork, mCmi.syncGetCurrentNetwork());
         mLooper.stopAutoDispatch();
-        verify(mPasspointManager, never()).startSubscriptionProvisioning(
-                anyInt(), any(OsuProvider.class), any(IProvisioningCallback.class));
     }
 
     /**
      *  Test that we disconnect from a network if it was removed while we are in the
-     *  ObtainingIpState.
+     *  L3ProvisioningState.
      */
     @Test
     public void disconnectFromNetworkWhenRemovedWhileObtainingIpAddr() throws Exception {
         initializeAndAddNetworkAndVerifySuccess();
 
-        when(mWifiConfigManager.enableNetwork(eq(0), eq(true), anyInt(), any()))
-                .thenReturn(true);
-        when(mWifiConfigManager.updateLastConnectUid(eq(0), anyInt())).thenReturn(true);
-
         verify(mWifiNative).removeAllNetworks(WIFI_IFACE_NAME);
 
-        IActionListener connectActionListener = mock(IActionListener.class);
-        mCmi.connect(null, 0, mock(Binder.class), connectActionListener, 0, Binder.getCallingUid());
-        mLooper.dispatchAll();
-        verify(connectActionListener).onSuccess();
+        startConnectSuccess();
 
-        verify(mWifiConfigManager).enableNetwork(eq(0), eq(true), anyInt(), any());
-        verify(mWifiConnectivityManager).setUserConnectChoice(eq(0));
         when(mWifiConfigManager.getScanDetailCacheForNetwork(FRAMEWORK_NETWORK_ID))
                 .thenReturn(mScanDetailCache);
 
-        when(mScanDetailCache.getScanDetail(sBSSID)).thenReturn(
-                getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq));
-        when(mScanDetailCache.getScanResult(sBSSID)).thenReturn(
-                getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq).getScanResult());
+        when(mScanDetailCache.getScanDetail(TEST_BSSID_STR)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, TEST_BSSID_STR, sFreq));
+        when(mScanDetailCache.getScanResult(TEST_BSSID_STR)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, TEST_BSSID_STR, sFreq).getScanResult());
 
-        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, sBSSID);
+        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT,
+                new NetworkConnectionEventInfo(0, TEST_WIFI_SSID, TEST_BSSID_STR, false));
         mLooper.dispatchAll();
 
         mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
-                new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.COMPLETED));
+                new StateChangeResult(0, TEST_WIFI_SSID, TEST_BSSID_STR,
+                        SupplicantState.COMPLETED));
         mLooper.dispatchAll();
 
-        assertEquals("ObtainingIpState", getCurrentState().getName());
+        assertEquals("L3ProvisioningState", getCurrentState().getName());
 
-        // now remove the config
-        reset(connectActionListener);
-        when(mWifiConfigManager.removeNetwork(eq(FRAMEWORK_NETWORK_ID), anyInt(), any()))
-                .thenReturn(true);
-        mCmi.forget(FRAMEWORK_NETWORK_ID, mock(Binder.class), connectActionListener, 0,
-                Binder.getCallingUid());
-        mLooper.dispatchAll();
-        verify(connectActionListener).onSuccess();
-        verify(mWifiConfigManager).removeNetwork(eq(FRAMEWORK_NETWORK_ID), anyInt(), any());
         // trigger removal callback to trigger disconnect.
         WifiConfiguration removedConfig = new WifiConfiguration();
         removedConfig.networkId = FRAMEWORK_NETWORK_ID;
@@ -2253,29 +2455,7 @@
         injectDhcpSuccess(dhcpResults);
         mLooper.dispatchAll();
 
-        assertEquals("DisconnectingState", getCurrentState().getName());
-    }
-
-    /**
-     * Test verifying that interface Supplicant update for inactive driver does not trigger
-     * SelfRecovery when WifiNative reports the interface is up.
-     */
-    @Test
-    public void testSupplicantUpdateDriverInactiveIfaceUpClientModeDoesNotTriggerSelfRecovery()
-            throws Exception {
-        // Trigger initialize to capture the death handler registration.
-        loadComponentsInStaMode();
-
-        when(mWifiNative.isInterfaceUp(eq(WIFI_IFACE_NAME))).thenReturn(true);
-
-        // make sure supplicant has been reported as inactive
-        mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
-                new StateChangeResult(0, WifiSsid.createFromAsciiEncoded(""), null,
-                        SupplicantState.INTERFACE_DISABLED));
-        mLooper.dispatchAll();
-
-        // CMI should trigger self recovery, but not disconnect until externally triggered
-        verify(mSelfRecovery, never()).trigger(eq(SelfRecovery.REASON_STA_IFACE_DOWN));
+        verify(mWifiNative, times(2)).disconnect(WIFI_IFACE_NAME);
     }
 
     /**
@@ -2283,37 +2463,83 @@
      */
     @Test
     public void testWifiInfoUpdatedUponSupplicantStateChangedEvent() throws Exception {
-        // Connect to network with |sBSSID|, |sFreq|.
+        // Connect to network with |TEST_BSSID_STR|, |sFreq|.
         connect();
 
         // Set the scan detail cache for roaming target.
         when(mWifiConfigManager.getScanDetailCacheForNetwork(FRAMEWORK_NETWORK_ID))
                 .thenReturn(mScanDetailCache);
-        when(mScanDetailCache.getScanDetail(sBSSID1)).thenReturn(
-                getGoogleGuestScanDetail(TEST_RSSI, sBSSID1, sFreq1));
-        when(mScanDetailCache.getScanResult(sBSSID1)).thenReturn(
-                getGoogleGuestScanDetail(TEST_RSSI, sBSSID1, sFreq1).getScanResult());
+        when(mScanDetailCache.getScanDetail(TEST_BSSID_STR1)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, TEST_BSSID_STR1, sFreq1));
+        when(mScanDetailCache.getScanResult(TEST_BSSID_STR1)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, TEST_BSSID_STR1, sFreq1).getScanResult());
 
-        // This simulates the behavior of roaming to network with |sBSSID1|, |sFreq1|.
+        // This simulates the behavior of roaming to network with |TEST_BSSID_STR1|, |sFreq1|.
         // Send a SUPPLICANT_STATE_CHANGE_EVENT, verify WifiInfo is updated.
         mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
-                new StateChangeResult(0, sWifiSsid, sBSSID1, SupplicantState.COMPLETED));
+                new StateChangeResult(0, TEST_WIFI_SSID, TEST_BSSID_STR1,
+                        SupplicantState.COMPLETED));
         mLooper.dispatchAll();
 
-        WifiInfo wifiInfo = mCmi.getWifiInfo();
-        assertEquals(sBSSID1, wifiInfo.getBSSID());
+        WifiInfo wifiInfo = mWifiInfo;
+        assertEquals(TEST_BSSID_STR1, wifiInfo.getBSSID());
         assertEquals(sFreq1, wifiInfo.getFrequency());
         assertEquals(SupplicantState.COMPLETED, wifiInfo.getSupplicantState());
 
         mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
-                new StateChangeResult(0, sWifiSsid, sBSSID1, SupplicantState.DISCONNECTED));
+                new StateChangeResult(0, TEST_WIFI_SSID, TEST_BSSID_STR1,
+                        SupplicantState.DISCONNECTED));
         mLooper.dispatchAll();
 
-        wifiInfo = mCmi.getWifiInfo();
+        wifiInfo = mWifiInfo;
         assertNull(wifiInfo.getBSSID());
         assertEquals(WifiManager.UNKNOWN_SSID, wifiInfo.getSSID());
         assertEquals(WifiConfiguration.INVALID_NETWORK_ID, wifiInfo.getNetworkId());
         assertEquals(SupplicantState.DISCONNECTED, wifiInfo.getSupplicantState());
+        assertEquals("DisconnectedState", getCurrentState().getName());
+    }
+
+
+    /**
+     * Verifies that WifiInfo is updated upon SUPPLICANT_STATE_CHANGE_EVENT.
+     */
+    @Test
+    public void testWifiInfoUpdatedUponSupplicantStateChangedEventWithWrongSsid() throws Exception {
+        // Connect to network with |TEST_BSSID_STR|, |sFreq|.
+        connect();
+
+        // Set the scan detail cache for roaming target.
+        when(mWifiConfigManager.getScanDetailCacheForNetwork(FRAMEWORK_NETWORK_ID))
+                .thenReturn(mScanDetailCache);
+        when(mScanDetailCache.getScanDetail(TEST_BSSID_STR1)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, TEST_BSSID_STR1, sFreq1));
+        when(mScanDetailCache.getScanResult(TEST_BSSID_STR1)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, TEST_BSSID_STR1, sFreq1).getScanResult());
+
+        // This simulates the behavior of roaming to network with |TEST_BSSID_STR1|, |sFreq1|.
+        // Send a SUPPLICANT_STATE_CHANGE_EVENT, verify WifiInfo is updated.
+        mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
+                new StateChangeResult(0, TEST_WIFI_SSID, TEST_BSSID_STR1,
+                        SupplicantState.COMPLETED));
+        mLooper.dispatchAll();
+
+        WifiInfo wifiInfo = mWifiInfo;
+        assertEquals(TEST_BSSID_STR1, wifiInfo.getBSSID());
+        assertEquals(sFreq1, wifiInfo.getFrequency());
+        assertEquals(SupplicantState.COMPLETED, wifiInfo.getSupplicantState());
+
+        // Send state change event with wrong ssid.
+        mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
+                new StateChangeResult(0, TEST_WIFI_SSID1, TEST_BSSID_STR,
+                        SupplicantState.DISCONNECTED));
+        mLooper.dispatchAll();
+
+        wifiInfo = mWifiInfo;
+        assertNull(wifiInfo.getBSSID());
+        assertEquals(WifiManager.UNKNOWN_SSID, wifiInfo.getSSID());
+        assertEquals(WifiConfiguration.INVALID_NETWORK_ID, wifiInfo.getNetworkId());
+        assertEquals(SupplicantState.DISCONNECTED, wifiInfo.getSupplicantState());
+        assertEquals("DisconnectedState", getCurrentState().getName());
     }
 
     /**
@@ -2321,26 +2547,28 @@
      */
     @Test
     public void testWifiInfoUpdatedUponAssociatedBSSIDEvent() throws Exception {
-        // Connect to network with |sBSSID|, |sFreq|.
+        // Connect to network with |TEST_BSSID_STR|, |sFreq|.
         connect();
 
         // Set the scan detail cache for roaming target.
         when(mWifiConfigManager.getScanDetailCacheForNetwork(FRAMEWORK_NETWORK_ID))
                 .thenReturn(mScanDetailCache);
-        when(mScanDetailCache.getScanDetail(sBSSID1)).thenReturn(
-                getGoogleGuestScanDetail(TEST_RSSI, sBSSID1, sFreq1));
-        when(mScanDetailCache.getScanResult(sBSSID1)).thenReturn(
-                getGoogleGuestScanDetail(TEST_RSSI, sBSSID1, sFreq1).getScanResult());
+        when(mScanDetailCache.getScanDetail(TEST_BSSID_STR1)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, TEST_BSSID_STR1, sFreq1));
+        when(mScanDetailCache.getScanResult(TEST_BSSID_STR1)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, TEST_BSSID_STR1, sFreq1).getScanResult());
 
-        // This simulates the behavior of roaming to network with |sBSSID1|, |sFreq1|.
+        // This simulates the behavior of roaming to network with |TEST_BSSID_STR1|, |sFreq1|.
         // Send a CMD_ASSOCIATED_BSSID, verify WifiInfo is updated.
-        mCmi.sendMessage(WifiMonitor.ASSOCIATED_BSSID_EVENT, 0, 0, sBSSID1);
+        mCmi.sendMessage(WifiMonitor.ASSOCIATED_BSSID_EVENT, 0, 0, TEST_BSSID_STR1);
         mLooper.dispatchAll();
 
-        WifiInfo wifiInfo = mCmi.getWifiInfo();
-        assertEquals(sBSSID1, wifiInfo.getBSSID());
+        WifiInfo wifiInfo = mWifiInfo;
+        assertEquals(TEST_BSSID_STR1, wifiInfo.getBSSID());
         assertEquals(sFreq1, wifiInfo.getFrequency());
         assertEquals(SupplicantState.COMPLETED, wifiInfo.getSupplicantState());
+        verify(mContext, times(2)).sendStickyBroadcastAsUser(
+                argThat(new NetworkStateChangedIntentMatcher(CONNECTED)), any());
     }
 
     /**
@@ -2351,74 +2579,73 @@
      * WifiConnectivityManager does not attempt any new Connections, freezing wifi.
      */
     @Test
-    public void testWifiInfoCleanedUpEnteringExitingConnectModeState() throws Exception {
-        InOrder inOrder = inOrder(mWifiConnectivityManager, mWifiNetworkFactory);
-        InOrder inOrderSarMgr = inOrder(mSarManager);
+    public void testWifiInfoCleanedUpEnteringExitingConnectableState() throws Exception {
         InOrder inOrderMetrics = inOrder(mWifiMetrics);
         Log.i(TAG, mCmi.getCurrentState().getName());
         String initialBSSID = "aa:bb:cc:dd:ee:ff";
-        WifiInfo wifiInfo = mCmi.getWifiInfo();
+        WifiInfo wifiInfo = mWifiInfo;
         wifiInfo.setBSSID(initialBSSID);
 
+        // reset mWifiNative since initializeCmi() was called in setup()
+        resetWifiNative();
+
         // Set CMI to CONNECT_MODE and verify state, and wifi enabled in ConnectivityManager
-        startSupplicantAndDispatchMessages();
-        assertEquals(ClientModeImpl.CONNECT_MODE, mCmi.getOperationalModeForTest());
-        assertEquals(WifiManager.WIFI_STATE_ENABLED, mCmi.syncGetWifiState());
-        inOrder.verify(mWifiConnectivityManager).setWifiEnabled(eq(true));
-        inOrder.verify(mWifiNetworkFactory).setWifiState(eq(true));
+        initializeCmi();
         inOrderMetrics.verify(mWifiMetrics)
-                .setWifiState(WifiMetricsProto.WifiLog.WIFI_DISCONNECTED);
-        inOrderMetrics.verify(mWifiMetrics).logStaEvent(StaEvent.TYPE_WIFI_ENABLED);
+                .setWifiState(WIFI_IFACE_NAME, WifiMetricsProto.WifiLog.WIFI_DISCONNECTED);
+        inOrderMetrics.verify(mWifiMetrics)
+                .logStaEvent(WIFI_IFACE_NAME, StaEvent.TYPE_WIFI_ENABLED);
         assertNull(wifiInfo.getBSSID());
 
         // Send a SUPPLICANT_STATE_CHANGE_EVENT, verify WifiInfo is updated
-        mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
-                new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.COMPLETED));
-        mLooper.dispatchAll();
-        assertEquals(sBSSID, wifiInfo.getBSSID());
+        connect();
+        assertEquals(TEST_BSSID_STR, wifiInfo.getBSSID());
         assertEquals(SupplicantState.COMPLETED, wifiInfo.getSupplicantState());
 
         // Set CMI to DISABLED_MODE, verify state and wifi disabled in ConnectivityManager, and
         // WifiInfo is reset() and state set to DISCONNECTED
-        mCmi.setWifiStateForApiCalls(WifiManager.WIFI_STATE_DISABLED);
-        mCmi.setOperationalMode(ClientModeImpl.DISABLED_MODE, null);
+        mCmi.stop();
         mLooper.dispatchAll();
 
-        assertEquals(ClientModeImpl.DISABLED_MODE, mCmi.getOperationalModeForTest());
-        assertEquals("DefaultState", getCurrentState().getName());
-        assertEquals(WifiManager.WIFI_STATE_DISABLED, mCmi.syncGetWifiState());
-        inOrder.verify(mWifiConnectivityManager).setWifiEnabled(eq(false));
-        inOrder.verify(mWifiNetworkFactory).setWifiState(eq(false));
-        inOrderMetrics.verify(mWifiMetrics).setWifiState(WifiMetricsProto.WifiLog.WIFI_DISABLED);
-        inOrderMetrics.verify(mWifiMetrics).logStaEvent(StaEvent.TYPE_WIFI_DISABLED);
+        inOrderMetrics.verify(mWifiMetrics).setWifiState(WIFI_IFACE_NAME,
+                WifiMetricsProto.WifiLog.WIFI_DISABLED);
+        inOrderMetrics.verify(mWifiMetrics)
+                .logStaEvent(WIFI_IFACE_NAME, StaEvent.TYPE_WIFI_DISABLED);
         assertNull(wifiInfo.getBSSID());
         assertEquals(SupplicantState.DISCONNECTED, wifiInfo.getSupplicantState());
-        verify(mPasspointManager).clearAnqpRequestsAndFlushCache();
+    }
+
+    @Test
+    public void testWifiInfoCleanedUpEnteringExitingConnectableState2() throws Exception {
+        String initialBSSID = "aa:bb:cc:dd:ee:ff";
+        InOrder inOrderMetrics = inOrder(mWifiMetrics);
 
         // Send a SUPPLICANT_STATE_CHANGE_EVENT, verify WifiInfo is not updated
         mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
-                new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.COMPLETED));
+                new StateChangeResult(0, TEST_WIFI_SSID, TEST_BSSID_STR,
+                        SupplicantState.COMPLETED));
         mLooper.dispatchAll();
-        assertNull(wifiInfo.getBSSID());
-        assertEquals(SupplicantState.DISCONNECTED, wifiInfo.getSupplicantState());
+        assertNull(mWifiInfo.getBSSID());
+        assertEquals(SupplicantState.DISCONNECTED, mWifiInfo.getSupplicantState());
+    }
+
+    @Test
+    public void testWifiInfoCleanedUpEnteringExitingConnectableState3() throws Exception {
+        String initialBSSID = "aa:bb:cc:dd:ee:ff";
+        InOrder inOrderMetrics = inOrder(mWifiMetrics);
 
         // Set the bssid to something, so we can verify it is cleared (just in case)
-        wifiInfo.setBSSID(initialBSSID);
+        mWifiInfo.setBSSID(initialBSSID);
 
-        // Set CMI to CONNECT_MODE and verify state, and wifi enabled in ConnectivityManager,
-        // and WifiInfo has been reset
-        startSupplicantAndDispatchMessages();
+        initializeCmi();
 
-        assertEquals(ClientModeImpl.CONNECT_MODE, mCmi.getOperationalModeForTest());
-        assertEquals(WifiManager.WIFI_STATE_ENABLED, mCmi.syncGetWifiState());
-        inOrder.verify(mWifiConnectivityManager).setWifiEnabled(eq(true));
-        inOrder.verify(mWifiNetworkFactory).setWifiState(eq(true));
         inOrderMetrics.verify(mWifiMetrics)
-                .setWifiState(WifiMetricsProto.WifiLog.WIFI_DISCONNECTED);
-        inOrderMetrics.verify(mWifiMetrics).logStaEvent(StaEvent.TYPE_WIFI_ENABLED);
+                .setWifiState(WIFI_IFACE_NAME, WifiMetricsProto.WifiLog.WIFI_DISCONNECTED);
+        inOrderMetrics.verify(mWifiMetrics)
+                .logStaEvent(WIFI_IFACE_NAME, StaEvent.TYPE_WIFI_ENABLED);
         assertEquals("DisconnectedState", getCurrentState().getName());
-        assertEquals(SupplicantState.DISCONNECTED, wifiInfo.getSupplicantState());
-        assertNull(wifiInfo.getBSSID());
+        assertEquals(SupplicantState.DISCONNECTED, mWifiInfo.getSupplicantState());
+        assertNull(mWifiInfo.getBSSID());
     }
 
     /**
@@ -2428,13 +2655,15 @@
      */
     @Test
     public void testConnectedIdsAreVisibleFromSystemServer() throws Exception {
-        WifiInfo wifiInfo = mCmi.getWifiInfo();
+        WifiInfo wifiInfo = mWifiInfo;
         // Get into a connected state, with known BSSID and SSID
         connect();
-        assertEquals(sBSSID, wifiInfo.getBSSID());
-        assertEquals(sWifiSsid, wifiInfo.getWifiSsid());
+        assertEquals(TEST_BSSID_STR, wifiInfo.getBSSID());
+        assertEquals(TEST_WIFI_SSID, wifiInfo.getWifiSsid());
 
+        mLooper.startAutoDispatch();
         WifiInfo connectionInfo = mCmi.syncRequestConnectionInfo();
+        mLooper.stopAutoDispatch();
 
         assertEquals(wifiInfo.getSSID(), connectionInfo.getSSID());
         assertEquals(wifiInfo.getBSSID(), connectionInfo.getBSSID());
@@ -2447,10 +2676,10 @@
      */
     @Test
     public void testReconnectCommandWhenDisconnected() throws Exception {
-        // Connect to network with |sBSSID|, |sFreq|, and then disconnect.
+        // Connect to network with |TEST_BSSID_STR|, |sFreq|, and then disconnect.
         disconnect();
 
-        mCmi.reconnectCommand(ClientModeImpl.WIFI_WORK_SOURCE);
+        mCmi.reconnect(ClientModeImpl.WIFI_WORK_SOURCE);
         mLooper.dispatchAll();
         verify(mWifiConnectivityManager).forceConnectivityScan(ClientModeImpl.WIFI_WORK_SOURCE);
     }
@@ -2461,27 +2690,16 @@
      */
     @Test
     public void testReconnectCommandWhenConnected() throws Exception {
-        // Connect to network with |sBSSID|, |sFreq|.
+        // Connect to network with |TEST_BSSID_STR|, |sFreq|.
         connect();
 
-        mCmi.reconnectCommand(ClientModeImpl.WIFI_WORK_SOURCE);
+        mCmi.reconnect(ClientModeImpl.WIFI_WORK_SOURCE);
         mLooper.dispatchAll();
         verify(mWifiConnectivityManager, never())
                 .forceConnectivityScan(ClientModeImpl.WIFI_WORK_SOURCE);
     }
 
     /**
-     * Adds the network without putting ClientModeImpl into ConnectMode.
-     */
-    @Test
-    public void addNetworkInDefaultState() throws Exception {
-        // We should not be in initial state now.
-        assertTrue("DefaultState".equals(getCurrentState().getName()));
-        initializeMocksForAddedNetwork(false);
-        verify(mWifiConnectivityManager, never()).setUserConnectChoice(eq(0));
-    }
-
-    /**
      * Verifies that ClientModeImpl sets and unsets appropriate 'RecentFailureReason' values
      * on a WifiConfiguration when it fails association, authentication, or successfully connects
      */
@@ -2491,31 +2709,45 @@
         initializeAndAddNetworkAndVerifySuccess();
         // Trigger a connection to this (CMD_START_CONNECT will actually fail, but it sets up
         // targetNetworkId state)
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
         mLooper.dispatchAll();
         // Simulate an ASSOCIATION_REJECTION_EVENT, due to the AP being busy
-        mCmi.sendMessage(WifiMonitor.ASSOCIATION_REJECTION_EVENT, 0,
-                ISupplicantStaIfaceCallback.StatusCode.AP_UNABLE_TO_HANDLE_NEW_STA, sBSSID);
+        mCmi.sendMessage(WifiMonitor.ASSOCIATION_REJECTION_EVENT,
+                new AssocRejectEventInfo(TEST_SSID, TEST_BSSID_STR,
+                        ISupplicantStaIfaceCallback.StatusCode.AP_UNABLE_TO_HANDLE_NEW_STA,
+                        false));
         mLooper.dispatchAll();
         verify(mWifiConfigManager).setRecentFailureAssociationStatus(eq(0),
                 eq(WifiConfiguration.RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA));
         assertEquals("DisconnectedState", getCurrentState().getName());
 
         // Simulate an AUTHENTICATION_FAILURE_EVENT, which should clear the ExtraFailureReason
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
+        reset(mWifiConfigManager);
+        initializeAndAddNetworkAndVerifySuccess();
+        // Trigger a connection to this (CMD_START_CONNECT will actually fail, but it sets up
+        // targetNetworkId state)
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
         mLooper.dispatchAll();
         mCmi.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT, 0, 0, null);
+        DisconnectEventInfo disconnectEventInfo =
+                new DisconnectEventInfo(TEST_SSID, TEST_BSSID_STR, 0, false);
+        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, disconnectEventInfo);
         mLooper.dispatchAll();
-        verify(mWifiConfigManager, times(1)).clearRecentFailureReason(eq(0));
-        verify(mWifiConfigManager, times(1)).setRecentFailureAssociationStatus(anyInt(), anyInt());
+        verify(mWifiConfigManager).clearRecentFailureReason(eq(0));
+        verify(mWifiConfigManager, never()).setRecentFailureAssociationStatus(anyInt(), anyInt());
 
         // Simulate a NETWORK_CONNECTION_EVENT which should clear the ExtraFailureReason
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
+        reset(mWifiConfigManager);
+        initializeAndAddNetworkAndVerifySuccess();
+        // Trigger a connection to this (CMD_START_CONNECT will actually fail, but it sets up
+        // targetNetworkId state)
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
         mLooper.dispatchAll();
-        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, null);
+        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT,
+                new NetworkConnectionEventInfo(0, TEST_WIFI_SSID, null, false));
         mLooper.dispatchAll();
-        verify(mWifiConfigManager, times(2)).clearRecentFailureReason(eq(0));
-        verify(mWifiConfigManager, times(1)).setRecentFailureAssociationStatus(anyInt(), anyInt());
+        verify(mWifiConfigManager).clearRecentFailureReason(eq(0));
+        verify(mWifiConfigManager, never()).setRecentFailureAssociationStatus(anyInt(), anyInt());
     }
 
     private WifiConfiguration makeLastSelectedWifiConfiguration(int lastSelectedNetworkId,
@@ -2534,41 +2766,41 @@
 
     /**
      * Test that the helper method
-     * {@link ClientModeImpl#shouldEvaluateWhetherToSendExplicitlySelected(WifiConfiguration)}
+     * {@link ClientModeImpl#isRecentlySelectedByTheUser(WifiConfiguration)}
      * returns true when we connect to the last selected network before expiration of
      * {@link ClientModeImpl#LAST_SELECTED_NETWORK_EXPIRATION_AGE_MILLIS}.
      */
     @Test
-    public void testShouldEvaluateWhetherToSendExplicitlySelected_SameNetworkNotExpired() {
+    public void testIsRecentlySelectedByTheUser_SameNetworkNotExpired() {
         WifiConfiguration currentConfig = makeLastSelectedWifiConfiguration(5,
                 ClientModeImpl.LAST_SELECTED_NETWORK_EXPIRATION_AGE_MILLIS - 1);
-        assertTrue(mCmi.shouldEvaluateWhetherToSendExplicitlySelected(currentConfig));
+        assertTrue(mCmi.isRecentlySelectedByTheUser(currentConfig));
     }
 
     /**
      * Test that the helper method
-     * {@link ClientModeImpl#shouldEvaluateWhetherToSendExplicitlySelected(WifiConfiguration)}
+     * {@link ClientModeImpl#isRecentlySelectedByTheUser(WifiConfiguration)}
      * returns false when we connect to the last selected network after expiration of
      * {@link ClientModeImpl#LAST_SELECTED_NETWORK_EXPIRATION_AGE_MILLIS}.
      */
     @Test
-    public void testShouldEvaluateWhetherToSendExplicitlySelected_SameNetworkExpired() {
+    public void testIsRecentlySelectedByTheUser_SameNetworkExpired() {
         WifiConfiguration currentConfig = makeLastSelectedWifiConfiguration(5,
                 ClientModeImpl.LAST_SELECTED_NETWORK_EXPIRATION_AGE_MILLIS + 1);
-        assertFalse(mCmi.shouldEvaluateWhetherToSendExplicitlySelected(currentConfig));
+        assertFalse(mCmi.isRecentlySelectedByTheUser(currentConfig));
     }
 
     /**
      * Test that the helper method
-     * {@link ClientModeImpl#shouldEvaluateWhetherToSendExplicitlySelected(WifiConfiguration)}
+     * {@link ClientModeImpl#isRecentlySelectedByTheUser(WifiConfiguration)}
      * returns false when we connect to a different network to the last selected network.
      */
     @Test
-    public void testShouldEvaluateWhetherToSendExplicitlySelected_DifferentNetwork() {
+    public void testIsRecentlySelectedByTheUser_DifferentNetwork() {
         WifiConfiguration currentConfig = makeLastSelectedWifiConfiguration(5,
                 ClientModeImpl.LAST_SELECTED_NETWORK_EXPIRATION_AGE_MILLIS - 1);
         currentConfig.networkId = 4;
-        assertFalse(mCmi.shouldEvaluateWhetherToSendExplicitlySelected(currentConfig));
+        assertFalse(mCmi.isRecentlySelectedByTheUser(currentConfig));
     }
 
     private void expectRegisterNetworkAgent(Consumer<NetworkAgentConfig> configChecker,
@@ -2576,51 +2808,28 @@
         // Expects that the code calls registerNetworkAgent and provides a way for the test to
         // verify the messages sent through the NetworkAgent to ConnectivityService.
         // We cannot just use a mock object here because mWifiNetworkAgent is private to CMI.
-        // TODO (b/134538181): consider exposing WifiNetworkAgent and using mocks.
-        ArgumentCaptor<INetworkAgent> naCaptor = ArgumentCaptor.forClass(INetworkAgent.class);
         ArgumentCaptor<NetworkAgentConfig> configCaptor =
                 ArgumentCaptor.forClass(NetworkAgentConfig.class);
         ArgumentCaptor<NetworkCapabilities> networkCapabilitiesCaptor =
                 ArgumentCaptor.forClass(NetworkCapabilities.class);
-        verify(mConnectivityManager).registerNetworkAgent(naCaptor.capture(),
-                any(NetworkInfo.class), any(LinkProperties.class),
-                networkCapabilitiesCaptor.capture(),
-                any(), configCaptor.capture(), anyInt());
 
-        mNetworkAgentBinder = naCaptor.getValue();
+        verify(mWifiInjector).makeWifiNetworkAgent(
+                networkCapabilitiesCaptor.capture(),
+                any(),
+                configCaptor.capture(),
+                any(),
+                mWifiNetworkAgentCallbackCaptor.capture());
+
         configChecker.accept(configCaptor.getValue());
         networkCapabilitiesChecker.accept(networkCapabilitiesCaptor.getValue());
-        replyNetworkAgentRegistered(mNetworkAgentBinder);
-    }
-
-    private void replyNetworkAgentRegistered(INetworkAgent agent) {
-        new Handler(mLooper.getLooper()).post(() -> {
-            try {
-                agent.onRegistered(mNetworkAgentRegistry);
-            } catch (RemoteException e) {
-                throw new AssertionError("Error connecting NetworkAgent", e);
-            }
-        });
-        mLooper.dispatchAll();
-    }
-
-    private void expectUnregisterNetworkAgent() throws Exception {
-        // We cannot just use a mock object here because mWifiNetworkAgent is private to CMI.
-        // TODO (b/134538181): consider exposing WifiNetworkAgent and using mocks.
-        final ArgumentCaptor<NetworkInfo> captor = ArgumentCaptor.forClass(NetworkInfo.class);
-        mLooper.dispatchAll();
-        verify(mNetworkAgentRegistry).sendNetworkInfo(captor.capture());
-        assertEquals(NetworkInfo.DetailedState.DISCONNECTED, captor.getValue().getDetailedState());
     }
 
     private void expectNetworkAgentUpdateCapabilities(
             Consumer<NetworkCapabilities> networkCapabilitiesChecker) throws Exception {
-        // We cannot just use a mock object here because mWifiNetworkAgent is private to CMI.
-        // TODO (b/134538181): consider exposing WifiNetworkAgent and using mocks.
         ArgumentCaptor<NetworkCapabilities> captor = ArgumentCaptor.forClass(
                 NetworkCapabilities.class);
         mLooper.dispatchAll();
-        verify(mNetworkAgentRegistry).sendNetworkCapabilities(captor.capture());
+        verify(mWifiNetworkAgent).sendNetworkCapabilitiesAndCache(captor.capture());
         networkCapabilitiesChecker.accept(captor.getValue());
     }
 
@@ -2645,6 +2854,29 @@
     }
 
     /**
+     * Verify that when a network is explicitly selected, has role SECONDARY_TRANSIENT, but
+     * noInternetAccessExpected is false, the {@link NetworkAgentConfig} contains the right values
+     * of explicitlySelected, acceptUnvalidated and acceptPartialConnectivity.
+     */
+    @Test
+    public void testExplicitlySelected_secondaryTransient_expectNotExplicitlySelected()
+            throws Exception {
+        // Network is explicitly selected.
+        WifiConfiguration config = makeLastSelectedWifiConfiguration(FRAMEWORK_NETWORK_ID,
+                ClientModeImpl.LAST_SELECTED_NETWORK_EXPIRATION_AGE_MILLIS - 1);
+        mConnectedNetwork.noInternetAccessExpected = false;
+
+        when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_TRANSIENT);
+
+        connect();
+        expectRegisterNetworkAgent((agentConfig) -> {
+            assertFalse(agentConfig.explicitlySelected);
+            assertFalse(agentConfig.acceptUnvalidated);
+            assertFalse(agentConfig.acceptPartialConnectivity);
+        }, (cap) -> { });
+    }
+
+    /**
      * Verify that when a network is not explicitly selected, but noInternetAccessExpected is true,
      * the {@link NetworkAgentConfig} contains the right values of explicitlySelected,
      * acceptUnvalidated and acceptPartialConnectivity.
@@ -2685,38 +2917,20 @@
     }
 
     /**
-     * Verify that CMI dump includes WakeupController.
-     */
-    @Test
-    public void testDumpShouldDumpWakeupController() {
-        ByteArrayOutputStream stream = new ByteArrayOutputStream();
-        PrintWriter writer = new PrintWriter(stream);
-        mCmi.dump(null, writer, null);
-        verify(mWakeupController).dump(null, writer, null);
-    }
-
-    @Test
-    public void takeBugReportCallsWifiDiagnostics() {
-        mCmi.takeBugReport(anyString(), anyString());
-        verify(mWifiDiagnostics).takeBugReport(anyString(), anyString());
-    }
-
-    /**
      * Verify that Rssi Monitoring is started and the callback registered after connecting.
      */
     @Test
     public void verifyRssiMonitoringCallbackIsRegistered() throws Exception {
         // Simulate the first connection.
         connect();
-        ArgumentCaptor<INetworkAgent> agentCaptor = ArgumentCaptor.forClass(INetworkAgent.class);
-        verify(mConnectivityManager).registerNetworkAgent(agentCaptor.capture(),
-                any(NetworkInfo.class), any(LinkProperties.class), any(NetworkCapabilities.class),
-                any(), any(NetworkAgentConfig.class), anyInt());
+
+        verify(mWifiInjector).makeWifiNetworkAgent(any(), any(), any(), any(),
+                mWifiNetworkAgentCallbackCaptor.capture());
 
         ArrayList<Integer> thresholdsArray = new ArrayList<>();
         thresholdsArray.add(RSSI_THRESHOLD_MAX);
         thresholdsArray.add(RSSI_THRESHOLD_MIN);
-        agentCaptor.getValue().onSignalStrengthThresholdsUpdated(
+        mWifiNetworkAgentCallbackCaptor.getValue().onSignalStrengthThresholdsUpdated(
                 thresholdsArray.stream().mapToInt(Integer::intValue).toArray());
         mLooper.dispatchAll();
 
@@ -2728,7 +2942,7 @@
         // breach below min
         rssiEventHandlerCaptor.getValue().onRssiThresholdBreached(RSSI_THRESHOLD_BREACH_MIN);
         mLooper.dispatchAll();
-        WifiInfo wifiInfo = mCmi.getWifiInfo();
+        WifiInfo wifiInfo = mWifiInfo;
         assertEquals(RSSI_THRESHOLD_BREACH_MIN, wifiInfo.getRssi());
 
         // breach above max
@@ -2756,7 +2970,7 @@
         mLooper.dispatchAll();
         when(mClock.getWallClockMillis()).thenReturn(startMillis + 3333);
         mLooper.dispatchAll();
-        WifiInfo wifiInfo = mCmi.getWifiInfo();
+        WifiInfo wifiInfo = mWifiInfo;
         assertEquals(llStats.txmpdu_be, wifiInfo.txSuccess);
         assertEquals(llStats.rxmpdu_bk, wifiInfo.rxSuccess);
         assertEquals(signalPollResult.currentRssiDbm, wifiInfo.getRssi());
@@ -2764,17 +2978,76 @@
         assertEquals(signalPollResult.txBitrateMbps, wifiInfo.getTxLinkSpeedMbps());
         assertEquals(signalPollResult.rxBitrateMbps, wifiInfo.getRxLinkSpeedMbps());
         assertEquals(sFreq, wifiInfo.getFrequency());
-        verify(mWifiDataStall, atLeastOnce()).getTxThroughputKbps();
-        verify(mWifiDataStall, atLeastOnce()).getRxThroughputKbps();
+        verify(mPerNetwork, atLeastOnce()).getTxLinkBandwidthKbps();
+        verify(mPerNetwork, atLeastOnce()).getRxLinkBandwidthKbps();
         verify(mWifiScoreCard).noteSignalPoll(any());
     }
 
     /**
+     * Verify link bandwidth update in connected mode
+     */
+    @Test
+    public void verifyConnectedModeNetworkCapabilitiesBandwidthUpdate() throws Exception {
+        when(mPerNetwork.getTxLinkBandwidthKbps()).thenReturn(40_000);
+        when(mPerNetwork.getRxLinkBandwidthKbps()).thenReturn(50_000);
+        when(mWifiNetworkFactory.getSpecificNetworkRequestUidAndPackageName(any(), any()))
+                .thenReturn(Pair.create(Process.INVALID_UID, ""));
+        // Simulate the first connection.
+        connectWithValidInitRssi(-42);
+
+        // NetworkCapabilities should be always updated after the connection
+        ArgumentCaptor<NetworkCapabilities> networkCapabilitiesCaptor =
+                ArgumentCaptor.forClass(NetworkCapabilities.class);
+        verify(mWifiInjector).makeWifiNetworkAgent(
+                networkCapabilitiesCaptor.capture(), any(), any(), any(), any());
+        NetworkCapabilities networkCapabilities = networkCapabilitiesCaptor.getValue();
+        assertNotNull(networkCapabilities);
+        assertEquals(-42, mWifiInfo.getRssi());
+        assertEquals(40_000, networkCapabilities.getLinkUpstreamBandwidthKbps());
+        assertEquals(50_000, networkCapabilities.getLinkDownstreamBandwidthKbps());
+        verify(mCmi.mNetworkAgent, times(2))
+                .sendNetworkCapabilitiesAndCache(networkCapabilitiesCaptor.capture());
+
+        // Enable RSSI polling
+        final long startMillis = 1_500_000_000_100L;
+        WifiLinkLayerStats llStats = new WifiLinkLayerStats();
+        WifiNl80211Manager.SignalPollResult signalPollResult =
+                new WifiNl80211Manager.SignalPollResult(-42, 65, 54, sFreq);
+        when(mWifiNative.getWifiLinkLayerStats(any())).thenReturn(llStats);
+        when(mWifiNative.signalPoll(any())).thenReturn(signalPollResult);
+        when(mClock.getWallClockMillis()).thenReturn(startMillis + 0);
+        when(mPerNetwork.getTxLinkBandwidthKbps()).thenReturn(82_000);
+        when(mPerNetwork.getRxLinkBandwidthKbps()).thenReturn(92_000);
+        mCmi.enableRssiPolling(true);
+        mLooper.dispatchAll();
+        when(mClock.getWallClockMillis()).thenReturn(startMillis + 3333);
+        mLooper.dispatchAll();
+
+        // NetworkCapabilities should be updated after a big change of bandwidth
+        verify(mCmi.mNetworkAgent, times(3))
+                .sendNetworkCapabilitiesAndCache(networkCapabilitiesCaptor.capture());
+        networkCapabilities = networkCapabilitiesCaptor.getValue();
+        assertEquals(82_000, networkCapabilities.getLinkUpstreamBandwidthKbps());
+        assertEquals(92_000, networkCapabilities.getLinkDownstreamBandwidthKbps());
+
+        // No update after a small change of bandwidth
+        when(mPerNetwork.getTxLinkBandwidthKbps()).thenReturn(72_000);
+        when(mPerNetwork.getRxLinkBandwidthKbps()).thenReturn(82_000);
+        when(mClock.getWallClockMillis()).thenReturn(startMillis + 3333);
+        mLooper.dispatchAll();
+        verify(mCmi.mNetworkAgent, times(3))
+                .sendNetworkCapabilitiesAndCache(networkCapabilitiesCaptor.capture());
+        networkCapabilities = networkCapabilitiesCaptor.getValue();
+        assertEquals(82_000, networkCapabilities.getLinkUpstreamBandwidthKbps());
+        assertEquals(92_000, networkCapabilities.getLinkDownstreamBandwidthKbps());
+    }
+
+    /**
      * Verify RSSI polling with verbose logging
      */
     @Test
     public void verifyConnectedModeRssiPollingWithVerboseLogging() throws Exception {
-        mCmi.enableVerboseLogging(1);
+        mCmi.enableVerboseLogging(true);
         verifyConnectedModeRssiPolling();
     }
 
@@ -2784,7 +3057,6 @@
      */
     @Test
     public void verifyMcastLockManagerFilterControllerCallsUpdateIpClient() throws Exception {
-        loadComponentsInStaMode();
         reset(mIpClient);
         WifiMulticastLockManager.FilterController filterController =
                 mCmi.getMcastLockManagerFilterController();
@@ -2798,29 +3070,31 @@
      * Verifies that when
      * 1. Global feature support flag is set to false
      * 2. connected MAC randomization is on and
-     * 3. macRandomizationSetting of the WifiConfiguration is RANDOMIZATION_PERSISTENT and
+     * 3. macRandomizationSetting of the WifiConfiguration is RANDOMIZATION_AUTO and
      * 4. randomized MAC for the network to connect to is different from the current MAC.
      *
      * The factory MAC address is used for the connection, and no attempt is made to change it.
      */
     @Test
     public void testConnectedMacRandomizationNotSupported() throws Exception {
-        mResources.setBoolean(R.bool.config_wifi_connected_mac_randomization_supported, false);
+        // reset mWifiNative since initializeCmi() was called in setup()
+        resetWifiNative();
+
+        when(mWifiGlobals.isConnectedMacRandomizationEnabled()).thenReturn(false);
         initializeCmi();
         initializeAndAddNetworkAndVerifySuccess();
-        assertEquals(ClientModeImpl.CONNECT_MODE, mCmi.getOperationalModeForTest());
-        assertEquals(WifiManager.WIFI_STATE_ENABLED, mCmi.syncGetWifiState());
 
         connect();
-        assertEquals(TEST_GLOBAL_MAC_ADDRESS.toString(), mCmi.getWifiInfo().getMacAddress());
-        verify(mWifiNative, never()).setMacAddress(any(), any());
-        verify(mWifiNative, never()).getFactoryMacAddress(any());
+        assertEquals(TEST_GLOBAL_MAC_ADDRESS.toString(), mWifiInfo.getMacAddress());
+        verify(mWifiNative, never()).setStaMacAddress(any(), any());
+        // try to retrieve factory MAC address (once at bootup, once for this connection)
+        verify(mSettingsConfigStore, times(2)).get(any());
     }
 
     /**
      * Verifies that when
      * 1. connected MAC randomization is on and
-     * 2. macRandomizationSetting of the WifiConfiguration is RANDOMIZATION_PERSISTENT and
+     * 2. macRandomizationSetting of the WifiConfiguration is RANDOMIZATION_AUTO and
      * 3. randomized MAC for the network to connect to is different from the current MAC.
      *
      * Then the current MAC gets set to the randomized MAC when CMD_START_CONNECT executes.
@@ -2829,20 +3103,18 @@
     public void testConnectedMacRandomizationRandomizationPersistentDifferentMac()
             throws Exception {
         initializeAndAddNetworkAndVerifySuccess();
-        assertEquals(ClientModeImpl.CONNECT_MODE, mCmi.getOperationalModeForTest());
-        assertEquals(WifiManager.WIFI_STATE_ENABLED, mCmi.syncGetWifiState());
 
         connect();
-        verify(mWifiNative).setMacAddress(WIFI_IFACE_NAME, TEST_LOCAL_MAC_ADDRESS);
-        verify(mWifiMetrics)
-                .logStaEvent(eq(StaEvent.TYPE_MAC_CHANGE), any(WifiConfiguration.class));
-        assertEquals(TEST_LOCAL_MAC_ADDRESS.toString(), mCmi.getWifiInfo().getMacAddress());
+        verify(mWifiNative).setStaMacAddress(WIFI_IFACE_NAME, TEST_LOCAL_MAC_ADDRESS);
+        verify(mWifiMetrics).logStaEvent(
+                eq(WIFI_IFACE_NAME), eq(StaEvent.TYPE_MAC_CHANGE), any(WifiConfiguration.class));
+        assertEquals(TEST_LOCAL_MAC_ADDRESS.toString(), mWifiInfo.getMacAddress());
     }
 
     /**
      * Verifies that when
      * 1. connected MAC randomization is on and
-     * 2. macRandomizationSetting of the WifiConfiguration is RANDOMIZATION_PERSISTENT and
+     * 2. macRandomizationSetting of the WifiConfiguration is RANDOMIZATION_AUTO and
      * 3. randomized MAC for the network to connect to is same as the current MAC.
      *
      * Then MAC change should not occur when CMD_START_CONNECT executes.
@@ -2850,17 +3122,15 @@
     @Test
     public void testConnectedMacRandomizationRandomizationPersistentSameMac() throws Exception {
         initializeAndAddNetworkAndVerifySuccess();
-        assertEquals(ClientModeImpl.CONNECT_MODE, mCmi.getOperationalModeForTest());
-        assertEquals(WifiManager.WIFI_STATE_ENABLED, mCmi.syncGetWifiState());
 
         when(mWifiNative.getMacAddress(WIFI_IFACE_NAME))
                 .thenReturn(TEST_LOCAL_MAC_ADDRESS.toString());
 
         connect();
-        verify(mWifiNative, never()).setMacAddress(WIFI_IFACE_NAME, TEST_LOCAL_MAC_ADDRESS);
-        verify(mWifiMetrics, never())
-                .logStaEvent(eq(StaEvent.TYPE_MAC_CHANGE), any(WifiConfiguration.class));
-        assertEquals(TEST_LOCAL_MAC_ADDRESS.toString(), mCmi.getWifiInfo().getMacAddress());
+        verify(mWifiNative, never()).setStaMacAddress(WIFI_IFACE_NAME, TEST_LOCAL_MAC_ADDRESS);
+        verify(mWifiMetrics, never()).logStaEvent(
+                any(), eq(StaEvent.TYPE_MAC_CHANGE), any(WifiConfiguration.class));
+        assertEquals(TEST_LOCAL_MAC_ADDRESS.toString(), mWifiInfo.getMacAddress());
     }
 
     /**
@@ -2875,24 +3145,23 @@
     @Test
     public void testConnectedMacRandomizationRandomizationNoneDifferentMac() throws Exception {
         initializeAndAddNetworkAndVerifySuccess();
-        assertEquals(ClientModeImpl.CONNECT_MODE, mCmi.getOperationalModeForTest());
-        assertEquals(WifiManager.WIFI_STATE_ENABLED, mCmi.syncGetWifiState());
 
         when(mWifiNative.getMacAddress(WIFI_IFACE_NAME))
                 .thenReturn(TEST_LOCAL_MAC_ADDRESS.toString());
 
         WifiConfiguration config = new WifiConfiguration();
+        config.SSID = TEST_SSID;
         config.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_NONE;
         config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         when(mWifiConfigManager.getConfiguredNetworkWithoutMasking(0)).thenReturn(config);
 
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
         mLooper.dispatchAll();
 
-        verify(mWifiNative).setMacAddress(WIFI_IFACE_NAME, TEST_GLOBAL_MAC_ADDRESS);
-        verify(mWifiMetrics)
-                .logStaEvent(eq(StaEvent.TYPE_MAC_CHANGE), any(WifiConfiguration.class));
-        assertEquals(TEST_GLOBAL_MAC_ADDRESS.toString(), mCmi.getWifiInfo().getMacAddress());
+        verify(mWifiNative).setStaMacAddress(WIFI_IFACE_NAME, TEST_GLOBAL_MAC_ADDRESS);
+        verify(mWifiMetrics).logStaEvent(
+                eq(WIFI_IFACE_NAME), eq(StaEvent.TYPE_MAC_CHANGE), any(WifiConfiguration.class));
+        assertEquals(TEST_GLOBAL_MAC_ADDRESS.toString(), mWifiInfo.getMacAddress());
     }
 
     /**
@@ -2906,18 +3175,37 @@
     @Test
     public void testConnectedMacRandomizationRandomizationNoneSameMac() throws Exception {
         initializeAndAddNetworkAndVerifySuccess();
-        assertEquals(ClientModeImpl.CONNECT_MODE, mCmi.getOperationalModeForTest());
-        assertEquals(WifiManager.WIFI_STATE_ENABLED, mCmi.syncGetWifiState());
+
+        clearInvocations(mWifiNative, mSettingsConfigStore);
 
         WifiConfiguration config = new WifiConfiguration();
+        config.SSID = TEST_SSID;
         config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         config.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_NONE;
         when(mWifiConfigManager.getConfiguredNetworkWithoutMasking(0)).thenReturn(config);
 
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
         mLooper.dispatchAll();
 
-        assertEquals(TEST_GLOBAL_MAC_ADDRESS.toString(), mCmi.getWifiInfo().getMacAddress());
+        verify(mSettingsConfigStore).get(WIFI_STA_FACTORY_MAC_ADDRESS);
+        verify(mWifiNative, never()).getStaFactoryMacAddress(WIFI_IFACE_NAME);
+        verify(mSettingsConfigStore, never()).put(
+                WIFI_STA_FACTORY_MAC_ADDRESS, TEST_GLOBAL_MAC_ADDRESS.toString());
+
+        assertEquals(TEST_GLOBAL_MAC_ADDRESS.toString(), mWifiInfo.getMacAddress());
+
+        // Now disconnect & reconnect - should use the cached factory MAC address.
+        mCmi.disconnect();
+        mLooper.dispatchAll();
+
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
+        mLooper.dispatchAll();
+
+        verify(mSettingsConfigStore, times(2)).get(WIFI_STA_FACTORY_MAC_ADDRESS);
+        // No new call to retrieve & store factory MAC address.
+        verify(mWifiNative, never()).getStaFactoryMacAddress(WIFI_IFACE_NAME);
+        verify(mSettingsConfigStore, never()).put(
+                WIFI_STA_FACTORY_MAC_ADDRESS, TEST_GLOBAL_MAC_ADDRESS.toString());
     }
 
     /**
@@ -2930,17 +3218,20 @@
                 .thenReturn(TEST_LOCAL_MAC_ADDRESS.toString());
 
         connect();
-        assertEquals(TEST_LOCAL_MAC_ADDRESS.toString(), mCmi.getWifiInfo().getMacAddress());
+        assertEquals(TEST_LOCAL_MAC_ADDRESS.toString(), mWifiInfo.getMacAddress());
 
-        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, -1, 3, sBSSID);
+        DisconnectEventInfo disconnectEventInfo =
+                new DisconnectEventInfo(mConnectedNetwork.SSID, TEST_BSSID_STR, 0, false);
+        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, disconnectEventInfo);
         mLooper.dispatchAll();
         mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
-                new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.DISCONNECTED));
+                new StateChangeResult(0, WifiSsid.createFromAsciiEncoded(mConnectedNetwork.SSID),
+                        TEST_BSSID_STR, SupplicantState.DISCONNECTED));
         mLooper.dispatchAll();
 
         assertEquals("DisconnectedState", getCurrentState().getName());
-        assertEquals(WifiInfo.DEFAULT_MAC_ADDRESS, mCmi.getWifiInfo().getMacAddress());
-        assertFalse(mCmi.getWifiInfo().hasRealMacAddress());
+        assertEquals(WifiInfo.DEFAULT_MAC_ADDRESS, mWifiInfo.getMacAddress());
+        assertFalse(mWifiInfo.hasRealMacAddress());
     }
 
     /**
@@ -2949,20 +3240,20 @@
     @Test
     public void testDoNotSetMacWhenInvalid() throws Exception {
         initializeAndAddNetworkAndVerifySuccess();
-        assertEquals(ClientModeImpl.CONNECT_MODE, mCmi.getOperationalModeForTest());
-        assertEquals(WifiManager.WIFI_STATE_ENABLED, mCmi.syncGetWifiState());
 
         WifiConfiguration config = new WifiConfiguration();
+        config.SSID = TEST_SSID;
         config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
-        config.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_PERSISTENT;
+        config.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_AUTO;
         config.setRandomizedMacAddress(MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS));
         when(mWifiConfigManager.getConfiguredNetworkWithoutMasking(0)).thenReturn(config);
 
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
         mLooper.dispatchAll();
 
-        // setMacAddress is invoked once when ClientModeImpl starts to prevent leak of factory MAC.
-        verify(mWifiNative).setMacAddress(eq(WIFI_IFACE_NAME), any(MacAddress.class));
+        // setStaMacAddress is invoked once when ClientModeImpl starts to prevent leak of factory
+        // MAC.
+        verify(mWifiNative).setStaMacAddress(eq(WIFI_IFACE_NAME), any(MacAddress.class));
     }
 
     /**
@@ -2973,11 +3264,9 @@
     public void testMacRandomizationWifiNativeReturningNull() throws Exception {
         when(mWifiNative.getMacAddress(anyString())).thenReturn(null);
         initializeAndAddNetworkAndVerifySuccess();
-        assertEquals(ClientModeImpl.CONNECT_MODE, mCmi.getOperationalModeForTest());
-        assertEquals(WifiManager.WIFI_STATE_ENABLED, mCmi.syncGetWifiState());
 
         connect();
-        verify(mWifiNative).setMacAddress(WIFI_IFACE_NAME, TEST_LOCAL_MAC_ADDRESS);
+        verify(mWifiNative).setStaMacAddress(WIFI_IFACE_NAME, TEST_LOCAL_MAC_ADDRESS);
     }
 
     /**
@@ -2990,12 +3279,12 @@
         when(mWifiConfigManager.isInFlakyRandomizationSsidHotlist(anyInt())).thenReturn(true);
         // Setup CONNECT_MODE & a WifiConfiguration
         initializeAndAddNetworkAndVerifySuccess();
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, FRAMEWORK_NETWORK_ID, 0, sBSSID);
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, FRAMEWORK_NETWORK_ID, 0, TEST_BSSID_STR);
         mCmi.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT,
                 WifiManager.ERROR_AUTH_FAILURE_TIMEOUT);
         mLooper.dispatchAll();
 
-        WifiConfiguration config = mCmi.getCurrentWifiConfiguration();
+        WifiConfiguration config = mCmi.getConnectedWifiConfiguration();
         verify(mConnectionFailureNotifier)
                 .showFailedToConnectDueToNoRandomizedMacSupportNotification(FRAMEWORK_NETWORK_ID);
     }
@@ -3009,7 +3298,7 @@
         when(mWifiConfigManager.isInFlakyRandomizationSsidHotlist(anyInt())).thenReturn(true);
         // Setup CONNECT_MODE & a WifiConfiguration
         initializeAndAddNetworkAndVerifySuccess();
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, FRAMEWORK_NETWORK_ID, 0, sBSSID);
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, FRAMEWORK_NETWORK_ID, 0, TEST_BSSID_STR);
         mCmi.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT,
                 WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD);
         mLooper.dispatchAll();
@@ -3027,12 +3316,12 @@
     public void testReportConnectionEventIsCalledAfterCmdStartConnect() throws Exception {
         // Setup CONNECT_MODE & a WifiConfiguration
         initializeAndAddNetworkAndVerifySuccess();
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
         verify(mWifiDiagnostics, never()).reportConnectionEvent(
-                eq(WifiDiagnostics.CONNECTION_EVENT_STARTED));
+                eq(WifiDiagnostics.CONNECTION_EVENT_STARTED), any());
         mLooper.dispatchAll();
         verify(mWifiDiagnostics).reportConnectionEvent(
-                eq(WifiDiagnostics.CONNECTION_EVENT_STARTED));
+                eq(WifiDiagnostics.CONNECTION_EVENT_STARTED), any());
     }
 
     /**
@@ -3044,12 +3333,12 @@
     public void testCmdDiagsConnectTimeoutIsGeneratedAfterCmdStartConnect() throws Exception {
         // Setup CONNECT_MODE & a WifiConfiguration
         initializeAndAddNetworkAndVerifySuccess();
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
         mLooper.dispatchAll();
         mLooper.moveTimeForward(ClientModeImpl.DIAGS_CONNECT_TIMEOUT_MILLIS);
         mLooper.dispatchAll();
         verify(mWifiDiagnostics).reportConnectionEvent(
-                eq(BaseWifiDiagnostics.CONNECTION_EVENT_TIMEOUT));
+                eq(WifiDiagnostics.CONNECTION_EVENT_TIMEOUT), any());
     }
 
     /**
@@ -3060,19 +3349,19 @@
     public void testCmdDiagsConnectTimeoutIsNotProcessedBeforeTimerExpires() throws Exception {
         // Setup CONNECT_MODE & a WifiConfiguration
         initializeAndAddNetworkAndVerifySuccess();
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
         mLooper.dispatchAll();
         mLooper.moveTimeForward(ClientModeImpl.DIAGS_CONNECT_TIMEOUT_MILLIS - 1000);
         mLooper.dispatchAll();
         verify(mWifiDiagnostics, never()).reportConnectionEvent(
-                eq(BaseWifiDiagnostics.CONNECTION_EVENT_TIMEOUT));
+                eq(WifiDiagnostics.CONNECTION_EVENT_TIMEOUT), any());
     }
 
     private void verifyConnectionEventTimeoutDoesNotOccur() {
         mLooper.moveTimeForward(ClientModeImpl.DIAGS_CONNECT_TIMEOUT_MILLIS);
         mLooper.dispatchAll();
         verify(mWifiDiagnostics, never()).reportConnectionEvent(
-                eq(BaseWifiDiagnostics.CONNECTION_EVENT_TIMEOUT));
+                eq(WifiDiagnostics.CONNECTION_EVENT_TIMEOUT), any());
     }
 
     /**
@@ -3084,28 +3373,44 @@
     @Test
     public void testReportConnectionEventIsCalledAfterAssociationFailure() throws Exception {
         mConnectedNetwork.getNetworkSelectionStatus()
-                .setCandidate(getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq).getScanResult());
+                .setCandidate(getGoogleGuestScanDetail(TEST_RSSI, TEST_BSSID_STR, sFreq)
+                        .getScanResult());
         // Setup CONNECT_MODE & a WifiConfiguration
         initializeAndAddNetworkAndVerifySuccess();
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
-        mCmi.sendMessage(WifiMonitor.ASSOCIATION_REJECTION_EVENT, 0,
-                ISupplicantStaIfaceCallback.StatusCode.AP_UNABLE_TO_HANDLE_NEW_STA, sBSSID);
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
+        mCmi.sendMessage(WifiMonitor.ASSOCIATION_REJECTION_EVENT,
+                new AssocRejectEventInfo(TEST_SSID, TEST_BSSID_STR,
+                        ISupplicantStaIfaceCallback.StatusCode.AP_UNABLE_TO_HANDLE_NEW_STA, false));
         verify(mWifiDiagnostics, never()).reportConnectionEvent(
-                eq(WifiDiagnostics.CONNECTION_EVENT_FAILED));
+                eq(WifiDiagnostics.CONNECTION_EVENT_FAILED), any());
         mLooper.dispatchAll();
         verify(mWifiDiagnostics).reportConnectionEvent(
-                eq(WifiDiagnostics.CONNECTION_EVENT_FAILED));
+                eq(WifiDiagnostics.CONNECTION_EVENT_FAILED), any());
         verify(mWifiConnectivityManager).handleConnectionAttemptEnded(
-                WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_REJECTION, sBSSID, sSSID);
+                mClientModeManager,
+                WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_REJECTION, TEST_BSSID_STR,
+                TEST_SSID);
         verify(mWifiNetworkFactory).handleConnectionAttemptEnded(
                 eq(WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_REJECTION),
-                any(WifiConfiguration.class));
+                any(WifiConfiguration.class), eq(TEST_BSSID_STR));
         verify(mWifiNetworkSuggestionsManager).handleConnectionAttemptEnded(
                 eq(WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_REJECTION),
                 any(WifiConfiguration.class), eq(null));
         verify(mWifiMetrics, never())
                 .incrementNumBssidDifferentSelectionBetweenFrameworkAndFirmware();
         verifyConnectionEventTimeoutDoesNotOccur();
+
+        clearInvocations(mWifiDiagnostics, mWifiConfigManager, mWifiNetworkFactory,
+                mWifiNetworkSuggestionsManager);
+
+        // Now trigger a disconnect event from supplicant, this should be ignored since the
+        // connection tracking should have already ended.
+        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT,
+                new DisconnectEventInfo(TEST_SSID, TEST_BSSID_STR, 0, false));
+        mLooper.dispatchAll();
+
+        verifyNoMoreInteractions(mWifiDiagnostics, mWifiConfigManager, mWifiNetworkFactory,
+                mWifiNetworkSuggestionsManager);
     }
 
     /**
@@ -3117,36 +3422,49 @@
     @Test
     public void testReportConnectionEventIsCalledAfterAuthenticationFailure() throws Exception {
         mConnectedNetwork.getNetworkSelectionStatus()
-                .setCandidate(getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq).getScanResult());
+                .setCandidate(getGoogleGuestScanDetail(TEST_RSSI, TEST_BSSID_STR, sFreq)
+                        .getScanResult());
         // Setup CONNECT_MODE & a WifiConfiguration
         initializeAndAddNetworkAndVerifySuccess();
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
         mCmi.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT,
                 WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD);
-        verify(mWifiDiagnostics, never()).reportConnectionEvent(
-                eq(WifiDiagnostics.CONNECTION_EVENT_FAILED));
         mLooper.dispatchAll();
         verify(mWifiDiagnostics).reportConnectionEvent(
-                eq(WifiDiagnostics.CONNECTION_EVENT_FAILED));
+                eq(WifiDiagnostics.CONNECTION_EVENT_FAILED), any());
         verify(mWifiConnectivityManager).handleConnectionAttemptEnded(
-                WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE, sBSSID, sSSID);
+                mClientModeManager,
+                WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE, TEST_BSSID_STR,
+                TEST_SSID);
         verify(mWifiNetworkFactory).handleConnectionAttemptEnded(
                 eq(WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE),
-                any(WifiConfiguration.class));
+                any(WifiConfiguration.class), eq(TEST_BSSID_STR));
         verify(mWifiNetworkSuggestionsManager).handleConnectionAttemptEnded(
                 eq(WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE),
                 any(WifiConfiguration.class), eq(null));
         verify(mWifiMetrics, never())
                 .incrementNumBssidDifferentSelectionBetweenFrameworkAndFirmware();
         verifyConnectionEventTimeoutDoesNotOccur();
+
+        clearInvocations(mWifiDiagnostics, mWifiConfigManager, mWifiNetworkFactory,
+                mWifiNetworkSuggestionsManager);
+
+        // Now trigger a disconnect event from supplicant, this should be ignored since the
+        // connection tracking should have already ended.
+        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT,
+                new DisconnectEventInfo(TEST_SSID, TEST_BSSID_STR, 0, false));
+        mLooper.dispatchAll();
+
+        verifyNoMoreInteractions(mWifiDiagnostics, mWifiConfigManager, mWifiNetworkFactory,
+                mWifiNetworkSuggestionsManager);
     }
 
     /**
-     * Verify that if a NETWORK_DISCONNECTION_EVENT is received in ConnectedState, then an
-     * abnormal disconnect is reported to BssidBlocklistMonitor.
+     * Verify that if a NETWORK_DISCONNECTION_EVENT is received in L3ConnectedState, then an
+     * abnormal disconnect is reported to WifiBlocklistMonitor.
      */
     @Test
-    public void testAbnormalDisconnectNotifiesBssidBlocklistMonitor() throws Exception {
+    public void testAbnormalDisconnectNotifiesWifiBlocklistMonitor() throws Exception {
         // trigger RSSI poll to update WifiInfo
         mCmi.enableRssiPolling(true);
         WifiLinkLayerStats llStats = new WifiLinkLayerStats();
@@ -3159,39 +3477,212 @@
 
         connect();
         mLooper.dispatchAll();
-        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, 0, 0, sBSSID);
+        DisconnectEventInfo disconnectEventInfo =
+                new DisconnectEventInfo(TEST_SSID, TEST_BSSID_STR, 0, false);
+        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, disconnectEventInfo);
         mLooper.dispatchAll();
 
-        verify(mBssidBlocklistMonitor).handleBssidConnectionFailure(eq(sBSSID), eq(sSSID),
-                eq(BssidBlocklistMonitor.REASON_ABNORMAL_DISCONNECT), anyInt());
+        verify(mWifiBlocklistMonitor).handleBssidConnectionFailure(eq(TEST_BSSID_STR),
+                eq(TEST_SSID), eq(WifiBlocklistMonitor.REASON_ABNORMAL_DISCONNECT), anyInt());
     }
 
     /**
-     * Verify that ClientModeImpl notifies BssidBlocklistMonitor correctly when the RSSI is
+     * Verify that ClientModeImpl notifies WifiBlocklistMonitor correctly when the RSSI is
      * too low.
      */
     @Test
-    public void testNotifiesBssidBlocklistMonitorLowRssi() throws Exception {
+    public void testNotifiesWifiBlocklistMonitorLowRssi() throws Exception {
         int testLowRssi = -80;
         initializeAndAddNetworkAndVerifySuccess();
         mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, FRAMEWORK_NETWORK_ID, 0,
                 ClientModeImpl.SUPPLICANT_BSSID_ANY);
-        mCmi.sendMessage(WifiMonitor.ASSOCIATION_REJECTION_EVENT, 1, 0, sBSSID);
-        when(mWifiConfigManager.findScanRssi(eq(FRAMEWORK_NETWORK_ID), anyInt())).thenReturn(-80);
+        mCmi.sendMessage(WifiMonitor.ASSOCIATION_REJECTION_EVENT,
+                new AssocRejectEventInfo(TEST_SSID, TEST_BSSID_STR, 0, true));
+        when(mWifiConfigManager.findScanRssi(eq(FRAMEWORK_NETWORK_ID), anyInt()))
+                .thenReturn(testLowRssi);
         when(mWifiConfigManager.getScanDetailCacheForNetwork(FRAMEWORK_NETWORK_ID))
                 .thenReturn(mScanDetailCache);
-        when(mScanDetailCache.getScanDetail(sBSSID)).thenReturn(
-                getGoogleGuestScanDetail(testLowRssi, sBSSID, sFreq));
-        when(mScanDetailCache.getScanResult(sBSSID)).thenReturn(
-                getGoogleGuestScanDetail(testLowRssi, sBSSID, sFreq).getScanResult());
+        when(mScanDetailCache.getScanDetail(TEST_BSSID_STR)).thenReturn(
+                getGoogleGuestScanDetail(testLowRssi, TEST_BSSID_STR, sFreq));
+        when(mScanDetailCache.getScanResult(TEST_BSSID_STR)).thenReturn(
+                getGoogleGuestScanDetail(testLowRssi, TEST_BSSID_STR, sFreq).getScanResult());
         mLooper.dispatchAll();
 
-        verify(mBssidBlocklistMonitor).handleBssidConnectionFailure(sBSSID, sSSID,
-                BssidBlocklistMonitor.REASON_ASSOCIATION_TIMEOUT, testLowRssi);
+        verify(mWifiBlocklistMonitor).handleBssidConnectionFailure(TEST_BSSID_STR, TEST_SSID,
+                WifiBlocklistMonitor.REASON_ASSOCIATION_TIMEOUT, testLowRssi);
     }
 
     /**
-     * Verifies that the BssidBlocklistMonitor is notified, but the WifiLastResortWatchdog is
+     * Verify that the recent failure association status is updated properly when
+     * ASSOC_REJECTED_TEMPORARILY occurs.
+     */
+    @Test
+    public void testAssocRejectedTemporarilyUpdatesRecentAssociationFailureStatus()
+            throws Exception {
+        initializeAndAddNetworkAndVerifySuccess();
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
+        mCmi.sendMessage(WifiMonitor.ASSOCIATION_REJECTION_EVENT,
+                new AssocRejectEventInfo(TEST_SSID, TEST_BSSID_STR,
+                        ISupplicantStaIfaceCallback.StatusCode.ASSOC_REJECTED_TEMPORARILY,
+                        false));
+        mLooper.dispatchAll();
+        verify(mWifiConfigManager).setRecentFailureAssociationStatus(anyInt(),
+                eq(WifiConfiguration.RECENT_FAILURE_REFUSED_TEMPORARILY));
+    }
+
+    /**
+     * Verify that WifiScoreCard and WifiBlocklistMonitor are notified properly when
+     * disconnection occurs in middle of connection states.
+     */
+    @Test
+    public void testDisconnectConnecting() throws Exception {
+        initializeAndAddNetworkAndVerifySuccess();
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
+        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT,
+                new DisconnectEventInfo(TEST_SSID, TEST_BSSID_STR,
+                        ISupplicantStaIfaceCallback.ReasonCode.FOURWAY_HANDSHAKE_TIMEOUT,
+                        false));
+        mLooper.dispatchAll();
+        verify(mWifiScoreCard).noteConnectionFailure(any(), anyInt(), anyString(), anyInt());
+        verify(mWifiScoreCard).resetConnectionState(WIFI_IFACE_NAME);
+        // Verify that the WifiBlocklistMonitor is notified of a non-locally generated disconnect
+        // that occurred mid connection attempt.
+        verify(mWifiBlocklistMonitor).handleBssidConnectionFailure(anyString(), anyString(),
+                eq(WifiBlocklistMonitor.REASON_NONLOCAL_DISCONNECT_CONNECTING), anyInt());
+        verify(mWifiConfigManager, never()).updateNetworkSelectionStatus(anyInt(),
+                eq(WifiConfiguration.NetworkSelectionStatus.DISABLED_CONSECUTIVE_FAILURES));
+    }
+
+    /**
+     * Verify that the WifiConfigManager is notified when a network experiences consecutive
+     * connection failures.
+     */
+    @Test
+    public void testDisableNetworkConsecutiveFailures() throws Exception {
+        initializeAndAddNetworkAndVerifySuccess();
+        when(mPerNetworkRecentStats.getCount(WifiScoreCard.CNT_CONSECUTIVE_CONNECTION_FAILURE))
+                .thenReturn(WifiBlocklistMonitor.NUM_CONSECUTIVE_FAILURES_PER_NETWORK_EXP_BACKOFF);
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, FRAMEWORK_NETWORK_ID, 0, TEST_BSSID_STR);
+        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT,
+                new DisconnectEventInfo(TEST_SSID, TEST_BSSID_STR,
+                        ISupplicantStaIfaceCallback.ReasonCode.FOURWAY_HANDSHAKE_TIMEOUT,
+                        false));
+        mLooper.dispatchAll();
+        verify(mWifiScoreCard).noteConnectionFailure(any(), anyInt(), anyString(), anyInt());
+        verify(mWifiScoreCard).resetConnectionState(WIFI_IFACE_NAME);
+        verify(mWifiBlocklistMonitor).handleBssidConnectionFailure(anyString(), anyString(),
+                eq(WifiBlocklistMonitor.REASON_NONLOCAL_DISCONNECT_CONNECTING), anyInt());
+        verify(mWifiConfigManager).updateNetworkSelectionStatus(FRAMEWORK_NETWORK_ID,
+                WifiConfiguration.NetworkSelectionStatus.DISABLED_CONSECUTIVE_FAILURES);
+    }
+
+    /**
+     * Verify that the recent failure association status is updated properly when
+     * DENIED_POOR_CHANNEL_CONDITIONS occurs.
+     */
+    @Test
+    public void testAssocRejectedPoorChannelConditionsUpdatesRecentAssociationFailureStatus()
+            throws Exception {
+        initializeAndAddNetworkAndVerifySuccess();
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
+        mCmi.sendMessage(WifiMonitor.ASSOCIATION_REJECTION_EVENT,
+                new AssocRejectEventInfo(TEST_SSID, TEST_BSSID_STR,
+                        ISupplicantStaIfaceCallback.StatusCode.DENIED_POOR_CHANNEL_CONDITIONS,
+                        false));
+        mLooper.dispatchAll();
+        verify(mWifiConfigManager).setRecentFailureAssociationStatus(anyInt(),
+                eq(WifiConfiguration.RECENT_FAILURE_POOR_CHANNEL_CONDITIONS));
+    }
+
+    /**
+     * Verify that the recent failure association status is updated properly when a disconnection
+     * with reason code DISASSOC_AP_BUSY occurs.
+     */
+    @Test
+    public void testNetworkDisconnectionApBusyUpdatesRecentAssociationFailureStatus()
+            throws Exception {
+        connect();
+        // Disconnection with reason = DISASSOC_AP_BUSY
+        DisconnectEventInfo disconnectEventInfo =
+                new DisconnectEventInfo(TEST_SSID, TEST_BSSID_STR, 5, false);
+        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, disconnectEventInfo);
+        mLooper.dispatchAll();
+        verify(mWifiConfigManager).setRecentFailureAssociationStatus(anyInt(),
+                eq(WifiConfiguration.RECENT_FAILURE_DISCONNECTION_AP_BUSY));
+    }
+
+    /**
+     * Verify that the recent failure association status is updated properly when a disconnection
+     * with reason code DISASSOC_AP_BUSY occurs.
+     */
+    @Test
+    public void testMidConnectionDisconnectionApBusyUpdatesRecentAssociationFailureStatus()
+            throws Exception {
+        initializeAndAddNetworkAndVerifySuccess();
+        startConnectSuccess();
+        assertEquals("L2ConnectingState", getCurrentState().getName());
+
+        // Disconnection with reason = DISASSOC_AP_BUSY
+        DisconnectEventInfo disconnectEventInfo =
+                new DisconnectEventInfo(TEST_SSID, TEST_BSSID_STR, 5, false);
+        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, disconnectEventInfo);
+        mLooper.dispatchAll();
+        verify(mWifiConfigManager).setRecentFailureAssociationStatus(anyInt(),
+                eq(WifiConfiguration.RECENT_FAILURE_DISCONNECTION_AP_BUSY));
+    }
+
+    /**
+     * Verify that the recent failure association status is updated properly when
+     * ASSOCIATION_REJECTION_EVENT with OCE RSSI based association rejection attribute is received.
+     */
+    @Test
+    public void testOceRssiBasedAssociationRejectionUpdatesRecentAssociationFailureStatus()
+            throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        initializeAndAddNetworkAndVerifySuccess();
+        AssociationRejectionData assocRejectData = new AssociationRejectionData();
+        assocRejectData.ssid = NativeUtil.decodeSsid(TEST_SSID);
+        assocRejectData.bssid = NativeUtil.macAddressToByteArray(TEST_BSSID_STR);
+        assocRejectData.statusCode =
+                ISupplicantStaIfaceCallback.StatusCode.DENIED_POOR_CHANNEL_CONDITIONS;
+        assocRejectData.isOceRssiBasedAssocRejectAttrPresent = true;
+        assocRejectData.oceRssiBasedAssocRejectData.retryDelayS = 10;
+        assocRejectData.oceRssiBasedAssocRejectData.deltaRssi = 20;
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
+        mCmi.sendMessage(WifiMonitor.ASSOCIATION_REJECTION_EVENT,
+                new AssocRejectEventInfo(assocRejectData));
+        mLooper.dispatchAll();
+        verify(mWifiConfigManager).setRecentFailureAssociationStatus(anyInt(),
+                eq(WifiConfiguration.RECENT_FAILURE_OCE_RSSI_BASED_ASSOCIATION_REJECTION));
+    }
+
+    /**
+     * Verify that the recent failure association status is updated properly when
+     * ASSOCIATION_REJECTION_EVENT with MBO association disallowed attribute is received.
+     */
+    @Test
+    public void testMboAssocDisallowedIndInAssocRejectUpdatesRecentAssociationFailureStatus()
+            throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        initializeAndAddNetworkAndVerifySuccess();
+        AssociationRejectionData assocRejectData = new AssociationRejectionData();
+        assocRejectData.ssid = NativeUtil.decodeSsid(TEST_SSID);
+        assocRejectData.bssid = NativeUtil.macAddressToByteArray(TEST_BSSID_STR);
+        assocRejectData.statusCode =
+                ISupplicantStaIfaceCallback.StatusCode.DENIED_POOR_CHANNEL_CONDITIONS;
+        assocRejectData.isMboAssocDisallowedReasonCodePresent = true;
+        assocRejectData.mboAssocDisallowedReason = MboAssocDisallowedReasonCode
+                .MAX_NUM_STA_ASSOCIATED;
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
+        mCmi.sendMessage(WifiMonitor.ASSOCIATION_REJECTION_EVENT,
+                new AssocRejectEventInfo(assocRejectData));
+        mLooper.dispatchAll();
+        verify(mWifiConfigManager).setRecentFailureAssociationStatus(anyInt(),
+                eq(WifiConfiguration.RECENT_FAILURE_MBO_ASSOC_DISALLOWED_MAX_NUM_STA_ASSOCIATED));
+    }
+
+    /**
+     * Verifies that the WifiBlocklistMonitor is notified, but the WifiLastResortWatchdog is
      * not notified of association rejections of type REASON_CODE_AP_UNABLE_TO_HANDLE_NEW_STA.
      * @throws Exception
      */
@@ -3199,18 +3690,41 @@
     public void testAssociationRejectionWithReasonApUnableToHandleNewStaUpdatesWatchdog()
             throws Exception {
         initializeAndAddNetworkAndVerifySuccess();
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
-        mCmi.sendMessage(WifiMonitor.ASSOCIATION_REJECTION_EVENT, 0,
-                ClientModeImpl.REASON_CODE_AP_UNABLE_TO_HANDLE_NEW_STA, sBSSID);
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
+        mCmi.sendMessage(WifiMonitor.ASSOCIATION_REJECTION_EVENT,
+                new AssocRejectEventInfo(TEST_SSID, TEST_BSSID_STR,
+                        ISupplicantStaIfaceCallback.StatusCode.AP_UNABLE_TO_HANDLE_NEW_STA, false));
         mLooper.dispatchAll();
         verify(mWifiLastResortWatchdog, never()).noteConnectionFailureAndTriggerIfNeeded(
-                anyString(), anyString(), anyInt());
-        verify(mBssidBlocklistMonitor).handleBssidConnectionFailure(eq(sBSSID), eq(sSSID),
-                eq(BssidBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA), anyInt());
+                anyString(), anyString(), anyInt(), anyBoolean());
+        verify(mWifiBlocklistMonitor).handleBssidConnectionFailure(eq(TEST_BSSID_STR),
+                eq(TEST_SSID), eq(WifiBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA),
+                anyInt());
     }
 
     /**
-     * Verifies that WifiLastResortWatchdog and BssidBlocklistMonitor is notified of
+     * Verifies that the WifiBlocklistMonitor is notified, but the WifiLastResortWatchdog is
+     * not notified of association rejections of type DENIED_INSUFFICIENT_BANDWIDTH.
+     * @throws Exception
+     */
+    @Test
+    public void testAssociationRejectionWithReasonDeniedInsufficientBandwidth()
+            throws Exception {
+        initializeAndAddNetworkAndVerifySuccess();
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
+        mCmi.sendMessage(WifiMonitor.ASSOCIATION_REJECTION_EVENT,
+                new AssocRejectEventInfo(TEST_SSID, TEST_BSSID_STR, ISupplicantStaIfaceCallback
+                        .StatusCode.DENIED_INSUFFICIENT_BANDWIDTH, false));
+        mLooper.dispatchAll();
+        verify(mWifiLastResortWatchdog, never()).noteConnectionFailureAndTriggerIfNeeded(
+                anyString(), anyString(), anyInt(), anyBoolean());
+        verify(mWifiBlocklistMonitor).handleBssidConnectionFailure(eq(TEST_BSSID_STR),
+                eq(TEST_SSID), eq(WifiBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA),
+                anyInt());
+    }
+
+    /**
+     * Verifies that WifiLastResortWatchdog and WifiBlocklistMonitor is notified of
      * general association rejection failures.
      * @throws Exception
      */
@@ -3219,13 +3733,14 @@
         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);
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
+        mCmi.sendMessage(WifiMonitor.ASSOCIATION_REJECTION_EVENT,
+                new AssocRejectEventInfo(TEST_SSID, TEST_BSSID_STR, 0, false));
         mLooper.dispatchAll();
         verify(mWifiLastResortWatchdog).noteConnectionFailureAndTriggerIfNeeded(
-                anyString(), anyString(), anyInt());
-        verify(mBssidBlocklistMonitor).handleBssidConnectionFailure(eq(sBSSID), eq(sSSID),
-                eq(BssidBlocklistMonitor.REASON_ASSOCIATION_REJECTION), anyInt());
+                anyString(), anyString(), anyInt(), anyBoolean());
+        verify(mWifiBlocklistMonitor).handleBssidConnectionFailure(eq(TEST_BSSID_STR),
+                eq(TEST_SSID), eq(WifiBlocklistMonitor.REASON_ASSOCIATION_REJECTION), anyInt());
         verify(mWifiMetrics).incrementNumOfCarrierWifiConnectionNonAuthFailure();
     }
 
@@ -3238,16 +3753,17 @@
     public void testFailureWrongPassIsIgnoredByWatchdog() throws Exception {
         // Setup CONNECT_MODE & a WifiConfiguration
         initializeAndAddNetworkAndVerifySuccess();
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
         mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
-                new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.COMPLETED));
+                new StateChangeResult(0, TEST_WIFI_SSID, TEST_BSSID_STR,
+                        SupplicantState.COMPLETED));
         mCmi.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT,
                 WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD);
         mLooper.dispatchAll();
         verify(mWifiLastResortWatchdog, never()).noteConnectionFailureAndTriggerIfNeeded(
-                anyString(), anyString(), anyInt());
-        verify(mBssidBlocklistMonitor).handleBssidConnectionFailure(eq(sBSSID), eq(sSSID),
-                eq(BssidBlocklistMonitor.REASON_WRONG_PASSWORD), anyInt());
+                anyString(), anyString(), anyInt(), anyBoolean());
+        verify(mWifiBlocklistMonitor).handleBssidConnectionFailure(eq(TEST_BSSID_STR),
+                eq(TEST_SSID), eq(WifiBlocklistMonitor.REASON_WRONG_PASSWORD), anyInt());
     }
 
     /**
@@ -3259,16 +3775,17 @@
     public void testEapFailureIsIgnoredByWatchdog() throws Exception {
         // Setup CONNECT_MODE & a WifiConfiguration
         initializeAndAddNetworkAndVerifySuccess();
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
         mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
-                new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.COMPLETED));
+                new StateChangeResult(0, TEST_WIFI_SSID, TEST_BSSID_STR,
+                        SupplicantState.COMPLETED));
         mCmi.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT,
                 WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE);
         mLooper.dispatchAll();
         verify(mWifiLastResortWatchdog, never()).noteConnectionFailureAndTriggerIfNeeded(
-                anyString(), anyString(), anyInt());
-        verify(mBssidBlocklistMonitor).handleBssidConnectionFailure(eq(sBSSID), eq(sSSID),
-                eq(BssidBlocklistMonitor.REASON_EAP_FAILURE), anyInt());
+                anyString(), anyString(), anyInt(), anyBoolean());
+        verify(mWifiBlocklistMonitor).handleBssidConnectionFailure(eq(TEST_BSSID_STR),
+                eq(TEST_SSID), eq(WifiBlocklistMonitor.REASON_EAP_FAILURE), anyInt());
     }
 
     /**
@@ -3279,29 +3796,31 @@
     public void testAuthenticationFailureUpdatesWatchdog() throws Exception {
         // Setup CONNECT_MODE & a WifiConfiguration
         initializeAndAddNetworkAndVerifySuccess();
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
         mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
-                new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.COMPLETED));
+                new StateChangeResult(0, TEST_WIFI_SSID, TEST_BSSID_STR,
+                        SupplicantState.COMPLETED));
         mCmi.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT,
                 WifiManager.ERROR_AUTH_FAILURE_TIMEOUT);
         mLooper.dispatchAll();
         verify(mWifiLastResortWatchdog).noteConnectionFailureAndTriggerIfNeeded(
-                anyString(), anyString(), anyInt());
-        verify(mBssidBlocklistMonitor).handleBssidConnectionFailure(eq(sBSSID), eq(sSSID),
-                eq(BssidBlocklistMonitor.REASON_AUTHENTICATION_FAILURE), anyInt());
+                anyString(), anyString(), anyInt(), anyBoolean());
+        verify(mWifiBlocklistMonitor).handleBssidConnectionFailure(eq(TEST_BSSID_STR),
+                eq(TEST_SSID), eq(WifiBlocklistMonitor.REASON_AUTHENTICATION_FAILURE), anyInt());
     }
 
     /**
-     * Verify that BssidBlocklistMonitor is notified of the SSID pre-connection so that it could
+     * Verify that WifiBlocklistMonitor is notified of the SSID pre-connection so that it could
      * send down to firmware the list of blocked BSSIDs.
      */
     @Test
     public void testBssidBlocklistSentToFirmwareAfterCmdStartConnect() throws Exception {
         initializeAndAddNetworkAndVerifySuccess();
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
-        verify(mBssidBlocklistMonitor, never()).updateFirmwareRoamingConfiguration(sSSID);
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
+        verify(mWifiBlocklistMonitor, never()).updateFirmwareRoamingConfiguration(
+                Set.of(TEST_SSID));
         mLooper.dispatchAll();
-        verify(mBssidBlocklistMonitor).updateFirmwareRoamingConfiguration(sSSID);
+        verify(mWifiBlocklistMonitor).updateFirmwareRoamingConfiguration(Set.of(TEST_SSID));
         // But don't expect to see connection success yet
         verify(mWifiScoreCard, never()).noteIpConfiguration(any());
         // And certainly not validation success
@@ -3317,14 +3836,17 @@
     @Test
     public void testReportConnectionEventIsCalledAfterDhcpFailure() throws Exception {
         mConnectedNetwork.getNetworkSelectionStatus()
-                .setCandidate(getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq).getScanResult());
+                .setCandidate(getGoogleGuestScanDetail(TEST_RSSI, TEST_BSSID_STR, sFreq)
+                        .getScanResult());
         testDhcpFailure();
         verify(mWifiDiagnostics, atLeastOnce()).reportConnectionEvent(
-                eq(WifiDiagnostics.CONNECTION_EVENT_FAILED));
+                eq(WifiDiagnostics.CONNECTION_EVENT_FAILED), any());
         verify(mWifiConnectivityManager, atLeastOnce()).handleConnectionAttemptEnded(
-                WifiMetrics.ConnectionEvent.FAILURE_DHCP, sBSSID, sSSID);
+                mClientModeManager,
+                WifiMetrics.ConnectionEvent.FAILURE_DHCP, TEST_BSSID_STR, TEST_SSID);
         verify(mWifiNetworkFactory, atLeastOnce()).handleConnectionAttemptEnded(
-                eq(WifiMetrics.ConnectionEvent.FAILURE_DHCP), any(WifiConfiguration.class));
+                eq(WifiMetrics.ConnectionEvent.FAILURE_DHCP), any(WifiConfiguration.class),
+                eq(TEST_BSSID_STR));
         verify(mWifiNetworkSuggestionsManager, atLeastOnce()).handleConnectionAttemptEnded(
                 eq(WifiMetrics.ConnectionEvent.FAILURE_DHCP), any(WifiConfiguration.class),
                 any(String.class));
@@ -3337,30 +3859,31 @@
      * Verifies that a successful validation make WifiDiagnostics report CONNECTION_EVENT_SUCCEEDED
      * and then cancel any pending timeouts.
      * Also, send connection status to {@link WifiNetworkFactory} & {@link WifiConnectivityManager}.
-     * @throws Exception
      */
     @Test
     public void testReportConnectionEventIsCalledAfterSuccessfulConnection() throws Exception {
         mConnectedNetwork.getNetworkSelectionStatus()
-                .setCandidate(getGoogleGuestScanDetail(TEST_RSSI, sBSSID1, sFreq).getScanResult());
+                .setCandidate(getGoogleGuestScanDetail(TEST_RSSI, TEST_BSSID_STR1, sFreq)
+                        .getScanResult());
         connect();
-        ArgumentCaptor<INetworkAgent> agentCaptor = ArgumentCaptor.forClass(INetworkAgent.class);
-        verify(mConnectivityManager).registerNetworkAgent(agentCaptor.capture(),
-                any(NetworkInfo.class), any(LinkProperties.class), any(NetworkCapabilities.class),
-                any(), any(NetworkAgentConfig.class), anyInt());
-        agentCaptor.getValue()
-                .onValidationStatusChanged(NetworkAgent.VALID_NETWORK, null /* captivePortalUrl */);
+        verify(mWifiInjector).makeWifiNetworkAgent(any(), any(), any(), any(),
+                mWifiNetworkAgentCallbackCaptor.capture());
+        mWifiNetworkAgentCallbackCaptor.getValue().onValidationStatus(
+                NetworkAgent.VALIDATION_STATUS_VALID, null /* captivePortalUrl */);
         mLooper.dispatchAll();
 
         verify(mWifiDiagnostics).reportConnectionEvent(
-                eq(WifiDiagnostics.CONNECTION_EVENT_SUCCEEDED));
+                eq(WifiDiagnostics.CONNECTION_EVENT_SUCCEEDED), any());
         verify(mWifiConnectivityManager).handleConnectionAttemptEnded(
-                WifiMetrics.ConnectionEvent.FAILURE_NONE, sBSSID, sSSID);
+                mClientModeManager,
+                WifiMetrics.ConnectionEvent.FAILURE_NONE, TEST_BSSID_STR, TEST_SSID);
         verify(mWifiNetworkFactory).handleConnectionAttemptEnded(
-                eq(WifiMetrics.ConnectionEvent.FAILURE_NONE), any(WifiConfiguration.class));
+                eq(WifiMetrics.ConnectionEvent.FAILURE_NONE), any(WifiConfiguration.class),
+                eq(TEST_BSSID_STR));
         verify(mWifiNetworkSuggestionsManager).handleConnectionAttemptEnded(
                 eq(WifiMetrics.ConnectionEvent.FAILURE_NONE), any(WifiConfiguration.class),
                 any(String.class));
+        verify(mCmiMonitor).onInternetValidated(mClientModeManager);
         // BSSID different, record this connection.
         verify(mWifiMetrics).incrementNumBssidDifferentSelectionBetweenFrameworkAndFirmware();
         verifyConnectionEventTimeoutDoesNotOccur();
@@ -3372,7 +3895,7 @@
     @Test
     public void testScoreCardNoteConnectionAttemptAfterCmdStartConnect() throws Exception {
         initializeAndAddNetworkAndVerifySuccess();
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
         verify(mWifiScoreCard, never()).noteConnectionAttempt(any(), anyInt(), anyString());
         mLooper.dispatchAll();
         verify(mWifiScoreCard).noteConnectionAttempt(any(), anyInt(), anyString());
@@ -3411,16 +3934,13 @@
         disconnect();
         mLooper.dispatchAll();
 
-        verify(mWifiScoreCard, times(1)).resetConnectionState();
+        verify(mWifiScoreCard, times(1)).resetConnectionState(WIFI_IFACE_NAME);
         verify(mWifiScoreCard, never()).noteWifiDisabled(any());
-        verify(mWifiHealthMonitor, never()).setWifiEnabled(false);
 
         // disabling while disconnected should note wifi disabled
-        mCmi.setWifiStateForApiCalls(WifiManager.WIFI_STATE_DISABLED);
-        mCmi.setOperationalMode(ClientModeImpl.DISABLED_MODE, null);
+        mCmi.stop();
         mLooper.dispatchAll();
-        verify(mWifiScoreCard, times(2)).resetConnectionState();
-        verify(mWifiHealthMonitor).setWifiEnabled(false);
+        verify(mWifiScoreCard, times(2)).resetConnectionState(WIFI_IFACE_NAME);
     }
 
     /**
@@ -3432,78 +3952,13 @@
         connect();
         mLooper.dispatchAll();
         verify(mWifiScoreCard, never()).noteWifiDisabled(any());
-        verify(mWifiHealthMonitor, never()).setWifiEnabled(false);
 
         // disabling while connected should note wifi disabled
-        mCmi.setWifiStateForApiCalls(WifiManager.WIFI_STATE_DISABLED);
-        mCmi.setOperationalMode(ClientModeImpl.DISABLED_MODE, null);
+        mCmi.stop();
         mLooper.dispatchAll();
 
         verify(mWifiScoreCard).noteWifiDisabled(any());
-        verify(mWifiScoreCard).resetConnectionState();
-        verify(mWifiHealthMonitor).setWifiEnabled(false);
-    }
-
-    /**
-     * Verify that we do not crash on quick toggling wifi on/off
-     */
-    @Test
-    public void quickTogglesDoNotCrash() throws Exception {
-        mCmi.setOperationalMode(ClientModeImpl.CONNECT_MODE, WIFI_IFACE_NAME);
-        mCmi.setOperationalMode(ClientModeImpl.DISABLED_MODE, null);
-        mLooper.dispatchAll();
-
-        mCmi.setOperationalMode(ClientModeImpl.DISABLED_MODE, null);
-        mCmi.setOperationalMode(ClientModeImpl.CONNECT_MODE, WIFI_IFACE_NAME);
-        mLooper.dispatchAll();
-
-        mCmi.setOperationalMode(ClientModeImpl.DISABLED_MODE, null);
-        mCmi.setOperationalMode(ClientModeImpl.CONNECT_MODE, WIFI_IFACE_NAME);
-        mCmi.setOperationalMode(ClientModeImpl.DISABLED_MODE, null);
-        mCmi.setOperationalMode(ClientModeImpl.CONNECT_MODE, WIFI_IFACE_NAME);
-        mLooper.dispatchAll();
-
-        mCmi.setOperationalMode(ClientModeImpl.CONNECT_MODE, WIFI_IFACE_NAME);
-        mCmi.setOperationalMode(ClientModeImpl.DISABLED_MODE, null);
-        mCmi.setOperationalMode(ClientModeImpl.CONNECT_MODE, WIFI_IFACE_NAME);
-        mCmi.setOperationalMode(ClientModeImpl.DISABLED_MODE, null);
-        mLooper.dispatchAll();
-    }
-
-    /**
-     * Verify that valid calls to set the current wifi state are returned when requested.
-     */
-    @Test
-    public void verifySetAndGetWifiStateCallsWorking() throws Exception {
-        // we start off disabled
-        assertEquals(WifiManager.WIFI_STATE_DISABLED, mCmi.syncGetWifiState());
-
-        // now check after updating
-        mCmi.setWifiStateForApiCalls(WifiManager.WIFI_STATE_UNKNOWN);
-        assertEquals(WifiManager.WIFI_STATE_UNKNOWN, mCmi.syncGetWifiState());
-
-        // check after two updates
-        mCmi.setWifiStateForApiCalls(WifiManager.WIFI_STATE_ENABLING);
-        mCmi.setWifiStateForApiCalls(WifiManager.WIFI_STATE_ENABLED);
-        assertEquals(WifiManager.WIFI_STATE_ENABLED, mCmi.syncGetWifiState());
-    }
-
-    /**
-     * Verify that invalid states do not change the saved wifi state.
-     */
-    @Test
-    public void verifyInvalidStatesDoNotChangeSavedWifiState() throws Exception {
-        int invalidStateNegative = -1;
-        int invalidStatePositive = 5;
-
-        // we start off disabled
-        assertEquals(WifiManager.WIFI_STATE_DISABLED, mCmi.syncGetWifiState());
-
-        mCmi.setWifiStateForApiCalls(invalidStateNegative);
-        assertEquals(WifiManager.WIFI_STATE_DISABLED, mCmi.syncGetWifiState());
-
-        mCmi.setWifiStateForApiCalls(invalidStatePositive);
-        assertEquals(WifiManager.WIFI_STATE_DISABLED, mCmi.syncGetWifiState());
+        verify(mWifiScoreCard).resetConnectionState(WIFI_IFACE_NAME);
     }
 
     /**
@@ -3511,31 +3966,28 @@
      */
     @Test
     public void verifyIpClientShutdownWhenDisabled() throws Exception {
-        loadComponentsInStaMode();
-
-        mCmi.setOperationalMode(ClientModeImpl.DISABLED_MODE, null);
+        mCmi.stop();
         mLooper.dispatchAll();
         verify(mIpClient).shutdown();
-        verify(mWifiConfigManager).removeAllEphemeralOrPasspointConfiguredNetworks();
-        verify(mWifiConfigManager).clearUserTemporarilyDisabledList();
     }
 
     /**
      * Verify that WifiInfo's MAC address is updated when the state machine receives
-     * NETWORK_CONNECTION_EVENT while in ConnectedState.
+     * NETWORK_CONNECTION_EVENT while in L3ConnectedState.
      */
     @Test
     public void verifyWifiInfoMacUpdatedWithNetworkConnectionWhileConnected() throws Exception {
         connect();
-        assertEquals("ConnectedState", getCurrentState().getName());
-        assertEquals(TEST_LOCAL_MAC_ADDRESS.toString(), mCmi.getWifiInfo().getMacAddress());
+        assertEquals("L3ConnectedState", getCurrentState().getName());
+        assertEquals(TEST_LOCAL_MAC_ADDRESS.toString(), mWifiInfo.getMacAddress());
 
         // Verify receiving a NETWORK_CONNECTION_EVENT changes the MAC in WifiInfo
         when(mWifiNative.getMacAddress(WIFI_IFACE_NAME))
                 .thenReturn(TEST_GLOBAL_MAC_ADDRESS.toString());
-        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, sBSSID);
+        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT,
+                new NetworkConnectionEventInfo(0, TEST_WIFI_SSID, TEST_BSSID_STR, false));
         mLooper.dispatchAll();
-        assertEquals(TEST_GLOBAL_MAC_ADDRESS.toString(), mCmi.getWifiInfo().getMacAddress());
+        assertEquals(TEST_GLOBAL_MAC_ADDRESS.toString(), mWifiInfo.getMacAddress());
     }
 
     /**
@@ -3548,21 +4000,20 @@
         assertEquals("DisconnectedState", getCurrentState().getName());
         // Since MAC randomization is enabled, wifiInfo's MAC should be set to default MAC
         // when disconnect happens.
-        assertEquals(WifiInfo.DEFAULT_MAC_ADDRESS, mCmi.getWifiInfo().getMacAddress());
+        assertEquals(WifiInfo.DEFAULT_MAC_ADDRESS, mWifiInfo.getMacAddress());
 
+        setupAndStartConnectSequence(mConnectedNetwork);
         when(mWifiNative.getMacAddress(WIFI_IFACE_NAME))
                 .thenReturn(TEST_LOCAL_MAC_ADDRESS.toString());
-        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, sBSSID);
+        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT,
+                new NetworkConnectionEventInfo(0, TEST_WIFI_SSID, TEST_BSSID_STR, false));
         mLooper.dispatchAll();
-        assertEquals(TEST_LOCAL_MAC_ADDRESS.toString(), mCmi.getWifiInfo().getMacAddress());
+        assertEquals(TEST_LOCAL_MAC_ADDRESS.toString(), mWifiInfo.getMacAddress());
     }
 
-    /**
-     * Verify that we temporarily disable the network when auto-connected to a network
-     * with no internet access.
-     */
     @Test
-    public void verifyAutoConnectedNetworkWithInternetValidationFailure() throws Exception {
+    public void internetValidationFailure_notUserSelected_expectTemporarilyDisabled()
+            throws Exception {
         // Setup RSSI poll to update WifiInfo with low RSSI
         mCmi.enableRssiPolling(true);
         WifiLinkLayerStats llStats = new WifiLinkLayerStats();
@@ -3573,96 +4024,203 @@
         when(mWifiNative.getWifiLinkLayerStats(any())).thenReturn(llStats);
         when(mWifiNative.signalPoll(any())).thenReturn(signalPollResult);
 
-        // Simulate the first connection.
         connect();
-        ArgumentCaptor<INetworkAgent> agentCaptor = ArgumentCaptor.forClass(INetworkAgent.class);
-        verify(mConnectivityManager).registerNetworkAgent(agentCaptor.capture(),
-                any(NetworkInfo.class), any(LinkProperties.class), any(NetworkCapabilities.class),
-                any(), any(NetworkAgentConfig.class), anyInt());
+        verify(mWifiInjector).makeWifiNetworkAgent(any(), any(), any(), any(),
+                mWifiNetworkAgentCallbackCaptor.capture());
 
         WifiConfiguration currentNetwork = new WifiConfiguration();
         currentNetwork.networkId = FRAMEWORK_NETWORK_ID;
         currentNetwork.SSID = DEFAULT_TEST_SSID;
         currentNetwork.noInternetAccessExpected = false;
         currentNetwork.numNoInternetAccessReports = 1;
+
+        // not user selected
         when(mWifiConfigManager.getConfiguredNetwork(FRAMEWORK_NETWORK_ID))
                 .thenReturn(currentNetwork);
         when(mWifiConfigManager.getLastSelectedNetwork()).thenReturn(FRAMEWORK_NETWORK_ID + 1);
 
-        agentCaptor.getValue().onValidationStatusChanged(
-                NetworkAgent.INVALID_NETWORK, null /* captivePortalUr; */);
+        // internet validation failure
+        mWifiNetworkAgentCallbackCaptor.getValue().onValidationStatus(
+                NetworkAgent.VALIDATION_STATUS_NOT_VALID, null /* captivePortalUr; */);
         mLooper.dispatchAll();
 
         verify(mWifiConfigManager)
                 .incrementNetworkNoInternetAccessReports(FRAMEWORK_NETWORK_ID);
+        // expect temporarily disabled
         verify(mWifiConfigManager).updateNetworkSelectionStatus(
                 FRAMEWORK_NETWORK_ID, DISABLED_NO_INTERNET_TEMPORARY);
-        verify(mBssidBlocklistMonitor).handleBssidConnectionFailure(sBSSID, sSSID,
-                BssidBlocklistMonitor.REASON_NETWORK_VALIDATION_FAILURE, RSSI_THRESHOLD_BREACH_MIN);
+        verify(mWifiBlocklistMonitor).handleBssidConnectionFailure(TEST_BSSID_STR, TEST_SSID,
+                WifiBlocklistMonitor.REASON_NETWORK_VALIDATION_FAILURE, RSSI_THRESHOLD_BREACH_MIN);
         verify(mWifiScoreCard).noteValidationFailure(any());
     }
 
-    /**
-     * Verify that we don't temporarily disable the network when user selected to connect to a
-     * network with no internet access.
-     */
     @Test
-    public void verifyLastSelectedNetworkWithInternetValidationFailure() throws Exception {
-        // Simulate the first connection.
+    public void mbb_internetValidationError_expectDisconnect() throws Exception {
         connect();
-        ArgumentCaptor<INetworkAgent> agentCaptor = ArgumentCaptor.forClass(INetworkAgent.class);
-        verify(mConnectivityManager).registerNetworkAgent(agentCaptor.capture(),
-                any(NetworkInfo.class), any(LinkProperties.class), any(NetworkCapabilities.class),
-                any(), any(NetworkAgentConfig.class), anyInt());
+        verify(mWifiInjector).makeWifiNetworkAgent(any(), any(), any(), any(),
+                mWifiNetworkAgentCallbackCaptor.capture());
+
+        // Make Before Break CMM
+        when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_TRANSIENT);
+
+        // internet validation failure
+        mWifiNetworkAgentCallbackCaptor.getValue().onValidationStatus(
+                NetworkAgent.VALIDATION_STATUS_NOT_VALID, null /* captivePortalUr; */);
+        mLooper.dispatchAll();
+
+        // expect disconnection
+        verify(mWifiNative).disconnect(WIFI_IFACE_NAME);
+    }
+
+    @Test
+    public void captivePortalDetected_notifiesCmiMonitor() throws Exception {
+        connect();
+        verify(mWifiInjector).makeWifiNetworkAgent(any(), any(), any(), any(),
+                mWifiNetworkAgentCallbackCaptor.capture());
+
+        // captive portal detected
+        when(mMockUri.toString()).thenReturn("TEST_URI");
+        mWifiNetworkAgentCallbackCaptor.getValue().onValidationStatus(
+                NetworkAgent.VALIDATION_STATUS_NOT_VALID, mMockUri);
+        mLooper.dispatchAll();
+
+        verify(mWifiConfigManager).noteCaptivePortalDetected(anyInt());
+        verify(mCmiMonitor).onCaptivePortalDetected(mClientModeManager);
+    }
+
+    @Test
+    public void internetValidationFailure_userSelectedRecently_expectNotDisabled()
+            throws Exception {
+        connect();
+        verify(mWifiInjector).makeWifiNetworkAgent(any(), any(), any(), any(),
+                mWifiNetworkAgentCallbackCaptor.capture());
 
         WifiConfiguration currentNetwork = new WifiConfiguration();
         currentNetwork.networkId = FRAMEWORK_NETWORK_ID;
         currentNetwork.noInternetAccessExpected = false;
         currentNetwork.numNoInternetAccessReports = 1;
+
+        // user last picked this network
         when(mWifiConfigManager.getConfiguredNetwork(FRAMEWORK_NETWORK_ID))
                 .thenReturn(currentNetwork);
         when(mWifiConfigManager.getLastSelectedNetwork()).thenReturn(FRAMEWORK_NETWORK_ID);
 
-        agentCaptor.getValue().onValidationStatusChanged(
-                NetworkAgent.INVALID_NETWORK, null /* captivePortalUrl */);
+        // user recently picked this network
+        when(mWifiConfigManager.getLastSelectedTimeStamp()).thenReturn(1234L);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(1235L);
+
+        // internet validation failure
+        mWifiNetworkAgentCallbackCaptor.getValue().onValidationStatus(
+                NetworkAgent.VALIDATION_STATUS_NOT_VALID, null /* captivePortalUrl */);
         mLooper.dispatchAll();
 
         verify(mWifiConfigManager)
                 .incrementNetworkNoInternetAccessReports(FRAMEWORK_NETWORK_ID);
+        // expect not disabled
+        verify(mWifiConfigManager, never()).updateNetworkSelectionStatus(
+                FRAMEWORK_NETWORK_ID, DISABLED_NO_INTERNET_TEMPORARY);
+    }
+
+    @Test
+    public void internetValidationFailure_userSelectedTooLongAgo_expectTemporarilyDisabled()
+            throws Exception {
+        connect();
+        verify(mWifiInjector).makeWifiNetworkAgent(any(), any(), any(), any(),
+                mWifiNetworkAgentCallbackCaptor.capture());
+
+        WifiConfiguration currentNetwork = new WifiConfiguration();
+        currentNetwork.networkId = FRAMEWORK_NETWORK_ID;
+        currentNetwork.noInternetAccessExpected = false;
+        currentNetwork.numNoInternetAccessReports = 1;
+
+        // user last picked this network
+        when(mWifiConfigManager.getConfiguredNetwork(FRAMEWORK_NETWORK_ID))
+                .thenReturn(currentNetwork);
+        when(mWifiConfigManager.getLastSelectedNetwork()).thenReturn(FRAMEWORK_NETWORK_ID);
+
+        // user picked this network a long time ago
+        when(mWifiConfigManager.getLastSelectedTimeStamp()).thenReturn(1234L);
+        when(mClock.getElapsedSinceBootMillis())
+                .thenReturn(1235L + ClientModeImpl.LAST_SELECTED_NETWORK_EXPIRATION_AGE_MILLIS);
+
+        // internet validation failure
+        mWifiNetworkAgentCallbackCaptor.getValue().onValidationStatus(
+                NetworkAgent.VALIDATION_STATUS_NOT_VALID, null /* captivePortalUrl */);
+        mLooper.dispatchAll();
+
+        verify(mWifiConfigManager)
+                .incrementNetworkNoInternetAccessReports(FRAMEWORK_NETWORK_ID);
+        // expect temporarily disabled
+        verify(mWifiConfigManager).updateNetworkSelectionStatus(
+                FRAMEWORK_NETWORK_ID, DISABLED_NO_INTERNET_TEMPORARY);
+    }
+
+    @Test
+    public void noInternetExpectedNetwork_internetValidationFailure_notUserSelected_expectNotDisabled()
+            throws Exception {
+        connect();
+        verify(mWifiInjector).makeWifiNetworkAgent(any(), any(), any(), any(),
+                mWifiNetworkAgentCallbackCaptor.capture());
+
+        WifiConfiguration currentNetwork = new WifiConfiguration();
+        currentNetwork.networkId = FRAMEWORK_NETWORK_ID;
+        // no internet expected
+        currentNetwork.noInternetAccessExpected = true;
+        currentNetwork.numNoInternetAccessReports = 1;
+
+        // user didn't pick this network
+        when(mWifiConfigManager.getConfiguredNetwork(FRAMEWORK_NETWORK_ID))
+                .thenReturn(currentNetwork);
+        when(mWifiConfigManager.getLastSelectedNetwork()).thenReturn(FRAMEWORK_NETWORK_ID + 1);
+
+        // internet validation failure
+        mWifiNetworkAgentCallbackCaptor.getValue().onValidationStatus(
+                NetworkAgent.VALIDATION_STATUS_NOT_VALID, null /* captivePortalUrl */);
+        mLooper.dispatchAll();
+
+        verify(mWifiConfigManager)
+                .incrementNetworkNoInternetAccessReports(FRAMEWORK_NETWORK_ID);
+        // expect not disabled
         verify(mWifiConfigManager, never()).updateNetworkSelectionStatus(
                 FRAMEWORK_NETWORK_ID, DISABLED_NO_INTERNET_TEMPORARY);
     }
 
     /**
-     * Verify that we temporarily disable the network when auto-connected to a network
-     * with no internet access.
+     * Verify that we do not set the user connect choice after a successful connection if the
+     * connection is not made by the user.
      */
     @Test
-    public void verifyAutoConnectedNoInternetExpectedNetworkWithInternetValidationFailure()
-            throws Exception {
-        // Simulate the first connection.
+    public void testNonSettingsConnectionNotSetUserConnectChoice() throws Exception {
+        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(false);
         connect();
-        ArgumentCaptor<INetworkAgent> agentCaptor = ArgumentCaptor.forClass(INetworkAgent.class);
-        verify(mConnectivityManager).registerNetworkAgent(agentCaptor.capture(),
-                any(NetworkInfo.class), any(LinkProperties.class), any(NetworkCapabilities.class),
-                any(), any(NetworkAgentConfig.class), anyInt());
+        verify(mWifiBlocklistMonitor).handleBssidConnectionSuccess(TEST_BSSID_STR, TEST_SSID);
+        verify(mWifiConfigManager).updateNetworkAfterConnect(eq(FRAMEWORK_NETWORK_ID), eq(false),
+                anyInt());
+    }
 
-        WifiConfiguration currentNetwork = new WifiConfiguration();
-        currentNetwork.networkId = FRAMEWORK_NETWORK_ID;
-        currentNetwork.noInternetAccessExpected = true;
-        currentNetwork.numNoInternetAccessReports = 1;
-        when(mWifiConfigManager.getConfiguredNetwork(FRAMEWORK_NETWORK_ID))
-                .thenReturn(currentNetwork);
-        when(mWifiConfigManager.getLastSelectedNetwork()).thenReturn(FRAMEWORK_NETWORK_ID + 1);
+    /**
+     * Verify that we do not set the user connect choice after connecting to a newly added network.
+     */
+    @Test
+    public void testNoSetUserConnectChoiceOnFirstConnection() throws Exception {
+        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(true);
+        connect();
+        verify(mWifiBlocklistMonitor).handleBssidConnectionSuccess(TEST_BSSID_STR, TEST_SSID);
+        verify(mWifiConfigManager).updateNetworkAfterConnect(eq(FRAMEWORK_NETWORK_ID), eq(false),
+                anyInt());
+    }
 
-        agentCaptor.getValue().onValidationStatusChanged(
-                NetworkAgent.INVALID_NETWORK, null /* captivePortalUrl */);
-        mLooper.dispatchAll();
-
-        verify(mWifiConfigManager)
-                .incrementNetworkNoInternetAccessReports(FRAMEWORK_NETWORK_ID);
-        verify(mWifiConfigManager, never()).updateNetworkSelectionStatus(
-                FRAMEWORK_NETWORK_ID, DISABLED_NO_INTERNET_TEMPORARY);
+    /**
+     * Verify that on the second successful connection to a network we set the user connect choice.
+     */
+    @Test
+    public void testConnectionSetUserConnectChoiceOnSecondConnection() throws Exception {
+        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(true);
+        mTestNetworkParams.hasEverConnected = true;
+        connect();
+        verify(mWifiBlocklistMonitor).handleBssidConnectionSuccess(TEST_BSSID_STR, TEST_SSID);
+        verify(mWifiConfigManager).updateNetworkAfterConnect(eq(FRAMEWORK_NETWORK_ID), eq(true),
+                anyInt());
     }
 
     /**
@@ -3672,19 +4230,18 @@
     public void verifyNetworkSelectionEnableOnInternetValidation() throws Exception {
         // Simulate the first connection.
         connect();
-        verify(mBssidBlocklistMonitor).handleBssidConnectionSuccess(sBSSID, sSSID);
-        verify(mBssidBlocklistMonitor).handleDhcpProvisioningSuccess(sBSSID, sSSID);
-        verify(mBssidBlocklistMonitor, never()).handleNetworkValidationSuccess(sBSSID, sSSID);
+        verify(mWifiBlocklistMonitor).handleBssidConnectionSuccess(TEST_BSSID_STR, TEST_SSID);
+        verify(mWifiBlocklistMonitor).handleDhcpProvisioningSuccess(TEST_BSSID_STR, TEST_SSID);
+        verify(mWifiBlocklistMonitor, never()).handleNetworkValidationSuccess(
+                TEST_BSSID_STR, TEST_SSID);
 
-        ArgumentCaptor<INetworkAgent> agentCaptor = ArgumentCaptor.forClass(INetworkAgent.class);
-        verify(mConnectivityManager).registerNetworkAgent(agentCaptor.capture(),
-                any(NetworkInfo.class), any(LinkProperties.class), any(NetworkCapabilities.class),
-                any(), any(NetworkAgentConfig.class), anyInt());
+        verify(mWifiInjector).makeWifiNetworkAgent(any(), any(), any(), any(),
+                mWifiNetworkAgentCallbackCaptor.capture());
 
         when(mWifiConfigManager.getLastSelectedNetwork()).thenReturn(FRAMEWORK_NETWORK_ID + 1);
 
-        agentCaptor.getValue().onValidationStatusChanged(
-                NetworkAgent.VALID_NETWORK, null /* captivePortalUrl */);
+        mWifiNetworkAgentCallbackCaptor.getValue().onValidationStatus(
+                NetworkAgent.VALIDATION_STATUS_VALID, null /* captivePortalUrl */);
         mLooper.dispatchAll();
 
         verify(mWifiConfigManager)
@@ -3692,24 +4249,89 @@
         verify(mWifiConfigManager).updateNetworkSelectionStatus(
                 FRAMEWORK_NETWORK_ID, DISABLED_NONE);
         verify(mWifiScoreCard).noteValidationSuccess(any());
-        verify(mBssidBlocklistMonitor).handleNetworkValidationSuccess(sBSSID, sSSID);
+        verify(mWifiBlocklistMonitor).handleNetworkValidationSuccess(TEST_BSSID_STR, TEST_SSID);
+    }
+
+    /**
+     * Verify that the logic clears the terms and conditions URL after we got a notification that
+     * the network was validated (i.e. the user accepted and internt access is available).
+     */
+    @Test
+    public void testTermsAndConditionsClearUrlAfterNetworkValidation() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        InOrder inOrder = inOrder(mWifiNetworkAgent);
+
+        // Simulate the first connection.
+        mConnectedNetwork = spy(WifiConfigurationTestUtil.createPasspointNetwork());
+        WnmData wnmData = WnmData.createTermsAndConditionsAccetanceRequiredEvent(TEST_BSSID,
+                TEST_TERMS_AND_CONDITIONS_URL);
+        when(mPasspointManager.handleTermsAndConditionsEvent(eq(wnmData),
+                any(WifiConfiguration.class))).thenReturn(new URL(TEST_TERMS_AND_CONDITIONS_URL));
+        connect(wnmData);
+        // Verify that link properties contains the T&C URL and captive is set to true
+        inOrder.verify(mWifiNetworkAgent)
+                .sendLinkProperties(argThat(linkProperties -> TEST_TERMS_AND_CONDITIONS_URL.equals(
+                        linkProperties.getCaptivePortalData().getUserPortalUrl().toString())
+                        && linkProperties.getCaptivePortalData().isCaptive()));
+        verify(mWifiBlocklistMonitor).handleBssidConnectionSuccess(TEST_BSSID_STR, TEST_SSID);
+        verify(mWifiBlocklistMonitor).handleDhcpProvisioningSuccess(TEST_BSSID_STR, TEST_SSID);
+        verify(mWifiBlocklistMonitor, never())
+                .handleNetworkValidationSuccess(TEST_BSSID_STR, TEST_SSID);
+        verify(mWifiInjector).makeWifiNetworkAgent(any(), any(), any(), any(),
+                mWifiNetworkAgentCallbackCaptor.capture());
+
+        when(mWifiConfigManager.getLastSelectedNetwork()).thenReturn(FRAMEWORK_NETWORK_ID + 1);
+        mWifiNetworkAgentCallbackCaptor.getValue().onValidationStatus(
+                NetworkAgent.VALIDATION_STATUS_VALID, null /* captivePortalUrl */);
+        mLooper.dispatchAll();
+
+        verify(mWifiConfigManager)
+                .setNetworkValidatedInternetAccess(FRAMEWORK_NETWORK_ID, true);
+        verify(mWifiConfigManager).updateNetworkSelectionStatus(
+                FRAMEWORK_NETWORK_ID, DISABLED_NONE);
+        verify(mWifiScoreCard).noteValidationSuccess(any());
+        verify(mWifiBlocklistMonitor).handleNetworkValidationSuccess(TEST_BSSID_STR, TEST_SSID);
+
+        // Now that the network has been validated, link properties must not have a T&C URL anymore
+        // and captive is set to false
+        inOrder.verify(mWifiNetworkAgent)
+                .sendLinkProperties(argThat(linkProperties ->
+                        linkProperties.getCaptivePortalData().getUserPortalUrl() == null
+                                && !linkProperties.getCaptivePortalData().isCaptive()));
     }
 
     private void connectWithValidInitRssi(int initRssiDbm) throws Exception {
         triggerConnect();
-        mCmi.getWifiInfo().setRssi(initRssiDbm);
+        mWifiInfo.setRssi(initRssiDbm);
         mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
-                new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.ASSOCIATED));
+                new StateChangeResult(0, TEST_WIFI_SSID, TEST_BSSID_STR,
+                        SupplicantState.ASSOCIATED));
         mLooper.dispatchAll();
 
-        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, sBSSID);
+        WifiSsid wifiSsid = WifiSsid.createFromByteArray(
+                NativeUtil.byteArrayFromArrayList(NativeUtil.decodeSsid(mConnectedNetwork.SSID)));
+        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT,
+                new NetworkConnectionEventInfo(0, wifiSsid, TEST_BSSID_STR, false));
         mLooper.dispatchAll();
 
         mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
-                new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.COMPLETED));
+                new StateChangeResult(0, TEST_WIFI_SSID, TEST_BSSID_STR,
+                        SupplicantState.COMPLETED));
         mLooper.dispatchAll();
 
-        assertEquals("ObtainingIpState", getCurrentState().getName());
+        assertEquals("L3ProvisioningState", getCurrentState().getName());
+
+        DhcpResultsParcelable dhcpResults = new DhcpResultsParcelable();
+        dhcpResults.baseConfiguration = new StaticIpConfiguration();
+        dhcpResults.baseConfiguration.gateway = InetAddresses.parseNumericAddress("1.2.3.4");
+        dhcpResults.baseConfiguration.ipAddress =
+                new LinkAddress(InetAddresses.parseNumericAddress("192.168.1.100"), 0);
+        dhcpResults.baseConfiguration.dnsServers.add(InetAddresses.parseNumericAddress("8.8.8.8"));
+        dhcpResults.leaseDuration = 3600;
+
+        injectDhcpSuccess(dhcpResults);
+        mLooper.dispatchAll();
+
     }
 
     /**
@@ -3719,26 +4341,24 @@
      */
     @Test
     public void verifyNetworkCapabilities() throws Exception {
-        when(mWifiDataStall.getTxThroughputKbps()).thenReturn(70_000);
-        when(mWifiDataStall.getRxThroughputKbps()).thenReturn(-1);
-        when(mWifiNetworkFactory.getSpecificNetworkRequestUidAndPackageName(any()))
+        mWifiInfo.setFrequency(5825);
+        when(mPerNetwork.getTxLinkBandwidthKbps()).thenReturn(40_000);
+        when(mPerNetwork.getRxLinkBandwidthKbps()).thenReturn(50_000);
+        when(mWifiNetworkFactory.getSpecificNetworkRequestUidAndPackageName(any(), any()))
                 .thenReturn(Pair.create(Process.INVALID_UID, ""));
         // Simulate the first connection.
         connectWithValidInitRssi(-42);
 
         ArgumentCaptor<NetworkCapabilities> networkCapabilitiesCaptor =
                 ArgumentCaptor.forClass(NetworkCapabilities.class);
-        verify(mConnectivityManager).registerNetworkAgent(any(),
-                any(NetworkInfo.class), any(LinkProperties.class),
-                networkCapabilitiesCaptor.capture(), any(), any(NetworkAgentConfig.class),
-                anyInt());
+        verify(mWifiInjector).makeWifiNetworkAgent(
+                networkCapabilitiesCaptor.capture(), any(), any(), any(), any());
 
         NetworkCapabilities networkCapabilities = networkCapabilitiesCaptor.getValue();
         assertNotNull(networkCapabilities);
 
         // Should have internet capability.
         assertTrue(networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET));
-        assertNull(networkCapabilities.getNetworkSpecifier());
 
         assertEquals(mConnectedNetwork.creatorUid, networkCapabilities.getOwnerUid());
         assertArrayEquals(
@@ -3746,9 +4366,19 @@
                 networkCapabilities.getAdministratorUids());
 
         // Should set bandwidth correctly
-        assertEquals(-42, mCmi.getWifiInfo().getRssi());
-        assertEquals(70_000, networkCapabilities.getLinkUpstreamBandwidthKbps());
-        assertEquals(70_000, networkCapabilities.getLinkDownstreamBandwidthKbps());
+        assertEquals(-42, mWifiInfo.getRssi());
+        assertEquals(40_000, networkCapabilities.getLinkUpstreamBandwidthKbps());
+        assertEquals(50_000, networkCapabilities.getLinkDownstreamBandwidthKbps());
+
+        // Should set band correctly.
+        // There is no accessor to get the band from the WifiNetworkAgentSpecifier, so match against
+        // a WifiNetworkSpecifier.
+        // TODO: should there be?
+        final NetworkSpecifier spec = networkCapabilities.getNetworkSpecifier();
+        assertTrue(spec instanceof WifiNetworkAgentSpecifier);
+        final WifiNetworkAgentSpecifier wnas = (WifiNetworkAgentSpecifier) spec;
+        assertTrue(wnas.satisfiesNetworkSpecifier(
+                new WifiNetworkSpecifier.Builder().setBand(ScanResult.WIFI_BAND_5_GHZ).build()));
     }
 
     /**
@@ -3758,18 +4388,18 @@
      */
     @Test
     public void verifyNetworkCapabilitiesForSpecificRequest() throws Exception {
-        when(mWifiDataStall.getTxThroughputKbps()).thenReturn(-1);
-        when(mWifiDataStall.getRxThroughputKbps()).thenReturn(-1);
-        when(mWifiNetworkFactory.getSpecificNetworkRequestUidAndPackageName(any()))
+        mWifiInfo.setFrequency(2437);
+        when(mPerNetwork.getTxLinkBandwidthKbps()).thenReturn(30_000);
+        when(mPerNetwork.getRxLinkBandwidthKbps()).thenReturn(40_000);
+        when(mWifiNetworkFactory.getSpecificNetworkRequestUidAndPackageName(any(), any()))
                 .thenReturn(Pair.create(TEST_UID, OP_PACKAGE_NAME));
         // Simulate the first connection.
         connectWithValidInitRssi(-42);
         ArgumentCaptor<NetworkCapabilities> networkCapabilitiesCaptor =
                 ArgumentCaptor.forClass(NetworkCapabilities.class);
-        verify(mConnectivityManager).registerNetworkAgent(any(),
-                any(NetworkInfo.class), any(LinkProperties.class),
-                networkCapabilitiesCaptor.capture(), any(), any(NetworkAgentConfig.class),
-                anyInt());
+
+        verify(mWifiInjector).makeWifiNetworkAgent(
+                networkCapabilitiesCaptor.capture(), any(), any(), any(), any());
 
         NetworkCapabilities networkCapabilities = networkCapabilitiesCaptor.getValue();
         assertNotNull(networkCapabilities);
@@ -3781,13 +4411,19 @@
         assertTrue(networkSpecifier instanceof WifiNetworkAgentSpecifier);
         WifiNetworkAgentSpecifier wifiNetworkAgentSpecifier =
                 (WifiNetworkAgentSpecifier) networkSpecifier;
+
+        // createNetworkAgentSpecifier does not write the BSSID to the current wifi configuration.
+        WifiConfiguration expectedConfig = new WifiConfiguration(
+                mCmi.getConnectedWifiConfiguration());
+        expectedConfig.BSSID = TEST_BSSID_STR;
         WifiNetworkAgentSpecifier expectedWifiNetworkAgentSpecifier =
-                new WifiNetworkAgentSpecifier(mCmi.getCurrentWifiConfiguration());
+                new WifiNetworkAgentSpecifier(expectedConfig, ScanResult.WIFI_BAND_24_GHZ,
+                        true /* matchLocalOnlySpecifiers */);
         assertEquals(expectedWifiNetworkAgentSpecifier, wifiNetworkAgentSpecifier);
         assertEquals(TEST_UID, networkCapabilities.getRequestorUid());
         assertEquals(OP_PACKAGE_NAME, networkCapabilities.getRequestorPackageName());
-        assertEquals(90_000, networkCapabilities.getLinkUpstreamBandwidthKbps());
-        assertEquals(80_000, networkCapabilities.getLinkDownstreamBandwidthKbps());
+        assertEquals(30_000, networkCapabilities.getLinkUpstreamBandwidthKbps());
+        assertEquals(40_000, networkCapabilities.getLinkDownstreamBandwidthKbps());
     }
 
     /**
@@ -3807,9 +4443,9 @@
         when(mWifiNative.getWifiLinkLayerStats(any())).thenReturn(newLLStats);
         mCmi.sendMessage(ClientModeImpl.CMD_RSSI_POLL, 1);
         mLooper.dispatchAll();
-        verify(mWifiDataStall).checkDataStallAndThroughputSufficiency(
-                oldLLStats, newLLStats, mCmi.getWifiInfo());
-        verify(mWifiMetrics).incrementWifiLinkLayerUsageStats(newLLStats);
+        verify(mWifiDataStall).checkDataStallAndThroughputSufficiency(WIFI_IFACE_NAME,
+                mConnectionCapabilities, oldLLStats, newLLStats, mWifiInfo);
+        verify(mWifiMetrics).incrementWifiLinkLayerUsageStats(WIFI_IFACE_NAME, newLLStats);
     }
 
     /**
@@ -3824,26 +4460,28 @@
 
         WifiLinkLayerStats stats = new WifiLinkLayerStats();
         when(mWifiNative.getWifiLinkLayerStats(any())).thenReturn(stats);
-        when(mWifiDataStall.checkDataStallAndThroughputSufficiency(any(), any(), any()))
+        when(mWifiDataStall.checkDataStallAndThroughputSufficiency(any(),
+                any(), any(), any(), any()))
                 .thenReturn(WifiIsUnusableEvent.TYPE_UNKNOWN);
         mCmi.sendMessage(ClientModeImpl.CMD_RSSI_POLL, 1);
         mLooper.dispatchAll();
-        verify(mWifiMetrics).updateWifiUsabilityStatsEntries(any(), eq(stats));
-        verify(mWifiMetrics, never()).addToWifiUsabilityStatsList(WifiUsabilityStats.LABEL_BAD,
-                eq(anyInt()), eq(-1));
+        verify(mWifiMetrics).updateWifiUsabilityStatsEntries(any(), any(), eq(stats));
+        verify(mWifiMetrics, never()).addToWifiUsabilityStatsList(any(),
+                WifiUsabilityStats.LABEL_BAD, eq(anyInt()), eq(-1));
 
-        when(mWifiDataStall.checkDataStallAndThroughputSufficiency(any(), any(), any()))
+        when(mWifiDataStall.checkDataStallAndThroughputSufficiency(any(), any(), any(), any(),
+                any()))
                 .thenReturn(WifiIsUnusableEvent.TYPE_DATA_STALL_BAD_TX);
         when(mClock.getElapsedSinceBootMillis()).thenReturn(10L);
         mCmi.sendMessage(ClientModeImpl.CMD_RSSI_POLL, 1);
         mLooper.dispatchAll();
-        verify(mWifiMetrics, times(2)).updateWifiUsabilityStatsEntries(any(), eq(stats));
+        verify(mWifiMetrics, times(2)).updateWifiUsabilityStatsEntries(any(), any(), eq(stats));
         when(mClock.getElapsedSinceBootMillis())
                 .thenReturn(10L + ClientModeImpl.DURATION_TO_WAIT_ADD_STATS_AFTER_DATA_STALL_MS);
         mCmi.sendMessage(ClientModeImpl.CMD_RSSI_POLL, 1);
         mLooper.dispatchAll();
-        verify(mWifiMetrics).addToWifiUsabilityStatsList(WifiUsabilityStats.LABEL_BAD,
-                WifiIsUnusableEvent.TYPE_DATA_STALL_BAD_TX, -1);
+        verify(mWifiMetrics).addToWifiUsabilityStatsList(WIFI_IFACE_NAME,
+                WifiUsabilityStats.LABEL_BAD, WifiIsUnusableEvent.TYPE_DATA_STALL_BAD_TX, -1);
     }
 
     /**
@@ -3852,9 +4490,11 @@
      */
     @Test
     public void verifySetPowerSaveTrueSuccess() throws Exception {
-        mCmi.setOperationalMode(ClientModeImpl.CONNECT_MODE, WIFI_IFACE_NAME);
-        assertTrue(mCmi.setPowerSave(true));
+        // called once during setup()
         verify(mWifiNative).setPowerSave(WIFI_IFACE_NAME, true);
+
+        assertTrue(mCmi.setPowerSave(true));
+        verify(mWifiNative, times(2)).setPowerSave(WIFI_IFACE_NAME, true);
     }
 
     /**
@@ -3863,24 +4503,11 @@
      */
     @Test
     public void verifySetPowerSaveFalseSuccess() throws Exception {
-        mCmi.setOperationalMode(ClientModeImpl.CONNECT_MODE, WIFI_IFACE_NAME);
         assertTrue(mCmi.setPowerSave(false));
         verify(mWifiNative).setPowerSave(WIFI_IFACE_NAME, false);
     }
 
     /**
-     * Verify that when interface is not created yet (InterfaceName is null),
-     * then setPowerSave() returns with error and no call in WifiNative happens.
-     */
-    @Test
-    public void verifySetPowerSaveFailure() throws Exception {
-        boolean powerSave = true;
-        mCmi.setOperationalMode(ClientModeImpl.DISABLED_MODE, null);
-        assertFalse(mCmi.setPowerSave(powerSave));
-        verify(mWifiNative, never()).setPowerSave(anyString(), anyBoolean());
-    }
-
-    /**
      * Verify that we call into WifiTrafficPoller during rssi poll
      */
     @Test
@@ -3899,14 +4526,14 @@
         mCmi.enableRssiPolling(true);
 
         connect();
-        // reset() should be called when RSSI polling is enabled and entering L2ConnectedState
+        // reset() should be called when RSSI polling is enabled and entering L2L3ConnectedState
         verify(mLinkProbeManager).resetOnNewConnection(); // called first time here
         verify(mLinkProbeManager, never()).resetOnScreenTurnedOn(); // not called
         verify(mLinkProbeManager).updateConnectionStats(any(), any());
 
         mCmi.enableRssiPolling(false);
         mLooper.dispatchAll();
-        // reset() should be called when in L2ConnectedState (or child states) and RSSI polling
+        // reset() should be called when in L2L3ConnectedState (or child states) and RSSI polling
         // becomes enabled
         mCmi.enableRssiPolling(true);
         mLooper.dispatchAll();
@@ -3954,9 +4581,21 @@
     @Test
     public void testGetFactoryMacAddressSuccess() throws Exception {
         initializeAndAddNetworkAndVerifySuccess();
+
+        clearInvocations(mWifiNative, mSettingsConfigStore);
+
         assertEquals(TEST_GLOBAL_MAC_ADDRESS.toString(), mCmi.getFactoryMacAddress());
-        verify(mWifiNative).getFactoryMacAddress(WIFI_IFACE_NAME);
-        verify(mWifiNative).getMacAddress(anyString()); // called once when setting up client mode
+        verify(mSettingsConfigStore).get(WIFI_STA_FACTORY_MAC_ADDRESS); // try config store.
+        verify(mWifiNative, never()).getStaFactoryMacAddress(WIFI_IFACE_NAME); // not native
+        verify(mSettingsConfigStore, never()).put(eq(WIFI_STA_FACTORY_MAC_ADDRESS), any());
+
+        clearInvocations(mWifiNative, mSettingsConfigStore);
+
+        // get it again, should now use the config store MAC address, not native.
+        assertEquals(TEST_GLOBAL_MAC_ADDRESS.toString(), mCmi.getFactoryMacAddress());
+        verify(mSettingsConfigStore).get(WIFI_STA_FACTORY_MAC_ADDRESS);
+
+        verifyNoMoreInteractions(mWifiNative, mSettingsConfigStore);
     }
 
     /**
@@ -3965,36 +4604,44 @@
     @Test
     public void testGetFactoryMacAddressFail() throws Exception {
         initializeAndAddNetworkAndVerifySuccess();
-        when(mWifiNative.getFactoryMacAddress(WIFI_IFACE_NAME)).thenReturn(null);
+
+        clearInvocations(mWifiNative, mSettingsConfigStore);
+
+        when(mSettingsConfigStore.get(WIFI_STA_FACTORY_MAC_ADDRESS)).thenReturn(null);
+        when(mWifiNative.getStaFactoryMacAddress(WIFI_IFACE_NAME)).thenReturn(null);
         assertNull(mCmi.getFactoryMacAddress());
-        verify(mWifiNative).getFactoryMacAddress(WIFI_IFACE_NAME);
-        verify(mWifiNative).getMacAddress(anyString()); // called once when setting up client mode
+        verify(mSettingsConfigStore).get(WIFI_STA_FACTORY_MAC_ADDRESS);
+        verify(mWifiNative).getStaFactoryMacAddress(WIFI_IFACE_NAME);
+
+        verifyNoMoreInteractions(mWifiNative, mSettingsConfigStore);
     }
 
     /**
-     * Verify that when WifiNative#getFactoryMacAddress fails, if the device does not support
+     * Verify that when WifiNative#getStaFactoryMacAddress fails, if the device does not support
      * MAC randomization then the currently programmed MAC address gets returned.
      */
     @Test
     public void testGetFactoryMacAddressFailWithNoMacRandomizationSupport() throws Exception {
-        mResources.setBoolean(R.bool.config_wifi_connected_mac_randomization_supported, false);
+        // reset mWifiNative since initializeCmi() was called in setup()
+        resetWifiNative();
+
+        when(mWifiGlobals.isConnectedMacRandomizationEnabled()).thenReturn(false);
         initializeCmi();
         initializeAndAddNetworkAndVerifySuccess();
-        when(mWifiNative.getFactoryMacAddress(WIFI_IFACE_NAME)).thenReturn(null);
-        mCmi.getFactoryMacAddress();
-        verify(mWifiNative).getFactoryMacAddress(anyString());
-        verify(mWifiNative, times(2)).getMacAddress(WIFI_IFACE_NAME);
-    }
 
-    @Test
-    public void testSetDeviceMobilityState() {
-        mCmi.setDeviceMobilityState(WifiManager.DEVICE_MOBILITY_STATE_STATIONARY);
-        verify(mWifiConnectivityManager).setDeviceMobilityState(
-                WifiManager.DEVICE_MOBILITY_STATE_STATIONARY);
-        verify(mWifiHealthMonitor).setDeviceMobilityState(
-                WifiManager.DEVICE_MOBILITY_STATE_STATIONARY);
-        verify(mWifiDataStall).setDeviceMobilityState(
-                WifiManager.DEVICE_MOBILITY_STATE_STATIONARY);
+        clearInvocations(mWifiNative, mSettingsConfigStore);
+
+        when(mSettingsConfigStore.get(WIFI_STA_FACTORY_MAC_ADDRESS)).thenReturn(null);
+        when(mWifiNative.getStaFactoryMacAddress(WIFI_IFACE_NAME)).thenReturn(null);
+        when(mWifiNative.getMacAddress(WIFI_IFACE_NAME))
+                .thenReturn(TEST_DEFAULT_MAC_ADDRESS.toString());
+        assertEquals(TEST_DEFAULT_MAC_ADDRESS.toString(), mCmi.getFactoryMacAddress());
+
+        verify(mSettingsConfigStore).get(WIFI_STA_FACTORY_MAC_ADDRESS);
+        verify(mWifiNative).getStaFactoryMacAddress(WIFI_IFACE_NAME);
+        verify(mWifiNative).getMacAddress(WIFI_IFACE_NAME);
+
+        verifyNoMoreInteractions(mWifiNative, mSettingsConfigStore);
     }
 
     /**
@@ -4003,8 +4650,7 @@
     @Test
     public void testRandomizeMacAddressOnStart() throws Exception {
         ArgumentCaptor<MacAddress> macAddressCaptor = ArgumentCaptor.forClass(MacAddress.class);
-        loadComponentsInStaMode();
-        verify(mWifiNative).setMacAddress(anyString(), macAddressCaptor.capture());
+        verify(mWifiNative).setStaMacAddress(anyString(), macAddressCaptor.capture());
         MacAddress currentMac = macAddressCaptor.getValue();
 
         assertNotEquals("The currently programmed MAC address should be different from the factory "
@@ -4017,9 +4663,12 @@
      */
     @Test
     public void testNoRandomizeMacAddressOnStartIfMacRandomizationNotEnabled() throws Exception {
-        mResources.setBoolean(R.bool.config_wifi_connected_mac_randomization_supported, false);
-        loadComponentsInStaMode();
-        verify(mWifiNative, never()).setMacAddress(anyString(), any());
+        // reset mWifiNative since initializeCmi() was called in setup()
+        resetWifiNative();
+
+        when(mWifiGlobals.isConnectedMacRandomizationEnabled()).thenReturn(false);
+        initializeCmi();
+        verify(mWifiNative, never()).setStaMacAddress(anyString(), any());
     }
 
     /**
@@ -4031,75 +4680,43 @@
 
         mCmi.sendMessage(ClientModeImpl.CMD_IP_REACHABILITY_LOST);
         mLooper.dispatchAll();
-        verify(mWifiDiagnostics).captureBugReportData(
+        verify(mWifiDiagnostics).triggerBugReportDataCapture(
                 eq(WifiDiagnostics.REPORT_REASON_REACHABILITY_LOST));
     }
 
     /**
-     * Verify removing sim will also remove an ephemeral Passpoint Provider. And reset carrier
-     * privileged suggestor apps.
-     */
-    @Test
-    public void testResetSimNetworkWhenRemovingSim() throws Exception {
-        // Switch to connect mode and verify wifi is reported as enabled
-        startSupplicantAndDispatchMessages();
-
-        // Indicate that sim is removed.
-        mCmi.sendMessage(ClientModeImpl.CMD_RESET_SIM_NETWORKS,
-                ClientModeImpl.RESET_SIM_REASON_SIM_REMOVED);
-        mLooper.dispatchAll();
-
-        verify(mWifiConfigManager).resetSimNetworks();
-        verify(mWifiNetworkSuggestionsManager).resetCarrierPrivilegedApps();
-    }
-
-    /**
-     * Verify inserting sim will reset carrier privileged suggestor apps.
-     * and remove any previous notifications due to sim removal
-     */
-    @Test
-    public void testResetCarrierPrivilegedAppsWhenInsertingSim() throws Exception {
-        // Switch to connect mode and verify wifi is reported as enabled
-        startSupplicantAndDispatchMessages();
-
-        // Indicate that sim is inserted.
-        mCmi.sendMessage(ClientModeImpl.CMD_RESET_SIM_NETWORKS,
-                ClientModeImpl.RESET_SIM_REASON_SIM_INSERTED);
-        mLooper.dispatchAll();
-
-        verify(mWifiConfigManager, never()).resetSimNetworks();
-        verify(mWifiNetworkSuggestionsManager).resetCarrierPrivilegedApps();
-        verify(mSimRequiredNotifier).dismissSimRequiredNotification();
-    }
-
-    /**
      * Verifies that WifiLastResortWatchdog is notified of FOURWAY_HANDSHAKE_TIMEOUT.
      */
     @Test
     public void testHandshakeTimeoutUpdatesWatchdog() throws Exception {
         // Setup CONNECT_MODE & a WifiConfiguration
         initializeAndAddNetworkAndVerifySuccess();
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
         mLooper.dispatchAll();
         // Verifies that WifiLastResortWatchdog won't be notified
         // by other reason code
-        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, 0, 2, sBSSID);
+        DisconnectEventInfo disconnectEventInfo =
+                new DisconnectEventInfo(TEST_SSID, TEST_BSSID_STR, 2, false);
+        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, disconnectEventInfo);
         mLooper.dispatchAll();
 
         assertEquals("DisconnectedState", getCurrentState().getName());
         verify(mWifiLastResortWatchdog, never()).noteConnectionFailureAndTriggerIfNeeded(
-                sSSID, sBSSID, WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                eq(TEST_SSID), eq(TEST_BSSID_STR),
+                eq(WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION), anyBoolean());
 
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
+        mLooper.dispatchAll();
         // Verifies that WifiLastResortWatchdog be notified
         // for FOURWAY_HANDSHAKE_TIMEOUT.
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
-        mLooper.dispatchAll();
-        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, 0, 15, sBSSID);
+        disconnectEventInfo = new DisconnectEventInfo(TEST_SSID, TEST_BSSID_STR, 15, false);
+        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, disconnectEventInfo);
         mLooper.dispatchAll();
 
         assertEquals("DisconnectedState", getCurrentState().getName());
         verify(mWifiLastResortWatchdog).noteConnectionFailureAndTriggerIfNeeded(
-                sSSID, sBSSID, WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                eq(TEST_SSID), eq(TEST_BSSID_STR),
+                eq(WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION), anyBoolean());
 
     }
 
@@ -4114,10 +4731,10 @@
         mConnectedNetwork.creatorName = OP_PACKAGE_NAME;
         connect();
 
-        assertTrue(mCmi.getWifiInfo().isEphemeral());
-        assertTrue(mCmi.getWifiInfo().isTrusted());
+        assertTrue(mWifiInfo.isEphemeral());
+        assertTrue(mWifiInfo.isTrusted());
         assertEquals(OP_PACKAGE_NAME,
-                mCmi.getWifiInfo().getRequestingPackageName());
+                mWifiInfo.getRequestingPackageName());
     }
 
     /**
@@ -4131,10 +4748,10 @@
         mConnectedNetwork.creatorName = OP_PACKAGE_NAME;
         connect();
 
-        assertTrue(mCmi.getWifiInfo().isEphemeral());
-        assertTrue(mCmi.getWifiInfo().isTrusted());
+        assertTrue(mWifiInfo.isEphemeral());
+        assertTrue(mWifiInfo.isTrusted());
         assertEquals(OP_PACKAGE_NAME,
-                mCmi.getWifiInfo().getRequestingPackageName());
+                mWifiInfo.getRequestingPackageName());
     }
 
     /**
@@ -4148,9 +4765,10 @@
 
         mCmi.sendMessage(ClientModeImpl.CMD_IP_REACHABILITY_LOST);
         mLooper.dispatchAll();
-        verify(mWifiMetrics).logWifiIsUnusableEvent(
+        verify(mWifiMetrics).logWifiIsUnusableEvent(WIFI_IFACE_NAME,
                 WifiIsUnusableEvent.TYPE_IP_REACHABILITY_LOST);
-        verify(mWifiMetrics).addToWifiUsabilityStatsList(WifiUsabilityStats.LABEL_BAD,
+        verify(mWifiMetrics).addToWifiUsabilityStatsList(WIFI_IFACE_NAME,
+                WifiUsabilityStats.LABEL_BAD,
                 WifiUsabilityStats.TYPE_IP_REACHABILITY_LOST, -1);
     }
 
@@ -4163,16 +4781,13 @@
         mConnectedNetwork = spy(WifiConfigurationTestUtil.createEapNetwork(
                 WifiEnterpriseConfig.Eap.SIM, WifiEnterpriseConfig.Phase2.NONE));
         mConnectedNetwork.SSID = DEFAULT_TEST_SSID;
+        String expectetIdentity = "13214561234567890@wlan.mnc456.mcc321.3gppnetwork.org";
 
-        when(mDataTelephonyManager.getSubscriberId()).thenReturn("3214561234567890");
-        when(mDataTelephonyManager.getSimState()).thenReturn(TelephonyManager.SIM_STATE_READY);
-        when(mDataTelephonyManager.getSimOperator()).thenReturn("321456");
-        when(mDataTelephonyManager.getCarrierInfoForImsiEncryption(anyInt())).thenReturn(null);
-        MockitoSession mockSession = ExtendedMockito.mockitoSession()
-                .mockStatic(SubscriptionManager.class)
-                .startMocking();
-        when(SubscriptionManager.getDefaultDataSubscriptionId()).thenReturn(DATA_SUBID);
-        when(SubscriptionManager.isValidSubscriptionId(anyInt())).thenReturn(true);
+        when(mWifiCarrierInfoManager.getBestMatchSubscriptionId(any(WifiConfiguration.class)))
+                .thenReturn(DATA_SUBID);
+        when(mWifiCarrierInfoManager.isSimReady(DATA_SUBID)).thenReturn(true);
+        when(mWifiCarrierInfoManager.getSimIdentity(any()))
+                .thenReturn(Pair.create(expectetIdentity, ""));
 
         triggerConnect();
 
@@ -4181,13 +4796,12 @@
         mLooper.dispatchAll();
 
         verify(mWifiNative).simIdentityResponse(WIFI_IFACE_NAME,
-                "13214561234567890@wlan.mnc456.mcc321.3gppnetwork.org", "");
-        mockSession.finishMocking();
+                expectetIdentity, "");
     }
 
     /**
      * Verifies that WifiLastResortWatchdog is notified of DHCP failures when recevied
-     * NETWORK_DISCONNECTION_EVENT while in ObtainingIpState.
+     * NETWORK_DISCONNECTION_EVENT while in L3ProvisioningState.
      */
     @Test
     public void testDhcpFailureUpdatesWatchdog_WhenDisconnectedWhileObtainingIpAddr()
@@ -4196,29 +4810,29 @@
 
         verify(mWifiNative).removeAllNetworks(WIFI_IFACE_NAME);
 
-        IActionListener connectActionListener = mock(IActionListener.class);
-        mCmi.connect(null, 0, mock(Binder.class), connectActionListener, 0, Binder.getCallingUid());
-        mLooper.dispatchAll();
-        verify(connectActionListener).onSuccess();
+        startConnectSuccess();
 
-        verify(mWifiConfigManager).enableNetwork(eq(0), eq(true), anyInt(), any());
-
-        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, sBSSID);
+        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT,
+                new NetworkConnectionEventInfo(0, TEST_WIFI_SSID, TEST_BSSID_STR, false));
         mLooper.dispatchAll();
 
         mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
-                new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.COMPLETED));
+                new StateChangeResult(0, TEST_WIFI_SSID, TEST_BSSID_STR,
+                        SupplicantState.COMPLETED));
         mLooper.dispatchAll();
 
-        assertEquals("ObtainingIpState", getCurrentState().getName());
+        assertEquals("L3ProvisioningState", getCurrentState().getName());
 
         // Verifies that WifiLastResortWatchdog be notified.
-        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, 0, 0, sBSSID);
+        DisconnectEventInfo disconnectEventInfo =
+                new DisconnectEventInfo(TEST_SSID, TEST_BSSID_STR, 0, false);
+        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, disconnectEventInfo);
         mLooper.dispatchAll();
 
         assertEquals("DisconnectedState", getCurrentState().getName());
         verify(mWifiLastResortWatchdog).noteConnectionFailureAndTriggerIfNeeded(
-                sSSID, sBSSID, WifiLastResortWatchdog.FAILURE_CODE_DHCP);
+                eq(TEST_SSID), eq(TEST_BSSID_STR),
+                eq(WifiLastResortWatchdog.FAILURE_CODE_DHCP), anyBoolean());
     }
 
     /**
@@ -4235,7 +4849,7 @@
         mLooper.dispatchAll();
 
         verify(mWifiNative).removeNetworkCachedData(FRAMEWORK_NETWORK_ID);
-        assertEquals("DisconnectingState", getCurrentState().getName());
+        verify(mWifiNative).disconnect(WIFI_IFACE_NAME);
     }
 
     /**
@@ -4252,7 +4866,7 @@
                 WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD);
         mLooper.dispatchAll();
 
-        assertEquals("DisconnectingState", getCurrentState().getName());
+        verify(mWifiNative).disconnect(WIFI_IFACE_NAME);
     }
 
     /**
@@ -4269,7 +4883,7 @@
                 WifiConfiguration.NetworkSelectionStatus.DISABLED_NO_INTERNET_PERMANENT);
         mLooper.dispatchAll();
 
-        assertEquals("ConnectedState", getCurrentState().getName());
+        assertEquals("L3ConnectedState", getCurrentState().getName());
     }
 
     /**
@@ -4286,7 +4900,7 @@
                 WifiConfiguration.NetworkSelectionStatus.DISABLED_NO_INTERNET_TEMPORARY);
         mLooper.dispatchAll();
 
-        assertEquals("ConnectedState", getCurrentState().getName());
+        assertEquals("L3ConnectedState", getCurrentState().getName());
     }
 
     /**
@@ -4294,48 +4908,46 @@
      */
     @Test
     public void verifyMboOceWifiDataStallSetupInClientMode() throws Exception {
-        startSupplicantAndDispatchMessages();
         verify(mMboOceController).enable();
-        verify(mWifiDataStall).enablePhoneStateListener();
-        mCmi.setOperationalMode(ClientModeImpl.DISABLED_MODE, null);
+        mCmi.stop();
         mLooper.dispatchAll();
         verify(mMboOceController).disable();
-        verify(mWifiDataStall).disablePhoneStateListener();
     }
 
-    /**
-     * Verify that Bluetooth active is set correctly with BT state/connection state changes
-     */
     @Test
-    public void verifyBluetoothStateAndConnectionStateChanges() throws Exception {
-        startSupplicantAndDispatchMessages();
-        mCmi.sendBluetoothAdapterStateChange(BluetoothAdapter.STATE_ON);
-        mLooper.dispatchAll();
-        verify(mWifiConnectivityManager, times(1)).setBluetoothConnected(false);
+    public void verifyWifiMonitorHandlersDeregisteredOnStop() throws Exception {
+        verify(mWifiMonitor, atLeastOnce())
+                .registerHandler(eq(WIFI_IFACE_NAME), anyInt(), any());
+        verify(mWifiMetrics).registerForWifiMonitorEvents(WIFI_IFACE_NAME);
+        verify(mWifiLastResortWatchdog).registerForWifiMonitorEvents(WIFI_IFACE_NAME);
 
-        mCmi.sendBluetoothAdapterConnectionStateChange(BluetoothAdapter.STATE_CONNECTED);
+        mCmi.stop();
         mLooper.dispatchAll();
-        verify(mWifiConnectivityManager, times(1)).setBluetoothConnected(true);
 
-        mCmi.sendBluetoothAdapterStateChange(BluetoothAdapter.STATE_OFF);
-        mLooper.dispatchAll();
-        verify(mWifiConnectivityManager, times(2)).setBluetoothConnected(false);
+        verify(mWifiMonitor, atLeastOnce())
+                .deregisterHandler(eq(WIFI_IFACE_NAME), anyInt(), any());
+        verify(mWifiMetrics).deregisterForWifiMonitorEvents(WIFI_IFACE_NAME);
+        verify(mWifiLastResortWatchdog).deregisterForWifiMonitorEvents(WIFI_IFACE_NAME);
+    }
 
-        mCmi.sendBluetoothAdapterStateChange(BluetoothAdapter.STATE_ON);
-        mLooper.dispatchAll();
-        verify(mWifiConnectivityManager, times(3)).setBluetoothConnected(false);
+    @Test
+    public void onBluetoothConnectionStateChanged() throws Exception {
+        // reset mWifiNative since initializeCmi() was called in setup()
+        resetWifiNative();
 
-        mCmi.sendBluetoothAdapterConnectionStateChange(BluetoothAdapter.STATE_CONNECTING);
-        mLooper.dispatchAll();
-        verify(mWifiConnectivityManager, times(2)).setBluetoothConnected(true);
+        when(mWifiGlobals.isBluetoothConnected()).thenReturn(false);
+        initializeCmi();
+        verify(mWifiNative).setBluetoothCoexistenceScanMode(any(), eq(false));
 
-        mCmi.sendBluetoothAdapterConnectionStateChange(BluetoothAdapter.STATE_DISCONNECTED);
+        when(mWifiGlobals.isBluetoothConnected()).thenReturn(true);
+        mCmi.onBluetoothConnectionStateChanged();
         mLooper.dispatchAll();
-        verify(mWifiConnectivityManager, times(4)).setBluetoothConnected(false);
+        verify(mWifiNative).setBluetoothCoexistenceScanMode(any(), eq(true));
 
-        mCmi.sendBluetoothAdapterConnectionStateChange(BluetoothAdapter.STATE_CONNECTED);
+        when(mWifiGlobals.isBluetoothConnected()).thenReturn(false);
+        mCmi.onBluetoothConnectionStateChanged();
         mLooper.dispatchAll();
-        verify(mWifiConnectivityManager, times(3)).setBluetoothConnected(true);
+        verify(mWifiNative, times(2)).setBluetoothCoexistenceScanMode(any(), eq(false));
     }
 
     /**
@@ -4345,7 +4957,7 @@
      */
     @Test
     public void testBtmFrameWithMboAssocretryDelayBlockListTheBssid() throws Exception {
-        // Connect to network with |sBSSID|, |sFreq|.
+        // Connect to network with |TEST_BSSID_STR|, |sFreq|.
         connect();
 
         MboOceController.BtmFrameData btmFrmData = new MboOceController.BtmFrameData();
@@ -4359,7 +4971,7 @@
         mLooper.dispatchAll();
 
         verify(mWifiMetrics, times(1)).incrementSteeringRequestCountIncludingMboAssocRetryDelay();
-        verify(mBssidBlocklistMonitor).blockBssidForDurationMs(eq(sBSSID), eq(sSSID),
+        verify(mWifiBlocklistMonitor).blockBssidForDurationMs(eq(TEST_BSSID_STR), eq(TEST_SSID),
                 eq(btmFrmData.mBlockListDurationMs), anyInt(), anyInt());
     }
 
@@ -4370,7 +4982,7 @@
      */
     @Test
     public void testBtmFrameWithDisassocImminentBitBlockListTheBssid() throws Exception {
-        // Connect to network with |sBSSID|, |sFreq|.
+        // Connect to network with |TEST_BSSID_STR|, |sFreq|.
         connect();
 
         MboOceController.BtmFrameData btmFrmData = new MboOceController.BtmFrameData();
@@ -4382,7 +4994,7 @@
         mLooper.dispatchAll();
 
         verify(mWifiMetrics, never()).incrementSteeringRequestCountIncludingMboAssocRetryDelay();
-        verify(mBssidBlocklistMonitor).blockBssidForDurationMs(eq(sBSSID), eq(sSSID),
+        verify(mWifiBlocklistMonitor).blockBssidForDurationMs(eq(TEST_BSSID_STR), eq(TEST_SSID),
                 eq(MboOceConstants.DEFAULT_BLOCKLIST_DURATION_MS), anyInt(), anyInt());
     }
 
@@ -4392,7 +5004,7 @@
      */
     @Test
     public void testBTMRequestRejectTriggerNetworkSelction() throws Exception {
-        // Connect to network with |sBSSID|, |sFreq|.
+        // Connect to network with |TEST_BSSID_STR|, |sFreq|.
         connect();
 
         MboOceController.BtmFrameData btmFrmData = new MboOceController.BtmFrameData();
@@ -4406,8 +5018,8 @@
         mCmi.sendMessage(WifiMonitor.MBO_OCE_BSS_TM_HANDLING_DONE, btmFrmData);
         mLooper.dispatchAll();
 
-        verify(mBssidBlocklistMonitor, never()).blockBssidForDurationMs(eq(sBSSID), eq(sSSID),
-                eq(btmFrmData.mBlockListDurationMs), anyInt(), anyInt());
+        verify(mWifiBlocklistMonitor, never()).blockBssidForDurationMs(eq(TEST_BSSID_STR),
+                eq(TEST_SSID), eq(btmFrmData.mBlockListDurationMs), anyInt(), anyInt());
         verify(mWifiConnectivityManager).forceConnectivityScan(ClientModeImpl.WIFI_WORK_SOURCE);
         verify(mWifiMetrics, times(1)).incrementMboCellularSwitchRequestCount();
         verify(mWifiMetrics, times(1))
@@ -4421,7 +5033,7 @@
      */
     @Test
     public void testBTMRequestAcceptDoNotTriggerNetworkSelction() throws Exception {
-        // Connect to network with |sBSSID|, |sFreq|.
+        // Connect to network with |TEST_BSSID_STR|, |sFreq|.
         connect();
 
         MboOceController.BtmFrameData btmFrmData = new MboOceController.BtmFrameData();
@@ -4436,301 +5048,6 @@
                 .forceConnectivityScan(ClientModeImpl.WIFI_WORK_SOURCE);
     }
 
-    /**
-     * Test that the interval for poll RSSI is read from config overlay correctly.
-     */
-    @Test
-    public void testPollRssiIntervalIsSetCorrectly() throws Exception {
-        assertEquals(3000, mCmi.getPollRssiIntervalMsecs());
-        mResources.setInteger(R.integer.config_wifiPollRssiIntervalMilliseconds, 6000);
-        assertEquals(6000, mCmi.getPollRssiIntervalMsecs());
-        mResources.setInteger(R.integer.config_wifiPollRssiIntervalMilliseconds, 7000);
-        assertEquals(6000, mCmi.getPollRssiIntervalMsecs());
-    }
-
-    /**
-     * Verifies that the logic does not modify PSK key management when WPA3 auto upgrade feature is
-     * disabled.
-     *
-     * @throws Exception
-     */
-    @Test
-    public void testNoWpa3UpgradeWhenOverlaysAreOff() throws Exception {
-        initializeAndAddNetworkAndVerifySuccess();
-        assertEquals(ClientModeImpl.CONNECT_MODE, mCmi.getOperationalModeForTest());
-        assertEquals(WifiManager.WIFI_STATE_ENABLED, mCmi.syncGetWifiState());
-
-        WifiConfiguration config = mock(WifiConfiguration.class);
-        BitSet allowedKeyManagement = mock(BitSet.class);
-        config.allowedKeyManagement = allowedKeyManagement;
-        when(config.allowedKeyManagement.get(eq(WifiConfiguration.KeyMgmt.WPA_PSK))).thenReturn(
-                true);
-        when(config.getNetworkSelectionStatus())
-                .thenReturn(new WifiConfiguration.NetworkSelectionStatus());
-        when(mWifiConfigManager.getConfiguredNetworkWithoutMasking(0)).thenReturn(config);
-        mResources.setBoolean(R.bool.config_wifiSaeUpgradeEnabled, false);
-        mResources.setBoolean(R.bool.config_wifiSaeUpgradeOffloadEnabled, false);
-
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
-        mLooper.dispatchAll();
-
-        verify(config, never()).setSecurityParams(eq(WifiConfiguration.SECURITY_TYPE_SAE));
-    }
-
-    /**
-     * Verifies that the logic always enables SAE key management when WPA3 auto upgrade offload
-     * feature is enabled.
-     *
-     * @throws Exception
-     */
-    @Test
-    public void testWpa3UpgradeOffload() throws Exception {
-        initializeAndAddNetworkAndVerifySuccess();
-        assertEquals(ClientModeImpl.CONNECT_MODE, mCmi.getOperationalModeForTest());
-        assertEquals(WifiManager.WIFI_STATE_ENABLED, mCmi.syncGetWifiState());
-
-        WifiConfiguration config = mock(WifiConfiguration.class);
-        BitSet allowedKeyManagement = mock(BitSet.class);
-        BitSet allowedAuthAlgorithms = mock(BitSet.class);
-        config.allowedKeyManagement = allowedKeyManagement;
-        config.allowedAuthAlgorithms = allowedAuthAlgorithms;
-        when(config.allowedKeyManagement.get(eq(WifiConfiguration.KeyMgmt.WPA_PSK))).thenReturn(
-                true);
-        when(config.getNetworkSelectionStatus())
-                .thenReturn(new WifiConfiguration.NetworkSelectionStatus());
-        when(mWifiConfigManager.getConfiguredNetworkWithoutMasking(0)).thenReturn(config);
-        mResources.setBoolean(R.bool.config_wifiSaeUpgradeEnabled, true);
-        mResources.setBoolean(R.bool.config_wifiSaeUpgradeOffloadEnabled, true);
-
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
-        mLooper.dispatchAll();
-
-        verify(allowedKeyManagement).set(eq(WifiConfiguration.KeyMgmt.SAE));
-        verify(allowedAuthAlgorithms).clear();
-    }
-
-    /**
-     * Verifies that the logic does not enable SAE key management when WPA3 auto upgrade feature is
-     * enabled but no SAE candidate is available.
-     *
-     * @throws Exception
-     */
-    @Test
-    public void testNoWpa3UpgradeWithPskCandidate() throws Exception {
-        initializeAndAddNetworkAndVerifySuccess();
-        assertEquals(ClientModeImpl.CONNECT_MODE, mCmi.getOperationalModeForTest());
-        assertEquals(WifiManager.WIFI_STATE_ENABLED, mCmi.syncGetWifiState());
-
-        WifiConfiguration config = mock(WifiConfiguration.class);
-        BitSet allowedKeyManagement = mock(BitSet.class);
-        config.allowedKeyManagement = allowedKeyManagement;
-        when(config.allowedKeyManagement.get(eq(WifiConfiguration.KeyMgmt.WPA_PSK)))
-                .thenReturn(true);
-        WifiConfiguration.NetworkSelectionStatus networkSelectionStatus =
-                mock(WifiConfiguration.NetworkSelectionStatus.class);
-        when(config.getNetworkSelectionStatus()).thenReturn(networkSelectionStatus);
-        when(mWifiConfigManager.getConfiguredNetworkWithoutMasking(0)).thenReturn(config);
-        mResources.setBoolean(R.bool.config_wifiSaeUpgradeEnabled, true);
-        mResources.setBoolean(R.bool.config_wifiSaeUpgradeOffloadEnabled, false);
-
-        String ssid = "WPA2-Network";
-        String caps = "[WPA2-FT/PSK+PSK][ESS][WPS]";
-        ScanResult scanResult = new ScanResult(WifiSsid.createFromAsciiEncoded(ssid), ssid,
-                "ab:cd:01:ef:45:89", 1245, 0, caps, -78, 2450, 1025, 22, 33, 20, 0,
-                0, true);
-        scanResult.informationElements = new ScanResult.InformationElement[]{
-                createIE(ScanResult.InformationElement.EID_SSID,
-                        ssid.getBytes(StandardCharsets.UTF_8))
-        };
-        when(networkSelectionStatus.getCandidate()).thenReturn(scanResult);
-
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
-        mLooper.dispatchAll();
-
-        verify(config, never()).setSecurityParams(eq(WifiConfiguration.SECURITY_TYPE_SAE));
-    }
-
-    /**
-     * Verifies that the logic enables SAE key management when WPA3 auto upgrade feature is
-     * enabled and an SAE candidate is available.
-     *
-     * @throws Exception
-     */
-    @Test
-    public void testWpa3UpgradeWithSaeCandidate() throws Exception {
-        initializeAndAddNetworkAndVerifySuccess();
-        assertEquals(ClientModeImpl.CONNECT_MODE, mCmi.getOperationalModeForTest());
-        assertEquals(WifiManager.WIFI_STATE_ENABLED, mCmi.syncGetWifiState());
-
-        WifiConfiguration config = mock(WifiConfiguration.class);
-        BitSet allowedKeyManagement = mock(BitSet.class);
-        BitSet allowedAuthAlgorithms = mock(BitSet.class);
-        BitSet allowedProtocols = mock(BitSet.class);
-        BitSet allowedPairwiseCiphers = mock(BitSet.class);
-        BitSet allowedGroupCiphers = mock(BitSet.class);
-        config.allowedKeyManagement = allowedKeyManagement;
-        config.allowedAuthAlgorithms = allowedAuthAlgorithms;
-        config.allowedProtocols = allowedProtocols;
-        config.allowedPairwiseCiphers = allowedPairwiseCiphers;
-        config.allowedGroupCiphers = allowedGroupCiphers;
-        when(config.allowedKeyManagement.get(eq(WifiConfiguration.KeyMgmt.WPA_PSK)))
-                .thenReturn(true);
-        WifiConfiguration.NetworkSelectionStatus networkSelectionStatus =
-                mock(WifiConfiguration.NetworkSelectionStatus.class);
-        when(config.getNetworkSelectionStatus()).thenReturn(networkSelectionStatus);
-        when(mWifiConfigManager.getConfiguredNetworkWithoutMasking(anyInt())).thenReturn(config);
-        when(mWifiConfigManager.getScanDetailCacheForNetwork(anyInt())).thenReturn(null);
-        mResources.setBoolean(R.bool.config_wifiSaeUpgradeEnabled, true);
-        mResources.setBoolean(R.bool.config_wifiSaeUpgradeOffloadEnabled, false);
-
-        final String ssid = "WPA3-Network";
-        String caps = "[WPA2-FT/SAE+SAE][ESS][WPS]";
-        ScanResult scanResult = new ScanResult(WifiSsid.createFromAsciiEncoded(ssid), ssid,
-                "ab:cd:01:ef:45:89", 1245, 0, caps, -78, 2450, 1025, 22, 33, 20, 0,
-                0, true);
-        scanResult.informationElements = new ScanResult.InformationElement[]{
-                createIE(ScanResult.InformationElement.EID_SSID,
-                        ssid.getBytes(StandardCharsets.UTF_8))
-        };
-        when(networkSelectionStatus.getCandidate()).thenReturn(scanResult);
-
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
-        mLooper.dispatchAll();
-
-        verify(config).setSecurityParams(eq(WifiConfiguration.SECURITY_TYPE_SAE));
-    }
-
-    /**
-     * Verifies that the logic does not enable SAE key management when WPA3 auto upgrade feature is
-     * enabled and an SAE candidate is available, while another WPA2 AP is in range.
-     *
-     * @throws Exception
-     */
-    @Test
-    public void testWpa3UpgradeWithSaeCandidateAndPskApInRange() throws Exception {
-        initializeAndAddNetworkAndVerifySuccess();
-        assertEquals(ClientModeImpl.CONNECT_MODE, mCmi.getOperationalModeForTest());
-        assertEquals(WifiManager.WIFI_STATE_ENABLED, mCmi.syncGetWifiState());
-
-        WifiConfiguration config = mock(WifiConfiguration.class);
-        BitSet allowedKeyManagement = mock(BitSet.class);
-        BitSet allowedAuthAlgorithms = mock(BitSet.class);
-        BitSet allowedProtocols = mock(BitSet.class);
-        BitSet allowedPairwiseCiphers = mock(BitSet.class);
-        BitSet allowedGroupCiphers = mock(BitSet.class);
-        config.allowedKeyManagement = allowedKeyManagement;
-        config.allowedAuthAlgorithms = allowedAuthAlgorithms;
-        config.allowedProtocols = allowedProtocols;
-        config.allowedPairwiseCiphers = allowedPairwiseCiphers;
-        config.allowedGroupCiphers = allowedGroupCiphers;
-        when(config.allowedKeyManagement.get(eq(WifiConfiguration.KeyMgmt.WPA_PSK)))
-                .thenReturn(true);
-        WifiConfiguration.NetworkSelectionStatus networkSelectionStatus =
-                mock(WifiConfiguration.NetworkSelectionStatus.class);
-        when(config.getNetworkSelectionStatus()).thenReturn(networkSelectionStatus);
-        when(mWifiConfigManager.getConfiguredNetworkWithoutMasking(anyInt())).thenReturn(config);
-        when(mWifiConfigManager.getScanDetailCacheForNetwork(anyInt())).thenReturn(null);
-        mResources.setBoolean(R.bool.config_wifiSaeUpgradeEnabled, true);
-        mResources.setBoolean(R.bool.config_wifiSaeUpgradeOffloadEnabled, false);
-
-        final String saeBssid = "ab:cd:01:ef:45:89";
-        final String pskBssid = "ab:cd:01:ef:45:9a";
-
-        final String ssidSae = "Mixed-Network";
-        config.SSID = ScanResultUtil.createQuotedSSID(ssidSae);
-        config.networkId = 1;
-
-        String capsSae = "[WPA2-FT/SAE+SAE][ESS][WPS]";
-        ScanResult scanResultSae = new ScanResult(WifiSsid.createFromAsciiEncoded(ssidSae), ssidSae,
-                saeBssid, 1245, 0, capsSae, -78, 2412, 1025, 22, 33, 20, 0,
-                0, true);
-        ScanResult.InformationElement ieSae = createIE(ScanResult.InformationElement.EID_SSID,
-                ssidSae.getBytes(StandardCharsets.UTF_8));
-        scanResultSae.informationElements = new ScanResult.InformationElement[]{ieSae};
-        when(networkSelectionStatus.getCandidate()).thenReturn(scanResultSae);
-        ScanResult.InformationElement[] ieArr = new ScanResult.InformationElement[1];
-        ieArr[0] = ieSae;
-
-        final String ssidPsk = "Mixed-Network";
-        String capsPsk = "[WPA2-FT/PSK+PSK][ESS][WPS]";
-        ScanResult scanResultPsk = new ScanResult(WifiSsid.createFromAsciiEncoded(ssidPsk), ssidPsk,
-                pskBssid, 1245, 0, capsPsk, -48, 2462, 1025, 22, 33, 20, 0,
-                0, true);
-        ScanResult.InformationElement iePsk = createIE(ScanResult.InformationElement.EID_SSID,
-                ssidPsk.getBytes(StandardCharsets.UTF_8));
-        scanResultPsk.informationElements = new ScanResult.InformationElement[]{iePsk};
-        ScanResult.InformationElement[] ieArrPsk = new ScanResult.InformationElement[1];
-        ieArrPsk[0] = iePsk;
-        List<ScanResult> scanResults = new ArrayList<>();
-        scanResults.add(scanResultPsk);
-        scanResults.add(scanResultSae);
-
-        when(mScanRequestProxy.getScanResults()).thenReturn(scanResults);
-
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
-        mLooper.dispatchAll();
-
-        verify(config, never()).setSecurityParams(eq(WifiConfiguration.SECURITY_TYPE_SAE));
-    }
-
-    /**
-     * Verifies that the logic enables SAE key management when WPA3 auto upgrade feature is
-     * enabled and no candidate is available (i.e. user selected a WPA2 saved network and the
-     * network is actually WPA3.
-     *
-     * @throws Exception
-     */
-    @Test
-    public void testWpa3UpgradeWithNoCandidate() throws Exception {
-        initializeAndAddNetworkAndVerifySuccess();
-        assertEquals(ClientModeImpl.CONNECT_MODE, mCmi.getOperationalModeForTest());
-        assertEquals(WifiManager.WIFI_STATE_ENABLED, mCmi.syncGetWifiState());
-
-        WifiConfiguration config = mock(WifiConfiguration.class);
-        BitSet allowedKeyManagement = mock(BitSet.class);
-        BitSet allowedAuthAlgorithms = mock(BitSet.class);
-        BitSet allowedProtocols = mock(BitSet.class);
-        BitSet allowedPairwiseCiphers = mock(BitSet.class);
-        BitSet allowedGroupCiphers = mock(BitSet.class);
-        config.allowedKeyManagement = allowedKeyManagement;
-        config.allowedAuthAlgorithms = allowedAuthAlgorithms;
-        config.allowedProtocols = allowedProtocols;
-        config.allowedPairwiseCiphers = allowedPairwiseCiphers;
-        config.allowedGroupCiphers = allowedGroupCiphers;
-        when(config.allowedKeyManagement.get(eq(WifiConfiguration.KeyMgmt.WPA_PSK)))
-                .thenReturn(true);
-        WifiConfiguration.NetworkSelectionStatus networkSelectionStatus =
-                mock(WifiConfiguration.NetworkSelectionStatus.class);
-        when(config.getNetworkSelectionStatus()).thenReturn(networkSelectionStatus);
-        when(mWifiConfigManager.getConfiguredNetworkWithoutMasking(anyInt())).thenReturn(config);
-
-        mResources.setBoolean(R.bool.config_wifiSaeUpgradeEnabled, true);
-        mResources.setBoolean(R.bool.config_wifiSaeUpgradeOffloadEnabled, false);
-
-        final String saeBssid = "ab:cd:01:ef:45:89";
-        final String ssidSae = "WPA3-Network";
-        config.SSID = ScanResultUtil.createQuotedSSID(ssidSae);
-        config.networkId = 1;
-
-        String capsSae = "[WPA2-FT/SAE+SAE][ESS][WPS]";
-        ScanResult scanResultSae = new ScanResult(WifiSsid.createFromAsciiEncoded(ssidSae), ssidSae,
-                saeBssid, 1245, 0, capsSae, -78, 2412, 1025, 22, 33, 20, 0,
-                0, true);
-        ScanResult.InformationElement ieSae = createIE(ScanResult.InformationElement.EID_SSID,
-                ssidSae.getBytes(StandardCharsets.UTF_8));
-        scanResultSae.informationElements = new ScanResult.InformationElement[]{ieSae};
-        when(networkSelectionStatus.getCandidate()).thenReturn(null);
-        List<ScanResult> scanResults = new ArrayList<>();
-        scanResults.add(scanResultSae);
-
-        when(mScanRequestProxy.getScanResults()).thenReturn(scanResults);
-
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
-        mLooper.dispatchAll();
-
-        verify(config).setSecurityParams(eq(WifiConfiguration.SECURITY_TYPE_SAE));
-    }
-
     private static ScanResult.InformationElement createIE(int id, byte[] bytes) {
         ScanResult.InformationElement ie = new ScanResult.InformationElement();
         ie.id = id;
@@ -4738,74 +5055,6 @@
         return ie;
     }
 
-    /*
-     * Verify isWifiBandSupported for 5GHz with an overlay override config
-     */
-    @Test
-    public void testIsWifiBandSupported5gWithOverride() throws Exception {
-        mResources.setBoolean(R.bool.config_wifi5ghzSupport, true);
-        assertTrue(mCmi.isWifiBandSupported(WifiScanner.WIFI_BAND_5_GHZ));
-        verify(mWifiNative, never()).getChannelsForBand(anyInt());
-    }
-
-    /**
-     * Verify isWifiBandSupported for 6GHz with an overlay override config
-     */
-    @Test
-    public void testIsWifiBandSupported6gWithOverride() throws Exception {
-        mResources.setBoolean(R.bool.config_wifi6ghzSupport, true);
-        assertTrue(mCmi.isWifiBandSupported(WifiScanner.WIFI_BAND_6_GHZ));
-        verify(mWifiNative, never()).getChannelsForBand(anyInt());
-    }
-
-    /**
-     * Verify isWifiBandSupported for 5GHz with no overlay override config no channels
-     */
-    @Test
-    public void testIsWifiBandSupported5gNoOverrideNoChannels() throws Exception {
-        final int[] emptyArray = {};
-        mResources.setBoolean(R.bool.config_wifi5ghzSupport, false);
-        when(mWifiNative.getChannelsForBand(anyInt())).thenReturn(emptyArray);
-        assertFalse(mCmi.isWifiBandSupported(WifiScanner.WIFI_BAND_5_GHZ));
-        verify(mWifiNative).getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ);
-    }
-
-    /**
-     * Verify isWifiBandSupported for 5GHz with no overlay override config with channels
-     */
-    @Test
-    public void testIsWifiBandSupported5gNoOverrideWithChannels() throws Exception {
-        final int[] channelArray = {5170};
-        mResources.setBoolean(R.bool.config_wifi5ghzSupport, false);
-        when(mWifiNative.getChannelsForBand(anyInt())).thenReturn(channelArray);
-        assertTrue(mCmi.isWifiBandSupported(WifiScanner.WIFI_BAND_5_GHZ));
-        verify(mWifiNative).getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ);
-    }
-
-    /**
-     * Verify isWifiBandSupported for 6GHz with no overlay override config no channels
-     */
-    @Test
-    public void testIsWifiBandSupported6gNoOverrideNoChannels() throws Exception {
-        final int[] emptyArray = {};
-        mResources.setBoolean(R.bool.config_wifi6ghzSupport, false);
-        when(mWifiNative.getChannelsForBand(anyInt())).thenReturn(emptyArray);
-        assertFalse(mCmi.isWifiBandSupported(WifiScanner.WIFI_BAND_6_GHZ));
-        verify(mWifiNative).getChannelsForBand(WifiScanner.WIFI_BAND_6_GHZ);
-    }
-
-    /**
-     * Verify isWifiBandSupported for 6GHz with no overlay override config with channels
-     */
-    @Test
-    public void testIsWifiBandSupported6gNoOverrideWithChannels() throws Exception {
-        final int[] channelArray = {6420};
-        mResources.setBoolean(R.bool.config_wifi6ghzSupport, false);
-        when(mWifiNative.getChannelsForBand(anyInt())).thenReturn(channelArray);
-        assertTrue(mCmi.isWifiBandSupported(WifiScanner.WIFI_BAND_6_GHZ));
-        verify(mWifiNative).getChannelsForBand(WifiScanner.WIFI_BAND_6_GHZ);
-    }
-
     /**
      * Helper function for setting up fils test.
      *
@@ -4813,9 +5062,6 @@
      * @return wifi configuration.
      */
     private WifiConfiguration setupFilsTest(boolean isDriverSupportFils) {
-        assertEquals(ClientModeImpl.CONNECT_MODE, mCmi.getOperationalModeForTest());
-        assertEquals(WifiManager.WIFI_STATE_ENABLED, mCmi.syncGetWifiState());
-
         WifiConfiguration config = new WifiConfiguration();
         config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
         config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
@@ -4823,7 +5069,7 @@
         config.SSID = ScanResultUtil.createQuotedSSID(sFilsSsid);
         config.networkId = 1;
         config.setRandomizedMacAddress(TEST_LOCAL_MAC_ADDRESS);
-        config.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_PERSISTENT;
+        config.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_AUTO;
 
         when(mWifiConfigManager.getConfiguredNetworkWithoutMasking(anyInt())).thenReturn(config);
         if (isDriverSupportFils) {
@@ -4841,17 +5087,15 @@
      *
      */
     private void setupFilsEnabledApInScanResult() {
-        String caps = "[WPA2-EAP+EAP-SHA256+EAP-FILS-SHA256-CCMP]"
-                + "[RSN-EAP+EAP-SHA256+EAP-FILS-SHA256-CCMP][ESS]";
+        String caps = "[WPA2-EAP/SHA1+EAP/SHA256+EAP-FILS-SHA256-CCMP]"
+                + "[RSN-EAP/SHA1+EAP/SHA256+EAP-FILS-SHA256-CCMP][ESS]";
         ScanResult scanResult = new ScanResult(WifiSsid.createFromAsciiEncoded(sFilsSsid),
-                sFilsSsid, sBSSID, 1245, 0, caps, -78, 2412, 1025, 22, 33, 20, 0, 0, true);
+                sFilsSsid, TEST_BSSID_STR, 1245, 0, caps, -78, 2412, 1025, 22, 33, 20, 0, 0, true);
         ScanResult.InformationElement ie = createIE(ScanResult.InformationElement.EID_SSID,
                 sFilsSsid.getBytes(StandardCharsets.UTF_8));
         scanResult.informationElements = new ScanResult.InformationElement[]{ie};
-        List<ScanResult> scanResults = new ArrayList<>();
-        scanResults.add(scanResult);
-
-        when(mScanRequestProxy.getScanResults()).thenReturn(scanResults);
+        when(mScanRequestProxy.getScanResults()).thenReturn(Arrays.asList(scanResult));
+        when(mScanRequestProxy.getScanResult(eq(TEST_BSSID_STR))).thenReturn(scanResult);
     }
 
 
@@ -4882,7 +5126,7 @@
         WifiConfiguration config = setupFilsTest(true);
         setupFilsEnabledApInScanResult();
 
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
         mLooper.dispatchAll();
 
         assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.FILS_SHA256));
@@ -4901,7 +5145,7 @@
         WifiConfiguration config = setupFilsTest(false);
         setupFilsEnabledApInScanResult();
 
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
         mLooper.dispatchAll();
 
         assertFalse(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.FILS_SHA256));
@@ -4921,7 +5165,7 @@
         WifiConfiguration config = setupFilsTest(true);
         setupFilsEnabledApInScanResult();
 
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
         mLooper.dispatchAll();
 
         prepareFilsHlpPktAndSendStartConnect();
@@ -4943,17 +5187,18 @@
         WifiConfiguration config = setupFilsTest(true);
         setupFilsEnabledApInScanResult();
 
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
         mLooper.dispatchAll();
 
         prepareFilsHlpPktAndSendStartConnect();
 
         verify(mWifiNative, times(1)).connectToNetwork(eq(WIFI_IFACE_NAME), eq(config));
 
-        mCmi.sendMessage(WifiMonitor.ASSOCIATION_REJECTION_EVENT, 0, 2, sBSSID);
+        mCmi.sendMessage(WifiMonitor.ASSOCIATION_REJECTION_EVENT,
+                new AssocRejectEventInfo(TEST_SSID, TEST_BSSID_STR, 2, false));
         mLooper.dispatchAll();
 
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
         mLooper.dispatchAll();
         prepareFilsHlpPktAndSendStartConnect();
 
@@ -4971,24 +5216,25 @@
         WifiConfiguration config = setupFilsTest(true);
         setupFilsEnabledApInScanResult();
 
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
         mLooper.dispatchAll();
 
         prepareFilsHlpPktAndSendStartConnect();
 
         verify(mWifiMetrics, times(1)).incrementConnectRequestWithFilsAkmCount();
 
-        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 1, sBSSID);
+        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT,
+                new NetworkConnectionEventInfo(0, TEST_WIFI_SSID, TEST_BSSID_STR, true));
         mLooper.dispatchAll();
 
         verify(mWifiMetrics, times(1)).incrementL2ConnectionThroughFilsAuthCount();
 
         mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
                 new StateChangeResult(0, WifiSsid.createFromAsciiEncoded(sFilsSsid),
-                sBSSID, SupplicantState.COMPLETED));
+                TEST_BSSID_STR, SupplicantState.COMPLETED));
         mLooper.dispatchAll();
 
-        assertEquals("ObtainingIpState", getCurrentState().getName());
+        assertEquals("L3ProvisioningState", getCurrentState().getName());
 
         DhcpResultsParcelable dhcpResults = new DhcpResultsParcelable();
         dhcpResults.baseConfiguration = new StaticIpConfiguration();
@@ -5001,11 +5247,11 @@
         injectDhcpSuccess(dhcpResults);
         mLooper.dispatchAll();
 
-        WifiInfo wifiInfo = mCmi.getWifiInfo();
+        WifiInfo wifiInfo = mWifiInfo;
         assertNotNull(wifiInfo);
-        assertEquals(sBSSID, wifiInfo.getBSSID());
+        assertEquals(TEST_BSSID_STR, wifiInfo.getBSSID());
         assertTrue(WifiSsid.createFromAsciiEncoded(sFilsSsid).equals(wifiInfo.getWifiSsid()));
-        assertEquals("ConnectedState", getCurrentState().getName());
+        assertEquals("L3ConnectedState", getCurrentState().getName());
     }
 
     /**
@@ -5013,33 +5259,42 @@
      */
     @Test
     public void testWifiInfoOnConnectingNextNetwork() throws Exception {
-
         mConnectedNetwork.ephemeral = true;
         mConnectedNetwork.trusted = true;
+        mConnectedNetwork.oemPaid = true;
+        mConnectedNetwork.oemPrivate = true;
+        mConnectedNetwork.carrierMerged = true;
         mConnectedNetwork.osu = true;
+        mConnectedNetwork.subscriptionId = DATA_SUBID;
 
         triggerConnect();
         when(mWifiConfigManager.getScanDetailCacheForNetwork(FRAMEWORK_NETWORK_ID))
                 .thenReturn(mScanDetailCache);
 
-        when(mScanDetailCache.getScanDetail(sBSSID)).thenReturn(
-                getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq));
-        when(mScanDetailCache.getScanResult(sBSSID)).thenReturn(
-                getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq).getScanResult());
+        when(mScanDetailCache.getScanDetail(TEST_BSSID_STR)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, TEST_BSSID_STR, sFreq));
+        when(mScanDetailCache.getScanResult(TEST_BSSID_STR)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, TEST_BSSID_STR, sFreq).getScanResult());
 
         // before the fist success connection, there is no valid wifi info.
-        assertEquals(WifiConfiguration.INVALID_NETWORK_ID, mCmi.getWifiInfo().getNetworkId());
+        assertEquals(WifiConfiguration.INVALID_NETWORK_ID, mWifiInfo.getNetworkId());
 
         mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
                 new StateChangeResult(FRAMEWORK_NETWORK_ID,
-                    sWifiSsid, sBSSID, SupplicantState.ASSOCIATED));
+                    TEST_WIFI_SSID, TEST_BSSID_STR, SupplicantState.ASSOCIATED));
         mLooper.dispatchAll();
 
         // retrieve correct wifi info on receiving the supplicant state change event.
-        assertEquals(FRAMEWORK_NETWORK_ID, mCmi.getWifiInfo().getNetworkId());
-        assertEquals(mConnectedNetwork.ephemeral, mCmi.getWifiInfo().isEphemeral());
-        assertEquals(mConnectedNetwork.trusted, mCmi.getWifiInfo().isTrusted());
-        assertEquals(mConnectedNetwork.osu, mCmi.getWifiInfo().isOsuAp());
+        assertEquals(FRAMEWORK_NETWORK_ID, mWifiInfo.getNetworkId());
+        assertEquals(mConnectedNetwork.ephemeral, mWifiInfo.isEphemeral());
+        assertEquals(mConnectedNetwork.trusted, mWifiInfo.isTrusted());
+        assertEquals(mConnectedNetwork.osu, mWifiInfo.isOsuAp());
+        if (SdkLevel.isAtLeastS()) {
+            assertEquals(mConnectedNetwork.oemPaid, mWifiInfo.isOemPaid());
+            assertEquals(mConnectedNetwork.oemPrivate, mWifiInfo.isOemPrivate());
+            assertEquals(mConnectedNetwork.carrierMerged, mWifiInfo.isCarrierMerged());
+            assertEquals(DATA_SUBID, mWifiInfo.getSubscriptionId());
+        }
     }
 
     /**
@@ -5057,7 +5312,9 @@
 
         mConfigUpdateListenerCaptor.getValue().onNetworkUpdated(mConnectedNetwork, oldConfig);
         mLooper.dispatchAll();
-        assertEquals("DisconnectingState", getCurrentState().getName());
+        verify(mWifiNative).disconnect(WIFI_IFACE_NAME);
+        verify(mWifiMetrics).logStaEvent(anyString(), eq(StaEvent.TYPE_FRAMEWORK_DISCONNECT),
+                eq(StaEvent.DISCONNECT_NETWORK_METERED));
     }
 
     /**
@@ -5070,14 +5327,14 @@
         expectRegisterNetworkAgent((config) -> { }, (cap) -> {
             assertFalse(cap.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED));
         });
-        reset(mNetworkAgentRegistry);
+        reset(mWifiNetworkAgent);
 
         WifiConfiguration oldConfig = new WifiConfiguration(mConnectedNetwork);
         mConnectedNetwork.meteredOverride = METERED_OVERRIDE_NOT_METERED;
 
         mConfigUpdateListenerCaptor.getValue().onNetworkUpdated(mConnectedNetwork, oldConfig);
         mLooper.dispatchAll();
-        assertEquals("ConnectedState", getCurrentState().getName());
+        assertEquals("L3ConnectedState", getCurrentState().getName());
 
         expectNetworkAgentUpdateCapabilities((cap) -> {
             assertTrue(cap.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED));
@@ -5095,19 +5352,19 @@
         expectRegisterNetworkAgent((config) -> { }, (cap) -> {
             assertTrue(cap.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED));
         });
-        reset(mNetworkAgentRegistry);
+        reset(mWifiNetworkAgent);
 
         // Mark network metered none.
         WifiConfiguration oldConfig = new WifiConfiguration(mConnectedNetwork);
         mConnectedNetwork.meteredOverride = METERED_OVERRIDE_NONE;
 
         // Set metered hint in WifiInfo (either via DHCP or ScanResult IE).
-        WifiInfo wifiInfo = mCmi.getWifiInfo();
+        WifiInfo wifiInfo = mWifiInfo;
         wifiInfo.setMeteredHint(true);
 
         mConfigUpdateListenerCaptor.getValue().onNetworkUpdated(mConnectedNetwork, oldConfig);
         mLooper.dispatchAll();
-        assertEquals("DisconnectingState", getCurrentState().getName());
+        verify(mWifiNative).disconnect(WIFI_IFACE_NAME);
     }
 
     /**
@@ -5121,18 +5378,18 @@
         expectRegisterNetworkAgent((config) -> { }, (cap) -> {
             assertFalse(cap.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED));
         });
-        reset(mNetworkAgentRegistry);
+        reset(mWifiNetworkAgent);
 
         WifiConfiguration oldConfig = new WifiConfiguration(mConnectedNetwork);
         mConnectedNetwork.meteredOverride = METERED_OVERRIDE_NONE;
 
         // Reset metered hint in WifiInfo.
-        WifiInfo wifiInfo = mCmi.getWifiInfo();
+        WifiInfo wifiInfo = mWifiInfo;
         wifiInfo.setMeteredHint(false);
 
         mConfigUpdateListenerCaptor.getValue().onNetworkUpdated(mConnectedNetwork, oldConfig);
         mLooper.dispatchAll();
-        assertEquals("ConnectedState", getCurrentState().getName());
+        assertEquals("L3ConnectedState", getCurrentState().getName());
 
         expectNetworkAgentUpdateCapabilities((cap) -> {
             assertTrue(cap.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED));
@@ -5149,21 +5406,21 @@
         expectRegisterNetworkAgent((config) -> { }, (cap) -> {
             assertFalse(cap.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED));
         });
-        reset(mNetworkAgentRegistry);
+        reset(mWifiNetworkAgent);
 
         // Mark network metered none.
         WifiConfiguration oldConfig = new WifiConfiguration(mConnectedNetwork);
         mConnectedNetwork.meteredOverride = METERED_OVERRIDE_NONE;
 
         // Set metered hint in WifiInfo (either via DHCP or ScanResult IE).
-        WifiInfo wifiInfo = mCmi.getWifiInfo();
+        WifiInfo wifiInfo = mWifiInfo;
         wifiInfo.setMeteredHint(true);
 
         mConfigUpdateListenerCaptor.getValue().onNetworkUpdated(mConnectedNetwork, oldConfig);
         mLooper.dispatchAll();
-        assertEquals("ConnectedState", getCurrentState().getName());
+        assertEquals("L3ConnectedState", getCurrentState().getName());
 
-        verifyNoMoreInteractions(mNetworkAgentRegistry);
+        verifyNoMoreInteractions(mWifiNetworkAgent);
     }
 
     /**
@@ -5176,21 +5433,21 @@
         expectRegisterNetworkAgent((config) -> { }, (cap) -> {
             assertTrue(cap.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED));
         });
-        reset(mNetworkAgentRegistry);
+        reset(mWifiNetworkAgent);
 
         // Mark network metered none.
         WifiConfiguration oldConfig = new WifiConfiguration(mConnectedNetwork);
         mConnectedNetwork.meteredOverride = METERED_OVERRIDE_NONE;
 
         // Reset metered hint in WifiInfo.
-        WifiInfo wifiInfo = mCmi.getWifiInfo();
+        WifiInfo wifiInfo = mWifiInfo;
         wifiInfo.setMeteredHint(false);
 
         mConfigUpdateListenerCaptor.getValue().onNetworkUpdated(mConnectedNetwork, oldConfig);
         mLooper.dispatchAll();
-        assertEquals("ConnectedState", getCurrentState().getName());
+        assertEquals("L3ConnectedState", getCurrentState().getName());
 
-        verifyNoMoreInteractions(mNetworkAgentRegistry);
+        verifyNoMoreInteractions(mWifiNetworkAgent);
     }
 
     /*
@@ -5201,20 +5458,23 @@
     public void testNetworkCachedDataIsClearedCorrectlyInDisconnectedState() throws Exception {
         // Setup CONNECT_MODE & a WifiConfiguration
         initializeAndAddNetworkAndVerifySuccess();
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
         mLooper.dispatchAll();
 
         // got UNSPECIFIED during this connection attempt
-        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, 0, 1, sBSSID);
+        DisconnectEventInfo disconnectEventInfo =
+                new DisconnectEventInfo(TEST_SSID, TEST_BSSID_STR, 1, false);
+        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, disconnectEventInfo);
         mLooper.dispatchAll();
 
         assertEquals("DisconnectedState", getCurrentState().getName());
         verify(mWifiNative, never()).removeNetworkCachedData(anyInt());
 
-        // got 4WAY_HANDSHAKE_TIMEOUT during this connection attempt
-        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
         mLooper.dispatchAll();
-        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, 0, 15, sBSSID);
+        // got 4WAY_HANDSHAKE_TIMEOUT during this connection attempt
+        disconnectEventInfo = new DisconnectEventInfo(TEST_SSID, TEST_BSSID_STR, 15, false);
+        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, disconnectEventInfo);
         mLooper.dispatchAll();
 
         assertEquals("DisconnectedState", getCurrentState().getName());
@@ -5226,47 +5486,49 @@
      * disconnected state.
      */
     @Test
-    public void testNetworkCachedDataIsClearedCorrectlyInObtainingIpState() throws Exception {
+    public void testNetworkCachedDataIsClearedCorrectlyInL3ProvisioningState() throws Exception {
         initializeAndAddNetworkAndVerifySuccess();
 
         verify(mWifiNative).removeAllNetworks(WIFI_IFACE_NAME);
 
-        IActionListener connectActionListener = mock(IActionListener.class);
-        mCmi.connect(null, 0, mock(Binder.class), connectActionListener, 0, Binder.getCallingUid());
-        mLooper.dispatchAll();
-        verify(connectActionListener).onSuccess();
+        startConnectSuccess();
 
-        verify(mWifiConfigManager).enableNetwork(eq(0), eq(true), anyInt(), any());
-
-        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, sBSSID);
+        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT,
+                new NetworkConnectionEventInfo(0, TEST_WIFI_SSID, TEST_BSSID_STR, false));
         mLooper.dispatchAll();
 
         mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
-                new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.COMPLETED));
+                new StateChangeResult(0, TEST_WIFI_SSID, TEST_BSSID_STR,
+                        SupplicantState.COMPLETED));
         mLooper.dispatchAll();
 
-        assertEquals("ObtainingIpState", getCurrentState().getName());
+        assertEquals("L3ProvisioningState", getCurrentState().getName());
 
         // got 4WAY_HANDSHAKE_TIMEOUT during this connection attempt
-        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, 0, 15, sBSSID);
+        DisconnectEventInfo disconnectEventInfo =
+                new DisconnectEventInfo(TEST_SSID, TEST_BSSID_STR, 15, false);
+        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, disconnectEventInfo);
         mLooper.dispatchAll();
 
         verify(mWifiNative).removeNetworkCachedData(FRAMEWORK_NETWORK_ID);
     }
 
     /*
-     * Verify that network cached data is NOT cleared in ConnectedState.
+     * Verify that network cached data is NOT cleared in L3ConnectedState.
      */
     @Test
     public void testNetworkCachedDataIsClearedIf4WayHandshakeFailure() throws Exception {
-        when(mWifiScoreCard.detectAbnormalDisconnection())
+        when(mWifiScoreCard.detectAbnormalDisconnection(any()))
                 .thenReturn(WifiHealthMonitor.REASON_SHORT_CONNECTION_NONLOCAL);
         InOrder inOrderWifiLockManager = inOrder(mWifiLockManager);
         connect();
-        inOrderWifiLockManager.verify(mWifiLockManager).updateWifiClientConnected(true);
+        inOrderWifiLockManager.verify(mWifiLockManager)
+                .updateWifiClientConnected(mClientModeManager, true);
 
         // got 4WAY_HANDSHAKE_TIMEOUT
-        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, 0, 15, sBSSID);
+        DisconnectEventInfo disconnectEventInfo =
+                new DisconnectEventInfo(TEST_SSID, TEST_BSSID_STR, 15, false);
+        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, disconnectEventInfo);
         mLooper.dispatchAll();
         verify(mWifiNative, never()).removeNetworkCachedData(anyInt());
     }
@@ -5286,48 +5548,206 @@
 
 
     @Test
-    public void testIpReachabilityLostAndRoamEventsRace() throws Exception {
+    public void testVerifyWifiInfoStateOnFrameworkDisconnect() throws Exception {
         connect();
-        expectRegisterNetworkAgent((agentConfig) -> { }, (cap) -> { });
-        reset(mNetworkAgentRegistry);
 
-        // Trigger ip reachibility loss and ensure we trigger a disconnect & we're in
-        // "DisconnectingState"
-        mCmi.sendMessage(ClientModeImpl.CMD_IP_REACHABILITY_LOST);
+        assertEquals(mWifiInfo.getSupplicantState(), SupplicantState.COMPLETED);
+
+        // Now trigger disconnect
+        mCmi.disconnect();
         mLooper.dispatchAll();
-        verify(mWifiNative).disconnect(any());
-        assertEquals("DisconnectingState", getCurrentState().getName());
 
-        // Now send a network connection (indicating a roam) event before we get the disconnect
-        // event.
-        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, sBSSID);
+        // get disconnect event
+        mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
+                new StateChangeResult(0, WifiSsid.createFromAsciiEncoded(mConnectedNetwork.SSID),
+                        TEST_BSSID_STR, SupplicantState.DISCONNECTED));
         mLooper.dispatchAll();
-        // ensure that we ignored the transient roam while we're disconnecting.
-        assertEquals("DisconnectingState", getCurrentState().getName());
-        verifyNoMoreInteractions(mNetworkAgentRegistry);
 
-        // Now send the disconnect event and ensure that we transition to "DisconnectedState".
-        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, 0, 0, sBSSID);
-        mLooper.dispatchAll();
-        assertEquals("DisconnectedState", getCurrentState().getName());
-        expectUnregisterNetworkAgent();
-
-        verifyNoMoreInteractions(mNetworkAgentRegistry);
+        assertEquals(mWifiInfo.getSupplicantState(), SupplicantState.DISCONNECTED);
     }
 
     @Test
-    public void testSyncGetCurrentNetwork() throws Exception {
-        // syncGetCurrentNetwork() returns null when disconnected
-        mLooper.startAutoDispatch();
-        assertNull(mCmi.syncGetCurrentNetwork(mCmiAsyncChannel));
-        mLooper.stopAutoDispatch();
-
+    public void testVerifyWifiInfoStateOnFrameworkDisconnectButMissingDisconnectEvent()
+            throws Exception {
         connect();
 
-        // syncGetCurrentNetwork() returns non-null Network when connected
+        assertEquals(mWifiInfo.getSupplicantState(), SupplicantState.COMPLETED);
+
+        // Now trigger disconnect
+        mCmi.disconnect();
+        mLooper.dispatchAll();
+
+        // missing disconnect event, but got supplicant state change with disconnect state instead.
+        mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
+                new StateChangeResult(0, WifiSsid.createFromAsciiEncoded(mConnectedNetwork.SSID),
+                        TEST_BSSID_STR, SupplicantState.DISCONNECTED));
+        mLooper.dispatchAll();
+
+        assertEquals(mWifiInfo.getSupplicantState(), SupplicantState.DISCONNECTED);
+    }
+
+    /**
+     * Ensures that we only disable the current network & set MAC address only when we exit
+     * ConnectingState.
+     * @throws Exception
+     */
+    @Test
+    public void testDisableNetworkOnExitingConnectingOrConnectedState() throws Exception {
+        connect();
+        String oldSsid = mConnectedNetwork.SSID;
+
+        // Trigger connection to a different network
+        mConnectedNetwork.SSID = oldSsid.concat("blah");
+        mConnectedNetwork.networkId++;
+        mConnectedNetwork.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_NONE;
+        setupAndStartConnectSequence(mConnectedNetwork);
+
+        // Send disconnect event for the old network.
+        DisconnectEventInfo disconnectEventInfo =
+                new DisconnectEventInfo(oldSsid, TEST_BSSID_STR, 0, false);
+        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, disconnectEventInfo);
+        mLooper.dispatchAll();
+
+        assertEquals("L2ConnectingState", getCurrentState().getName());
+        // Since we remain in connecting state, we should not disable the network or set random MAC
+        // address on disconnect.
+        verify(mWifiNative, never()).disableNetwork(WIFI_IFACE_NAME);
+        // Set MAC address thrice - once at bootup, twice for the 2 connections.
+        verify(mWifiNative, times(3)).setStaMacAddress(eq(WIFI_IFACE_NAME), any());
+
+        // Send disconnect event for the new network.
+        disconnectEventInfo =
+                new DisconnectEventInfo(mConnectedNetwork.SSID, TEST_BSSID_STR, 0, false);
+        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, disconnectEventInfo);
+        mLooper.dispatchAll();
+
+        verify(mWifiNative).disableNetwork(WIFI_IFACE_NAME);
+        // Set MAC address thrice - once at bootup, twice for the connections,
+        // once for the disconnect.
+        verify(mWifiNative, times(4)).setStaMacAddress(eq(WIFI_IFACE_NAME), any());
+    }
+
+    @Test
+    public void testIpReachabilityLostAndRoamEventsRace() throws Exception {
+        connect();
+        expectRegisterNetworkAgent((agentConfig) -> { }, (cap) -> { });
+        reset(mWifiNetworkAgent);
+
+        // Trigger ip reachability loss and ensure we trigger a disconnect.
+        mCmi.sendMessage(ClientModeImpl.CMD_IP_REACHABILITY_LOST);
+        mLooper.dispatchAll();
+        verify(mWifiNative).disconnect(WIFI_IFACE_NAME);
+
+        // Now send a network connection (indicating a roam) event before we get the disconnect
+        // event.
+        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT,
+                new NetworkConnectionEventInfo(0, TEST_WIFI_SSID, TEST_BSSID_STR, false));
+        mLooper.dispatchAll();
+        // ensure that we ignored the transient roam while we're disconnecting.
+        verifyNoMoreInteractions(mWifiNetworkAgent);
+
+        // Now send the disconnect event and ensure that we transition to "DisconnectedState".
+        DisconnectEventInfo disconnectEventInfo =
+                new DisconnectEventInfo(TEST_SSID, TEST_BSSID_STR, 0, false);
+        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, disconnectEventInfo);
+        mLooper.dispatchAll();
+        assertEquals("DisconnectedState", getCurrentState().getName());
+        verify(mWifiNetworkAgent).unregister();
+
+        verifyNoMoreInteractions(mWifiNetworkAgent);
+    }
+
+    @Test
+    public void testConnectionWhileDisconnecting() throws Exception {
+        connect();
+
+        // Trigger a disconnect event.
+        mCmi.disconnect();
+        mLooper.dispatchAll();
+        assertEquals("L3ConnectedState", getCurrentState().getName());
+
+        // Trigger a new connection before the NETWORK_DISCONNECTION_EVENT comes in.
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+        config.networkId = FRAMEWORK_NETWORK_ID + 1;
+        setupAndStartConnectSequence(config);
+        // Ensure that we triggered the connection attempt.
+        validateSuccessfulConnectSequence(config);
+
+        // Now trigger the disconnect event for the previous disconnect and ensure we handle it
+        // correctly and remain in ConnectingState.
+        DisconnectEventInfo disconnectEventInfo =
+                new DisconnectEventInfo(mConnectedNetwork.SSID, TEST_BSSID_STR, 0, false);
+        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, disconnectEventInfo);
+        mLooper.dispatchAll();
+        assertEquals("L2ConnectingState", mCmi.getCurrentState().getName());
+    }
+
+    @Test
+    public void testConnectionWatchdog() throws Exception {
+        triggerConnect();
+        Log.i(TAG, "Triggering Connect done");
+
+        // Simulate watchdog timeout and ensure we retuned to disconnected state.
+        mLooper.moveTimeForward(ClientModeImpl.CONNECTING_WATCHDOG_TIMEOUT_MS + 5L);
+        mLooper.dispatchAll();
+
+        verify(mWifiNative).disableNetwork(WIFI_IFACE_NAME);
+        assertEquals("DisconnectedState", mCmi.getCurrentState().getName());
+    }
+
+    @Test
+    public void testRoamAfterConnectDoesNotChangeNetworkInfoInNetworkStateChangeBroadcast()
+            throws Exception {
+        connect();
+
+        // The last NETWORK_STATE_CHANGED_ACTION should be to mark the network connected.
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext, atLeastOnce()).sendStickyBroadcastAsUser(intentCaptor.capture(), any());
+        Intent intent = intentCaptor.getValue();
+        assertNotNull(intent);
+        assertEquals(WifiManager.NETWORK_STATE_CHANGED_ACTION, intent.getAction());
+        NetworkInfo networkInfo = (NetworkInfo) intent.getExtra(WifiManager.EXTRA_NETWORK_INFO);
+        assertTrue(networkInfo.isConnected());
+
+        reset(mContext);
+        when(mContext.getResources()).thenReturn(mResources);
+
+        // send roam event
+        mCmi.sendMessage(WifiMonitor.ASSOCIATED_BSSID_EVENT, 0, 0, TEST_BSSID_STR1);
+        mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
+                new StateChangeResult(0, TEST_WIFI_SSID, TEST_BSSID_STR1,
+                        SupplicantState.COMPLETED));
+        mLooper.dispatchAll();
+
+        verify(mContext, atLeastOnce()).sendStickyBroadcastAsUser(intentCaptor.capture(), any());
+        intent = intentCaptor.getValue();
+        assertNotNull(intent);
+        assertEquals(WifiManager.NETWORK_STATE_CHANGED_ACTION, intent.getAction());
+        networkInfo = (NetworkInfo) intent.getExtra(WifiManager.EXTRA_NETWORK_INFO);
+        assertTrue(networkInfo.isConnected());
+    }
+
+
+    /**
+     * Ensure that {@link ClientModeImpl#dump(FileDescriptor, PrintWriter, String[])}
+     * {@link WifiNative#getWifiLinkLayerStats(String)}, at least once before calling
+     * {@link WifiScoreReport#dump(FileDescriptor, PrintWriter, String[])}.
+     *
+     * This ensures that WifiScoreReport will always get updated RSSI and link layer stats before
+     * dumping during a bug report, no matter if the screen is on or not.
+     */
+    @Test
+    public void testWifiScoreReportDump() throws Exception {
+        connect();
+
         mLooper.startAutoDispatch();
-        assertEquals(mNetwork, mCmi.syncGetCurrentNetwork(mCmiAsyncChannel));
-        mLooper.stopAutoDispatch();
+        mCmi.dump(new FileDescriptor(), new PrintWriter(new StringWriter()), null);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+
+        InOrder inOrder = inOrder(mWifiNative, mWifiScoreReport);
+
+        inOrder.verify(mWifiNative).getWifiLinkLayerStats(any());
+        inOrder.verify(mWifiScoreReport).dump(any(), any(), any());
     }
 
     @Test
@@ -5340,17 +5760,835 @@
 
         // association completed
         mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
-                new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.ASSOCIATED));
+                new StateChangeResult(0, TEST_WIFI_SSID, TEST_BSSID_STR,
+                        SupplicantState.ASSOCIATED));
         mLooper.dispatchAll();
 
-        assertTrue(mCmi.getWifiInfo().isEphemeral());
-        assertEquals(OP_PACKAGE_NAME, mCmi.getWifiInfo().getRequestingPackageName());
+        assertTrue(mWifiInfo.isEphemeral());
+        assertEquals(OP_PACKAGE_NAME, mWifiInfo.getRequestingPackageName());
 
         // fail the connection.
-        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, FRAMEWORK_NETWORK_ID, 0, sBSSID);
+        DisconnectEventInfo disconnectEventInfo =
+                new DisconnectEventInfo(mConnectedNetwork.SSID, TEST_BSSID_STR, 0, false);
+        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, disconnectEventInfo);
         mLooper.dispatchAll();
 
-        assertFalse(mCmi.getWifiInfo().isEphemeral());
-        assertNull(mCmi.getWifiInfo().getRequestingPackageName());
+        assertFalse(mWifiInfo.isEphemeral());
+        assertNull(mWifiInfo.getRequestingPackageName());
+    }
+
+    @Test
+    public void handleAssociationRejectionWhenRoaming() throws Exception {
+        connect();
+
+        assertTrue(SupplicantState.isConnecting(mWifiInfo.getSupplicantState()));
+
+        when(mWifiNative.roamToNetwork(any(), any())).thenReturn(true);
+
+        // Trigger roam to a BSSID.
+        mCmi.startRoamToNetwork(FRAMEWORK_NETWORK_ID, TEST_BSSID_STR1);
+        mLooper.dispatchAll();
+
+
+        assertEquals(TEST_BSSID_STR1, mCmi.getConnectingBssid());
+        assertEquals(FRAMEWORK_NETWORK_ID, mCmi.getConnectingWifiConfiguration().networkId);
+
+        verify(mWifiNative).roamToNetwork(any(), any());
+        assertEquals("RoamingState", getCurrentState().getName());
+
+        // fail the connection.
+        mCmi.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
+                new StateChangeResult(0, TEST_WIFI_SSID, TEST_BSSID_STR,
+                        SupplicantState.DISCONNECTED));
+        mLooper.dispatchAll();
+
+        // Ensure we reset WifiInfo fields.
+        assertFalse(SupplicantState.isConnecting(mWifiInfo.getSupplicantState()));
+    }
+
+    @Test
+    public void testOemPaidNetworkCapability() throws Exception {
+        // oemPaid introduced in S, not applicable to R
+        assumeTrue(SdkLevel.isAtLeastS());
+        mConnectedNetwork.oemPaid = true;
+        connect();
+        expectRegisterNetworkAgent((agentConfig) -> { },
+                (cap) -> {
+                    assertTrue(cap.hasCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID));
+                    assertFalse(cap.hasCapability(NetworkCapabilities
+                            .NET_CAPABILITY_NOT_RESTRICTED));
+                });
+    }
+    @Test
+    public void testNotOemPaidNetworkCapability() throws Exception {
+        // oemPaid introduced in S, not applicable to R
+        assumeTrue(SdkLevel.isAtLeastS());
+        mConnectedNetwork.oemPaid = false;
+        connect();
+        expectRegisterNetworkAgent((agentConfig) -> { },
+                (cap) -> {
+                    assertFalse(cap.hasCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID));
+                    assertTrue(cap.hasCapability(NetworkCapabilities
+                            .NET_CAPABILITY_NOT_RESTRICTED));
+                });
+    }
+
+    @Test
+    public void testOemPrivateNetworkCapability() throws Exception {
+        // oemPrivate introduced in S, not applicable to R
+        assumeTrue(SdkLevel.isAtLeastS());
+        mConnectedNetwork.oemPrivate = true;
+        connect();
+        expectRegisterNetworkAgent((agentConfig) -> { },
+                (cap) -> {
+                    assertTrue(cap.hasCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE));
+                    assertFalse(cap.hasCapability(NetworkCapabilities
+                            .NET_CAPABILITY_NOT_RESTRICTED));
+                });
+    }
+
+    @Test
+    public void testNotOemPrivateNetworkCapability() throws Exception {
+        // oemPrivate introduced in S, not applicable to R
+        assumeTrue(SdkLevel.isAtLeastS());
+        mConnectedNetwork.oemPrivate = false;
+        connect();
+        expectRegisterNetworkAgent((agentConfig) -> { },
+                (cap) -> {
+                    assertFalse(cap.hasCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE));
+                    assertTrue(cap.hasCapability(NetworkCapabilities
+                            .NET_CAPABILITY_NOT_RESTRICTED));
+                });
+    }
+
+    @Test
+    public void testSendLinkProbeFailure() throws Exception {
+        mCmi.probeLink(mLinkProbeCallback, -1);
+
+        verify(mLinkProbeCallback).onFailure(LinkProbeCallback.LINK_PROBE_ERROR_NOT_CONNECTED);
+        verify(mLinkProbeCallback, never()).onAck(anyInt());
+        verify(mWifiNative, never()).probeLink(any(), any(), any(), anyInt());
+    }
+
+    @Test
+    public void testSendLinkProbeSuccess() throws Exception {
+        connect();
+
+        mCmi.probeLink(mLinkProbeCallback, -1);
+
+        verify(mWifiNative).probeLink(any(), any(), eq(mLinkProbeCallback), eq(-1));
+        verify(mLinkProbeCallback, never()).onFailure(anyInt());
+        verify(mLinkProbeCallback, never()).onAck(anyInt());
+    }
+
+    private void setupPasspointConnection() throws Exception {
+        mConnectedNetwork = spy(WifiConfigurationTestUtil.createPasspointNetwork());
+        mConnectedNetwork.carrierId = CARRIER_ID_1;
+        when(mWifiCarrierInfoManager.getBestMatchSubscriptionId(any(WifiConfiguration.class)))
+                .thenReturn(DATA_SUBID);
+        when(mWifiCarrierInfoManager.isSimReady(DATA_SUBID)).thenReturn(true);
+        mConnectedNetwork.enterpriseConfig.setAnonymousIdentity("");
+        triggerConnect();
+
+        when(mWifiConfigManager.getConfiguredNetwork(anyInt())).thenReturn(mConnectedNetwork);
+        when(mWifiConfigManager.getScanDetailCacheForNetwork(FRAMEWORK_NETWORK_ID))
+                .thenReturn(mScanDetailCache);
+        when(mScanRequestProxy.getScanResult(TEST_BSSID_STR)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, TEST_BSSID_STR, sFreq).getScanResult());
+        when(mScanDetailCache.getScanDetail(TEST_BSSID_STR)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, TEST_BSSID_STR, sFreq));
+        when(mScanDetailCache.getScanResult(TEST_BSSID_STR)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, TEST_BSSID_STR, sFreq).getScanResult());
+
+        WifiSsid wifiSsid = WifiSsid.createFromByteArray(
+                NativeUtil.byteArrayFromArrayList(NativeUtil.decodeSsid(mConnectedNetwork.SSID)));
+        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT,
+                new NetworkConnectionEventInfo(0, wifiSsid, TEST_BSSID_STR, false));
+        mLooper.dispatchAll();
+        assertEquals("L3ProvisioningState", getCurrentState().getName());
+    }
+
+    /**
+     * When connecting to a Passpoint network, verify that the Venue URL ANQP request is sent.
+     */
+    @Test
+    public void testVenueUrlRequestForPasspointNetworks() throws Exception {
+        setupPasspointConnection();
+        verify(mPasspointManager).requestVenueUrlAnqpElement(any(ScanResult.class));
+        assertEquals("L3ProvisioningState", getCurrentState().getName());
+    }
+
+    /**
+     * Verify that the Venue URL ANQP request is not sent for non-Passpoint EAP networks
+     */
+    @Test
+    public void testVenueUrlNotRequestedForNonPasspointNetworks() throws Exception {
+        setupEapSimConnection();
+        verify(mPasspointManager, never()).requestVenueUrlAnqpElement(any(ScanResult.class));
+        assertEquals("L3ProvisioningState", getCurrentState().getName());
+    }
+
+    @Test
+    public void testFirmwareRoam() throws Exception {
+        connect();
+
+        // Now send a network connection (indicating a roam) event
+        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT,
+                new NetworkConnectionEventInfo(0, TEST_WIFI_SSID, TEST_BSSID_STR1, false));
+        mLooper.dispatchAll();
+
+        verify(mContext, times(2)).sendStickyBroadcastAsUser(
+                argThat(new NetworkStateChangedIntentMatcher(CONNECTED)), any());
+    }
+
+    @Test
+    public void testProvisioningUpdateAfterConnect() throws Exception {
+        connect();
+
+        // Trigger a IP params update (maybe a dhcp lease renewal).
+        DhcpResultsParcelable dhcpResults = new DhcpResultsParcelable();
+        dhcpResults.baseConfiguration = new StaticIpConfiguration();
+        dhcpResults.baseConfiguration.gateway = InetAddresses.parseNumericAddress("1.2.3.4");
+        dhcpResults.baseConfiguration.ipAddress =
+                new LinkAddress(InetAddresses.parseNumericAddress("192.168.1.100"), 0);
+        dhcpResults.baseConfiguration.dnsServers.add(InetAddresses.parseNumericAddress("8.8.8.8"));
+        dhcpResults.leaseDuration = 3600;
+
+        injectDhcpSuccess(dhcpResults);
+        mLooper.dispatchAll();
+
+        verify(mContext, times(2)).sendStickyBroadcastAsUser(
+                argThat(new NetworkStateChangedIntentMatcher(CONNECTED)), any());
+    }
+
+    /**
+     * Verify that the Deauth-Imminent WNM-Notification is handled by relaying to the Passpoint
+     * Manager.
+     */
+    @Test
+    public void testHandlePasspointDeauthImminentWnmNotification() throws Exception {
+        setupEapSimConnection();
+        WnmData wnmData = WnmData.createDeauthImminentEvent(TEST_BSSID, "", false,
+                TEST_DELAY_IN_SECONDS);
+        mCmi.sendMessage(WifiMonitor.HS20_DEAUTH_IMMINENT_EVENT, 0, 0, wnmData);
+        mLooper.dispatchAll();
+        verify(mPasspointManager).handleDeauthImminentEvent(eq(wnmData),
+                any(WifiConfiguration.class));
+    }
+
+    /**
+     * Verify that the network selection status will be updated and the function onEapFailure()
+     * in EapFailureNotifier is called when a EAP Authentication failure is detected
+     * with carrier erroe code.
+     */
+    @Test
+    public void testCarrierEapFailure() throws Exception {
+        initializeAndAddNetworkAndVerifySuccess();
+
+        startConnectSuccess();
+
+        WifiConfiguration config = new WifiConfiguration();
+        config.SSID = TEST_SSID;
+        config.getNetworkSelectionStatus().setHasEverConnected(true);
+        config.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.SIM);
+        when(mWifiConfigManager.getConfiguredNetwork(anyInt())).thenReturn(config);
+        when(mEapFailureNotifier.onEapFailure(anyInt(), eq(config), anyBoolean())).thenReturn(true);
+
+        mCmi.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT,
+                WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE,
+                DEFINED_ERROR_CODE
+        );
+        mLooper.dispatchAll();
+
+        verify(mEapFailureNotifier).onEapFailure(DEFINED_ERROR_CODE, config, true);
+        verify(mWifiBlocklistMonitor).loadCarrierConfigsForDisableReasonInfos();
+        verify(mWifiConfigManager).updateNetworkSelectionStatus(anyInt(),
+                eq(WifiConfiguration.NetworkSelectionStatus
+                        .DISABLED_AUTHENTICATION_PRIVATE_EAP_ERROR));
+    }
+
+    /**
+     * When connected to a Passpoint network, verify that the Venue URL and T&C URL are updated in
+     * the {@link LinkProperties} object when provisioning complete and when link properties change
+     * events are received.
+     */
+    @Test
+    public void testVenueAndTCUrlsUpdateForPasspointNetworks() throws Exception {
+        // This tests new S functionality/APIs, not applicable to R.
+        assumeTrue(SdkLevel.isAtLeastS());
+        setupPasspointConnection();
+        when(mPasspointManager.getVenueUrl(any(ScanResult.class))).thenReturn(new URL(VENUE_URL));
+        WnmData wnmData = WnmData.createTermsAndConditionsAccetanceRequiredEvent(TEST_BSSID,
+                TEST_TERMS_AND_CONDITIONS_URL);
+        when(mPasspointManager.handleTermsAndConditionsEvent(eq(wnmData),
+                any(WifiConfiguration.class))).thenReturn(new URL(TEST_TERMS_AND_CONDITIONS_URL));
+        mCmi.sendMessage(WifiMonitor.HS20_TERMS_AND_CONDITIONS_ACCEPTANCE_REQUIRED_EVENT,
+                0, 0, wnmData);
+        DhcpResultsParcelable dhcpResults = new DhcpResultsParcelable();
+        dhcpResults.baseConfiguration = new StaticIpConfiguration();
+        dhcpResults.baseConfiguration.gateway = InetAddresses.parseNumericAddress("1.2.3.4");
+        dhcpResults.baseConfiguration.ipAddress =
+                new LinkAddress(InetAddresses.parseNumericAddress("192.168.1.100"), 0);
+        dhcpResults.baseConfiguration.dnsServers.add(InetAddresses.parseNumericAddress("8.8.8.8"));
+        dhcpResults.leaseDuration = 3600;
+        injectDhcpSuccess(dhcpResults);
+        mCmi.mNetworkAgent = null;
+        mLooper.dispatchAll();
+        LinkProperties linkProperties = mock(LinkProperties.class);
+        mIpClientCallback.onLinkPropertiesChange(linkProperties);
+        mLooper.dispatchAll();
+        verify(mPasspointManager, times(2)).getVenueUrl(any(ScanResult.class));
+        final ArgumentCaptor<CaptivePortalData> captivePortalDataCaptor =
+                ArgumentCaptor.forClass(CaptivePortalData.class);
+        verify(linkProperties).setCaptivePortalData(captivePortalDataCaptor.capture());
+        assertEquals(WifiConfigurationTestUtil.TEST_PROVIDER_FRIENDLY_NAME,
+                captivePortalDataCaptor.getValue().getVenueFriendlyName());
+        assertEquals(VENUE_URL, captivePortalDataCaptor.getValue().getVenueInfoUrl().toString());
+        assertEquals(TEST_TERMS_AND_CONDITIONS_URL, captivePortalDataCaptor.getValue()
+                .getUserPortalUrl().toString());
+    }
+
+    /**
+     * Verify that the T&C WNM-Notification is handled by relaying to the Passpoint
+     * Manager.
+     */
+    @Test
+    public void testHandlePasspointTermsAndConditionsWnmNotification() throws Exception {
+        setupEapSimConnection();
+        WnmData wnmData = WnmData.createTermsAndConditionsAccetanceRequiredEvent(TEST_BSSID,
+                TEST_TERMS_AND_CONDITIONS_URL);
+        when(mPasspointManager.handleTermsAndConditionsEvent(eq(wnmData),
+                any(WifiConfiguration.class))).thenReturn(new URL(TEST_TERMS_AND_CONDITIONS_URL));
+        mCmi.sendMessage(WifiMonitor.HS20_TERMS_AND_CONDITIONS_ACCEPTANCE_REQUIRED_EVENT,
+                0, 0, wnmData);
+        mLooper.dispatchAll();
+        verify(mPasspointManager).handleTermsAndConditionsEvent(eq(wnmData),
+                any(WifiConfiguration.class));
+        verify(mWifiNative, never()).disconnect(anyString());
+    }
+
+    /**
+     * Verify that when a bad URL is received in the T&C WNM-Notification, the connection is
+     * disconnected.
+     */
+    @Test
+    public void testHandlePasspointTermsAndConditionsWnmNotificationWithBadUrl() throws Exception {
+        setupEapSimConnection();
+        WnmData wnmData = WnmData.createTermsAndConditionsAccetanceRequiredEvent(TEST_BSSID,
+                TEST_TERMS_AND_CONDITIONS_URL);
+        when(mPasspointManager.handleTermsAndConditionsEvent(eq(wnmData),
+                any(WifiConfiguration.class))).thenReturn(null);
+        mCmi.sendMessage(WifiMonitor.HS20_TERMS_AND_CONDITIONS_ACCEPTANCE_REQUIRED_EVENT,
+                0, 0, wnmData);
+        mLooper.dispatchAll();
+        verify(mPasspointManager).handleTermsAndConditionsEvent(eq(wnmData),
+                any(WifiConfiguration.class));
+        verify(mWifiNative).disconnect(eq(WIFI_IFACE_NAME));
+        verify(mWifiMetrics).logStaEvent(anyString(), eq(StaEvent.TYPE_FRAMEWORK_DISCONNECT),
+                eq(StaEvent.DISCONNECT_PASSPOINT_TAC));
+    }
+
+    /**
+     * Verify that the Transition Disable event is routed correctly.
+     */
+    @Test
+    public void testTransitionDisableEvent() throws Exception {
+        final int networkId = FRAMEWORK_NETWORK_ID;
+        final int indication = WifiMonitor.TDI_USE_WPA3_PERSONAL
+                | WifiMonitor.TDI_USE_WPA3_ENTERPRISE;
+
+        initializeAndAddNetworkAndVerifySuccess();
+
+        startConnectSuccess();
+
+        mCmi.sendMessage(WifiMonitor.TRANSITION_DISABLE_INDICATION,
+                networkId, indication);
+        mLooper.dispatchAll();
+
+        verify(mWifiConfigManager).updateNetworkTransitionDisable(
+                eq(networkId), eq(indication));
+    }
+
+    /**
+     * Verify that the network selection status will be updated with DISABLED_NETWORK_NOT_FOUND
+     * when number of NETWORK_NOT_FOUND_EVENT event reaches the threshold.
+     */
+    @Test
+    public void testNetworkNotFoundEventUpdatesAssociationFailureStatus()
+            throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        initializeAndAddNetworkAndVerifySuccess();
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, TEST_BSSID_STR);
+        for (int i = 0; i < ClientModeImpl.NETWORK_NOT_FOUND_EVENT_THRESHOLD; i++) {
+            mCmi.sendMessage(WifiMonitor.NETWORK_NOT_FOUND_EVENT, DEFAULT_TEST_SSID);
+        }
+        mLooper.dispatchAll();
+        verify(mWifiConfigManager).updateNetworkSelectionStatus(anyInt(),
+                eq(WifiConfiguration.NetworkSelectionStatus.DISABLED_NETWORK_NOT_FOUND));
+        verify(mWifiConfigManager).setRecentFailureAssociationStatus(anyInt(),
+                eq(WifiConfiguration.RECENT_FAILURE_NETWORK_NOT_FOUND));
+
+        verify(mWifiDiagnostics).reportConnectionEvent(
+                eq(WifiDiagnostics.CONNECTION_EVENT_FAILED), any());
+        verify(mWifiConnectivityManager).handleConnectionAttemptEnded(
+                mClientModeManager,
+                WifiMetrics.ConnectionEvent.FAILURE_NETWORK_NOT_FOUND, TEST_BSSID_STR, TEST_SSID);
+        verify(mWifiNetworkFactory).handleConnectionAttemptEnded(
+                eq(WifiMetrics.ConnectionEvent.FAILURE_NETWORK_NOT_FOUND),
+                any(WifiConfiguration.class), eq(TEST_BSSID_STR));
+        verify(mWifiNetworkSuggestionsManager).handleConnectionAttemptEnded(
+                eq(WifiMetrics.ConnectionEvent.FAILURE_NETWORK_NOT_FOUND),
+                any(WifiConfiguration.class), eq(null));
+        verify(mWifiMetrics, never())
+                .incrementNumBssidDifferentSelectionBetweenFrameworkAndFirmware();
+        verifyConnectionEventTimeoutDoesNotOccur();
+
+        clearInvocations(mWifiDiagnostics, mWifiConfigManager, mWifiNetworkFactory,
+                mWifiNetworkSuggestionsManager);
+
+        // Now trigger a disconnect event from supplicant, this should be ignored since the
+        // connection tracking should have already ended.
+        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT,
+                new DisconnectEventInfo(TEST_SSID, TEST_BSSID_STR, 0, false));
+        mLooper.dispatchAll();
+
+        verifyNoMoreInteractions(mWifiDiagnostics, mWifiConfigManager, mWifiNetworkFactory,
+                mWifiNetworkSuggestionsManager);
+    }
+
+    /**
+     * Verify that the subscriberId will be filled in NetworkAgentConfig
+     * after connecting to a merged network. And also VCN policy will be checked.
+     */
+    @Test
+    public void triggerConnectToMergedNetwork() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        VcnManager vcnManager = mock(VcnManager.class);
+        VcnNetworkPolicyResult vcnUnderlyingNetworkPolicy = mock(VcnNetworkPolicyResult.class);
+        when(mContext.getSystemService(VcnManager.class)).thenReturn(vcnManager);
+        ArgumentCaptor<VcnManager.VcnNetworkPolicyChangeListener> policyChangeListenerCaptor =
+                ArgumentCaptor.forClass(VcnManager.VcnNetworkPolicyChangeListener.class);
+        InOrder inOrder = inOrder(vcnManager, vcnUnderlyingNetworkPolicy);
+        doAnswer(new AnswerWithArguments() {
+            public VcnNetworkPolicyResult answer(NetworkCapabilities networkCapabilities,
+                    LinkProperties linkProperties) throws Exception {
+                networkCapabilities.removeCapability(
+                        NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
+                when(vcnUnderlyingNetworkPolicy.getNetworkCapabilities())
+                        .thenReturn(networkCapabilities);
+                return vcnUnderlyingNetworkPolicy;
+            }
+        }).when(vcnManager).applyVcnNetworkPolicy(any(), any());
+        when(vcnUnderlyingNetworkPolicy.isTeardownRequested()).thenReturn(false);
+
+        String testSubscriberId = "TestSubscriberId";
+        when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mDataTelephonyManager);
+        when(mDataTelephonyManager.getSubscriberId()).thenReturn(testSubscriberId);
+        mConnectedNetwork.carrierMerged = true;
+        mConnectedNetwork.subscriptionId = DATA_SUBID;
+        connect();
+        expectRegisterNetworkAgent((agentConfig) -> {
+            assertEquals(testSubscriberId, agentConfig.subscriberId);
+        }, (cap) -> {
+                assertFalse(cap.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED));
+                assertEquals(Collections.singleton(DATA_SUBID), cap.getSubscriptionIds());
+            });
+        // Verify VCN policy listener is registered
+        inOrder.verify(vcnManager).addVcnNetworkPolicyChangeListener(any(),
+                    policyChangeListenerCaptor.capture());
+        assertNotNull(policyChangeListenerCaptor.getValue());
+
+        // Verify getting new capability from VcnManager
+        inOrder.verify(vcnManager).applyVcnNetworkPolicy(any(NetworkCapabilities.class),
+                any(LinkProperties.class));
+        inOrder.verify(vcnUnderlyingNetworkPolicy).isTeardownRequested();
+        inOrder.verify(vcnUnderlyingNetworkPolicy).getNetworkCapabilities();
+
+        // Update policy with tear down request.
+        when(vcnUnderlyingNetworkPolicy.isTeardownRequested()).thenReturn(true);
+        policyChangeListenerCaptor.getValue().onPolicyChanged();
+        mLooper.dispatchAll();
+
+        // The merged carrier network should be disconnected.
+        inOrder.verify(vcnManager).applyVcnNetworkPolicy(any(NetworkCapabilities.class),
+                any(LinkProperties.class));
+        inOrder.verify(vcnUnderlyingNetworkPolicy).isTeardownRequested();
+        inOrder.verify(vcnUnderlyingNetworkPolicy).getNetworkCapabilities();
+        verify(mWifiNative).disconnect(WIFI_IFACE_NAME);
+        verify(mWifiMetrics).logStaEvent(anyString(), eq(StaEvent.TYPE_FRAMEWORK_DISCONNECT),
+                eq(StaEvent.DISCONNECT_VCN_REQUEST));
+        DisconnectEventInfo disconnectEventInfo =
+                new DisconnectEventInfo(TEST_SSID, TEST_BSSID_STR, 0, false);
+        mCmi.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, disconnectEventInfo);
+        mLooper.dispatchAll();
+        assertEquals("DisconnectedState", getCurrentState().getName());
+
+        // In DisconnectedState, policy update should result no capability update.
+        reset(mWifiConfigManager, vcnManager);
+        policyChangeListenerCaptor.getValue().onPolicyChanged();
+        verifyNoMoreInteractions(mWifiConfigManager, vcnManager);
+    }
+
+    /**
+     * Verify when connect to a unmerged network, will not mark it as a VCN network.
+     */
+    @Test
+    public void triggerConnectToUnmergedNetwork() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        VcnManager vcnManager = mock(VcnManager.class);
+        when(mContext.getSystemService(VcnManager.class)).thenReturn(vcnManager);
+        VcnNetworkPolicyResult vcnUnderlyingNetworkPolicy = mock(VcnNetworkPolicyResult.class);
+        ArgumentCaptor<VcnManager.VcnNetworkPolicyChangeListener> policyChangeListenerCaptor =
+                ArgumentCaptor.forClass(VcnManager.VcnNetworkPolicyChangeListener.class);
+        doAnswer(new AnswerWithArguments() {
+            public VcnNetworkPolicyResult answer(NetworkCapabilities networkCapabilities,
+                    LinkProperties linkProperties) throws Exception {
+                networkCapabilities.removeCapability(
+                        NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
+                when(vcnUnderlyingNetworkPolicy.getNetworkCapabilities())
+                        .thenReturn(networkCapabilities);
+                return vcnUnderlyingNetworkPolicy;
+            }
+        }).when(vcnManager).applyVcnNetworkPolicy(any(), any());
+        when(vcnUnderlyingNetworkPolicy.isTeardownRequested()).thenReturn(false);
+
+        String testSubscriberId = "TestSubscriberId";
+        when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mDataTelephonyManager);
+        when(mDataTelephonyManager.getSubscriberId()).thenReturn(testSubscriberId);
+        connect();
+        expectRegisterNetworkAgent((agentConfig) -> {
+            assertEquals(null, agentConfig.subscriberId);
+        }, (cap) -> {
+                assertTrue(cap.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED));
+                assertTrue(cap.getSubscriptionIds().isEmpty());
+            });
+
+        // Verify VCN policy listener is registered
+        verify(vcnManager, atLeastOnce()).addVcnNetworkPolicyChangeListener(any(),
+                policyChangeListenerCaptor.capture());
+        assertNotNull(policyChangeListenerCaptor.getValue());
+
+        policyChangeListenerCaptor.getValue().onPolicyChanged();
+        mLooper.dispatchAll();
+
+        verifyNoMoreInteractions(vcnManager, vcnUnderlyingNetworkPolicy);
+    }
+
+    /**
+     * Verifies that we trigger a disconnect when the {@link WifiConfigManager}.
+     * OnNetworkUpdateListener#onNetworkRemoved(WifiConfiguration)} is invoked.
+     */
+    @Test
+    public void testOnCarrierOffloadDisabled() throws Exception {
+        mConnectedNetwork.subscriptionId = DATA_SUBID;
+        connect();
+
+        mOffloadDisabledListenerArgumentCaptor.getValue()
+                .onCarrierOffloadDisabled(DATA_SUBID, false);
+        mLooper.dispatchAll();
+
+        verify(mWifiNative).disconnect(WIFI_IFACE_NAME);
+        verify(mWifiMetrics).logStaEvent(anyString(), eq(StaEvent.TYPE_FRAMEWORK_DISCONNECT),
+                eq(StaEvent.DISCONNECT_CARRIER_OFFLOAD_DISABLED));
+    }
+
+    @Test
+    public void testPacketFilter() throws Exception {
+        connect();
+
+        byte[] filter = new byte[20];
+        new Random().nextBytes(filter);
+        mIpClientCallback.installPacketFilter(filter);
+        mLooper.dispatchAll();
+
+        verify(mWifiNative).installPacketFilter(WIFI_IFACE_NAME, filter);
+
+        when(mWifiNative.readPacketFilter(WIFI_IFACE_NAME)).thenReturn(filter);
+        mIpClientCallback.startReadPacketFilter();
+        mLooper.dispatchAll();
+        verify(mIpClient).readPacketFilterComplete(filter);
+        verify(mWifiNative).readPacketFilter(WIFI_IFACE_NAME);
+    }
+
+    @Test
+    public void testPacketFilterOnRoleChangeOnSecondaryCmm() throws Exception {
+        when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_TRANSIENT);
+        connect();
+
+        verify(mWifiScoreReport).onRoleChanged(ROLE_CLIENT_PRIMARY);
+
+        byte[] filter = new byte[20];
+        new Random().nextBytes(filter);
+        mIpClientCallback.installPacketFilter(filter);
+        mLooper.dispatchAll();
+
+        // just cache the data.
+        verify(mWifiNative, never()).installPacketFilter(WIFI_IFACE_NAME, filter);
+
+        when(mWifiNative.readPacketFilter(WIFI_IFACE_NAME)).thenReturn(filter);
+        mIpClientCallback.startReadPacketFilter();
+        mLooper.dispatchAll();
+        verify(mIpClient).readPacketFilterComplete(filter);
+        // return the cached the data.
+        verify(mWifiNative, never()).readPacketFilter(WIFI_IFACE_NAME);
+
+        // Now invoke role change, that should apply the APF
+        when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_PRIMARY);
+        mCmi.onRoleChanged();
+        verify(mWifiNative).installPacketFilter(WIFI_IFACE_NAME, filter);
+        verify(mWifiScoreReport, times(2)).onRoleChanged(ROLE_CLIENT_PRIMARY);
+    }
+
+
+    @Test
+    public void testPacketFilterOnRoleChangeOnSecondaryCmmWithSupportForNonPrimaryApf()
+            throws Exception {
+        mResources.setBoolean(R.bool.config_wifiEnableApfOnNonPrimarySta, true);
+        when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_TRANSIENT);
+        connect();
+
+        byte[] filter = new byte[20];
+        new Random().nextBytes(filter);
+        mIpClientCallback.installPacketFilter(filter);
+        mLooper.dispatchAll();
+
+        // apply the data.
+        verify(mWifiNative).installPacketFilter(WIFI_IFACE_NAME, filter);
+
+        when(mWifiNative.readPacketFilter(WIFI_IFACE_NAME)).thenReturn(filter);
+        mIpClientCallback.startReadPacketFilter();
+        mLooper.dispatchAll();
+        verify(mIpClient).readPacketFilterComplete(filter);
+        // return the applied data.
+        verify(mWifiNative).readPacketFilter(WIFI_IFACE_NAME);
+
+        // Now invoke role change, that should not apply the APF
+        when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_PRIMARY);
+        mCmi.onRoleChanged();
+        // ignore (since it was already applied)
+        verify(mWifiNative, times(1)).installPacketFilter(WIFI_IFACE_NAME, filter);
+    }
+
+    @Test
+    public void testWifiInfoUpdateOnRoleChange() throws Exception {
+        when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_TRANSIENT);
+        connect();
+        // Should not set WifiInfo.isPrimary
+        expectRegisterNetworkAgent((config) -> { }, (cap) -> {
+            if (SdkLevel.isAtLeastS()) {
+                WifiInfo wifiInfoFromTi = (WifiInfo) cap.getTransportInfo();
+                assertFalse(wifiInfoFromTi.isPrimary());
+            }
+        });
+        reset(mWifiNetworkAgent);
+
+        // Now invoke role change, that should set WifiInfo.isPrimary
+        when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_PRIMARY);
+        mCmi.onRoleChanged();
+        expectNetworkAgentUpdateCapabilities((cap) -> {
+            if (SdkLevel.isAtLeastS()) {
+                WifiInfo wifiInfoFromTi = (WifiInfo) cap.getTransportInfo();
+                assertTrue(wifiInfoFromTi.isPrimary());
+            }
+        });
+    }
+
+    /**
+     * Verify onCellularConnectivityChanged plumbs the information to the right locations.
+     */
+    @Test
+    public void testOnCellularConnectivityChanged() {
+        mCmi.onCellularConnectivityChanged(WifiDataStall.CELLULAR_DATA_AVAILABLE);
+        verify(mWifiConfigManager).onCellularConnectivityChanged(
+                WifiDataStall.CELLULAR_DATA_AVAILABLE);
+
+        mCmi.onCellularConnectivityChanged(WifiDataStall.CELLULAR_DATA_NOT_AVAILABLE);
+        verify(mWifiConfigManager).onCellularConnectivityChanged(
+                WifiDataStall.CELLULAR_DATA_NOT_AVAILABLE);
+    }
+
+    /**
+     * Verify that when cellular data is lost and wifi is not connected, we force a connectivity
+     * scan.
+     */
+    @Test
+    public void testOnCellularConnectivityChangedForceConnectivityScan() throws Exception {
+        mResources.setBoolean(R.bool.config_wifiScanOnCellularDataLossEnabled, true);
+        // verify a connectivity scan is forced since wifi is not connected
+        mCmi.onCellularConnectivityChanged(WifiDataStall.CELLULAR_DATA_NOT_AVAILABLE);
+        verify(mWifiConnectivityManager).forceConnectivityScan(WIFI_WORK_SOURCE);
+
+        // verify that after wifi is connected, loss of cellular data will not trigger scans.
+        connect();
+        mCmi.onCellularConnectivityChanged(WifiDataStall.CELLULAR_DATA_NOT_AVAILABLE);
+        verify(mWifiConnectivityManager).forceConnectivityScan(WIFI_WORK_SOURCE);
+    }
+
+    private void setScreenState(boolean screenOn) {
+        BroadcastReceiver broadcastReceiver = mScreenStateBroadcastReceiverCaptor.getValue();
+        assertNotNull(broadcastReceiver);
+        Intent intent = new Intent(screenOn  ? ACTION_SCREEN_ON : ACTION_SCREEN_OFF);
+        broadcastReceiver.onReceive(mContext, intent);
+    }
+
+    @Test
+    public void verifyRssiPollOnScreenStateChange() throws Exception {
+        setScreenState(true);
+        connect();
+        clearInvocations(mWifiNative, mWifiMetrics, mWifiDataStall);
+
+        WifiLinkLayerStats oldLLStats = new WifiLinkLayerStats();
+        when(mWifiNative.getWifiLinkLayerStats(any())).thenReturn(oldLLStats);
+        mLooper.moveTimeForward(mWifiGlobals.getPollRssiIntervalMillis());
+        mLooper.dispatchAll();
+        verify(mWifiNative).getWifiLinkLayerStats(WIFI_IFACE_NAME);
+        verify(mWifiDataStall).checkDataStallAndThroughputSufficiency(WIFI_IFACE_NAME,
+                mConnectionCapabilities, null, oldLLStats, mWifiInfo);
+        verify(mWifiMetrics).incrementWifiLinkLayerUsageStats(WIFI_IFACE_NAME, oldLLStats);
+
+        WifiLinkLayerStats newLLStats = new WifiLinkLayerStats();
+        when(mWifiNative.getWifiLinkLayerStats(any())).thenReturn(newLLStats);
+        mLooper.moveTimeForward(mWifiGlobals.getPollRssiIntervalMillis());
+        mLooper.dispatchAll();
+        verify(mWifiNative, times(2)).getWifiLinkLayerStats(WIFI_IFACE_NAME);
+
+        verify(mWifiDataStall).checkDataStallAndThroughputSufficiency(WIFI_IFACE_NAME,
+                mConnectionCapabilities, oldLLStats, newLLStats, mWifiInfo);
+        verify(mWifiMetrics).incrementWifiLinkLayerUsageStats(WIFI_IFACE_NAME, newLLStats);
+
+        // Now set the screen state to false & move time forward, ensure no more link layer stats
+        // collection.
+        setScreenState(false);
+        mLooper.dispatchAll();
+        clearInvocations(mWifiNative, mWifiMetrics, mWifiDataStall);
+
+        mLooper.moveTimeForward(mWifiGlobals.getPollRssiIntervalMillis());
+        mLooper.dispatchAll();
+
+        verifyNoMoreInteractions(mWifiNative, mWifiMetrics, mWifiDataStall);
+    }
+
+    @Test
+    public void verifyRssiPollOnSecondaryCmm() throws Exception {
+        when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_TRANSIENT);
+        mCmi.onRoleChanged();
+        setScreenState(true);
+        connect();
+        clearInvocations(mWifiNative, mWifiMetrics, mWifiDataStall);
+
+        verifyNoMoreInteractions(mWifiNative, mWifiMetrics, mWifiDataStall);
+
+        when(mWifiNative.getWifiLinkLayerStats(any())).thenReturn(new WifiLinkLayerStats());
+
+        // No link layer stats collection on secondary CMM.
+        mLooper.moveTimeForward(mWifiGlobals.getPollRssiIntervalMillis());
+        mLooper.dispatchAll();
+
+        verifyNoMoreInteractions(mWifiNative, mWifiMetrics, mWifiDataStall);
+    }
+
+    @Test
+    public void verifyRssiPollOnOnRoleChangeToPrimary() throws Exception {
+        when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_TRANSIENT);
+        mCmi.onRoleChanged();
+        setScreenState(true);
+        connect();
+        clearInvocations(mWifiNative, mWifiMetrics, mWifiDataStall);
+
+        when(mWifiNative.getWifiLinkLayerStats(any())).thenReturn(new WifiLinkLayerStats());
+
+        // No link layer stats collection on secondary CMM.
+        mLooper.moveTimeForward(mWifiGlobals.getPollRssiIntervalMillis());
+        mLooper.dispatchAll();
+
+        verifyNoMoreInteractions(mWifiNative, mWifiMetrics, mWifiDataStall);
+
+        // Now invoke role change, that should start rssi polling on the new primary.
+        when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_PRIMARY);
+        mCmi.onRoleChanged();
+        mLooper.dispatchAll();
+        clearInvocations(mWifiNative, mWifiMetrics, mWifiDataStall);
+
+        WifiLinkLayerStats oldLLStats = new WifiLinkLayerStats();
+        when(mWifiNative.getWifiLinkLayerStats(any())).thenReturn(oldLLStats);
+        mLooper.moveTimeForward(mWifiGlobals.getPollRssiIntervalMillis());
+        mLooper.dispatchAll();
+        verify(mWifiNative).getWifiLinkLayerStats(WIFI_IFACE_NAME);
+        verify(mWifiDataStall).checkDataStallAndThroughputSufficiency(WIFI_IFACE_NAME,
+                mConnectionCapabilities, null, oldLLStats, mWifiInfo);
+        verify(mWifiMetrics).incrementWifiLinkLayerUsageStats(WIFI_IFACE_NAME, oldLLStats);
+    }
+
+    @Test
+    public void verifyRssiPollOnOnRoleChangeToSecondary() throws Exception {
+        setScreenState(true);
+        connect();
+        clearInvocations(mWifiNative, mWifiMetrics, mWifiDataStall);
+
+        // RSSI polling is enabled on primary.
+        WifiLinkLayerStats oldLLStats = new WifiLinkLayerStats();
+        when(mWifiNative.getWifiLinkLayerStats(any())).thenReturn(oldLLStats);
+        mLooper.moveTimeForward(mWifiGlobals.getPollRssiIntervalMillis());
+        mLooper.dispatchAll();
+        verify(mWifiNative).getWifiLinkLayerStats(WIFI_IFACE_NAME);
+        verify(mWifiDataStall).checkDataStallAndThroughputSufficiency(WIFI_IFACE_NAME,
+                mConnectionCapabilities, null, oldLLStats, mWifiInfo);
+        verify(mWifiMetrics).incrementWifiLinkLayerUsageStats(WIFI_IFACE_NAME, oldLLStats);
+
+        // Now invoke role change, that should stop rssi polling on the secondary.
+        when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_TRANSIENT);
+        mCmi.onRoleChanged();
+        mLooper.dispatchAll();
+        clearInvocations(mWifiNative, mWifiMetrics, mWifiDataStall);
+
+        // No link layer stats collection on secondary CMM.
+        mLooper.moveTimeForward(mWifiGlobals.getPollRssiIntervalMillis());
+        mLooper.dispatchAll();
+
+        verifyNoMoreInteractions(mWifiNative, mWifiMetrics, mWifiDataStall);
+    }
+
+    @Test
+    public void testClientModeImplWhenIpClientIsNotReady() throws Exception {
+        WifiConfiguration config = mConnectedNetwork;
+        config.networkId = FRAMEWORK_NETWORK_ID;
+        config.setRandomizedMacAddress(TEST_LOCAL_MAC_ADDRESS);
+        config.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_AUTO;
+        config.getNetworkSelectionStatus().setHasEverConnected(mTestNetworkParams.hasEverConnected);
+        assertNull(config.getNetworkSelectionStatus().getCandidateSecurityParams());
+
+        mFrameworkFacade = mock(FrameworkFacade.class);
+        ArgumentCaptor<IpClientCallbacks> captor = ArgumentCaptor.forClass(IpClientCallbacks.class);
+        // reset mWifiNative since initializeCmi() was called in setup()
+        resetWifiNative();
+
+        // reinitialize ClientModeImpl with IpClient is not ready.
+        initializeCmi();
+        verify(mFrameworkFacade).makeIpClient(any(), anyString(), captor.capture());
+
+        // Manually connect should fail.
+        IActionListener connectActionListener = mock(IActionListener.class);
+        mCmi.connectNetwork(
+                new NetworkUpdateResult(config.networkId),
+                new ActionListenerWrapper(connectActionListener),
+                Binder.getCallingUid());
+        mLooper.dispatchAll();
+        verify(connectActionListener).onFailure(WifiManager.ERROR);
+        verify(mWifiConfigManager, never())
+                .getConfiguredNetworkWithoutMasking(eq(config.networkId));
+        verify(mWifiNative, never()).connectToNetwork(eq(WIFI_IFACE_NAME), eq(config));
+
+        // Auto connect should also fail
+        mCmi.startConnectToNetwork(config.networkId, MANAGED_PROFILE_UID, config.BSSID);
+        mLooper.dispatchAll();
+        verify(mWifiConfigManager, never())
+                .getConfiguredNetworkWithoutMasking(eq(config.networkId));
+        verify(mWifiNative, never()).connectToNetwork(eq(WIFI_IFACE_NAME), eq(config));
+
+        // Make IpClient ready connection should succeed.
+        captor.getValue().onIpClientCreated(mIpClient);
+        mLooper.dispatchAll();
+
+        triggerConnect();
     }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/ClientModeManagerBroadcastQueueTest.java b/service/tests/wifitests/src/com/android/server/wifi/ClientModeManagerBroadcastQueueTest.java
new file mode 100644
index 0000000..91479e1
--- /dev/null
+++ b/service/tests/wifitests/src/com/android/server/wifi/ClientModeManagerBroadcastQueueTest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_PRIMARY;
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SCAN_ONLY;
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SECONDARY_TRANSIENT;
+
+import static org.mockito.Mockito.*;
+
+import android.content.Context;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.wifi.ActiveModeWarden.ModeChangeCallback;
+import com.android.server.wifi.ActiveModeWarden.PrimaryClientModeManagerChangedCallback;
+import com.android.server.wifi.ClientModeManagerBroadcastQueue.QueuedBroadcast;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+
+/** Unit tests for {@link ClientModeManagerBroadcastQueue}. */
+@SmallTest
+public class ClientModeManagerBroadcastQueueTest extends WifiBaseTest {
+
+    private ClientModeManagerBroadcastQueue mBroadcastQueue;
+
+    @Mock private Context mContext;
+    @Mock private ActiveModeWarden mActiveModeWarden;
+    @Mock private ConcreteClientModeManager mPrimaryManager;
+    @Mock private ConcreteClientModeManager mSecondaryManager;
+    @Mock private QueuedBroadcast mQueuedBroadcast1;
+    @Mock private QueuedBroadcast mQueuedBroadcast2;
+    @Mock private QueuedBroadcast mQueuedBroadcast3;
+
+    @Captor private ArgumentCaptor<ModeChangeCallback> mModeChangeCallbackCaptor;
+    @Captor private ArgumentCaptor<PrimaryClientModeManagerChangedCallback> mPrimaryChangedCaptor;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        when(mPrimaryManager.getRole()).thenReturn(ROLE_CLIENT_PRIMARY);
+        when(mSecondaryManager.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_TRANSIENT);
+
+        mBroadcastQueue = new ClientModeManagerBroadcastQueue(mActiveModeWarden, mContext);
+
+        verify(mActiveModeWarden).registerModeChangeCallback(mModeChangeCallbackCaptor.capture());
+        verify(mActiveModeWarden)
+                .registerPrimaryClientModeManagerChangedCallback(mPrimaryChangedCaptor.capture());
+    }
+
+    @Test
+    public void primaryManagerSendBroadcast_sentImmediately() {
+        mBroadcastQueue.queueOrSendBroadcast(mPrimaryManager, mQueuedBroadcast1);
+
+        verify(mQueuedBroadcast1).send();
+    }
+
+    @Test
+    public void scanOnlyModeManagerSendBroadcast_sentImmediately() {
+        when(mPrimaryManager.getRole()).thenReturn(ROLE_CLIENT_SCAN_ONLY);
+        mBroadcastQueue.queueOrSendBroadcast(mPrimaryManager, mQueuedBroadcast1);
+
+        verify(mQueuedBroadcast1).send();
+    }
+
+    @Test
+    public void nonPrimaryManagerSendBroadcast_notSentImmediately() {
+        mBroadcastQueue.queueOrSendBroadcast(mSecondaryManager, mQueuedBroadcast1);
+
+        verify(mQueuedBroadcast1, never()).send();
+    }
+
+    @Test
+    public void secondaryManagerSendBroadcast_queuedAndSentOnPrimaryChangeInOrderAndCleared()
+            throws Exception {
+        InOrder order = inOrder(mQueuedBroadcast1, mQueuedBroadcast2, mQueuedBroadcast3, mContext);
+
+        // queue first broadcast - not sent
+        mBroadcastQueue.queueOrSendBroadcast(mSecondaryManager, mQueuedBroadcast1);
+        order.verify(mQueuedBroadcast1, never()).send();
+
+        // queue second broadcast - neither first nor second sent
+        mBroadcastQueue.queueOrSendBroadcast(mSecondaryManager, mQueuedBroadcast2);
+        order.verify(mQueuedBroadcast1, never()).send();
+        order.verify(mQueuedBroadcast2, never()).send();
+
+        // primary changed - queued broadcasts sent in order
+        when(mSecondaryManager.getRole()).thenReturn(ROLE_CLIENT_PRIMARY);
+        when(mPrimaryManager.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_TRANSIENT);
+        when(mActiveModeWarden.getClientModeManagersInRoles(ROLE_CLIENT_SECONDARY_TRANSIENT))
+                .thenReturn(Arrays.asList(mPrimaryManager));
+        when(mPrimaryManager.isConnected()).thenReturn(true);
+        mPrimaryChangedCaptor.getValue().onChange(mPrimaryManager, mSecondaryManager);
+        // queued broadcasts sent
+        order.verify(mQueuedBroadcast1).send();
+        order.verify(mQueuedBroadcast2).send();
+
+        // queue third broadcast - triggered immediately, first and second not triggered again
+        mBroadcastQueue.queueOrSendBroadcast(mSecondaryManager, mQueuedBroadcast3);
+        order.verify(mQueuedBroadcast1, never()).send();
+        order.verify(mQueuedBroadcast2, never()).send();
+        order.verify(mQueuedBroadcast3).send();
+
+        // primary changed back
+        when(mPrimaryManager.getRole()).thenReturn(ROLE_CLIENT_PRIMARY);
+        when(mSecondaryManager.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_TRANSIENT);
+        mPrimaryChangedCaptor.getValue().onChange(mSecondaryManager, mPrimaryManager);
+
+        // primary changed again
+        when(mSecondaryManager.getRole()).thenReturn(ROLE_CLIENT_PRIMARY);
+        when(mPrimaryManager.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_TRANSIENT);
+        mPrimaryChangedCaptor.getValue().onChange(mPrimaryManager, mSecondaryManager);
+
+        // broadcasts not triggered again (verify that the queued broadcasts were cleared)
+        order.verify(mQueuedBroadcast1, never()).send();
+        order.verify(mQueuedBroadcast2, never()).send();
+        order.verify(mQueuedBroadcast3, never()).send();
+    }
+
+    @Test
+    public void secondaryManagerSendBroadcast_clientModeManagerRemoved_queueCleared() {
+        InOrder order = inOrder(mQueuedBroadcast1, mQueuedBroadcast2, mQueuedBroadcast3);
+
+        // queue broadcasts
+        mBroadcastQueue.queueOrSendBroadcast(mSecondaryManager, mQueuedBroadcast1);
+        mBroadcastQueue.queueOrSendBroadcast(mSecondaryManager, mQueuedBroadcast2);
+
+        // remove manager
+        mModeChangeCallbackCaptor.getValue().onActiveModeManagerRemoved(mSecondaryManager);
+
+        // broadcasts not sent
+        order.verify(mQueuedBroadcast1, never()).send();
+        order.verify(mQueuedBroadcast2, never()).send();
+
+        // trigger queued broadcasts
+        when(mSecondaryManager.getRole()).thenReturn(ROLE_CLIENT_PRIMARY);
+        when(mPrimaryManager.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_TRANSIENT);
+        mPrimaryChangedCaptor.getValue().onChange(mPrimaryManager, mSecondaryManager);
+
+        // broadcasts still not sent because queue should have been cleared
+        order.verify(mQueuedBroadcast1, never()).send();
+        order.verify(mQueuedBroadcast2, never()).send();
+    }
+}
diff --git a/service/tests/wifitests/src/com/android/server/wifi/ConcreteCandidate.java b/service/tests/wifitests/src/com/android/server/wifi/ConcreteCandidate.java
index 37d2945..b5f1f0f 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/ConcreteCandidate.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/ConcreteCandidate.java
@@ -31,6 +31,8 @@
     private boolean mIsPasspoint;
     private boolean mIsEphemeral;
     private boolean mIsTrusted = true;
+    private boolean mIsOemPaid;
+    private boolean mIsOemPrivate;
     private boolean mCarrierOrPrivileged;
     private boolean mIsMetered;
     private boolean mHasNoInternetAccess;
@@ -57,6 +59,7 @@
         mIsPasspoint = candidate.isPasspoint();
         mIsEphemeral = candidate.isEphemeral();
         mIsTrusted = candidate.isTrusted();
+        mIsOemPaid = candidate.isOemPaid();
         mCarrierOrPrivileged = candidate.isCarrierOrPrivileged();
         mIsMetered = candidate.isMetered();
         mHasNoInternetAccess = candidate.hasNoInternetAccess();
@@ -136,6 +139,26 @@
         return mIsTrusted;
     }
 
+    public ConcreteCandidate setOemPaid(boolean isOemPaid) {
+        mIsOemPaid = isOemPaid;
+        return this;
+    }
+
+    @Override
+    public boolean isOemPaid() {
+        return mIsOemPaid;
+    }
+
+    public ConcreteCandidate setOemPrivate(boolean isOemPrivate) {
+        mIsOemPrivate = isOemPrivate;
+        return this;
+    }
+
+    @Override
+    public boolean isOemPrivate() {
+        return mIsOemPrivate;
+    }
+
     public ConcreteCandidate setCarrierOrPrivileged(boolean carrierOrPrivileged) {
         mCarrierOrPrivileged = carrierOrPrivileged;
         return this;
diff --git a/service/tests/wifitests/src/com/android/server/wifi/ClientModeManagerTest.java b/service/tests/wifitests/src/com/android/server/wifi/ConcreteClientModeManagerTest.java
similarity index 65%
rename from service/tests/wifitests/src/com/android/server/wifi/ClientModeManagerTest.java
rename to service/tests/wifitests/src/com/android/server/wifi/ConcreteClientModeManagerTest.java
index 92f3d9b..819750f 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/ClientModeManagerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/ConcreteClientModeManagerTest.java
@@ -26,9 +26,11 @@
 import static android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_PRIMARY;
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SCAN_ONLY;
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SECONDARY_TRANSIENT;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -44,9 +46,13 @@
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
 import android.net.NetworkRequest;
+import android.net.wifi.IWifiConnectedNetworkScorer;
+import android.net.wifi.WifiManager;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.PersistableBundle;
 import android.os.UserHandle;
+import android.os.WorkSource;
 import android.os.test.TestLooper;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.CarrierConfigManager;
@@ -57,6 +63,7 @@
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 
+import com.android.server.wifi.ClientModeManagerBroadcastQueue.QueuedBroadcast;
 import com.android.wifi.resources.R;
 
 import org.junit.After;
@@ -73,27 +80,29 @@
 import java.util.concurrent.Executor;
 
 /**
- * Unit tests for {@link ClientModeManager}.
+ * Unit tests for {@link ConcreteClientModeManager}.
  */
 @SmallTest
-public class ClientModeManagerTest extends WifiBaseTest {
+public class ConcreteClientModeManagerTest extends WifiBaseTest {
     private static final String TAG = "ClientModeManagerTest";
     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;
+    private static final WorkSource TEST_WORKSOURCE = new WorkSource();
+    private static final WorkSource TEST_WORKSOURCE2 = new WorkSource();
 
     TestLooper mLooper;
 
-    ClientModeManager mClientModeManager;
+    ConcreteClientModeManager mClientModeManager;
 
     @Mock Context mContext;
     @Mock WifiMetrics mWifiMetrics;
     @Mock WifiNative mWifiNative;
     @Mock Clock mClock;
-    @Mock ClientModeManager.Listener mListener;
-    @Mock SarManager mSarManager;
+    @Mock ActiveModeManager.Listener<ConcreteClientModeManager> mListener;
     @Mock WakeupController mWakeupController;
+    @Mock WifiInjector mWifiInjector;
     @Mock ClientModeImpl mClientModeImpl;
     @Mock CarrierConfigManager mCarrierConfigManager;
     @Mock PersistableBundle mCarrierConfigBundle;
@@ -101,6 +110,12 @@
     @Mock ConnectivityManager mConnectivityManager;
     @Mock SubscriptionManager mSubscriptionManager;
     @Mock SubscriptionInfo mActiveSubscriptionInfo;
+    @Mock SelfRecovery mSelfRecovery;
+    @Mock WifiGlobals mWifiGlobals;
+    @Mock ScanOnlyModeImpl mScanOnlyModeImpl;
+    @Mock DefaultClientModeManager mDefaultClientModeManager;
+    @Mock ClientModeManagerBroadcastQueue mBroadcastQueue;
+
     private RegistrationManager.RegistrationCallback mImsMmTelManagerRegistrationCallback = null;
     private @RegistrationManager.ImsRegistrationState int mCurrentImsRegistrationState =
             RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED;
@@ -130,6 +145,9 @@
         when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE))
                 .thenReturn(mConnectivityManager);
         when(mContext.getResources()).thenReturn(mResources);
+        when(mWifiInjector.makeClientModeImpl(any(), any(), anyBoolean()))
+                .thenReturn(mClientModeImpl);
+        when(mWifiInjector.makeScanOnlyModeImpl(any())).thenReturn(mScanOnlyModeImpl);
     }
 
     /*
@@ -217,10 +235,15 @@
                 return mElapsedSinceBootMillis;
             }
         }).when(mClock).getElapsedSinceBootMillis();
+        when(mWifiNative.replaceStaIfaceRequestorWs(TEST_INTERFACE_NAME, TEST_WORKSOURCE))
+                .thenReturn(true);
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ClientModeManager manager, QueuedBroadcast broadcast) {
+                broadcast.send();
+            }
+        }).when(mBroadcastQueue).queueOrSendBroadcast(any(), any());
 
         mLooper = new TestLooper();
-        mClientModeManager = createClientModeManager();
-        mLooper.dispatchAll();
     }
 
     @After
@@ -228,22 +251,22 @@
         mStaticMockSession.finishMocking();
     }
 
-    private ClientModeManager createClientModeManager() {
-        return new ClientModeManager(mContext, mLooper.getLooper(), mClock, mWifiNative, mListener,
-                mWifiMetrics, mSarManager, mWakeupController, mClientModeImpl);
+    private ConcreteClientModeManager createClientModeManager(ActiveModeManager.ClientRole role) {
+        return new ConcreteClientModeManager(mContext, mLooper.getLooper(), mClock, mWifiNative,
+                mListener, mWifiMetrics, mWakeupController, mWifiInjector, mSelfRecovery,
+                mWifiGlobals, mDefaultClientModeManager, 0, TEST_WORKSOURCE, role,
+                mBroadcastQueue, false);
     }
 
     private void startClientInScanOnlyModeAndVerifyEnabled() throws Exception {
-        when(mWifiNative.setupInterfaceForClientInScanMode(any()))
+        when(mWifiNative.setupInterfaceForClientInScanMode(any(), any()))
                 .thenReturn(TEST_INTERFACE_NAME);
-        mClientModeManager.start();
+        mClientModeManager = createClientModeManager(ROLE_CLIENT_SCAN_ONLY);
         mLooper.dispatchAll();
 
         verify(mWifiNative).setupInterfaceForClientInScanMode(
-                mInterfaceCallbackCaptor.capture());
-        verify(mClientModeImpl).setOperationalMode(
-                ClientModeImpl.SCAN_ONLY_MODE, TEST_INTERFACE_NAME);
-        verify(mSarManager).setScanOnlyWifiState(WIFI_STATE_ENABLED);
+                mInterfaceCallbackCaptor.capture(), eq(TEST_WORKSOURCE));
+        verify(mWifiInjector, never()).makeClientModeImpl(any(), any(), anyBoolean());
 
         // now mark the interface as up
         mInterfaceCallbackCaptor.getValue().onUp(TEST_INTERFACE_NAME);
@@ -251,30 +274,24 @@
 
         // Ensure that no public broadcasts were sent.
         verifyNoMoreInteractions(mContext);
-        verify(mListener).onStarted();
+        verify(mListener).onStarted(mClientModeManager);
+        verify(mWifiNative).setScanMode(TEST_INTERFACE_NAME, true);
     }
 
     private void startClientInConnectModeAndVerifyEnabled() throws Exception {
-        when(mWifiNative.setupInterfaceForClientInScanMode(any()))
+        when(mWifiNative.setupInterfaceForClientInScanMode(any(), any()))
                 .thenReturn(TEST_INTERFACE_NAME);
-        when(mWifiNative.switchClientInterfaceToConnectivityMode(any()))
+        when(mWifiNative.switchClientInterfaceToConnectivityMode(any(), any()))
                 .thenReturn(true);
-        mClientModeManager.start();
+        mClientModeManager = createClientModeManager(ROLE_CLIENT_PRIMARY);
         mLooper.dispatchAll();
 
         verify(mWifiNative).setupInterfaceForClientInScanMode(
-                mInterfaceCallbackCaptor.capture());
-        verify(mClientModeImpl).setOperationalMode(
-                ClientModeImpl.SCAN_ONLY_MODE, TEST_INTERFACE_NAME);
-        mLooper.dispatchAll();
-
-        mClientModeManager.setRole(ActiveModeManager.ROLE_CLIENT_PRIMARY);
-        mLooper.dispatchAll();
-
-        verify(mWifiNative).switchClientInterfaceToConnectivityMode(TEST_INTERFACE_NAME);
-        verify(mClientModeImpl).setOperationalMode(
-                ClientModeImpl.CONNECT_MODE, TEST_INTERFACE_NAME);
-        verify(mSarManager).setClientWifiState(WIFI_STATE_ENABLED);
+                mInterfaceCallbackCaptor.capture(), eq(TEST_WORKSOURCE));
+        verify(mWifiNative).switchClientInterfaceToConnectivityMode(
+                TEST_INTERFACE_NAME, TEST_WORKSOURCE);
+        verify(mWifiInjector)
+                .makeClientModeImpl(eq(TEST_INTERFACE_NAME), eq(mClientModeManager), anyBoolean());
 
         // now mark the interface as up
         mInterfaceCallbackCaptor.getValue().onUp(TEST_INTERFACE_NAME);
@@ -291,8 +308,9 @@
                 WIFI_STATE_DISABLED);
         checkWifiConnectModeStateChangedBroadcast(intents.get(1), WIFI_STATE_ENABLED,
                 WIFI_STATE_ENABLING);
+        assertEquals(WIFI_STATE_ENABLED, mClientModeManager.syncGetWifiState());
 
-        verify(mListener, times(2)).onStarted();
+        verify(mListener).onStarted(mClientModeManager);
     }
 
     private void checkWifiConnectModeStateChangedBroadcast(
@@ -303,8 +321,6 @@
         assertEquals(expectedCurrentState, currentState);
         int prevState = intent.getIntExtra(EXTRA_PREVIOUS_WIFI_STATE, WIFI_STATE_UNKNOWN);
         assertEquals(expectedPrevState, prevState);
-
-        verify(mClientModeImpl, atLeastOnce()).setWifiStateForApiCalls(expectedCurrentState);
     }
 
     private void verifyConnectModeNotificationsForCleanShutdown(int fromState) {
@@ -318,6 +334,7 @@
                 WIFI_STATE_DISABLING, fromState);
         checkWifiConnectModeStateChangedBroadcast(intents.get(intents.size() - 1),
                 WIFI_STATE_DISABLED, WIFI_STATE_DISABLING);
+        assertEquals(WIFI_STATE_DISABLED, mClientModeManager.syncGetWifiState());
     }
 
     private void verifyConnectModeNotificationsForFailure() {
@@ -331,6 +348,7 @@
                 WIFI_STATE_UNKNOWN);
         checkWifiConnectModeStateChangedBroadcast(intents.get(1), WIFI_STATE_DISABLED,
                 WIFI_STATE_DISABLING);
+        assertEquals(WIFI_STATE_DISABLED, mClientModeManager.syncGetWifiState());
     }
 
     /**
@@ -347,6 +365,10 @@
     @Test
     public void clientInScanOnlyModeStartCreatesClientInterface() throws Exception {
         startClientInScanOnlyModeAndVerifyEnabled();
+
+        mClientModeManager.getFactoryMacAddress();
+        // in scan only mode, should get value from ScanOnlyModeImpl
+        verify(mScanOnlyModeImpl).getFactoryMacAddress();
     }
 
     /**
@@ -356,17 +378,15 @@
     public void switchFromScanOnlyModeToConnectMode() throws Exception {
         startClientInScanOnlyModeAndVerifyEnabled();
 
-        when(mWifiNative.switchClientInterfaceToConnectivityMode(any()))
+        when(mWifiNative.switchClientInterfaceToConnectivityMode(any(), any()))
                 .thenReturn(true);
-        mClientModeManager.setRole(ActiveModeManager.ROLE_CLIENT_PRIMARY);
+        mClientModeManager.setRole(ActiveModeManager.ROLE_CLIENT_PRIMARY, TEST_WORKSOURCE);
         mLooper.dispatchAll();
 
-        verify(mSarManager).setScanOnlyWifiState(WIFI_STATE_DISABLED);
-        verify(mClientModeImpl).setOperationalMode(
-                ClientModeImpl.SCAN_ONLY_MODE, TEST_INTERFACE_NAME);
-        verify(mClientModeImpl).setOperationalMode(
-                ClientModeImpl.CONNECT_MODE, TEST_INTERFACE_NAME);
-        verify(mSarManager).setClientWifiState(WIFI_STATE_ENABLED);
+        verify(mWifiNative).setScanMode(TEST_INTERFACE_NAME, false);
+
+        verify(mWifiInjector)
+                .makeClientModeImpl(eq(TEST_INTERFACE_NAME), eq(mClientModeManager), anyBoolean());
 
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mContext, atLeastOnce()).sendStickyBroadcastAsUser(intentCaptor.capture(),
@@ -379,8 +399,43 @@
                 WIFI_STATE_DISABLED);
         checkWifiConnectModeStateChangedBroadcast(intents.get(1), WIFI_STATE_ENABLED,
                 WIFI_STATE_ENABLING);
+        assertEquals(WIFI_STATE_ENABLED, mClientModeManager.syncGetWifiState());
 
-        verify(mListener, times(2)).onStarted();
+        verify(mListener).onStarted(mClientModeManager);
+        verify(mListener).onRoleChanged(mClientModeManager);
+
+        mClientModeManager.getFactoryMacAddress();
+        // in client mode, should get value from ClientModeImpl
+        verify(mClientModeImpl).getFactoryMacAddress();
+    }
+
+    /**
+     * Verify that no more public broadcasts are sent out after
+     * setWifiStateChangeBroadcastEnabled(false) is called.
+     */
+    @Test
+    public void testDisableWifiStateChangedBroadcasts() throws Exception {
+        startClientInConnectModeAndVerifyEnabled();
+
+        mClientModeManager.setWifiStateChangeBroadcastEnabled(false);
+        mClientModeManager.stop();
+        mLooper.dispatchAll();
+
+        verify(mClientModeImpl).stop();
+        assertEquals(WIFI_STATE_DISABLED, mClientModeManager.syncGetWifiState());
+
+        // Ensure that only public broadcasts for the "start" events were sent.
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext, atLeastOnce()).sendStickyBroadcastAsUser(intentCaptor.capture(),
+                eq(UserHandle.ALL));
+
+        List<Intent> intents = intentCaptor.getAllValues();
+        assertEquals(2, intents.size());
+        Log.d(TAG, "captured intents: " + intents);
+        checkWifiConnectModeStateChangedBroadcast(intents.get(0), WIFI_STATE_ENABLING,
+                WIFI_STATE_DISABLED);
+        checkWifiConnectModeStateChangedBroadcast(intents.get(1), WIFI_STATE_ENABLED,
+                WIFI_STATE_ENABLING);
     }
 
     /**
@@ -390,20 +445,16 @@
     public void switchFromConnectModeToScanOnlyMode() throws Exception {
         startClientInConnectModeAndVerifyEnabled();
 
-        when(mWifiNative.switchClientInterfaceToScanMode(any()))
+        when(mWifiNative.switchClientInterfaceToScanMode(any(), any()))
                 .thenReturn(true);
-        mClientModeManager.setRole(ActiveModeManager.ROLE_CLIENT_SCAN_ONLY);
+        mClientModeManager.setRole(ROLE_CLIENT_SCAN_ONLY, TEST_WORKSOURCE);
         mLooper.dispatchAll();
 
         verifyConnectModeNotificationsForCleanShutdown(WIFI_STATE_ENABLED);
-
-        verify(mSarManager).setClientWifiState(WIFI_STATE_DISABLED);
         verify(mWifiNative).setupInterfaceForClientInScanMode(
-                mInterfaceCallbackCaptor.capture());
-        verify(mWifiNative).switchClientInterfaceToScanMode(TEST_INTERFACE_NAME);
-        verify(mClientModeImpl, times(2)).setOperationalMode(
-                ClientModeImpl.SCAN_ONLY_MODE, TEST_INTERFACE_NAME);
-        verify(mSarManager, times(2)).setScanOnlyWifiState(WIFI_STATE_ENABLED);
+                mInterfaceCallbackCaptor.capture(), eq(TEST_WORKSOURCE));
+        verify(mWifiNative).switchClientInterfaceToScanMode(TEST_INTERFACE_NAME, TEST_WORKSOURCE);
+        verify(mClientModeImpl).stop();
 
         verify(mContext).getSystemService(anyString());
         verify(mImsMmTelManager, never()).registerImsRegistrationCallback(any(), any());
@@ -411,7 +462,8 @@
 
         // Ensure that no public broadcasts were sent.
         verifyNoMoreInteractions(mContext);
-        verify(mListener, times(3)).onStarted();
+        verify(mListener).onStarted(mClientModeManager);
+        verify(mListener).onRoleChanged(mClientModeManager);
     }
 
     /**
@@ -420,14 +472,11 @@
     @Test
     public void detectAndReportErrorWhenSetupForClientInConnectivityModeWifiNativeFailure()
             throws Exception {
-        when(mWifiNative.setupInterfaceForClientInScanMode(any()))
+        when(mWifiNative.setupInterfaceForClientInScanMode(any(), any()))
                 .thenReturn(TEST_INTERFACE_NAME);
-        when(mWifiNative.switchClientInterfaceToConnectivityMode(any())).thenReturn(false);
+        when(mWifiNative.switchClientInterfaceToConnectivityMode(any(), any())).thenReturn(false);
 
-        mClientModeManager.start();
-        mLooper.dispatchAll();
-
-        mClientModeManager.setRole(ActiveModeManager.ROLE_CLIENT_PRIMARY);
+        mClientModeManager = createClientModeManager(ROLE_CLIENT_PRIMARY);
         mLooper.dispatchAll();
 
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -439,23 +488,50 @@
                 WIFI_STATE_DISABLED);
         checkWifiConnectModeStateChangedBroadcast(intents.get(1), WIFI_STATE_DISABLED,
                 WIFI_STATE_UNKNOWN);
-        verify(mListener).onStartFailure();
+        assertEquals(WIFI_STATE_DISABLED, mClientModeManager.syncGetWifiState());
+        verify(mListener).onStartFailure(mClientModeManager);
+    }
+
+    /** Tests failure when setting up iface for scan only mode. */
+    @Test
+    public void detectAndReportErrorWhenSetupInterfaceForClientInScanModeWifiNativeFailure()
+            throws Exception {
+        // failed to setup iface in Scan Only mode
+        when(mWifiNative.setupInterfaceForClientInScanMode(any(), any())).thenReturn(null);
+
+        mClientModeManager = createClientModeManager(ROLE_CLIENT_PRIMARY);
+        mLooper.dispatchAll();
+
+        assertEquals(WIFI_STATE_DISABLED, mClientModeManager.syncGetWifiState());
+        verify(mListener).onStartFailure(mClientModeManager);
+
+        mClientModeManager.getFactoryMacAddress();
+        // wifi is off, should get value from DefaultClientModeManager
+        verify(mDefaultClientModeManager).getFactoryMacAddress();
     }
 
     /**
-     * Calling ClientModeManager.start twice does not crash or restart client mode.
+     * ClientMode stop before start has been processed properly cleans up state & invokes the
+     * onStopped callback.
      */
     @Test
-    public void clientModeStartCalledTwice() throws Exception {
-        startClientInConnectModeAndVerifyEnabled();
-        reset(mWifiNative, mContext);
-        mClientModeManager.start();
+    public void clientModeStopBeforeStartCleansUpState() throws Exception {
+        mClientModeManager = createClientModeManager(ROLE_CLIENT_PRIMARY);
+        // Invoke stop before the internal start is processed by the state machine.
+        mClientModeManager.stop();
         mLooper.dispatchAll();
-        verifyNoMoreInteractions(mWifiNative, mContext);
+        when(mClientModeImpl.hasQuit()).thenReturn(true);
+        mClientModeManager.onClientModeImplQuit();
+        verify(mListener).onStopped(mClientModeManager);
+
+        // Don't initiate wifi native setup.
+        verifyNoMoreInteractions(mListener, mWifiNative);
+        assertNull(mClientModeManager.getRole());
+        assertNull(mClientModeManager.getPreviousRole());
     }
 
     /**
-     * ClientMode stop properly cleans up state
+     * ClientMode stop properly cleans up state & invokes the onStopped callback.
      */
     @Test
     public void clientModeStopCleansUpState() throws Exception {
@@ -463,10 +539,24 @@
         reset(mContext, mListener);
         setUpSystemServiceForContext();
         mClientModeManager.stop();
-        assertTrue(mClientModeManager.isStopping());
         mLooper.dispatchAll();
-        verify(mListener).onStopped();
-        assertFalse(mClientModeManager.isStopping());
+        // role has not been reset yet
+        ActiveModeManager.ClientRole lastRole = mClientModeManager.getRole();
+        assertNotNull(lastRole);
+        assertNull(mClientModeManager.getPreviousRole());
+
+        long testChangeRoleTimestamp = 12234455L;
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(testChangeRoleTimestamp);
+        when(mClientModeImpl.hasQuit()).thenReturn(true);
+        mClientModeManager.onClientModeImplQuit();
+        verify(mListener).onStopped(mClientModeManager);
+
+        // then role will be reset
+        assertNull(mClientModeManager.getRole());
+        assertEquals("Should equal previous role", lastRole,
+                mClientModeManager.getPreviousRole());
+        assertEquals("The role change timestamp should match", testChangeRoleTimestamp,
+                mClientModeManager.getLastRoleChangeSinceBootMs());
 
         verify(mImsMmTelManager, never()).registerImsRegistrationCallback(any(), any());
         verify(mImsMmTelManager, never()).unregisterImsRegistrationCallback(any());
@@ -478,27 +568,6 @@
     }
 
     /**
-     * Calling stop when ClientMode is not started should not send scan state updates
-     */
-    @Test
-    public void clientModeStopWhenNotStartedDoesNotUpdateScanStateUpdates() throws Exception {
-        startClientInConnectModeAndVerifyEnabled();
-        reset(mContext);
-        setUpSystemServiceForContext();
-        mClientModeManager.stop();
-        mLooper.dispatchAll();
-        verifyConnectModeNotificationsForCleanShutdown(WIFI_STATE_ENABLED);
-
-        reset(mContext, mListener);
-        setUpSystemServiceForContext();
-        // now call stop again
-        mClientModeManager.stop();
-        mLooper.dispatchAll();
-        verify(mContext, never()).sendStickyBroadcastAsUser(any(), any());
-        verifyNoMoreInteractions(mListener);
-    }
-
-    /**
      * Triggering interface down when ClientMode is active properly exits the active state.
      */
     @Test
@@ -506,12 +575,14 @@
         startClientInConnectModeAndVerifyEnabled();
         reset(mContext);
         setUpSystemServiceForContext();
-        when(mClientModeImpl.isConnectedMacRandomizationEnabled()).thenReturn(false);
+        when(mWifiGlobals.isConnectedMacRandomizationEnabled()).thenReturn(false);
         mInterfaceCallbackCaptor.getValue().onDown(TEST_INTERFACE_NAME);
         mLooper.dispatchAll();
-        verify(mClientModeImpl).failureDetected(eq(SelfRecovery.REASON_STA_IFACE_DOWN));
+        verify(mSelfRecovery).trigger(SelfRecovery.REASON_STA_IFACE_DOWN);
         verifyConnectModeNotificationsForFailure();
-        verify(mListener).onStopped();
+        when(mClientModeImpl.hasQuit()).thenReturn(true);
+        mClientModeManager.onClientModeImplQuit();
+        verify(mListener).onStopped(mClientModeManager);
     }
 
     /**
@@ -524,10 +595,11 @@
         startClientInConnectModeAndVerifyEnabled();
         reset(mContext);
         setUpSystemServiceForContext();
-        when(mClientModeImpl.isConnectedMacRandomizationEnabled()).thenReturn(true);
+        when(mWifiGlobals.isConnectedMacRandomizationEnabled()).thenReturn(true);
+        when(mClientModeImpl.isConnecting()).thenReturn(true);
         mInterfaceCallbackCaptor.getValue().onDown(TEST_INTERFACE_NAME);
         mLooper.dispatchAll();
-        verify(mClientModeImpl, never()).failureDetected(eq(SelfRecovery.REASON_STA_IFACE_DOWN));
+        verify(mSelfRecovery, never()).trigger(SelfRecovery.REASON_STA_IFACE_DOWN);
         verify(mContext, never()).sendStickyBroadcastAsUser(any(), any());
     }
 
@@ -543,7 +615,9 @@
         mLooper.dispatchAll();
         verifyConnectModeNotificationsForCleanShutdown(WIFI_STATE_ENABLED);
         verify(mClientModeImpl).handleIfaceDestroyed();
-        verify(mListener).onStopped();
+        when(mClientModeImpl.hasQuit()).thenReturn(true);
+        mClientModeManager.onClientModeImplQuit();
+        verify(mListener).onStopped(mClientModeManager);
     }
 
     /**
@@ -561,7 +635,9 @@
         // now trigger interface destroyed and make sure callback doesn't get called
         mInterfaceCallbackCaptor.getValue().onDestroyed(TEST_INTERFACE_NAME);
         mLooper.dispatchAll();
-        verify(mListener).onStopped();
+        when(mClientModeImpl.hasQuit()).thenReturn(true);
+        mClientModeManager.onClientModeImplQuit();
+        verify(mListener).onStopped(mClientModeManager);
 
         verifyNoMoreInteractions(mListener);
         verify(mClientModeImpl, never()).handleIfaceDestroyed();
@@ -587,9 +663,11 @@
         mClientModeManager.stop();
         mLooper.dispatchAll();
 
+        assertNull(mClientModeManager.getRole());
+
         InOrder inOrder = inOrder(mWakeupController, mWifiNative, mListener);
 
-        inOrder.verify(mListener).onStarted();
+        inOrder.verify(mListener).onStarted(mClientModeManager);
         inOrder.verify(mWakeupController).start();
         inOrder.verify(mWakeupController).stop();
         inOrder.verify(mWifiNative).teardownInterface(eq(TEST_INTERFACE_NAME));
@@ -623,7 +701,9 @@
         setUpSystemServiceForContext();
         mClientModeManager.stop();
         mLooper.dispatchAll();
-        verify(mListener).onStopped();
+        when(mClientModeImpl.hasQuit()).thenReturn(true);
+        mClientModeManager.onClientModeImplQuit();
+        verify(mListener).onStopped(mClientModeManager);
 
         verifyConnectModeNotificationsForCleanShutdown(WIFI_STATE_ENABLED);
 
@@ -649,7 +729,9 @@
         setUpSystemServiceForContext();
         mClientModeManager.stop();
         mLooper.dispatchAll();
-        verify(mListener).onStopped();
+        when(mClientModeImpl.hasQuit()).thenReturn(true);
+        mClientModeManager.onClientModeImplQuit();
+        verify(mListener).onStopped(mClientModeManager);
 
         verify(mImsMmTelManager).registerImsRegistrationCallback(
                 any(Executor.class),
@@ -684,7 +766,7 @@
                 any(Executor.class),
                 any(RegistrationManager.RegistrationCallback.class));
         verify(mImsMmTelManager, never()).unregisterImsRegistrationCallback(any());
-        verify(mListener, never()).onStopped();
+        verify(mListener, never()).onStopped(any());
         verify(mWifiMetrics, never()).noteWifiOff(anyBoolean(), anyBoolean(), anyInt());
 
         // Notify wifi service IMS service is de-registered.
@@ -699,7 +781,70 @@
                 any(RegistrationManager.RegistrationCallback.class));
         assertNull(mImsMmTelManagerRegistrationCallback);
         assertNull(mImsNetworkCallback);
-        verify(mListener).onStopped();
+        when(mClientModeImpl.hasQuit()).thenReturn(true);
+        mClientModeManager.onClientModeImplQuit();
+        verify(mListener).onStopped(mClientModeManager);
+        verify(mWifiMetrics).noteWifiOff(eq(true), eq(false), anyInt());
+
+        verifyConnectModeNotificationsForCleanShutdown(WIFI_STATE_ENABLED);
+
+        // on an explicit stop, we should not trigger the callback
+        verifyNoMoreInteractions(mListener);
+    }
+
+    /**
+     * ClientMode stop properly with IMS deferring time, Wifi calling.
+     *
+     * The network losts first and then IMS is de-registered.
+     * The WIFI should be off after IMS deregistration.
+     */
+    @Test
+    public void clientModeStopWithWifiOffDeferringTimeWithWifiCallingAndNetworkLostFirst()
+            throws Exception {
+        setUpVoWifiTest(true,
+                TEST_WIFI_OFF_DEFERRING_TIME_MS);
+
+        startClientInConnectModeAndVerifyEnabled();
+        reset(mContext, mListener);
+        setUpSystemServiceForContext();
+        mClientModeManager.stop();
+        mLooper.dispatchAll();
+
+        // Not yet finish IMS deregistration.
+        verify(mImsMmTelManager).registerImsRegistrationCallback(
+                any(Executor.class),
+                any(RegistrationManager.RegistrationCallback.class));
+        verify(mImsMmTelManager, never()).unregisterImsRegistrationCallback(any());
+        when(mClientModeImpl.hasQuit()).thenReturn(true);
+        mClientModeManager.onClientModeImplQuit();
+        verify(mListener, never()).onStopped(any());
+        verify(mWifiMetrics, never()).noteWifiOff(anyBoolean(), anyBoolean(), anyInt());
+
+        // Notify wifi service the network lost.
+        assertNotNull(mImsNetworkCallback);
+        mImsNetworkCallback.onLost(null);
+        mLooper.dispatchAll();
+
+        // Since IMS service is not de-registered yet, wifi should be available.
+        moveTimeForward(1000);
+        mLooper.dispatchAll();
+        verify(mWifiNative, never()).switchClientInterfaceToScanMode(any(), any());
+        verify(mImsMmTelManager, never()).unregisterImsRegistrationCallback(any());
+        verify(mWifiMetrics, never()).noteWifiOff(anyBoolean(), anyBoolean(), anyInt());
+
+        // Notify wifi service IMS service is de-registered.
+        assertNotNull(mImsMmTelManagerRegistrationCallback);
+        mImsMmTelManagerRegistrationCallback.onUnregistered(null);
+        mLooper.dispatchAll();
+
+        // Now Wifi could be turned off actually.
+        verify(mImsMmTelManager).unregisterImsRegistrationCallback(
+                any(RegistrationManager.RegistrationCallback.class));
+        assertNull(mImsMmTelManagerRegistrationCallback);
+        assertNull(mImsNetworkCallback);
+        when(mClientModeImpl.hasQuit()).thenReturn(true);
+        mClientModeManager.onClientModeImplQuit();
+        verify(mListener).onStopped(mClientModeManager);
         verify(mWifiMetrics).noteWifiOff(eq(true), eq(false), anyInt());
 
         verifyConnectModeNotificationsForCleanShutdown(WIFI_STATE_ENABLED);
@@ -730,7 +875,9 @@
                 any(Executor.class),
                 any(RegistrationManager.RegistrationCallback.class));
         verify(mImsMmTelManager, never()).unregisterImsRegistrationCallback(any());
-        verify(mListener, never()).onStopped();
+        when(mClientModeImpl.hasQuit()).thenReturn(true);
+        mClientModeManager.onClientModeImplQuit();
+        verify(mListener, never()).onStopped(any());
         verify(mWifiMetrics, never()).noteWifiOff(anyBoolean(), anyBoolean(), anyInt());
 
         // Notify wifi service IMS service is de-registered.
@@ -742,7 +889,9 @@
         verify(mImsMmTelManager).unregisterImsRegistrationCallback(
                 any(RegistrationManager.RegistrationCallback.class));
         assertNull(mImsMmTelManagerRegistrationCallback);
-        verify(mListener).onStopped();
+        when(mClientModeImpl.hasQuit()).thenReturn(true);
+        mClientModeManager.onClientModeImplQuit();
+        verify(mListener).onStopped(mClientModeManager);
         verify(mWifiMetrics).noteWifiOff(eq(true), eq(false), anyInt());
 
         verifyConnectModeNotificationsForCleanShutdown(WIFI_STATE_ENABLED);
@@ -771,13 +920,15 @@
                 any(Executor.class),
                 any(RegistrationManager.RegistrationCallback.class));
         verify(mImsMmTelManager, never()).unregisterImsRegistrationCallback(any());
-        verify(mListener, never()).onStopped();
+        when(mClientModeImpl.hasQuit()).thenReturn(true);
+        mClientModeManager.onClientModeImplQuit();
+        verify(mListener, never()).onStopped(any());
 
         // 1/2 deferring time passed, should be still waiting for the callback.
         moveTimeForward(TEST_WIFI_OFF_DEFERRING_TIME_MS / 2);
         mLooper.dispatchAll();
         verify(mImsMmTelManager, never()).unregisterImsRegistrationCallback(any());
-        verify(mListener, never()).onStopped();
+        verify(mListener, never()).onStopped(any());
         verify(mWifiMetrics, never()).noteWifiOff(anyBoolean(), anyBoolean(), anyInt());
 
         // Exceeding the timeout, wifi should be stopped.
@@ -786,7 +937,9 @@
         verify(mImsMmTelManager).unregisterImsRegistrationCallback(
                 any(RegistrationManager.RegistrationCallback.class));
         assertNull(mImsMmTelManagerRegistrationCallback);
-        verify(mListener).onStopped();
+        when(mClientModeImpl.hasQuit()).thenReturn(true);
+        mClientModeManager.onClientModeImplQuit();
+        verify(mListener).onStopped(mClientModeManager);
         verify(mWifiMetrics).noteWifiOff(eq(true), eq(true), anyInt());
 
         verifyConnectModeNotificationsForCleanShutdown(WIFI_STATE_ENABLED);
@@ -815,7 +968,7 @@
                 any(Executor.class),
                 any(RegistrationManager.RegistrationCallback.class));
         verify(mImsMmTelManager, never()).unregisterImsRegistrationCallback(any());
-        verify(mListener, never()).onStopped();
+        verify(mListener, never()).onStopped(any());
 
         mClientModeManager.stop();
         mLooper.dispatchAll();
@@ -824,7 +977,7 @@
                 any(Executor.class),
                 any(RegistrationManager.RegistrationCallback.class));
         verify(mImsMmTelManager, never()).unregisterImsRegistrationCallback(any());
-        verify(mListener, never()).onStopped();
+        verify(mListener, never()).onStopped(any());
         verify(mWifiMetrics, never()).noteWifiOff(anyBoolean(), anyBoolean(), anyInt());
 
         // Exceeding the timeout, wifi should be stopped.
@@ -833,7 +986,9 @@
         verify(mImsMmTelManager).unregisterImsRegistrationCallback(
                 any(RegistrationManager.RegistrationCallback.class));
         assertNull(mImsMmTelManagerRegistrationCallback);
-        verify(mListener).onStopped();
+        when(mClientModeImpl.hasQuit()).thenReturn(true);
+        mClientModeManager.onClientModeImplQuit();
+        verify(mListener).onStopped(mClientModeManager);
         verify(mWifiMetrics).noteWifiOff(eq(true), eq(true), anyInt());
 
         verifyConnectModeNotificationsForCleanShutdown(WIFI_STATE_ENABLED);
@@ -843,59 +998,6 @@
     }
 
     /**
-     * ClientMode does not stop with IMS deferring time and Wifi calling
-     * when the target role is not ROLE_UNSPECIFIED.
-     *
-     * Simulate a user toggle wifi multiple times before doing wifi stop and stay at
-     * ON position.
-     */
-    @Test
-    public void clientModeNotStopWithWifiOffDeferringTimeAndWifiCallingTimedOut()
-            throws Exception {
-        setUpVoWifiTest(true,
-                TEST_WIFI_OFF_DEFERRING_TIME_MS);
-
-        startClientInConnectModeAndVerifyEnabled();
-        reset(mContext, mListener);
-        setUpSystemServiceForContext();
-        mClientModeManager.stop();
-        mLooper.dispatchAll();
-        verify(mImsMmTelManager).registerImsRegistrationCallback(
-                any(Executor.class),
-                any(RegistrationManager.RegistrationCallback.class));
-        verify(mImsMmTelManager, never()).unregisterImsRegistrationCallback(any());
-        verify(mListener, never()).onStopped();
-
-        mClientModeManager.start();
-        mClientModeManager.setRole(ActiveModeManager.ROLE_CLIENT_PRIMARY);
-        mLooper.dispatchAll();
-        mClientModeManager.stop();
-        mLooper.dispatchAll();
-        mClientModeManager.start();
-        mClientModeManager.setRole(ActiveModeManager.ROLE_CLIENT_PRIMARY);
-        mLooper.dispatchAll();
-        // should not register another listener.
-        verify(mImsMmTelManager, times(1)).registerImsRegistrationCallback(
-                any(Executor.class),
-                any(RegistrationManager.RegistrationCallback.class));
-        verify(mImsMmTelManager, never()).unregisterImsRegistrationCallback(any());
-        verify(mListener, never()).onStopped();
-        verify(mWifiMetrics, never()).noteWifiOff(anyBoolean(), anyBoolean(), anyInt());
-
-        // Exceeding the timeout, wifi should NOT be stopped.
-        moveTimeForward(TEST_WIFI_OFF_DEFERRING_TIME_MS + 1000);
-        mLooper.dispatchAll();
-        verify(mImsMmTelManager).unregisterImsRegistrationCallback(
-                any(RegistrationManager.RegistrationCallback.class));
-        assertNull(mImsMmTelManagerRegistrationCallback);
-        verify(mListener, never()).onStopped();
-        verify(mWifiMetrics, never()).noteWifiOff(anyBoolean(), anyBoolean(), anyInt());
-
-        // on an explicit stop, we should not trigger the callback
-        verifyNoMoreInteractions(mListener);
-    }
-
-    /**
      * Switch to scan mode properly with IMS deferring time without WifiCalling.
      */
     @Test
@@ -906,13 +1008,13 @@
         startClientInConnectModeAndVerifyEnabled();
         reset(mContext, mListener);
         setUpSystemServiceForContext();
-        when(mWifiNative.switchClientInterfaceToScanMode(any()))
+        when(mWifiNative.switchClientInterfaceToScanMode(any(), any()))
                 .thenReturn(true);
 
-        mClientModeManager.setRole(ActiveModeManager.ROLE_CLIENT_SCAN_ONLY);
+        mClientModeManager.setRole(ROLE_CLIENT_SCAN_ONLY, TEST_WORKSOURCE);
         mLooper.dispatchAll();
 
-        verify(mWifiNative).switchClientInterfaceToScanMode(TEST_INTERFACE_NAME);
+        verify(mWifiNative).switchClientInterfaceToScanMode(TEST_INTERFACE_NAME, TEST_WORKSOURCE);
         verify(mImsMmTelManager, never()).registerImsRegistrationCallback(any(), any());
         verify(mImsMmTelManager, never()).unregisterImsRegistrationCallback(any());
         verify(mWifiMetrics).noteWifiOff(eq(false), eq(false), anyInt());
@@ -930,13 +1032,13 @@
         startClientInConnectModeAndVerifyEnabled();
         reset(mContext, mListener);
         setUpSystemServiceForContext();
-        when(mWifiNative.switchClientInterfaceToScanMode(any()))
+        when(mWifiNative.switchClientInterfaceToScanMode(any(), any()))
                 .thenReturn(true);
 
-        mClientModeManager.setRole(ActiveModeManager.ROLE_CLIENT_SCAN_ONLY);
+        mClientModeManager.setRole(ROLE_CLIENT_SCAN_ONLY, TEST_WORKSOURCE);
         mLooper.dispatchAll();
 
-        verify(mWifiNative).switchClientInterfaceToScanMode(TEST_INTERFACE_NAME);
+        verify(mWifiNative).switchClientInterfaceToScanMode(TEST_INTERFACE_NAME, TEST_WORKSOURCE);
 
         verify(mImsMmTelManager).registerImsRegistrationCallback(
                 any(Executor.class),
@@ -961,14 +1063,14 @@
         startClientInConnectModeAndVerifyEnabled();
         reset(mContext, mListener);
         setUpSystemServiceForContext();
-        when(mWifiNative.switchClientInterfaceToScanMode(any()))
+        when(mWifiNative.switchClientInterfaceToScanMode(any(), any()))
                 .thenReturn(true);
 
-        mClientModeManager.setRole(ActiveModeManager.ROLE_CLIENT_SCAN_ONLY);
+        mClientModeManager.setRole(ROLE_CLIENT_SCAN_ONLY, TEST_WORKSOURCE);
         mLooper.dispatchAll();
 
         // Not yet finish IMS deregistration.
-        verify(mWifiNative, never()).switchClientInterfaceToScanMode(any());
+        verify(mWifiNative, never()).switchClientInterfaceToScanMode(any(), any());
         verify(mImsMmTelManager).registerImsRegistrationCallback(
                 any(Executor.class),
                 any(RegistrationManager.RegistrationCallback.class));
@@ -983,7 +1085,62 @@
         mLooper.dispatchAll();
 
         // Now Wifi could be switched to scan mode actually.
-        verify(mWifiNative).switchClientInterfaceToScanMode(TEST_INTERFACE_NAME);
+        verify(mWifiNative).switchClientInterfaceToScanMode(TEST_INTERFACE_NAME, TEST_WORKSOURCE);
+        verify(mImsMmTelManager).unregisterImsRegistrationCallback(
+                any(RegistrationManager.RegistrationCallback.class));
+        assertNull(mImsMmTelManagerRegistrationCallback);
+        assertNull(mImsNetworkCallback);
+        verify(mWifiMetrics).noteWifiOff(eq(true), eq(false), anyInt());
+    }
+
+    /**
+     * Switch to scan mode properly with IMS deferring time and Wifi calling.
+     *
+     * Network lost before IMS deregistration is done. The wifi should be still available
+     * until IMS is de-registered or time out.
+     */
+    @Test
+    public void switchToScanOnlyModeWithWifiOffDeferringTimeAndWifiCallingOnNetworkLostFirst()
+            throws Exception {
+        setUpVoWifiTest(true,
+                TEST_WIFI_OFF_DEFERRING_TIME_MS);
+
+        startClientInConnectModeAndVerifyEnabled();
+        reset(mContext, mListener);
+        setUpSystemServiceForContext();
+        when(mWifiNative.switchClientInterfaceToScanMode(any(), any()))
+                .thenReturn(true);
+
+        mClientModeManager.setRole(ROLE_CLIENT_SCAN_ONLY, TEST_WORKSOURCE);
+        mLooper.dispatchAll();
+
+        // Not yet finish IMS deregistration.
+        verify(mWifiNative, never()).switchClientInterfaceToScanMode(any(), any());
+        verify(mImsMmTelManager).registerImsRegistrationCallback(
+                any(Executor.class),
+                any(RegistrationManager.RegistrationCallback.class));
+        verify(mImsMmTelManager, never()).unregisterImsRegistrationCallback(any());
+        verify(mWifiMetrics, never()).noteWifiOff(anyBoolean(), anyBoolean(), anyInt());
+
+        // Notify wifi service network lost
+        assertNotNull(mImsNetworkCallback);
+        mImsNetworkCallback.onLost(null);
+        mLooper.dispatchAll();
+
+        // Since IMS service is not de-registered yet, wifi should be available.
+        moveTimeForward(1000);
+        mLooper.dispatchAll();
+        verify(mWifiNative, never()).switchClientInterfaceToScanMode(any(), any());
+        verify(mImsMmTelManager, never()).unregisterImsRegistrationCallback(any());
+        verify(mWifiMetrics, never()).noteWifiOff(anyBoolean(), anyBoolean(), anyInt());
+
+        // Notify wifi service IMS service is de-registered.
+        assertNotNull(mImsMmTelManagerRegistrationCallback);
+        mImsMmTelManagerRegistrationCallback.onUnregistered(null);
+        mLooper.dispatchAll();
+
+        // Now Wifi could be switched to scan mode actually.
+        verify(mWifiNative).switchClientInterfaceToScanMode(TEST_INTERFACE_NAME, TEST_WORKSOURCE);
         verify(mImsMmTelManager).unregisterImsRegistrationCallback(
                 any(RegistrationManager.RegistrationCallback.class));
         assertNull(mImsMmTelManagerRegistrationCallback);
@@ -1005,14 +1162,14 @@
         startClientInConnectModeAndVerifyEnabled();
         reset(mContext, mListener);
         setUpSystemServiceForContext();
-        when(mWifiNative.switchClientInterfaceToScanMode(any()))
+        when(mWifiNative.switchClientInterfaceToScanMode(any(), any()))
                 .thenReturn(true);
 
-        mClientModeManager.setRole(ActiveModeManager.ROLE_CLIENT_SCAN_ONLY);
+        mClientModeManager.setRole(ROLE_CLIENT_SCAN_ONLY, TEST_WORKSOURCE);
         mLooper.dispatchAll();
 
         // Not yet finish IMS deregistration.
-        verify(mWifiNative, never()).switchClientInterfaceToScanMode(any());
+        verify(mWifiNative, never()).switchClientInterfaceToScanMode(any(), any());
         verify(mImsMmTelManager).registerImsRegistrationCallback(
                 any(Executor.class),
                 any(RegistrationManager.RegistrationCallback.class));
@@ -1025,7 +1182,7 @@
         mLooper.dispatchAll();
 
         // Now Wifi could be switched to scan mode actually.
-        verify(mWifiNative).switchClientInterfaceToScanMode(TEST_INTERFACE_NAME);
+        verify(mWifiNative).switchClientInterfaceToScanMode(TEST_INTERFACE_NAME, TEST_WORKSOURCE);
         verify(mImsMmTelManager).unregisterImsRegistrationCallback(
                 any(RegistrationManager.RegistrationCallback.class));
         assertNull(mImsMmTelManagerRegistrationCallback);
@@ -1046,13 +1203,13 @@
         startClientInConnectModeAndVerifyEnabled();
         reset(mContext, mListener);
         setUpSystemServiceForContext();
-        when(mWifiNative.switchClientInterfaceToScanMode(any()))
+        when(mWifiNative.switchClientInterfaceToScanMode(any(), any()))
                 .thenReturn(true);
 
-        mClientModeManager.setRole(ActiveModeManager.ROLE_CLIENT_SCAN_ONLY);
+        mClientModeManager.setRole(ROLE_CLIENT_SCAN_ONLY, TEST_WORKSOURCE);
         mLooper.dispatchAll();
 
-        verify(mWifiNative, never()).switchClientInterfaceToScanMode(any());
+        verify(mWifiNative, never()).switchClientInterfaceToScanMode(any(), any());
         verify(mImsMmTelManager).registerImsRegistrationCallback(
                 any(Executor.class),
                 any(RegistrationManager.RegistrationCallback.class));
@@ -1061,14 +1218,14 @@
         // 1/2 deferring time passed, should be still waiting for the callback.
         moveTimeForward(TEST_WIFI_OFF_DEFERRING_TIME_MS / 2);
         mLooper.dispatchAll();
-        verify(mWifiNative, never()).switchClientInterfaceToScanMode(any());
+        verify(mWifiNative, never()).switchClientInterfaceToScanMode(any(), any());
         verify(mImsMmTelManager, never()).unregisterImsRegistrationCallback(any());
         verify(mWifiMetrics, never()).noteWifiOff(anyBoolean(), anyBoolean(), anyInt());
 
         // Exceeding the timeout, wifi should be stopped.
         moveTimeForward(TEST_WIFI_OFF_DEFERRING_TIME_MS / 2 + 1000);
         mLooper.dispatchAll();
-        verify(mWifiNative).switchClientInterfaceToScanMode(TEST_INTERFACE_NAME);
+        verify(mWifiNative).switchClientInterfaceToScanMode(TEST_INTERFACE_NAME, TEST_WORKSOURCE);
         verify(mImsMmTelManager).unregisterImsRegistrationCallback(
                 any(RegistrationManager.RegistrationCallback.class));
         assertNull(mImsMmTelManagerRegistrationCallback);
@@ -1090,10 +1247,10 @@
         startClientInConnectModeAndVerifyEnabled();
         reset(mContext, mListener);
         setUpSystemServiceForContext();
-        when(mWifiNative.switchClientInterfaceToScanMode(any()))
+        when(mWifiNative.switchClientInterfaceToScanMode(any(), any()))
                 .thenReturn(true);
 
-        mClientModeManager.setRole(ActiveModeManager.ROLE_CLIENT_SCAN_ONLY);
+        mClientModeManager.setRole(ROLE_CLIENT_SCAN_ONLY, TEST_WORKSOURCE);
         mLooper.dispatchAll();
 
         verify(mImsMmTelManager).registerImsRegistrationCallback(
@@ -1101,10 +1258,10 @@
                 any(RegistrationManager.RegistrationCallback.class));
         verify(mImsMmTelManager, never()).unregisterImsRegistrationCallback(any());
 
-        mClientModeManager.setRole(ActiveModeManager.ROLE_CLIENT_SCAN_ONLY);
+        mClientModeManager.setRole(ROLE_CLIENT_SCAN_ONLY, TEST_WORKSOURCE);
         mLooper.dispatchAll();
         // should not register another listener.
-        verify(mWifiNative, never()).switchClientInterfaceToScanMode(any());
+        verify(mWifiNative, never()).switchClientInterfaceToScanMode(any(), any());
         verify(mImsMmTelManager, times(1)).registerImsRegistrationCallback(
                 any(Executor.class),
                 any(RegistrationManager.RegistrationCallback.class));
@@ -1114,7 +1271,7 @@
         // Exceeding the timeout, wifi should be stopped.
         moveTimeForward(TEST_WIFI_OFF_DEFERRING_TIME_MS + 1000);
         mLooper.dispatchAll();
-        verify(mWifiNative).switchClientInterfaceToScanMode(TEST_INTERFACE_NAME);
+        verify(mWifiNative).switchClientInterfaceToScanMode(TEST_INTERFACE_NAME, TEST_WORKSOURCE);
         verify(mImsMmTelManager).unregisterImsRegistrationCallback(
                 any(RegistrationManager.RegistrationCallback.class));
         assertNull(mImsMmTelManagerRegistrationCallback);
@@ -1138,16 +1295,16 @@
         startClientInConnectModeAndVerifyEnabled();
         reset(mContext, mListener);
         setUpSystemServiceForContext();
-        when(mWifiNative.switchClientInterfaceToScanMode(any()))
+        when(mWifiNative.switchClientInterfaceToScanMode(any(), any()))
                 .thenReturn(true);
 
-        mClientModeManager.setRole(ActiveModeManager.ROLE_CLIENT_SCAN_ONLY);
+        mClientModeManager.setRole(ROLE_CLIENT_SCAN_ONLY, TEST_WORKSOURCE);
         mLooper.dispatchAll();
-        mClientModeManager.setRole(ActiveModeManager.ROLE_CLIENT_PRIMARY);
+        mClientModeManager.setRole(ActiveModeManager.ROLE_CLIENT_PRIMARY, TEST_WORKSOURCE);
         mLooper.dispatchAll();
-        mClientModeManager.setRole(ActiveModeManager.ROLE_CLIENT_SCAN_ONLY);
+        mClientModeManager.setRole(ROLE_CLIENT_SCAN_ONLY, TEST_WORKSOURCE);
         mLooper.dispatchAll();
-        mClientModeManager.setRole(ActiveModeManager.ROLE_CLIENT_PRIMARY);
+        mClientModeManager.setRole(ActiveModeManager.ROLE_CLIENT_PRIMARY, TEST_WORKSOURCE);
         mLooper.dispatchAll();
 
         verify(mImsMmTelManager).registerImsRegistrationCallback(
@@ -1156,7 +1313,7 @@
         verify(mImsMmTelManager, never()).unregisterImsRegistrationCallback(any());
 
         // should not register another listener.
-        verify(mWifiNative, never()).switchClientInterfaceToScanMode(any());
+        verify(mWifiNative, never()).switchClientInterfaceToScanMode(any(), any());
         verify(mImsMmTelManager, times(1)).registerImsRegistrationCallback(
                 any(Executor.class),
                 any(RegistrationManager.RegistrationCallback.class));
@@ -1166,7 +1323,8 @@
         // Exceeding the timeout, wifi should NOT be stopped.
         moveTimeForward(TEST_WIFI_OFF_DEFERRING_TIME_MS + 1000);
         mLooper.dispatchAll();
-        verify(mWifiNative, never()).switchClientInterfaceToScanMode(TEST_INTERFACE_NAME);
+        verify(mWifiNative, never()).switchClientInterfaceToScanMode(
+                TEST_INTERFACE_NAME, TEST_WORKSOURCE);
         verify(mImsMmTelManager).unregisterImsRegistrationCallback(
                 any(RegistrationManager.RegistrationCallback.class));
         assertNull(mImsMmTelManagerRegistrationCallback);
@@ -1174,6 +1332,35 @@
     }
 
     /**
+     * ClientMode stop properly with IMS deferring time without WifiCalling.
+     */
+    @Test
+    public void clientModeStopWithImsManagerException() throws Exception {
+        setUpVoWifiTest(true,
+                TEST_WIFI_OFF_DEFERRING_TIME_MS);
+        when(mImsMmTelManager.isAvailable(anyInt(), anyInt()))
+                .thenThrow(new RuntimeException("Test Runtime Exception"));
+
+        startClientInConnectModeAndVerifyEnabled();
+        reset(mContext, mListener);
+        setUpSystemServiceForContext();
+        mClientModeManager.stop();
+        mLooper.dispatchAll();
+        when(mClientModeImpl.hasQuit()).thenReturn(true);
+        mClientModeManager.onClientModeImplQuit();
+        verify(mListener).onStopped(mClientModeManager);
+
+        verifyConnectModeNotificationsForCleanShutdown(WIFI_STATE_ENABLED);
+
+        verify(mImsMmTelManager, never()).registerImsRegistrationCallback(any(), any());
+        verify(mImsMmTelManager, never()).unregisterImsRegistrationCallback(any());
+        verify(mWifiMetrics).noteWifiOff(eq(false), eq(false), anyInt());
+
+        // on an explicit stop, we should not trigger the callback
+        verifyNoMoreInteractions(mListener);
+    }
+
+    /**
      * ClientMode starts up in connect mode and then change connectivity roles.
      */
     @Test
@@ -1183,13 +1370,161 @@
 
         // Set the same role again, no-op.
         assertEquals(ActiveModeManager.ROLE_CLIENT_PRIMARY, mClientModeManager.getRole());
-        mClientModeManager.setRole(ActiveModeManager.ROLE_CLIENT_PRIMARY);
+        mClientModeManager.setRole(ActiveModeManager.ROLE_CLIENT_PRIMARY, TEST_WORKSOURCE);
         mLooper.dispatchAll();
-        verify(mListener, never()).onStarted(); // no callback sent.
+        verify(mWifiNative).replaceStaIfaceRequestorWs(
+                eq(TEST_INTERFACE_NAME), same(TEST_WORKSOURCE));
+        verify(mListener, never()).onRoleChanged(any()); // no callback sent.
 
         // Change the connectivity role.
-        mClientModeManager.setRole(ActiveModeManager.ROLE_CLIENT_SECONDARY);
+        ActiveModeManager.Listener<ConcreteClientModeManager> newListener =
+                mock(ActiveModeManager.Listener.class);
+        mClientModeManager.setRole(
+                ActiveModeManager.ROLE_CLIENT_SECONDARY_TRANSIENT, TEST_WORKSOURCE, newListener);
         mLooper.dispatchAll();
-        verify(mListener).onStarted(); // callback sent.
+        verify(mWifiNative, times(2)).replaceStaIfaceRequestorWs(
+                eq(TEST_INTERFACE_NAME), same(TEST_WORKSOURCE));
+        verify(newListener).onRoleChanged(mClientModeManager); // callback sent on new listener.
+        verifyNoMoreInteractions(mListener); // no callback sent on older listener.
+    }
+
+    @Test
+    public void clientInConnectModeChangeRolesNewWorkSource_WifiNativeFailed_noCallback()
+            throws Exception {
+        startClientInConnectModeAndVerifyEnabled();
+        reset(mListener);
+
+        when(mWifiNative.replaceStaIfaceRequestorWs(TEST_INTERFACE_NAME, TEST_WORKSOURCE2))
+                .thenReturn(false);
+
+        // set new mode with new WorkSource
+        mClientModeManager.setRole(ROLE_CLIENT_SECONDARY_TRANSIENT, TEST_WORKSOURCE2);
+        mLooper.dispatchAll();
+
+        verify(mWifiNative).replaceStaIfaceRequestorWs(
+                eq(TEST_INTERFACE_NAME), same(TEST_WORKSOURCE2));
+        // no callback sent since replaceStaIfaceRequestorWs failed
+        verify(mListener, never()).onRoleChanged(any());
+    }
+
+    @Test
+    public void setRoleBeforeInvokingListener() throws Exception {
+        when(mWifiNative.setupInterfaceForClientInScanMode(any(), any()))
+                .thenReturn(TEST_INTERFACE_NAME);
+        when(mWifiNative.switchClientInterfaceToConnectivityMode(any(), any()))
+                .thenReturn(true);
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ActiveModeManager clientModeManager) throws Exception {
+                assertEquals(ROLE_CLIENT_SCAN_ONLY, clientModeManager.getRole());
+            }
+        }).when(mListener).onStarted(mClientModeManager);
+        mClientModeManager = createClientModeManager(ROLE_CLIENT_SCAN_ONLY);
+        mLooper.dispatchAll();
+
+        ActiveModeManager.ClientRole lastRole = mClientModeManager.getRole();
+        assertEquals(ROLE_CLIENT_SCAN_ONLY, lastRole);
+        assertNull(mClientModeManager.getPreviousRole());
+
+        verify(mWifiNative).setupInterfaceForClientInScanMode(
+                mInterfaceCallbackCaptor.capture(), eq(TEST_WORKSOURCE));
+        mInterfaceCallbackCaptor.getValue().onUp(TEST_INTERFACE_NAME);
+        mLooper.dispatchAll();
+        verify(mListener).onStarted(mClientModeManager); // callback sent.
+
+        // Change to connectivity role.
+        long testChangeRoleTimestamp = 12234455L;
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(testChangeRoleTimestamp);
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ActiveModeManager clientModeManager) throws Exception {
+                assertEquals(ActiveModeManager.ROLE_CLIENT_PRIMARY, clientModeManager.getRole());
+            }
+        }).when(mListener).onRoleChanged(mClientModeManager);
+        mClientModeManager.setRole(ActiveModeManager.ROLE_CLIENT_PRIMARY, TEST_WORKSOURCE);
+        mLooper.dispatchAll();
+        verify(mListener).onRoleChanged(mClientModeManager); // callback sent.
+
+        // Verify the role is changed and previousRole is updated.
+        assertEquals("Should equal previous role", lastRole,
+                mClientModeManager.getPreviousRole());
+        assertEquals("The role change timestamp should match", testChangeRoleTimestamp,
+                mClientModeManager.getLastRoleChangeSinceBootMs());
+    }
+
+    @Test
+    public void propagateSettingsToClientModeImpl() throws Exception {
+        startClientInConnectModeAndVerifyEnabled();
+        verify(mWifiInjector).makeClientModeImpl(any(), any(), eq(false));
+        verify(mClientModeImpl).setShouldReduceNetworkScore(false);
+
+        mClientModeManager.enableVerboseLogging(true);
+        verify(mClientModeImpl).enableVerboseLogging(true);
+
+        mClientModeManager.enableVerboseLogging(false);
+        verify(mClientModeImpl).enableVerboseLogging(false);
+
+        mClientModeManager.setShouldReduceNetworkScore(true);
+        verify(mClientModeImpl).setShouldReduceNetworkScore(true);
+
+        mClientModeManager.setShouldReduceNetworkScore(false);
+        verify(mClientModeImpl, times(2)).setShouldReduceNetworkScore(false);
+    }
+
+    @Test
+    public void propagateConnectedWifiScorerToPrimaryClientModeImpl() throws Exception {
+        startClientInConnectModeAndVerifyEnabled();
+
+        IBinder iBinder = mock(IBinder.class);
+        IWifiConnectedNetworkScorer iScorer = mock(IWifiConnectedNetworkScorer.class);
+        mClientModeManager.setWifiConnectedNetworkScorer(iBinder, iScorer);
+        verify(mClientModeImpl).setWifiConnectedNetworkScorer(iBinder, iScorer);
+
+        mClientModeManager.clearWifiConnectedNetworkScorer();
+        verify(mClientModeImpl).clearWifiConnectedNetworkScorer();
+
+        mClientModeManager.setWifiConnectedNetworkScorer(iBinder, iScorer);
+        verify(mClientModeImpl, times(2)).setWifiConnectedNetworkScorer(iBinder, iScorer);
+    }
+
+    @Test
+    public void updateConnectModeStateInAllRoles() throws Exception {
+        startClientInConnectModeAndVerifyEnabled();
+        mClientModeManager.setRole(ROLE_CLIENT_SECONDARY_TRANSIENT, TEST_WORKSOURCE);
+        mLooper.dispatchAll();
+        mClientModeManager.stop();
+        // disabling broadcast wasn't sent out (since role is secondary)
+        verify(mContext, never()).sendStickyBroadcastAsUser(
+                argThat(intent ->
+                        intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, -1)
+                                == WifiManager.WIFI_STATE_DISABLING),
+                any());
+        // but wifi state was updated (should be updated no matter the role)
+        assertEquals(WifiManager.WIFI_STATE_DISABLING, mClientModeManager.syncGetWifiState());
+    }
+
+    @Test
+    public void changeRoleResetsSettings() throws Exception {
+        startClientInConnectModeAndVerifyEnabled();
+
+        verify(mClientModeImpl).setShouldReduceNetworkScore(false);
+
+        mClientModeManager.setRole(ROLE_CLIENT_SECONDARY_TRANSIENT, TEST_WORKSOURCE);
+        mLooper.dispatchAll();
+
+        // reset upon role change
+        verify(mClientModeImpl, times(2)).setShouldReduceNetworkScore(false);
+    }
+
+    @Test
+    public void sameRoleDoesntResetsSettings() throws Exception {
+        startClientInConnectModeAndVerifyEnabled();
+
+        verify(mClientModeImpl).setShouldReduceNetworkScore(false);
+
+        mClientModeManager.setRole(ROLE_CLIENT_PRIMARY, TEST_WORKSOURCE);
+        mLooper.dispatchAll();
+
+        // no role change, no reset
+        verify(mClientModeImpl).setShouldReduceNetworkScore(false);
     }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/ConfigurationMapTest.java b/service/tests/wifitests/src/com/android/server/wifi/ConfigurationMapTest.java
index 02994ec..6dd864c 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/ConfigurationMapTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/ConfigurationMapTest.java
@@ -16,25 +16,29 @@
 
 package com.android.server.wifi;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.*;
 
 import android.content.pm.UserInfo;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.SparseArray;
 
 import androidx.test.filters.SmallTest;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -49,19 +53,26 @@
 @SmallTest
 public class ConfigurationMapTest extends WifiBaseTest {
     private static final int SYSTEM_MANAGE_PROFILE_USER_ID = 12;
+    private static final String TEST_BSSID = "0a:08:5c:67:89:01";
     private static final List<WifiConfiguration> CONFIGS = Arrays.asList(
             WifiConfigurationTestUtil.generateWifiConfig(
-                    0, 1000000, "\"red\"", true, true, null, null),
+                    0, 1000000, "\"red\"", true, true, null, null,
+                    WifiConfigurationTestUtil.SECURITY_NONE),
             WifiConfigurationTestUtil.generateWifiConfig(
-                    1, 1000001, "\"green\"", true, false, "example.com", "Green"),
+                    1, 1000001, "\"green\"", true, false, "example.com", "Green",
+                    WifiConfigurationTestUtil.SECURITY_NONE),
             WifiConfigurationTestUtil.generateWifiConfig(
-                    2, 1200000, "\"blue\"", false, true, null, null),
+                    2, 1200000, "\"blue\"", false, true, null, null,
+                    WifiConfigurationTestUtil.SECURITY_NONE),
             WifiConfigurationTestUtil.generateWifiConfig(
-                    3, 1100000, "\"cyan\"", true, true, null, null),
+                    3, 1100000, "\"cyan\"", true, true, null, null,
+                    WifiConfigurationTestUtil.SECURITY_NONE),
             WifiConfigurationTestUtil.generateWifiConfig(
-                    4, 1100001, "\"yellow\"", true, true, "example.org", "Yellow"),
+                    4, 1100001, "\"yellow\"", true, true, "example.org", "Yellow",
+                    WifiConfigurationTestUtil.SECURITY_NONE),
             WifiConfigurationTestUtil.generateWifiConfig(
-                    5, 1100002, "\"magenta\"", false, false, null, null));
+                    5, 1100002, "\"magenta\"", false, false, null, null,
+                    WifiConfigurationTestUtil.SECURITY_NONE));
 
     private static final SparseArray<List<UserInfo>> USER_PROFILES = new SparseArray<>();
     static {
@@ -73,6 +84,11 @@
     }
 
     @Mock UserManager mUserManager;
+    @Mock WifiInjector mWifiInjector;
+    @Mock ActiveModeWarden mActiveModeWarden;
+    @Mock ClientModeManager mPrimaryClientModeManager;
+    @Mock WifiGlobals mWifiGlobals;
+    private MockitoSession mStaticMockSession = null;
 
     private int mCurrentUserId = UserHandle.USER_SYSTEM;
     private ConfigurationMap mConfigs;
@@ -83,6 +99,17 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        mStaticMockSession = mockitoSession()
+                .mockStatic(WifiInjector.class)
+                .startMocking();
+        lenient().when(WifiInjector.getInstance()).thenReturn(mWifiInjector);
+        when(mWifiInjector.getActiveModeWarden()).thenReturn(mActiveModeWarden);
+        when(mWifiInjector.getWifiGlobals()).thenReturn(mWifiGlobals);
+        when(mActiveModeWarden.getPrimaryClientModeManager()).thenReturn(mPrimaryClientModeManager);
+        when(mPrimaryClientModeManager.getSupportedFeatures()).thenReturn(
+                WifiManager.WIFI_FEATURE_WPA3_SAE | WifiManager.WIFI_FEATURE_OWE);
+        when(mWifiGlobals.isWpa3SaeUpgradeEnabled()).thenReturn(true);
+        when(mWifiGlobals.isOweUpgradeEnabled()).thenReturn(true);
 
         // by default, return false
         when(mUserManager.isSameProfileGroup(any(), any())).thenReturn(false);
@@ -96,6 +123,13 @@
         mConfigs = new ConfigurationMap(mUserManager);
     }
 
+    @After
+    public void cleanUp() throws Exception {
+        if (null != mStaticMockSession) {
+            mStaticMockSession.finishMocking();
+        }
+    }
+
     private void switchUser(int newUserId) {
         mCurrentUserId = newUserId;
         mConfigs.setNewUser(newUserId);
@@ -155,7 +189,8 @@
         // visible to the current user.
         for (WifiConfiguration config : configsForCurrentUser) {
             assertEquals(config, mConfigs.getForCurrentUser(config.networkId));
-            assertEquals(config, mConfigs.getByConfigKeyForCurrentUser(config.getKey()));
+            assertEquals(config, mConfigs.getByConfigKeyForCurrentUser(
+                    config.getProfileKey()));
             final boolean wasEphemeral = config.ephemeral;
             config.ephemeral = false;
             assertNull(getEphemeralForCurrentUser(config.SSID));
@@ -168,7 +203,7 @@
         // visible to the current user.
         for (WifiConfiguration config : configsNotForCurrentUser) {
             assertNull(mConfigs.getForCurrentUser(config.networkId));
-            assertNull(mConfigs.getByConfigKeyForCurrentUser(config.getKey()));
+            assertNull(mConfigs.getByConfigKeyForCurrentUser(config.getProfileKey()));
             final boolean wasEphemeral = config.ephemeral;
             config.ephemeral = false;
             assertNull(getEphemeralForCurrentUser(config.SSID));
@@ -188,7 +223,7 @@
     }
 
     private ScanResult createScanResultForNetwork(WifiConfiguration config) {
-        return WifiConfigurationTestUtil.createScanDetailForNetwork(config, "", 0, 0, 0, 0)
+        return WifiConfigurationTestUtil.createScanDetailForNetwork(config, TEST_BSSID, 0, 0, 0, 0)
                 .getScanResult();
     }
 
@@ -202,7 +237,7 @@
         WifiConfiguration retrievedConfig =
                 mConfigs.getByScanResultForCurrentUser(scanResult);
         assertNotNull(retrievedConfig);
-        assertEquals(config.getKey(), retrievedConfig.getKey());
+        assertEquals(config.getProfileKey(), retrievedConfig.getProfileKey());
     }
 
     /**
@@ -292,8 +327,7 @@
         WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
         ScanResult scanResult = createScanResultForNetwork(config);
         // Change the network security type and the old scan result should not match now.
-        config.allowedKeyManagement.clear();
-        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+        config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
         mConfigs.put(config);
         assertNull(mConfigs.getByScanResultForCurrentUser(scanResult));
     }
@@ -352,4 +386,33 @@
         mConfigs.put(config);
         assertNull(mConfigs.getByScanResultForCurrentUser(scanResult));
     }
+
+    @Test
+    public void testScanResultDoesNotMatchForWifiNetworkSuggestion() {
+        // 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 WifiNetworkSuggestion network, this should not create a scan result match info
+        // cache entry.
+        config.ephemeral = true;
+        config.fromWifiNetworkSuggestion = true;
+        mConfigs.put(config);
+        assertNull(mConfigs.getByScanResultForCurrentUser(scanResult));
+    }
+
+    @Test
+    public void testScanResultDoesNotMatchForPasspoint() {
+        // Add passpoint network, this should not create a scan result match info cache entry.
+        WifiConfiguration config = WifiConfigurationTestUtil.createPasspointNetwork();
+        ScanResult scanResult = createScanResultForNetwork(config);
+        config.networkId = 5;
+        mConfigs.put(config);
+        assertNull(mConfigs.getByScanResultForCurrentUser(scanResult));
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/ConnectHelperTest.java b/service/tests/wifitests/src/com/android/server/wifi/ConnectHelperTest.java
new file mode 100644
index 0000000..e6d3f2b
--- /dev/null
+++ b/service/tests/wifitests/src/com/android/server/wifi/ConnectHelperTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.wifi.util.ActionListenerWrapper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Unit tests for {@link ConnectHelper} */
+@SmallTest
+public class ConnectHelperTest extends WifiBaseTest {
+
+    private static final int TEST_CALLING_UID = 1000;
+    private static final int TEST_NETWORK_ID = 42;
+    private static final String TEST_SSID = "TestSSID";
+
+    private ConnectHelper mConnectHelper;
+
+    @Mock
+    private WifiConfigManager mWifiConfigManager;
+    @Mock
+    private ClientModeManager mClientModeManager;
+    @Mock
+    private ActionListenerWrapper mActionListener;
+
+    private WifiConfiguration mWifiConfig;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        ActiveModeWarden activeModeWarden = mock(ActiveModeWarden.class);
+        when(activeModeWarden.getPrimaryClientModeManager()).thenReturn(mClientModeManager);
+
+        mConnectHelper = new ConnectHelper(activeModeWarden, mWifiConfigManager);
+        mWifiConfig = new WifiConfiguration();
+        mWifiConfig.SSID = TEST_SSID;
+        mWifiConfig.networkId = TEST_NETWORK_ID;
+    }
+
+    @Test
+    public void connectToNetwork_success() throws Exception {
+        when(mWifiConfigManager.getConfiguredNetwork(TEST_NETWORK_ID)).thenReturn(mWifiConfig);
+
+        NetworkUpdateResult result = new NetworkUpdateResult(TEST_NETWORK_ID);
+        mConnectHelper.connectToNetwork(result, mActionListener, TEST_CALLING_UID);
+
+        verify(mWifiConfigManager).updateBeforeConnect(TEST_NETWORK_ID, TEST_CALLING_UID);
+        verify(mClientModeManager).connectNetwork(eq(result), any(), eq(TEST_CALLING_UID));
+        // success is sent by ClientModeManager, not sent by ConnectHelper
+        verify(mActionListener, never()).sendSuccess();
+        verify(mActionListener, never()).sendFailure(anyInt());
+    }
+
+    @Test
+    public void connectToNetwork_invalidNetId_failure() throws Exception {
+        when(mWifiConfigManager.getConfiguredNetwork(TEST_NETWORK_ID)).thenReturn(null);
+
+        mConnectHelper.connectToNetwork(new NetworkUpdateResult(TEST_NETWORK_ID), mActionListener,
+                TEST_CALLING_UID);
+
+        verify(mWifiConfigManager, never()).updateBeforeConnect(TEST_NETWORK_ID, TEST_CALLING_UID);
+        verify(mClientModeManager, never()).connectNetwork(any(), any(), anyInt());
+        verify(mActionListener).sendFailure(WifiManager.ERROR);
+        verify(mActionListener, never()).sendSuccess();
+    }
+}
diff --git a/service/tests/wifitests/src/com/android/server/wifi/ConnectionFailureNotifierTest.java b/service/tests/wifitests/src/com/android/server/wifi/ConnectionFailureNotifierTest.java
index 1c9df67..fda7b6d 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/ConnectionFailureNotifierTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/ConnectionFailureNotifierTest.java
@@ -20,15 +20,10 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.*;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
 import android.app.AlertDialog;
 import android.app.Notification;
-import android.app.NotificationManager;
 import android.content.BroadcastReceiver;
-import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.res.Resources;
@@ -53,21 +48,20 @@
  */
 @SmallTest
 public class ConnectionFailureNotifierTest extends WifiBaseTest {
-    @Mock private Context mContext;
-    @Mock private WifiInjector mWifiInjector;
+    @Mock private WifiContext mContext;
     @Mock private Resources mResources;
     @Mock private FrameworkFacade mFrameworkFacade;
     @Mock private WifiConfigManager mWifiConfigManager;
     @Mock private WifiConnectivityManager mWifiConnectivityManager;
-    @Mock private NotificationManager mNotificationManager;
+    @Mock private WifiNotificationManager mWifiNotificationManager;
     @Mock private ConnectionFailureNotificationBuilder mConnectionFailureNotificationBuilder;
     @Mock private Notification mNotification;
     @Mock private AlertDialog mAlertDialog;
 
-    final ArgumentCaptor<BroadcastReceiver> mBroadCastReceiverCaptor =
+    private final ArgumentCaptor<BroadcastReceiver> mBroadCastReceiverCaptor =
             ArgumentCaptor.forClass(BroadcastReceiver.class);
     private ConnectionFailureNotifier mConnectionFailureNotifier;
-    TestLooper mLooper;
+    private TestLooper mLooper;
 
     /** Initialize objects before each test run. */
     @Before
@@ -76,16 +70,14 @@
         mLooper = new TestLooper();
         MockitoAnnotations.initMocks(this);
         when(mContext.getResources()).thenReturn(mResources);
-        when(mWifiInjector.getNotificationManager()).thenReturn(mNotificationManager);
-        when(mWifiInjector.getConnectionFailureNotificationBuilder())
-                .thenReturn(mConnectionFailureNotificationBuilder);
         when(mConnectionFailureNotificationBuilder
                 .buildNoMacRandomizationSupportNotification(any())).thenReturn(mNotification);
         when(mConnectionFailureNotificationBuilder.buildChangeMacRandomizationSettingDialog(any(),
                 any())).thenReturn(mAlertDialog);
-        mConnectionFailureNotifier = new ConnectionFailureNotifier(mContext, mWifiInjector,
-                mFrameworkFacade, mWifiConfigManager, mWifiConnectivityManager,
-                new Handler(mLooper.getLooper()));
+        mConnectionFailureNotifier = new ConnectionFailureNotifier(
+                mContext, mFrameworkFacade, mWifiConfigManager, mWifiConnectivityManager,
+                new Handler(mLooper.getLooper()), mWifiNotificationManager,
+                mConnectionFailureNotificationBuilder);
 
         verify(mContext).registerReceiver(mBroadCastReceiverCaptor.capture(), any());
     }
@@ -121,12 +113,12 @@
         // Verify that the network is using randomized MAC at the start.
         WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
         when(mWifiConfigManager.getConfiguredNetwork(config.networkId)).thenReturn(config);
-        assertEquals(WifiConfiguration.RANDOMIZATION_PERSISTENT, config.macRandomizationSetting);
+        assertEquals(WifiConfiguration.RANDOMIZATION_AUTO, config.macRandomizationSetting);
 
         mConnectionFailureNotifier.showFailedToConnectDueToNoRandomizedMacSupportNotification(
                 config.networkId);
         // verify that a notification is sent
-        verify(mNotificationManager).notify(
+        verify(mWifiNotificationManager).notify(
                 eq(SystemMessage.NOTE_NETWORK_NO_MAC_RANDOMIZATION_SUPPORT), eq(mNotification));
 
         // sets up the intent that simulates the user tapping on the notification.
@@ -162,7 +154,7 @@
         mConnectionFailureNotifier.showFailedToConnectDueToNoRandomizedMacSupportNotification(
                 config.networkId);
         // verify that a notification is sent
-        verify(mNotificationManager).notify(
+        verify(mWifiNotificationManager).notify(
                 eq(SystemMessage.NOTE_NETWORK_NO_MAC_RANDOMIZATION_SUPPORT), any());
 
         // sets up the intent that simulates the user tapping on the notification.
diff --git a/service/tests/wifitests/src/com/android/server/wifi/DeviceConfigFacadeTest.java b/service/tests/wifitests/src/com/android/server/wifi/DeviceConfigFacadeTest.java
index a570d29..5b16b9c 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/DeviceConfigFacadeTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/DeviceConfigFacadeTest.java
@@ -150,6 +150,10 @@
                 mDeviceConfigFacade.getConnectionFailureHighThrPercent());
         assertEquals(DeviceConfigFacade.DEFAULT_CONNECTION_FAILURE_COUNT_MIN,
                 mDeviceConfigFacade.getConnectionFailureCountMin());
+        assertEquals(DeviceConfigFacade.DEFAULT_CONNECTION_FAILURE_DISCONNECTION_HIGH_THR_PERCENT,
+                mDeviceConfigFacade.getConnectionFailureDisconnectionHighThrPercent());
+        assertEquals(DeviceConfigFacade.DEFAULT_CONNECTION_FAILURE_DISCONNECTION_COUNT_MIN,
+                mDeviceConfigFacade.getConnectionFailureDisconnectionCountMin());
         assertEquals(DeviceConfigFacade.DEFAULT_ASSOC_REJECTION_HIGH_THR_PERCENT,
                 mDeviceConfigFacade.getAssocRejectionHighThrPercent());
         assertEquals(DeviceConfigFacade.DEFAULT_ASSOC_REJECTION_COUNT_MIN,
@@ -193,6 +197,7 @@
                 mDeviceConfigFacade.getTxLinkSpeedLowThresholdMbps());
         assertEquals(DeviceConfigFacade.DEFAULT_RX_LINK_SPEED_LOW_THRESHOLD_MBPS,
                 mDeviceConfigFacade.getRxLinkSpeedLowThresholdMbps());
+        assertEquals(false, mDeviceConfigFacade.isWifiBatterySaverEnabled());
         assertEquals(DeviceConfigFacade.DEFAULT_HEALTH_MONITOR_RSSI_POLL_VALID_TIME_MS,
                 mDeviceConfigFacade.getHealthMonitorRssiPollValidTimeMs());
         assertEquals(DeviceConfigFacade.DEFAULT_HEALTH_MONITOR_SHORT_CONNECTION_DURATION_THR_MS,
@@ -211,6 +216,11 @@
                 mDeviceConfigFacade.getMinConfirmationDurationSendHighScoreMs());
         assertEquals(DeviceConfigFacade.DEFAULT_RSSI_THRESHOLD_NOT_SEND_LOW_SCORE_TO_CS_DBM,
                 mDeviceConfigFacade.getRssiThresholdNotSendLowScoreToCsDbm());
+        assertEquals(false, mDeviceConfigFacade.allowEnhancedMacRandomizationOnOpenSsids());
+        assertEquals(DeviceConfigFacade.DEFAULT_TRAFFIC_STATS_THRESHOLD_MAX_KB,
+                mDeviceConfigFacade.getTrafficStatsThresholdMaxKbyte());
+        assertEquals(DeviceConfigFacade.DEFAULT_BANDWIDTH_ESTIMATOR_TIME_CONSTANT_LARGE_SEC,
+                mDeviceConfigFacade.getBandwidthEstimatorLargeTimeConstantSec());
     }
 
     /**
@@ -253,6 +263,12 @@
                 anyInt())).thenReturn(31);
         when(DeviceConfig.getInt(anyString(), eq("connection_failure_count_min"),
                 anyInt())).thenReturn(4);
+        when(DeviceConfig.getInt(anyString(),
+                eq("connection_failure_disconnection_high_thr_percent"),
+                anyInt())).thenReturn(32);
+        when(DeviceConfig.getInt(anyString(),
+                eq("connection_failure_disconnection_count_min"),
+                anyInt())).thenReturn(8);
         when(DeviceConfig.getInt(anyString(), eq("assoc_rejection_high_thr_percent"),
                 anyInt())).thenReturn(10);
         when(DeviceConfig.getInt(anyString(), eq("assoc_rejection_count_min"),
@@ -301,6 +317,8 @@
                 anyInt())).thenReturn(9);
         when(DeviceConfig.getInt(anyString(), eq("rx_link_speed_low_threshold_mbps"),
                 anyInt())).thenReturn(10);
+        when(DeviceConfig.getBoolean(anyString(), eq("battery_saver_enabled"), anyBoolean()))
+                .thenReturn(true);
         when(DeviceConfig.getInt(anyString(), eq("health_monitor_short_connection_duration_thr_ms"),
                 anyInt())).thenReturn(30_000);
         when(DeviceConfig.getLong(anyString(), eq("abnormal_disconnection_reason_code_mask"),
@@ -319,6 +337,13 @@
                 anyInt())).thenReturn(1000);
         when(DeviceConfig.getInt(anyString(), eq("rssi_threshold_not_send_low_score_to_cs_dbm"),
                 anyInt())).thenReturn(-70);
+        when(DeviceConfig.getBoolean(anyString(),
+                eq("allow_enhanced_mac_randomization_on_open_ssids"),
+                anyBoolean())).thenReturn(true);
+        when(DeviceConfig.getInt(anyString(), eq("traffic_stats_threshold_max_kbyte"),
+                anyInt())).thenReturn(5000);
+        when(DeviceConfig.getInt(anyString(), eq("bandwidth_estimator_time_constant_large_sec"),
+                anyInt())).thenReturn(30);
         mOnPropertiesChangedListenerCaptor.getValue().onPropertiesChanged(null);
 
         // Verifying fields are updated to the new values
@@ -342,6 +367,8 @@
         assertEquals(5, mDeviceConfigFacade.getRxPktPerSecondThr());
         assertEquals(31, mDeviceConfigFacade.getConnectionFailureHighThrPercent());
         assertEquals(4, mDeviceConfigFacade.getConnectionFailureCountMin());
+        assertEquals(32, mDeviceConfigFacade.getConnectionFailureDisconnectionHighThrPercent());
+        assertEquals(8, mDeviceConfigFacade.getConnectionFailureDisconnectionCountMin());
         assertEquals(10, mDeviceConfigFacade.getAssocRejectionHighThrPercent());
         assertEquals(5, mDeviceConfigFacade.getAssocRejectionCountMin());
         assertEquals(12, mDeviceConfigFacade.getAssocTimeoutHighThrPercent());
@@ -367,6 +394,7 @@
         assertEquals(50000, mDeviceConfigFacade.getOverlappingConnectionDurationThresholdMs());
         assertEquals(9, mDeviceConfigFacade.getTxLinkSpeedLowThresholdMbps());
         assertEquals(10, mDeviceConfigFacade.getRxLinkSpeedLowThresholdMbps());
+        assertEquals(true, mDeviceConfigFacade.isWifiBatterySaverEnabled());
         assertEquals(30_000,
                 mDeviceConfigFacade.getHealthMonitorShortConnectionDurationThrMs());
         assertEquals(0xffff_fff3_0000_ffffL,
@@ -378,5 +406,8 @@
         assertEquals(4000, mDeviceConfigFacade.getMinConfirmationDurationSendLowScoreMs());
         assertEquals(1000, mDeviceConfigFacade.getMinConfirmationDurationSendHighScoreMs());
         assertEquals(-70, mDeviceConfigFacade.getRssiThresholdNotSendLowScoreToCsDbm());
+        assertEquals(true, mDeviceConfigFacade.allowEnhancedMacRandomizationOnOpenSsids());
+        assertEquals(5000, mDeviceConfigFacade.getTrafficStatsThresholdMaxKbyte());
+        assertEquals(30, mDeviceConfigFacade.getBandwidthEstimatorLargeTimeConstantSec());
     }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/DppManagerTest.java b/service/tests/wifitests/src/com/android/server/wifi/DppManagerTest.java
index e274d68..0be8888 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/DppManagerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/DppManagerTest.java
@@ -39,6 +39,7 @@
 import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK;
 import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_CONFIGURATION;
 import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION;
+import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_ENROLLEE_FAILED_TO_SCAN_NETWORK_CHANNEL;
 import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION;
 import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_GENERIC;
 import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK;
@@ -51,8 +52,13 @@
 import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_PROGRESS_RESPONSE_PENDING;
 import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_APPLIED;
 import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT;
+import static android.net.wifi.WifiManager.EASY_CONNECT_CRYPTOGRAPHY_CURVE_PRIME256V1;
 import static android.net.wifi.WifiManager.EASY_CONNECT_NETWORK_ROLE_STA;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyString;
@@ -69,12 +75,15 @@
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiSsid;
+import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.test.TestLooper;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.internal.util.WakeupMessage;
+import com.android.modules.utils.build.SdkLevel;
+import com.android.server.wifi.util.WifiPermissionsUtil;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -93,6 +102,8 @@
     private static final String TAG = "DppManagerTest";
     private static final String TEST_INTERFACE_NAME = "testif0";
     private static final int TEST_PEER_ID = 1;
+    private static final int TEST_BOOTSTRAP_ID = 1;
+    private static final int TEST_LISTEN_CHANNEL = 6;
     private static final String TEST_SSID = "\"Test_SSID\"";
     private static final String TEST_SSID_NO_QUOTE = TEST_SSID.replace("\"", "");
     private static final String TEST_SSID_ENCODED = "546573745f53534944";
@@ -100,6 +111,7 @@
     private static final String TEST_PASSWORD_ENCODED = "73656372657450617373776f7264";
     private static final int TEST_NETWORK_ID = 1;
     private static final String TEST_BSSID = "01:02:03:04:05:06";
+    private static final String TEST_PACKAGE_NAME = "TestPackage";
 
     TestLooper mLooper;
 
@@ -125,10 +137,13 @@
     DppMetrics mDppMetrics;
     @Mock
     ScanRequestProxy mScanRequestProxy;
+    @Mock
+    WifiPermissionsUtil mWifiPermissionsUtil;
 
     String mUri =
             "DPP:C:81/1;I:DPP_TESTER;K:MDkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDIgADebGHMJoCcE7OZP/aek5muaJo"
                     + "zGy2FVKPRjA/I/qyC8Q=;;";
+    String mDeviceInfo = "DPP_TESTER";
 
     @Before
     public void setUp() {
@@ -143,9 +158,6 @@
         when(mWifiNative.removeDppUri(anyString(), anyInt()))
                 .thenReturn(true);
 
-        // Return test interface name
-        when(mWifiNative.getClientInterfaceName()).thenReturn(TEST_INTERFACE_NAME);
-
         // Successfully start enrollee
         when(mWifiNative.startDppEnrolleeInitiator(anyString(), anyInt(), anyInt())).thenReturn(
                 true);
@@ -153,11 +165,23 @@
         // Successfully start configurator
         when(mWifiNative.startDppConfiguratorInitiator(anyString(), anyInt(), anyInt(), anyString(),
                 any(), any(), anyInt(), anyInt())).thenReturn(true);
+
+        // Successfully generate the bootstrap QR code.
+        WifiNative.DppBootstrapQrCodeInfo mBootStrapInfo =
+                new WifiNative.DppBootstrapQrCodeInfo();
+        mBootStrapInfo.bootstrapId = TEST_BOOTSTRAP_ID;
+        mBootStrapInfo.listenChannel = TEST_LISTEN_CHANNEL;
+        mBootStrapInfo.uri = mUri;
+        when(mWifiNative.generateDppBootstrapInfoForResponder(
+                anyString(), anyString(), anyInt())).thenReturn(mBootStrapInfo);
+
+        // Successfully start enrollee responder
+        when(mWifiNative.startDppEnrolleeResponder(anyString(), anyInt())).thenReturn(true);
     }
 
     private DppManager createDppManager() {
         DppManager dppManger = new DppManager(new Handler(mLooper.getLooper()), mWifiNative,
-                mWifiConfigManager, mContext, mDppMetrics, mScanRequestProxy);
+                mWifiConfigManager, mContext, mDppMetrics, mScanRequestProxy, mWifiPermissionsUtil);
         dppManger.mDppTimeoutMessage = mWakeupMessage;
         dppManger.enableVerboseLogging(1);
         return dppManger;
@@ -171,9 +195,9 @@
         // Return NULL when for the selected network (invalid network)
         when(mWifiConfigManager.getConfiguredNetworkWithoutMasking(anyInt())).thenReturn(null);
 
-        mDppManager.startDppAsConfiguratorInitiator(0, mBinder, mUri, 1,
-                EASY_CONNECT_NETWORK_ROLE_STA,
-                mDppCallback);
+        assertFalse(mDppManager.isSessionInProgress());
+        mDppManager.startDppAsConfiguratorInitiator(0, TEST_PACKAGE_NAME, TEST_INTERFACE_NAME,
+                mBinder, mUri, 1, EASY_CONNECT_NETWORK_ROLE_STA, mDppCallback);
         verify(mDppCallback).onFailure(
                 eq(EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK), eq(null),
                 eq(null), eq(new int[0]));
@@ -183,6 +207,7 @@
         verify(mDppMetrics).updateDppFailure(eq(EasyConnectStatusCallback
                 .EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK));
         verifyNoMoreInteractions(mDppMetrics);
+        assertFalse(mDppManager.isSessionInProgress());
     }
 
     @Test
@@ -192,7 +217,7 @@
         selectedNetwork.SSID = "\"Test_SSID\"";
         selectedNetwork.networkId = TEST_NETWORK_ID;
         selectedNetwork.preSharedKey = "\"secretPassword\"";
-        selectedNetwork.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+        selectedNetwork.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
 
         when(mWifiConfigManager.getConfiguredNetworkWithoutMasking(anyInt())).thenReturn(
                 selectedNetwork);
@@ -200,9 +225,9 @@
         // Fail to add Peer URI
         when(mWifiNative.addDppPeerUri(anyString(), anyString())).thenReturn(-1);
 
-        mDppManager.startDppAsConfiguratorInitiator(0, mBinder, mUri, 1,
-                EASY_CONNECT_NETWORK_ROLE_STA,
-                mDppCallback);
+        assertFalse(mDppManager.isSessionInProgress());
+        mDppManager.startDppAsConfiguratorInitiator(0, TEST_PACKAGE_NAME, TEST_INTERFACE_NAME,
+                mBinder, mUri, 1, EASY_CONNECT_NETWORK_ROLE_STA, mDppCallback);
         verify(mDppCallback).onFailure(
                 eq(EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_INVALID_URI), eq(null),
                 eq(null), eq(new int[0]));
@@ -213,6 +238,7 @@
                 .EASY_CONNECT_EVENT_FAILURE_INVALID_URI));
         verify(mDppMetrics).updateDppOperationTime(anyInt());
         verifyNoMoreInteractions(mDppMetrics);
+        assertFalse(mDppManager.isSessionInProgress());
     }
 
     @Test
@@ -220,7 +246,9 @@
         // Fail to add Peer URI
         when(mWifiNative.addDppPeerUri(anyString(), anyString())).thenReturn(-1);
 
-        mDppManager.startDppAsEnrolleeInitiator(0, mBinder, mUri, mDppCallback);
+        assertFalse(mDppManager.isSessionInProgress());
+        mDppManager.startDppAsEnrolleeInitiator(0, TEST_INTERFACE_NAME, mBinder, mUri,
+                mDppCallback);
         verify(mDppCallback).onFailure(
                 eq(EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_INVALID_URI), eq(null),
                 eq(null), eq(new int[0]));
@@ -231,6 +259,7 @@
                 .EASY_CONNECT_EVENT_FAILURE_INVALID_URI));
         verify(mDppMetrics).updateDppOperationTime(anyInt());
         verifyNoMoreInteractions(mDppMetrics);
+        assertFalse(mDppManager.isSessionInProgress());
     }
 
     @Test
@@ -240,18 +269,18 @@
         selectedNetwork.SSID = "\"Test_SSID\"";
         selectedNetwork.networkId = TEST_NETWORK_ID;
         selectedNetwork.preSharedKey = "\"secretPassword\"";
-        selectedNetwork.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+        selectedNetwork.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
 
         when(mWifiConfigManager.getConfiguredNetworkWithoutMasking(anyInt())).thenReturn(
                 selectedNetwork);
 
+        assertFalse(mDppManager.isSessionInProgress());
         // Fail to start
         when(mWifiNative.startDppConfiguratorInitiator(anyString(), anyInt(), anyInt(), anyString(),
                 any(), any(), anyInt(), anyInt())).thenReturn(false);
 
-        mDppManager.startDppAsConfiguratorInitiator(0, mBinder, mUri, 1,
-                EASY_CONNECT_NETWORK_ROLE_STA,
-                mDppCallback);
+        mDppManager.startDppAsConfiguratorInitiator(0, TEST_PACKAGE_NAME, TEST_INTERFACE_NAME,
+                mBinder, mUri, 1, EASY_CONNECT_NETWORK_ROLE_STA, mDppCallback);
         verify(mDppCallback).onFailure(eq(EASY_CONNECT_EVENT_FAILURE_GENERIC), eq(null),
                 eq(null), eq(new int[0]));
         verify(mDppCallback, never()).onSuccess(anyInt());
@@ -260,6 +289,7 @@
         verify(mDppMetrics).updateDppFailure(eq(EASY_CONNECT_EVENT_FAILURE_GENERIC));
         verify(mDppMetrics).updateDppOperationTime(anyInt());
         verifyNoMoreInteractions(mDppMetrics);
+        assertFalse(mDppManager.isSessionInProgress());
     }
 
     @Test
@@ -268,7 +298,9 @@
         when(mWifiNative.startDppEnrolleeInitiator(anyString(), anyInt(), anyInt())).thenReturn(
                 false);
 
-        mDppManager.startDppAsEnrolleeInitiator(0, mBinder, mUri, mDppCallback);
+        assertFalse(mDppManager.isSessionInProgress());
+        mDppManager.startDppAsEnrolleeInitiator(0, TEST_INTERFACE_NAME, mBinder, mUri,
+                mDppCallback);
         verify(mDppCallback).onFailure(eq(EASY_CONNECT_EVENT_FAILURE_GENERIC), eq(null),
                 eq(null), eq(new int[0]));
         verify(mDppCallback, never()).onSuccess(anyInt());
@@ -277,6 +309,7 @@
         verify(mDppMetrics).updateDppFailure(eq(EASY_CONNECT_EVENT_FAILURE_GENERIC));
         verify(mDppMetrics).updateDppOperationTime(anyInt());
         verifyNoMoreInteractions(mDppMetrics);
+        assertFalse(mDppManager.isSessionInProgress());
     }
 
     @Test
@@ -286,14 +319,15 @@
         selectedNetwork.SSID = TEST_SSID;
         selectedNetwork.networkId = TEST_NETWORK_ID;
         selectedNetwork.preSharedKey = TEST_PASSWORD;
-        selectedNetwork.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+        selectedNetwork.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
+        selectedNetwork.addSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
 
         when(mWifiConfigManager.getConfiguredNetworkWithoutMasking(anyInt())).thenReturn(
                 selectedNetwork);
 
-        mDppManager.startDppAsConfiguratorInitiator(0, mBinder, mUri, 1,
-                EASY_CONNECT_NETWORK_ROLE_STA,
-                mDppCallback);
+        assertFalse(mDppManager.isSessionInProgress());
+        mDppManager.startDppAsConfiguratorInitiator(0, TEST_PACKAGE_NAME, TEST_INTERFACE_NAME,
+                mBinder, mUri, 1, EASY_CONNECT_NETWORK_ROLE_STA, mDppCallback);
         verify(mDppCallback, never()).onFailure(anyInt(), anyString(), anyString(), any());
         verify(mDppCallback, never()).onSuccess(anyInt());
         verify(mDppCallback, never()).onSuccessConfigReceived(anyInt());
@@ -302,6 +336,7 @@
                 eq(EASY_CONNECT_NETWORK_ROLE_STA), eq(PSK));
         verify(mDppMetrics).updateDppConfiguratorInitiatorRequests();
         verifyNoMoreInteractions(mDppMetrics);
+        assertTrue(mDppManager.isSessionInProgress());
     }
 
     @Test
@@ -311,14 +346,14 @@
         selectedNetwork.SSID = TEST_SSID;
         selectedNetwork.networkId = TEST_NETWORK_ID;
         selectedNetwork.preSharedKey = TEST_PASSWORD;
-        selectedNetwork.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.SAE);
+        selectedNetwork.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
 
         when(mWifiConfigManager.getConfiguredNetworkWithoutMasking(anyInt())).thenReturn(
                 selectedNetwork);
 
-        mDppManager.startDppAsConfiguratorInitiator(0, mBinder, mUri, 1,
-                EASY_CONNECT_NETWORK_ROLE_STA,
-                mDppCallback);
+        assertFalse(mDppManager.isSessionInProgress());
+        mDppManager.startDppAsConfiguratorInitiator(0, TEST_PACKAGE_NAME, TEST_INTERFACE_NAME,
+                mBinder, mUri, 1, EASY_CONNECT_NETWORK_ROLE_STA, mDppCallback);
         verify(mDppCallback, never()).onFailure(anyInt(), anyString(), anyString(), any());
         verify(mDppCallback, never()).onSuccess(anyInt());
         verify(mDppCallback, never()).onSuccessConfigReceived(anyInt());
@@ -327,6 +362,7 @@
                 eq(EASY_CONNECT_NETWORK_ROLE_STA), eq(SAE));
         verify(mDppMetrics).updateDppConfiguratorInitiatorRequests();
         verifyNoMoreInteractions(mDppMetrics);
+        assertTrue(mDppManager.isSessionInProgress());
     }
 
     @Test
@@ -335,14 +371,14 @@
         WifiConfiguration selectedNetwork = new WifiConfiguration();
         selectedNetwork.SSID = TEST_SSID;
         selectedNetwork.networkId = TEST_NETWORK_ID;
-        selectedNetwork.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+        selectedNetwork.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN);
 
         when(mWifiConfigManager.getConfiguredNetworkWithoutMasking(anyInt())).thenReturn(
                 selectedNetwork);
 
-        mDppManager.startDppAsConfiguratorInitiator(0, mBinder, mUri, 1,
-                EASY_CONNECT_NETWORK_ROLE_STA,
-                mDppCallback);
+        assertFalse(mDppManager.isSessionInProgress());
+        mDppManager.startDppAsConfiguratorInitiator(0, TEST_PACKAGE_NAME, TEST_INTERFACE_NAME,
+                mBinder, mUri, 1, EASY_CONNECT_NETWORK_ROLE_STA, mDppCallback);
         verify(mDppCallback).onFailure(eq(EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK), eq(null),
                 eq(null), eq(new int[0]));
         verify(mDppCallback, never()).onSuccess(anyInt());
@@ -351,6 +387,7 @@
         verify(mDppMetrics).updateDppFailure(eq(EasyConnectStatusCallback
                 .EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK));
         verifyNoMoreInteractions(mDppMetrics);
+        assertFalse(mDppManager.isSessionInProgress());
     }
 
     @Test
@@ -359,14 +396,14 @@
         WifiConfiguration selectedNetwork = new WifiConfiguration();
         selectedNetwork.SSID = TEST_SSID;
         selectedNetwork.networkId = TEST_NETWORK_ID;
-        selectedNetwork.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
+        selectedNetwork.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
 
         when(mWifiConfigManager.getConfiguredNetworkWithoutMasking(anyInt())).thenReturn(
                 selectedNetwork);
 
-        mDppManager.startDppAsConfiguratorInitiator(0, mBinder, mUri, 1,
-                EASY_CONNECT_NETWORK_ROLE_STA,
-                mDppCallback);
+        assertFalse(mDppManager.isSessionInProgress());
+        mDppManager.startDppAsConfiguratorInitiator(0, TEST_PACKAGE_NAME, TEST_INTERFACE_NAME,
+                mBinder, mUri, 1, EASY_CONNECT_NETWORK_ROLE_STA, mDppCallback);
         verify(mDppCallback).onFailure(eq(EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK), eq(null),
                 eq(null), eq(new int[0]));
         verify(mDppCallback, never()).onSuccess(anyInt());
@@ -375,11 +412,14 @@
         verify(mDppMetrics).updateDppFailure(eq(EasyConnectStatusCallback
                 .EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK));
         verifyNoMoreInteractions(mDppMetrics);
+        assertFalse(mDppManager.isSessionInProgress());
     }
 
     @Test
     public void testStartDppAsEnrolleeInitiatorStartCorrectly() throws Exception {
-        mDppManager.startDppAsEnrolleeInitiator(0, mBinder, mUri, mDppCallback);
+        assertFalse(mDppManager.isSessionInProgress());
+        mDppManager.startDppAsEnrolleeInitiator(0, TEST_INTERFACE_NAME, mBinder, mUri,
+                mDppCallback);
         verify(mDppCallback, never()).onFailure(anyInt(), anyString(), anyString(), any());
         verify(mDppCallback, never()).onSuccess(anyInt());
         verify(mDppCallback, never()).onSuccessConfigReceived(anyInt());
@@ -387,6 +427,7 @@
                 eq(TEST_PEER_ID), anyInt());
         verify(mDppMetrics).updateDppEnrolleeInitiatorRequests();
         verifyNoMoreInteractions(mDppMetrics);
+        assertTrue(mDppManager.isSessionInProgress());
     }
 
     @Test
@@ -397,23 +438,24 @@
         selectedNetwork.SSID = TEST_SSID;
         selectedNetwork.networkId = 1;
         selectedNetwork.preSharedKey = TEST_PASSWORD;
-        selectedNetwork.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.SAE);
+        selectedNetwork.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
 
         when(mWifiConfigManager.getConfiguredNetworkWithoutMasking(anyInt())).thenReturn(
                 selectedNetwork);
 
-        mDppManager.startDppAsConfiguratorInitiator(0, mBinder, mUri, 1,
-                EASY_CONNECT_NETWORK_ROLE_STA, mDppCallback);
+        assertFalse(mDppManager.isSessionInProgress());
+        mDppManager.startDppAsConfiguratorInitiator(0, TEST_PACKAGE_NAME, TEST_INTERFACE_NAME,
+                mBinder, mUri, 1, EASY_CONNECT_NETWORK_ROLE_STA, mDppCallback);
         verify(mDppCallback, never()).onFailure(anyInt(), anyString(), anyString(), any());
         verify(mDppCallback, never()).onSuccess(anyInt());
         verify(mDppCallback, never()).onSuccessConfigReceived(anyInt());
         verify(mWifiNative).startDppConfiguratorInitiator(eq(TEST_INTERFACE_NAME),
                 eq(TEST_PEER_ID), anyInt(), eq(TEST_SSID_ENCODED), eq(TEST_PASSWORD_ENCODED), any(),
                 eq(EASY_CONNECT_NETWORK_ROLE_STA), eq(SAE));
+        assertTrue(mDppManager.isSessionInProgress());
 
-        mDppManager.startDppAsConfiguratorInitiator(1, mBinder, mUri, 1,
-                EASY_CONNECT_NETWORK_ROLE_STA,
-                mDppCallbackConcurrent);
+        mDppManager.startDppAsConfiguratorInitiator(1, TEST_PACKAGE_NAME, TEST_INTERFACE_NAME,
+                mBinder, mUri, 1, EASY_CONNECT_NETWORK_ROLE_STA, mDppCallbackConcurrent);
         verify(mDppCallbackConcurrent).onFailure(eq(EASY_CONNECT_EVENT_FAILURE_BUSY), eq(null),
                 eq(null), eq(new int[0]));
         verify(mDppCallbackConcurrent, never()).onSuccess(anyInt());
@@ -421,19 +463,24 @@
         verify(mDppMetrics, times(2)).updateDppConfiguratorInitiatorRequests();
         verify(mDppMetrics).updateDppFailure(eq(EASY_CONNECT_EVENT_FAILURE_BUSY));
         verifyNoMoreInteractions(mDppMetrics);
+        assertTrue(mDppManager.isSessionInProgress());
     }
 
     @Test
     public void testStartDppAsEnrolleeInitiatorStartCorrectlyAndRejectConcurrentRequest()
             throws Exception {
-        mDppManager.startDppAsEnrolleeInitiator(0, mBinder, mUri, mDppCallback);
+        assertFalse(mDppManager.isSessionInProgress());
+        mDppManager.startDppAsEnrolleeInitiator(0, TEST_INTERFACE_NAME, mBinder, mUri,
+                mDppCallback);
         verify(mDppCallback, never()).onFailure(anyInt(), anyString(), anyString(), any());
         verify(mDppCallback, never()).onSuccess(anyInt());
         verify(mDppCallback, never()).onSuccessConfigReceived(anyInt());
         verify(mWifiNative).startDppEnrolleeInitiator(eq(TEST_INTERFACE_NAME),
                 eq(TEST_PEER_ID), anyInt());
+        assertTrue(mDppManager.isSessionInProgress());
 
-        mDppManager.startDppAsEnrolleeInitiator(1, mBinder, mUri, mDppCallbackConcurrent);
+        mDppManager.startDppAsEnrolleeInitiator(1, TEST_INTERFACE_NAME, mBinder, mUri,
+                mDppCallbackConcurrent);
         verify(mDppCallbackConcurrent).onFailure(eq(EASY_CONNECT_EVENT_FAILURE_BUSY), eq(null),
                 eq(null), eq(new int[0]));
         verify(mDppCallbackConcurrent, never()).onSuccess(anyInt());
@@ -441,6 +488,7 @@
         verify(mDppMetrics, times(2)).updateDppEnrolleeInitiatorRequests();
         verify(mDppMetrics).updateDppFailure(eq(EASY_CONNECT_EVENT_FAILURE_BUSY));
         verifyNoMoreInteractions(mDppMetrics);
+        assertTrue(mDppManager.isSessionInProgress());
     }
 
     @Test
@@ -450,7 +498,7 @@
         selectedNetwork.SSID = TEST_SSID;
         selectedNetwork.networkId = 1;
         selectedNetwork.preSharedKey = TEST_PASSWORD;
-        selectedNetwork.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.SAE);
+        selectedNetwork.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
 
         when(mWifiConfigManager.getConfiguredNetworkWithoutMasking(anyInt())).thenReturn(
                 selectedNetwork);
@@ -459,8 +507,9 @@
                 ArgumentCaptor.forClass(
                         WifiNative.DppEventCallback.class);
 
-        mDppManager.startDppAsConfiguratorInitiator(0, mBinder, mUri, 1,
-                EASY_CONNECT_NETWORK_ROLE_STA, mDppCallback);
+        assertFalse(mDppManager.isSessionInProgress());
+        mDppManager.startDppAsConfiguratorInitiator(0, TEST_PACKAGE_NAME, TEST_INTERFACE_NAME,
+                mBinder, mUri, 1, EASY_CONNECT_NETWORK_ROLE_STA, mDppCallback);
         verify(mWifiNative).registerDppEventCallback(dppEventCallbackCaptor.capture());
         verify(mDppCallback, never()).onFailure(anyInt(), anyString(), anyString(), any());
         verify(mDppCallback, never()).onSuccess(anyInt());
@@ -469,6 +518,7 @@
                 eq(TEST_PEER_ID), anyInt(), eq(TEST_SSID_ENCODED), eq(TEST_PASSWORD_ENCODED), any(),
                 eq(EASY_CONNECT_NETWORK_ROLE_STA), eq(
                         SAE));
+        assertTrue(mDppManager.isSessionInProgress());
 
         WifiNative.DppEventCallback dppEventCallback = dppEventCallbackCaptor.getValue();
 
@@ -489,6 +539,7 @@
                 .EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT));
         verify(mDppMetrics).updateDppOperationTime(anyInt());
         verifyNoMoreInteractions(mDppMetrics);
+        assertFalse(mDppManager.isSessionInProgress());
     }
 
     @Test
@@ -497,13 +548,16 @@
                 ArgumentCaptor.forClass(
                         WifiNative.DppEventCallback.class);
 
-        mDppManager.startDppAsEnrolleeInitiator(0, mBinder, mUri, mDppCallback);
+        assertFalse(mDppManager.isSessionInProgress());
+        mDppManager.startDppAsEnrolleeInitiator(0, TEST_INTERFACE_NAME, mBinder, mUri,
+                mDppCallback);
         verify(mWifiNative).registerDppEventCallback(dppEventCallbackCaptor.capture());
         verify(mDppCallback, never()).onFailure(anyInt(), anyString(), anyString(), any());
         verify(mDppCallback, never()).onSuccess(anyInt());
         verify(mDppCallback, never()).onSuccessConfigReceived(anyInt());
         verify(mWifiNative).startDppEnrolleeInitiator(eq(TEST_INTERFACE_NAME),
                 eq(TEST_PEER_ID), anyInt());
+        assertTrue(mDppManager.isSessionInProgress());
 
         // Generate an onSuccessConfigReceived callback
         WifiNative.DppEventCallback dppEventCallback = dppEventCallbackCaptor.getValue();
@@ -513,7 +567,7 @@
         selectedNetwork.SSID = TEST_SSID;
         selectedNetwork.networkId = TEST_NETWORK_ID;
         selectedNetwork.preSharedKey = TEST_PASSWORD;
-        selectedNetwork.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.SAE);
+        selectedNetwork.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
 
         // Generate a progress event
         dppEventCallback.onProgress(AUTHENTICATION_SUCCESS);
@@ -527,14 +581,17 @@
 
         dppEventCallback.onSuccessConfigReceived(selectedNetwork);
         mLooper.dispatchAll();
-        verify(mDppCallback).onSuccessConfigReceived(eq(TEST_NETWORK_ID));
+        verify(mDppCallback).onSuccessConfigReceived(
+                eq(WifiConfigurationUtil.addSecurityTypeToNetworkId(
+                        TEST_NETWORK_ID, WifiConfiguration.SECURITY_TYPE_SAE)));
         verify(mDppCallback, never()).onSuccess(anyInt());
         verify(mDppCallback, never()).onFailure(anyInt(), anyString(), anyString(), any());
         verify(mDppMetrics).updateDppEnrolleeInitiatorRequests();
         verify(mDppMetrics).updateDppEnrolleeSuccess();
         verify(mDppMetrics).updateDppOperationTime(anyInt());
         verifyNoMoreInteractions(mDppMetrics);
-        verifyCleanUpResources();
+        verifyCleanUpResources(DppManager.DPP_AUTH_ROLE_INITIATOR);
+        assertFalse(mDppManager.isSessionInProgress());
     }
 
     @Test
@@ -544,7 +601,7 @@
         selectedNetwork.SSID = TEST_SSID;
         selectedNetwork.networkId = TEST_NETWORK_ID;
         selectedNetwork.preSharedKey = TEST_PASSWORD;
-        selectedNetwork.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.SAE);
+        selectedNetwork.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
 
         when(mWifiConfigManager.getConfiguredNetworkWithoutMasking(anyInt())).thenReturn(
                 selectedNetwork);
@@ -552,9 +609,9 @@
                 ArgumentCaptor.forClass(
                         WifiNative.DppEventCallback.class);
 
-        mDppManager.startDppAsConfiguratorInitiator(0, mBinder, mUri, 1,
-                EASY_CONNECT_NETWORK_ROLE_STA,
-                mDppCallback);
+        assertFalse(mDppManager.isSessionInProgress());
+        mDppManager.startDppAsConfiguratorInitiator(0, TEST_PACKAGE_NAME, TEST_INTERFACE_NAME,
+                mBinder, mUri, 1, EASY_CONNECT_NETWORK_ROLE_STA, mDppCallback);
         verify(mWifiNative).registerDppEventCallback(dppEventCallbackCaptor.capture());
         verify(mDppCallback, never()).onFailure(anyInt(), anyString(), anyString(), any());
         verify(mDppCallback, never()).onSuccess(anyInt());
@@ -563,6 +620,7 @@
                 eq(TEST_PEER_ID), anyInt(), eq(TEST_SSID_ENCODED), eq(TEST_PASSWORD_ENCODED), any(),
                 eq(EASY_CONNECT_NETWORK_ROLE_STA), eq(
                         SAE));
+        assertTrue(mDppManager.isSessionInProgress());
 
         WifiNative.DppEventCallback dppEventCallback = dppEventCallbackCaptor.getValue();
 
@@ -583,7 +641,8 @@
                 .EASY_CONNECT_EVENT_FAILURE_AUTHENTICATION));
         verify(mDppMetrics).updateDppOperationTime(anyInt());
         verifyNoMoreInteractions(mDppMetrics);
-        verifyCleanUpResources();
+        verifyCleanUpResources(DppManager.DPP_AUTH_ROLE_INITIATOR);
+        assertFalse(mDppManager.isSessionInProgress());
     }
 
     @Test
@@ -594,9 +653,6 @@
         when(mWifiNative.removeDppUri(anyString(), anyInt()))
                 .thenReturn(true);
 
-        // Return test interface name
-        when(mWifiNative.getClientInterfaceName()).thenReturn(TEST_INTERFACE_NAME);
-
         // Successful start
         when(mWifiNative.startDppEnrolleeInitiator(anyString(), anyInt(), anyInt())).thenReturn(
                 true);
@@ -605,13 +661,16 @@
                 ArgumentCaptor.forClass(
                         WifiNative.DppEventCallback.class);
 
-        mDppManager.startDppAsEnrolleeInitiator(0, mBinder, mUri, mDppCallback);
+        assertFalse(mDppManager.isSessionInProgress());
+        mDppManager.startDppAsEnrolleeInitiator(0, TEST_INTERFACE_NAME, mBinder, mUri,
+                mDppCallback);
         verify(mWifiNative).registerDppEventCallback(dppEventCallbackCaptor.capture());
         verify(mDppCallback, never()).onFailure(anyInt(), anyString(), anyString(), any());
         verify(mDppCallback, never()).onSuccess(anyInt());
         verify(mDppCallback, never()).onSuccessConfigReceived(anyInt());
         verify(mWifiNative).startDppEnrolleeInitiator(eq(TEST_INTERFACE_NAME),
                 eq(TEST_PEER_ID), anyInt());
+        assertTrue(mDppManager.isSessionInProgress());
 
         WifiNative.DppEventCallback dppEventCallback = dppEventCallbackCaptor.getValue();
 
@@ -632,7 +691,8 @@
                 .EASY_CONNECT_EVENT_FAILURE_AUTHENTICATION));
         verify(mDppMetrics).updateDppOperationTime(anyInt());
         verifyNoMoreInteractions(mDppMetrics);
-        verifyCleanUpResources();
+        verifyCleanUpResources(DppManager.DPP_AUTH_ROLE_INITIATOR);
+        assertFalse(mDppManager.isSessionInProgress());
     }
 
     @Test
@@ -655,9 +715,6 @@
         when(mWifiNative.removeDppUri(anyString(), anyInt()))
                 .thenReturn(true);
 
-        // Return test interface name
-        when(mWifiNative.getClientInterfaceName()).thenReturn(TEST_INTERFACE_NAME);
-
         // Successful start
         when(mWifiNative.startDppEnrolleeInitiator(anyString(), anyInt(), anyInt())).thenReturn(
                 true);
@@ -666,19 +723,23 @@
                 ArgumentCaptor.forClass(
                         WifiNative.DppEventCallback.class);
 
+        assertFalse(mDppManager.isSessionInProgress());
         // Start with UID 10
-        mDppManager.startDppAsEnrolleeInitiator(10, mBinder, mUri, mDppCallback);
+        mDppManager.startDppAsEnrolleeInitiator(10, TEST_INTERFACE_NAME, mBinder, mUri,
+                mDppCallback);
         verify(mWifiNative).registerDppEventCallback(dppEventCallbackCaptor.capture());
         verify(mDppCallback, never()).onFailure(anyInt(), anyString(), anyString(), any());
         verify(mDppCallback, never()).onSuccess(anyInt());
         verify(mDppCallback, never()).onSuccessConfigReceived(anyInt());
         verify(mWifiNative).startDppEnrolleeInitiator(eq(TEST_INTERFACE_NAME),
                 eq(TEST_PEER_ID), anyInt());
+        assertTrue(mDppManager.isSessionInProgress());
 
         // Check that nothing is removed or canceled
         mDppManager.stopDppSession(0);
         verify(mWifiNative, never()).removeDppUri(eq(TEST_INTERFACE_NAME), anyInt());
         verify(mWakeupMessage, never()).cancel();
+        assertTrue(mDppManager.isSessionInProgress());
     }
 
     @Test
@@ -690,9 +751,6 @@
         when(mWifiNative.removeDppUri(anyString(), anyInt()))
                 .thenReturn(true);
 
-        // Return test interface name
-        when(mWifiNative.getClientInterfaceName()).thenReturn(TEST_INTERFACE_NAME);
-
         // Successful start
         when(mWifiNative.startDppEnrolleeInitiator(anyString(), anyInt(), anyInt())).thenReturn(
                 true);
@@ -701,23 +759,29 @@
                 ArgumentCaptor.forClass(
                         WifiNative.DppEventCallback.class);
 
+        assertFalse(mDppManager.isSessionInProgress());
         // Start with UID 10
-        mDppManager.startDppAsEnrolleeInitiator(10, mBinder, mUri, mDppCallback);
+        mDppManager.startDppAsEnrolleeInitiator(10, TEST_INTERFACE_NAME, mBinder, mUri,
+                mDppCallback);
         verify(mWifiNative).registerDppEventCallback(dppEventCallbackCaptor.capture());
         verify(mDppCallback, never()).onFailure(anyInt(), anyString(), anyString(), any());
         verify(mDppCallback, never()).onSuccess(anyInt());
         verify(mDppCallback, never()).onSuccessConfigReceived(anyInt());
         verify(mWifiNative).startDppEnrolleeInitiator(eq(TEST_INTERFACE_NAME),
                 eq(TEST_PEER_ID), anyInt());
+        assertTrue(mDppManager.isSessionInProgress());
 
         // Check that WifiNative is called to stop the DPP session
         mDppManager.stopDppSession(10);
         verify(mWifiNative).stopDppInitiator(eq(TEST_INTERFACE_NAME));
-        verifyCleanUpResources();
+        verifyCleanUpResources(DppManager.DPP_AUTH_ROLE_INITIATOR);
+        assertFalse(mDppManager.isSessionInProgress());
     }
 
-    private void verifyCleanUpResources() {
-        verify(mWifiNative).removeDppUri(eq(TEST_INTERFACE_NAME), anyInt());
+    private void verifyCleanUpResources(int authRole) {
+        if (authRole == DppManager.DPP_AUTH_ROLE_INITIATOR) {
+            verify(mWifiNative).removeDppUri(eq(TEST_INTERFACE_NAME), anyInt());
+        }
         verify(mWakeupMessage).cancel();
     }
 
@@ -781,6 +845,35 @@
     @Test
     public void testOnFailureCallbackCannotFindNetworkErrCodeIsUpdatedOnChannelMismatch()
             throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(), eq(Build.VERSION_CODES.S),
+                anyInt())).thenReturn(false);
+        int[] bandList = new int[]{81, 83, 84, 115};
+        addTestNetworkInScanResult(5180); //channel number 36
+        // Don't include Network channel(36) in Enrollee scanned channels.
+        testOnFailureCallback(CANNOT_FIND_NETWORK, TEST_SSID_NO_QUOTE,
+                "81/1,2,3,4,5,6,7,8,9,10,11,115/48",
+                bandList, EASY_CONNECT_EVENT_FAILURE_ENROLLEE_FAILED_TO_SCAN_NETWORK_CHANNEL);
+    }
+
+    @Test
+    public void testCannotFindNetworkErrCodeIsUpdatedToNotCompatibleOnSdkLevelLessThanS()
+            throws Exception {
+        assumeFalse(SdkLevel.isAtLeastS());
+        int[] bandList = new int[]{81, 83, 84, 115};
+        addTestNetworkInScanResult(5180); //channel number 36
+        // Don't include Network channel(36) in Enrollee scanned channels.
+        testOnFailureCallback(CANNOT_FIND_NETWORK, TEST_SSID_NO_QUOTE,
+                "81/1,2,3,4,5,6,7,8,9,10,11,115/48",
+                bandList, EASY_CONNECT_EVENT_FAILURE_NOT_COMPATIBLE);
+    }
+
+    @Test
+    public void testCannotFindNetworkErrCodeIsUpdatedToNotCompatibleOnTargetSdkLessThanS()
+            throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(), eq(Build.VERSION_CODES.S),
+                anyInt())).thenReturn(true);
         int[] bandList = new int[]{81, 83, 84, 115};
         addTestNetworkInScanResult(5180); //channel number 36
         // Don't include Network channel(36) in Enrollee scanned channels.
@@ -827,7 +920,7 @@
         selectedNetwork.SSID = TEST_SSID;
         selectedNetwork.networkId = 1;
         selectedNetwork.preSharedKey = TEST_PASSWORD;
-        selectedNetwork.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.SAE);
+        selectedNetwork.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
 
         when(mWifiConfigManager.getConfiguredNetworkWithoutMasking(anyInt())).thenReturn(
                 selectedNetwork);
@@ -836,8 +929,9 @@
                 ArgumentCaptor.forClass(
                         WifiNative.DppEventCallback.class);
 
-        mDppManager.startDppAsConfiguratorInitiator(0, mBinder, mUri, 1,
-                EASY_CONNECT_NETWORK_ROLE_STA, mDppCallback);
+        assertFalse(mDppManager.isSessionInProgress());
+        mDppManager.startDppAsConfiguratorInitiator(0, TEST_PACKAGE_NAME, TEST_INTERFACE_NAME,
+                mBinder, mUri, 1, EASY_CONNECT_NETWORK_ROLE_STA, mDppCallback);
         verify(mWifiNative).registerDppEventCallback(dppEventCallbackCaptor.capture());
         verify(mDppCallback, never()).onFailure(anyInt(), anyString(), anyString(), any());
         verify(mDppCallback, never()).onSuccess(anyInt());
@@ -846,6 +940,7 @@
                 eq(TEST_PEER_ID), anyInt(), eq(TEST_SSID_ENCODED), eq(TEST_PASSWORD_ENCODED), any(),
                 eq(EASY_CONNECT_NETWORK_ROLE_STA), eq(
                         SAE));
+        assertTrue(mDppManager.isSessionInProgress());
 
         WifiNative.DppEventCallback dppEventCallback = dppEventCallbackCaptor.getValue();
 
@@ -875,6 +970,7 @@
                 .EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_APPLIED));
         verify(mDppMetrics).updateDppOperationTime(anyInt());
         verifyNoMoreInteractions(mDppMetrics);
+        assertFalse(mDppManager.isSessionInProgress());
     }
 
     private void testOnFailureCallback(int internalFailure, String ssid, String channelList,
@@ -884,7 +980,7 @@
         selectedNetwork.SSID = TEST_SSID;
         selectedNetwork.networkId = TEST_NETWORK_ID;
         selectedNetwork.preSharedKey = TEST_PASSWORD;
-        selectedNetwork.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.SAE);
+        selectedNetwork.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
 
         when(mWifiConfigManager.getConfiguredNetworkWithoutMasking(anyInt())).thenReturn(
                 selectedNetwork);
@@ -892,8 +988,9 @@
                 ArgumentCaptor.forClass(
                         WifiNative.DppEventCallback.class);
 
-        mDppManager.startDppAsConfiguratorInitiator(0, mBinder, mUri, 1,
-                EASY_CONNECT_NETWORK_ROLE_STA, mDppCallback);
+        assertFalse(mDppManager.isSessionInProgress());
+        mDppManager.startDppAsConfiguratorInitiator(0, TEST_PACKAGE_NAME, TEST_INTERFACE_NAME,
+                mBinder, mUri, 1, EASY_CONNECT_NETWORK_ROLE_STA, mDppCallback);
         verify(mWifiNative).registerDppEventCallback(dppEventCallbackCaptor.capture());
         verify(mDppCallback, never()).onFailure(anyInt(), anyString(), anyString(), any());
         verify(mDppCallback, never()).onSuccess(anyInt());
@@ -901,6 +998,7 @@
         verify(mWifiNative).startDppConfiguratorInitiator(eq(TEST_INTERFACE_NAME),
                 eq(TEST_PEER_ID), anyInt(), eq(TEST_SSID_ENCODED), eq(TEST_PASSWORD_ENCODED), any(),
                 eq(EASY_CONNECT_NETWORK_ROLE_STA), eq(SAE));
+        assertTrue(mDppManager.isSessionInProgress());
 
         WifiNative.DppEventCallback dppEventCallback = dppEventCallbackCaptor.getValue();
 
@@ -915,14 +1013,227 @@
         verify(mDppMetrics).updateDppFailure(eq(appFailure));
         verify(mDppMetrics).updateDppOperationTime(anyInt());
         if ((internalFailure == CANNOT_FIND_NETWORK)
-                && (appFailure == EASY_CONNECT_EVENT_FAILURE_NOT_COMPATIBLE)) {
+                && (appFailure == EASY_CONNECT_EVENT_FAILURE_NOT_COMPATIBLE
+                || appFailure
+                == EASY_CONNECT_EVENT_FAILURE_ENROLLEE_FAILED_TO_SCAN_NETWORK_CHANNEL)) {
             verify(mDppMetrics, times(1)).updateDppR2EnrolleeResponderIncompatibleConfiguration();
         }
         verifyNoMoreInteractions(mDppMetrics);
-        verifyCleanUpResources();
+        verifyCleanUpResources(DppManager.DPP_AUTH_ROLE_INITIATOR);
+        assertFalse(mDppManager.isSessionInProgress());
     }
 
     private void testOnFailureCallback(int internalFailure, int appFailure) throws Exception {
         testOnFailureCallback(internalFailure, null, null, new int[0], appFailure);
     }
+
+    @Test
+    public void testStartDppAsEnrolleeResponderFailGenerateQrCode() throws Exception {
+        // Fail to generate QR code
+        assumeTrue(SdkLevel.isAtLeastS());
+        WifiNative.DppBootstrapQrCodeInfo bootStrapInfo =
+                new WifiNative.DppBootstrapQrCodeInfo();
+        when(mWifiNative.generateDppBootstrapInfoForResponder(
+                anyString(), anyString(), anyInt())).thenReturn(bootStrapInfo);
+        assertFalse(mDppManager.isSessionInProgress());
+        mDppManager.startDppAsEnrolleeResponder(0, TEST_INTERFACE_NAME, mBinder, mDeviceInfo,
+                EASY_CONNECT_CRYPTOGRAPHY_CURVE_PRIME256V1, mDppCallback);
+        verify(mDppCallback).onFailure(
+                eq(EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_URI_GENERATION),
+                eq(null), eq(null), eq(new int[0]));
+        verify(mDppCallback, never()).onSuccess(anyInt());
+        verify(mDppCallback, never()).onSuccessConfigReceived(anyInt());
+        verify(mDppMetrics).updateDppEnrolleeResponderRequests();
+        verify(mDppMetrics).updateDppFailure(eq(EasyConnectStatusCallback
+                .EASY_CONNECT_EVENT_FAILURE_URI_GENERATION));
+        verify(mDppMetrics).updateDppOperationTime(anyInt());
+        verifyNoMoreInteractions(mDppMetrics);
+        assertFalse(mDppManager.isSessionInProgress());
+    }
+
+    @Test
+    public void testStartDppAsEnrolleeResponderFailStart() throws Exception {
+        // Fail to start
+        assumeTrue(SdkLevel.isAtLeastS());
+        when(mWifiNative.startDppEnrolleeResponder(anyString(), anyInt())).thenReturn(false);
+
+        assertFalse(mDppManager.isSessionInProgress());
+        mDppManager.startDppAsEnrolleeResponder(0, TEST_INTERFACE_NAME, mBinder, mDeviceInfo,
+                EASY_CONNECT_CRYPTOGRAPHY_CURVE_PRIME256V1, mDppCallback);
+        verify(mDppCallback).onFailure(eq(EASY_CONNECT_EVENT_FAILURE_GENERIC), eq(null),
+                eq(null), eq(new int[0]));
+        verify(mDppCallback, never()).onSuccess(anyInt());
+        verify(mDppCallback, never()).onSuccessConfigReceived(anyInt());
+        verify(mDppMetrics).updateDppEnrolleeResponderRequests();
+        verify(mDppMetrics).updateDppFailure(eq(EASY_CONNECT_EVENT_FAILURE_GENERIC));
+        verify(mDppMetrics).updateDppOperationTime(anyInt());
+        verifyNoMoreInteractions(mDppMetrics);
+        assertFalse(mDppManager.isSessionInProgress());
+    }
+
+    @Test
+    public void testStartDppAsEnrolleeResponderStartCorrectly() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        assertFalse(mDppManager.isSessionInProgress());
+        mDppManager.startDppAsEnrolleeResponder(0, TEST_INTERFACE_NAME, mBinder, mDeviceInfo,
+                EASY_CONNECT_CRYPTOGRAPHY_CURVE_PRIME256V1, mDppCallback);
+        verify(mDppCallback, never()).onFailure(anyInt(), anyString(), anyString(), any());
+        verify(mDppCallback, never()).onSuccess(anyInt());
+        verify(mDppCallback, never()).onSuccessConfigReceived(anyInt());
+        verify(mWifiNative).startDppEnrolleeResponder(eq(TEST_INTERFACE_NAME),
+                eq(TEST_LISTEN_CHANNEL));
+        verify(mDppMetrics).updateDppEnrolleeResponderRequests();
+        verifyNoMoreInteractions(mDppMetrics);
+        assertTrue(mDppManager.isSessionInProgress());
+    }
+
+    @Test
+    public void testStartDppAsEnrolleeResponderStartCorrectlyAndRejectConcurrentRequest()
+            throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        assertFalse(mDppManager.isSessionInProgress());
+        mDppManager.startDppAsEnrolleeResponder(0, TEST_INTERFACE_NAME, mBinder, mDeviceInfo,
+                EASY_CONNECT_CRYPTOGRAPHY_CURVE_PRIME256V1, mDppCallback);
+        verify(mDppCallback, never()).onFailure(anyInt(), anyString(), anyString(), any());
+        verify(mDppCallback, never()).onSuccess(anyInt());
+        verify(mDppCallback, never()).onSuccessConfigReceived(anyInt());
+        verify(mWifiNative).startDppEnrolleeResponder(eq(TEST_INTERFACE_NAME),
+                eq(TEST_LISTEN_CHANNEL));
+        assertTrue(mDppManager.isSessionInProgress());
+
+        mDppManager.startDppAsEnrolleeResponder(1, TEST_INTERFACE_NAME, mBinder, mDeviceInfo,
+                EASY_CONNECT_CRYPTOGRAPHY_CURVE_PRIME256V1, mDppCallbackConcurrent);
+        verify(mDppCallbackConcurrent).onFailure(eq(EASY_CONNECT_EVENT_FAILURE_BUSY), eq(null),
+                eq(null), eq(new int[0]));
+        verify(mDppCallbackConcurrent, never()).onSuccess(anyInt());
+        verify(mDppCallbackConcurrent, never()).onSuccessConfigReceived(anyInt());
+        verify(mDppMetrics, times(2)).updateDppEnrolleeResponderRequests();
+        verify(mDppMetrics).updateDppFailure(eq(EASY_CONNECT_EVENT_FAILURE_BUSY));
+        verifyNoMoreInteractions(mDppMetrics);
+        assertTrue(mDppManager.isSessionInProgress());
+    }
+
+    @Test
+    public void testStartDppAsEnrolleeResponderStartCorrectlyOnSuccessCallback() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        ArgumentCaptor<WifiNative.DppEventCallback> dppEventCallbackCaptor =
+                ArgumentCaptor.forClass(
+                        WifiNative.DppEventCallback.class);
+
+        assertFalse(mDppManager.isSessionInProgress());
+        mDppManager.startDppAsEnrolleeResponder(0, TEST_INTERFACE_NAME, mBinder, mDeviceInfo,
+                EASY_CONNECT_CRYPTOGRAPHY_CURVE_PRIME256V1, mDppCallback);
+        verify(mWifiNative).registerDppEventCallback(dppEventCallbackCaptor.capture());
+        verify(mDppCallback, never()).onFailure(anyInt(), anyString(), anyString(), any());
+        verify(mDppCallback, never()).onSuccess(anyInt());
+        verify(mDppCallback, never()).onSuccessConfigReceived(anyInt());
+        verify(mWifiNative).startDppEnrolleeResponder(eq(TEST_INTERFACE_NAME),
+                eq(TEST_LISTEN_CHANNEL));
+        assertTrue(mDppManager.isSessionInProgress());
+
+        // Generate an onSuccessConfigReceived callback
+        WifiNative.DppEventCallback dppEventCallback = dppEventCallbackCaptor.getValue();
+
+        // Generate a mock WifiConfiguration object
+        WifiConfiguration selectedNetwork = new WifiConfiguration();
+        selectedNetwork.SSID = TEST_SSID;
+        selectedNetwork.networkId = TEST_NETWORK_ID;
+        selectedNetwork.preSharedKey = TEST_PASSWORD;
+        selectedNetwork.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
+
+        // Generate a progress event
+        dppEventCallback.onProgress(AUTHENTICATION_SUCCESS);
+        mLooper.dispatchAll();
+        verify(mDppCallback).onProgress(eq(EASY_CONNECT_EVENT_PROGRESS_AUTHENTICATION_SUCCESS));
+
+        // Generate result
+        NetworkUpdateResult networkUpdateResult = new NetworkUpdateResult(TEST_NETWORK_ID);
+        when(mWifiConfigManager.addOrUpdateNetwork(any(WifiConfiguration.class),
+                anyInt())).thenReturn(networkUpdateResult);
+
+        dppEventCallback.onSuccessConfigReceived(selectedNetwork);
+        mLooper.dispatchAll();
+        verify(mDppCallback).onSuccessConfigReceived(
+                eq(WifiConfigurationUtil.addSecurityTypeToNetworkId(
+                        TEST_NETWORK_ID, WifiConfiguration.SECURITY_TYPE_SAE)));
+        verify(mDppCallback, never()).onSuccess(anyInt());
+        verify(mDppCallback, never()).onFailure(anyInt(), anyString(), anyString(), any());
+        verify(mDppMetrics).updateDppEnrolleeResponderRequests();
+        verify(mDppMetrics).updateDppEnrolleeSuccess();
+        verify(mDppMetrics).updateDppEnrolleeResponderSuccess();
+        verify(mDppMetrics).updateDppOperationTime(anyInt());
+        verifyNoMoreInteractions(mDppMetrics);
+        verifyCleanUpResources(DppManager.DPP_AUTH_ROLE_RESPONDER);
+        assertFalse(mDppManager.isSessionInProgress());
+    }
+
+    @Test
+    public void testStartDppAsEnrolleeResponderStartCorrectlyOnFailureCallback() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        ArgumentCaptor<WifiNative.DppEventCallback> dppEventCallbackCaptor =
+                ArgumentCaptor.forClass(
+                        WifiNative.DppEventCallback.class);
+
+        assertFalse(mDppManager.isSessionInProgress());
+        mDppManager.startDppAsEnrolleeResponder(0, TEST_INTERFACE_NAME, mBinder, mDeviceInfo,
+                EASY_CONNECT_CRYPTOGRAPHY_CURVE_PRIME256V1, mDppCallback);
+        verify(mWifiNative).registerDppEventCallback(dppEventCallbackCaptor.capture());
+        verify(mDppCallback, never()).onFailure(anyInt(), anyString(), anyString(), any());
+        verify(mDppCallback, never()).onSuccess(anyInt());
+        verify(mDppCallback, never()).onSuccessConfigReceived(anyInt());
+        verify(mWifiNative).startDppEnrolleeResponder(eq(TEST_INTERFACE_NAME),
+                eq(TEST_LISTEN_CHANNEL));
+        assertTrue(mDppManager.isSessionInProgress());
+
+        WifiNative.DppEventCallback dppEventCallback = dppEventCallbackCaptor.getValue();
+
+        // Generate a progress event
+        dppEventCallback.onProgress(RESPONSE_PENDING);
+        mLooper.dispatchAll();
+        verify(mDppCallback).onProgress(eq(EASY_CONNECT_EVENT_PROGRESS_RESPONSE_PENDING));
+
+        // Generate an onFailure callback
+        dppEventCallback.onFailure(AUTHENTICATION, null, null, null);
+        mLooper.dispatchAll();
+        verify(mDppCallback).onFailure(eq(EASY_CONNECT_EVENT_FAILURE_AUTHENTICATION),
+                eq(null), eq(null), eq(new int[0]));
+        verify(mDppCallback, never()).onSuccess(anyInt());
+        verify(mDppCallback, never()).onSuccessConfigReceived(anyInt());
+        verify(mDppMetrics).updateDppEnrolleeResponderRequests();
+        verify(mDppMetrics).updateDppFailure(eq(EasyConnectStatusCallback
+                .EASY_CONNECT_EVENT_FAILURE_AUTHENTICATION));
+        verify(mDppMetrics).updateDppOperationTime(anyInt());
+        verifyNoMoreInteractions(mDppMetrics);
+        verifyCleanUpResources(DppManager.DPP_AUTH_ROLE_RESPONDER);
+        assertFalse(mDppManager.isSessionInProgress());
+    }
+
+    @Test
+    public void testDppStopSessionInResponderMode() throws Exception {
+        // Check that nothing happens if UID is incorrect
+        assumeTrue(SdkLevel.isAtLeastS());
+        ArgumentCaptor<WifiNative.DppEventCallback> dppEventCallbackCaptor =
+                ArgumentCaptor.forClass(
+                        WifiNative.DppEventCallback.class);
+
+        assertFalse(mDppManager.isSessionInProgress());
+        // Start with UID 10
+        mDppManager.startDppAsEnrolleeResponder(10, TEST_INTERFACE_NAME, mBinder, mDeviceInfo,
+                EASY_CONNECT_CRYPTOGRAPHY_CURVE_PRIME256V1, mDppCallback);
+        verify(mWifiNative).registerDppEventCallback(dppEventCallbackCaptor.capture());
+        verify(mDppCallback, never()).onFailure(anyInt(), anyString(), anyString(), any());
+        verify(mDppCallback, never()).onSuccess(anyInt());
+        verify(mDppCallback, never()).onSuccessConfigReceived(anyInt());
+        verify(mWifiNative).startDppEnrolleeResponder(eq(TEST_INTERFACE_NAME),
+                eq(TEST_LISTEN_CHANNEL));
+        assertTrue(mDppManager.isSessionInProgress());
+
+        // Check that WifiNative is called to stop the DPP session
+        mDppManager.stopDppSession(10);
+        verify(mWifiNative).stopDppResponder(eq(TEST_INTERFACE_NAME), eq(TEST_BOOTSTRAP_ID));
+        verify(mDppMetrics).updateDppEnrolleeResponderRequests();
+        verifyNoMoreInteractions(mDppMetrics);
+        verifyCleanUpResources(DppManager.DPP_AUTH_ROLE_RESPONDER);
+        assertFalse(mDppManager.isSessionInProgress());
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/DppMetricsTest.java b/service/tests/wifitests/src/com/android/server/wifi/DppMetricsTest.java
index e43da8f..d0e4cbc 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/DppMetricsTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/DppMetricsTest.java
@@ -21,6 +21,7 @@
 import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK;
 import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_CONFIGURATION;
 import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION;
+import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_ENROLLEE_FAILED_TO_SCAN_NETWORK_CHANNEL;
 import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION;
 import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_GENERIC;
 import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK;
@@ -28,6 +29,7 @@
 import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_NOT_COMPATIBLE;
 import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_NOT_SUPPORTED;
 import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_TIMEOUT;
+import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_URI_GENERATION;
 import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_APPLIED;
 import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT;
 
@@ -35,9 +37,11 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.proto.nano.WifiMetricsProto;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.HistogramBucketInt32;
 
@@ -177,6 +181,26 @@
     }
 
     /**
+     * Test numDppEnrolleeResponderRequests
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testUpdateDppEnrolleeResponderRequests() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        // Get a random value and call the update method 'value' times
+        int value = getNumOfTimes(MAX_ITERATIONS) + 1;
+
+        for (int i = 0; i < value; i++) {
+            mDppMetrics.updateDppEnrolleeResponderRequests();
+        }
+
+        // Confirm that the consolidated log has the expected value
+        WifiMetricsProto.WifiDppLog mWifiDppLogProto = mDppMetrics.consolidateProto();
+        assertEquals(mWifiDppLogProto.numDppEnrolleeResponderRequests, value);
+    }
+
+    /**
      * Test EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT
      *
      * @throws Exception
@@ -208,6 +232,7 @@
      */
     @Test
     public void testUpdateDppEnrolleeSuccess() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
         // Get a random value and call the update method 'value' times
         int value = getNumOfTimes(MAX_ITERATIONS) + 1;
 
@@ -221,6 +246,25 @@
     }
 
     /**
+     * Test numDppEnrolleeResponderSuccess
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testUpdateDppEnrolleeResponderSuccess() throws Exception {
+        // Get a random value and call the update method 'value' times
+        int value = getNumOfTimes(MAX_ITERATIONS) + 1;
+
+        for (int i = 0; i < value; i++) {
+            mDppMetrics.updateDppEnrolleeResponderSuccess();
+        }
+
+        // Confirm that the consolidated log has the expected value
+        WifiMetricsProto.WifiDppLog mWifiDppLogProto = mDppMetrics.consolidateProto();
+        assertEquals(mWifiDppLogProto.numDppEnrolleeResponderSuccess, value);
+    }
+
+    /**
      * Test EASY_CONNECT_EVENT_FAILURE_INVALID_URI
      *
      * @throws Exception
@@ -447,6 +491,48 @@
     }
 
     /**
+     * Test EASY_CONNECT_EVENT_FAILURE_URI_GENERATION
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testUpdateDppFailureUriGeneration() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        // Get a random value and call the update method 'value' times
+        int value = getNumOfTimes(MAX_ITERATIONS) + 1;
+
+        for (int i = 0; i < value; i++) {
+            mDppMetrics.updateDppFailure(EASY_CONNECT_EVENT_FAILURE_URI_GENERATION);
+        }
+
+        // Confirm that the consolidated log has the expected value
+        checkDppFailures(WifiMetricsProto.WifiDppLog.EASY_CONNECT_EVENT_FAILURE_URI_GENERATION,
+                value);
+    }
+
+    /**
+     * Test EASY_CONNECT_EVENT_FAILURE_ENROLLEE_FAILED_TO_SCAN_NETWORK_CHANNEL
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testUpdateDppFailureEnrolleeFailedToScanNetworkChannel() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        // Get a random value and call the update method 'value' times
+        int value = getNumOfTimes(MAX_ITERATIONS) + 1;
+
+        for (int i = 0; i < value; i++) {
+            mDppMetrics.updateDppFailure(
+                    EASY_CONNECT_EVENT_FAILURE_ENROLLEE_FAILED_TO_SCAN_NETWORK_CHANNEL);
+        }
+
+        // Confirm that the consolidated log has the expected value
+        checkDppFailures(WifiMetricsProto
+                .WifiDppLog.EASY_CONNECT_EVENT_FAILURE_ENROLLEE_FAILED_TO_SCAN_NETWORK_CHANNEL,
+                value);
+    }
+
+    /**
      * Test DPP operation time histogram. Pick a single time value from each bucket by selecting
      * the max value minus 1, and call the update method random amount of times with this value.
      * Then confirm that the output histogram has the expected value in each target bucket.
diff --git a/service/tests/wifitests/src/com/android/server/wifi/EapFailureNotifierTest.java b/service/tests/wifitests/src/com/android/server/wifi/EapFailureNotifierTest.java
index 2639133..62ed458 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/EapFailureNotifierTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/EapFailureNotifierTest.java
@@ -18,20 +18,14 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.*;
 
-import android.app.ActivityManager;
 import android.app.Notification;
-import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.net.wifi.WifiConfiguration;
-import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
 import android.telephony.SubscriptionManager;
@@ -47,19 +41,17 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.MockitoSession;
 
-import java.util.Arrays;
-
 /**
  * Unit tests for {@link com.android.server.wifi.EapFailureNotifier}.
  */
 @SmallTest
 public class EapFailureNotifierTest extends WifiBaseTest {
-    private static final String TEST_SETTINGS_PACKAGE = "android";
+    private static final String TEST_SETTINGS_PACKAGE = "test.com.android.settings";
+    private static final String NOTIFICATION_TAG = "com.android.wifi";
 
     @Mock WifiContext mContext;
     @Mock Resources mResources;
-    @Mock PackageManager mPackageManager;
-    @Mock NotificationManager mNotificationManager;
+    @Mock WifiNotificationManager mWifiNotificationManager;
     @Mock FrameworkFacade mFrameworkFacade;
     @Mock Notification mNotification;
     @Mock
@@ -75,7 +67,6 @@
     private static final String UNDEFINED_ERROR_RESOURCE_NAME = "wifi_eap_error_message_code_12345";
     private MockitoSession mStaticMockSession = null;
 
-
     EapFailureNotifier mEapFailureNotifier;
 
     /**
@@ -84,26 +75,14 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        ResolveInfo settingsResolveInfo = new ResolveInfo();
-        settingsResolveInfo.activityInfo = new ActivityInfo();
-        settingsResolveInfo.activityInfo.packageName = TEST_SETTINGS_PACKAGE;
-        when(mPackageManager.queryIntentActivitiesAsUser(
-                argThat(((intent) -> intent.getAction().equals(Settings.ACTION_WIFI_SETTINGS))),
-                anyInt(), any()))
-                .thenReturn(Arrays.asList(settingsResolveInfo));
         // static mocking
         mStaticMockSession = mockitoSession()
             .mockStatic(SubscriptionManager.class)
-            .mockStatic(ActivityManager.class, withSettings().lenient())
             .startMocking();
-        when(ActivityManager.getCurrentUser()).thenReturn(UserHandle.USER_SYSTEM);
-        when(mContext.getSystemService(NotificationManager.class))
-                .thenReturn(mNotificationManager);
         when(mWifiCarrierInfoManager.getBestMatchSubscriptionId(mWifiConfiguration)).thenReturn(0);
         lenient().when(SubscriptionManager.getResourcesForSubId(eq(mContext), anyInt()))
                 .thenReturn(mResources);
         when(mContext.getResources()).thenReturn(mResources);
-        when(mContext.getPackageManager()).thenReturn(mPackageManager);
         when(mResources.getIdentifier(eq(DEFINED_ERROR_RESOURCE_NAME), anyString(),
                 anyString())).thenReturn(1);
         when(mResources.getIdentifier(eq(UNDEFINED_ERROR_RESOURCE_NAME), anyString(),
@@ -111,10 +90,11 @@
         when(mResources.getString(eq(0), anyString())).thenReturn(null);
         when(mResources.getString(eq(1), anyString())).thenReturn("Error Message");
         when(mContext.createPackageContext(anyString(), eq(0))).thenReturn(mContext);
-        when(mContext.getWifiOverlayApkPkgName()).thenReturn("test.com.oem.android.wifi.resources");
-        when(mContext.getWifiOverlayJavaPkgName()).thenReturn("test.com.android.wifi.resources");
+        when(mContext.getWifiOverlayApkPkgName()).thenReturn("test.com.android.wifi.resources");
+        when(mFrameworkFacade.getSettingsPackageName(any())).thenReturn(TEST_SETTINGS_PACKAGE);
         mEapFailureNotifier =
-                new EapFailureNotifier(mContext, mFrameworkFacade, mWifiCarrierInfoManager);
+                new EapFailureNotifier(mContext, mFrameworkFacade, mWifiCarrierInfoManager,
+                        mWifiNotificationManager);
     }
 
     @After
@@ -132,24 +112,44 @@
      * @throws Exception
      */
     @Test
-    public void onEapFailureWithDefinedErroCodeWithoutNotificationShown() throws Exception {
+    public void onEapFailureWithDefinedErrorCodeWithoutNotificationShown() throws Exception {
         when(mFrameworkFacade.makeNotificationBuilder(any(),
                 eq(WifiService.NOTIFICATION_NETWORK_ALERTS))).thenReturn(mNotificationBuilder);
         StatusBarNotification[] activeNotifications = new StatusBarNotification[1];
         activeNotifications[0] = new StatusBarNotification("android", "", 56, "", 0, 0, 0,
                 mNotification, android.os.Process.myUserHandle(), 0);
-        when(mNotificationManager.getActiveNotifications()).thenReturn(activeNotifications);
+        when(mWifiNotificationManager.getActiveNotifications()).thenReturn(activeNotifications);
         mWifiConfiguration.SSID = SSID_2;
-        mEapFailureNotifier.onEapFailure(DEFINED_ERROR_CODE, mWifiConfiguration);
-        verify(mNotificationManager).notify(eq(EapFailureNotifier.NOTIFICATION_ID), any());
+        mEapFailureNotifier.onEapFailure(DEFINED_ERROR_CODE, mWifiConfiguration, true);
+        verify(mWifiNotificationManager).notify(eq(EapFailureNotifier.NOTIFICATION_ID), any());
         ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
         verify(mFrameworkFacade).getActivity(
-                eq(mContext), eq(0), intent.capture(), eq(PendingIntent.FLAG_UPDATE_CURRENT));
+                eq(mContext), eq(0), intent.capture(),
+                eq(PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE));
         assertEquals(TEST_SETTINGS_PACKAGE, intent.getValue().getPackage());
         assertEquals(Settings.ACTION_WIFI_SETTINGS, intent.getValue().getAction());
     }
 
     /**
+     * Verify that a eap failure notification will not be generated with the following conditions :
+     * No eap failure notification of eap failure is displayed now.
+     * Error code is defined by carrier
+     * showNotification = false
+     */
+    @Test
+    public void onEapFailure_showNotificationFalse_notShown() throws Exception {
+        when(mFrameworkFacade.makeNotificationBuilder(any(),
+                eq(WifiService.NOTIFICATION_NETWORK_ALERTS))).thenReturn(mNotificationBuilder);
+        StatusBarNotification[] activeNotifications = new StatusBarNotification[1];
+        activeNotifications[0] = new StatusBarNotification("android", "", 56, "", 0, 0, 0,
+                mNotification, android.os.Process.myUserHandle(), 0);
+        when(mWifiNotificationManager.getActiveNotifications()).thenReturn(activeNotifications);
+        mWifiConfiguration.SSID = SSID_2;
+        mEapFailureNotifier.onEapFailure(DEFINED_ERROR_CODE, mWifiConfiguration, false);
+        verify(mWifiNotificationManager, never()).notify(anyInt(), any());
+    }
+
+    /**
      * Verify that a eap failure notification will be generated with the following conditions :
      * Previous notification of eap failure is still displayed on the notification bar.
      * Ssid of previous notification is not same as current ssid
@@ -164,14 +164,15 @@
         StatusBarNotification[] activeNotifications = new StatusBarNotification[1];
         activeNotifications[0] = new StatusBarNotification("android", "", 57, "", 0, 0, 0,
                 mNotification, android.os.Process.myUserHandle(), 0);
-        when(mNotificationManager.getActiveNotifications()).thenReturn(activeNotifications);
+        when(mWifiNotificationManager.getActiveNotifications()).thenReturn(activeNotifications);
         mEapFailureNotifier.setCurrentShownSsid(SSID_1);
         mWifiConfiguration.SSID = SSID_2;
-        mEapFailureNotifier.onEapFailure(DEFINED_ERROR_CODE, mWifiConfiguration);
-        verify(mNotificationManager).notify(eq(EapFailureNotifier.NOTIFICATION_ID), any());
+        mEapFailureNotifier.onEapFailure(DEFINED_ERROR_CODE, mWifiConfiguration, true);
+        verify(mWifiNotificationManager).notify(eq(EapFailureNotifier.NOTIFICATION_ID), any());
         ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
         verify(mFrameworkFacade).getActivity(
-                eq(mContext), eq(0), intent.capture(), eq(PendingIntent.FLAG_UPDATE_CURRENT));
+                eq(mContext), eq(0), intent.capture(),
+                eq(PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE));
         assertEquals(TEST_SETTINGS_PACKAGE, intent.getValue().getPackage());
         assertEquals(Settings.ACTION_WIFI_SETTINGS, intent.getValue().getAction());
     }
@@ -191,10 +192,10 @@
         StatusBarNotification[] activeNotifications = new StatusBarNotification[1];
         activeNotifications[0] = new StatusBarNotification("android", "", 57, "", 0, 0, 0,
                 mNotification, android.os.Process.myUserHandle(), 0);
-        when(mNotificationManager.getActiveNotifications()).thenReturn(activeNotifications);
+        when(mWifiNotificationManager.getActiveNotifications()).thenReturn(activeNotifications);
         mEapFailureNotifier.setCurrentShownSsid(SSID_1);
         mWifiConfiguration.SSID = SSID_1;
-        mEapFailureNotifier.onEapFailure(DEFINED_ERROR_CODE, mWifiConfiguration);
+        mEapFailureNotifier.onEapFailure(DEFINED_ERROR_CODE, mWifiConfiguration, true);
         verify(mFrameworkFacade, never()).makeNotificationBuilder(any(),
                 eq(WifiService.NOTIFICATION_NETWORK_ALERTS));
     }
@@ -211,9 +212,9 @@
         StatusBarNotification[] activeNotifications = new StatusBarNotification[1];
         activeNotifications[0] = new StatusBarNotification("android", "", 56, "", 0, 0, 0,
                 mNotification, android.os.Process.myUserHandle(), 0);
-        when(mNotificationManager.getActiveNotifications()).thenReturn(activeNotifications);
+        when(mWifiNotificationManager.getActiveNotifications()).thenReturn(activeNotifications);
         mWifiConfiguration.SSID = SSID_1;
-        mEapFailureNotifier.onEapFailure(UNDEFINED_ERROR_CODE, mWifiConfiguration);
+        mEapFailureNotifier.onEapFailure(UNDEFINED_ERROR_CODE, mWifiConfiguration, true);
         verify(mFrameworkFacade, never()).makeNotificationBuilder(any(),
                 eq(WifiService.NOTIFICATION_NETWORK_ALERTS));
     }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/ExternalScoreUpdateObserverProxyTest.java b/service/tests/wifitests/src/com/android/server/wifi/ExternalScoreUpdateObserverProxyTest.java
new file mode 100644
index 0000000..de6ff47
--- /dev/null
+++ b/service/tests/wifitests/src/com/android/server/wifi/ExternalScoreUpdateObserverProxyTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.test.TestLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.modules.utils.build.SdkLevel;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.ExternalScoreUpdateObserverProxy}.
+ */
+@SmallTest
+public class ExternalScoreUpdateObserverProxyTest extends WifiBaseTest {
+    private static final int TEST_SESSION_ID = 7;
+    private static final int TEST_SCORE = 7;
+    private static final boolean TEST_STATUS = true;
+
+    TestLooper mLooper = new TestLooper();
+    @Mock WifiManager.ScoreUpdateObserver mCallback;
+    ExternalScoreUpdateObserverProxy mExternalScoreUpdateObserverProxy;
+
+    /**
+     * Sets up for unit test
+     */
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mExternalScoreUpdateObserverProxy = new ExternalScoreUpdateObserverProxy(
+                new WifiThreadRunner(new Handler(mLooper.getLooper())));
+    }
+
+    @Test
+    public void testCallback() throws Exception {
+        mExternalScoreUpdateObserverProxy.registerCallback(mCallback);
+
+        mExternalScoreUpdateObserverProxy.notifyScoreUpdate(TEST_SESSION_ID, TEST_SCORE);
+        mLooper.dispatchAll();
+        verify(mCallback).notifyScoreUpdate(TEST_SESSION_ID, TEST_SCORE);
+
+        mExternalScoreUpdateObserverProxy.triggerUpdateOfWifiUsabilityStats(TEST_SESSION_ID);
+        mLooper.dispatchAll();
+        verify(mCallback).triggerUpdateOfWifiUsabilityStats(TEST_SESSION_ID);
+
+        // Unregister the callback
+        mExternalScoreUpdateObserverProxy.unregisterCallback(mCallback);
+
+        mExternalScoreUpdateObserverProxy.notifyScoreUpdate(TEST_SESSION_ID, TEST_SCORE);
+        mExternalScoreUpdateObserverProxy.triggerUpdateOfWifiUsabilityStats(TEST_SESSION_ID);
+        mLooper.dispatchAll();
+
+        verifyNoMoreInteractions(mCallback);
+    }
+
+    @Test
+    public void testCallbackForApiInS() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        mExternalScoreUpdateObserverProxy.registerCallback(mCallback);
+
+        mExternalScoreUpdateObserverProxy.notifyStatusUpdate(TEST_SESSION_ID, TEST_STATUS);
+        mLooper.dispatchAll();
+        verify(mCallback).notifyStatusUpdate(TEST_SESSION_ID, TEST_STATUS);
+
+        mExternalScoreUpdateObserverProxy.requestNudOperation(TEST_SESSION_ID);
+        mLooper.dispatchAll();
+        verify(mCallback).requestNudOperation(TEST_SESSION_ID);
+
+        mExternalScoreUpdateObserverProxy.blocklistCurrentBssid(TEST_SESSION_ID);
+        mLooper.dispatchAll();
+        verify(mCallback).blocklistCurrentBssid(TEST_SESSION_ID);
+
+        // Unregister the callback
+        mExternalScoreUpdateObserverProxy.unregisterCallback(mCallback);
+
+        mExternalScoreUpdateObserverProxy.notifyStatusUpdate(TEST_SESSION_ID, TEST_STATUS);
+        mExternalScoreUpdateObserverProxy.requestNudOperation(TEST_SESSION_ID);
+        mExternalScoreUpdateObserverProxy.blocklistCurrentBssid(TEST_SESSION_ID);
+        mLooper.dispatchAll();
+
+        verifyNoMoreInteractions(mCallback);
+    }
+}
diff --git a/service/tests/wifitests/src/com/android/server/wifi/FakeKeys.java b/service/tests/wifitests/src/com/android/server/wifi/FakeKeys.java
index 5145b13..00876b7 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/FakeKeys.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/FakeKeys.java
@@ -581,6 +581,242 @@
     public static final PrivateKey CLIENT_SUITE_B_RSA3072_KEY =
             loadPrivateKey("RSA", CLIENT_SUITE_B_RSA3072_KEY_DATA);
 
+    private static final String CLIENT_BAD_SUITE_B_RSA2048_CERT_STRING =
+            "-----BEGIN CERTIFICATE-----\n"
+                    + "MIIDYjCCAkoCFFZktoDELelsicIU2YhwSzH4S7RaMA0GCSqGSIb3DQEBDAUAMGUx\n"
+                    + "CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoGA1UEBwwDTVRWMRAwDgYDVQQK\n"
+                    + "DAdBbmRyb2lkMQ4wDAYDVQQLDAVXaS1GaTEZMBcGA1UEAwwQd2lmaS5hbmRyb2lk\n"
+                    + "LmNvbTAeFw0yMDA5MDEyMjA1MzVaFw0zMDA3MTEyMjA1MzVaMHYxCzAJBgNVBAYT\n"
+                    + "AlVTMQswCQYDVQQIDAJDQTEMMAoGA1UEBwwDTVRWMRAwDgYDVQQKDAdBbmRyb2lk\n"
+                    + "MRcwFQYDVQQLDA5XaS1GaSAyMDQ4IFJTQTEhMB8GA1UEAwwYcnNhMjA0OC53aWZp\n"
+                    + "LmFuZHJvaWQuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuOxv\n"
+                    + "qWnzBmS0SWXnNrpSKS/ixRfpRwOcji/RDTMgjMPb0pCpQfIydCxEwClcPckcdn7Z\n"
+                    + "EdFK69Z3if+zZNpixtdAjDpQcGGLVvvN+EwbVLFT15Km/DiwWdxgaprrBU0Ar4rN\n"
+                    + "2YihZwOPxgrVbCunXJIklq2I4gY5rolQtX1c3zU7MNvjEI5xsefBPr9mCN24dmVf\n"
+                    + "3tP24GPmizLy6gaeHwiEAcmLEZl5dP1EFN2me6frkUSIpQgnV5A1wnvnU7EF6Gd0\n"
+                    + "7Hwexs54h+fyHx1tPGXnt1Y/0vFC6dYQW0YUp7Zg6BkuIqlttWkw7HZMNziwp7eq\n"
+                    + "C182n3gkvim6wFiOlwIDAQABMA0GCSqGSIb3DQEBDAUAA4IBAQBnz8KoMn5SDwJi\n"
+                    + "wWcHhm4BZcAtTOjpU5mKABtcTLdmt9MvHVfWr5jXg4i4gJJP/JwHewC6bTUojQiS\n"
+                    + "AjPWwrIKtqmB0f2o8GiIoXSJYESCwnJTrxD3GwFDTASO+rGeP6kpnEXlsN5w5pVi\n"
+                    + "lYs0yLAJBOCfINMDm0yNgn5U4nyhnBp/9NUaQrKTZWvobeP+GUIQlhmOsFztoYZ8\n"
+                    + "UBzTzqzE6e73Kqt8b3XmGG8O0Sh/wFGPYp0A0hgN2Q6OQO/erSMVBk1JF8K8AoLP\n"
+                    + "swBM34V0yn5c+axqyfUr09r9WhbT2By5i6VgQ1NseGfz+TuX+CSCqXkT4H17Q4iP\n"
+                    + "V4Rb5Ji6\n"
+                    + "-----END CERTIFICATE-----\n";
+
+    public static final X509Certificate CLIENT_BAD_SUITE_B_RSA2048_CERT =
+            loadCertificate(CLIENT_BAD_SUITE_B_RSA2048_CERT_STRING);
+
+    private static final byte[] CLIENT_BAD_SUITE_B_RSA2048_KEY_DATA = new byte[]{
+            (byte) 0x30, (byte) 0x82, (byte) 0x04, (byte) 0xbe, (byte) 0x02, (byte) 0x01,
+            (byte) 0x00, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a,
+            (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01,
+            (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x04, (byte) 0x82,
+            (byte) 0x04, (byte) 0xa8, (byte) 0x30, (byte) 0x82, (byte) 0x04, (byte) 0xa4,
+            (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x02, (byte) 0x82, (byte) 0x01,
+            (byte) 0x01, (byte) 0x00, (byte) 0xb8, (byte) 0xec, (byte) 0x6f, (byte) 0xa9,
+            (byte) 0x69, (byte) 0xf3, (byte) 0x06, (byte) 0x64, (byte) 0xb4, (byte) 0x49,
+            (byte) 0x65, (byte) 0xe7, (byte) 0x36, (byte) 0xba, (byte) 0x52, (byte) 0x29,
+            (byte) 0x2f, (byte) 0xe2, (byte) 0xc5, (byte) 0x17, (byte) 0xe9, (byte) 0x47,
+            (byte) 0x03, (byte) 0x9c, (byte) 0x8e, (byte) 0x2f, (byte) 0xd1, (byte) 0x0d,
+            (byte) 0x33, (byte) 0x20, (byte) 0x8c, (byte) 0xc3, (byte) 0xdb, (byte) 0xd2,
+            (byte) 0x90, (byte) 0xa9, (byte) 0x41, (byte) 0xf2, (byte) 0x32, (byte) 0x74,
+            (byte) 0x2c, (byte) 0x44, (byte) 0xc0, (byte) 0x29, (byte) 0x5c, (byte) 0x3d,
+            (byte) 0xc9, (byte) 0x1c, (byte) 0x76, (byte) 0x7e, (byte) 0xd9, (byte) 0x11,
+            (byte) 0xd1, (byte) 0x4a, (byte) 0xeb, (byte) 0xd6, (byte) 0x77, (byte) 0x89,
+            (byte) 0xff, (byte) 0xb3, (byte) 0x64, (byte) 0xda, (byte) 0x62, (byte) 0xc6,
+            (byte) 0xd7, (byte) 0x40, (byte) 0x8c, (byte) 0x3a, (byte) 0x50, (byte) 0x70,
+            (byte) 0x61, (byte) 0x8b, (byte) 0x56, (byte) 0xfb, (byte) 0xcd, (byte) 0xf8,
+            (byte) 0x4c, (byte) 0x1b, (byte) 0x54, (byte) 0xb1, (byte) 0x53, (byte) 0xd7,
+            (byte) 0x92, (byte) 0xa6, (byte) 0xfc, (byte) 0x38, (byte) 0xb0, (byte) 0x59,
+            (byte) 0xdc, (byte) 0x60, (byte) 0x6a, (byte) 0x9a, (byte) 0xeb, (byte) 0x05,
+            (byte) 0x4d, (byte) 0x00, (byte) 0xaf, (byte) 0x8a, (byte) 0xcd, (byte) 0xd9,
+            (byte) 0x88, (byte) 0xa1, (byte) 0x67, (byte) 0x03, (byte) 0x8f, (byte) 0xc6,
+            (byte) 0x0a, (byte) 0xd5, (byte) 0x6c, (byte) 0x2b, (byte) 0xa7, (byte) 0x5c,
+            (byte) 0x92, (byte) 0x24, (byte) 0x96, (byte) 0xad, (byte) 0x88, (byte) 0xe2,
+            (byte) 0x06, (byte) 0x39, (byte) 0xae, (byte) 0x89, (byte) 0x50, (byte) 0xb5,
+            (byte) 0x7d, (byte) 0x5c, (byte) 0xdf, (byte) 0x35, (byte) 0x3b, (byte) 0x30,
+            (byte) 0xdb, (byte) 0xe3, (byte) 0x10, (byte) 0x8e, (byte) 0x71, (byte) 0xb1,
+            (byte) 0xe7, (byte) 0xc1, (byte) 0x3e, (byte) 0xbf, (byte) 0x66, (byte) 0x08,
+            (byte) 0xdd, (byte) 0xb8, (byte) 0x76, (byte) 0x65, (byte) 0x5f, (byte) 0xde,
+            (byte) 0xd3, (byte) 0xf6, (byte) 0xe0, (byte) 0x63, (byte) 0xe6, (byte) 0x8b,
+            (byte) 0x32, (byte) 0xf2, (byte) 0xea, (byte) 0x06, (byte) 0x9e, (byte) 0x1f,
+            (byte) 0x08, (byte) 0x84, (byte) 0x01, (byte) 0xc9, (byte) 0x8b, (byte) 0x11,
+            (byte) 0x99, (byte) 0x79, (byte) 0x74, (byte) 0xfd, (byte) 0x44, (byte) 0x14,
+            (byte) 0xdd, (byte) 0xa6, (byte) 0x7b, (byte) 0xa7, (byte) 0xeb, (byte) 0x91,
+            (byte) 0x44, (byte) 0x88, (byte) 0xa5, (byte) 0x08, (byte) 0x27, (byte) 0x57,
+            (byte) 0x90, (byte) 0x35, (byte) 0xc2, (byte) 0x7b, (byte) 0xe7, (byte) 0x53,
+            (byte) 0xb1, (byte) 0x05, (byte) 0xe8, (byte) 0x67, (byte) 0x74, (byte) 0xec,
+            (byte) 0x7c, (byte) 0x1e, (byte) 0xc6, (byte) 0xce, (byte) 0x78, (byte) 0x87,
+            (byte) 0xe7, (byte) 0xf2, (byte) 0x1f, (byte) 0x1d, (byte) 0x6d, (byte) 0x3c,
+            (byte) 0x65, (byte) 0xe7, (byte) 0xb7, (byte) 0x56, (byte) 0x3f, (byte) 0xd2,
+            (byte) 0xf1, (byte) 0x42, (byte) 0xe9, (byte) 0xd6, (byte) 0x10, (byte) 0x5b,
+            (byte) 0x46, (byte) 0x14, (byte) 0xa7, (byte) 0xb6, (byte) 0x60, (byte) 0xe8,
+            (byte) 0x19, (byte) 0x2e, (byte) 0x22, (byte) 0xa9, (byte) 0x6d, (byte) 0xb5,
+            (byte) 0x69, (byte) 0x30, (byte) 0xec, (byte) 0x76, (byte) 0x4c, (byte) 0x37,
+            (byte) 0x38, (byte) 0xb0, (byte) 0xa7, (byte) 0xb7, (byte) 0xaa, (byte) 0x0b,
+            (byte) 0x5f, (byte) 0x36, (byte) 0x9f, (byte) 0x78, (byte) 0x24, (byte) 0xbe,
+            (byte) 0x29, (byte) 0xba, (byte) 0xc0, (byte) 0x58, (byte) 0x8e, (byte) 0x97,
+            (byte) 0x02, (byte) 0x03, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x02,
+            (byte) 0x82, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x98, (byte) 0x4f,
+            (byte) 0x8d, (byte) 0x8c, (byte) 0xf1, (byte) 0x4a, (byte) 0x0c, (byte) 0xdb,
+            (byte) 0x07, (byte) 0x2f, (byte) 0x69, (byte) 0x32, (byte) 0x6e, (byte) 0x7e,
+            (byte) 0x3a, (byte) 0x4b, (byte) 0xd8, (byte) 0x38, (byte) 0x24, (byte) 0x74,
+            (byte) 0x14, (byte) 0x5c, (byte) 0xbc, (byte) 0x59, (byte) 0xc7, (byte) 0x37,
+            (byte) 0x15, (byte) 0x2d, (byte) 0x95, (byte) 0x0d, (byte) 0xdb, (byte) 0x43,
+            (byte) 0x3a, (byte) 0x9c, (byte) 0x8d, (byte) 0x30, (byte) 0x0b, (byte) 0xb4,
+            (byte) 0x0b, (byte) 0xe0, (byte) 0x69, (byte) 0xd1, (byte) 0xda, (byte) 0xa0,
+            (byte) 0x76, (byte) 0x6c, (byte) 0x21, (byte) 0x68, (byte) 0x43, (byte) 0x25,
+            (byte) 0x29, (byte) 0x6f, (byte) 0x26, (byte) 0x7e, (byte) 0x5a, (byte) 0x0f,
+            (byte) 0x54, (byte) 0x78, (byte) 0x22, (byte) 0x56, (byte) 0xc4, (byte) 0xdb,
+            (byte) 0xa6, (byte) 0xfd, (byte) 0xf5, (byte) 0xaf, (byte) 0x21, (byte) 0x90,
+            (byte) 0xaa, (byte) 0x4e, (byte) 0x55, (byte) 0xd9, (byte) 0x69, (byte) 0xb4,
+            (byte) 0x8e, (byte) 0xaa, (byte) 0x53, (byte) 0x2d, (byte) 0x33, (byte) 0xad,
+            (byte) 0xb6, (byte) 0xcb, (byte) 0xd8, (byte) 0xeb, (byte) 0x96, (byte) 0xd4,
+            (byte) 0xae, (byte) 0x53, (byte) 0xe5, (byte) 0x52, (byte) 0xb8, (byte) 0x9b,
+            (byte) 0x8e, (byte) 0xb5, (byte) 0xd7, (byte) 0xfd, (byte) 0x97, (byte) 0x98,
+            (byte) 0x71, (byte) 0x02, (byte) 0x1a, (byte) 0x2a, (byte) 0x2e, (byte) 0x69,
+            (byte) 0xdd, (byte) 0x92, (byte) 0x95, (byte) 0xbb, (byte) 0xc1, (byte) 0x93,
+            (byte) 0x97, (byte) 0x0e, (byte) 0x85, (byte) 0x04, (byte) 0x83, (byte) 0xe2,
+            (byte) 0xbc, (byte) 0xda, (byte) 0xc5, (byte) 0x9b, (byte) 0xde, (byte) 0xe9,
+            (byte) 0x14, (byte) 0xc3, (byte) 0xd7, (byte) 0x54, (byte) 0x96, (byte) 0x36,
+            (byte) 0x9e, (byte) 0xad, (byte) 0x92, (byte) 0x73, (byte) 0x43, (byte) 0x44,
+            (byte) 0x5d, (byte) 0x8e, (byte) 0x02, (byte) 0x2a, (byte) 0x7a, (byte) 0xc0,
+            (byte) 0xee, (byte) 0x47, (byte) 0xe8, (byte) 0x8d, (byte) 0x97, (byte) 0xdc,
+            (byte) 0xb5, (byte) 0x99, (byte) 0x59, (byte) 0x56, (byte) 0x07, (byte) 0x18,
+            (byte) 0xb0, (byte) 0x29, (byte) 0xd3, (byte) 0x56, (byte) 0x23, (byte) 0xd1,
+            (byte) 0x79, (byte) 0xaa, (byte) 0xe3, (byte) 0x2b, (byte) 0x40, (byte) 0x4b,
+            (byte) 0x13, (byte) 0xf1, (byte) 0x11, (byte) 0x88, (byte) 0xd8, (byte) 0x84,
+            (byte) 0x3e, (byte) 0x86, (byte) 0x3b, (byte) 0xfb, (byte) 0xf7, (byte) 0x3b,
+            (byte) 0xcb, (byte) 0xbf, (byte) 0xff, (byte) 0x13, (byte) 0x1e, (byte) 0x89,
+            (byte) 0xd7, (byte) 0xdd, (byte) 0x4a, (byte) 0x34, (byte) 0xb2, (byte) 0xd9,
+            (byte) 0x25, (byte) 0x80, (byte) 0xc1, (byte) 0x37, (byte) 0x3d, (byte) 0x18,
+            (byte) 0x90, (byte) 0xc1, (byte) 0xdc, (byte) 0x73, (byte) 0x53, (byte) 0xb5,
+            (byte) 0x14, (byte) 0x87, (byte) 0x14, (byte) 0xbc, (byte) 0xc6, (byte) 0x72,
+            (byte) 0x0b, (byte) 0x1f, (byte) 0xef, (byte) 0x39, (byte) 0x87, (byte) 0xb2,
+            (byte) 0x9d, (byte) 0xed, (byte) 0x6b, (byte) 0xdb, (byte) 0xf4, (byte) 0x5b,
+            (byte) 0xff, (byte) 0xd1, (byte) 0x18, (byte) 0x8b, (byte) 0x48, (byte) 0x91,
+            (byte) 0x17, (byte) 0x93, (byte) 0x29, (byte) 0xbd, (byte) 0x6f, (byte) 0x85,
+            (byte) 0x75, (byte) 0x7a, (byte) 0xce, (byte) 0xf1, (byte) 0x7f, (byte) 0x7e,
+            (byte) 0x34, (byte) 0x88, (byte) 0xc4, (byte) 0x5f, (byte) 0xe3, (byte) 0x9d,
+            (byte) 0x36, (byte) 0xb8, (byte) 0xb0, (byte) 0xf6, (byte) 0x27, (byte) 0x4a,
+            (byte) 0x70, (byte) 0xcd, (byte) 0x8e, (byte) 0xcd, (byte) 0x5f, (byte) 0x7d,
+            (byte) 0xfb, (byte) 0xb1, (byte) 0x02, (byte) 0x81, (byte) 0x81, (byte) 0x00,
+            (byte) 0xf0, (byte) 0x62, (byte) 0xb2, (byte) 0x65, (byte) 0x64, (byte) 0x15,
+            (byte) 0x4e, (byte) 0xb5, (byte) 0x47, (byte) 0x2b, (byte) 0x6b, (byte) 0xd8,
+            (byte) 0x94, (byte) 0x59, (byte) 0x3e, (byte) 0x01, (byte) 0x9a, (byte) 0xe9,
+            (byte) 0x2b, (byte) 0xe3, (byte) 0xed, (byte) 0x2d, (byte) 0xb2, (byte) 0x64,
+            (byte) 0xec, (byte) 0x30, (byte) 0x97, (byte) 0xce, (byte) 0xa1, (byte) 0xcb,
+            (byte) 0x64, (byte) 0xe2, (byte) 0x1d, (byte) 0x77, (byte) 0x82, (byte) 0xf7,
+            (byte) 0xa0, (byte) 0xd5, (byte) 0x91, (byte) 0x3d, (byte) 0x34, (byte) 0x66,
+            (byte) 0x08, (byte) 0xe5, (byte) 0x66, (byte) 0xe2, (byte) 0xad, (byte) 0x80,
+            (byte) 0xf0, (byte) 0x6a, (byte) 0x6b, (byte) 0xd0, (byte) 0x6b, (byte) 0x19,
+            (byte) 0x55, (byte) 0x83, (byte) 0xaa, (byte) 0x47, (byte) 0x8c, (byte) 0x35,
+            (byte) 0x89, (byte) 0x7a, (byte) 0x48, (byte) 0x07, (byte) 0x54, (byte) 0xb3,
+            (byte) 0xf4, (byte) 0x61, (byte) 0x85, (byte) 0x7c, (byte) 0x25, (byte) 0x39,
+            (byte) 0x64, (byte) 0xca, (byte) 0xdb, (byte) 0xff, (byte) 0x66, (byte) 0x20,
+            (byte) 0xfc, (byte) 0x85, (byte) 0x80, (byte) 0x72, (byte) 0x5e, (byte) 0x15,
+            (byte) 0x9c, (byte) 0x09, (byte) 0xec, (byte) 0x6b, (byte) 0x71, (byte) 0x52,
+            (byte) 0x8c, (byte) 0x53, (byte) 0x85, (byte) 0x90, (byte) 0x74, (byte) 0x28,
+            (byte) 0xbc, (byte) 0x83, (byte) 0x1b, (byte) 0xc4, (byte) 0xd1, (byte) 0x8c,
+            (byte) 0x09, (byte) 0xa7, (byte) 0x43, (byte) 0x74, (byte) 0x0c, (byte) 0xc7,
+            (byte) 0x55, (byte) 0x6f, (byte) 0x09, (byte) 0x7d, (byte) 0xac, (byte) 0xda,
+            (byte) 0xb8, (byte) 0x2e, (byte) 0x37, (byte) 0xaf, (byte) 0x0b, (byte) 0xd4,
+            (byte) 0xc6, (byte) 0xe4, (byte) 0x0a, (byte) 0xa4, (byte) 0x09, (byte) 0x21,
+            (byte) 0xce, (byte) 0xcf, (byte) 0x02, (byte) 0x81, (byte) 0x81, (byte) 0x00,
+            (byte) 0xc4, (byte) 0xef, (byte) 0x7a, (byte) 0x66, (byte) 0x73, (byte) 0x5c,
+            (byte) 0x29, (byte) 0x0a, (byte) 0x94, (byte) 0xcf, (byte) 0xd4, (byte) 0xad,
+            (byte) 0x9b, (byte) 0xef, (byte) 0x30, (byte) 0x85, (byte) 0xa1, (byte) 0xd1,
+            (byte) 0x92, (byte) 0x77, (byte) 0x61, (byte) 0x64, (byte) 0xe5, (byte) 0x49,
+            (byte) 0xed, (byte) 0xb3, (byte) 0xf9, (byte) 0x11, (byte) 0x9c, (byte) 0x1f,
+            (byte) 0xef, (byte) 0xeb, (byte) 0xad, (byte) 0xaf, (byte) 0x10, (byte) 0x74,
+            (byte) 0x27, (byte) 0x28, (byte) 0x34, (byte) 0xb6, (byte) 0x33, (byte) 0xf1,
+            (byte) 0x1c, (byte) 0xca, (byte) 0xeb, (byte) 0x30, (byte) 0x8c, (byte) 0x07,
+            (byte) 0x0c, (byte) 0x9f, (byte) 0xd9, (byte) 0x86, (byte) 0x7b, (byte) 0xb0,
+            (byte) 0x62, (byte) 0xe7, (byte) 0x1c, (byte) 0x2b, (byte) 0xe8, (byte) 0x75,
+            (byte) 0x92, (byte) 0x97, (byte) 0x8e, (byte) 0x51, (byte) 0x90, (byte) 0xe3,
+            (byte) 0xc7, (byte) 0x10, (byte) 0x23, (byte) 0x93, (byte) 0x3b, (byte) 0x33,
+            (byte) 0xc4, (byte) 0x83, (byte) 0x47, (byte) 0xdd, (byte) 0xdb, (byte) 0xee,
+            (byte) 0x29, (byte) 0x93, (byte) 0x65, (byte) 0x7b, (byte) 0x76, (byte) 0x52,
+            (byte) 0xe7, (byte) 0xbf, (byte) 0x0d, (byte) 0x0f, (byte) 0x0d, (byte) 0x85,
+            (byte) 0x6f, (byte) 0x87, (byte) 0x13, (byte) 0x11, (byte) 0x9c, (byte) 0x4a,
+            (byte) 0x9f, (byte) 0xc0, (byte) 0x13, (byte) 0x11, (byte) 0x64, (byte) 0x90,
+            (byte) 0x8c, (byte) 0xdf, (byte) 0x98, (byte) 0xd0, (byte) 0xae, (byte) 0x74,
+            (byte) 0xf2, (byte) 0xa4, (byte) 0x4e, (byte) 0xa1, (byte) 0x3f, (byte) 0xf5,
+            (byte) 0x11, (byte) 0x97, (byte) 0x9c, (byte) 0x53, (byte) 0x73, (byte) 0x23,
+            (byte) 0x9e, (byte) 0xb1, (byte) 0x52, (byte) 0xa6, (byte) 0x83, (byte) 0xa2,
+            (byte) 0xf5, (byte) 0xb9, (byte) 0x02, (byte) 0x81, (byte) 0x80, (byte) 0x7b,
+            (byte) 0xbb, (byte) 0x01, (byte) 0x82, (byte) 0x28, (byte) 0xba, (byte) 0xc2,
+            (byte) 0xf1, (byte) 0x91, (byte) 0xf9, (byte) 0x6a, (byte) 0xa0, (byte) 0x66,
+            (byte) 0xf3, (byte) 0x6c, (byte) 0x74, (byte) 0x1d, (byte) 0x0d, (byte) 0x69,
+            (byte) 0xd6, (byte) 0xfe, (byte) 0xec, (byte) 0xe7, (byte) 0x87, (byte) 0x04,
+            (byte) 0xff, (byte) 0x9a, (byte) 0x13, (byte) 0xf4, (byte) 0xb9, (byte) 0xa1,
+            (byte) 0x6a, (byte) 0xb5, (byte) 0xaa, (byte) 0x1f, (byte) 0x52, (byte) 0x60,
+            (byte) 0x89, (byte) 0xad, (byte) 0x21, (byte) 0x68, (byte) 0xc8, (byte) 0x20,
+            (byte) 0x4a, (byte) 0x1b, (byte) 0xe4, (byte) 0x0f, (byte) 0x54, (byte) 0x6f,
+            (byte) 0xeb, (byte) 0x07, (byte) 0x25, (byte) 0x97, (byte) 0x0a, (byte) 0xca,
+            (byte) 0x36, (byte) 0x64, (byte) 0x3e, (byte) 0x41, (byte) 0x90, (byte) 0x08,
+            (byte) 0xfc, (byte) 0x54, (byte) 0xf8, (byte) 0xd9, (byte) 0x03, (byte) 0x6b,
+            (byte) 0x85, (byte) 0x8a, (byte) 0xd4, (byte) 0xb4, (byte) 0xc0, (byte) 0x4e,
+            (byte) 0xfe, (byte) 0x0f, (byte) 0xb0, (byte) 0xed, (byte) 0x1b, (byte) 0x8b,
+            (byte) 0x7f, (byte) 0x17, (byte) 0xf7, (byte) 0x46, (byte) 0x30, (byte) 0x2f,
+            (byte) 0x49, (byte) 0xf8, (byte) 0x53, (byte) 0xf1, (byte) 0x57, (byte) 0x80,
+            (byte) 0xfd, (byte) 0x38, (byte) 0xa1, (byte) 0x7a, (byte) 0xf8, (byte) 0xc0,
+            (byte) 0xd7, (byte) 0x67, (byte) 0x42, (byte) 0xae, (byte) 0x6d, (byte) 0x3b,
+            (byte) 0x46, (byte) 0xc9, (byte) 0xf4, (byte) 0x80, (byte) 0xe3, (byte) 0x55,
+            (byte) 0x39, (byte) 0x3b, (byte) 0x14, (byte) 0x62, (byte) 0x15, (byte) 0x8e,
+            (byte) 0x79, (byte) 0xd3, (byte) 0x95, (byte) 0x99, (byte) 0x2c, (byte) 0x63,
+            (byte) 0x57, (byte) 0x5b, (byte) 0x96, (byte) 0xc5, (byte) 0x50, (byte) 0x8e,
+            (byte) 0x87, (byte) 0x02, (byte) 0x81, (byte) 0x80, (byte) 0x74, (byte) 0x85,
+            (byte) 0xe1, (byte) 0xdd, (byte) 0x66, (byte) 0x9a, (byte) 0x7c, (byte) 0x2a,
+            (byte) 0x27, (byte) 0x9c, (byte) 0xc5, (byte) 0x11, (byte) 0x27, (byte) 0xf7,
+            (byte) 0xa1, (byte) 0xb5, (byte) 0x1f, (byte) 0xe6, (byte) 0xf9, (byte) 0x8b,
+            (byte) 0xab, (byte) 0x53, (byte) 0xe0, (byte) 0x9b, (byte) 0x1a, (byte) 0x8b,
+            (byte) 0x67, (byte) 0x6e, (byte) 0xb4, (byte) 0xfa, (byte) 0xec, (byte) 0xa1,
+            (byte) 0x7d, (byte) 0x35, (byte) 0xdd, (byte) 0x3d, (byte) 0x6c, (byte) 0xc1,
+            (byte) 0xcc, (byte) 0x56, (byte) 0x96, (byte) 0x6b, (byte) 0x2e, (byte) 0x87,
+            (byte) 0x1e, (byte) 0x1b, (byte) 0xae, (byte) 0x6e, (byte) 0xa9, (byte) 0x58,
+            (byte) 0x97, (byte) 0x83, (byte) 0x8c, (byte) 0x01, (byte) 0xf4, (byte) 0xb3,
+            (byte) 0x1c, (byte) 0x27, (byte) 0x1a, (byte) 0xb7, (byte) 0x1e, (byte) 0x52,
+            (byte) 0x90, (byte) 0x41, (byte) 0xd0, (byte) 0xc2, (byte) 0x05, (byte) 0x51,
+            (byte) 0x96, (byte) 0x4b, (byte) 0x12, (byte) 0x37, (byte) 0x72, (byte) 0x29,
+            (byte) 0xdf, (byte) 0x46, (byte) 0xf5, (byte) 0x4f, (byte) 0x78, (byte) 0xc8,
+            (byte) 0x1e, (byte) 0xbe, (byte) 0xab, (byte) 0x67, (byte) 0x28, (byte) 0x7a,
+            (byte) 0x11, (byte) 0x86, (byte) 0xfb, (byte) 0x90, (byte) 0x1d, (byte) 0x4a,
+            (byte) 0x45, (byte) 0xcd, (byte) 0x20, (byte) 0xb5, (byte) 0xc2, (byte) 0xca,
+            (byte) 0x0a, (byte) 0x3e, (byte) 0x3b, (byte) 0x4b, (byte) 0x90, (byte) 0x1e,
+            (byte) 0xe8, (byte) 0xb3, (byte) 0x68, (byte) 0xd6, (byte) 0x07, (byte) 0x8f,
+            (byte) 0x92, (byte) 0x7a, (byte) 0xb4, (byte) 0x76, (byte) 0x13, (byte) 0xbf,
+            (byte) 0xaa, (byte) 0x1e, (byte) 0x72, (byte) 0x3c, (byte) 0xfc, (byte) 0x33,
+            (byte) 0x9d, (byte) 0x5c, (byte) 0xaa, (byte) 0xfc, (byte) 0xab, (byte) 0xf9,
+            (byte) 0x02, (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0xcc, (byte) 0x49,
+            (byte) 0xa3, (byte) 0xd7, (byte) 0xc5, (byte) 0x91, (byte) 0x24, (byte) 0x30,
+            (byte) 0xa6, (byte) 0xa1, (byte) 0x06, (byte) 0x74, (byte) 0xf9, (byte) 0x33,
+            (byte) 0xb7, (byte) 0xb5, (byte) 0x38, (byte) 0xf0, (byte) 0xb1, (byte) 0x5b,
+            (byte) 0xe0, (byte) 0xdf, (byte) 0xdf, (byte) 0xf6, (byte) 0x81, (byte) 0x1c,
+            (byte) 0xb5, (byte) 0xb4, (byte) 0xc9, (byte) 0x53, (byte) 0x36, (byte) 0x9c,
+            (byte) 0xc3, (byte) 0xb8, (byte) 0x9c, (byte) 0x15, (byte) 0x42, (byte) 0xb4,
+            (byte) 0x80, (byte) 0x69, (byte) 0x7e, (byte) 0x0c, (byte) 0x74, (byte) 0xb7,
+            (byte) 0x70, (byte) 0xf6, (byte) 0xe7, (byte) 0xce, (byte) 0x15, (byte) 0x4a,
+            (byte) 0x07, (byte) 0x9c, (byte) 0xa1, (byte) 0x5c, (byte) 0x82, (byte) 0x9b,
+            (byte) 0x8c, (byte) 0xd3, (byte) 0xdf, (byte) 0xee, (byte) 0x85, (byte) 0xd7,
+            (byte) 0x76, (byte) 0xdf, (byte) 0x2d, (byte) 0x71, (byte) 0x84, (byte) 0x10,
+            (byte) 0x6a, (byte) 0x24, (byte) 0x77, (byte) 0xc6, (byte) 0xf3, (byte) 0x96,
+            (byte) 0x73, (byte) 0xe6, (byte) 0x89, (byte) 0xb0, (byte) 0xfb, (byte) 0x2d,
+            (byte) 0xef, (byte) 0xe0, (byte) 0x42, (byte) 0x02, (byte) 0x4b, (byte) 0xac,
+            (byte) 0x3a, (byte) 0x91, (byte) 0xe4, (byte) 0x0c, (byte) 0x07, (byte) 0xd5,
+            (byte) 0xba, (byte) 0xb9, (byte) 0xdc, (byte) 0x7b, (byte) 0x6d, (byte) 0x3a,
+            (byte) 0x5b, (byte) 0x2d, (byte) 0x25, (byte) 0x0d, (byte) 0xc6, (byte) 0x00,
+            (byte) 0xb7, (byte) 0xb4, (byte) 0xb6, (byte) 0xb8, (byte) 0xec, (byte) 0x0e,
+            (byte) 0xe9, (byte) 0xc4, (byte) 0x04, (byte) 0x7f, (byte) 0xac, (byte) 0xeb,
+            (byte) 0x8b, (byte) 0xd6, (byte) 0xc3, (byte) 0xec, (byte) 0x0d, (byte) 0xa3,
+            (byte) 0x3f, (byte) 0x7e, (byte) 0x14, (byte) 0x4e, (byte) 0xce, (byte) 0xa6
+    };
+
+    // RSA 2048 bit key which is less than the minimum of 3072 bit
+    public static final PrivateKey CLIENT_BAD_SUITE_B_RSA2048_KEY =
+            loadPrivateKey("RSA", CLIENT_BAD_SUITE_B_RSA2048_KEY_DATA);
+
     private static final String CLIENT_SUITE_B_ECDSA_CERT_STRING =
             "-----BEGIN CERTIFICATE-----\n"
                     + "MIIB9zCCAX4CFDpfSZh3AH07BEfGWuMDa7Ynz6y+MAoGCCqGSM49BAMDMF4xCzAJ\n"
@@ -634,6 +870,53 @@
     public static final PrivateKey CLIENT_SUITE_B_ECC_KEY =
             loadPrivateKey("EC", CLIENT_SUITE_B_ECC_KEY_DATA);
 
+    private static final String CLIENT_BAD_SUITE_B_ECDSA_256_CERT_STRING =
+            "-----BEGIN CERTIFICATE-----\n"
+                    + "MIIB0zCCAXoCFD2/mD14uMFQpTxhk4fjAuReIUI6MAoGCCqGSM49BAMDMGUxCzAJ\n"
+                    + "BgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoGA1UEBwwDTVRWMRAwDgYDVQQKDAdB\n"
+                    + "bmRyb2lkMQ4wDAYDVQQLDAVXaS1GaTEZMBcGA1UEAwwQd2lmaS5hbmRyb2lkLmNv\n"
+                    + "bTAeFw0yMDA5MDEyMjA5NTdaFw0zMDA3MTEyMjA5NTdaMHQxCzAJBgNVBAYTAlVT\n"
+                    + "MQswCQYDVQQIDAJDQTEMMAoGA1UEBwwDTVRWMRAwDgYDVQQKDAdBbmRyb2lkMRYw\n"
+                    + "FAYDVQQLDA1XaS1GaSAyNTYgRUNDMSAwHgYDVQQDDBdlY2MyNTYud2lmaS5hbmRy\n"
+                    + "b2lkLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABPlAIUqSBuQ04i1Mz0k0\n"
+                    + "Nj7qiiyFj7cweQTZktd8Iz4KzH3OH/FSI3jjVne4PDMlyiawkBkkoufq9+cKWyPW\n"
+                    + "lW0wCgYIKoZIzj0EAwMDRwAwRAIgFj44/+HDOt3MxwXCa4jhzyrqVUVN31z2pMpv\n"
+                    + "VnrQrtUCIByzIcG92+pNFfAlu6oZ4Q1rRrJLAuwWU5pZDR+mBRq6\n"
+                    + "-----END CERTIFICATE-----\n";
+    public static final X509Certificate CLIENT_BAD_SUITE_B_ECDSA_256_CERT =
+            loadCertificate(CLIENT_BAD_SUITE_B_ECDSA_256_CERT_STRING);
+
+    // ECC 256 bit key which is less than the minimum of 384 bit
+    private static final byte[] CLIENT_BAD_SUITE_B_ECC_256_KEY_DATA = new byte[]{
+            (byte) 0x30, (byte) 0x81, (byte) 0x87, (byte) 0x02, (byte) 0x01, (byte) 0x00,
+            (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07, (byte) 0x2a, (byte) 0x86,
+            (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x02, (byte) 0x01, (byte) 0x06,
+            (byte) 0x08, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d,
+            (byte) 0x03, (byte) 0x01, (byte) 0x07, (byte) 0x04, (byte) 0x6d, (byte) 0x30,
+            (byte) 0x6b, (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x04, (byte) 0x20,
+            (byte) 0x41, (byte) 0x26, (byte) 0xae, (byte) 0xd5, (byte) 0x3e, (byte) 0xd7,
+            (byte) 0x83, (byte) 0x07, (byte) 0x02, (byte) 0xc7, (byte) 0xae, (byte) 0x06,
+            (byte) 0xed, (byte) 0xc9, (byte) 0x58, (byte) 0xe7, (byte) 0x39, (byte) 0x7a,
+            (byte) 0xba, (byte) 0x1a, (byte) 0x76, (byte) 0x67, (byte) 0xc1, (byte) 0x85,
+            (byte) 0xd4, (byte) 0x23, (byte) 0xf9, (byte) 0x1e, (byte) 0x93, (byte) 0x28,
+            (byte) 0x3f, (byte) 0x2b, (byte) 0xa1, (byte) 0x44, (byte) 0x03, (byte) 0x42,
+            (byte) 0x00, (byte) 0x04, (byte) 0xf9, (byte) 0x40, (byte) 0x21, (byte) 0x4a,
+            (byte) 0x92, (byte) 0x06, (byte) 0xe4, (byte) 0x34, (byte) 0xe2, (byte) 0x2d,
+            (byte) 0x4c, (byte) 0xcf, (byte) 0x49, (byte) 0x34, (byte) 0x36, (byte) 0x3e,
+            (byte) 0xea, (byte) 0x8a, (byte) 0x2c, (byte) 0x85, (byte) 0x8f, (byte) 0xb7,
+            (byte) 0x30, (byte) 0x79, (byte) 0x04, (byte) 0xd9, (byte) 0x92, (byte) 0xd7,
+            (byte) 0x7c, (byte) 0x23, (byte) 0x3e, (byte) 0x0a, (byte) 0xcc, (byte) 0x7d,
+            (byte) 0xce, (byte) 0x1f, (byte) 0xf1, (byte) 0x52, (byte) 0x23, (byte) 0x78,
+            (byte) 0xe3, (byte) 0x56, (byte) 0x77, (byte) 0xb8, (byte) 0x3c, (byte) 0x33,
+            (byte) 0x25, (byte) 0xca, (byte) 0x26, (byte) 0xb0, (byte) 0x90, (byte) 0x19,
+            (byte) 0x24, (byte) 0xa2, (byte) 0xe7, (byte) 0xea, (byte) 0xf7, (byte) 0xe7,
+            (byte) 0x0a, (byte) 0x5b, (byte) 0x23, (byte) 0xd6, (byte) 0x95, (byte) 0x6d
+    };
+
+    public static final PrivateKey CLIENT_BAD_SUITE_B_ECC_256_KEY =
+            loadPrivateKey("EC", CLIENT_BAD_SUITE_B_ECC_256_KEY_DATA);
+
+
     private static X509Certificate loadCertificate(String blob) {
         try {
             final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
diff --git a/service/tests/wifitests/src/com/android/server/wifi/HalDeviceManagerTest.java b/service/tests/wifitests/src/com/android/server/wifi/HalDeviceManagerTest.java
index bd9a169..b287c2a 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/HalDeviceManagerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/HalDeviceManagerTest.java
@@ -16,21 +16,32 @@
 
 package com.android.server.wifi;
 
+import static com.android.server.wifi.HalDeviceManager.HDM_CREATE_IFACE_AP;
+import static com.android.server.wifi.HalDeviceManager.HDM_CREATE_IFACE_AP_BRIDGE;
+import static com.android.server.wifi.HalDeviceManager.HDM_CREATE_IFACE_NAN;
+import static com.android.server.wifi.HalDeviceManager.HDM_CREATE_IFACE_P2P;
+import static com.android.server.wifi.HalDeviceManager.HDM_CREATE_IFACE_STA;
 import static com.android.server.wifi.HalDeviceManager.START_HAL_RETRY_TIMES;
 
 import static junit.framework.Assert.assertEquals;
 
+import static org.hamcrest.Matchers.is;
 import static org.hamcrest.core.IsEqual.equalTo;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -54,13 +65,17 @@
 import android.hidl.manager.V1_2.IServiceManager;
 import android.os.Handler;
 import android.os.IHwBinder;
+import android.os.RemoteException;
+import android.os.WorkSource;
 import android.os.test.TestLooper;
 import android.util.Log;
 import android.util.SparseArray;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.HalDeviceManager.InterfaceDestroyedListener;
+import com.android.server.wifi.util.WorkSourceHelper;
 
 import org.hamcrest.core.IsNull;
 import org.junit.After;
@@ -87,12 +102,22 @@
  */
 @SmallTest
 public class HalDeviceManagerTest extends WifiBaseTest {
+    private static final WorkSource TEST_WORKSOURCE_0 = new WorkSource(450, "com.test.0");
+    private static final WorkSource TEST_WORKSOURCE_1 = new WorkSource(451, "com.test.1");
+    private static final WorkSource TEST_WORKSOURCE_2 = new WorkSource(452, "com.test.2");
+
     private HalDeviceManager mDut;
     @Mock IServiceManager mServiceManagerMock;
     @Mock IWifi mWifiMock;
+    @Mock android.hardware.wifi.V1_5.IWifi mWifiMockV15;
     @Mock IWifiRttController mRttControllerMock;
     @Mock HalDeviceManager.ManagerStatusListener mManagerStatusListenerMock;
     @Mock private Clock mClock;
+    @Mock private WifiInjector mWifiInjector;
+    @Mock private WorkSourceHelper mWorkSourceHelper0;
+    @Mock private WorkSourceHelper mWorkSourceHelper1;
+    @Mock private WorkSourceHelper mWorkSourceHelper2;
+    private android.hardware.wifi.V1_5.IWifiChip mWifiChipV15 = null;
     private TestLooper mTestLooper;
     private Handler mHandler;
     private ArgumentCaptor<IHwBinder.DeathRecipient> mDeathRecipientCaptor =
@@ -101,6 +126,9 @@
             ArgumentCaptor.forClass(IServiceNotification.Stub.class);
     private ArgumentCaptor<IWifiEventCallback> mWifiEventCallbackCaptor = ArgumentCaptor.forClass(
             IWifiEventCallback.class);
+    private ArgumentCaptor<android.hardware.wifi.V1_5.IWifiEventCallback>
+            mWifiEventCallbackCaptorV15 = ArgumentCaptor.forClass(
+            android.hardware.wifi.V1_5.IWifiEventCallback.class);
     private InOrder mInOrder;
     @Rule public ErrorCollector collector = new ErrorCollector();
     private WifiStatus mStatusOk;
@@ -108,7 +136,7 @@
 
     private class HalDeviceManagerSpy extends HalDeviceManager {
         HalDeviceManagerSpy() {
-            super(mClock, mHandler);
+            super(mClock, mWifiInjector, mHandler);
         }
 
         @Override
@@ -117,9 +145,21 @@
         }
 
         @Override
+        protected android.hardware.wifi.V1_5.IWifi getWifiServiceForV1_5Mockable(IWifi iWifi) {
+            return (mWifiMockV15 != null)
+                    ? mWifiMockV15
+                    : null;
+        }
+
+        @Override
         protected IServiceManager getServiceManagerMockable() {
             return mServiceManagerMock;
         }
+
+        @Override
+        protected android.hardware.wifi.V1_5.IWifiChip getWifiChipForV1_5Mockable(IWifiChip chip) {
+            return mWifiChipV15;
+        }
     }
 
     @Before
@@ -129,10 +169,19 @@
         mTestLooper = new TestLooper();
         mHandler = new Handler(mTestLooper.getLooper());
 
-        // initialize dummy status objects
+        // initialize placeholder status objects
         mStatusOk = getStatus(WifiStatusCode.SUCCESS);
         mStatusFail = getStatus(WifiStatusCode.ERROR_UNKNOWN);
 
+        setupWifiV15(mWifiMock);
+
+        when(mWifiInjector.makeWsHelper(TEST_WORKSOURCE_0)).thenReturn(mWorkSourceHelper0);
+        when(mWifiInjector.makeWsHelper(TEST_WORKSOURCE_1)).thenReturn(mWorkSourceHelper1);
+        when(mWifiInjector.makeWsHelper(TEST_WORKSOURCE_2)).thenReturn(mWorkSourceHelper2);
+        when(mWorkSourceHelper0.hasAnyPrivilegedAppRequest()).thenReturn(true);
+        when(mWorkSourceHelper1.hasAnyPrivilegedAppRequest()).thenReturn(true);
+        when(mWorkSourceHelper2.hasAnyPrivilegedAppRequest()).thenReturn(true);
+
         when(mServiceManagerMock.linkToDeath(any(IHwBinder.DeathRecipient.class),
                 anyLong())).thenReturn(true);
         when(mServiceManagerMock.registerForNotifications(anyString(), anyString(),
@@ -145,6 +194,7 @@
         when(mWifiMock.start()).thenReturn(mStatusOk);
         when(mWifiMock.stop()).thenReturn(mStatusOk);
         when(mWifiMock.isStarted()).thenReturn(true);
+        when(mWifiMockV15.isStarted()).thenReturn(true);
 
         mDut = new HalDeviceManagerSpy();
     }
@@ -172,7 +222,8 @@
      */
     @Test
     public void testStartStopFlow() throws Exception {
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mManagerStatusListenerMock);
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15,
+                mManagerStatusListenerMock);
         executeAndValidateInitializationSequence();
         executeAndValidateStartupSequence();
 
@@ -194,7 +245,8 @@
      */
     @Test
     public void testServiceRegisterationAfterInitialize() throws Exception {
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mManagerStatusListenerMock);
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15,
+                mManagerStatusListenerMock);
         executeAndValidateInitializationSequence();
 
         // This should now be ignored since IWifi is already non-null.
@@ -209,7 +261,8 @@
      */
     @Test
     public void testMultipleCallbackRegistrations() throws Exception {
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mManagerStatusListenerMock);
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15,
+                mManagerStatusListenerMock);
         executeAndValidateInitializationSequence();
 
         // register another 2 callbacks - one of them twice
@@ -236,7 +289,8 @@
      */
     @Test
     public void testWifiDeathAndRegistration() throws Exception {
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mManagerStatusListenerMock);
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, mWifiMockV15,
+                mManagerStatusListenerMock);
         executeAndValidateInitializationSequence();
         executeAndValidateStartupSequence();
 
@@ -252,11 +306,20 @@
 
         // verify: initialization of IWifi
         mInOrder.verify(mWifiMock).linkToDeath(mDeathRecipientCaptor.capture(), anyLong());
-        mInOrder.verify(mWifiMock).registerEventCallback(mWifiEventCallbackCaptor.capture());
+        if (null != mWifiMockV15) {
+            mInOrder.verify(mWifiMockV15).registerEventCallback_1_5(
+                    mWifiEventCallbackCaptorV15.capture());
+        } else {
+            mInOrder.verify(mWifiMock).registerEventCallback(mWifiEventCallbackCaptor.capture());
+        }
 
         // act: start
         collector.checkThat(mDut.start(), equalTo(true));
-        mWifiEventCallbackCaptor.getValue().onStart();
+        if (null != mWifiMockV15) {
+            mWifiEventCallbackCaptorV15.getValue().onStart();
+        } else {
+            mWifiEventCallbackCaptor.getValue().onStart();
+        }
         mTestLooper.dispatchAll();
 
         // verify: service and callback calls
@@ -271,12 +334,17 @@
      */
     @Test
     public void testWifiFail() throws Exception {
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mManagerStatusListenerMock);
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, mWifiMockV15,
+                mManagerStatusListenerMock);
         executeAndValidateInitializationSequence();
         executeAndValidateStartupSequence();
 
         // act: IWifi failure
-        mWifiEventCallbackCaptor.getValue().onFailure(mStatusFail);
+        if (null != mWifiMockV15) {
+            mWifiEventCallbackCaptorV15.getValue().onFailure(mStatusFail);
+        } else {
+            mWifiEventCallbackCaptor.getValue().onFailure(mStatusFail);
+        }
         mTestLooper.dispatchAll();
 
         // verify: getting onStop
@@ -284,7 +352,11 @@
 
         // act: start again
         collector.checkThat(mDut.start(), equalTo(true));
-        mWifiEventCallbackCaptor.getValue().onStart();
+        if (null != mWifiMockV15) {
+            mWifiEventCallbackCaptorV15.getValue().onStart();
+        } else {
+            mWifiEventCallbackCaptor.getValue().onStart();
+        }
         mTestLooper.dispatchAll();
 
         // verify: service and callback calls
@@ -305,64 +377,52 @@
     public void testCacheMismatchError() throws Exception {
         TestChipV1 chipMock = new TestChipV1();
         chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
                 mManagerStatusListenerMock);
         executeAndValidateInitializationSequence();
         executeAndValidateStartupSequence();
 
         InterfaceDestroyedListener staDestroyedListener = mock(
                 InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener staAvailListener = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
 
         InterfaceDestroyedListener nanDestroyedListener = mock(
                 InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener nanAvailListener = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
-
-        InOrder availInOrder = inOrder(staAvailListener, nanAvailListener);
 
         // Request STA
         IWifiIface staIface = validateInterfaceSequence(chipMock,
                 false, // chipModeValid
                 -1000, // chipModeId (only used if chipModeValid is true)
-                IfaceType.STA, // ifaceTypeToCreate
+                HDM_CREATE_IFACE_STA, // ifaceTypeToCreate
                 "wlan0", // ifaceName
                 TestChipV1.STA_CHIP_MODE_ID, // finalChipMode
                 null, // tearDownList
                 staDestroyedListener, // destroyedListener
-                staAvailListener // availableListener
+                TEST_WORKSOURCE_0 // requestorWs
         );
-        availInOrder.verify(staAvailListener).onAvailabilityChanged(false);
+        collector.checkThat("STA can't be created", staIface, IsNull.notNullValue());
 
         // Request NAN
         IWifiIface nanIface = validateInterfaceSequence(chipMock,
                 true, // chipModeValid
                 TestChipV1.STA_CHIP_MODE_ID, // chipModeId
-                IfaceType.NAN, // ifaceTypeToCreate
+                HDM_CREATE_IFACE_NAN, // ifaceTypeToCreate
                 "wlan0", // ifaceName
                 TestChipV1.STA_CHIP_MODE_ID, // finalChipMode
                 null, // tearDownList
                 nanDestroyedListener, // destroyedListener
-                nanAvailListener // availableListener
+                TEST_WORKSOURCE_0 // requestorWs
         );
-        availInOrder.verify(nanAvailListener).onAvailabilityChanged(false);
+        collector.checkThat("NAN can't be created", nanIface, IsNull.notNullValue());
 
         // fiddle with the "chip" by removing the STA
         chipMock.interfaceNames.get(IfaceType.STA).remove("wlan0");
 
         // now try to request another NAN
-        IWifiIface nanIface2 = mDut.createNanIface(nanDestroyedListener, mHandler);
+        IWifiIface nanIface2 =
+                mDut.createNanIface(nanDestroyedListener, mHandler, TEST_WORKSOURCE_0);
         collector.checkThat("NAN can't be created", nanIface2, IsNull.nullValue());
-
-        mDut.registerInterfaceAvailableForRequestListener(IfaceType.NAN, nanAvailListener,
-                mHandler);
         mTestLooper.dispatchAll();
 
-        // extra (apparently duplicate) call since everything was cleaned-up once a cache mismatch
-        // was detected - so this is a call on a new registration
-        availInOrder.verify(nanAvailListener).onAvailabilityChanged(false);
-
         // verify that Wi-Fi is shut-down: should also get all onDestroyed messages that are
         // registered (even if they seem out-of-sync to chip)
         verify(mWifiMock, times(2)).stop();
@@ -370,61 +430,8 @@
         verify(staDestroyedListener).onDestroyed(getName(staIface));
         verify(nanDestroyedListener).onDestroyed(getName(nanIface));
 
-        verifyNoMoreInteractions(mManagerStatusListenerMock, staDestroyedListener, staAvailListener,
-                nanDestroyedListener, nanAvailListener);
-    }
-
-    /**
-     * Validates that a duplicate registration of the same InterfaceAvailableForRequestListener
-     * listener will result in a single callback.
-     *
-     * Also validates that get an immediate call on registration if available.
-     *
-     * Uses TestChipV1 - but nothing specific to its configuration. The test validates internal
-     * HDM behavior.
-     */
-    @Test
-    public void testDuplicateAvailableRegistrations() throws Exception {
-        TestChipV1 chipMock = new TestChipV1();
-        chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
-                mManagerStatusListenerMock);
-        executeAndValidateInitializationSequence();
-        executeAndValidateStartupSequence();
-
-        HalDeviceManager.InterfaceAvailableForRequestListener staAvailListener = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
-
-        // get STA interface
-        IWifiIface staIface = validateInterfaceSequence(chipMock,
-                false, // chipModeValid
-                -1000, // chipModeId (only used if chipModeValid is true)
-                IfaceType.STA, // ifaceTypeToCreate
-                "wlan0", // ifaceName
-                TestChipV1.STA_CHIP_MODE_ID, // finalChipMode
-                null, // tearDownList
-                null, // destroyedListener
-                null // availableListener
-        );
-        collector.checkThat("STA created", staIface, IsNull.notNullValue());
-
-        // act: register the same listener twice
-        mDut.registerInterfaceAvailableForRequestListener(IfaceType.STA, staAvailListener,
-                mHandler);
-        mDut.registerInterfaceAvailableForRequestListener(IfaceType.STA, staAvailListener,
-                mHandler);
-        mTestLooper.dispatchAll();
-
-        verify(staAvailListener).onAvailabilityChanged(false);
-
-        // remove STA interface -> should trigger callbacks
-        mDut.removeIface(staIface);
-        mTestLooper.dispatchAll();
-
-        // verify: only a single trigger
-        verify(staAvailListener).onAvailabilityChanged(true);
-
-        verifyNoMoreInteractions(staAvailListener);
+        verifyNoMoreInteractions(mManagerStatusListenerMock, staDestroyedListener,
+                nanDestroyedListener);
     }
 
     /**
@@ -454,7 +461,7 @@
 
         TestChipV1 chipMock = new TestChipV1();
         chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
                 mManagerStatusListenerMock);
         executeAndValidateInitializationSequence();
         executeAndValidateStartupSequence(2, true);
@@ -473,7 +480,7 @@
 
         TestChipV1 chipMock = new TestChipV1();
         chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip);
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip);
         executeAndValidateInitializationSequence();
         executeAndValidateStartupSequence(START_HAL_RETRY_TIMES + 1, false);
     }
@@ -491,7 +498,7 @@
 
         TestChipV1 chipMock = new TestChipV1();
         chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip);
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip);
         executeAndValidateInitializationSequence();
         executeAndValidateStartupSequence(1, false);
     }
@@ -502,7 +509,7 @@
      */
     @Test
     public void testIsSupportedTrue() throws Exception {
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock);
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15);
         executeAndValidateInitializationSequence();
         assertTrue(mDut.isSupported());
     }
@@ -535,7 +542,7 @@
         // concurrency in this test).
         ChipMockBase chipMock = new TestChipV1();
         chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
                 mManagerStatusListenerMock);
         executeAndValidateInitializationSequence();
         executeAndValidateStartupSequence();
@@ -549,12 +556,12 @@
         validateInterfaceSequence(chipMock,
                 false, // chipModeValid
                 -1000, // chipModeId (only used if chipModeValid is true)
-                IfaceType.STA,
+                HDM_CREATE_IFACE_STA,
                 "wlan0",
                 TestChipV1.STA_CHIP_MODE_ID,
                 null, // tearDownList
                 null, // destroyedListener
-                null // availableListener
+                TEST_WORKSOURCE_0 // requestorWs
         );
         verify(chipMock.chip).createRttController(any(), any());
         io.verify(cb).onNewRttController(any());
@@ -588,19 +595,19 @@
         // STA (which will configure the chip).
         ChipMockBase chipMock = new TestChipV1();
         chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
                 mManagerStatusListenerMock);
         executeAndValidateInitializationSequence();
         executeAndValidateStartupSequence();
         validateInterfaceSequence(chipMock,
                 false, // chipModeValid
                 -1000, // chipModeId (only used if chipModeValid is true)
-                IfaceType.STA,
+                HDM_CREATE_IFACE_STA,
                 "wlan0",
                 TestChipV1.STA_CHIP_MODE_ID,
                 null, // tearDownList
                 null, // destroyedListener
-                null // availableListener
+                TEST_WORKSOURCE_0 // requestorWs
         );
         mInOrder.verify(chipMock.chip, times(0)).createRttController(any(), any());
 
@@ -620,12 +627,12 @@
         validateInterfaceSequence(chipMock,
                 true, // chipModeValid
                 TestChipV1.STA_CHIP_MODE_ID, // chipModeId (only used if chipModeValid is true)
-                IfaceType.AP,
+                HDM_CREATE_IFACE_AP,
                 "wlan0",
                 TestChipV1.AP_CHIP_MODE_ID,
                 null, // tearDownList
                 null, // destroyedListener
-                null // availableListener
+                TEST_WORKSOURCE_0 // requestorWs
         );
         mTestLooper.dispatchAll();
         verify(chipMock.chip, times(2)).createRttController(any(), any()); // but returns a null!
@@ -636,12 +643,12 @@
         validateInterfaceSequence(chipMock,
                 true, // chipModeValid
                 TestChipV1.AP_CHIP_MODE_ID, // chipModeId (only used if chipModeValid is true)
-                IfaceType.STA,
+                HDM_CREATE_IFACE_STA,
                 "wlan0",
                 TestChipV1.STA_CHIP_MODE_ID,
                 null, // tearDownList
                 null, // destroyedListener
-                null // availableListener
+                TEST_WORKSOURCE_0 // requestorWs
         );
         mTestLooper.dispatchAll();
         verify(chipMock.chip, times(3)).createRttController(any(), any());
@@ -666,19 +673,19 @@
         // & create a STA (which will configure the chip).
         ChipMockBase chipMock = new TestChipV2();
         chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
                 mManagerStatusListenerMock);
         executeAndValidateInitializationSequence();
         executeAndValidateStartupSequence();
         validateInterfaceSequence(chipMock,
                 false, // chipModeValid
                 -1000, // chipModeId (only used if chipModeValid is true)
-                IfaceType.STA,
+                HDM_CREATE_IFACE_STA,
                 "wlan0",
                 TestChipV2.CHIP_MODE_ID,
                 null, // tearDownList
                 null, // destroyedListener
-                null // availableListener
+                TEST_WORKSOURCE_0 // requestorWs
         );
         mInOrder.verify(chipMock.chip, times(0)).createRttController(any(), any());
 
@@ -692,18 +699,77 @@
         validateInterfaceSequence(chipMock,
                 true, // chipModeValid
                 TestChipV2.CHIP_MODE_ID, // chipModeId (only used if chipModeValid is true)
-                IfaceType.AP,
+                HDM_CREATE_IFACE_AP,
                 "wlan0",
                 TestChipV2.CHIP_MODE_ID,
                 null, // tearDownList
                 null, // destroyedListener
-                null // availableListener
+                TEST_WORKSOURCE_0 // requestorWs
         );
         mTestLooper.dispatchAll();
 
         verifyNoMoreInteractions(cb);
     }
 
+    /**
+     * Validate a flow sequence for test chip 1:
+     * - create STA (privileged app)
+     * - create AP (system app): will get refused
+     * - replace STA requestorWs with fg app
+     * - create AP (system app)
+     */
+    @Test
+    public void testReplaceRequestorWs() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        // initialize a test chip & create a STA (which will configure the chip).
+        ChipMockBase chipMock = new TestChipV1();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        // create STA interface from privileged app: should succeed.
+        IWifiIface staIface = validateInterfaceSequence(chipMock,
+                false, // chipModeValid
+                -1000, // chipModeId (only used if chipModeValid is true)
+                HDM_CREATE_IFACE_STA,
+                "wlan0",
+                TestChipV1.STA_CHIP_MODE_ID,
+                null, // tearDownList
+                null, // destroyedListener
+                TEST_WORKSOURCE_0 // requestorWs
+        );
+        collector.checkThat("STA created", staIface, IsNull.notNullValue());
+
+        // get AP interface from a system app: should fail
+        when(mWorkSourceHelper1.hasAnyPrivilegedAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper1.hasAnySystemAppRequest()).thenReturn(true);
+        IWifiApIface apIface = mDut.createApIface(null, null, TEST_WORKSOURCE_1, false);
+        collector.checkThat("not allocated interface", apIface, IsNull.nullValue());
+
+        // Now replace the requestorWs (fg app now) for the STA iface.
+        when(mWorkSourceHelper2.hasAnyPrivilegedAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper2.hasAnyForegroundAppRequest()).thenReturn(true);
+        assertTrue(mDut.replaceRequestorWs(staIface, TEST_WORKSOURCE_2));
+
+        // get AP interface again from a system app: should succeed now
+        when(mWorkSourceHelper1.hasAnyPrivilegedAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper1.hasAnySystemAppRequest()).thenReturn(true);
+        apIface = (IWifiApIface) validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                TestChipV1.STA_CHIP_MODE_ID, // chipModeId (only used if chipModeValid is true)
+                HDM_CREATE_IFACE_AP,
+                "wlan0",
+                TestChipV1.AP_CHIP_MODE_ID,
+                null, // tearDownList
+                null, // destroyedListener
+                TEST_WORKSOURCE_1 // requestorWs
+        );
+        collector.checkThat("not allocated interface", apIface, IsNull.notNullValue());
+    }
+
+
     //////////////////////////////////////////////////////////////////////////////////////
     // Chip Specific Tests - but should work on all chips!
     // (i.e. add copies for each test chip)
@@ -716,8 +782,8 @@
      */
     @Test
     public void testCreateStaInterfaceNoInitModeTestChipV1() throws Exception {
-        runCreateSingleXxxInterfaceNoInitMode(new TestChipV1(), IfaceType.STA, "wlan0",
-                TestChipV1.STA_CHIP_MODE_ID, false);
+        runCreateSingleXxxInterfaceNoInitMode(new TestChipV1(), HDM_CREATE_IFACE_STA, "wlan0",
+                TestChipV1.STA_CHIP_MODE_ID);
     }
 
     /**
@@ -725,8 +791,8 @@
      */
     @Test
     public void testCreateApInterfaceNoInitModeTestChipV1() throws Exception {
-        runCreateSingleXxxInterfaceNoInitMode(new TestChipV1(), IfaceType.AP, "wlan0",
-                TestChipV1.AP_CHIP_MODE_ID, false);
+        runCreateSingleXxxInterfaceNoInitMode(new TestChipV1(), HDM_CREATE_IFACE_AP, "wlan0",
+                TestChipV1.AP_CHIP_MODE_ID);
     }
 
     /**
@@ -734,8 +800,8 @@
      */
     @Test
     public void testCreateP2pInterfaceNoInitModeTestChipV1() throws Exception {
-        runCreateSingleXxxInterfaceNoInitMode(new TestChipV1(), IfaceType.P2P, "p2p0",
-                TestChipV1.STA_CHIP_MODE_ID, false);
+        runCreateSingleXxxInterfaceNoInitMode(new TestChipV1(), HDM_CREATE_IFACE_P2P, "p2p0",
+                TestChipV1.STA_CHIP_MODE_ID);
     }
 
     /**
@@ -743,32 +809,19 @@
      */
     @Test
     public void testCreateNanInterfaceNoInitModeTestChipV1() throws Exception {
-        runCreateSingleXxxInterfaceNoInitMode(new TestChipV1(), IfaceType.NAN, "wlan0",
-                TestChipV1.STA_CHIP_MODE_ID, false);
+        runCreateSingleXxxInterfaceNoInitMode(new TestChipV1(), HDM_CREATE_IFACE_NAN, "wlan0",
+                TestChipV1.STA_CHIP_MODE_ID);
     }
 
     // TestChipV2
 
     /**
-     * Validate creation of STA interface from blank start-up. The remove interface.
-     */
-    @Test
-    public void testCreateStaInterfaceNoInitModeTestChipV2() throws Exception {
-        // Note: we expected 2 available callbacks since we now have 2 STAs possible. So
-        // we get callback 1 after creating the first STA (since we can create another STA),
-        // and we get callback 2 after destroying the first STA (since we can create another STA -
-        // as expected).
-        runCreateSingleXxxInterfaceNoInitMode(new TestChipV2(), IfaceType.STA, "wlan0",
-                TestChipV2.CHIP_MODE_ID, true);
-    }
-
-    /**
      * Validate creation of AP interface from blank start-up. The remove interface.
      */
     @Test
     public void testCreateApInterfaceNoInitModeTestChipV2() throws Exception {
-        runCreateSingleXxxInterfaceNoInitMode(new TestChipV2(), IfaceType.AP, "wlan0",
-                TestChipV2.CHIP_MODE_ID, false);
+        runCreateSingleXxxInterfaceNoInitMode(new TestChipV2(), HDM_CREATE_IFACE_AP, "wlan0",
+                TestChipV2.CHIP_MODE_ID);
     }
 
     /**
@@ -776,8 +829,8 @@
      */
     @Test
     public void testCreateP2pInterfaceNoInitModeTestChipV2() throws Exception {
-        runCreateSingleXxxInterfaceNoInitMode(new TestChipV2(), IfaceType.P2P, "p2p0",
-                TestChipV2.CHIP_MODE_ID, false);
+        runCreateSingleXxxInterfaceNoInitMode(new TestChipV2(), HDM_CREATE_IFACE_P2P, "p2p0",
+                TestChipV2.CHIP_MODE_ID);
     }
 
     /**
@@ -785,32 +838,18 @@
      */
     @Test
     public void testCreateNanInterfaceNoInitModeTestChipV2() throws Exception {
-        runCreateSingleXxxInterfaceNoInitMode(new TestChipV2(), IfaceType.NAN, "wlan0",
-                TestChipV2.CHIP_MODE_ID, false);
+        runCreateSingleXxxInterfaceNoInitMode(new TestChipV2(), HDM_CREATE_IFACE_NAN, "wlan0",
+                TestChipV2.CHIP_MODE_ID);
     }
 
     // TestChipV3
-
-    /**
-     * Validate creation of STA interface from blank start-up. The remove interface.
-     */
-    @Test
-    public void testCreateStaInterfaceNoInitModeTestChipV3() throws Exception {
-        // Note: we expected 2 available callbacks since we now have 2 STAs possible. So
-        // we get callback 1 after creating the first STA (since we can create another STA),
-        // and we get callback 2 after destroying the first STA (since we can create another STA -
-        // as expected).
-        runCreateSingleXxxInterfaceNoInitMode(new TestChipV3(), IfaceType.STA, "wlan0",
-                TestChipV3.CHIP_MODE_ID, true);
-    }
-
     /**
      * Validate creation of AP interface from blank start-up. The remove interface.
      */
     @Test
     public void testCreateApInterfaceNoInitModeTestChipV3() throws Exception {
-        runCreateSingleXxxInterfaceNoInitMode(new TestChipV3(), IfaceType.AP, "wlan0",
-                TestChipV3.CHIP_MODE_ID, false);
+        runCreateSingleXxxInterfaceNoInitMode(new TestChipV3(), HDM_CREATE_IFACE_AP, "wlan0",
+                TestChipV3.CHIP_MODE_ID);
     }
 
     /**
@@ -818,8 +857,8 @@
      */
     @Test
     public void testCreateP2pInterfaceNoInitModeTestChipV3() throws Exception {
-        runCreateSingleXxxInterfaceNoInitMode(new TestChipV3(), IfaceType.P2P, "p2p0",
-                TestChipV3.CHIP_MODE_ID, false);
+        runCreateSingleXxxInterfaceNoInitMode(new TestChipV3(), HDM_CREATE_IFACE_P2P, "p2p0",
+                TestChipV3.CHIP_MODE_ID);
     }
 
     /**
@@ -827,8 +866,8 @@
      */
     @Test
     public void testCreateNanInterfaceNoInitModeTestChipV3() throws Exception {
-        runCreateSingleXxxInterfaceNoInitMode(new TestChipV3(), IfaceType.NAN, "wlan0",
-                TestChipV3.CHIP_MODE_ID, false);
+        runCreateSingleXxxInterfaceNoInitMode(new TestChipV3(), HDM_CREATE_IFACE_NAN, "wlan0",
+                TestChipV3.CHIP_MODE_ID);
     }
 
     // TestChipV4
@@ -838,8 +877,8 @@
      */
     @Test
     public void testCreateStaInterfaceNoInitModeTestChipV4() throws Exception {
-        runCreateSingleXxxInterfaceNoInitMode(new TestChipV4(), IfaceType.STA, "wlan0",
-                TestChipV4.CHIP_MODE_ID, false);
+        runCreateSingleXxxInterfaceNoInitMode(new TestChipV4(), HDM_CREATE_IFACE_STA, "wlan0",
+                TestChipV4.CHIP_MODE_ID);
     }
 
     /**
@@ -847,8 +886,8 @@
      */
     @Test
     public void testCreateApInterfaceNoInitModeTestChipV4() throws Exception {
-        runCreateSingleXxxInterfaceNoInitMode(new TestChipV4(), IfaceType.AP, "wlan0",
-                TestChipV4.CHIP_MODE_ID, false);
+        runCreateSingleXxxInterfaceNoInitMode(new TestChipV4(), HDM_CREATE_IFACE_AP, "wlan0",
+                TestChipV4.CHIP_MODE_ID);
     }
 
     /**
@@ -856,8 +895,8 @@
      */
     @Test
     public void testCreateP2pInterfaceNoInitModeTestChipV4() throws Exception {
-        runCreateSingleXxxInterfaceNoInitMode(new TestChipV4(), IfaceType.P2P, "p2p0",
-                TestChipV4.CHIP_MODE_ID, false);
+        runCreateSingleXxxInterfaceNoInitMode(new TestChipV4(), HDM_CREATE_IFACE_P2P, "p2p0",
+                TestChipV4.CHIP_MODE_ID);
     }
 
     /**
@@ -865,8 +904,8 @@
      */
     @Test
     public void testCreateNanInterfaceNoInitModeTestChipV4() throws Exception {
-        runCreateSingleXxxInterfaceNoInitMode(new TestChipV4(), IfaceType.NAN, "wlan0",
-                TestChipV4.CHIP_MODE_ID, false);
+        runCreateSingleXxxInterfaceNoInitMode(new TestChipV4(), HDM_CREATE_IFACE_NAN, "wlan0",
+                TestChipV4.CHIP_MODE_ID);
     }
 
     //////////////////////////////////////////////////////////////////////////////////////
@@ -883,30 +922,26 @@
 
         TestChipV1 chipMock = new TestChipV1();
         chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
                 mManagerStatusListenerMock);
         executeAndValidateInitializationSequence();
         executeAndValidateStartupSequence();
 
         InterfaceDestroyedListener idl = mock(
                 InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener iafrl = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
 
         IWifiApIface iface = (IWifiApIface) validateInterfaceSequence(chipMock,
                 true, // chipModeValid
                 TestChipV1.STA_CHIP_MODE_ID, // chipModeId
-                IfaceType.AP, // ifaceTypeToCreate
+                HDM_CREATE_IFACE_AP, // ifaceTypeToCreate
                 name, // ifaceName
                 TestChipV1.AP_CHIP_MODE_ID, // finalChipMode
                 null, // tearDownList
                 idl, // destroyedListener
-                iafrl // availableListener
+                TEST_WORKSOURCE_0 // requestorWs
         );
         collector.checkThat("allocated interface", iface, IsNull.notNullValue());
 
-        verify(iafrl).onAvailabilityChanged(false);
-
         // act: stop Wi-Fi
         mDut.stop();
         mTestLooper.dispatchAll();
@@ -915,7 +950,7 @@
         verify(idl).onDestroyed(getName(iface));
         verify(mManagerStatusListenerMock, times(2)).onStatusChanged();
 
-        verifyNoMoreInteractions(mManagerStatusListenerMock, idl, iafrl);
+        verifyNoMoreInteractions(mManagerStatusListenerMock, idl);
     }
 
     /**
@@ -929,15 +964,11 @@
 
         InterfaceDestroyedListener staIdl = mock(
                 InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener staIafrl = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
         InterfaceDestroyedListener apIdl = mock(
                 InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener apIafrl = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
 
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
-                mManagerStatusListenerMock, staIdl, staIafrl, apIdl, apIafrl);
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
+                mManagerStatusListenerMock, staIdl, apIdl);
         executeAndValidateInitializationSequence();
 
         // Register listener & start Wi-Fi
@@ -945,12 +976,6 @@
         assertTrue(mDut.start());
         mInOrder.verify(mManagerStatusListenerMock).onStatusChanged();
 
-        mDut.registerInterfaceAvailableForRequestListener(IfaceType.STA, staIafrl, null);
-        mDut.registerInterfaceAvailableForRequestListener(IfaceType.AP, apIafrl, null);
-
-        mInOrder.verify(staIafrl).onAvailabilityChanged(true);
-        mInOrder.verify(apIafrl).onAvailabilityChanged(true);
-
         // Create STA Iface first.
         IWifiStaIface staIface = mock(IWifiStaIface.class);
         doAnswer(new GetNameAnswer("wlan0")).when(staIface).getName(
@@ -959,10 +984,9 @@
                 any(IWifiIface.getTypeCallback.class));
         doAnswer(new CreateXxxIfaceAnswer(chipMock, mStatusOk, staIface)).when(
                 chipMock.chip).createStaIface(any(IWifiChip.createStaIfaceCallback.class));
-        assertEquals(staIface, mDut.createStaIface(staIdl, null));
+        assertEquals(staIface, mDut.createStaIface(staIdl, null, TEST_WORKSOURCE_0));
 
         mInOrder.verify(chipMock.chip).configureChip(TestChipV1.STA_CHIP_MODE_ID);
-        mInOrder.verify(staIafrl).onAvailabilityChanged(false);
 
         // Now Create AP Iface.
         IWifiApIface apIface = mock(IWifiApIface.class);
@@ -971,14 +995,13 @@
         doAnswer(new GetTypeAnswer(IfaceType.AP)).when(apIface).getType(
                 any(IWifiIface.getTypeCallback.class));
         doAnswer(new CreateXxxIfaceAnswer(chipMock, mStatusOk, apIface)).when(
-                chipMock.chip).createApIface(any(IWifiChip.createApIfaceCallback.class));
-        assertEquals(apIface, mDut.createApIface(apIdl, null));
+                chipMock.chip).createApIface(
+                any(IWifiChip.createApIfaceCallback.class));
+        assertEquals(apIface, mDut.createApIface(apIdl, null, TEST_WORKSOURCE_0, false));
 
         mInOrder.verify(chipMock.chip).removeStaIface(getName(staIface));
         mInOrder.verify(staIdl).onDestroyed(getName(staIface));
         mInOrder.verify(chipMock.chip).configureChip(TestChipV1.AP_CHIP_MODE_ID);
-        mInOrder.verify(apIafrl).onAvailabilityChanged(false);
-        mInOrder.verify(staIafrl).onAvailabilityChanged(true);
 
         // Stop Wi-Fi
         mDut.stop();
@@ -987,7 +1010,7 @@
         mInOrder.verify(mManagerStatusListenerMock).onStatusChanged();
         mInOrder.verify(apIdl).onDestroyed(getName(apIface));
 
-        verifyNoMoreInteractions(mManagerStatusListenerMock, staIdl, staIafrl, apIdl, apIafrl);
+        verifyNoMoreInteractions(mManagerStatusListenerMock, staIdl, apIdl);
     }
 
     /**
@@ -1000,30 +1023,26 @@
 
         TestChipV1 chipMock = new TestChipV1();
         chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
                 mManagerStatusListenerMock);
         executeAndValidateInitializationSequence();
         executeAndValidateStartupSequence();
 
         InterfaceDestroyedListener idl = mock(
                 InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener iafrl = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
 
         IWifiApIface iface = (IWifiApIface) validateInterfaceSequence(chipMock,
                 true, // chipModeValid
                 TestChipV1.AP_CHIP_MODE_ID, // chipModeId
-                IfaceType.AP, // ifaceTypeToCreate
+                HDM_CREATE_IFACE_AP, // ifaceTypeToCreate
                 name, // ifaceName
                 TestChipV1.AP_CHIP_MODE_ID, // finalChipMode
                 null, // tearDownList
                 idl, // destroyedListener
-                iafrl // availableListener
+                TEST_WORKSOURCE_0 // requestorWs
         );
         collector.checkThat("allocated interface", iface, IsNull.notNullValue());
 
-        verify(iafrl).onAvailabilityChanged(false);
-
         // act: stop Wi-Fi
         mDut.stop();
         mTestLooper.dispatchAll();
@@ -1032,215 +1051,7 @@
         verify(idl).onDestroyed(getName(iface));
         verify(mManagerStatusListenerMock, times(2)).onStatusChanged();
 
-        verifyNoMoreInteractions(mManagerStatusListenerMock, idl, iafrl);
-    }
-
-    /**
-     * Validate AP up/down creation of AP interface when a STA already created. Expect:
-     * - STA created
-     * - P2P created
-     * - When AP requested:
-     *   - STA & P2P torn down
-     *   - AP created
-     * - P2P creation refused
-     * - Request STA: will tear down AP
-     * - When AP destroyed:
-     *   - Get p2p available listener callback
-     *   - Can create P2P when requested
-     * - Create P2P
-     * - Request NAN: will get refused
-     * - Tear down P2P:
-     *    - should get nan available listener callback
-     *    - Can create NAN when requested
-     */
-    @Test
-    public void testCreateSameAndDiffPrioritiesTestChipV1() throws Exception {
-        TestChipV1 chipMock = new TestChipV1();
-        chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
-                mManagerStatusListenerMock);
-        executeAndValidateInitializationSequence();
-        executeAndValidateStartupSequence();
-
-        InterfaceDestroyedListener staDestroyedListener = mock(
-                InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener staAvailListener = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
-
-        InterfaceDestroyedListener staDestroyedListener2 = mock(
-                InterfaceDestroyedListener.class);
-
-        InterfaceDestroyedListener apDestroyedListener = mock(
-                InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener apAvailListener = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
-
-        InterfaceDestroyedListener p2pDestroyedListener = mock(
-                InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener p2pAvailListener = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
-
-        InterfaceDestroyedListener p2pDestroyedListener2 = mock(
-                InterfaceDestroyedListener.class);
-
-        InterfaceDestroyedListener nanDestroyedListener = mock(
-                InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener nanAvailListener = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
-
-        InOrder inOrderAvail = inOrder(staAvailListener, apAvailListener, p2pAvailListener,
-                nanAvailListener);
-
-        // register listeners for interface availability
-        mDut.registerInterfaceAvailableForRequestListener(IfaceType.STA, staAvailListener,
-                mHandler);
-        mDut.registerInterfaceAvailableForRequestListener(IfaceType.AP, apAvailListener, mHandler);
-        mDut.registerInterfaceAvailableForRequestListener(IfaceType.P2P, p2pAvailListener,
-                mHandler);
-        mDut.registerInterfaceAvailableForRequestListener(IfaceType.NAN, nanAvailListener,
-                mHandler);
-        mTestLooper.dispatchAll();
-
-        inOrderAvail.verify(staAvailListener).onAvailabilityChanged(true);
-        inOrderAvail.verify(apAvailListener).onAvailabilityChanged(true);
-        inOrderAvail.verify(p2pAvailListener).onAvailabilityChanged(true);
-        inOrderAvail.verify(nanAvailListener).onAvailabilityChanged(true);
-
-        // Request STA
-        IWifiIface staIface = validateInterfaceSequence(chipMock,
-                false, // chipModeValid
-                -1000, // chipModeId (only used if chipModeValid is true)
-                IfaceType.STA, // ifaceTypeToCreate
-                "wlan0", // ifaceName
-                TestChipV1.STA_CHIP_MODE_ID, // finalChipMode
-                null, // tearDownList
-                staDestroyedListener, // destroyedListener
-                null // availableListener
-        );
-        collector.checkThat("allocated STA interface", staIface, IsNull.notNullValue());
-
-        inOrderAvail.verify(staAvailListener).onAvailabilityChanged(false);
-
-        // request STA2: should fail
-        IWifiIface staIface2 = mDut.createStaIface(null, null);
-        collector.checkThat("STA2 should not be created", staIface2, IsNull.nullValue());
-
-        // register additional InterfaceDestroyedListeners - including a duplicate (verify that
-        // only called once!)
-        mDut.registerDestroyedListener(staIface, staDestroyedListener2, mHandler);
-        mDut.registerDestroyedListener(staIface, staDestroyedListener, mHandler);
-
-        // Request P2P
-        IWifiIface p2pIface = validateInterfaceSequence(chipMock,
-                true, // chipModeValid
-                TestChipV1.STA_CHIP_MODE_ID, // chipModeId
-                IfaceType.P2P, // ifaceTypeToCreate
-                "p2p0", // ifaceName
-                TestChipV1.STA_CHIP_MODE_ID, // finalChipMode
-                null, // tearDownList
-                p2pDestroyedListener, // destroyedListener
-                null // availableListener
-        );
-        collector.checkThat("allocated P2P interface", p2pIface, IsNull.notNullValue());
-
-        inOrderAvail.verify(p2pAvailListener).onAvailabilityChanged(false);
-
-        // Request AP
-        IWifiIface apIface = validateInterfaceSequence(chipMock,
-                true, // chipModeValid
-                TestChipV1.STA_CHIP_MODE_ID, // chipModeId
-                IfaceType.AP, // ifaceTypeToCreate
-                "wlan0", // ifaceName
-                TestChipV1.AP_CHIP_MODE_ID, // finalChipMode
-                new IWifiIface[]{staIface, p2pIface}, // tearDownList
-                apDestroyedListener, // destroyedListener
-                null, // availableListener
-                // destroyedInterfacesDestroyedListeners...
-                new InterfaceDestroyedListenerWithIfaceName(
-                        getName(staIface), staDestroyedListener),
-                new InterfaceDestroyedListenerWithIfaceName(
-                        getName(staIface), staDestroyedListener2),
-                new InterfaceDestroyedListenerWithIfaceName(
-                        getName(p2pIface), p2pDestroyedListener)
-        );
-        collector.checkThat("allocated AP interface", apIface, IsNull.notNullValue());
-
-        inOrderAvail.verify(apAvailListener).onAvailabilityChanged(false);
-        inOrderAvail.verify(staAvailListener).onAvailabilityChanged(true);
-        inOrderAvail.verify(nanAvailListener).onAvailabilityChanged(false);
-
-        // request AP2: should fail
-        IWifiIface apIface2 = mDut.createApIface(null, null);
-        collector.checkThat("AP2 should not be created", apIface2, IsNull.nullValue());
-
-        // Request P2P: expect failure
-        p2pIface = mDut.createP2pIface(p2pDestroyedListener, mHandler);
-        collector.checkThat("P2P can't be created", p2pIface, IsNull.nullValue());
-
-        // Request STA: expect success
-        staIface = validateInterfaceSequence(chipMock,
-                true, // chipModeValid
-                TestChipV1.AP_CHIP_MODE_ID, // chipModeId
-                IfaceType.STA, // ifaceTypeToCreate
-                "wlan0", // ifaceName
-                TestChipV1.STA_CHIP_MODE_ID, // finalChipMode
-                null, // tearDownList
-                staDestroyedListener, // destroyedListener
-                null, // availableListener
-                // destroyedInterfacesDestroyedListeners...
-                new InterfaceDestroyedListenerWithIfaceName(
-                        getName(apIface), apDestroyedListener)
-        );
-        collector.checkThat("allocated STA interface", staIface, IsNull.notNullValue());
-        inOrderAvail.verify(apAvailListener).onAvailabilityChanged(true);
-        inOrderAvail.verify(staAvailListener).onAvailabilityChanged(false);
-        inOrderAvail.verify(p2pAvailListener).onAvailabilityChanged(true);
-        inOrderAvail.verify(nanAvailListener).onAvailabilityChanged(true);
-
-        mTestLooper.dispatchAll();
-        verify(apDestroyedListener).onDestroyed(getName(apIface));
-
-        // Request P2P: expect success now
-        p2pIface = validateInterfaceSequence(chipMock,
-                true, // chipModeValid
-                TestChipV1.STA_CHIP_MODE_ID, // chipModeId
-                IfaceType.P2P, // ifaceTypeToCreate
-                "p2p0", // ifaceName
-                TestChipV1.STA_CHIP_MODE_ID, // finalChipMode
-                null, // tearDownList
-                p2pDestroyedListener2, // destroyedListener
-                null // availableListener
-        );
-        collector.checkThat("allocated P2P interface", p2pIface, IsNull.notNullValue());
-        inOrderAvail.verify(p2pAvailListener).onAvailabilityChanged(false);
-
-        // create NAN: will destroy P2P
-        IWifiIface nanIface = validateInterfaceSequence(chipMock,
-                true, // chipModeValid
-                TestChipV1.STA_CHIP_MODE_ID, // chipModeId
-                IfaceType.NAN, // ifaceTypeToCreate
-                "wlan0", // ifaceName
-                TestChipV1.STA_CHIP_MODE_ID, // finalChipMode
-                null, // tearDownList
-                nanDestroyedListener, // destroyedListener
-                nanAvailListener, // availableListener
-                new InterfaceDestroyedListenerWithIfaceName("p2p0", p2pDestroyedListener2)
-        );
-        collector.checkThat("allocated NAN interface", nanIface, IsNull.notNullValue());
-        inOrderAvail.verify(p2pAvailListener).onAvailabilityChanged(true);
-        inOrderAvail.verify(nanAvailListener).onAvailabilityChanged(false);
-
-        // Tear down NAN
-        mDut.removeIface(nanIface);
-        mTestLooper.dispatchAll();
-
-        inOrderAvail.verify(nanAvailListener).onAvailabilityChanged(true);
-        verify(chipMock.chip, times(1)).removeNanIface("wlan0");
-        verify(nanDestroyedListener).onDestroyed(getName(nanIface));
-
-        verifyNoMoreInteractions(mManagerStatusListenerMock, staDestroyedListener, staAvailListener,
-                staDestroyedListener2, apDestroyedListener, apAvailListener, p2pDestroyedListener,
-                nanDestroyedListener, nanAvailListener, p2pDestroyedListener2);
+        verifyNoMoreInteractions(mManagerStatusListenerMock, idl);
     }
 
     /**
@@ -1261,47 +1072,47 @@
     }
 
     /**
-     * Validates that trying to allocate a STA and then another STA fails. Only one STA at a time
-     * is permitted (by TestChipV1 chip).
+     * Validates that trying to allocate a STA from a lower priority app and then another STA from
+     * a privileged app exists, the request fails. Only one STA at a time is permitted (by
+     * TestChipV1 chip).
      */
     @Test
-    public void testDuplicateStaRequestsTestChipV1() throws Exception {
+    public void testDuplicateStaRequestsFromLowerPriorityAppTestChipV1() throws Exception {
         TestChipV1 chipMock = new TestChipV1();
         chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
                 mManagerStatusListenerMock);
         executeAndValidateInitializationSequence();
         executeAndValidateStartupSequence();
 
         InterfaceDestroyedListener staDestroyedListener1 = mock(
                 InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener staAvailListener1 = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
 
         InterfaceDestroyedListener staDestroyedListener2 = mock(
                 InterfaceDestroyedListener.class);
 
-        // get STA interface
+        // get STA interface (from a privileged app)
         IWifiIface staIface1 = validateInterfaceSequence(chipMock,
                 false, // chipModeValid
                 -1000, // chipModeId (only used if chipModeValid is true)
-                IfaceType.STA, // ifaceTypeToCreate
+                HDM_CREATE_IFACE_STA, // ifaceTypeToCreate
                 "wlan0", // ifaceName
                 TestChipV1.STA_CHIP_MODE_ID, // finalChipMode
                 null, // tearDownList
                 staDestroyedListener1, // destroyedListener
-                staAvailListener1 // availableListener
+                TEST_WORKSOURCE_0 // requestorWs
         );
         collector.checkThat("STA created", staIface1, IsNull.notNullValue());
 
-        verify(staAvailListener1).onAvailabilityChanged(false);
-
-        // get STA interface again
-        IWifiIface staIface2 = mDut.createStaIface(staDestroyedListener2, mHandler);
+        // get STA interface again (from a system app)
+        when(mWorkSourceHelper1.hasAnyPrivilegedAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper1.hasAnySystemAppRequest()).thenReturn(true);
+        IWifiIface staIface2 = mDut.createStaIface(
+                staDestroyedListener2, mHandler, TEST_WORKSOURCE_1);
         collector.checkThat("STA created", staIface2, IsNull.nullValue());
 
         verifyNoMoreInteractions(mManagerStatusListenerMock, staDestroyedListener1,
-                staAvailListener1, staDestroyedListener2);
+                staDestroyedListener2);
     }
 
     /**
@@ -1311,7 +1122,7 @@
     public void testGetSupportedIfaceTypesAllTestChipV1() throws Exception {
         TestChipV1 chipMock = new TestChipV1();
         chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
                 mManagerStatusListenerMock);
         executeAndValidateInitializationSequence();
         executeAndValidateStartupSequence();
@@ -1336,7 +1147,7 @@
     public void testGetSupportedIfaceTypesOneChipTestChipV1() throws Exception {
         TestChipV1 chipMock = new TestChipV1();
         chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
                 mManagerStatusListenerMock);
         executeAndValidateInitializationSequence();
         executeAndValidateStartupSequence();
@@ -1363,11 +1174,21 @@
 
         TestChipV1 chipMock = new TestChipV1();
         chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
                 mManagerStatusListenerMock);
         executeAndValidateInitializationSequence();
+        // Try to query iface support before starting the HAL. Should return false.
+        when(mWifiMock.isStarted()).thenReturn(false);
+        assertFalse(mDut.canSupportIfaceCombo(new SparseArray<Integer>() {{
+                put(IfaceType.STA, 1);
+            }}
+        ));
+        verify(mWifiMock, never()).getChipIds(any());
+        when(mWifiMock.isStarted()).thenReturn(true);
         executeAndValidateStartupSequence();
 
+        clearInvocations(mWifiMock);
+
         assertTrue(mDut.canSupportIfaceCombo(new SparseArray<Integer>() {{
                 put(IfaceType.STA, 1);
             }}
@@ -1410,9 +1231,101 @@
             }}
         ));
 
+        // Ensure we only fetched chip info once, use the cache after that.
+        verify(mWifiMock, times(1)).getChipIds(any());
+
         verifyNoMoreInteractions(mManagerStatusListenerMock);
     }
 
+    @Test
+    public void testIsItPossibleToCreateIfaceTestChipV1() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        final String name = "wlan0";
+
+        TestChipV1 chipMock = new TestChipV1();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        // get STA interface from system app.
+        when(mWorkSourceHelper0.hasAnyPrivilegedAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper0.hasAnySystemAppRequest()).thenReturn(true);
+        IWifiIface staIface = validateInterfaceSequence(chipMock,
+                false, // chipModeValid
+                -1000, // chipModeId (only used if chipModeValid is true)
+                HDM_CREATE_IFACE_STA, // ifaceTypeToCreate
+                "wlan0", // ifaceName
+                TestChipV1.STA_CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                mock(InterfaceDestroyedListener.class), // destroyedListener
+                TEST_WORKSOURCE_0 // requestorWs
+        );
+        collector.checkThat("STA created", staIface, IsNull.notNullValue());
+
+        // FG app not allowed to create AP interface.
+        when(mWorkSourceHelper1.hasAnyPrivilegedAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper1.hasAnyForegroundAppRequest()).thenReturn(true);
+        assertFalse(mDut.isItPossibleToCreateIface(IfaceType.AP, TEST_WORKSOURCE_1));
+
+        // New system app not allowed to create AP interface.
+        when(mWorkSourceHelper1.hasAnyForegroundAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper1.hasAnySystemAppRequest()).thenReturn(true);
+        assertFalse(mDut.isItPossibleToCreateIface(IfaceType.AP, TEST_WORKSOURCE_1));
+
+        // Privileged app allowed to create AP interface.
+        when(mWorkSourceHelper1.hasAnySystemAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper1.hasAnyPrivilegedAppRequest()).thenReturn(true);
+        assertTrue(mDut.isItPossibleToCreateIface(IfaceType.AP, TEST_WORKSOURCE_1));
+
+        // FG app allowed to create NAN interface (since there is no need to delete any interfaces).
+        when(mWorkSourceHelper1.hasAnyPrivilegedAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper1.hasAnyForegroundAppRequest()).thenReturn(true);
+        assertTrue(mDut.isItPossibleToCreateIface(IfaceType.NAN, TEST_WORKSOURCE_1));
+
+        // BG app allowed to create P2P interface (since there is no need to delete any interfaces).
+        when(mWorkSourceHelper1.hasAnyForegroundAppRequest()).thenReturn(false);
+        assertTrue(mDut.isItPossibleToCreateIface(IfaceType.P2P, TEST_WORKSOURCE_1));
+    }
+
+    @Test
+    public void testIsItPossibleToCreateIfaceTestChipV1ForR() throws Exception {
+        assumeFalse(SdkLevel.isAtLeastS());
+        final String name = "wlan0";
+
+        TestChipV1 chipMock = new TestChipV1();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        // get STA interface.
+        IWifiIface staIface = validateInterfaceSequence(chipMock,
+                false, // chipModeValid
+                -1000, // chipModeId (only used if chipModeValid is true)
+                HDM_CREATE_IFACE_STA, // ifaceTypeToCreate
+                "wlan0", // ifaceName
+                TestChipV1.STA_CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                mock(InterfaceDestroyedListener.class), // destroyedListener
+                TEST_WORKSOURCE_0 // requestorWs
+        );
+        collector.checkThat("STA created", staIface, IsNull.notNullValue());
+
+        // Allowed to create AP interface (since AP can teardown STA interface)
+        assertTrue(mDut.isItPossibleToCreateIface(IfaceType.AP, TEST_WORKSOURCE_1));
+
+        // Allow to create NAN interface (since there is no need to delete any interfaces).
+        when(mWorkSourceHelper1.hasAnyPrivilegedAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper1.hasAnyForegroundAppRequest()).thenReturn(true);
+        assertTrue(mDut.isItPossibleToCreateIface(IfaceType.NAN, TEST_WORKSOURCE_1));
+
+        // Allow to create P2P interface (since there is no need to delete any interfaces).
+        when(mWorkSourceHelper1.hasAnyForegroundAppRequest()).thenReturn(false);
+        assertTrue(mDut.isItPossibleToCreateIface(IfaceType.P2P, TEST_WORKSOURCE_1));
+    }
 
     //////////////////////////////////////////////////////////////////////////////////////
     // TestChipV2 Specific Tests
@@ -1420,24 +1333,26 @@
 
     /**
      * Validate a flow sequence for test chip 2:
-     * - create STA
-     * - create P2P
-     * - request NAN: failure
-     * - create AP
-     * - create STA: will get refused
-     * - create AP: will get refused
+     * - create STA (system app)
+     * - create P2P (system app)
+     * - create NAN (privileged app): should tear down P2P first
+     * - create AP (privileged app)
+     * - create STA (system app): will get refused
+     * - create AP (system app): will get refuse
      * - tear down AP
-     * - create STA
-     * - create STA: will get refused
-     * - create AP: should get created and the last created STA should get destroyed
+     * - create STA (system app)
+     * - create STA (system app): will get refused
+     * - create AP (privileged app): should get created and the last created STA should get
+     *   destroyed
      * - tear down P2P
-     * - create NAN
+     * - create NAN (system app)
      */
     @Test
     public void testInterfaceCreationFlowTestChipV2() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
         TestChipV2 chipMock = new TestChipV2();
         chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
                 mManagerStatusListenerMock);
         executeAndValidateInitializationSequence();
         executeAndValidateStartupSequence();
@@ -1446,165 +1361,128 @@
                 InterfaceDestroyedListener.class);
         InterfaceDestroyedListener staDestroyedListener2 = mock(
                 InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener staAvailListener = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
 
         InterfaceDestroyedListener apDestroyedListener = mock(
                 InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener apAvailListener = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
 
         InterfaceDestroyedListener p2pDestroyedListener = mock(
                 InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener p2pAvailListener = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
 
         InterfaceDestroyedListener nanDestroyedListener = mock(
                 InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener nanAvailListener = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
 
-        InOrder inOrderAvail = inOrder(staAvailListener, apAvailListener, p2pAvailListener,
-                nanAvailListener);
-
-        // register listeners for interface availability
-        mDut.registerInterfaceAvailableForRequestListener(IfaceType.STA, staAvailListener,
-                mHandler);
-        mDut.registerInterfaceAvailableForRequestListener(IfaceType.AP, apAvailListener, mHandler);
-        mDut.registerInterfaceAvailableForRequestListener(IfaceType.P2P, p2pAvailListener,
-                mHandler);
-        mDut.registerInterfaceAvailableForRequestListener(IfaceType.NAN, nanAvailListener,
-                mHandler);
-        mTestLooper.dispatchAll();
-
-        inOrderAvail.verify(staAvailListener).onAvailabilityChanged(true);
-        inOrderAvail.verify(apAvailListener).onAvailabilityChanged(true);
-        inOrderAvail.verify(p2pAvailListener).onAvailabilityChanged(true);
-        inOrderAvail.verify(nanAvailListener).onAvailabilityChanged(true);
-
-        // create STA
+        // create STA (system app)
         when(mClock.getUptimeSinceBootMillis()).thenReturn(15L);
+        when(mWorkSourceHelper0.hasAnyPrivilegedAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper0.hasAnySystemAppRequest()).thenReturn(true);
         IWifiIface staIface = validateInterfaceSequence(chipMock,
                 false, // chipModeValid
                 -1000, // chipModeId (only used if chipModeValid is true)
-                IfaceType.STA, // ifaceTypeToCreate
+                HDM_CREATE_IFACE_STA, // ifaceTypeToCreate
                 "wlan0", // ifaceName
                 TestChipV2.CHIP_MODE_ID, // finalChipMode
                 null, // tearDownList
                 staDestroyedListener, // destroyedListener
-                null // availableListener (already registered)
+                TEST_WORKSOURCE_0 // requestorWs
         );
         collector.checkThat("STA interface wasn't created", staIface, IsNull.notNullValue());
 
-        // create P2P
+        // create P2P (system app)
+        when(mWorkSourceHelper1.hasAnyPrivilegedAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper1.hasAnySystemAppRequest()).thenReturn(true);
         IWifiIface p2pIface = validateInterfaceSequence(chipMock,
                 true, // chipModeValid
                 TestChipV2.CHIP_MODE_ID, // chipModeId
-                IfaceType.P2P, // ifaceTypeToCreate
+                HDM_CREATE_IFACE_P2P, // ifaceTypeToCreate
                 "p2p0", // ifaceName
                 TestChipV2.CHIP_MODE_ID, // finalChipMode
                 null, // tearDownList
                 p2pDestroyedListener, // destroyedListener
-                null // availableListener (already registered)
+                TEST_WORKSOURCE_1 // requestorWs
         );
         collector.checkThat("P2P interface wasn't created", p2pIface, IsNull.notNullValue());
 
-        inOrderAvail.verify(p2pAvailListener).onAvailabilityChanged(false);
-
-        // create NAN
+        // create NAN (system app)
         IWifiIface nanIface = validateInterfaceSequence(chipMock,
                 true, // chipModeValid
                 TestChipV2.CHIP_MODE_ID, // chipModeId
-                IfaceType.NAN, // ifaceTypeToCreate
+                HDM_CREATE_IFACE_NAN, // ifaceTypeToCreate
                 "wlan0", // ifaceName
                 TestChipV2.CHIP_MODE_ID, // finalChipMode
                 null, // tearDownList
                 nanDestroyedListener, // destroyedListener
-                null, // availableListener (already registered)
+                TEST_WORKSOURCE_2, // requestorWs
                 new InterfaceDestroyedListenerWithIfaceName(
                         getName(p2pIface), p2pDestroyedListener)
         );
         collector.checkThat("NAN interface wasn't created", nanIface, IsNull.notNullValue());
 
-        inOrderAvail.verify(p2pAvailListener).onAvailabilityChanged(true);
-        inOrderAvail.verify(nanAvailListener).onAvailabilityChanged(false);
-
-        // create AP
+        // create AP (privileged app)
         IWifiIface apIface = validateInterfaceSequence(chipMock,
                 true, // chipModeValid
                 TestChipV2.CHIP_MODE_ID, // chipModeId
-                IfaceType.AP, // ifaceTypeToCreate
+                HDM_CREATE_IFACE_AP, // ifaceTypeToCreate
                 "wlan1", // ifaceName
                 TestChipV2.CHIP_MODE_ID, // finalChipMode
                 null, // tearDownList
                 apDestroyedListener, // destroyedListener
-                null // availableListener (already registered)
+                TEST_WORKSOURCE_2 // requestorWs
         );
         collector.checkThat("AP interface wasn't created", apIface, IsNull.notNullValue());
 
-        inOrderAvail.verify(apAvailListener).onAvailabilityChanged(false);
-        inOrderAvail.verify(staAvailListener).onAvailabilityChanged(false);
-
-        // request STA2: should fail
-        IWifiIface staIface2 = mDut.createStaIface(null, null);
+        // request STA2 (system app): should fail
+        IWifiIface staIface2 = mDut.createStaIface(null, null, TEST_WORKSOURCE_0);
         collector.checkThat("STA2 should not be created", staIface2, IsNull.nullValue());
 
-        // request AP2: should fail
-        IWifiIface apIface2 = mDut.createApIface(null, null);
+        // request AP2 (system app): should fail
+        IWifiIface apIface2 = mDut.createApIface(null, null, TEST_WORKSOURCE_0, false);
         collector.checkThat("AP2 should not be created", apIface2, IsNull.nullValue());
 
         // tear down AP
         mDut.removeIface(apIface);
         mTestLooper.dispatchAll();
 
-        inOrderAvail.verify(apAvailListener).onAvailabilityChanged(true);
-        inOrderAvail.verify(staAvailListener).onAvailabilityChanged(true);
         verify(chipMock.chip).removeApIface("wlan1");
         verify(apDestroyedListener).onDestroyed(getName(apIface));
 
-        // create STA2: using a later clock
+        // create STA2 (system app): using a later clock
         when(mClock.getUptimeSinceBootMillis()).thenReturn(20L);
         staIface2 = validateInterfaceSequence(chipMock,
                 true, // chipModeValid
                 TestChipV2.CHIP_MODE_ID, // chipModeId
-                IfaceType.STA, // ifaceTypeToCreate
+                HDM_CREATE_IFACE_STA, // ifaceTypeToCreate
                 "wlan1", // ifaceName
                 TestChipV2.CHIP_MODE_ID, // finalChipMode
                 null, // tearDownList
                 staDestroyedListener2, // destroyedListener
-                null // availableListener (already registered)
+                TEST_WORKSOURCE_0 // requestorWs
         );
         collector.checkThat("STA 2 interface wasn't created", staIface2, IsNull.notNullValue());
 
-        inOrderAvail.verify(staAvailListener).onAvailabilityChanged(false);
-
-        // request STA3: should fail
-        IWifiIface staIface3 = mDut.createStaIface(null, null);
+        // request STA3 (system app): should fail
+        IWifiIface staIface3 = mDut.createStaIface(null, null, TEST_WORKSOURCE_0);
         collector.checkThat("STA3 should not be created", staIface3, IsNull.nullValue());
 
-        // create AP - this will destroy the last STA created, i.e. STA2
+        // create AP (privileged app) - this will destroy the last STA created, i.e. STA2
         apIface = validateInterfaceSequence(chipMock,
                 true, // chipModeValid
                 TestChipV2.CHIP_MODE_ID, // chipModeId
-                IfaceType.AP, // ifaceTypeToCreate
+                HDM_CREATE_IFACE_AP, // ifaceTypeToCreate
                 "wlan1", // ifaceName
                 TestChipV2.CHIP_MODE_ID, // finalChipMode
                 null, // tearDownList
                 apDestroyedListener, // destroyedListener
-                null, // availableListener (already registered),
+                TEST_WORKSOURCE_2, // requestorWs
                 // destroyedInterfacesDestroyedListeners...
                 new InterfaceDestroyedListenerWithIfaceName(
-                        getName(staIface2), staDestroyedListener2)
+                        getName(staIface), staDestroyedListener)
         );
         collector.checkThat("AP interface wasn't created", apIface, IsNull.notNullValue());
 
-        inOrderAvail.verify(apAvailListener).onAvailabilityChanged(false);
-
         // tear down NAN
         mDut.removeIface(nanIface);
         mTestLooper.dispatchAll();
 
-        inOrderAvail.verify(nanAvailListener).onAvailabilityChanged(true);
         verify(chipMock.chip).removeNanIface("wlan0");
         verify(nanDestroyedListener).onDestroyed(getName(nanIface));
 
@@ -1612,22 +1490,18 @@
         nanIface = validateInterfaceSequence(chipMock,
                 true, // chipModeValid
                 TestChipV2.CHIP_MODE_ID, // chipModeId
-                IfaceType.NAN, // ifaceTypeToCreate
+                HDM_CREATE_IFACE_NAN, // ifaceTypeToCreate
                 "wlan0", // ifaceName
                 TestChipV2.CHIP_MODE_ID, // finalChipMode
                 null, // tearDownList
                 nanDestroyedListener, // destroyedListener
-                null // availableListener (already registered)
+                TEST_WORKSOURCE_0 // requestorWs
         );
         collector.checkThat("NAN interface wasn't created", nanIface, IsNull.notNullValue());
 
-        inOrderAvail.verify(nanAvailListener).onAvailabilityChanged(false);
-
         verifyNoMoreInteractions(mManagerStatusListenerMock, staDestroyedListener,
                 staDestroyedListener2, apDestroyedListener, p2pDestroyedListener,
-                nanDestroyedListener, staAvailListener, apAvailListener, p2pAvailListener,
-                nanAvailListener, staAvailListener, apAvailListener, p2pAvailListener,
-                nanAvailListener);
+                nanDestroyedListener);
     }
 
     /**
@@ -1654,7 +1528,7 @@
     public void testGetSupportedIfaceTypesAllTestChipV2() throws Exception {
         TestChipV2 chipMock = new TestChipV2();
         chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
                 mManagerStatusListenerMock);
         executeAndValidateInitializationSequence();
         executeAndValidateStartupSequence();
@@ -1679,7 +1553,7 @@
     public void testGetSupportedIfaceTypesOneChipTestChipV2() throws Exception {
         TestChipV2 chipMock = new TestChipV2();
         chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
                 mManagerStatusListenerMock);
         executeAndValidateInitializationSequence();
         executeAndValidateStartupSequence();
@@ -1706,11 +1580,21 @@
 
         TestChipV2 chipMock = new TestChipV2();
         chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
                 mManagerStatusListenerMock);
         executeAndValidateInitializationSequence();
+        // Try to query iface support before starting the HAL. Should return false.
+        when(mWifiMock.isStarted()).thenReturn(false);
+        assertFalse(mDut.canSupportIfaceCombo(new SparseArray<Integer>() {{
+                put(IfaceType.STA, 1);
+            }}
+        ));
+        verify(mWifiMock, never()).getChipIds(any());
+        when(mWifiMock.isStarted()).thenReturn(true);
         executeAndValidateStartupSequence();
 
+        clearInvocations(mWifiMock);
+
         assertTrue(mDut.canSupportIfaceCombo(new SparseArray<Integer>() {{
                 put(IfaceType.STA, 1);
             }}
@@ -1779,33 +1663,104 @@
             }}
         ));
 
+        // Ensure we only fetched chip info once, use the cache after that.
+        verify(mWifiMock, times(1)).getChipIds(any());
+
         verifyNoMoreInteractions(mManagerStatusListenerMock);
     }
 
+    @Test
+    public void testIsItPossibleToCreateIfaceTestChipV2() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        final String name = "wlan0";
+
+        TestChipV2 chipMock = new TestChipV2();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        // get STA interface from system app.
+        when(mWorkSourceHelper0.hasAnyPrivilegedAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper0.hasAnySystemAppRequest()).thenReturn(true);
+        IWifiIface staIface = validateInterfaceSequence(chipMock,
+                false, // chipModeValid
+                -1000, // chipModeId (only used if chipModeValid is true)
+                HDM_CREATE_IFACE_STA, // ifaceTypeToCreate
+                "wlan0", // ifaceName
+                TestChipV2.CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                mock(InterfaceDestroyedListener.class), // destroyedListener
+                TEST_WORKSOURCE_0 // requestorWs
+        );
+        collector.checkThat("STA created", staIface, IsNull.notNullValue());
+
+        // get AP interface from system app.
+        when(mWorkSourceHelper0.hasAnyPrivilegedAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper0.hasAnySystemAppRequest()).thenReturn(true);
+        IWifiIface apIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                TestChipV2.CHIP_MODE_ID, // chipModeId
+                HDM_CREATE_IFACE_AP, // ifaceTypeToCreate
+                "wlan0", // ifaceName
+                TestChipV2.CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                mock(InterfaceDestroyedListener.class), // destroyedListener
+                TEST_WORKSOURCE_0 // requestorWs
+        );
+        collector.checkThat("AP created", apIface, IsNull.notNullValue());
+
+        // FG app not allowed to create STA interface.
+        when(mWorkSourceHelper1.hasAnyPrivilegedAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper1.hasAnyForegroundAppRequest()).thenReturn(true);
+        assertFalse(mDut.isItPossibleToCreateIface(IfaceType.STA, TEST_WORKSOURCE_1));
+
+        // New system app not allowed to create STA interface.
+        when(mWorkSourceHelper1.hasAnyForegroundAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper1.hasAnySystemAppRequest()).thenReturn(true);
+        assertFalse(mDut.isItPossibleToCreateIface(IfaceType.STA, TEST_WORKSOURCE_1));
+
+        // Privileged app allowed to create STA interface.
+        when(mWorkSourceHelper1.hasAnySystemAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper1.hasAnyPrivilegedAppRequest()).thenReturn(true);
+        assertTrue(mDut.isItPossibleToCreateIface(IfaceType.STA, TEST_WORKSOURCE_1));
+
+        // FG app allowed to create NAN interface (since there is no need to delete any interfaces).
+        when(mWorkSourceHelper1.hasAnyPrivilegedAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper1.hasAnyForegroundAppRequest()).thenReturn(true);
+        assertTrue(mDut.isItPossibleToCreateIface(IfaceType.NAN, TEST_WORKSOURCE_1));
+
+        // BG app allowed to create P2P interface (since there is no need to delete any interfaces).
+        when(mWorkSourceHelper1.hasAnyForegroundAppRequest()).thenReturn(false);
+        assertTrue(mDut.isItPossibleToCreateIface(IfaceType.P2P, TEST_WORKSOURCE_1));
+    }
+
     //////////////////////////////////////////////////////////////////////////////////////
     // TestChipV3 Specific Tests
     //////////////////////////////////////////////////////////////////////////////////////
 
     /**
      * Validate a flow sequence for test chip 3:
-     * - create STA
-     * - create P2P
-     * - request NAN: failure
-     * - create AP: should tear down P2P first
-     * - create STA: will get refused
-     * - create AP: will get refused
-     * - request P2P: failure
+     * - create STA (system app)
+     * - create P2P (system app)
+     * - create NAN (privileged app): should tear down P2P first
+     * - create AP (privileged app): should tear down NAN first
+     * - create STA (system app): will get refused
+     * - create AP (system app): will get refused
+     * - request P2P (system app): failure
      * - tear down AP
-     * - create STA
-     * - create STA: will get refused
-     * - create NAN: should tear down last created STA
-     * - create STA: will get refused
+     * - create STA (system app)
+     * - create STA (system app): will get refused
+     * - create NAN (privileged app): should tear down last created STA
+     * - create STA (foreground app): will get refused
      */
     @Test
     public void testInterfaceCreationFlowTestChipV3() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
         TestChipV3 chipMock = new TestChipV3();
         chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
                 mManagerStatusListenerMock);
         executeAndValidateInitializationSequence();
         executeAndValidateStartupSequence();
@@ -1814,178 +1769,140 @@
                 InterfaceDestroyedListener.class);
         InterfaceDestroyedListener staDestroyedListener2 = mock(
                 InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener staAvailListener = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
 
         InterfaceDestroyedListener apDestroyedListener = mock(
                 InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener apAvailListener = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
 
         InterfaceDestroyedListener p2pDestroyedListener = mock(
                 InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener p2pAvailListener = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
 
         InterfaceDestroyedListener nanDestroyedListener = mock(
                 InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener nanAvailListener = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
 
-        InOrder inOrderAvail = inOrder(staAvailListener, apAvailListener, p2pAvailListener,
-                nanAvailListener);
-
-        // register listeners for interface availability
-        mDut.registerInterfaceAvailableForRequestListener(IfaceType.STA, staAvailListener,
-                mHandler);
-        mDut.registerInterfaceAvailableForRequestListener(IfaceType.AP, apAvailListener, mHandler);
-        mDut.registerInterfaceAvailableForRequestListener(IfaceType.P2P, p2pAvailListener,
-                mHandler);
-        mDut.registerInterfaceAvailableForRequestListener(IfaceType.NAN, nanAvailListener,
-                mHandler);
-        mTestLooper.dispatchAll();
-
-        inOrderAvail.verify(staAvailListener).onAvailabilityChanged(true);
-        inOrderAvail.verify(apAvailListener).onAvailabilityChanged(true);
-        inOrderAvail.verify(p2pAvailListener).onAvailabilityChanged(true);
-        inOrderAvail.verify(nanAvailListener).onAvailabilityChanged(true);
-
-        // create STA
+        // create STA (system app)
         when(mClock.getUptimeSinceBootMillis()).thenReturn(15L);
+        when(mWorkSourceHelper0.hasAnyPrivilegedAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper0.hasAnySystemAppRequest()).thenReturn(true);
         IWifiIface staIface = validateInterfaceSequence(chipMock,
                 false, // chipModeValid
                 -1000, // chipModeId (only used if chipModeValid is true)
-                IfaceType.STA, // ifaceTypeToCreate
+                HDM_CREATE_IFACE_STA, // ifaceTypeToCreate
                 "wlan0", // ifaceName
                 TestChipV3.CHIP_MODE_ID, // finalChipMode
                 null, // tearDownList
                 staDestroyedListener, // destroyedListener
-                null // availableListener (already registered)
+                TEST_WORKSOURCE_0 // requestorWs
         );
         collector.checkThat("STA interface wasn't created", staIface, IsNull.notNullValue());
 
-        // create P2P
+        // create P2P (system app)
+        when(mWorkSourceHelper1.hasAnyPrivilegedAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper1.hasAnySystemAppRequest()).thenReturn(true);
         IWifiIface p2pIface = validateInterfaceSequence(chipMock,
                 true, // chipModeValid
                 TestChipV3.CHIP_MODE_ID, // chipModeId
-                IfaceType.P2P, // ifaceTypeToCreate
+                HDM_CREATE_IFACE_P2P, // ifaceTypeToCreate
                 "p2p0", // ifaceName
                 TestChipV3.CHIP_MODE_ID, // finalChipMode
                 null, // tearDownList
                 p2pDestroyedListener, // destroyedListener
-                null // availableListener (already registered)
+                TEST_WORKSOURCE_1 // requestorWs
         );
         collector.checkThat("P2P interface wasn't created", p2pIface, IsNull.notNullValue());
 
-        inOrderAvail.verify(staAvailListener).onAvailabilityChanged(false);
-        inOrderAvail.verify(p2pAvailListener).onAvailabilityChanged(false);
-
-        // create NAN
+        // create NAN (privileged app): will destroy P2P
         IWifiIface nanIface = validateInterfaceSequence(chipMock,
                 true, // chipModeValid
                 TestChipV3.CHIP_MODE_ID, // chipModeId
-                IfaceType.NAN, // ifaceTypeToCreate
+                HDM_CREATE_IFACE_NAN, // ifaceTypeToCreate
                 "wlan0", // ifaceName
                 TestChipV3.CHIP_MODE_ID, // finalChipMode
                 null, // tearDownList
                 nanDestroyedListener, // destroyedListener
-                null, // availableListener (already registered)
+                TEST_WORKSOURCE_2, // requestorWs
                 new InterfaceDestroyedListenerWithIfaceName("p2p0", p2pDestroyedListener)
         );
         collector.checkThat("NAN interface wasn't created", nanIface, IsNull.notNullValue());
 
-        inOrderAvail.verify(p2pAvailListener).onAvailabilityChanged(true);
-        inOrderAvail.verify(nanAvailListener).onAvailabilityChanged(false);
-
-        // create AP: will destroy P2P
+        // create AP (privileged app): will destroy NAN
         IWifiIface apIface = validateInterfaceSequence(chipMock,
                 true, // chipModeValid
                 TestChipV3.CHIP_MODE_ID, // chipModeId
-                IfaceType.AP, // ifaceTypeToCreate
+                HDM_CREATE_IFACE_AP, // ifaceTypeToCreate
                 "wlan1", // ifaceName
                 TestChipV3.CHIP_MODE_ID, // finalChipMode
                 null, // tearDownList
                 apDestroyedListener, // destroyedListener
-                null, // availableListener (already registered)
+                TEST_WORKSOURCE_2, // requestorWs
                 new InterfaceDestroyedListenerWithIfaceName("wlan0", nanDestroyedListener)
         );
         collector.checkThat("AP interface wasn't created", apIface, IsNull.notNullValue());
         verify(chipMock.chip).removeP2pIface("p2p0");
 
-        inOrderAvail.verify(apAvailListener).onAvailabilityChanged(false);
-        inOrderAvail.verify(p2pAvailListener).onAvailabilityChanged(false);
-
-        // request STA2: should fail
-        IWifiIface staIface2 = mDut.createStaIface(null, null);
+        // request STA2 (system app): should fail
+        IWifiIface staIface2 = mDut.createStaIface(null, null, TEST_WORKSOURCE_0);
         collector.checkThat("STA2 should not be created", staIface2, IsNull.nullValue());
 
-        // request AP2: should fail
-        IWifiIface apIface2 = mDut.createApIface(null, null);
+        // request AP2 (system app): should fail
+        IWifiIface apIface2 = mDut.createApIface(null, null, TEST_WORKSOURCE_0, false);
         collector.checkThat("AP2 should not be created", apIface2, IsNull.nullValue());
 
-        // request P2P: should fail
-        p2pIface = mDut.createP2pIface(null, null);
+        // request P2P (system app): should fail
+        p2pIface = mDut.createP2pIface(null, null, TEST_WORKSOURCE_0);
         collector.checkThat("P2P should not be created", p2pIface, IsNull.nullValue());
 
         // tear down AP
         mDut.removeIface(apIface);
         mTestLooper.dispatchAll();
 
-        inOrderAvail.verify(apAvailListener).onAvailabilityChanged(true);
-        inOrderAvail.verify(staAvailListener).onAvailabilityChanged(true);
-        inOrderAvail.verify(p2pAvailListener).onAvailabilityChanged(true);
-        inOrderAvail.verify(nanAvailListener).onAvailabilityChanged(true);
         verify(chipMock.chip).removeApIface("wlan1");
         verify(apDestroyedListener).onDestroyed(getName(apIface));
 
-        // create STA2: using a later clock
+        // create STA2 (system app): using a later clock
         when(mClock.getUptimeSinceBootMillis()).thenReturn(20L);
         staIface2 = validateInterfaceSequence(chipMock,
                 true, // chipModeValid
                 TestChipV3.CHIP_MODE_ID, // chipModeId
-                IfaceType.STA, // ifaceTypeToCreate
+                HDM_CREATE_IFACE_STA, // ifaceTypeToCreate
                 "wlan1", // ifaceName
                 TestChipV3.CHIP_MODE_ID, // finalChipMode
                 null, // tearDownList
                 staDestroyedListener2, // destroyedListener
-                null // availableListener (already registered)
+                TEST_WORKSOURCE_0 // requestorWs
         );
         collector.checkThat("STA 2 interface wasn't created", staIface2, IsNull.notNullValue());
 
-        inOrderAvail.verify(staAvailListener).onAvailabilityChanged(false);
-
-        // request STA3: should fail
-        IWifiIface staIface3 = mDut.createStaIface(null, null);
+        // request STA3 (system app): should fail
+        IWifiIface staIface3 = mDut.createStaIface(null, null, TEST_WORKSOURCE_0);
         collector.checkThat("STA3 should not be created", staIface3, IsNull.nullValue());
 
-        // create NAN: should destroy the last created STA (STA2)
+        // create NAN (privileged app): should destroy the last created STA (STA2)
         nanIface = validateInterfaceSequence(chipMock,
                 true, // chipModeValid
                 TestChipV3.CHIP_MODE_ID, // chipModeId
-                IfaceType.NAN, // ifaceTypeToCreate
+                HDM_CREATE_IFACE_NAN, // ifaceTypeToCreate
                 "wlan0", // ifaceName
                 TestChipV3.CHIP_MODE_ID, // finalChipMode
                 null, // tearDownList
                 nanDestroyedListener, // destroyedListener
-                null, // availableListener (already registered)
+                TEST_WORKSOURCE_2, // requestorWs
                 new InterfaceDestroyedListenerWithIfaceName(
-                        getName(staIface2), staDestroyedListener2)
+                        getName(staIface), staDestroyedListener)
         );
         collector.checkThat("NAN interface wasn't created", nanIface, IsNull.notNullValue());
 
-        inOrderAvail.verify(nanAvailListener).onAvailabilityChanged(false);
-        verify(chipMock.chip).removeStaIface("wlan1");
-        verify(staDestroyedListener2).onDestroyed(getName(staIface2));
+        verify(chipMock.chip).removeStaIface("wlan0");
+        verify(staDestroyedListener).onDestroyed(getName(staIface));
 
-        // request STA2: should fail
-        staIface2 = mDut.createStaIface(null, null);
+        // request STA2 (foreground app): should fail
+        when(mWorkSourceHelper1.hasAnySystemAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper1.hasAnyForegroundAppRequest()).thenReturn(true);
+        staIface2 = mDut.createStaIface(null, null, TEST_WORKSOURCE_1);
         collector.checkThat("STA2 should not be created", staIface2, IsNull.nullValue());
 
         verifyNoMoreInteractions(mManagerStatusListenerMock, staDestroyedListener,
                 staDestroyedListener2, apDestroyedListener, p2pDestroyedListener,
-                nanDestroyedListener, staAvailListener, apAvailListener, p2pAvailListener,
-                nanAvailListener, staAvailListener, apAvailListener, p2pAvailListener,
-                nanAvailListener);
+                nanDestroyedListener);
     }
 
     /**
@@ -2012,7 +1929,7 @@
     public void testGetSupportedIfaceTypesAllTestChipV3() throws Exception {
         TestChipV3 chipMock = new TestChipV3();
         chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
                 mManagerStatusListenerMock);
         executeAndValidateInitializationSequence();
         executeAndValidateStartupSequence();
@@ -2037,7 +1954,7 @@
     public void testGetSupportedIfaceTypesOneChipTestChipV3() throws Exception {
         TestChipV3 chipMock = new TestChipV3();
         chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
                 mManagerStatusListenerMock);
         executeAndValidateInitializationSequence();
         executeAndValidateStartupSequence();
@@ -2055,29 +1972,97 @@
         assertEquals(correctResults, results);
     }
 
+    @Test
+    public void testIsItPossibleToCreateIfaceTestChipV3() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        final String name = "wlan0";
+
+        TestChipV3 chipMock = new TestChipV3();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        // get STA interface from system app.
+        when(mWorkSourceHelper0.hasAnyPrivilegedAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper0.hasAnySystemAppRequest()).thenReturn(true);
+        IWifiIface staIface = validateInterfaceSequence(chipMock,
+                false, // chipModeValid
+                -1000, // chipModeId (only used if chipModeValid is true)
+                HDM_CREATE_IFACE_STA, // ifaceTypeToCreate
+                "wlan0", // ifaceName
+                TestChipV3.CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                mock(InterfaceDestroyedListener.class), // destroyedListener
+                TEST_WORKSOURCE_0 // requestorWs
+        );
+        collector.checkThat("STA created", staIface, IsNull.notNullValue());
+
+        // get AP interface from system app.
+        when(mWorkSourceHelper0.hasAnyPrivilegedAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper0.hasAnySystemAppRequest()).thenReturn(true);
+        IWifiIface apIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                TestChipV3.CHIP_MODE_ID, // chipModeId (only used if chipModeValid is true)
+                HDM_CREATE_IFACE_AP, // ifaceTypeToCreate
+                "wlan0", // ifaceName
+                TestChipV3.CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                mock(InterfaceDestroyedListener.class), // destroyedListener
+                TEST_WORKSOURCE_0 // requestorWs
+        );
+        collector.checkThat("AP created", apIface, IsNull.notNullValue());
+
+        // FG app not allowed to create STA interface.
+        when(mWorkSourceHelper1.hasAnyPrivilegedAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper1.hasAnyForegroundAppRequest()).thenReturn(true);
+        assertFalse(mDut.isItPossibleToCreateIface(IfaceType.STA, TEST_WORKSOURCE_1));
+
+        // New system app not allowed to create STA interface.
+        when(mWorkSourceHelper1.hasAnyForegroundAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper1.hasAnySystemAppRequest()).thenReturn(true);
+        assertFalse(mDut.isItPossibleToCreateIface(IfaceType.STA, TEST_WORKSOURCE_1));
+
+        // Privileged app allowed to create STA interface.
+        when(mWorkSourceHelper1.hasAnySystemAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper1.hasAnyPrivilegedAppRequest()).thenReturn(true);
+        assertTrue(mDut.isItPossibleToCreateIface(IfaceType.STA, TEST_WORKSOURCE_1));
+
+        // FG app not allowed to create NAN interface.
+        when(mWorkSourceHelper1.hasAnyPrivilegedAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper1.hasAnyForegroundAppRequest()).thenReturn(true);
+        assertFalse(mDut.isItPossibleToCreateIface(IfaceType.NAN, TEST_WORKSOURCE_1));
+
+        // Privileged app allowed to create P2P interface.
+        when(mWorkSourceHelper1.hasAnyPrivilegedAppRequest()).thenReturn(true);
+        assertTrue(mDut.isItPossibleToCreateIface(IfaceType.P2P, TEST_WORKSOURCE_1));
+    }
+
     //////////////////////////////////////////////////////////////////////////////////////
     // TestChipV4 Specific Tests
     //////////////////////////////////////////////////////////////////////////////////////
 
     /**
      * Validate a flow sequence for test chip 4:
-     * - create STA
-     * - create P2P
-     * - request NAN: failure
-     * - create AP: should tear down P2P first
-     * - create STA: will get refused
-     * - create AP: will get refused
-     * - request P2P: failure
+     * - create STA (system app)
+     * - create P2P (system app)
+     * - create NAN (privileged app): should tear down P2P first
+     * - create AP (privileged app): should tear down NAN first
+     * - create STA (system app): will get refused
+     * - create AP (system app): will get refused
+     * - request P2P (system app): failure
      * - tear down AP
-     * - create STA: will get refused
-     * - create NAN
-     * - create STA: will get refused
+     * - create STA (system app): will get refused
+     * - create NAN (privileged app)
+     * - create STA (foreground app): will get refused
      */
     @Test
     public void testInterfaceCreationFlowTestChipV4() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
         TestChipV4 chipMock = new TestChipV4();
         chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
                 mManagerStatusListenerMock);
         executeAndValidateInitializationSequence();
         executeAndValidateStartupSequence();
@@ -2086,166 +2071,313 @@
                 InterfaceDestroyedListener.class);
         InterfaceDestroyedListener staDestroyedListener2 = mock(
                 InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener staAvailListener = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
 
         InterfaceDestroyedListener apDestroyedListener = mock(
                 InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener apAvailListener = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
 
         InterfaceDestroyedListener p2pDestroyedListener = mock(
                 InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener p2pAvailListener = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
 
         InterfaceDestroyedListener nanDestroyedListener = mock(
                 InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener nanAvailListener = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
 
-        InOrder inOrderAvail = inOrder(staAvailListener, apAvailListener, p2pAvailListener,
-                nanAvailListener);
-
-        // register listeners for interface availability
-        mDut.registerInterfaceAvailableForRequestListener(IfaceType.STA, staAvailListener,
-                mHandler);
-        mDut.registerInterfaceAvailableForRequestListener(IfaceType.AP, apAvailListener, mHandler);
-        mDut.registerInterfaceAvailableForRequestListener(IfaceType.P2P, p2pAvailListener,
-                mHandler);
-        mDut.registerInterfaceAvailableForRequestListener(IfaceType.NAN, nanAvailListener,
-                mHandler);
-        mTestLooper.dispatchAll();
-
-        inOrderAvail.verify(staAvailListener).onAvailabilityChanged(true);
-        inOrderAvail.verify(apAvailListener).onAvailabilityChanged(true);
-        inOrderAvail.verify(p2pAvailListener).onAvailabilityChanged(true);
-        inOrderAvail.verify(nanAvailListener).onAvailabilityChanged(true);
-
-        // create STA
+        // create STA (system app)
         when(mClock.getUptimeSinceBootMillis()).thenReturn(15L);
+        when(mWorkSourceHelper0.hasAnyPrivilegedAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper0.hasAnySystemAppRequest()).thenReturn(true);
         IWifiIface staIface = validateInterfaceSequence(chipMock,
                 false, // chipModeValid
                 -1000, // chipModeId (only used if chipModeValid is true)
-                IfaceType.STA, // ifaceTypeToCreate
+                HDM_CREATE_IFACE_STA, // ifaceTypeToCreate
                 "wlan0", // ifaceName
                 TestChipV4.CHIP_MODE_ID, // finalChipMode
                 null, // tearDownList
                 staDestroyedListener, // destroyedListener
-                null // availableListener (already registered)
+                TEST_WORKSOURCE_0 // requestorWs
         );
         collector.checkThat("STA interface wasn't created", staIface, IsNull.notNullValue());
-        inOrderAvail.verify(staAvailListener).onAvailabilityChanged(false);
 
-        // create P2P
+        // create P2P (system app)
+        when(mWorkSourceHelper1.hasAnyPrivilegedAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper1.hasAnySystemAppRequest()).thenReturn(true);
         IWifiIface p2pIface = validateInterfaceSequence(chipMock,
                 true, // chipModeValid
                 TestChipV4.CHIP_MODE_ID, // chipModeId
-                IfaceType.P2P, // ifaceTypeToCreate
+                HDM_CREATE_IFACE_P2P, // ifaceTypeToCreate
                 "p2p0", // ifaceName
                 TestChipV4.CHIP_MODE_ID, // finalChipMode
                 null, // tearDownList
                 p2pDestroyedListener, // destroyedListener
-                null // availableListener (already registered)
+                TEST_WORKSOURCE_0 // requestorWs
         );
         collector.checkThat("P2P interface wasn't created", p2pIface, IsNull.notNullValue());
 
-        inOrderAvail.verify(p2pAvailListener).onAvailabilityChanged(false);
-
-        // create NAN: will destroy P2P
+        // create NAN (privileged app): will destroy P2P
         IWifiIface nanIface = validateInterfaceSequence(chipMock,
                 true, // chipModeValid
                 TestChipV4.CHIP_MODE_ID, // chipModeId
-                IfaceType.NAN, // ifaceTypeToCreate
+                HDM_CREATE_IFACE_NAN, // ifaceTypeToCreate
                 "wlan0", // ifaceName
                 TestChipV4.CHIP_MODE_ID, // finalChipMode
                 null, // tearDownList
                 nanDestroyedListener, // destroyedListener
-                nanAvailListener, // availableListener
+                TEST_WORKSOURCE_2, // requestorWs
                 new InterfaceDestroyedListenerWithIfaceName("p2p0", p2pDestroyedListener)
         );
         collector.checkThat("allocated NAN interface", nanIface, IsNull.notNullValue());
-        inOrderAvail.verify(p2pAvailListener).onAvailabilityChanged(true);
-        inOrderAvail.verify(nanAvailListener).onAvailabilityChanged(false);
 
-        // create AP: will destroy NAN
+        // create AP (privileged app): will destroy NAN
         IWifiIface apIface = validateInterfaceSequence(chipMock,
                 true, // chipModeValid
                 TestChipV4.CHIP_MODE_ID, // chipModeId
-                IfaceType.AP, // ifaceTypeToCreate
+                HDM_CREATE_IFACE_AP, // ifaceTypeToCreate
                 "wlan1", // ifaceName
                 TestChipV4.CHIP_MODE_ID, // finalChipMode
                 null, // tearDownList
                 apDestroyedListener, // destroyedListener
-                null, // availableListener (already registered)
+                TEST_WORKSOURCE_2, // requestorWs
                 new InterfaceDestroyedListenerWithIfaceName("wlan0", nanDestroyedListener)
         );
         collector.checkThat("AP interface wasn't created", apIface, IsNull.notNullValue());
         verify(chipMock.chip).removeP2pIface("p2p0");
 
-        inOrderAvail.verify(apAvailListener).onAvailabilityChanged(false);
-        inOrderAvail.verify(p2pAvailListener).onAvailabilityChanged(false);
-
-        // request STA2: should fail
-        IWifiIface staIface2 = mDut.createStaIface(null, null);
+        // request STA2 (system app): should fail
+        IWifiIface staIface2 = mDut.createStaIface(null, null, TEST_WORKSOURCE_0);
         collector.checkThat("STA2 should not be created", staIface2, IsNull.nullValue());
 
-        // request AP2: should fail
-        IWifiIface apIface2 = mDut.createApIface(null, null);
+        // request AP2 (system app): should fail
+        IWifiIface apIface2 = mDut.createApIface(null, null, TEST_WORKSOURCE_0, false);
         collector.checkThat("AP2 should not be created", apIface2, IsNull.nullValue());
 
-        // request P2P: should fail
-        p2pIface = mDut.createP2pIface(null, null);
+        // request P2P (system app): should fail
+        p2pIface = mDut.createP2pIface(null, null, TEST_WORKSOURCE_0);
         collector.checkThat("P2P should not be created", p2pIface, IsNull.nullValue());
 
         // tear down AP
         mDut.removeIface(apIface);
         mTestLooper.dispatchAll();
 
-        inOrderAvail.verify(apAvailListener).onAvailabilityChanged(true);
-        inOrderAvail.verify(p2pAvailListener).onAvailabilityChanged(true);
-        inOrderAvail.verify(nanAvailListener).onAvailabilityChanged(true);
         verify(chipMock.chip).removeApIface("wlan1");
         verify(apDestroyedListener).onDestroyed(getName(apIface));
 
-        // request STA2: should fail
-        staIface2 = mDut.createStaIface(null, null);
+        // request STA2 (system app): should fail
+        staIface2 = mDut.createStaIface(null, null, TEST_WORKSOURCE_0);
         collector.checkThat("STA2 should not be created", staIface2, IsNull.nullValue());
 
-        // create NAN
+        // create NAN (privileged app)
         nanIface = validateInterfaceSequence(chipMock,
                 true, // chipModeValid
                 TestChipV4.CHIP_MODE_ID, // chipModeId
-                IfaceType.NAN, // ifaceTypeToCreate
+                HDM_CREATE_IFACE_NAN, // ifaceTypeToCreate
                 "wlan0", // ifaceName
                 TestChipV4.CHIP_MODE_ID, // finalChipMode
                 null, // tearDownList
                 nanDestroyedListener, // destroyedListener
-                null // availableListener (already registered)
+                TEST_WORKSOURCE_2 // requestorWs
         );
         collector.checkThat("NAN interface wasn't created", nanIface, IsNull.notNullValue());
 
-        inOrderAvail.verify(nanAvailListener).onAvailabilityChanged(false);
-
-        // request STA2: should fail
-        staIface2 = mDut.createStaIface(null, null);
+        // request STA2 (foreground app): should fail
+        when(mWorkSourceHelper1.hasAnySystemAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper1.hasAnyForegroundAppRequest()).thenReturn(true);
+        staIface2 = mDut.createStaIface(null, null, TEST_WORKSOURCE_1);
         collector.checkThat("STA2 should not be created", staIface2, IsNull.nullValue());
 
         // tear down STA
         mDut.removeIface(staIface);
         mTestLooper.dispatchAll();
 
-        inOrderAvail.verify(staAvailListener).onAvailabilityChanged(true);
         verify(chipMock.chip).removeStaIface("wlan0");
         verify(staDestroyedListener).onDestroyed(getName(staIface));
 
         verifyNoMoreInteractions(mManagerStatusListenerMock, staDestroyedListener,
                 staDestroyedListener2, apDestroyedListener, p2pDestroyedListener,
-                nanDestroyedListener, staAvailListener, apAvailListener, p2pAvailListener,
-                nanAvailListener, staAvailListener, apAvailListener, p2pAvailListener,
-                nanAvailListener);
+                nanDestroyedListener);
     }
 
+    @Test
+    public void testInterfaceCreationFlowTestChipV4ForR() throws Exception {
+        assumeFalse(SdkLevel.isAtLeastS());
+        TestChipV4 chipMock = new TestChipV4();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        InterfaceDestroyedListener staDestroyedListener = mock(
+                InterfaceDestroyedListener.class);
+        InterfaceDestroyedListener staDestroyedListener2 = mock(
+                InterfaceDestroyedListener.class);
+
+        InterfaceDestroyedListener apDestroyedListener = mock(
+                InterfaceDestroyedListener.class);
+
+        InterfaceDestroyedListener p2pDestroyedListener = mock(
+                InterfaceDestroyedListener.class);
+
+        InterfaceDestroyedListener nanDestroyedListener = mock(
+                InterfaceDestroyedListener.class);
+
+        // create STA
+        IWifiIface staIface = validateInterfaceSequence(chipMock,
+                false, // chipModeValid
+                -1000, // chipModeId (only used if chipModeValid is true)
+                HDM_CREATE_IFACE_STA, // ifaceTypeToCreate
+                "wlan0", // ifaceName
+                TestChipV4.CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                staDestroyedListener, // destroyedListener
+                TEST_WORKSOURCE_0 // requestorWs
+        );
+        collector.checkThat("STA interface wasn't created", staIface, IsNull.notNullValue());
+
+        // create P2P
+        IWifiIface p2pIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                TestChipV4.CHIP_MODE_ID, // chipModeId
+                HDM_CREATE_IFACE_P2P, // ifaceTypeToCreate
+                "p2p0", // ifaceName
+                TestChipV4.CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                p2pDestroyedListener, // destroyedListener
+                TEST_WORKSOURCE_1 // requestorWs
+        );
+        collector.checkThat("P2P interface wasn't created", p2pIface, IsNull.notNullValue());
+
+        // create NAN: will destroy P2P
+        IWifiIface nanIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                TestChipV4.CHIP_MODE_ID, // chipModeId
+                HDM_CREATE_IFACE_NAN, // ifaceTypeToCreate
+                "wlan0", // ifaceName
+                TestChipV4.CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                nanDestroyedListener, // destroyedListener
+                TEST_WORKSOURCE_2, // requestorWs
+                new InterfaceDestroyedListenerWithIfaceName("p2p0", p2pDestroyedListener)
+        );
+        collector.checkThat("NAN interface wasn't created", nanIface, IsNull.notNullValue());
+
+        // create AP: will destroy NAN
+        IWifiIface apIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                TestChipV4.CHIP_MODE_ID, // chipModeId
+                HDM_CREATE_IFACE_AP, // ifaceTypeToCreate
+                "wlan1", // ifaceName
+                TestChipV4.CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                apDestroyedListener, // destroyedListener
+                TEST_WORKSOURCE_2, // requestorWs
+                new InterfaceDestroyedListenerWithIfaceName("wlan0", nanDestroyedListener)
+        );
+        collector.checkThat("AP interface wasn't created", apIface, IsNull.notNullValue());
+        verify(chipMock.chip).removeP2pIface("p2p0");
+
+        // request STA2 (system app): should fail
+        IWifiIface staIface2 = mDut.createStaIface(null, null, TEST_WORKSOURCE_0);
+        collector.checkThat("STA2 should not be created", staIface2, IsNull.nullValue());
+
+        // request AP2: should fail
+        IWifiIface apIface2 = mDut.createApIface(null, null, TEST_WORKSOURCE_0, false);
+        collector.checkThat("AP2 should not be created", apIface2, IsNull.nullValue());
+
+        // request P2P: should fail
+        p2pIface = mDut.createP2pIface(null, null, TEST_WORKSOURCE_0);
+        collector.checkThat("P2P should not be created", p2pIface, IsNull.nullValue());
+
+        // request NAN: should fail
+        nanIface = mDut.createNanIface(null, null, TEST_WORKSOURCE_0);
+        collector.checkThat("NAN should not be created", nanIface, IsNull.nullValue());
+
+        // tear down AP
+        mDut.removeIface(apIface);
+        mTestLooper.dispatchAll();
+
+        verify(chipMock.chip).removeApIface("wlan1");
+        verify(apDestroyedListener).onDestroyed(getName(apIface));
+
+        // request STA2: should fail
+        staIface2 = mDut.createStaIface(null, null, TEST_WORKSOURCE_0);
+        collector.checkThat("STA2 should not be created", staIface2, IsNull.nullValue());
+
+        // create NAN
+        nanIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                TestChipV4.CHIP_MODE_ID, // chipModeId
+                HDM_CREATE_IFACE_NAN, // ifaceTypeToCreate
+                "wlan0", // ifaceName
+                TestChipV4.CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                nanDestroyedListener, // destroyedListener
+                TEST_WORKSOURCE_2 // requestorWs
+        );
+        collector.checkThat("NAN interface wasn't created", nanIface, IsNull.notNullValue());
+    }
+
+    /**
+     * Validate a flow sequence for test chip 3:
+     * - create NAN (internal request)
+     * - create AP (privileged app): should tear down NAN first
+     */
+    @Test
+    public void testInterfaceCreationFlowTestChipV3WithInternalRequest() throws Exception {
+        TestChipV3 chipMock = new TestChipV3();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        InterfaceDestroyedListener apDestroyedListener = mock(
+                InterfaceDestroyedListener.class);
+
+        InterfaceDestroyedListener nanDestroyedListener = mock(
+                InterfaceDestroyedListener.class);
+
+        // create P2P (internal request)
+        when(mWorkSourceHelper0.hasAnyPrivilegedAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper0.hasAnyInternalRequest()).thenReturn(true);
+        // create NAN (privileged app): will destroy P2P
+        IWifiIface nanIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                TestChipV3.CHIP_MODE_ID, // chipModeId
+                HDM_CREATE_IFACE_NAN, // ifaceTypeToCreate
+                "wlan0", // ifaceName
+                TestChipV3.CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                nanDestroyedListener, // destroyedListener
+                TEST_WORKSOURCE_0 // requestorWs)
+        );
+        collector.checkThat("NAN interface wasn't created", nanIface, IsNull.notNullValue());
+
+        // create AP (privileged app): will destroy NAN
+        IWifiIface apIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                TestChipV3.CHIP_MODE_ID, // chipModeId
+                HDM_CREATE_IFACE_AP, // ifaceTypeToCreate
+                "wlan1", // ifaceName
+                TestChipV3.CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                apDestroyedListener, // destroyedListener
+                TEST_WORKSOURCE_1, // requestorWs
+                new InterfaceDestroyedListenerWithIfaceName("wlan0", nanDestroyedListener)
+        );
+        collector.checkThat("AP interface wasn't created", apIface, IsNull.notNullValue());
+        verify(chipMock.chip).removeNanIface("wlan0");
+
+        // tear down AP
+        mDut.removeIface(apIface);
+        mTestLooper.dispatchAll();
+
+        verify(chipMock.chip).removeApIface("wlan1");
+        verify(apDestroyedListener).onDestroyed(getName(apIface));
+
+        verifyNoMoreInteractions(mManagerStatusListenerMock, apDestroyedListener,
+                nanDestroyedListener);
+    }
+
+
     /**
      * Validate P2P and NAN interactions. Expect:
      * - STA created
@@ -2270,7 +2402,7 @@
     public void testGetSupportedIfaceTypesAllTestChipV4() throws Exception {
         TestChipV4 chipMock = new TestChipV4();
         chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
                 mManagerStatusListenerMock);
         executeAndValidateInitializationSequence();
         executeAndValidateStartupSequence();
@@ -2295,7 +2427,7 @@
     public void testGetSupportedIfaceTypesOneChipTestChipV4() throws Exception {
         TestChipV4 chipMock = new TestChipV4();
         chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
                 mManagerStatusListenerMock);
         executeAndValidateInitializationSequence();
         executeAndValidateStartupSequence();
@@ -2313,10 +2445,266 @@
         assertEquals(correctResults, results);
     }
 
+    @Test
+    public void testIsItPossibleToCreateIfaceTestChipV4() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        final String name = "wlan0";
+
+        TestChipV4 chipMock = new TestChipV4();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        // get STA interface from system app.
+        when(mWorkSourceHelper0.hasAnyPrivilegedAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper0.hasAnySystemAppRequest()).thenReturn(true);
+        IWifiIface staIface = validateInterfaceSequence(chipMock,
+                false, // chipModeValid
+                -1000, // chipModeId (only used if chipModeValid is true)
+                HDM_CREATE_IFACE_STA, // ifaceTypeToCreate
+                "wlan0", // ifaceName
+                TestChipV4.CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                mock(InterfaceDestroyedListener.class), // destroyedListener
+                TEST_WORKSOURCE_0 // requestorWs
+        );
+        collector.checkThat("STA created", staIface, IsNull.notNullValue());
+
+        // get AP interface from system app.
+        when(mWorkSourceHelper0.hasAnyPrivilegedAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper0.hasAnySystemAppRequest()).thenReturn(true);
+        IWifiIface apIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                TestChipV4.CHIP_MODE_ID, // chipModeId (only used if chipModeValid is true)
+                HDM_CREATE_IFACE_AP, // ifaceTypeToCreate
+                "wlan0", // ifaceName
+                TestChipV4.CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                mock(InterfaceDestroyedListener.class), // destroyedListener
+                TEST_WORKSOURCE_0 // requestorWs
+        );
+        collector.checkThat("AP created", apIface, IsNull.notNullValue());
+
+        // FG app not allowed to create STA interface.
+        when(mWorkSourceHelper1.hasAnyPrivilegedAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper1.hasAnyForegroundAppRequest()).thenReturn(true);
+        assertFalse(mDut.isItPossibleToCreateIface(IfaceType.STA, TEST_WORKSOURCE_1));
+
+        // New system app not allowed to create STA interface.
+        when(mWorkSourceHelper1.hasAnyForegroundAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper1.hasAnySystemAppRequest()).thenReturn(true);
+        assertFalse(mDut.isItPossibleToCreateIface(IfaceType.STA, TEST_WORKSOURCE_1));
+
+        // Privileged app allowed to create STA interface.
+        when(mWorkSourceHelper1.hasAnySystemAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper1.hasAnyPrivilegedAppRequest()).thenReturn(true);
+        assertTrue(mDut.isItPossibleToCreateIface(IfaceType.STA, TEST_WORKSOURCE_1));
+
+        // FG app not allowed to create NAN interface.
+        when(mWorkSourceHelper1.hasAnyPrivilegedAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper1.hasAnyForegroundAppRequest()).thenReturn(true);
+        assertFalse(mDut.isItPossibleToCreateIface(IfaceType.NAN, TEST_WORKSOURCE_1));
+
+        // Privileged app allowed to create P2P interface.
+        when(mWorkSourceHelper1.hasAnyPrivilegedAppRequest()).thenReturn(true);
+        assertTrue(mDut.isItPossibleToCreateIface(IfaceType.P2P, TEST_WORKSOURCE_1));
+    }
+
+    @Test
+    public void testIsItPossibleToCreateIfaceTestChipV4ForR() throws Exception {
+        assumeFalse(SdkLevel.isAtLeastS());
+        final String name = "wlan0";
+
+        TestChipV4 chipMock = new TestChipV4();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        // get STA interface.
+        IWifiIface staIface = validateInterfaceSequence(chipMock,
+                false, // chipModeValid
+                -1000, // chipModeId (only used if chipModeValid is true)
+                HDM_CREATE_IFACE_STA, // ifaceTypeToCreate
+                "wlan0", // ifaceName
+                TestChipV4.CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                mock(InterfaceDestroyedListener.class), // destroyedListener
+                TEST_WORKSOURCE_0 // requestorWs
+        );
+        collector.checkThat("STA created", staIface, IsNull.notNullValue());
+
+        // get AP interface.
+        IWifiIface apIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                TestChipV4.CHIP_MODE_ID, // chipModeId (only used if chipModeValid is true)
+                HDM_CREATE_IFACE_AP, // ifaceTypeToCreate
+                "wlan0", // ifaceName
+                TestChipV4.CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                mock(InterfaceDestroyedListener.class), // destroyedListener
+                TEST_WORKSOURCE_0 // requestorWs
+        );
+        collector.checkThat("AP created", apIface, IsNull.notNullValue());
+
+        // Not allowed to create STA interface.
+        assertFalse(mDut.isItPossibleToCreateIface(IfaceType.STA, TEST_WORKSOURCE_1));
+
+        // Not allowed to create AP interface.
+        assertFalse(mDut.isItPossibleToCreateIface(IfaceType.AP, TEST_WORKSOURCE_1));
+
+        // Not allowed to create NAN interface.
+        assertFalse(mDut.isItPossibleToCreateIface(IfaceType.NAN, TEST_WORKSOURCE_1));
+
+        // Not allowed to create P2P interface.
+        assertFalse(mDut.isItPossibleToCreateIface(IfaceType.P2P, TEST_WORKSOURCE_1));
+    }
+
+    public void verify60GhzIfaceCreation(
+            ChipMockBase chipMock, int chipModeId, int finalChipModeId, boolean isWigigSupported)
+            throws Exception {
+        long requiredChipCapabilities =
+                android.hardware.wifi.V1_5.IWifiChip.ChipCapabilityMask.WIGIG;
+        chipMock.initialize();
+        if (mWifiChipV15 != null) {
+            mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
+                    mWifiChipV15, mManagerStatusListenerMock);
+        } else {
+            mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
+                    mManagerStatusListenerMock);
+        }
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        // get STA interface from system app.
+        when(mWorkSourceHelper0.hasAnyPrivilegedAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper0.hasAnySystemAppRequest()).thenReturn(true);
+        IWifiIface staIface;
+        if (isWigigSupported) {
+            staIface = validateInterfaceSequence(chipMock,
+                    false, // chipModeValid
+                    -1000, // chipModeId (only used if chipModeValid is true)
+                    HDM_CREATE_IFACE_STA, // ifaceTypeToCreate
+                    "wlan0", // ifaceName
+                    finalChipModeId, // finalChipMode
+                    requiredChipCapabilities, // requiredChipCapabilities
+                    null, // tearDownList
+                    mock(InterfaceDestroyedListener.class), // destroyedListener
+                    TEST_WORKSOURCE_0 // requestorWs
+            );
+            collector.checkThat("STA created", staIface, IsNull.notNullValue());
+        } else {
+            staIface = mDut.createStaIface(
+                    requiredChipCapabilities, null, null, TEST_WORKSOURCE_1);
+            mInOrder.verify(chipMock.chip, times(0)).configureChip(anyInt());
+            collector.checkThat("STA should not be created", staIface, IsNull.nullValue());
+        }
+
+        // get AP interface from system app.
+        when(mWorkSourceHelper0.hasAnyPrivilegedAppRequest()).thenReturn(false);
+        when(mWorkSourceHelper0.hasAnySystemAppRequest()).thenReturn(true);
+        IWifiIface apIface;
+        if (isWigigSupported) {
+            apIface = validateInterfaceSequence(chipMock,
+                    true, // chipModeValid
+                    chipModeId, // chipModeId (only used if chipModeValid is true)
+                    HDM_CREATE_IFACE_AP, // ifaceTypeToCreate
+                    "wlan0", // ifaceName
+                    finalChipModeId, // finalChipMode
+                    requiredChipCapabilities, // requiredChipCapabilities
+                    null, // tearDownList
+                    mock(InterfaceDestroyedListener.class), // destroyedListener
+                    TEST_WORKSOURCE_0 // requestorWs
+            );
+            collector.checkThat("AP created", apIface, IsNull.notNullValue());
+        } else {
+            apIface = mDut.createApIface(
+                    requiredChipCapabilities, null, null, TEST_WORKSOURCE_0, false);
+            collector.checkThat("AP should not be created", apIface, IsNull.nullValue());
+        }
+        if (SdkLevel.isAtLeastS()) {
+            // Privileged app allowed to create P2P interface.
+            when(mWorkSourceHelper1.hasAnyPrivilegedAppRequest()).thenReturn(true);
+            assertThat(mDut.isItPossibleToCreateIface(IfaceType.P2P,
+                    android.hardware.wifi.V1_5.IWifiChip.ChipCapabilityMask.WIGIG,
+                    TEST_WORKSOURCE_1), is(isWigigSupported));
+        }
+    }
+
+    /*
+     * Verify that 60GHz iface creation request could be procceed by a chip supports
+     * WIGIG.
+     */
+    @Test
+    public void testIsItPossibleToCreate60GhzIfaceTestChipV5() throws Exception {
+        TestChipV5 chipMock = new TestChipV5();
+        setupWifiChipV15(chipMock);
+        verify60GhzIfaceCreation(
+                chipMock, TestChipV5.CHIP_MODE_ID, TestChipV5.CHIP_MODE_ID, true);
+    }
+
+    /*
+     * Verify that 60GHz iface creation request could not be procceed by a chip does
+     * not supports WIGIG on V1.5 HAL.
+     */
+    @Test
+    public void testIsItPossibleToCreate60GhzIfaceTestChipV4() throws Exception {
+        TestChipV4 chipMock = new TestChipV4();
+        setupWifiChipV15(chipMock);
+        verify60GhzIfaceCreation(
+                chipMock, TestChipV4.CHIP_MODE_ID, TestChipV4.CHIP_MODE_ID, false);
+    }
+
+    /*
+     * Verify that 60GHz iface creation request could be procceed by a chip does
+     * not supports WIGIG on a HAL older than v1.5.
+     */
+    @Test
+    public void testIsItPossibleToCreate60GhzIfaceTestChipV4WithHalOlderThan1_5() throws Exception {
+        TestChipV4 chipMock = new TestChipV4();
+        verify60GhzIfaceCreation(
+                chipMock, TestChipV4.CHIP_MODE_ID, TestChipV4.CHIP_MODE_ID, true);
+    }
+
+    /**
+     * Validate creation of AP interface from blank start-up in chip V1.5
+     */
+    @Test
+    public void testCreateApInterfaceNoInitModeTestChipV15() throws Exception {
+        mWifiChipV15 = mock(android.hardware.wifi.V1_5.IWifiChip.class);
+        runCreateSingleXxxInterfaceNoInitMode(new TestChipV5(), HDM_CREATE_IFACE_AP, "wlan0",
+                TestChipV5.CHIP_MODE_ID);
+    }
+    /**
+     * Validate creation of AP Bridge interface from blank start-up in chip V1.5
+     */
+    @Test
+    public void testCreateApBridgeInterfaceNoInitModeTestChipV15() throws Exception {
+        mWifiChipV15 = mock(android.hardware.wifi.V1_5.IWifiChip.class);
+        runCreateSingleXxxInterfaceNoInitMode(new TestChipV5(), HDM_CREATE_IFACE_AP_BRIDGE, "wlan0",
+                TestChipV5.CHIP_MODE_ID);
+    }
+
+
 
     ///////////////////////////////////////////////////////////////////////////////////////
     // utilities
     ///////////////////////////////////////////////////////////////////////////////////////
+    private void setupWifiChipV15(ChipMockBase chipMock) throws RemoteException {
+        mWifiChipV15 = mock(android.hardware.wifi.V1_5.IWifiChip.class);
+        doAnswer(new GetCapabilities_1_5Answer(chipMock))
+                .when(mWifiChipV15).getCapabilities_1_5(any(
+                        android.hardware.wifi.V1_5.IWifiChip.getCapabilities_1_5Callback.class));
+    }
+
+    private void setupWifiV15(IWifi iWifiMock) throws RemoteException {
+        mWifiMockV15 = mock(android.hardware.wifi.V1_5.IWifi.class);
+        when(mWifiMockV15.registerEventCallback_1_5(
+                any(android.hardware.wifi.V1_5.IWifiEventCallback.class))).thenReturn(mStatusOk);
+    }
 
     private void dumpDut(String prefix) {
         StringWriter sw = new StringWriter();
@@ -2345,9 +2733,16 @@
         // verify: wifi initialization sequence if vendor HAL is supported.
         if (isSupported) {
             mInOrder.verify(mWifiMock).linkToDeath(mDeathRecipientCaptor.capture(), anyLong());
-            mInOrder.verify(mWifiMock).registerEventCallback(mWifiEventCallbackCaptor.capture());
+            if (null != mWifiMockV15) {
+                mInOrder.verify(mWifiMockV15).registerEventCallback_1_5(
+                        mWifiEventCallbackCaptorV15.capture());
+            } else {
+                mInOrder.verify(mWifiMock).registerEventCallback(
+                        mWifiEventCallbackCaptor.capture());
+            }
             // verify: onStop called as a part of initialize.
             mInOrder.verify(mWifiMock).stop();
+
             collector.checkThat("isReady is true", mDut.isReady(), equalTo(true));
         } else {
             collector.checkThat("isReady is false", mDut.isReady(), equalTo(false));
@@ -2369,7 +2764,11 @@
 
         if (success) {
             // act: trigger onStart callback of IWifiEventCallback
-            mWifiEventCallbackCaptor.getValue().onStart();
+            if (mWifiMockV15 != null) {
+                mWifiEventCallbackCaptorV15.getValue().onStart();
+            } else {
+                mWifiEventCallbackCaptor.getValue().onStart();
+            }
             mTestLooper.dispatchAll();
 
             // verify: onStart called on registered listener
@@ -2378,19 +2777,20 @@
     }
 
     private void runCreateSingleXxxInterfaceNoInitMode(ChipMockBase chipMock, int ifaceTypeToCreate,
-            String ifaceName, int finalChipMode, boolean multipleIfaceSupport) throws Exception {
+            String ifaceName, int finalChipMode) throws Exception {
         chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
-                mManagerStatusListenerMock);
+        if (mWifiChipV15 != null) {
+            mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
+                    mWifiChipV15, mManagerStatusListenerMock);
+        } else {
+            mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
+                    mManagerStatusListenerMock);
+        }
         executeAndValidateInitializationSequence();
         executeAndValidateStartupSequence();
 
         InterfaceDestroyedListener idl = mock(
                 InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener iafrl = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
-
-        InOrder availInOrder = inOrder(iafrl);
 
         IWifiIface iface = validateInterfaceSequence(chipMock,
                 false, // chipModeValid
@@ -2400,10 +2800,9 @@
                 finalChipMode,
                 null, // tearDownList
                 idl, // destroyedListener
-                iafrl // availableListener
+                TEST_WORKSOURCE_0 // requestorWs
         );
         collector.checkThat("allocated interface", iface, IsNull.notNullValue());
-        availInOrder.verify(iafrl).onAvailabilityChanged(multipleIfaceSupport);
 
         // act: remove interface
         mDut.removeIface(iface);
@@ -2411,26 +2810,24 @@
 
         // verify: callback triggered
         switch (ifaceTypeToCreate) {
-            case IfaceType.STA:
+            case HDM_CREATE_IFACE_STA:
                 mInOrder.verify(chipMock.chip).removeStaIface(ifaceName);
                 break;
-            case IfaceType.AP:
+            case HDM_CREATE_IFACE_AP_BRIDGE:
+            case HDM_CREATE_IFACE_AP:
                 mInOrder.verify(chipMock.chip).removeApIface(ifaceName);
                 break;
-            case IfaceType.P2P:
+            case HDM_CREATE_IFACE_P2P:
                 mInOrder.verify(chipMock.chip).removeP2pIface(ifaceName);
                 break;
-            case IfaceType.NAN:
+            case HDM_CREATE_IFACE_NAN:
                 mInOrder.verify(chipMock.chip).removeNanIface(ifaceName);
                 break;
         }
 
         verify(idl).onDestroyed(ifaceName);
-        if (!multipleIfaceSupport) {
-            availInOrder.verify(iafrl).onAvailabilityChanged(true);
-        }
 
-        verifyNoMoreInteractions(mManagerStatusListenerMock, idl, iafrl);
+        verifyNoMoreInteractions(mManagerStatusListenerMock, idl);
     }
 
     /**
@@ -2451,74 +2848,60 @@
     public void runP2pAndNanExclusiveInteractionsTestChip(ChipMockBase chipMock,
             int onlyChipMode) throws Exception {
         chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mWifiMockV15, chipMock.chip,
                 mManagerStatusListenerMock);
         executeAndValidateInitializationSequence();
         executeAndValidateStartupSequence();
 
         InterfaceDestroyedListener staDestroyedListener = mock(
                 InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener staAvailListener = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
 
         InterfaceDestroyedListener nanDestroyedListener = mock(
                 InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener nanAvailListener = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
 
         InterfaceDestroyedListener p2pDestroyedListener = mock(
                 InterfaceDestroyedListener.class);
 
-        InOrder availInOrder = inOrder(staAvailListener, nanAvailListener);
-
         // Request STA
         IWifiIface staIface = validateInterfaceSequence(chipMock,
                 false, // chipModeValid
                 -1000, // chipModeId (only used if chipModeValid is true)
-                IfaceType.STA, // ifaceTypeToCreate
+                HDM_CREATE_IFACE_STA, // ifaceTypeToCreate
                 "wlan0", // ifaceName
                 onlyChipMode, // finalChipMode
                 null, // tearDownList
                 staDestroyedListener, // destroyedListener
-                staAvailListener // availableListener
+                TEST_WORKSOURCE_0 // requestorWs
         );
         collector.checkThat("STA can't be created", staIface, IsNull.notNullValue());
-        availInOrder.verify(staAvailListener).onAvailabilityChanged(
-                chipMock.chipMockId == CHIP_MOCK_V2 || chipMock.chipMockId == CHIP_MOCK_V3);
 
         // Request NAN
         IWifiIface nanIface = validateInterfaceSequence(chipMock,
                 true, // chipModeValid
                 onlyChipMode, // chipModeId
-                IfaceType.NAN, // ifaceTypeToCreate
+                HDM_CREATE_IFACE_NAN, // ifaceTypeToCreate
                 "wlan0", // ifaceName
                 onlyChipMode, // finalChipMode
                 null, // tearDownList
                 nanDestroyedListener, // destroyedListener
-                nanAvailListener // availableListener
+                TEST_WORKSOURCE_0 // requestorWs
         );
-        if (chipMock.chipMockId == CHIP_MOCK_V3) {
-            availInOrder.verify(staAvailListener).onAvailabilityChanged(false);
-        }
-        availInOrder.verify(nanAvailListener).onAvailabilityChanged(false);
 
         // Request P2P
         IWifiIface p2pIface = validateInterfaceSequence(chipMock,
                 true, // chipModeValid
                 onlyChipMode, // chipModeId
-                IfaceType.P2P, // ifaceTypeToCreate
+                HDM_CREATE_IFACE_P2P, // ifaceTypeToCreate
                 "p2p0", // ifaceName
                 onlyChipMode, // finalChipMode
                 new IWifiIface[]{nanIface}, // tearDownList
                 p2pDestroyedListener, // destroyedListener
-                null, // availableListener
+                TEST_WORKSOURCE_0, // requestorWs
                 // destroyedInterfacesDestroyedListeners...
                 new InterfaceDestroyedListenerWithIfaceName(
                         getName(nanIface), nanDestroyedListener)
         );
         collector.checkThat("P2P can't be created", p2pIface, IsNull.notNullValue());
-        availInOrder.verify(nanAvailListener).onAvailabilityChanged(true);
-
         mTestLooper.dispatchAll();
         verify(nanDestroyedListener).onDestroyed(getName(nanIface));
 
@@ -2526,29 +2909,29 @@
         nanIface = validateInterfaceSequence(chipMock,
                 true, // chipModeValid
                 onlyChipMode, // chipModeId
-                IfaceType.NAN, // ifaceTypeToCreate
+                HDM_CREATE_IFACE_NAN, // ifaceTypeToCreate
                 "wlan0", // ifaceName
                 onlyChipMode, // finalChipMode
                 new IWifiIface[]{p2pIface}, // tearDownList
                 nanDestroyedListener, // destroyedListener
-                nanAvailListener // availableListener
+                TEST_WORKSOURCE_0 // requestorWs
         );
         collector.checkThat("NAN can't be created", nanIface, IsNull.notNullValue());
-        availInOrder.verify(nanAvailListener).onAvailabilityChanged(false);
 
         mTestLooper.dispatchAll();
         verify(p2pDestroyedListener).onDestroyed(getName(p2pIface));
 
-        verifyNoMoreInteractions(mManagerStatusListenerMock, staDestroyedListener, staAvailListener,
-                nanDestroyedListener, nanAvailListener, p2pDestroyedListener);
+        verifyNoMoreInteractions(mManagerStatusListenerMock, staDestroyedListener,
+                nanDestroyedListener, p2pDestroyedListener);
     }
 
     private IWifiIface validateInterfaceSequence(ChipMockBase chipMock,
             boolean chipModeValid, int chipModeId,
             int ifaceTypeToCreate, String ifaceName, int finalChipMode,
+            long requiredChipCapabilities,
             IWifiIface[] tearDownList,
             InterfaceDestroyedListener destroyedListener,
-            HalDeviceManager.InterfaceAvailableForRequestListener availableListener,
+            WorkSource requestorWs,
             InterfaceDestroyedListenerWithIfaceName...destroyedInterfacesDestroyedListeners)
             throws Exception {
         // configure chip mode response
@@ -2560,7 +2943,7 @@
         // configure: interface to be created
         // act: request the interface
         switch (ifaceTypeToCreate) {
-            case IfaceType.STA:
+            case HDM_CREATE_IFACE_STA:
                 iface = mock(IWifiStaIface.class);
                 doAnswer(new GetNameAnswer(ifaceName)).when(iface).getName(
                         any(IWifiIface.getNameCallback.class));
@@ -2569,20 +2952,36 @@
                 doAnswer(new CreateXxxIfaceAnswer(chipMock, mStatusOk, iface)).when(
                         chipMock.chip).createStaIface(any(IWifiChip.createStaIfaceCallback.class));
 
-                mDut.createStaIface(destroyedListener, mHandler);
+                mDut.createStaIface(requiredChipCapabilities,
+                        destroyedListener, mHandler, requestorWs);
                 break;
-            case IfaceType.AP:
+            case HDM_CREATE_IFACE_AP_BRIDGE:
+            case HDM_CREATE_IFACE_AP:
                 iface = mock(IWifiApIface.class);
                 doAnswer(new GetNameAnswer(ifaceName)).when(iface).getName(
                         any(IWifiIface.getNameCallback.class));
                 doAnswer(new GetTypeAnswer(IfaceType.AP)).when(iface).getType(
                         any(IWifiIface.getTypeCallback.class));
-                doAnswer(new CreateXxxIfaceAnswer(chipMock, mStatusOk, iface)).when(
-                        chipMock.chip).createApIface(any(IWifiChip.createApIfaceCallback.class));
-
-                mDut.createApIface(destroyedListener, mHandler);
+                if (mWifiChipV15 != null && ifaceTypeToCreate == HDM_CREATE_IFACE_AP_BRIDGE) {
+                    IWifiIface ifaceApV15 = mock(android.hardware.wifi.V1_5.IWifiApIface.class);
+                    doAnswer(new GetNameAnswer(ifaceName)).when(ifaceApV15).getName(
+                            any(IWifiIface.getNameCallback.class));
+                    doAnswer(new GetTypeAnswer(IfaceType.AP)).when(ifaceApV15).getType(
+                            any(IWifiIface.getTypeCallback.class));
+                    doAnswer(new CreateXxxIfaceAnswer(chipMock, mStatusOk, ifaceApV15)).when(
+                            mWifiChipV15).createBridgedApIface(
+                            any(android.hardware.wifi.V1_5.IWifiChip
+                            .createBridgedApIfaceCallback.class));
+                } else {
+                    doAnswer(new CreateXxxIfaceAnswer(chipMock, mStatusOk, iface)).when(
+                            chipMock.chip).createApIface(
+                            any(IWifiChip.createApIfaceCallback.class));
+                }
+                mDut.createApIface(requiredChipCapabilities,
+                        destroyedListener, mHandler, requestorWs,
+                        ifaceTypeToCreate == HDM_CREATE_IFACE_AP_BRIDGE);
                 break;
-            case IfaceType.P2P:
+            case HDM_CREATE_IFACE_P2P:
                 iface = mock(IWifiP2pIface.class);
                 doAnswer(new GetNameAnswer(ifaceName)).when(iface).getName(
                         any(IWifiIface.getNameCallback.class));
@@ -2591,9 +2990,10 @@
                 doAnswer(new CreateXxxIfaceAnswer(chipMock, mStatusOk, iface)).when(
                         chipMock.chip).createP2pIface(any(IWifiChip.createP2pIfaceCallback.class));
 
-                mDut.createP2pIface(destroyedListener, mHandler);
+                mDut.createP2pIface(requiredChipCapabilities,
+                        destroyedListener, mHandler, requestorWs);
                 break;
-            case IfaceType.NAN:
+            case HDM_CREATE_IFACE_NAN:
                 iface = mock(IWifiNanIface.class);
                 doAnswer(new GetNameAnswer(ifaceName)).when(iface).getName(
                         any(IWifiIface.getNameCallback.class));
@@ -2602,13 +3002,9 @@
                 doAnswer(new CreateXxxIfaceAnswer(chipMock, mStatusOk, iface)).when(
                         chipMock.chip).createNanIface(any(IWifiChip.createNanIfaceCallback.class));
 
-                mDut.createNanIface(destroyedListener, mHandler);
+                mDut.createNanIface(destroyedListener, mHandler, requestorWs);
                 break;
         }
-        if (availableListener != null) {
-            mDut.registerInterfaceAvailableForRequestListener(ifaceTypeToCreate, availableListener,
-                    mHandler);
-        }
 
         // validate: optional tear down of interfaces
         if (tearDownList != null) {
@@ -2639,19 +3035,26 @@
 
         // validate: create interface
         switch (ifaceTypeToCreate) {
-            case IfaceType.STA:
+            case HDM_CREATE_IFACE_STA:
                 mInOrder.verify(chipMock.chip).createStaIface(
                         any(IWifiChip.createStaIfaceCallback.class));
                 break;
-            case IfaceType.AP:
-                mInOrder.verify(chipMock.chip).createApIface(
-                        any(IWifiChip.createApIfaceCallback.class));
+            case HDM_CREATE_IFACE_AP_BRIDGE:
+            case HDM_CREATE_IFACE_AP:
+                if (mWifiChipV15 != null && ifaceTypeToCreate == HDM_CREATE_IFACE_AP_BRIDGE) {
+                    mInOrder.verify(mWifiChipV15)
+                            .createBridgedApIface(any(android.hardware.wifi.V1_5.IWifiChip
+                            .createBridgedApIfaceCallback.class));
+                } else {
+                    mInOrder.verify(chipMock.chip).createApIface(
+                            any(IWifiChip.createApIfaceCallback.class));
+                }
                 break;
-            case IfaceType.P2P:
+            case HDM_CREATE_IFACE_P2P:
                 mInOrder.verify(chipMock.chip).createP2pIface(
                         any(IWifiChip.createP2pIfaceCallback.class));
                 break;
-            case IfaceType.NAN:
+            case HDM_CREATE_IFACE_NAN:
                 mInOrder.verify(chipMock.chip).createNanIface(
                         any(IWifiChip.createNanIfaceCallback.class));
                 break;
@@ -2665,6 +3068,21 @@
         return iface;
     }
 
+    private IWifiIface validateInterfaceSequence(ChipMockBase chipMock,
+            boolean chipModeValid, int chipModeId,
+            int ifaceTypeToCreate, String ifaceName, int finalChipMode,
+            IWifiIface[] tearDownList,
+            InterfaceDestroyedListener destroyedListener,
+            WorkSource requestorWs,
+            InterfaceDestroyedListenerWithIfaceName...destroyedInterfacesDestroyedListeners)
+            throws Exception {
+        return validateInterfaceSequence(chipMock, chipModeValid, chipModeId,
+                ifaceTypeToCreate, ifaceName,
+                finalChipMode, HalDeviceManager.CHIP_CAPABILITY_ANY,
+                tearDownList, destroyedListener, requestorWs,
+                destroyedInterfacesDestroyedListeners);
+    }
+
     private int getType(IWifiIface iface) throws Exception {
         Mutable<Integer> typeResp = new Mutable<>();
         iface.getType((WifiStatus status, int type) -> {
@@ -2743,6 +3161,31 @@
         }
     }
 
+    private class GetCapabilitiesAnswer extends MockAnswerUtil.AnswerWithArguments {
+        private ChipMockBase mChipMockBase;
+
+        GetCapabilitiesAnswer(ChipMockBase chipMockBase) {
+            mChipMockBase = chipMockBase;
+        }
+
+        public void answer(IWifiChip.getCapabilitiesCallback cb) {
+            cb.onValues(mStatusOk, mChipMockBase.chipCapabilities);
+        }
+    }
+
+    private class GetCapabilities_1_5Answer extends MockAnswerUtil.AnswerWithArguments {
+        private ChipMockBase mChipMockBase;
+
+        GetCapabilities_1_5Answer(ChipMockBase chipMockBase) {
+            mChipMockBase = chipMockBase;
+        }
+
+        public void answer(
+                android.hardware.wifi.V1_5.IWifiChip.getCapabilities_1_5Callback cb) {
+            cb.onValues(mStatusOk, mChipMockBase.chipCapabilities);
+        }
+    }
+
     private class GetIdAnswer extends MockAnswerUtil.AnswerWithArguments {
         private ChipMockBase mChipMockBase;
 
@@ -2878,6 +3321,11 @@
             addInterfaceInfo(IfaceType.AP);
         }
 
+        public void answer(android.hardware.wifi.V1_5.IWifiChip.createBridgedApIfaceCallback cb) {
+            cb.onValues(mStatus, (android.hardware.wifi.V1_5.IWifiApIface) mWifiIface);
+            addInterfaceInfo(IfaceType.AP);
+        }
+
         public void answer(IWifiChip.createP2pIfaceCallback cb) {
             cb.onValues(mStatus, (IWifiP2pIface) mWifiIface);
             addInterfaceInfo(IfaceType.P2P);
@@ -2965,6 +3413,7 @@
     private static final int CHIP_MOCK_V2 = 1;
     private static final int CHIP_MOCK_V3 = 2;
     private static final int CHIP_MOCK_V4 = 3;
+    private static final int CHIP_MOCK_V5 = 4;
 
     private class ChipMockBase {
         public int chipMockId;
@@ -2974,6 +3423,7 @@
         public boolean chipModeValid = false;
         public int chipModeId = -1000;
         public int chipModeIdValidForRtt = -1; // single chip mode ID where RTT can be created
+        public int chipCapabilities = 0;
         public Map<Integer, ArrayList<String>> interfaceNames = new HashMap<>();
         public Map<Integer, Map<String, IWifiIface>> interfacesByName = new HashMap<>();
 
@@ -2995,6 +3445,8 @@
             when(chip.registerEventCallback(any(IWifiChipEventCallback.class))).thenReturn(
                     mStatusOk);
             when(chip.configureChip(anyInt())).thenAnswer(new ConfigureChipAnswer(this));
+            doAnswer(new GetCapabilitiesAnswer(this))
+                    .when(chip).getCapabilities(any(IWifiChip.getCapabilitiesCallback.class));
             doAnswer(new GetIdAnswer(this)).when(chip).getId(any(IWifiChip.getIdCallback.class));
             doAnswer(new GetModeAnswer(this)).when(chip).getMode(
                     any(IWifiChip.getModeCallback.class));
@@ -3053,7 +3505,7 @@
             doAnswer(new GetChipAnswer(mStatusOk, chip)).when(mWifiMock).getChip(eq(10),
                     any(IWifi.getChipCallback.class));
 
-            // initialize dummy chip modes
+            // initialize placeholder chip modes
             IWifiChip.ChipMode cm;
             IWifiChip.ChipIfaceCombination cic;
             IWifiChip.ChipIfaceCombinationLimit cicl;
@@ -3118,7 +3570,7 @@
             doAnswer(new GetChipAnswer(mStatusOk, chip)).when(mWifiMock).getChip(eq(12),
                     any(IWifi.getChipCallback.class));
 
-            // initialize dummy chip modes
+            // initialize placeholder chip modes
             IWifiChip.ChipMode cm;
             IWifiChip.ChipIfaceCombination cic;
             IWifiChip.ChipIfaceCombinationLimit cicl;
@@ -3180,7 +3632,7 @@
             doAnswer(new GetChipAnswer(mStatusOk, chip)).when(mWifiMock).getChip(eq(15),
                     any(IWifi.getChipCallback.class));
 
-            // initialize dummy chip modes
+            // initialize placeholder chip modes
             IWifiChip.ChipMode cm;
             IWifiChip.ChipIfaceCombination cic;
             IWifiChip.ChipIfaceCombinationLimit cicl;
@@ -3252,7 +3704,80 @@
             doAnswer(new GetChipAnswer(mStatusOk, chip)).when(mWifiMock).getChip(eq(23),
                     any(IWifi.getChipCallback.class));
 
-            // initialize dummy chip modes
+            // initialize placeholder chip modes
+            IWifiChip.ChipMode cm;
+            IWifiChip.ChipIfaceCombination cic;
+            IWifiChip.ChipIfaceCombinationLimit cicl;
+
+            //   Mode 0 (only one): 1xSTA + 1xAP, 1xSTA + 1x{P2P,NAN}
+            availableModes = new ArrayList<>();
+            cm = new IWifiChip.ChipMode();
+            cm.id = CHIP_MODE_ID;
+
+            cic = new IWifiChip.ChipIfaceCombination();
+
+            cicl = new IWifiChip.ChipIfaceCombinationLimit();
+            cicl.maxIfaces = 1;
+            cicl.types.add(IfaceType.STA);
+            cic.limits.add(cicl);
+
+            cicl = new IWifiChip.ChipIfaceCombinationLimit();
+            cicl.maxIfaces = 1;
+            cicl.types.add(IfaceType.AP);
+            cic.limits.add(cicl);
+
+            cm.availableCombinations.add(cic);
+
+            cic = new IWifiChip.ChipIfaceCombination();
+
+            cicl = new IWifiChip.ChipIfaceCombinationLimit();
+            cicl.maxIfaces = 1;
+            cicl.types.add(IfaceType.STA);
+            cic.limits.add(cicl);
+
+            cicl = new IWifiChip.ChipIfaceCombinationLimit();
+            cicl.maxIfaces = 1;
+            cicl.types.add(IfaceType.P2P);
+            cicl.types.add(IfaceType.NAN);
+            cic.limits.add(cicl);
+
+            cm.availableCombinations.add(cic);
+            availableModes.add(cm);
+
+            chipModeIdValidForRtt = CHIP_MODE_ID;
+
+            doAnswer(new GetAvailableModesAnswer(this)).when(chip)
+                    .getAvailableModes(any(IWifiChip.getAvailableModesCallback.class));
+        }
+    }
+
+    // test chip configuration V5 for 60GHz:
+    // mode:
+    //    STA + AP
+    //    STA + (NAN || P2P)
+    private class TestChipV5 extends ChipMockBase {
+        // only mode (different number from any in other TestChips so can catch test errors)
+        static final int CHIP_MODE_ID = 3;
+        static final int CHIP_ID = 5;
+
+        void initialize() throws Exception {
+            super.initialize();
+            chipMockId = CHIP_MOCK_V5;
+
+            chipCapabilities |= android.hardware.wifi.V1_5.IWifiChip.ChipCapabilityMask.WIGIG;
+
+            // chip Id configuration
+            ArrayList<Integer> chipIds;
+            chipId = CHIP_ID;
+            chipIds = new ArrayList<>();
+            chipIds.add(chipId);
+            doAnswer(new GetChipIdsAnswer(mStatusOk, chipIds)).when(mWifiMock).getChipIds(
+                    any(IWifi.getChipIdsCallback.class));
+
+            doAnswer(new GetChipAnswer(mStatusOk, chip)).when(mWifiMock).getChip(eq(CHIP_ID),
+                    any(IWifi.getChipCallback.class));
+
+            // initialize placeholder chip modes
             IWifiChip.ChipMode cm;
             IWifiChip.ChipIfaceCombination cic;
             IWifiChip.ChipIfaceCombinationLimit cicl;
diff --git a/service/tests/wifitests/src/com/android/server/wifi/HostapdHalTest.java b/service/tests/wifitests/src/com/android/server/wifi/HostapdHalTest.java
index 8f8c7a5..c779da5 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/HostapdHalTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/HostapdHalTest.java
@@ -16,6 +16,7 @@
 package com.android.server.wifi;
 
 import static org.junit.Assert.*;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.anyString;
@@ -29,20 +30,24 @@
 import android.hardware.wifi.hostapd.V1_0.IHostapd;
 import android.hardware.wifi.hostapd.V1_1.IHostapdCallback;
 import android.hardware.wifi.hostapd.V1_2.DebugLevel;
+import android.hardware.wifi.hostapd.V1_3.Bandwidth;
+import android.hardware.wifi.hostapd.V1_3.Generation;
 import android.hidl.manager.V1_0.IServiceManager;
 import android.hidl.manager.V1_0.IServiceNotification;
 import android.net.MacAddress;
 import android.net.wifi.SoftApConfiguration;
 import android.net.wifi.SoftApConfiguration.Builder;
 import android.net.wifi.WifiManager;
-import android.net.wifi.nl80211.WifiNl80211Manager;
 import android.os.Handler;
 import android.os.IHwBinder;
 import android.os.RemoteException;
 import android.os.test.TestLooper;
+import android.util.SparseIntArray;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.modules.utils.build.SdkLevel;
+import com.android.server.wifi.util.ApConfigUtil;
 import com.android.server.wifi.util.NativeUtil;
 import com.android.wifi.resources.R;
 
@@ -63,15 +68,22 @@
     private static final String IFACE_NAME = "mock-wlan0";
     private static final String NETWORK_SSID = "test-ssid";
     private static final String NETWORK_PSK = "test-psk";
+    private static final String TEST_CLIENT_MAC = "11:22:33:44:55:66";
+    private static final String TEST_AP_INSTANCE = "instance-wlan0";
+
+    private final int mBand256G = SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ
+            | SoftApConfiguration.BAND_6GHZ;
 
     private @Mock Context mContext;
     private @Mock IServiceManager mServiceManagerMock;
     private @Mock IHostapd mIHostapdMock;
     private @Mock WifiNative.HostapdDeathEventHandler mHostapdHalDeathHandler;
-    private @Mock WifiNl80211Manager.SoftApCallback mSoftApListener;
+    private @Mock WifiNative.SoftApListener mSoftApListener;
     private android.hardware.wifi.hostapd.V1_1.IHostapd mIHostapdMockV11;
     private android.hardware.wifi.hostapd.V1_2.IHostapd mIHostapdMockV12;
+    private android.hardware.wifi.hostapd.V1_3.IHostapd mIHostapdMockV13;
     private IHostapdCallback mIHostapdCallback;
+    private android.hardware.wifi.hostapd.V1_3.IHostapdCallback mIHostapdCallback13;
     private MockResources mResources;
     HostapdStatus mStatusSuccess;
     HostapdStatus mStatusFailure;
@@ -93,11 +105,17 @@
     private ArgumentCaptor<android.hardware.wifi.hostapd.V1_2.IHostapd.IfaceParams>
             mIfaceParamsCaptorV12 =
             ArgumentCaptor.forClass(android.hardware.wifi.hostapd.V1_2.IHostapd.IfaceParams.class);
+    private ArgumentCaptor<android.hardware.wifi.hostapd.V1_3.IHostapd.IfaceParams>
+            mIfaceParamsCaptorV13 =
+            ArgumentCaptor.forClass(android.hardware.wifi.hostapd.V1_3.IHostapd.IfaceParams.class);
     private ArgumentCaptor<IHostapd.NetworkParams> mNetworkParamsCaptor =
             ArgumentCaptor.forClass(IHostapd.NetworkParams.class);
     private ArgumentCaptor<android.hardware.wifi.hostapd.V1_2.IHostapd.NetworkParams>
             mNetworkParamsV12Captor = ArgumentCaptor.forClass(
             android.hardware.wifi.hostapd.V1_2.IHostapd.NetworkParams.class);
+    private ArgumentCaptor<android.hardware.wifi.hostapd.V1_3.IHostapd.NetworkParams>
+            mNetworkParamsV13Captor = ArgumentCaptor.forClass(
+            android.hardware.wifi.hostapd.V1_3.IHostapd.NetworkParams.class);
     private ArgumentCaptor<Long> mDeathRecipientCookieCaptor = ArgumentCaptor.forClass(Long.class);
     private InOrder mInOrder;
 
@@ -127,6 +145,12 @@
                 throws RemoteException {
             return mIHostapdMockV12;
         }
+
+        @Override
+        protected android.hardware.wifi.hostapd.V1_3.IHostapd getHostapdMockableV1_3()
+                throws RemoteException {
+            return mIHostapdMockV13;
+        }
     }
 
     @Before
@@ -259,7 +283,7 @@
         configurationBuilder.setChannel(apChannel, SoftApConfiguration.BAND_2GHZ);
 
         assertTrue(mHostapdHal.addAccessPoint(IFACE_NAME,
-                configurationBuilder.build(),
+                configurationBuilder.build(), true,
                 () -> mSoftApListener.onFailure()));
         verify(mIHostapdMock).addAccessPoint(any(), any());
 
@@ -292,7 +316,7 @@
         configurationBuilder.setChannel(apChannel, SoftApConfiguration.BAND_5GHZ);
 
         assertTrue(mHostapdHal.addAccessPoint(IFACE_NAME,
-                configurationBuilder.build(),
+                configurationBuilder.build(), true,
                 () -> mSoftApListener.onFailure()));
         verify(mIHostapdMock).addAccessPoint(any(), any());
 
@@ -327,7 +351,7 @@
         configurationBuilder.setChannel(apChannel, SoftApConfiguration.BAND_5GHZ);
 
         assertTrue(mHostapdHal.addAccessPoint(IFACE_NAME,
-                configurationBuilder.build(),
+                configurationBuilder.build(), true,
                 () -> mSoftApListener.onFailure()));
         verify(mIHostapdMock).addAccessPoint(any(), any());
 
@@ -366,7 +390,7 @@
         configurationBuilder.setChannel(apChannel, SoftApConfiguration.BAND_2GHZ);
 
         assertTrue(mHostapdHal.addAccessPoint(IFACE_NAME,
-                configurationBuilder.build(),
+                configurationBuilder.build(), true,
                 () -> mSoftApListener.onFailure()));
         verify(mIHostapdMock).addAccessPoint(any(), any());
 
@@ -406,7 +430,7 @@
         configurationBuilder.setChannel(apChannel, SoftApConfiguration.BAND_2GHZ);
 
         assertTrue(mHostapdHal.addAccessPoint(IFACE_NAME,
-                configurationBuilder.build(),
+                configurationBuilder.build(), true,
                 () -> mSoftApListener.onFailure()));
         verify(mIHostapdMock).addAccessPoint(any(), any());
 
@@ -441,10 +465,10 @@
         configurationBuilder.setHiddenSsid(false);
         configurationBuilder.setPassphrase(NETWORK_PSK,
                 SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
-        configurationBuilder.setBand(SoftApConfiguration.BAND_ANY);
+        configurationBuilder.setBand(mBand256G);
 
         assertTrue(mHostapdHal.addAccessPoint(IFACE_NAME,
-                configurationBuilder.build(),
+                configurationBuilder.build(), true,
                 () -> mSoftApListener.onFailure()));
         verify(mIHostapdMock).addAccessPoint(any(), any());
 
@@ -478,10 +502,10 @@
         configurationBuilder.setHiddenSsid(false);
         configurationBuilder.setPassphrase(NETWORK_PSK,
                 SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
-        configurationBuilder.setBand(SoftApConfiguration.BAND_ANY);
+        configurationBuilder.setBand(mBand256G);
 
         assertTrue(mHostapdHal.addAccessPoint(IFACE_NAME,
-                configurationBuilder.build(),
+                configurationBuilder.build(), true,
                 () -> mSoftApListener.onFailure()));
         verify(mIHostapdMock).addAccessPoint(any(), any());
 
@@ -547,10 +571,10 @@
         configurationBuilder.setHiddenSsid(false);
         configurationBuilder.setPassphrase(NETWORK_PSK,
                 SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
-        configurationBuilder.setBand(SoftApConfiguration.BAND_ANY);
+        configurationBuilder.setBand(mBand256G);
 
         assertTrue(mHostapdHal.addAccessPoint(IFACE_NAME,
-                configurationBuilder.build(),
+                configurationBuilder.build(), true,
                 () -> mSoftApListener.onFailure()));
         verify(mIHostapdMockV11).addAccessPoint_1_1(any(), any());
 
@@ -585,7 +609,7 @@
         configurationBuilder.setChannel(6, SoftApConfiguration.BAND_2GHZ);
 
         assertFalse(mHostapdHal.addAccessPoint(IFACE_NAME,
-                configurationBuilder.build(),
+                configurationBuilder.build(), true,
                 () -> mSoftApListener.onFailure()));
         verify(mIHostapdMock).addAccessPoint(any(), any());
     }
@@ -604,7 +628,7 @@
         configurationBuilder.setChannel(6, SoftApConfiguration.BAND_2GHZ);
 
         assertFalse(mHostapdHal.addAccessPoint(IFACE_NAME,
-                configurationBuilder.build(),
+                configurationBuilder.build(), true,
                 () -> mSoftApListener.onFailure()));
         verify(mIHostapdMock).addAccessPoint(any(), any());
     }
@@ -663,7 +687,7 @@
         configurationBuilder.setBand(SoftApConfiguration.BAND_2GHZ);
 
         assertTrue(mHostapdHal.addAccessPoint(IFACE_NAME,
-                configurationBuilder.build(),
+                configurationBuilder.build(), true,
                 () -> mSoftApListener.onFailure()));
         verify(mIHostapdMockV11).addAccessPoint_1_1(any(), any());
 
@@ -683,8 +707,8 @@
     }
 
     /**
-     * Calls.initialize(), mocking various callback answers and verifying flow, asserting for the
-     * expected result. Verifies if IHostapd manager is initialized or reset.
+     * Calls.initialize() on HIDL 1.0, mocking various callback answers and verifying flow,
+     * asserting for the expected result. Verifies if IHostapd manager is initialized or reset.
      */
     private void executeAndValidateInitializationSequence(
             boolean causeRegisterRemoteException, boolean causeRegisterFailure) throws Exception {
@@ -719,8 +743,8 @@
     }
 
     /**
-     * Calls.initialize(), mocking various callback answers and verifying flow, asserting for the
-     * expected result. Verifies if IHostapd manager is initialized or reset.
+     * Calls.initialize() on HIDL V1.1, mocking various callback answers and verifying flow,
+     * asserting for the expected result. Verifies if IHostapd manager is initialized or reset.
      */
     private void executeAndValidateInitializationSequenceV1_1(
             boolean causeCallbackFailure) throws Exception {
@@ -757,8 +781,8 @@
     }
 
     /**
-     * Calls.initialize(), mocking various callback answers and verifying flow, asserting for the
-     * expected result. Verifies if IHostapd manager is initialized or reset.
+     * Calls.initialize() on HIDL 1.2, mocking various callback answers and verifying flow,
+     * asserting for the expected result. Verifies if IHostapd manager is initialized or reset.
      */
     private void executeAndValidateInitializationSequenceV1_2(
             boolean causeCallbackFailure) throws Exception {
@@ -796,6 +820,61 @@
         verify(mIHostapdMockV11).registerCallback(any(IHostapdCallback.class));
     }
 
+    /**
+     * Calls.initialize() on HIDL 1.3, mocking various callback answers and verifying flow,
+     * asserting for the expected result. Verifies if IHostapd manager is initialized or reset.
+     */
+    private void executeAndValidateInitializationSequenceV1_3(
+            boolean causeCallbackFailure) throws Exception {
+        boolean shouldSucceed = !causeCallbackFailure;
+        mInOrder = inOrder(mServiceManagerMock, mIHostapdMock);
+        when(mIHostapdMockV12.setDebugParams(anyInt()))
+                .thenReturn(mStatusSuccess12);
+        if (causeCallbackFailure) {
+            doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+                public android.hardware.wifi.hostapd.V1_2.HostapdStatus answer(
+                        android.hardware.wifi.hostapd.V1_3.IHostapdCallback cb)
+                        throws RemoteException {
+                    return mStatusFailure12;
+                }
+            }).when(mIHostapdMockV13).registerCallback_1_3(
+                    any(android.hardware.wifi.hostapd.V1_3.IHostapdCallback.class));
+        } else {
+            doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+                public android.hardware.wifi.hostapd.V1_2.HostapdStatus answer(
+                        android.hardware.wifi.hostapd.V1_3.IHostapdCallback cb)
+                        throws RemoteException {
+                    mIHostapdCallback13 = cb;
+                    return mStatusSuccess12;
+                }
+            }).when(mIHostapdMockV13).registerCallback_1_3(
+                    any(android.hardware.wifi.hostapd.V1_3.IHostapdCallback.class));
+        }
+        // Initialize HostapdHal, should call serviceManager.registerForNotifications
+        assertTrue(mHostapdHal.initialize());
+        // verify: service manager initialization sequence
+        mInOrder.verify(mServiceManagerMock).linkToDeath(mServiceManagerDeathCaptor.capture(),
+                anyLong());
+        mInOrder.verify(mServiceManagerMock).registerForNotifications(
+                eq(IHostapd.kInterfaceName), eq(""), mServiceNotificationCaptor.capture());
+        // act: cause the onRegistration(...) callback to execute
+        mServiceNotificationCaptor.getValue().onRegistration(IHostapd.kInterfaceName, "", true);
+        assertEquals(shouldSucceed, mHostapdHal.isInitializationComplete());
+        mInOrder.verify(mIHostapdMock).linkToDeath(mHostapdDeathCaptor.capture(), anyLong());
+        verify(mIHostapdMockV11, never()).registerCallback(any(IHostapdCallback.class));
+        verify(mIHostapdMockV13).registerCallback_1_3(
+                any(android.hardware.wifi.hostapd.V1_3.IHostapdCallback.class));
+    }
+
+    /**
+     * Calls.initialize() on last HIDL, mocking various callback answers and verifying flow,
+     * asserting for the expected result. Verifies if IHostapd manager is initialized or reset.
+     */
+    private void executeAndValidateInitializationSequenceOnLastHIDL(
+            boolean causeCallbackFailure) throws Exception {
+        executeAndValidateInitializationSequenceV1_3(causeCallbackFailure);
+    }
+
     private HostapdStatus createHostapdStatus(int code) {
         HostapdStatus status = new HostapdStatus();
         status.code = code;
@@ -917,13 +996,67 @@
      */
     @Test
     public void testInitialize_successV1_2() throws Exception {
-        when(mServiceManagerMock.getTransport(anyString(), anyString()))
+        when(mServiceManagerMock.getTransport(eq(
+                android.hardware.wifi.hostapd.V1_0.IHostapd.kInterfaceName), anyString()))
+                .thenReturn(IServiceManager.Transport.HWBINDER);
+        when(mServiceManagerMock.getTransport(eq(
+                android.hardware.wifi.hostapd.V1_1.IHostapd.kInterfaceName), anyString()))
+                .thenReturn(IServiceManager.Transport.HWBINDER);
+        when(mServiceManagerMock.getTransport(eq(
+                android.hardware.wifi.hostapd.V1_2.IHostapd.kInterfaceName), anyString()))
                 .thenReturn(IServiceManager.Transport.HWBINDER);
         mIHostapdMockV11 = mock(android.hardware.wifi.hostapd.V1_1.IHostapd.class);
         mIHostapdMockV12 = mock(android.hardware.wifi.hostapd.V1_2.IHostapd.class);
         executeAndValidateInitializationSequenceV1_2(false);
     }
 
+    /*
+     * Sunny day scenario for V1.3 HostapdHal initialization
+     * Asserts successful initialization
+     */
+    @Test
+    public void testInitialize_successV1_3() throws Exception {
+        when(mServiceManagerMock.getTransport(eq(
+                android.hardware.wifi.hostapd.V1_0.IHostapd.kInterfaceName), anyString()))
+                .thenReturn(IServiceManager.Transport.HWBINDER);
+        when(mServiceManagerMock.getTransport(eq(
+                android.hardware.wifi.hostapd.V1_1.IHostapd.kInterfaceName), anyString()))
+                .thenReturn(IServiceManager.Transport.HWBINDER);
+        when(mServiceManagerMock.getTransport(eq(
+                android.hardware.wifi.hostapd.V1_2.IHostapd.kInterfaceName), anyString()))
+                .thenReturn(IServiceManager.Transport.HWBINDER);
+        when(mServiceManagerMock.getTransport(eq(
+                android.hardware.wifi.hostapd.V1_3.IHostapd.kInterfaceName), anyString()))
+                .thenReturn(IServiceManager.Transport.HWBINDER);
+        mIHostapdMockV11 = mock(android.hardware.wifi.hostapd.V1_1.IHostapd.class);
+        mIHostapdMockV12 = mock(android.hardware.wifi.hostapd.V1_2.IHostapd.class);
+        mIHostapdMockV13 = mock(android.hardware.wifi.hostapd.V1_3.IHostapd.class);
+        executeAndValidateInitializationSequenceV1_3(false);
+    }
+
+    /**
+     * Failure scenario for V1.1 HostapdHal initialization
+     */
+    @Test
+    public void testInitialize_registerCallbackFailureV1_3() throws Exception {
+        when(mServiceManagerMock.getTransport(eq(
+                android.hardware.wifi.hostapd.V1_0.IHostapd.kInterfaceName), anyString()))
+                .thenReturn(IServiceManager.Transport.HWBINDER);
+        when(mServiceManagerMock.getTransport(eq(
+                android.hardware.wifi.hostapd.V1_1.IHostapd.kInterfaceName), anyString()))
+                .thenReturn(IServiceManager.Transport.HWBINDER);
+        when(mServiceManagerMock.getTransport(eq(
+                android.hardware.wifi.hostapd.V1_2.IHostapd.kInterfaceName), anyString()))
+                .thenReturn(IServiceManager.Transport.HWBINDER);
+        when(mServiceManagerMock.getTransport(eq(
+                android.hardware.wifi.hostapd.V1_3.IHostapd.kInterfaceName), anyString()))
+                .thenReturn(IServiceManager.Transport.HWBINDER);
+        mIHostapdMockV11 = mock(android.hardware.wifi.hostapd.V1_1.IHostapd.class);
+        mIHostapdMockV12 = mock(android.hardware.wifi.hostapd.V1_2.IHostapd.class);
+        mIHostapdMockV13 = mock(android.hardware.wifi.hostapd.V1_3.IHostapd.class);
+        executeAndValidateInitializationSequenceV1_3(true);
+    }
+
     /**
      * Verifies the successful addition of access point with SAE.
      */
@@ -933,41 +1066,42 @@
                 .thenReturn(IServiceManager.Transport.HWBINDER);
         mIHostapdMockV11 = mock(android.hardware.wifi.hostapd.V1_1.IHostapd.class);
         mIHostapdMockV12 = mock(android.hardware.wifi.hostapd.V1_2.IHostapd.class);
+        mIHostapdMockV13 = mock(android.hardware.wifi.hostapd.V1_3.IHostapd.class);
         // Disable ACS in the config.
         mResources.setBoolean(R.bool.config_wifi_softap_acs_supported, false);
         mHostapdHal = new HostapdHalSpy();
 
-        executeAndValidateInitializationSequenceV1_2(false);
+        executeAndValidateInitializationSequenceOnLastHIDL(false);
 
         Builder configurationBuilder = new SoftApConfiguration.Builder();
         configurationBuilder.setSsid(NETWORK_SSID);
         configurationBuilder.setHiddenSsid(false);
         configurationBuilder.setPassphrase(NETWORK_PSK,
                 SoftApConfiguration.SECURITY_TYPE_WPA3_SAE);
-        configurationBuilder.setBand(SoftApConfiguration.BAND_ANY);
+        configurationBuilder.setBand(mBand256G);
 
-        when(mIHostapdMockV12.addAccessPoint_1_2(
-                mIfaceParamsCaptorV12.capture(), mNetworkParamsV12Captor.capture()))
+        when(mIHostapdMockV13.addAccessPoint_1_3(
+                mIfaceParamsCaptorV13.capture(), mNetworkParamsV13Captor.capture()))
                 .thenReturn(mStatusSuccess12);
 
 
         assertTrue(mHostapdHal.addAccessPoint(IFACE_NAME,
-                configurationBuilder.build(),
+                configurationBuilder.build(), true,
                 () -> mSoftApListener.onFailure()));
-        verify(mIHostapdMockV12).addAccessPoint_1_2(any(), any());
+        verify(mIHostapdMockV13).addAccessPoint_1_3(any(), any());
 
-        assertEquals(IFACE_NAME, mIfaceParamsCaptorV12.getValue().V1_1.V1_0.ifaceName);
-        assertTrue(mIfaceParamsCaptorV12.getValue().V1_1.V1_0.hwModeParams.enable80211N);
-        assertFalse(mIfaceParamsCaptorV12.getValue().V1_1.V1_0.hwModeParams.enable80211AC);
-        assertFalse(mIfaceParamsCaptorV12.getValue().V1_1.V1_0.channelParams.enableAcs);
+        assertEquals(IFACE_NAME, mIfaceParamsCaptorV13.getValue().V1_2.V1_1.V1_0.ifaceName);
+        assertTrue(mIfaceParamsCaptorV13.getValue().V1_2.V1_1.V1_0.hwModeParams.enable80211N);
+        assertFalse(mIfaceParamsCaptorV13.getValue().V1_2.V1_1.V1_0.hwModeParams.enable80211AC);
+        assertFalse(mIfaceParamsCaptorV13.getValue().V1_2.V1_1.V1_0.channelParams.enableAcs);
 
         assertEquals(NativeUtil.stringToByteArrayList(NETWORK_SSID),
-                mNetworkParamsV12Captor.getValue().V1_0.ssid);
-        assertFalse(mNetworkParamsV12Captor.getValue().V1_0.isHidden);
+                mNetworkParamsV13Captor.getValue().V1_2.V1_0.ssid);
+        assertFalse(mNetworkParamsV13Captor.getValue().V1_2.V1_0.isHidden);
         assertEquals(android.hardware.wifi.hostapd.V1_2.IHostapd.EncryptionType.WPA3_SAE,
-                mNetworkParamsV12Captor.getValue().encryptionType);
-        assertEquals(NETWORK_PSK, mNetworkParamsV12Captor.getValue().passphrase);
-        assertEquals("", mNetworkParamsV12Captor.getValue().V1_0.pskPassphrase);
+                mNetworkParamsV13Captor.getValue().V1_2.encryptionType);
+        assertEquals(NETWORK_PSK, mNetworkParamsV13Captor.getValue().V1_2.passphrase);
+        assertEquals("", mNetworkParamsV13Captor.getValue().V1_2.V1_0.pskPassphrase);
     }
 
     /**
@@ -979,41 +1113,42 @@
                 .thenReturn(IServiceManager.Transport.HWBINDER);
         mIHostapdMockV11 = mock(android.hardware.wifi.hostapd.V1_1.IHostapd.class);
         mIHostapdMockV12 = mock(android.hardware.wifi.hostapd.V1_2.IHostapd.class);
+        mIHostapdMockV13 = mock(android.hardware.wifi.hostapd.V1_3.IHostapd.class);
         // Disable ACS in the config.
         mResources.setBoolean(R.bool.config_wifi_softap_acs_supported, false);
         mHostapdHal = new HostapdHalSpy();
 
-        executeAndValidateInitializationSequenceV1_2(false);
+        executeAndValidateInitializationSequenceOnLastHIDL(false);
 
         Builder configurationBuilder = new SoftApConfiguration.Builder();
         configurationBuilder.setSsid(NETWORK_SSID);
         configurationBuilder.setHiddenSsid(false);
         configurationBuilder.setPassphrase(NETWORK_PSK,
                 SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION);
-        configurationBuilder.setBand(SoftApConfiguration.BAND_ANY);
+        configurationBuilder.setBand(mBand256G);
 
-        when(mIHostapdMockV12.addAccessPoint_1_2(
-                mIfaceParamsCaptorV12.capture(), mNetworkParamsV12Captor.capture()))
+        when(mIHostapdMockV13.addAccessPoint_1_3(
+                mIfaceParamsCaptorV13.capture(), mNetworkParamsV13Captor.capture()))
                 .thenReturn(mStatusSuccess12);
 
 
         assertTrue(mHostapdHal.addAccessPoint(IFACE_NAME,
-                configurationBuilder.build(),
+                configurationBuilder.build(), true,
                 () -> mSoftApListener.onFailure()));
-        verify(mIHostapdMockV12).addAccessPoint_1_2(any(), any());
+        verify(mIHostapdMockV13).addAccessPoint_1_3(any(), any());
 
-        assertEquals(IFACE_NAME, mIfaceParamsCaptorV12.getValue().V1_1.V1_0.ifaceName);
-        assertTrue(mIfaceParamsCaptorV12.getValue().V1_1.V1_0.hwModeParams.enable80211N);
-        assertFalse(mIfaceParamsCaptorV12.getValue().V1_1.V1_0.hwModeParams.enable80211AC);
-        assertFalse(mIfaceParamsCaptorV12.getValue().V1_1.V1_0.channelParams.enableAcs);
+        assertEquals(IFACE_NAME, mIfaceParamsCaptorV13.getValue().V1_2.V1_1.V1_0.ifaceName);
+        assertTrue(mIfaceParamsCaptorV13.getValue().V1_2.V1_1.V1_0.hwModeParams.enable80211N);
+        assertFalse(mIfaceParamsCaptorV13.getValue().V1_2.V1_1.V1_0.hwModeParams.enable80211AC);
+        assertFalse(mIfaceParamsCaptorV13.getValue().V1_2.V1_1.V1_0.channelParams.enableAcs);
 
         assertEquals(NativeUtil.stringToByteArrayList(NETWORK_SSID),
-                mNetworkParamsV12Captor.getValue().V1_0.ssid);
-        assertFalse(mNetworkParamsV12Captor.getValue().V1_0.isHidden);
+                mNetworkParamsV13Captor.getValue().V1_2.V1_0.ssid);
+        assertFalse(mNetworkParamsV13Captor.getValue().V1_2.V1_0.isHidden);
         assertEquals(android.hardware.wifi.hostapd.V1_2.IHostapd.EncryptionType.WPA3_SAE_TRANSITION,
-                mNetworkParamsV12Captor.getValue().encryptionType);
-        assertEquals(NETWORK_PSK, mNetworkParamsV12Captor.getValue().passphrase);
-        assertEquals("", mNetworkParamsV12Captor.getValue().V1_0.pskPassphrase);
+                mNetworkParamsV13Captor.getValue().V1_2.encryptionType);
+        assertEquals(NETWORK_PSK, mNetworkParamsV13Captor.getValue().V1_2.passphrase);
+        assertEquals("", mNetworkParamsV13Captor.getValue().V1_2.V1_0.pskPassphrase);
     }
 
     /**
@@ -1021,7 +1156,11 @@
      */
     @Test
     public void testAddAccessPointFailure_SAEWithOldHal() throws Exception {
-        when(mServiceManagerMock.getTransport(anyString(), anyString()))
+        when(mServiceManagerMock.getTransport(eq(
+                android.hardware.wifi.hostapd.V1_0.IHostapd.kInterfaceName), anyString()))
+                .thenReturn(IServiceManager.Transport.HWBINDER);
+        when(mServiceManagerMock.getTransport(eq(
+                android.hardware.wifi.hostapd.V1_1.IHostapd.kInterfaceName), anyString()))
                 .thenReturn(IServiceManager.Transport.HWBINDER);
         mIHostapdMockV11 = mock(android.hardware.wifi.hostapd.V1_1.IHostapd.class);
         // Disable ACS in the config.
@@ -1035,10 +1174,10 @@
         configurationBuilder.setHiddenSsid(false);
         configurationBuilder.setPassphrase(NETWORK_PSK,
                 SoftApConfiguration.SECURITY_TYPE_WPA3_SAE);
-        configurationBuilder.setBand(SoftApConfiguration.BAND_ANY);
+        configurationBuilder.setBand(mBand256G);
 
         assertFalse(mHostapdHal.addAccessPoint(IFACE_NAME,
-                configurationBuilder.build(),
+                configurationBuilder.build(), true,
                 () -> mSoftApListener.onFailure()));
     }
 
@@ -1059,10 +1198,10 @@
         configurationBuilder.setHiddenSsid(false);
         configurationBuilder.setPassphrase(NETWORK_PSK,
                 SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
-        configurationBuilder.setBand(SoftApConfiguration.BAND_ANY);
+        configurationBuilder.setBand(mBand256G);
 
         assertTrue(mHostapdHal.addAccessPoint(IFACE_NAME,
-                configurationBuilder.build(),
+                configurationBuilder.build(), true,
                 () -> mSoftApListener.onFailure()));
         verify(mIHostapdMock).addAccessPoint(any(), any());
 
@@ -1079,5 +1218,362 @@
         assertEquals(IHostapd.EncryptionType.WPA2, mNetworkParamsCaptor.getValue().encryptionType);
         assertEquals(NETWORK_PSK, mNetworkParamsCaptor.getValue().pskPassphrase);
     }
+
+
+    /*
+     * Sunny day scenario for V1.3 HostapdHal initialization
+     * Asserts successful initialization
+     */
+    @Test
+    public void testHostapdCallbackEventAfter1_3() throws Exception {
+        when(mServiceManagerMock.getTransport(eq(
+                android.hardware.wifi.hostapd.V1_0.IHostapd.kInterfaceName), anyString()))
+                .thenReturn(IServiceManager.Transport.HWBINDER);
+        when(mServiceManagerMock.getTransport(eq(
+                android.hardware.wifi.hostapd.V1_1.IHostapd.kInterfaceName), anyString()))
+                .thenReturn(IServiceManager.Transport.HWBINDER);
+        when(mServiceManagerMock.getTransport(eq(
+                android.hardware.wifi.hostapd.V1_2.IHostapd.kInterfaceName), anyString()))
+                .thenReturn(IServiceManager.Transport.HWBINDER);
+        when(mServiceManagerMock.getTransport(eq(
+                android.hardware.wifi.hostapd.V1_3.IHostapd.kInterfaceName), anyString()))
+                .thenReturn(IServiceManager.Transport.HWBINDER);
+        mIHostapdMockV11 = mock(android.hardware.wifi.hostapd.V1_1.IHostapd.class);
+        mIHostapdMockV12 = mock(android.hardware.wifi.hostapd.V1_2.IHostapd.class);
+        mIHostapdMockV13 = mock(android.hardware.wifi.hostapd.V1_3.IHostapd.class);
+        executeAndValidateInitializationSequenceV1_3(false);
+        Builder configurationBuilder = new SoftApConfiguration.Builder();
+        configurationBuilder.setSsid(NETWORK_SSID);
+        configurationBuilder.setBand(SoftApConfiguration.BAND_2GHZ);
+
+        when(mIHostapdMockV13.addAccessPoint_1_3(any(), any()))
+                .thenReturn(mStatusSuccess12);
+        assertTrue(mHostapdHal.addAccessPoint(IFACE_NAME,
+                configurationBuilder.build(), true,
+                () -> mSoftApListener.onFailure()));
+        verify(mIHostapdMockV13).addAccessPoint_1_3(any(), any());
+
+        // Trigger on failure.
+        mIHostapdCallback13.onFailure(IFACE_NAME);
+        verify(mSoftApListener).onFailure();
+        // Register SoftApManager callback
+        mHostapdHal.registerApCallback(IFACE_NAME, mSoftApListener);
+
+        int testFreq = 2412;
+        int testBandwidth = Bandwidth.WIFI_BANDWIDTH_20;
+        int testGeneration = Generation.WIFI_STANDARD_11N;
+        // Trigger on info changed.
+        mIHostapdCallback13.onApInstanceInfoChanged(IFACE_NAME, TEST_AP_INSTANCE,
+                testFreq, testBandwidth, testGeneration,
+                MacAddress.fromString(TEST_CLIENT_MAC).toByteArray());
+        verify(mSoftApListener).onInfoChanged(eq(TEST_AP_INSTANCE), eq(testFreq),
+                eq(mHostapdHal.mapHalBandwidthToSoftApInfo(testBandwidth)),
+                eq(mHostapdHal.mapHalGenerationToWifiStandard(testGeneration)),
+                eq(MacAddress.fromString(TEST_CLIENT_MAC)));
+
+        // Trigger on client connected.
+        mIHostapdCallback13.onConnectedClientsChanged(IFACE_NAME, TEST_AP_INSTANCE,
+                MacAddress.fromString(TEST_CLIENT_MAC).toByteArray(), true);
+        verify(mSoftApListener).onConnectedClientsChanged(eq(TEST_AP_INSTANCE),
+                eq(MacAddress.fromString(TEST_CLIENT_MAC)), eq(true));
+    }
+
+    /**
+     * Verifies the successful addition of access point with SAE with metered indication.
+     */
+    @Test
+    public void testAddAccessPointSuccess_WithMeteredSAE() throws Exception {
+        boolean isMetered = true;
+        mResources.setBoolean(R.bool.config_wifi_softap_acs_supported, true);
+        when(mServiceManagerMock.getTransport(anyString(), anyString()))
+                .thenReturn(IServiceManager.Transport.HWBINDER);
+        mIHostapdMockV11 = mock(android.hardware.wifi.hostapd.V1_1.IHostapd.class);
+        mIHostapdMockV12 = mock(android.hardware.wifi.hostapd.V1_2.IHostapd.class);
+        mIHostapdMockV13 = mock(android.hardware.wifi.hostapd.V1_3.IHostapd.class);
+        mHostapdHal = new HostapdHalSpy();
+
+        executeAndValidateInitializationSequenceOnLastHIDL(false);
+
+        Builder configurationBuilder = new SoftApConfiguration.Builder();
+        configurationBuilder.setSsid(NETWORK_SSID);
+        configurationBuilder.setHiddenSsid(false);
+        configurationBuilder.setPassphrase(NETWORK_PSK,
+                SoftApConfiguration.SECURITY_TYPE_WPA3_SAE);
+        configurationBuilder.setBand(mBand256G);
+
+        when(mIHostapdMockV13.addAccessPoint_1_3(
+                mIfaceParamsCaptorV13.capture(), mNetworkParamsV13Captor.capture()))
+                .thenReturn(mStatusSuccess12);
+
+
+        assertTrue(mHostapdHal.addAccessPoint(IFACE_NAME,
+                configurationBuilder.build(), isMetered,
+                () -> mSoftApListener.onFailure()));
+        verify(mIHostapdMockV13).addAccessPoint_1_3(any(), any());
+
+        assertEquals(IFACE_NAME, mIfaceParamsCaptorV13.getValue().V1_2.V1_1.V1_0.ifaceName);
+        assertTrue(mIfaceParamsCaptorV13.getValue().V1_2.V1_1.V1_0.hwModeParams.enable80211N);
+        assertFalse(mIfaceParamsCaptorV13.getValue().V1_2.V1_1.V1_0.hwModeParams.enable80211AC);
+        assertTrue(mIfaceParamsCaptorV13.getValue().V1_2.V1_1.V1_0.channelParams.enableAcs);
+
+        assertEquals(NativeUtil.stringToByteArrayList(NETWORK_SSID),
+                mNetworkParamsV13Captor.getValue().V1_2.V1_0.ssid);
+        assertFalse(mNetworkParamsV13Captor.getValue().V1_2.V1_0.isHidden);
+        assertEquals(android.hardware.wifi.hostapd.V1_2.IHostapd.EncryptionType.WPA3_SAE,
+                mNetworkParamsV13Captor.getValue().V1_2.encryptionType);
+        assertEquals(NETWORK_PSK, mNetworkParamsV13Captor.getValue().V1_2.passphrase);
+        assertEquals("", mNetworkParamsV13Captor.getValue().V1_2.V1_0.pskPassphrase);
+        assertTrue(mNetworkParamsV13Captor.getValue().isMetered);
+    }
+
+    /**
+     * Verifies the successful addition of access point with SAE with non metered indication.
+     */
+    @Test
+    public void testAddAccessPointSuccess_WithNonMeteredSAE() throws Exception {
+        boolean isMetered = false;
+        mResources.setBoolean(R.bool.config_wifi_softap_acs_supported, true);
+        when(mServiceManagerMock.getTransport(anyString(), anyString()))
+                .thenReturn(IServiceManager.Transport.HWBINDER);
+        mIHostapdMockV11 = mock(android.hardware.wifi.hostapd.V1_1.IHostapd.class);
+        mIHostapdMockV12 = mock(android.hardware.wifi.hostapd.V1_2.IHostapd.class);
+        mIHostapdMockV13 = mock(android.hardware.wifi.hostapd.V1_3.IHostapd.class);
+        mHostapdHal = new HostapdHalSpy();
+
+        executeAndValidateInitializationSequenceOnLastHIDL(false);
+
+        Builder configurationBuilder = new SoftApConfiguration.Builder();
+        configurationBuilder.setSsid(NETWORK_SSID);
+        configurationBuilder.setHiddenSsid(false);
+        configurationBuilder.setPassphrase(NETWORK_PSK,
+                SoftApConfiguration.SECURITY_TYPE_WPA3_SAE);
+        configurationBuilder.setBand(mBand256G);
+
+        when(mIHostapdMockV13.addAccessPoint_1_3(
+                mIfaceParamsCaptorV13.capture(), mNetworkParamsV13Captor.capture()))
+                .thenReturn(mStatusSuccess12);
+
+
+        assertTrue(mHostapdHal.addAccessPoint(IFACE_NAME,
+                configurationBuilder.build(), isMetered,
+                () -> mSoftApListener.onFailure()));
+        verify(mIHostapdMockV13).addAccessPoint_1_3(any(), any());
+
+        assertEquals(IFACE_NAME, mIfaceParamsCaptorV13.getValue().V1_2.V1_1.V1_0.ifaceName);
+        assertTrue(mIfaceParamsCaptorV13.getValue().V1_2.V1_1.V1_0.hwModeParams.enable80211N);
+        assertFalse(mIfaceParamsCaptorV13.getValue().V1_2.V1_1.V1_0.hwModeParams.enable80211AC);
+        assertTrue(mIfaceParamsCaptorV13.getValue().V1_2.V1_1.V1_0.channelParams.enableAcs);
+
+        assertEquals(NativeUtil.stringToByteArrayList(NETWORK_SSID),
+                mNetworkParamsV13Captor.getValue().V1_2.V1_0.ssid);
+        assertFalse(mNetworkParamsV13Captor.getValue().V1_2.V1_0.isHidden);
+        assertEquals(android.hardware.wifi.hostapd.V1_2.IHostapd.EncryptionType.WPA3_SAE,
+                mNetworkParamsV13Captor.getValue().V1_2.encryptionType);
+        assertEquals(NETWORK_PSK, mNetworkParamsV13Captor.getValue().V1_2.passphrase);
+        assertEquals("", mNetworkParamsV13Captor.getValue().V1_2.V1_0.pskPassphrase);
+        assertFalse(mNetworkParamsV13Captor.getValue().isMetered);
+    }
+
+    /**
+     * Verifies the successful addition of access point with Dual channel config.
+     */
+    @Test
+    public void testAddAccessPointSuccess_DualBandConfig() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS()); // dual band supported on S.
+        mResources.setBoolean(R.bool.config_wifi_softap_acs_supported, true);
+        when(mServiceManagerMock.getTransport(anyString(), anyString()))
+                .thenReturn(IServiceManager.Transport.HWBINDER);
+        mIHostapdMockV11 = mock(android.hardware.wifi.hostapd.V1_1.IHostapd.class);
+        mIHostapdMockV12 = mock(android.hardware.wifi.hostapd.V1_2.IHostapd.class);
+        mIHostapdMockV13 = mock(android.hardware.wifi.hostapd.V1_3.IHostapd.class);
+        // Enable ACS and set available channels in the config.
+        final String acsChannelStr2g = "1,6,11-13";
+        final String acsChannelStr5g = "40";
+        final String acsChannelStr6g = "";
+        mResources.setString(R.string.config_wifiSoftap2gChannelList, acsChannelStr2g);
+        mResources.setString(R.string.config_wifiSoftap5gChannelList, acsChannelStr5g);
+        mResources.setString(R.string.config_wifiSoftap6gChannelList, acsChannelStr6g);
+
+        android.hardware.wifi.hostapd.V1_2.IHostapd.AcsFrequencyRange freqRange1 =
+                new android.hardware.wifi.hostapd.V1_2.IHostapd.AcsFrequencyRange();
+        android.hardware.wifi.hostapd.V1_2.IHostapd.AcsFrequencyRange freqRange2 =
+                new android.hardware.wifi.hostapd.V1_2.IHostapd.AcsFrequencyRange();
+        android.hardware.wifi.hostapd.V1_2.IHostapd.AcsFrequencyRange freqRange3 =
+                new android.hardware.wifi.hostapd.V1_2.IHostapd.AcsFrequencyRange();
+        android.hardware.wifi.hostapd.V1_2.IHostapd.AcsFrequencyRange freqRange4 =
+                new android.hardware.wifi.hostapd.V1_2.IHostapd.AcsFrequencyRange();
+
+        freqRange1.start = freqRange1.end = ApConfigUtil.convertChannelToFrequency(
+                1, SoftApConfiguration.BAND_2GHZ);
+        freqRange2.start = freqRange2.end = ApConfigUtil.convertChannelToFrequency(
+                6, SoftApConfiguration.BAND_2GHZ);
+        freqRange3.start = ApConfigUtil.convertChannelToFrequency(11,
+                SoftApConfiguration.BAND_2GHZ);
+        freqRange3.end = ApConfigUtil.convertChannelToFrequency(13, SoftApConfiguration.BAND_2GHZ);
+        ArrayList<android.hardware.wifi.hostapd.V1_2.IHostapd.AcsFrequencyRange> acsFreqRanges =
+                new ArrayList<>();
+        acsFreqRanges.add(freqRange1);
+        acsFreqRanges.add(freqRange2);
+        acsFreqRanges.add(freqRange3);
+
+        mHostapdHal = new HostapdHalSpy();
+
+        executeAndValidateInitializationSequenceOnLastHIDL(false);
+
+        SparseIntArray dual_channels = new SparseIntArray(2);
+        dual_channels.put(SoftApConfiguration.BAND_5GHZ, 149);
+        dual_channels.put(SoftApConfiguration.BAND_2GHZ, 0);
+
+        Builder configurationBuilder = new SoftApConfiguration.Builder();
+        configurationBuilder.setSsid(NETWORK_SSID);
+        configurationBuilder.setHiddenSsid(false);
+        configurationBuilder.setPassphrase(NETWORK_PSK,
+                SoftApConfiguration.SECURITY_TYPE_WPA3_SAE);
+        configurationBuilder.setChannels(dual_channels);
+
+        when(mIHostapdMockV13.addAccessPoint_1_3(
+                mIfaceParamsCaptorV13.capture(), mNetworkParamsV13Captor.capture()))
+                .thenReturn(mStatusSuccess12);
+
+
+        assertTrue(mHostapdHal.addAccessPoint(IFACE_NAME,
+                configurationBuilder.build(), true,
+                () -> mSoftApListener.onFailure()));
+        verify(mIHostapdMockV13).addAccessPoint_1_3(any(), any());
+
+        assertEquals(IFACE_NAME, mIfaceParamsCaptorV13.getValue().V1_2.V1_1.V1_0.ifaceName);
+        assertTrue(mIfaceParamsCaptorV13.getValue().V1_2.V1_1.V1_0.hwModeParams.enable80211N);
+        assertFalse(mIfaceParamsCaptorV13.getValue().V1_2.V1_1.V1_0.hwModeParams.enable80211AC);
+        assertFalse(mIfaceParamsCaptorV13.getValue().V1_2.hwModeParams.enable80211AX);
+        // 2.4G band, ACS case.
+        assertTrue(mIfaceParamsCaptorV13.getValue().channelParamsList.get(0).enableAcs);
+        assertEquals(mIfaceParamsCaptorV13.getValue().channelParamsList.get(0).V1_2.bandMask,
+                android.hardware.wifi.hostapd.V1_2.IHostapd.BandMask.BAND_2_GHZ);
+        assertEquals(mIfaceParamsCaptorV13.getValue().channelParamsList.get(0).channel, 0);
+        assertEquals(mIfaceParamsCaptorV13.getValue().channelParamsList.get(0)
+                .V1_2.acsChannelFreqRangesMhz, acsFreqRanges);
+        // 5G band, specific channel.
+        assertFalse(mIfaceParamsCaptorV13.getValue().channelParamsList.get(1).enableAcs);
+        assertEquals(mIfaceParamsCaptorV13.getValue().channelParamsList.get(1).V1_2.bandMask,
+                android.hardware.wifi.hostapd.V1_2.IHostapd.BandMask.BAND_5_GHZ);
+        assertEquals(mIfaceParamsCaptorV13.getValue().channelParamsList.get(1).channel, 149);
+        // No acsChannelFreqRangesMh
+        assertEquals(mIfaceParamsCaptorV13.getValue().channelParamsList.get(1)
+                .V1_2.acsChannelFreqRangesMhz.size(), 0);
+
+        assertEquals(NativeUtil.stringToByteArrayList(NETWORK_SSID),
+                mNetworkParamsV13Captor.getValue().V1_2.V1_0.ssid);
+        assertFalse(mNetworkParamsV13Captor.getValue().V1_2.V1_0.isHidden);
+        assertEquals(android.hardware.wifi.hostapd.V1_2.IHostapd.EncryptionType.WPA3_SAE,
+                mNetworkParamsV13Captor.getValue().V1_2.encryptionType);
+        assertEquals(NETWORK_PSK, mNetworkParamsV13Captor.getValue().V1_2.passphrase);
+        assertEquals("", mNetworkParamsV13Captor.getValue().V1_2.V1_0.pskPassphrase);
+    }
+
+    /**
+     * Verifies the successful addition of access point with metered SAE indication on the 80211ax
+     * supported device.
+     */
+    @Test
+    public void testAddAccessPointSuccess_WithMeteredSAEOn11AXSupportedDevice()
+            throws Exception {
+        boolean isMetered = true;
+        mResources.setBoolean(R.bool.config_wifi_softap_acs_supported, true);
+        mResources.setBoolean(R.bool.config_wifiSoftapIeee80211axSupported, true);
+        when(mServiceManagerMock.getTransport(anyString(), anyString()))
+                .thenReturn(IServiceManager.Transport.HWBINDER);
+        mIHostapdMockV11 = mock(android.hardware.wifi.hostapd.V1_1.IHostapd.class);
+        mIHostapdMockV12 = mock(android.hardware.wifi.hostapd.V1_2.IHostapd.class);
+        mIHostapdMockV13 = mock(android.hardware.wifi.hostapd.V1_3.IHostapd.class);
+        mHostapdHal = new HostapdHalSpy();
+
+        executeAndValidateInitializationSequenceOnLastHIDL(false);
+
+        Builder configurationBuilder = new SoftApConfiguration.Builder();
+        configurationBuilder.setSsid(NETWORK_SSID);
+        configurationBuilder.setHiddenSsid(false);
+        configurationBuilder.setPassphrase(NETWORK_PSK,
+                SoftApConfiguration.SECURITY_TYPE_WPA3_SAE);
+        configurationBuilder.setBand(mBand256G);
+
+        when(mIHostapdMockV13.addAccessPoint_1_3(
+                mIfaceParamsCaptorV13.capture(), mNetworkParamsV13Captor.capture()))
+                .thenReturn(mStatusSuccess12);
+
+
+        assertTrue(mHostapdHal.addAccessPoint(IFACE_NAME,
+                configurationBuilder.build(), isMetered,
+                () -> mSoftApListener.onFailure()));
+        verify(mIHostapdMockV13).addAccessPoint_1_3(any(), any());
+
+        assertEquals(IFACE_NAME, mIfaceParamsCaptorV13.getValue().V1_2.V1_1.V1_0.ifaceName);
+        assertTrue(mIfaceParamsCaptorV13.getValue().V1_2.V1_1.V1_0.hwModeParams.enable80211N);
+        assertFalse(mIfaceParamsCaptorV13.getValue().V1_2.V1_1.V1_0.hwModeParams.enable80211AC);
+        assertTrue(mIfaceParamsCaptorV13.getValue().V1_2.hwModeParams.enable80211AX);
+        assertTrue(mIfaceParamsCaptorV13.getValue().V1_2.V1_1.V1_0.channelParams.enableAcs);
+
+        assertEquals(NativeUtil.stringToByteArrayList(NETWORK_SSID),
+                mNetworkParamsV13Captor.getValue().V1_2.V1_0.ssid);
+        assertFalse(mNetworkParamsV13Captor.getValue().V1_2.V1_0.isHidden);
+        assertEquals(android.hardware.wifi.hostapd.V1_2.IHostapd.EncryptionType.WPA3_SAE,
+                mNetworkParamsV13Captor.getValue().V1_2.encryptionType);
+        assertEquals(NETWORK_PSK, mNetworkParamsV13Captor.getValue().V1_2.passphrase);
+        assertEquals("", mNetworkParamsV13Captor.getValue().V1_2.V1_0.pskPassphrase);
+        assertTrue(mNetworkParamsV13Captor.getValue().isMetered);
+    }
+
+    /**
+     * Verifies the successful addition of access point with metered SAE indication on the 80211ax
+     * supported device but 80211ax is disabled in configuration.
+     */
+    @Test
+    public void testAddAccessPointSuccess_WithMeteredSAEOn11AXSupportedDeviceBut11AXDisabled()
+            throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS()); // setIeee80211axEnabled() added on Android S.
+        boolean isMetered = true;
+        mResources.setBoolean(R.bool.config_wifi_softap_acs_supported, true);
+        mResources.setBoolean(R.bool.config_wifiSoftapIeee80211axSupported, true);
+        when(mServiceManagerMock.getTransport(anyString(), anyString()))
+                .thenReturn(IServiceManager.Transport.HWBINDER);
+        mIHostapdMockV11 = mock(android.hardware.wifi.hostapd.V1_1.IHostapd.class);
+        mIHostapdMockV12 = mock(android.hardware.wifi.hostapd.V1_2.IHostapd.class);
+        mIHostapdMockV13 = mock(android.hardware.wifi.hostapd.V1_3.IHostapd.class);
+        mHostapdHal = new HostapdHalSpy();
+
+        executeAndValidateInitializationSequenceOnLastHIDL(false);
+
+        Builder configurationBuilder = new SoftApConfiguration.Builder();
+        configurationBuilder.setSsid(NETWORK_SSID);
+        configurationBuilder.setHiddenSsid(false);
+        configurationBuilder.setPassphrase(NETWORK_PSK,
+                SoftApConfiguration.SECURITY_TYPE_WPA3_SAE);
+        configurationBuilder.setBand(mBand256G);
+        configurationBuilder.setIeee80211axEnabled(false);
+
+        when(mIHostapdMockV13.addAccessPoint_1_3(
+                mIfaceParamsCaptorV13.capture(), mNetworkParamsV13Captor.capture()))
+                .thenReturn(mStatusSuccess12);
+
+
+        assertTrue(mHostapdHal.addAccessPoint(IFACE_NAME,
+                configurationBuilder.build(), isMetered,
+                () -> mSoftApListener.onFailure()));
+        verify(mIHostapdMockV13).addAccessPoint_1_3(any(), any());
+
+        assertEquals(IFACE_NAME, mIfaceParamsCaptorV13.getValue().V1_2.V1_1.V1_0.ifaceName);
+        assertTrue(mIfaceParamsCaptorV13.getValue().V1_2.V1_1.V1_0.hwModeParams.enable80211N);
+        assertFalse(mIfaceParamsCaptorV13.getValue().V1_2.V1_1.V1_0.hwModeParams.enable80211AC);
+        assertFalse(mIfaceParamsCaptorV13.getValue().V1_2.hwModeParams.enable80211AX);
+        assertTrue(mIfaceParamsCaptorV13.getValue().V1_2.V1_1.V1_0.channelParams.enableAcs);
+
+        assertEquals(NativeUtil.stringToByteArrayList(NETWORK_SSID),
+                mNetworkParamsV13Captor.getValue().V1_2.V1_0.ssid);
+        assertFalse(mNetworkParamsV13Captor.getValue().V1_2.V1_0.isHidden);
+        assertEquals(android.hardware.wifi.hostapd.V1_2.IHostapd.EncryptionType.WPA3_SAE,
+                mNetworkParamsV13Captor.getValue().V1_2.encryptionType);
+        assertEquals(NETWORK_PSK, mNetworkParamsV13Captor.getValue().V1_2.passphrase);
+        assertEquals("", mNetworkParamsV13Captor.getValue().V1_2.V1_0.pskPassphrase);
+        assertTrue(mNetworkParamsV13Captor.getValue().isMetered);
+    }
 }
 
diff --git a/service/tests/wifitests/src/com/android/server/wifi/ImsiPrivacyProtectionExemptionStoreDataTest.java b/service/tests/wifitests/src/com/android/server/wifi/ImsiPrivacyProtectionExemptionStoreDataTest.java
index 77d5c19..0e764d3 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/ImsiPrivacyProtectionExemptionStoreDataTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/ImsiPrivacyProtectionExemptionStoreDataTest.java
@@ -13,11 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package com.android.server.wifi;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -38,20 +36,16 @@
 import java.nio.charset.StandardCharsets;
 import java.util.HashMap;
 import java.util.Map;
-
 public class ImsiPrivacyProtectionExemptionStoreDataTest {
     private static final int TEST_CARRIER_ID = 1911;
-
     private @Mock ImsiPrivacyProtectionExemptionStoreData.DataSource mDataSource;
     private ImsiPrivacyProtectionExemptionStoreData mImsiPrivacyProtectionExemptionStoreData;
-
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mImsiPrivacyProtectionExemptionStoreData =
                 new ImsiPrivacyProtectionExemptionStoreData(mDataSource);
     }
-
     /**
      * Helper function for serializing configuration data to a XML block.
      */
@@ -63,7 +57,6 @@
         out.flush();
         return outputStream.toByteArray();
     }
-
     /**
      * Helper function for parsing configuration data from a XML block.
      */
@@ -74,7 +67,6 @@
         mImsiPrivacyProtectionExemptionStoreData.deserializeData(in, in.getDepth(),
                 WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION, null);
     }
-
     /**
      * Verify store file Id.
      */
@@ -83,7 +75,6 @@
         assertEquals(WifiConfigStore.STORE_FILE_USER_GENERAL,
                 mImsiPrivacyProtectionExemptionStoreData.getStoreFileId());
     }
-
     /**
      * Verify serialize and deserialize Protection exemption map.
      */
@@ -93,7 +84,6 @@
         imsiPrivacyProtectionExemptionMap.put(TEST_CARRIER_ID, true);
         assertSerializeDeserialize(imsiPrivacyProtectionExemptionMap);
     }
-
     /**
      * Verify serialize and deserialize empty protection exemption map.
      */
@@ -102,26 +92,12 @@
         Map<Integer, Boolean> imsiPrivacyProtectionExemptionMap = new HashMap<>();
         assertSerializeDeserialize(imsiPrivacyProtectionExemptionMap);
     }
-
-    @Test
-    public void testDeserializeOnNewDeviceOrNewUser() throws Exception {
-        ArgumentCaptor<Map> deserializedNetworkSuggestionsMap =
-                ArgumentCaptor.forClass(Map.class);
-        mImsiPrivacyProtectionExemptionStoreData.deserializeData(null, 0,
-                WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION, null);
-        verify(mDataSource).fromDeserialized(deserializedNetworkSuggestionsMap.capture());
-        assertTrue(deserializedNetworkSuggestionsMap.getValue().isEmpty());
-    }
-
-
     private Map<Integer, Boolean> assertSerializeDeserialize(
             Map<Integer, Boolean> mImsiPrivacyProtectionExemptionMap) throws Exception {
         // Setup the data to serialize.
         when(mDataSource.toSerialize()).thenReturn(mImsiPrivacyProtectionExemptionMap);
-
         // Serialize/deserialize data.
         deserializeData(serializeData());
-
         // Verify the deserialized data.
         ArgumentCaptor<HashMap> deserializedNetworkSuggestionsMap =
                 ArgumentCaptor.forClass(HashMap.class);
diff --git a/service/tests/wifitests/src/com/android/server/wifi/LastMileLoggerTest.java b/service/tests/wifitests/src/com/android/server/wifi/LastMileLoggerTest.java
index 6d76f4a..e165dea 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/LastMileLoggerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/LastMileLoggerTest.java
@@ -28,8 +28,6 @@
 
 import com.android.server.wifi.util.FileUtils;
 
-import libcore.io.IoUtils;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
@@ -37,14 +35,21 @@
 import org.mockito.Spy;
 
 import java.io.File;
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
 
 /**
  * Unit tests for {@link LastMileLogger}.
  */
 @SmallTest
 public class LastMileLoggerTest extends WifiBaseTest {
+
+    private static final String WLAN0 = "wlan0";
+    private static final String WLAN1 = "wlan1";
+
     @Mock WifiInjector mWifiInjector;
     @Spy FakeWifiLog mLog;
 
@@ -63,6 +68,10 @@
                 mTraceEnableFile.getPath(),  mTraceReleaseFile.getPath());
     }
 
+    private static String readFileAsString(File file) throws IOException {
+        return new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
+    }
+
     @Test
     public void ctorDoesNotCrash() throws Exception {
         new LastMileLogger(mWifiInjector, mTraceDataFile.getPath(), mTraceEnableFile.getPath(),
@@ -72,14 +81,14 @@
 
     @Test
     public void connectionEventStartedEnablesTracing() throws Exception {
-        mLastMileLogger.reportConnectionEvent(BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
-        assertEquals("1", IoUtils.readFileAsString(mTraceEnableFile.getPath()));
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_STARTED);
+        assertEquals("1", readFileAsString(mTraceEnableFile));
     }
 
     @Test
     public void connectionEventStartedDoesNotCrashIfReleaseFileIsMissing() throws Exception {
         mTraceReleaseFile.delete();
-        mLastMileLogger.reportConnectionEvent(BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_STARTED);
         verify(mLog).warn(contains("Failed to open free_buffer"));
     }
 
@@ -87,13 +96,13 @@
     public void connectionEventStartedDoesNotEnableTracingIfReleaseFileIsMissing()
             throws Exception {
         mTraceReleaseFile.delete();
-        mLastMileLogger.reportConnectionEvent(BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
-        assertEquals("0", IoUtils.readFileAsString(mTraceEnableFile.getPath()));
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_STARTED);
+        assertEquals("0", readFileAsString(mTraceEnableFile));
     }
 
     @Test
     public void connectionEventStartedDoesNotAttemptToReopenReleaseFile() throws Exception {
-        mLastMileLogger.reportConnectionEvent(BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_STARTED);
 
         // This is a rather round-about way of verifying that we don't attempt to re-open
         // the file. Namely: if we delete the |release| file, and CONNECTION_EVENT_STARTED
@@ -104,78 +113,96 @@
         // A more direct test would require the use of a factory for the creation of the
         // FileInputStream.
         mTraceReleaseFile.delete();
-        mLastMileLogger.reportConnectionEvent(BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_STARTED);
         verifyZeroInteractions(mLog);
     }
 
     @Test
     public void connectionEventStartedDoesNotCrashIfEnableFileIsMissing() throws Exception {
         mTraceEnableFile.delete();
-        mLastMileLogger.reportConnectionEvent(BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_STARTED);
     }
 
     @Test
     public void connectionEventStartedDoesNotCrashOnRepeatedCalls() throws Exception {
-        mLastMileLogger.reportConnectionEvent(BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
-        mLastMileLogger.reportConnectionEvent(BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_STARTED);
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_STARTED);
     }
 
     @Test
     public void connectionEventSucceededDisablesTracing() throws Exception {
-        mLastMileLogger.reportConnectionEvent(BaseWifiDiagnostics.CONNECTION_EVENT_SUCCEEDED);
-        assertEquals("0", IoUtils.readFileAsString(mTraceEnableFile.getPath()));
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_SUCCEEDED);
+        assertEquals("0", readFileAsString(mTraceEnableFile));
     }
 
     @Test
     public void connectionEventSucceededDoesNotCrashIfEnableFileIsMissing() throws Exception {
         mTraceEnableFile.delete();
-        mLastMileLogger.reportConnectionEvent(BaseWifiDiagnostics.CONNECTION_EVENT_SUCCEEDED);
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_SUCCEEDED);
     }
 
     @Test
     public void connectionEventSucceededDoesNotCrashOnRepeatedCalls() throws Exception {
-        mLastMileLogger.reportConnectionEvent(BaseWifiDiagnostics.CONNECTION_EVENT_SUCCEEDED);
-        mLastMileLogger.reportConnectionEvent(BaseWifiDiagnostics.CONNECTION_EVENT_SUCCEEDED);
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_SUCCEEDED);
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_SUCCEEDED);
     }
 
     @Test
     public void connectionEventFailedDisablesTracingWhenPendingFails() throws Exception {
-        mLastMileLogger.reportConnectionEvent(BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
-        mLastMileLogger.reportConnectionEvent(BaseWifiDiagnostics.CONNECTION_EVENT_FAILED);
-        assertEquals("0", IoUtils.readFileAsString(mTraceEnableFile.getPath()));
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_STARTED);
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_FAILED);
+        assertEquals("0", readFileAsString(mTraceEnableFile));
     }
 
     @Test
-    public void connectionEventTimeoutDisablesTracing()
-            throws Exception {
-        mLastMileLogger.reportConnectionEvent(BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
-        mLastMileLogger.reportConnectionEvent(BaseWifiDiagnostics.CONNECTION_EVENT_TIMEOUT);
-        assertEquals("0", IoUtils.readFileAsString(mTraceEnableFile.getPath()));
+    public void connectionEventTimeoutDisablesTracing() throws Exception {
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_STARTED);
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_TIMEOUT);
+        assertEquals("0", readFileAsString(mTraceEnableFile));
+    }
+
+    @Test
+    public void multipleIfaces() throws Exception {
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_STARTED);
+        assertEquals("1", readFileAsString(mTraceEnableFile));
+
+        mLastMileLogger.reportConnectionEvent(WLAN1, WifiDiagnostics.CONNECTION_EVENT_STARTED);
+        assertEquals("1", readFileAsString(mTraceEnableFile));
+
+        FileUtils.stringToFile(mTraceDataFile.getPath(), "rdev_connect");
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_TIMEOUT);
+        assertEquals("1", readFileAsString(mTraceEnableFile));
+        String dumpString = getDumpString();
+        assertTrue(dumpString.contains("--- Last failed"));
+        assertTrue(dumpString.contains("rdev_connect"));
+
+        mLastMileLogger.reportConnectionEvent(WLAN1, WifiDiagnostics.CONNECTION_EVENT_SUCCEEDED);
+        assertEquals("0", readFileAsString(mTraceEnableFile));
     }
 
     @Test
     public void connectionEventFailedDoesNotCrashIfEnableFileIsMissing() throws Exception {
         mTraceEnableFile.delete();
-        mLastMileLogger.reportConnectionEvent(BaseWifiDiagnostics.CONNECTION_EVENT_FAILED);
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_FAILED);
     }
 
     @Test
     public void connectionEventFailedDoesNotCrashIfDataFileIsMissing() throws Exception {
         mTraceDataFile.delete();
-        mLastMileLogger.reportConnectionEvent(BaseWifiDiagnostics.CONNECTION_EVENT_FAILED);
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_FAILED);
     }
 
     @Test
     public void connectionEventFailedDoesNotCrashOnRepeatedCalls() throws Exception {
-        mLastMileLogger.reportConnectionEvent(BaseWifiDiagnostics.CONNECTION_EVENT_FAILED);
-        mLastMileLogger.reportConnectionEvent(BaseWifiDiagnostics.CONNECTION_EVENT_FAILED);
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_FAILED);
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_FAILED);
     }
 
     @Test
     public void dumpShowsFailureTrace() throws Exception {
-        mLastMileLogger.reportConnectionEvent(BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_STARTED);
         FileUtils.stringToFile(mTraceDataFile.getPath(), "rdev_connect");
-        mLastMileLogger.reportConnectionEvent(BaseWifiDiagnostics.CONNECTION_EVENT_FAILED);
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_FAILED);
         assertTrue(getDumpString().contains("--- Last failed"));
         assertTrue(getDumpString().contains("rdev_connect"));
     }
@@ -183,7 +210,7 @@
 
     @Test
     public void dumpShowsPendingConnectionTrace() throws Exception {
-        mLastMileLogger.reportConnectionEvent(BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_STARTED);
         FileUtils.stringToFile(mTraceDataFile.getPath(), "rdev_connect");
         assertTrue(getDumpString().contains("No last mile log for \"Last failed"));
         assertTrue(getDumpString().contains("--- Latest"));
@@ -192,10 +219,10 @@
 
     @Test
     public void dumpShowsLastFailureTraceAndPendingConnectionTrace() throws Exception {
-        mLastMileLogger.reportConnectionEvent(BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_STARTED);
         FileUtils.stringToFile(mTraceDataFile.getPath(), "rdev_connect try #1");
-        mLastMileLogger.reportConnectionEvent(BaseWifiDiagnostics.CONNECTION_EVENT_FAILED);
-        mLastMileLogger.reportConnectionEvent(BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_FAILED);
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_STARTED);
         FileUtils.stringToFile(mTraceDataFile.getPath(), "rdev_connect try #2");
 
         String dumpString = getDumpString();
@@ -205,12 +232,12 @@
 
     @Test
     public void dumpShowsLastFailureTraceAndCurrentConnectionTrace() throws Exception {
-        mLastMileLogger.reportConnectionEvent(BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_STARTED);
         FileUtils.stringToFile(mTraceDataFile.getPath(), "rdev_connect try #1");
-        mLastMileLogger.reportConnectionEvent(BaseWifiDiagnostics.CONNECTION_EVENT_FAILED);
-        mLastMileLogger.reportConnectionEvent(BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_FAILED);
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_STARTED);
         FileUtils.stringToFile(mTraceDataFile.getPath(), "rdev_connect try #2");
-        mLastMileLogger.reportConnectionEvent(BaseWifiDiagnostics.CONNECTION_EVENT_SUCCEEDED);
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_SUCCEEDED);
 
         String dumpString = getDumpString();
         assertTrue(dumpString.contains("rdev_connect try #1"));
@@ -219,9 +246,9 @@
 
     @Test
     public void dumpDoesNotClearLastFailureData() throws Exception {
-        mLastMileLogger.reportConnectionEvent(BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_STARTED);
         FileUtils.stringToFile(mTraceDataFile.getPath(), "rdev_connect");
-        mLastMileLogger.reportConnectionEvent(BaseWifiDiagnostics.CONNECTION_EVENT_FAILED);
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_FAILED);
 
         getDumpString();
         String dumpString = getDumpString();
@@ -230,7 +257,7 @@
 
     @Test
     public void dumpDoesNotClearPendingConnectionTrace() throws Exception {
-        mLastMileLogger.reportConnectionEvent(BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+        mLastMileLogger.reportConnectionEvent(WLAN0, WifiDiagnostics.CONNECTION_EVENT_STARTED);
         FileUtils.stringToFile(mTraceDataFile.getPath(), "rdev_connect");
 
         getDumpString();
diff --git a/service/tests/wifitests/src/com/android/server/wifi/LinkProbeManagerTest.java b/service/tests/wifitests/src/com/android/server/wifi/LinkProbeManagerTest.java
index 746976a..8b901d9 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/LinkProbeManagerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/LinkProbeManagerTest.java
@@ -135,7 +135,8 @@
         assertEquals(numExperimentIds, new HashSet<>(experimentIdCaptor.getAllValues()).size());
 
         callbackCaptor.getValue().onAck(TEST_ELAPSED_TIME_MS);
-        verify(mWifiMetrics).logLinkProbeSuccess(timeDelta, rssi, linkSpeed, TEST_ELAPSED_TIME_MS);
+        verify(mWifiMetrics).logLinkProbeSuccess(
+                TEST_IFACE_NAME, timeDelta, rssi, linkSpeed, TEST_ELAPSED_TIME_MS);
     }
 
     /**
@@ -171,7 +172,7 @@
                 anyInt());
 
         callbackCaptor.getValue().onFailure(WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_NO_ACK);
-        verify(mWifiMetrics).logLinkProbeFailure(timeDelta, rssi, linkSpeed,
+        verify(mWifiMetrics).logLinkProbeFailure(TEST_IFACE_NAME, timeDelta, rssi, linkSpeed,
                 WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_NO_ACK);
     }
 
diff --git a/service/tests/wifitests/src/com/android/server/wifi/LocalOnlyHotspotRequestInfoTest.java b/service/tests/wifitests/src/com/android/server/wifi/LocalOnlyHotspotRequestInfoTest.java
index 0d29c31..95f24c4 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/LocalOnlyHotspotRequestInfoTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/LocalOnlyHotspotRequestInfoTest.java
@@ -25,6 +25,7 @@
 import android.os.IBinder;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.WorkSource;
 
 import androidx.test.filters.SmallTest;
 
@@ -41,6 +42,8 @@
 public class LocalOnlyHotspotRequestInfoTest extends WifiBaseTest {
 
     private static final String TAG = "LocalOnlyHotspotRequestInfoTest";
+    private static final WorkSource TEST_WORKSOURCE = new WorkSource();
+
     @Mock IBinder mAppBinder;
     @Mock ILocalOnlyHotspotCallback mCallback;
     @Mock LocalOnlyHotspotRequestInfo.RequestingApplicationDeathCallback mDeathCallback;
@@ -63,7 +66,8 @@
      */
     @Test
     public void verifyBinderLinkToDeathIsCalled() throws Exception {
-        mLOHSRequestInfo = new LocalOnlyHotspotRequestInfo(mCallback, mDeathCallback, null);
+        mLOHSRequestInfo = new LocalOnlyHotspotRequestInfo(
+                TEST_WORKSOURCE, mCallback, mDeathCallback, null);
         verify(mAppBinder).linkToDeath(eq(mLOHSRequestInfo), eq(0));
     }
 
@@ -72,7 +76,8 @@
      */
     @Test(expected = NullPointerException.class)
     public void verifyNullCallbackChecked() throws Exception {
-        mLOHSRequestInfo = new LocalOnlyHotspotRequestInfo(null, mDeathCallback, null);
+        mLOHSRequestInfo = new LocalOnlyHotspotRequestInfo(
+                TEST_WORKSOURCE, null, mDeathCallback, null);
     }
 
     /**
@@ -80,7 +85,8 @@
      */
     @Test(expected = NullPointerException.class)
     public void verifyNullDeathCallbackChecked() throws Exception {
-        mLOHSRequestInfo = new LocalOnlyHotspotRequestInfo(mCallback, null, null);
+        mLOHSRequestInfo = new LocalOnlyHotspotRequestInfo(
+                TEST_WORKSOURCE, mCallback, null, null);
     }
 
     /**
@@ -88,7 +94,8 @@
      */
     @Test
     public void verifyUnlinkDeathRecipientUnlinksFromBinder() throws Exception {
-        mLOHSRequestInfo = new LocalOnlyHotspotRequestInfo(mCallback, mDeathCallback, null);
+        mLOHSRequestInfo = new LocalOnlyHotspotRequestInfo(
+                TEST_WORKSOURCE, mCallback, mDeathCallback, null);
         mLOHSRequestInfo.unlinkDeathRecipient();
         verify(mAppBinder).unlinkToDeath(eq(mLOHSRequestInfo), eq(0));
     }
@@ -98,7 +105,8 @@
      */
     @Test
     public void verifyBinderDeathTriggersCallback() {
-        mLOHSRequestInfo = new LocalOnlyHotspotRequestInfo(mCallback, mDeathCallback, null);
+        mLOHSRequestInfo = new LocalOnlyHotspotRequestInfo(
+                TEST_WORKSOURCE, mCallback, mDeathCallback, null);
         mLOHSRequestInfo.binderDied();
         verify(mDeathCallback).onLocalOnlyHotspotRequestorDeath(eq(mLOHSRequestInfo));
     }
@@ -110,7 +118,8 @@
     public void verifyRemoteExceptionTriggersCallback() throws Exception {
         doThrow(mRemoteException).when(mAppBinder)
                 .linkToDeath(any(IBinder.DeathRecipient.class), eq(0));
-        mLOHSRequestInfo = new LocalOnlyHotspotRequestInfo(mCallback, mDeathCallback, null);
+        mLOHSRequestInfo = new LocalOnlyHotspotRequestInfo(
+                TEST_WORKSOURCE, mCallback, mDeathCallback, null);
         verify(mDeathCallback).onLocalOnlyHotspotRequestorDeath(eq(mLOHSRequestInfo));
     }
 
@@ -119,7 +128,8 @@
      */
     @Test
     public void verifyPid() {
-        mLOHSRequestInfo = new LocalOnlyHotspotRequestInfo(mCallback, mDeathCallback, null);
+        mLOHSRequestInfo = new LocalOnlyHotspotRequestInfo(
+                TEST_WORKSOURCE, mCallback, mDeathCallback, null);
         assertEquals(Process.myPid(), mLOHSRequestInfo.getPid());
     }
 
@@ -128,7 +138,8 @@
         SoftApConfiguration config = new SoftApConfiguration.Builder()
                 .setSsid("customSsid")
                 .build();
-        mLOHSRequestInfo = new LocalOnlyHotspotRequestInfo(mCallback, mDeathCallback, config);
+        mLOHSRequestInfo = new LocalOnlyHotspotRequestInfo(
+                TEST_WORKSOURCE, mCallback, mDeathCallback, config);
         assertEquals(config, mLOHSRequestInfo.getCustomConfig());
     }
 
@@ -137,7 +148,8 @@
      */
     @Test
     public void verifySendFailedMessage() throws Exception {
-        mLOHSRequestInfo = new LocalOnlyHotspotRequestInfo(mCallback, mDeathCallback, null);
+        mLOHSRequestInfo = new LocalOnlyHotspotRequestInfo(
+                TEST_WORKSOURCE, mCallback, mDeathCallback, null);
         mLOHSRequestInfo.sendHotspotFailedMessage(
                 WifiManager.LocalOnlyHotspotCallback.ERROR_GENERIC);
         verify(mCallback).onHotspotFailed(WifiManager.LocalOnlyHotspotCallback.ERROR_GENERIC);
@@ -148,7 +160,8 @@
      */
     @Test
     public void verifySendStartedMessage() throws Exception {
-        mLOHSRequestInfo = new LocalOnlyHotspotRequestInfo(mCallback, mDeathCallback, null);
+        mLOHSRequestInfo = new LocalOnlyHotspotRequestInfo(
+                TEST_WORKSOURCE, mCallback, mDeathCallback, null);
         SoftApConfiguration config = mock(SoftApConfiguration.class);
         mLOHSRequestInfo.sendHotspotStartedMessage(config);
         ArgumentCaptor<SoftApConfiguration> mSoftApConfigCaptor =
@@ -163,7 +176,8 @@
      */
     @Test
     public void verifySendStoppedMessage() throws Exception {
-        mLOHSRequestInfo = new LocalOnlyHotspotRequestInfo(mCallback, mDeathCallback, null);
+        mLOHSRequestInfo = new LocalOnlyHotspotRequestInfo(
+                TEST_WORKSOURCE, mCallback, mDeathCallback, null);
         mLOHSRequestInfo.sendHotspotStoppedMessage();
         verify(mCallback).onHotspotStopped();
     }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/MakeBeforeBreakManagerTest.java b/service/tests/wifitests/src/com/android/server/wifi/MakeBeforeBreakManagerTest.java
new file mode 100644
index 0000000..b384174
--- /dev/null
+++ b/service/tests/wifitests/src/com/android/server/wifi/MakeBeforeBreakManagerTest.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_PRIMARY;
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SECONDARY_LONG_LIVED;
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SECONDARY_TRANSIENT;
+
+import static org.mockito.Mockito.*;
+
+import android.content.Context;
+import android.os.WorkSource;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.wifi.ActiveModeWarden.ModeChangeCallback;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+/** Unit tests for {@link MakeBeforeBreakManager}. */
+@SmallTest
+public class MakeBeforeBreakManagerTest extends WifiBaseTest {
+
+    @Mock private ActiveModeWarden mActiveModeWarden;
+    @Mock private FrameworkFacade mFrameworkFacade;
+    @Mock private Context mContext;
+    @Mock private ConcreteClientModeManager mOldPrimaryCmm;
+    @Mock private ConcreteClientModeManager mNewPrimaryCmm;
+    @Mock private ConcreteClientModeManager mUnrelatedCmm;
+    @Mock private WorkSource mSettingsWorkSource;
+    @Mock private ClientModeImplMonitor mCmiMonitor;
+    @Mock private ClientModeManagerBroadcastQueue mBroadcastQueue;
+    @Mock private WifiMetrics mWifiMetrics;
+    @Mock private Runnable mOnStoppedListener;
+    @Captor private ArgumentCaptor<ModeChangeCallback> mModeChangeCallbackCaptor;
+    @Captor private ArgumentCaptor<ClientModeImplListener> mCmiListenerCaptor;
+
+    private MakeBeforeBreakManager mMbbManager;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        when(mActiveModeWarden.isStaStaConcurrencySupportedForMbb()).thenReturn(true);
+        when(mFrameworkFacade.getSettingsWorkSource(mContext)).thenReturn(mSettingsWorkSource);
+
+        when(mOldPrimaryCmm.getRole()).thenReturn(ROLE_CLIENT_PRIMARY);
+        when(mNewPrimaryCmm.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_TRANSIENT);
+
+        when(mActiveModeWarden.getPrimaryClientModeManagerNullable()).thenReturn(mOldPrimaryCmm);
+        when(mActiveModeWarden.getClientModeManagersInRoles(ROLE_CLIENT_SECONDARY_TRANSIENT))
+                .thenReturn(List.of(mNewPrimaryCmm));
+
+        mMbbManager = new MakeBeforeBreakManager(mActiveModeWarden, mFrameworkFacade, mContext,
+                mCmiMonitor, mBroadcastQueue, mWifiMetrics);
+
+        verify(mActiveModeWarden).registerModeChangeCallback(mModeChangeCallbackCaptor.capture());
+        verify(mCmiMonitor).registerListener(mCmiListenerCaptor.capture());
+    }
+
+    @Test
+    public void makeBeforeBreakDisabled_noOp() {
+        when(mActiveModeWarden.isStaStaConcurrencySupportedForMbb()).thenReturn(false);
+
+        mCmiListenerCaptor.getValue().onInternetValidated(mNewPrimaryCmm);
+        mModeChangeCallbackCaptor.getValue().onActiveModeManagerRemoved(mNewPrimaryCmm);
+        mModeChangeCallbackCaptor.getValue().onActiveModeManagerRoleChanged(mNewPrimaryCmm);
+        mModeChangeCallbackCaptor.getValue().onActiveModeManagerAdded(mNewPrimaryCmm);
+
+        verify(mNewPrimaryCmm, never()).setRole(any(), any());
+    }
+
+    @Test
+    public void onL3ValidatedNonSecondaryTransient_noOp() {
+        when(mNewPrimaryCmm.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_LONG_LIVED);
+        mCmiListenerCaptor.getValue().onInternetValidated(mNewPrimaryCmm);
+
+        verify(mNewPrimaryCmm, never()).setRole(any(), any());
+    }
+
+    @Test
+    public void onL3Validated_noPrimary_immediatelyMakeValidatedNetworkPrimary() {
+        when(mActiveModeWarden.getPrimaryClientModeManagerNullable()).thenReturn(null);
+        mCmiListenerCaptor.getValue().onInternetValidated(mNewPrimaryCmm);
+
+        verify(mNewPrimaryCmm).setRole(ROLE_CLIENT_PRIMARY, mSettingsWorkSource);
+    }
+
+    @Test
+    public void makeBeforeBreakSuccess() {
+        mCmiListenerCaptor.getValue().onInternetValidated(mNewPrimaryCmm);
+
+        verify(mOldPrimaryCmm).setRole(ROLE_CLIENT_SECONDARY_TRANSIENT,
+                ActiveModeWarden.INTERNAL_REQUESTOR_WS);
+        verify(mBroadcastQueue).fakeDisconnectionBroadcasts();
+
+        when(mOldPrimaryCmm.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_TRANSIENT);
+        when(mActiveModeWarden.getClientModeManagersInRoles(ROLE_CLIENT_SECONDARY_TRANSIENT))
+                .thenReturn(List.of(mNewPrimaryCmm, mOldPrimaryCmm));
+        mModeChangeCallbackCaptor.getValue().onActiveModeManagerRoleChanged(mOldPrimaryCmm);
+
+        verify(mNewPrimaryCmm).setRole(ROLE_CLIENT_PRIMARY, mSettingsWorkSource);
+        verify(mOldPrimaryCmm).setShouldReduceNetworkScore(true);
+    }
+
+    @Test
+    public void makeBeforeBreakEnded_mMakeBeforeBreakInfoCleared() {
+        makeBeforeBreakSuccess();
+        // only called once
+        verify(mOldPrimaryCmm).setRole(any(), any());
+        verify(mNewPrimaryCmm).setRole(any(), any());
+
+        when(mActiveModeWarden.getPrimaryClientModeManagerNullable()).thenReturn(mNewPrimaryCmm);
+        when(mNewPrimaryCmm.getRole()).thenReturn(ROLE_CLIENT_PRIMARY);
+        when(mOldPrimaryCmm.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_TRANSIENT);
+        when(mActiveModeWarden.getClientModeManagersInRoles(ROLE_CLIENT_SECONDARY_TRANSIENT))
+                .thenReturn(List.of(mOldPrimaryCmm));
+        mModeChangeCallbackCaptor.getValue().onActiveModeManagerRoleChanged(mOldPrimaryCmm);
+
+        // still only called once
+        verify(mOldPrimaryCmm).setRole(any(), any());
+        verify(mNewPrimaryCmm).setRole(any(), any());
+    }
+
+    @Test
+    public void modeChanged_anotherCmm_noOp() {
+        mCmiListenerCaptor.getValue().onInternetValidated(mNewPrimaryCmm);
+
+        verify(mOldPrimaryCmm).setRole(ROLE_CLIENT_SECONDARY_TRANSIENT,
+                ActiveModeWarden.INTERNAL_REQUESTOR_WS);
+
+        when(mOldPrimaryCmm.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_TRANSIENT);
+        when(mActiveModeWarden.getClientModeManagersInRoles(ROLE_CLIENT_SECONDARY_TRANSIENT))
+                .thenReturn(List.of(mNewPrimaryCmm, mOldPrimaryCmm));
+        mModeChangeCallbackCaptor.getValue().onActiveModeManagerRoleChanged(mUnrelatedCmm);
+
+        verify(mUnrelatedCmm, never()).setRole(any(), any());
+        verify(mOldPrimaryCmm).setRole(any(), any());
+        verify(mNewPrimaryCmm, never()).setRole(any(), any());
+    }
+
+    @Test
+    public void modeChanged_noMakeBeforeBreak_noOp() {
+        when(mOldPrimaryCmm.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_TRANSIENT);
+        when(mActiveModeWarden.getClientModeManagersInRoles(ROLE_CLIENT_SECONDARY_TRANSIENT))
+                .thenReturn(List.of(mNewPrimaryCmm, mOldPrimaryCmm));
+        mModeChangeCallbackCaptor.getValue().onActiveModeManagerRoleChanged(mOldPrimaryCmm);
+
+        verify(mOldPrimaryCmm, never()).setRole(any(), any());
+        verify(mNewPrimaryCmm, never()).setRole(any(), any());
+    }
+
+    @Test
+    public void modeChanged_oldPrimaryDidntBecomeSecondaryTransient_abortMbb() {
+        mCmiListenerCaptor.getValue().onInternetValidated(mNewPrimaryCmm);
+
+        verify(mOldPrimaryCmm).setRole(ROLE_CLIENT_SECONDARY_TRANSIENT,
+                ActiveModeWarden.INTERNAL_REQUESTOR_WS);
+
+        // didn't become SECONDARY_TRANSIENT
+        when(mOldPrimaryCmm.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_LONG_LIVED);
+        when(mActiveModeWarden.getClientModeManagersInRoles(ROLE_CLIENT_SECONDARY_TRANSIENT))
+                .thenReturn(List.of(mNewPrimaryCmm));
+        mModeChangeCallbackCaptor.getValue().onActiveModeManagerRoleChanged(mOldPrimaryCmm);
+
+        // no-op, abort MBB
+        verify(mOldPrimaryCmm).setRole(any(), any());
+        verify(mNewPrimaryCmm, never()).setRole(any(), any());
+
+        // became SECONDARY_TRANSIENT
+        when(mOldPrimaryCmm.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_TRANSIENT);
+        when(mActiveModeWarden.getClientModeManagersInRoles(ROLE_CLIENT_SECONDARY_TRANSIENT))
+                .thenReturn(List.of(mNewPrimaryCmm, mOldPrimaryCmm));
+        mModeChangeCallbackCaptor.getValue().onActiveModeManagerRoleChanged(mOldPrimaryCmm);
+
+        // but since aborted, still no-op
+        verify(mOldPrimaryCmm).setRole(any(), any());
+        verify(mNewPrimaryCmm, never()).setRole(any(), any());
+    }
+
+    @Test
+    public void modeChanged_newPrimaryNoLongerSecondaryTransient_abortMbb() {
+        mCmiListenerCaptor.getValue().onInternetValidated(mNewPrimaryCmm);
+
+        verify(mOldPrimaryCmm).setRole(ROLE_CLIENT_SECONDARY_TRANSIENT,
+                ActiveModeWarden.INTERNAL_REQUESTOR_WS);
+
+        when(mOldPrimaryCmm.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_TRANSIENT);
+        // new primary's role became something else
+        when(mNewPrimaryCmm.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_LONG_LIVED);
+        when(mActiveModeWarden.getClientModeManagersInRoles(ROLE_CLIENT_SECONDARY_TRANSIENT))
+                .thenReturn(List.of(mOldPrimaryCmm));
+        mModeChangeCallbackCaptor.getValue().onActiveModeManagerRoleChanged(mOldPrimaryCmm);
+
+        // no-op, abort MBB
+        verify(mOldPrimaryCmm).setRole(any(), any());
+        verify(mNewPrimaryCmm, never()).setRole(any(), any());
+
+        // both became SECONDARY_TRANSIENT
+        when(mOldPrimaryCmm.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_TRANSIENT);
+        when(mNewPrimaryCmm.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_TRANSIENT);
+        when(mActiveModeWarden.getClientModeManagersInRoles(ROLE_CLIENT_SECONDARY_TRANSIENT))
+                .thenReturn(List.of(mNewPrimaryCmm, mOldPrimaryCmm));
+        mModeChangeCallbackCaptor.getValue().onActiveModeManagerRoleChanged(mOldPrimaryCmm);
+
+        // but since aborted, still no-op
+        verify(mOldPrimaryCmm).setRole(any(), any());
+        verify(mNewPrimaryCmm, never()).setRole(any(), any());
+    }
+
+    @Test
+    public void recovery() {
+        when(mOldPrimaryCmm.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_TRANSIENT);
+        when(mActiveModeWarden.getClientModeManagersInRoles(ROLE_CLIENT_SECONDARY_TRANSIENT))
+                .thenReturn(List.of(mNewPrimaryCmm, mOldPrimaryCmm));
+        when(mActiveModeWarden.getPrimaryClientModeManagerNullable()).thenReturn(null);
+        mModeChangeCallbackCaptor.getValue().onActiveModeManagerRoleChanged(mOldPrimaryCmm);
+
+        verify(mNewPrimaryCmm).setRole(ROLE_CLIENT_PRIMARY, mSettingsWorkSource);
+        verify(mOldPrimaryCmm).stop();
+    }
+
+    @Test
+    public void captivePortalDetected_disconnectOldPrimary_makeCaptivePortalPrimary() {
+        // captive portal network detected on new primary CMM
+        mCmiListenerCaptor.getValue().onCaptivePortalDetected(mNewPrimaryCmm);
+
+        // we should disable wifi state change broadcast before stopping it.
+        verify(mOldPrimaryCmm).setWifiStateChangeBroadcastEnabled(false);
+
+        // we should stop the old primary
+        verify(mOldPrimaryCmm).stop();
+
+        // old primary destroyed
+        when(mOldPrimaryCmm.getRole()).thenReturn(null);
+        // currently no primary
+        when(mActiveModeWarden.getPrimaryClientModeManagerNullable()).thenReturn(null);
+
+        // trigger old primary removed callback
+        mModeChangeCallbackCaptor.getValue().onActiveModeManagerRemoved(mOldPrimaryCmm);
+
+        // ensure we make the captive portal network primary
+        verify(mNewPrimaryCmm).setRole(ROLE_CLIENT_PRIMARY, mSettingsWorkSource);
+    }
+
+    @Test
+    public void stopAllSecondaryTransientCmms_noSecondaryTransientCmm_triggerImmediately() {
+        when(mActiveModeWarden.getClientModeManagerInRole(ROLE_CLIENT_SECONDARY_TRANSIENT))
+                .thenReturn(null);
+
+        mMbbManager.stopAllSecondaryTransientClientModeManagers(mOnStoppedListener);
+
+        verify(mOnStoppedListener).run();
+    }
+
+    @Test
+    public void stopAllSecondaryTransientCmms_hasSecondaryTransientCmm_triggerAfterStopped() {
+        when(mActiveModeWarden.getClientModeManagerInRole(ROLE_CLIENT_SECONDARY_TRANSIENT))
+                .thenReturn(mNewPrimaryCmm);
+
+        mMbbManager.stopAllSecondaryTransientClientModeManagers(mOnStoppedListener);
+
+        verify(mOnStoppedListener, never()).run();
+
+        when(mActiveModeWarden.getClientModeManagerInRole(ROLE_CLIENT_SECONDARY_TRANSIENT))
+                .thenReturn(null);
+
+        mModeChangeCallbackCaptor.getValue().onActiveModeManagerRemoved(mNewPrimaryCmm);
+
+        verify(mOnStoppedListener).run();
+    }
+}
diff --git a/service/tests/wifitests/src/com/android/server/wifi/MboOceControllerTest.java b/service/tests/wifitests/src/com/android/server/wifi/MboOceControllerTest.java
index 239fea9..589711e 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/MboOceControllerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/MboOceControllerTest.java
@@ -20,7 +20,6 @@
 import static org.mockito.Mockito.*;
 
 import android.net.wifi.WifiManager;
-import android.os.test.TestLooper;
 import android.telephony.PhoneStateListener;
 import android.telephony.TelephonyManager;
 
@@ -43,9 +42,9 @@
     private static final String INTERFACE_NAME = "wlan0";
 
     private MboOceController mMboOceController;
-    private TestLooper mLooper;
-    @Mock WifiNative mWifiNative;
+    @Mock ConcreteClientModeManager mClientModeManager;
     @Mock TelephonyManager mTelephonyManager;
+    @Mock ActiveModeWarden mActiveModeWarden;
 
     /**
      * Initializes common state (e.g. mocks) needed by test cases.
@@ -53,12 +52,14 @@
     @Before
     public void setUp() throws Exception {
         /* Ensure Looper exists */
-        mLooper = new TestLooper();
         MockitoAnnotations.initMocks(this);
 
-        mMboOceController = new MboOceController(mTelephonyManager, mWifiNative);
+        when(mActiveModeWarden.getPrimaryClientModeManager())
+                .thenReturn(mClientModeManager);
+        when(mActiveModeWarden.getPrimaryClientModeManagerNullable())
+                .thenReturn(mClientModeManager);
 
-        when(mWifiNative.getClientInterfaceName()).thenReturn(INTERFACE_NAME);
+        mMboOceController = new MboOceController(mTelephonyManager, mActiveModeWarden);
     }
 
     /**
@@ -74,8 +75,7 @@
         if (isOceEnabled) {
             featureSet |= WifiManager.WIFI_FEATURE_OCE;
         }
-        when(mWifiNative.getSupportedFeatureSet(INTERFACE_NAME))
-                .thenReturn((long) featureSet);
+        when(mClientModeManager.getSupportedFeatures()).thenReturn(featureSet);
 
         mMboOceController.enable();
 
@@ -110,15 +110,15 @@
      */
     @Test
     public void testMboEnabledUpdateCellularDataStateChangeEvents() throws Exception {
-        InOrder inOrder = inOrder(mWifiNative);
+        InOrder inOrder = inOrder(mClientModeManager);
         PhoneStateListener dataConnectionStateListener;
         dataConnectionStateListener = enableMboOceController(true, false);
         dataConnectionStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
                 TelephonyManager.NETWORK_TYPE_LTE);
-        verify(mWifiNative).setMboCellularDataStatus(eq(INTERFACE_NAME), eq(true));
+        verify(mClientModeManager).setMboCellularDataStatus(true);
         dataConnectionStateListener.onDataConnectionStateChanged(
                 TelephonyManager.DATA_DISCONNECTED, TelephonyManager.NETWORK_TYPE_LTE);
-        verify(mWifiNative).setMboCellularDataStatus(eq(INTERFACE_NAME), eq(false));
+        verify(mClientModeManager).setMboCellularDataStatus(false);
     }
 
     /**
diff --git a/service/tests/wifitests/src/com/android/server/wifi/MockWifiMonitor.java b/service/tests/wifitests/src/com/android/server/wifi/MockWifiMonitor.java
index 908feaf..c339f58 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/MockWifiMonitor.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/MockWifiMonitor.java
@@ -17,7 +17,6 @@
 package com.android.server.wifi;
 
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
 
 import android.os.Handler;
 import android.os.Message;
@@ -31,13 +30,9 @@
  * WARNING: This does not perfectly mock the behavior of WifiMonitor at the moment
  *          ex. startMonitoring does nothing and will not send a connection/disconnection event
  */
-public class MockWifiMonitor extends  WifiMonitor {
+public class MockWifiMonitor extends WifiMonitor {
     private final Map<String, SparseArray<Handler>> mHandlerMap = new HashMap<>();
 
-    public MockWifiMonitor() {
-        super(mock(WifiInjector.class));
-    }
-
     @Override
     public void registerHandler(String iface, int what, Handler handler) {
         SparseArray<Handler> ifaceHandlers = mHandlerMap.get(iface);
@@ -86,5 +81,4 @@
         }
         return false;
     }
-
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/NetworkListStoreDataTest.java b/service/tests/wifitests/src/com/android/server/wifi/NetworkListStoreDataTest.java
index c59c8c4..bfba33f 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/NetworkListStoreDataTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/NetworkListStoreDataTest.java
@@ -18,6 +18,9 @@
 
 import static android.os.Process.SYSTEM_UID;
 
+import static com.android.server.wifi.WifiConfigurationTestUtil.TEST_EAP_PASSWORD;
+import static com.android.server.wifi.WifiConfigurationTestUtil.TEST_IDENTITY;
+
 import static org.junit.Assert.*;
 import static org.mockito.Mockito.*;
 
@@ -31,6 +34,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.util.FastXmlSerializer;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.util.ScanResultUtil;
 import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
 import com.android.server.wifi.util.XmlUtilTest;
@@ -40,7 +44,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
 
 import java.io.ByteArrayInputStream;
@@ -58,8 +61,6 @@
 public class NetworkListStoreDataTest extends WifiBaseTest {
 
     private static final String TEST_SSID = "WifiConfigStoreDataSSID_";
-    private static final String TEST_CONNECT_CHOICE = "XmlUtilConnectChoice";
-    private static final long TEST_CONNECT_CHOICE_TIMESTAMP = 0x4566;
     private static final String TEST_CREATOR_NAME = "CreatorName";
     private static final MacAddress TEST_RANDOMIZED_MAC =
             MacAddress.fromString("da:a1:19:c4:26:fa");
@@ -75,7 +76,7 @@
                     + "<boolean name=\"HiddenSSID\" value=\"false\" />\n"
                     + "<boolean name=\"RequirePMF\" value=\"false\" />\n"
                     + "<byte-array name=\"AllowedKeyMgmt\" num=\"1\">01</byte-array>\n"
-                    + "<byte-array name=\"AllowedProtocols\" num=\"0\"></byte-array>\n"
+                    + "<byte-array name=\"AllowedProtocols\" num=\"1\">03</byte-array>\n"
                     + "<byte-array name=\"AllowedAuthAlgos\" num=\"0\"></byte-array>\n"
                     + "<byte-array name=\"AllowedGroupCiphers\" num=\"0\"></byte-array>\n"
                     + "<byte-array name=\"AllowedPairwiseCiphers\" num=\"0\"></byte-array>\n"
@@ -83,7 +84,26 @@
                     + "<byte-array name=\"AllowedSuiteBCiphers\" num=\"0\"></byte-array>\n"
                     + "<boolean name=\"Shared\" value=\"%s\" />\n"
                     + "<boolean name=\"AutoJoinEnabled\" value=\"true\" />\n"
+                    + "<int name=\"DeletionPriority\" value=\"0\" />\n"
+                    + "<int name=\"NumRebootsSinceLastUse\" value=\"0\" />\n"
+                    + "<SecurityParamsList>\n"
+                    + "<SecurityParams>\n"
+                    + "<int name=\"SecurityType\" value=\"0\" />\n"
+                    + "<boolean name=\"SaeIsH2eOnlyMode\" value=\"false\" />\n"
+                    + "<boolean name=\"SaeIsPkOnlyMode\" value=\"false\" />\n"
+                    + "<boolean name=\"IsAddedByAutoUpgrade\" value=\"false\" />\n"
+                    + "</SecurityParams>\n"
+                    + "<SecurityParams>\n"
+                    + "<int name=\"SecurityType\" value=\"6\" />\n"
+                    + "<boolean name=\"SaeIsH2eOnlyMode\" value=\"false\" />\n"
+                    + "<boolean name=\"SaeIsPkOnlyMode\" value=\"false\" />\n"
+                    + "<boolean name=\"IsAddedByAutoUpgrade\" value=\"true\" />\n"
+                    + "</SecurityParams>\n"
+                    + "</SecurityParamsList>\n"
                     + "<boolean name=\"Trusted\" value=\"true\" />\n"
+                    + "<boolean name=\"OemPaid\" value=\"false\" />\n"
+                    + "<boolean name=\"OemPrivate\" value=\"false\" />\n"
+                    + "<boolean name=\"CarrierMerged\" value=\"false\" />\n"
                     + "<null name=\"BSSID\" />\n"
                     + "<int name=\"Status\" value=\"2\" />\n"
                     + "<null name=\"FQDN\" />\n"
@@ -103,15 +123,18 @@
                     + "<boolean name=\"IsLegacyPasspointConfig\" value=\"false\" />\n"
                     + "<long-array name=\"RoamingConsortiumOIs\" num=\"0\" />\n"
                     + "<string name=\"RandomizedMacAddress\">%s</string>\n"
-                    + "<int name=\"MacRandomizationSetting\" value=\"1\" />\n"
+                    + "<int name=\"MacRandomizationSetting\" value=\"3\" />\n"
                     + "<int name=\"CarrierId\" value=\"-1\" />\n"
                     + "<boolean name=\"IsMostRecentlyConnected\" value=\"false\" />\n"
+                    + "<int name=\"SubscriptionId\" value=\"-1\" />\n"
                     + "</WifiConfiguration>\n"
                     + "<NetworkStatus>\n"
                     + "<string name=\"SelectionStatus\">NETWORK_SELECTION_ENABLED</string>\n"
                     + "<string name=\"DisableReason\">NETWORK_SELECTION_ENABLE</string>\n"
                     + "<null name=\"ConnectChoice\" />\n"
+                    + "<int name=\"ConnectChoiceRssi\" value=\"0\" />\n"
                     + "<boolean name=\"HasEverConnected\" value=\"false\" />\n"
+                    + "<boolean name=\"CaptivePortalNeverDetected\" value=\"true\" />\n"
                     + "</NetworkStatus>\n"
                     + "<IpConfiguration>\n"
                     + "<string name=\"IpAssignment\">DHCP</string>\n"
@@ -130,15 +153,34 @@
                     + "<boolean name=\"HiddenSSID\" value=\"false\" />\n"
                     + "<boolean name=\"RequirePMF\" value=\"false\" />\n"
                     + "<byte-array name=\"AllowedKeyMgmt\" num=\"1\">0c</byte-array>\n"
-                    + "<byte-array name=\"AllowedProtocols\" num=\"0\"></byte-array>\n"
+                    + "<byte-array name=\"AllowedProtocols\" num=\"1\">03</byte-array>\n"
                     + "<byte-array name=\"AllowedAuthAlgos\" num=\"0\"></byte-array>\n"
-                    + "<byte-array name=\"AllowedGroupCiphers\" num=\"0\"></byte-array>\n"
-                    + "<byte-array name=\"AllowedPairwiseCiphers\" num=\"0\"></byte-array>\n"
+                    + "<byte-array name=\"AllowedGroupCiphers\" num=\"1\">0c</byte-array>\n"
+                    + "<byte-array name=\"AllowedPairwiseCiphers\" num=\"1\">06</byte-array>\n"
                     + "<byte-array name=\"AllowedGroupMgmtCiphers\" num=\"0\"></byte-array>\n"
                     + "<byte-array name=\"AllowedSuiteBCiphers\" num=\"0\"></byte-array>\n"
                     + "<boolean name=\"Shared\" value=\"%s\" />\n"
                     + "<boolean name=\"AutoJoinEnabled\" value=\"true\" />\n"
+                    + "<int name=\"DeletionPriority\" value=\"0\" />\n"
+                    + "<int name=\"NumRebootsSinceLastUse\" value=\"0\" />\n"
+                    + "<SecurityParamsList>\n"
+                    + "<SecurityParams>\n"
+                    + "<int name=\"SecurityType\" value=\"3\" />\n"
+                    + "<boolean name=\"SaeIsH2eOnlyMode\" value=\"false\" />\n"
+                    + "<boolean name=\"SaeIsPkOnlyMode\" value=\"false\" />\n"
+                    + "<boolean name=\"IsAddedByAutoUpgrade\" value=\"false\" />\n"
+                    + "</SecurityParams>\n"
+                    + "<SecurityParams>\n"
+                    + "<int name=\"SecurityType\" value=\"9\" />\n"
+                    + "<boolean name=\"SaeIsH2eOnlyMode\" value=\"false\" />\n"
+                    + "<boolean name=\"SaeIsPkOnlyMode\" value=\"false\" />\n"
+                    + "<boolean name=\"IsAddedByAutoUpgrade\" value=\"true\" />\n"
+                    + "</SecurityParams>\n"
+                    + "</SecurityParamsList>\n"
                     + "<boolean name=\"Trusted\" value=\"true\" />\n"
+                    + "<boolean name=\"OemPaid\" value=\"false\" />\n"
+                    + "<boolean name=\"OemPrivate\" value=\"false\" />\n"
+                    + "<boolean name=\"CarrierMerged\" value=\"false\" />\n"
                     + "<null name=\"BSSID\" />\n"
                     + "<int name=\"Status\" value=\"2\" />\n"
                     + "<null name=\"FQDN\" />\n"
@@ -158,24 +200,27 @@
                     + "<boolean name=\"IsLegacyPasspointConfig\" value=\"false\" />\n"
                     + "<long-array name=\"RoamingConsortiumOIs\" num=\"0\" />\n"
                     + "<string name=\"RandomizedMacAddress\">%s</string>\n"
-                    + "<int name=\"MacRandomizationSetting\" value=\"1\" />\n"
+                    + "<int name=\"MacRandomizationSetting\" value=\"3\" />\n"
                     + "<int name=\"CarrierId\" value=\"-1\" />\n"
                     + "<boolean name=\"IsMostRecentlyConnected\" value=\"false\" />\n"
+                    + "<int name=\"SubscriptionId\" value=\"-1\" />\n"
                     + "</WifiConfiguration>\n"
                     + "<NetworkStatus>\n"
                     + "<string name=\"SelectionStatus\">NETWORK_SELECTION_ENABLED</string>\n"
                     + "<string name=\"DisableReason\">NETWORK_SELECTION_ENABLE</string>\n"
                     + "<null name=\"ConnectChoice\" />\n"
+                    + "<int name=\"ConnectChoiceRssi\" value=\"0\" />\n"
                     + "<boolean name=\"HasEverConnected\" value=\"false\" />\n"
+                    + "<boolean name=\"CaptivePortalNeverDetected\" value=\"true\" />\n"
                     + "</NetworkStatus>\n"
                     + "<IpConfiguration>\n"
                     + "<string name=\"IpAssignment\">DHCP</string>\n"
                     + "<string name=\"ProxySettings\">NONE</string>\n"
                     + "</IpConfiguration>\n"
                     + "<WifiEnterpriseConfiguration>\n"
-                    + "<string name=\"Identity\"></string>\n"
+                    + "<string name=\"Identity\">" + TEST_IDENTITY + "</string>\n"
                     + "<string name=\"AnonIdentity\"></string>\n"
-                    + "<string name=\"Password\"></string>\n"
+                    + "<string name=\"Password\">" + TEST_EAP_PASSWORD + "</string>\n"
                     + "<string name=\"ClientCert\"></string>\n"
                     + "<string name=\"CaCert\"></string>\n"
                     + "<string name=\"SubjectMatch\"></string>\n"
@@ -186,13 +231,18 @@
                     + "<string name=\"DomSuffixMatch\">%s</string>\n"
                     + "<string name=\"CaPath\">%s</string>\n"
                     + "<int name=\"EapMethod\" value=\"2\" />\n"
-                    + "<int name=\"Phase2Method\" value=\"0\" />\n"
+                    + "<int name=\"Phase2Method\" value=\"3\" />\n"
                     + "<string name=\"PLMN\"></string>\n"
                     + "<string name=\"Realm\"></string>\n"
                     + "<int name=\"Ocsp\" value=\"0\" />\n"
                     + "<string name=\"WapiCertSuite\"></string>\n"
+                    + "<boolean name=\"AppInstalledRootCaCert\" value=\"false\" />\n"
+                    + "<boolean name=\"AppInstalledPrivateKey\" value=\"false\" />\n"
+                    + "<null name=\"KeyChainAlias\" />\n"
+                    + (SdkLevel.isAtLeastS()
+                    ? "<null name=\"DecoratedIdentityPrefix\" />\n" : "")
                     + "</WifiEnterpriseConfiguration>\n"
-                    + "</Network>\n";
+                    + "</Network>\n";;
 
     private static final String SINGLE_SAE_NETWORK_DATA_XML_STRING_FORMAT =
             "<Network>\n"
@@ -208,13 +258,26 @@
                     + "<byte-array name=\"AllowedKeyMgmt\" num=\"2\">0001</byte-array>\n"
                     + "<byte-array name=\"AllowedProtocols\" num=\"1\">02</byte-array>\n"
                     + "<byte-array name=\"AllowedAuthAlgos\" num=\"0\"></byte-array>\n"
-                    + "<byte-array name=\"AllowedGroupCiphers\" num=\"1\">28</byte-array>\n"
-                    + "<byte-array name=\"AllowedPairwiseCiphers\" num=\"1\">0c</byte-array>\n"
+                    + "<byte-array name=\"AllowedGroupCiphers\" num=\"1\">a8</byte-array>\n"
+                    + "<byte-array name=\"AllowedPairwiseCiphers\" num=\"1\">2c</byte-array>\n"
                     + "<byte-array name=\"AllowedGroupMgmtCiphers\" num=\"0\"></byte-array>\n"
                     + "<byte-array name=\"AllowedSuiteBCiphers\" num=\"0\"></byte-array>\n"
                     + "<boolean name=\"Shared\" value=\"%s\" />\n"
                     + "<boolean name=\"AutoJoinEnabled\" value=\"true\" />\n"
+                    + "<int name=\"DeletionPriority\" value=\"0\" />\n"
+                    + "<int name=\"NumRebootsSinceLastUse\" value=\"0\" />\n"
+                    + "<SecurityParamsList>\n"
+                    + "<SecurityParams>\n"
+                    + "<int name=\"SecurityType\" value=\"4\" />\n"
+                    + "<boolean name=\"SaeIsH2eOnlyMode\" value=\"false\" />\n"
+                    + "<boolean name=\"SaeIsPkOnlyMode\" value=\"false\" />\n"
+                    + "<boolean name=\"IsAddedByAutoUpgrade\" value=\"false\" />\n"
+                    + "</SecurityParams>\n"
+                    + "</SecurityParamsList>\n"
                     + "<boolean name=\"Trusted\" value=\"true\" />\n"
+                    + "<boolean name=\"OemPaid\" value=\"false\" />\n"
+                    + "<boolean name=\"OemPrivate\" value=\"false\" />\n"
+                    + "<boolean name=\"CarrierMerged\" value=\"false\" />\n"
                     + "<null name=\"BSSID\" />\n"
                     + "<int name=\"Status\" value=\"2\" />\n"
                     + "<null name=\"FQDN\" />\n"
@@ -234,15 +297,18 @@
                     + "<boolean name=\"IsLegacyPasspointConfig\" value=\"false\" />\n"
                     + "<long-array name=\"RoamingConsortiumOIs\" num=\"0\" />\n"
                     + "<string name=\"RandomizedMacAddress\">%s</string>\n"
-                    + "<int name=\"MacRandomizationSetting\" value=\"1\" />\n"
+                    + "<int name=\"MacRandomizationSetting\" value=\"3\" />\n"
                     + "<int name=\"CarrierId\" value=\"-1\" />\n"
                     + "<boolean name=\"IsMostRecentlyConnected\" value=\"false\" />\n"
+                    + "<int name=\"SubscriptionId\" value=\"-1\" />\n"
                     + "</WifiConfiguration>\n"
                     + "<NetworkStatus>\n"
                     + "<string name=\"SelectionStatus\">NETWORK_SELECTION_ENABLED</string>\n"
                     + "<string name=\"DisableReason\">NETWORK_SELECTION_ENABLE</string>\n"
                     + "<null name=\"ConnectChoice\" />\n"
+                    + "<int name=\"ConnectChoiceRssi\" value=\"0\" />\n"
                     + "<boolean name=\"HasEverConnected\" value=\"false\" />\n"
+                    + "<boolean name=\"CaptivePortalNeverDetected\" value=\"true\" />\n"
                     + "</NetworkStatus>\n"
                     + "<IpConfiguration>\n"
                     + "<string name=\"IpAssignment\">DHCP</string>\n"
@@ -268,14 +334,16 @@
                     + "<boolean name=\"HiddenSSID\" value=\"false\" />\n"
                     + "<boolean name=\"RequirePMF\" value=\"false\" />\n"
                     + "<byte-array name=\"AllowedKeyMgmt\" num=\"1\">20</byte-array>\n"
-                    + "<byte-array name=\"AllowedProtocols\" num=\"0\"></byte-array>\n"
+                    + "<byte-array name=\"AllowedProtocols\" num=\"1\">03</byte-array>\n"
                     + "<byte-array name=\"AllowedAuthAlgos\" num=\"0\"></byte-array>\n"
-                    + "<byte-array name=\"AllowedGroupCiphers\" num=\"0\"></byte-array>\n"
-                    + "<byte-array name=\"AllowedPairwiseCiphers\" num=\"0\"></byte-array>\n"
+                    + "<byte-array name=\"AllowedGroupCiphers\" num=\"1\">0f</byte-array>\n"
+                    + "<byte-array name=\"AllowedPairwiseCiphers\" num=\"1\">06</byte-array>\n"
                     + "<byte-array name=\"AllowedGroupMgmtCiphers\" num=\"0\"></byte-array>\n"
                     + "<byte-array name=\"AllowedSuiteBCiphers\" num=\"0\"></byte-array>\n"
                     + "<boolean name=\"Shared\" value=\"%s\" />\n"
                     + "<boolean name=\"AutoJoinEnabled\" value=\"true\" />\n"
+                    + "<int name=\"DeletionPriority\" value=\"0\" />\n"
+                    + "<int name=\"NumRebootsSinceLastUse\" value=\"0\" />\n"
                     + "<boolean name=\"Trusted\" value=\"true\" />\n"
                     + "<null name=\"BSSID\" />\n"
                     + "<int name=\"Status\" value=\"2\" />\n"
@@ -296,7 +364,7 @@
                     + "<boolean name=\"IsLegacyPasspointConfig\" value=\"false\" />\n"
                     + "<long-array name=\"RoamingConsortiumOIs\" num=\"0\" />\n"
                     + "<string name=\"RandomizedMacAddress\">%s</string>\n"
-                    + "<int name=\"MacRandomizationSetting\" value=\"1\" />\n"
+                    + "<int name=\"MacRandomizationSetting\" value=\"3\" />\n"
                     + "<int name=\"CarrierId\" value=\"-1\" />\n"
                     + "<boolean name=\"IsMostRecentlyConnected\" value=\"false\" />\n"
                     + "</WifiConfiguration>\n"
@@ -304,7 +372,9 @@
                     + "<string name=\"SelectionStatus\">NETWORK_SELECTION_ENABLED</string>\n"
                     + "<string name=\"DisableReason\">NETWORK_SELECTION_ENABLE</string>\n"
                     + "<null name=\"ConnectChoice\" />\n"
+                    + "<int name=\"ConnectChoiceRssi\" value=\"0\" />\n"
                     + "<boolean name=\"HasEverConnected\" value=\"false\" />\n"
+                    + "<boolean name=\"CaptivePortalNeverDetected\" value=\"true\" />\n"
                     + "</NetworkStatus>\n"
                     + "<IpConfiguration>\n"
                     + "<string name=\"IpAssignment\">UNASSIGNED</string>\n"
@@ -366,14 +436,14 @@
      * @return List of WifiConfiguration
      */
     private List<WifiConfiguration> getTestNetworksConfig(boolean shared) {
-        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenOweNetwork();
         openNetwork.creatorName = TEST_CREATOR_NAME;
         openNetwork.shared = shared;
         openNetwork.setIpConfiguration(
                 WifiConfigurationTestUtil.createDHCPIpConfigurationWithNoProxy());
         openNetwork.setRandomizedMacAddress(TEST_RANDOMIZED_MAC);
         openNetwork.meteredOverride = WifiConfiguration.METERED_OVERRIDE_NOT_METERED;
-        WifiConfiguration eapNetwork = WifiConfigurationTestUtil.createEapNetwork();
+        WifiConfiguration eapNetwork = WifiConfigurationTestUtil.createWpa2Wpa3EnterpriseNetwork();
         eapNetwork.shared = shared;
         eapNetwork.creatorName = TEST_CREATOR_NAME;
         eapNetwork.setIpConfiguration(
@@ -475,7 +545,8 @@
         byte[] expectedData = getTestNetworksXmlBytes(networkList.get(0), networkList.get(1),
                 networkList.get(2));
         byte[] serializedData = serializeData();
-        assertArrayEquals(expectedData, serializeData());
+        assertEquals(new String(expectedData, StandardCharsets.UTF_8),
+                new String(serializedData, StandardCharsets.UTF_8));
     }
 
     /**
@@ -532,13 +603,15 @@
                         + "<null name=\"LastUpdateName\" />\n"
                         + "<int name=\"LastConnectUid\" value=\"0\" />\n"
                         + "<string name=\"RandomizedMacAddress\">%s</string>\n"
-                        + "<int name=\"MacRandomizationSetting\" value=\"1\" />\n"
+                        + "<int name=\"MacRandomizationSetting\" value=\"3\" />\n"
                         + "</WifiConfiguration>\n"
                         + "<NetworkStatus>\n"
                         + "<string name=\"SelectionStatus\">NETWORK_SELECTION_ENABLED</string>\n"
                         + "<string name=\"DisableReason\">NETWORK_SELECTION_ENABLE</string>\n"
                         + "<null name=\"ConnectChoice\" />\n"
+                        + "<int name=\"ConnectChoiceRssi\" value=\"0\" />\n"
                         + "<boolean name=\"HasEverConnected\" value=\"false\" />\n"
+                        + "<boolean name=\"CaptivePortalNeverDetected\" value=\"true\" />\n"
                         + "</NetworkStatus>\n"
                         + "<IpConfiguration>\n"
                         + "<string name=\"IpAssignment\">DHCP</string>\n"
@@ -563,12 +636,11 @@
     }
 
     /**
-     * Verify that a XmlPullParseException will be thrown when parsing a network configuration
+     * Verify that no exception will be thrown when parsing a network configuration
      * containing a mismatched config key.
      *
      * @throws Exception
      */
-    @Test(expected = XmlPullParserException.class)
     public void parseNetworkWithMismatchConfigKey() throws Exception {
         WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
         byte[] xmlData = String.format(SINGLE_OPEN_NETWORK_DATA_XML_STRING_FORMAT,
diff --git a/service/tests/wifitests/src/com/android/server/wifi/NetworkSuggestionNominatorTest.java b/service/tests/wifitests/src/com/android/server/wifi/NetworkSuggestionNominatorTest.java
index 1e06773..4c11e3d 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/NetworkSuggestionNominatorTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/NetworkSuggestionNominatorTest.java
@@ -16,9 +16,7 @@
 
 package com.android.server.wifi;
 
-import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
-import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus
-        .NETWORK_SELECTION_TEMPORARY_DISABLED;
+import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED;
 
 import static com.android.server.wifi.WifiConfigurationTestUtil.SECURITY_EAP;
 import static com.android.server.wifi.WifiConfigurationTestUtil.SECURITY_PSK;
@@ -31,11 +29,13 @@
 import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiNetworkSuggestion;
 import android.net.wifi.WifiSsid;
+import android.telephony.SubscriptionManager;
 import android.util.LocalLog;
 import android.util.Pair;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.WifiNetworkSuggestionsManager.ExtendedWifiNetworkSuggestion;
 import com.android.server.wifi.WifiNetworkSuggestionsManager.PerAppInfo;
 import com.android.server.wifi.hotspot2.PasspointNetworkNominateHelper;
@@ -43,7 +43,6 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -67,14 +66,15 @@
     private static final int TEST_CARRIER_ID = 1911;
     private static final String TEST_CARRIER_NAME = "testCarrier";
     private static final int TEST_SUB_ID = 2020;
+    private static final String PASSPOINT_UNIQUE_ID = "uniqueId";
 
 
     private @Mock WifiConfigManager mWifiConfigManager;
     private @Mock WifiNetworkSuggestionsManager mWifiNetworkSuggestionsManager;
     private @Mock PasspointNetworkNominateHelper mPasspointNetworkNominateHelper;
     private @Mock Clock mClock;
-    private @Mock
-    WifiCarrierInfoManager mWifiCarrierInfoManager;
+    private @Mock WifiCarrierInfoManager mWifiCarrierInfoManager;
+    private @Mock WifiMetrics mWifiMetrics;
     private NetworkSuggestionNominator mNetworkSuggestionNominator;
 
     /** Sets up test. */
@@ -83,7 +83,10 @@
         MockitoAnnotations.initMocks(this);
         mNetworkSuggestionNominator = new NetworkSuggestionNominator(
                 mWifiNetworkSuggestionsManager, mWifiConfigManager, mPasspointNetworkNominateHelper,
-                new LocalLog(100), mWifiCarrierInfoManager);
+                new LocalLog(100), mWifiCarrierInfoManager, mWifiMetrics);
+        when(mWifiCarrierInfoManager.getBestMatchSubscriptionId(any())).thenReturn(
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        when(mWifiConfigManager.isNetworkTemporarilyDisabledByUser(anyString())).thenReturn(false);
     }
 
     /**
@@ -95,7 +98,7 @@
         String[] scanSsids = {"test1", "test2"};
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2470, 2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {-67, -76};
         String[] suggestionSsids = {};
         int[] securities = {};
@@ -106,23 +109,26 @@
         String[] packageNames = {};
         boolean[] autojoin = {};
         boolean[] shareWithUser = {};
+        int[] priorityGroup = {};
 
         ScanDetail[] scanDetails =
                 buildScanDetails(scanSsids, bssids, freqs, caps, levels, mClock);
         ExtendedWifiNetworkSuggestion[] suggestions = buildNetworkSuggestions(suggestionSsids,
                 securities, appInteractions, meteredness, priorities, uids,
-                packageNames, autojoin, shareWithUser);
+                packageNames, autojoin, shareWithUser, priorityGroup);
         // Link the scan result with suggestions.
         linkScanDetailsWithNetworkSuggestions(scanDetails, suggestions);
 
         List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
         mNetworkSuggestionNominator.nominateNetworks(
-                Arrays.asList(scanDetails), null, null, true, false,
+                Arrays.asList(scanDetails), false, false, false,
                 (ScanDetail scanDetail, WifiConfiguration configuration) -> {
                     connectableNetworks.add(Pair.create(scanDetail, configuration));
                 });
 
         assertTrue(connectableNetworks.isEmpty());
+        verify(mWifiMetrics, never())
+                .incrementNetworkSuggestionMoreThanOneSuggestionForSingleScanResult();
     }
 
     /**
@@ -134,7 +140,7 @@
         String[] scanSsids = {"test1", "test2"};
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2470, 2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {-67, -76};
         String[] suggestionSsids = {"\"" + scanSsids[0] + "\""};
         int[] securities = {SECURITY_PSK};
@@ -145,26 +151,27 @@
         String[] packageNames = {TEST_PACKAGE};
         boolean[] autojoin = {true};
         boolean[] shareWithUser = {true};
-
+        int[] priorityGroup = {0};
         ScanDetail[] scanDetails =
                 buildScanDetails(scanSsids, bssids, freqs, caps, levels, mClock);
         ExtendedWifiNetworkSuggestion[] suggestions = buildNetworkSuggestions(suggestionSsids,
                 securities, appInteractions, meteredness, priorities, uids,
-                packageNames, autojoin, shareWithUser);
+                packageNames, autojoin, shareWithUser, priorityGroup);
         // Link the scan result with suggestions.
         linkScanDetailsWithNetworkSuggestions(scanDetails, suggestions);
         // setup config manager interactions.
-        setupAddToWifiConfigManager(suggestions[0].wns.wifiConfiguration);
+        setupAddToWifiConfigManager(suggestions[0]);
 
         List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
         mNetworkSuggestionNominator.nominateNetworks(
-                Arrays.asList(scanDetails), null, null, true, false,
+                Arrays.asList(scanDetails), false, false, false,
                 (ScanDetail scanDetail, WifiConfiguration configuration) -> {
                     connectableNetworks.add(Pair.create(scanDetail, configuration));
                 });
 
         validateConnectableNetworks(connectableNetworks, scanSsids[0]);
-        verifyAddToWifiConfigManager(suggestions[0].wns.wifiConfiguration);
+        verify(mWifiMetrics, never())
+                .incrementNetworkSuggestionMoreThanOneSuggestionForSingleScanResult();
     }
 
     @Test
@@ -172,7 +179,7 @@
         String[] scanSsids = {"test1"};
         String[] bssids = {"6c:f3:7f:ae:8c:f3"};
         int[] freqs = {2470};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {-67};
         String[] suggestionSsids = {"\"" + scanSsids[0] + "\""};
         int[] securities = {SECURITY_EAP};
@@ -183,28 +190,31 @@
         String[] packageNames = {TEST_PACKAGE};
         boolean[] autojoin = {true};
         boolean[] shareWithUser = {true};
+        int[] priorityGroup = {0};
 
         ScanDetail[] scanDetails =
                 buildScanDetails(scanSsids, bssids, freqs, caps, levels, mClock);
         ExtendedWifiNetworkSuggestion[] suggestions = buildNetworkSuggestions(suggestionSsids,
                 securities, appInteractions, meteredness, priorities, uids,
-                packageNames, autojoin, shareWithUser);
+                packageNames, autojoin, shareWithUser, priorityGroup);
         WifiConfiguration config = suggestions[0].wns.wifiConfiguration;
         config.enterpriseConfig.setCaPath(null);
         // Link the scan result with suggestions.
         linkScanDetailsWithNetworkSuggestions(scanDetails, suggestions);
         // setup config manager interactions.
-        setupAddToWifiConfigManager(suggestions[0].wns.wifiConfiguration);
+        setupAddToWifiConfigManager(suggestions[0]);
 
         List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
         mNetworkSuggestionNominator.nominateNetworks(
-                Arrays.asList(scanDetails), null, null, true, false,
+                Arrays.asList(scanDetails), false, false, false,
                 (ScanDetail scanDetail, WifiConfiguration configuration) -> {
                     connectableNetworks.add(Pair.create(scanDetail, configuration));
                 });
 
         // Verify no network is nominated.
         assertTrue(connectableNetworks.isEmpty());
+        verify(mWifiMetrics, never())
+                .incrementNetworkSuggestionMoreThanOneSuggestionForSingleScanResult();
     }
 
     /**
@@ -216,7 +226,7 @@
         String[] scanSsids = {"test1", "test2"};
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2470, 2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {-56, -45};
         String[] suggestionSsids = {"\"" + scanSsids[0] + "\"", "\"" + scanSsids[1] + "\""};
         int[] securities = {SECURITY_PSK, SECURITY_PSK};
@@ -227,29 +237,28 @@
         String[] packageNames = {TEST_PACKAGE, TEST_PACKAGE};
         boolean[] autojoin = {true, true};
         boolean[] shareWithUser = {true, true};
+        int[] priorityGroup = {0, 0};
 
         ScanDetail[] scanDetails =
                 buildScanDetails(scanSsids, bssids, freqs, caps, levels, mClock);
         ExtendedWifiNetworkSuggestion[] suggestions = buildNetworkSuggestions(suggestionSsids,
                 securities, appInteractions, meteredness, priorities, uids,
-                packageNames, autojoin, shareWithUser);
+                packageNames, autojoin, shareWithUser, priorityGroup);
         // Link the scan result with suggestions.
         linkScanDetailsWithNetworkSuggestions(scanDetails, suggestions);
         // setup config manager interactions.
-        setupAddToWifiConfigManager(suggestions[0].wns.wifiConfiguration,
-                suggestions[1].wns.wifiConfiguration);
+        setupAddToWifiConfigManager(suggestions[0], suggestions[1]);
 
         List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
         mNetworkSuggestionNominator.nominateNetworks(
-                Arrays.asList(scanDetails), null, null, true, false,
+                Arrays.asList(scanDetails), false, false, false,
                 (ScanDetail scanDetail, WifiConfiguration configuration) -> {
                     connectableNetworks.add(Pair.create(scanDetail, configuration));
                 });
 
         validateConnectableNetworks(connectableNetworks, scanSsids[0], scanSsids[1]);
-
-        verifyAddToWifiConfigManager(suggestions[1].wns.wifiConfiguration,
-                suggestions[1].wns.wifiConfiguration);
+        verify(mWifiMetrics, never())
+                .incrementNetworkSuggestionMoreThanOneSuggestionForSingleScanResult();
     }
 
     /**
@@ -262,7 +271,7 @@
         String[] scanSsids = {"test1", "test2"};
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2470, 2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {-56, -45};
         String[] suggestionSsids = {"\"" + scanSsids[0] + "\"", "\"" + scanSsids[1] + "\""};
         int[] securities = {SECURITY_PSK, SECURITY_PSK};
@@ -273,42 +282,90 @@
         String[] packageNames = {TEST_PACKAGE, TEST_PACKAGE};
         boolean[] autojoin = {true, true};
         boolean[] shareWithUser = {true, true};
+        int[] priorityGroup = {1, 1};
 
         ScanDetail[] scanDetails =
                 buildScanDetails(scanSsids, bssids, freqs, caps, levels, mClock);
         ExtendedWifiNetworkSuggestion[] suggestions = buildNetworkSuggestions(suggestionSsids,
                 securities, appInteractions, meteredness, priorities, uids,
-                packageNames, autojoin, shareWithUser);
+                packageNames, autojoin, shareWithUser, priorityGroup);
         // Link the scan result with suggestions.
         linkScanDetailsWithNetworkSuggestions(scanDetails, suggestions);
         // setup config manager interactions.
-        setupAddToWifiConfigManager(suggestions[0].wns.wifiConfiguration,
-                suggestions[1].wns.wifiConfiguration);
+        setupAddToWifiConfigManager(suggestions[0], suggestions[1]);
 
         List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
         mNetworkSuggestionNominator.nominateNetworks(
-                Arrays.asList(scanDetails), null, null, true, false,
+                Arrays.asList(scanDetails), false, false, false,
                 (ScanDetail scanDetail, WifiConfiguration configuration) -> {
                     connectableNetworks.add(Pair.create(scanDetail, configuration));
                 });
 
         validateConnectableNetworks(connectableNetworks, scanSsids[0]);
-
-        verifyAddToWifiConfigManager(suggestions[0].wns.wifiConfiguration);
+        verify(mWifiMetrics, never())
+                .incrementNetworkSuggestionMoreThanOneSuggestionForSingleScanResult();
     }
 
     /**
-     * Ensure that we nominate one network when multiple suggestor suggested same network.
+     * Ensure that we nominate the network suggestion corresponding to the scan result with
+     * higest priority from each priority group within same suggestor app.
+     * Expected connectable Networks: {suggestionSsids[0], suggestionSsids[2]}
+     */
+    @Test
+    public void testSelectNetworkSuggestionForMultipleMatchHighPriorityFromEachPriorityGroupWins() {
+        String[] scanSsids = {"test1", "test2", "test3", "test4"};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4", "6c:f3:7f:ae:8c:f5",
+                "6c:f3:7f:ae:8c:f6"};
+        int[] freqs = {2470, 2437, 2470, 2437};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]",
+                "[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
+        int[] levels = {-56, -45, -44, -43};
+        String[] suggestionSsids = {"\"" + scanSsids[0] + "\"", "\"" + scanSsids[1] + "\"",
+                "\"" + scanSsids[2] + "\"", "\"" + scanSsids[3] + "\""};
+        int[] securities = {SECURITY_PSK, SECURITY_PSK, SECURITY_PSK, SECURITY_PSK};
+        boolean[] appInteractions = {true, true, true, true};
+        boolean[] meteredness = {false, false, false, false};
+        int[] priorities = {5, 4, 3, 1};
+        int[] uids = {TEST_UID, TEST_UID, TEST_UID, TEST_UID};
+        String[] packageNames = {TEST_PACKAGE, TEST_PACKAGE, TEST_PACKAGE, TEST_PACKAGE};
+        boolean[] autojoin = {true, true, true, true};
+        boolean[] shareWithUser = {true, true, true, true};
+        int[] priorityGroup = {0, 0 , 1 , 1};
+
+        ScanDetail[] scanDetails =
+                buildScanDetails(scanSsids, bssids, freqs, caps, levels, mClock);
+        ExtendedWifiNetworkSuggestion[] suggestions = buildNetworkSuggestions(suggestionSsids,
+                securities, appInteractions, meteredness, priorities, uids,
+                packageNames, autojoin, shareWithUser, priorityGroup);
+
+        // Link the scan result with suggestions.
+        linkScanDetailsWithNetworkSuggestions(scanDetails, suggestions);
+        // setup config manager interactions.
+        setupAddToWifiConfigManager(suggestions[0], suggestions[1], suggestions[2], suggestions[3]);
+
+        List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
+        mNetworkSuggestionNominator.nominateNetworks(
+                Arrays.asList(scanDetails), false, false, false,
+                (ScanDetail scanDetail, WifiConfiguration configuration) -> {
+                    connectableNetworks.add(Pair.create(scanDetail, configuration));
+                });
+
+        validateConnectableNetworks(connectableNetworks, scanSsids[0], scanSsids[2]);
+        verify(mWifiMetrics, never())
+                .incrementNetworkSuggestionMoreThanOneSuggestionForSingleScanResult();
+    }
+
+    /**
+     * Ensure that we nominate all profiles when multiple suggestor suggested same network.
      *
-     * Expected connectable Networks: {suggestionSsids[0],
-     *                                 (suggestionSsids[1] || suggestionSsids[2]}
+     * Expected connectable Networks: {suggestionSsids[0], suggestionSsids[1], suggestionSsids[2]}
      */
     @Test
     public void testSelectNetworkSuggestionForMultipleMatchWithMultipleSuggestions() {
         String[] scanSsids = {"test1", "test2"};
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2470, 2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {-23, -45};
         String[] suggestionSsids = {"\"" + scanSsids[0] + "\"", "\"" + scanSsids[1] + "\"",
                 "\"" + scanSsids[1] + "\""};
@@ -320,29 +377,27 @@
         String[] packageNames = {TEST_PACKAGE, TEST_PACKAGE, TEST_PACKAGE_OTHER};
         boolean[] autojoin = {true, true, true};
         boolean[] shareWithUser = {true, true, true};
+        int[] priorityGroup = {0, 0, 0};
 
         ScanDetail[] scanDetails =
                 buildScanDetails(scanSsids, bssids, freqs, caps, levels, mClock);
         ExtendedWifiNetworkSuggestion[] suggestions = buildNetworkSuggestions(suggestionSsids,
                 securities, appInteractions, meteredness, priorities, uids,
-                packageNames, autojoin, shareWithUser);
+                packageNames, autojoin, shareWithUser, priorityGroup);
         // Link the scan result with suggestions.
         linkScanDetailsWithNetworkSuggestions(scanDetails, suggestions);
         // setup config manager interactions.
-        setupAddToWifiConfigManager(suggestions[0].wns.wifiConfiguration,
-                suggestions[1].wns.wifiConfiguration, suggestions[2].wns.wifiConfiguration);
+        setupAddToWifiConfigManager(suggestions[0], suggestions[1], suggestions[2]);
 
         List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
         mNetworkSuggestionNominator.nominateNetworks(
-                Arrays.asList(scanDetails), null, null, true, false,
+                Arrays.asList(scanDetails), false, false, false,
                 (ScanDetail scanDetail, WifiConfiguration configuration) -> {
                     connectableNetworks.add(Pair.create(scanDetail, configuration));
                 });
 
-        validateConnectableNetworks(connectableNetworks, scanSsids);
-
-        verifyAddToWifiConfigManager(suggestions[0].wns.wifiConfiguration,
-                suggestions[1].wns.wifiConfiguration);
+        validateConnectableNetworks(connectableNetworks, scanSsids[0], scanSsids[1], scanSsids[1]);
+        verify(mWifiMetrics).incrementNetworkSuggestionMoreThanOneSuggestionForSingleScanResult();
     }
 
     /**
@@ -363,8 +418,8 @@
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4", "6c:fc:de:34:12",
                 "6c:fd:a1:11:11:98"};
         int[] freqs = {2470, 2437, 2470, 2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]",
-                "[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]",
+                "[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {-23, -45, -56, -65};
         String[] suggestionSsids = {"\"" + scanSsids[0] + "\"", "\"" + scanSsids[1] + "\"",
                 "\"" + scanSsids[2] + "\"", "\"" + scanSsids[3] + "\""};
@@ -377,30 +432,28 @@
                 TEST_PACKAGE_OTHER};
         boolean[] autojoin = {true, true, true, true};
         boolean[] shareWithUser = {true, true, true, true};
+        int[] priorityGroup = {0, 0, 0, 0};
 
         ScanDetail[] scanDetails =
                 buildScanDetails(scanSsids, bssids, freqs, caps, levels, mClock);
         ExtendedWifiNetworkSuggestion[] suggestions = buildNetworkSuggestions(suggestionSsids,
                 securities, appInteractions, meteredness, priorities, uids,
-                packageNames, autojoin, shareWithUser);
+                packageNames, autojoin, shareWithUser, priorityGroup);
         // Link the scan result with suggestions.
         linkScanDetailsWithNetworkSuggestions(scanDetails, suggestions);
         // setup config manager interactions.
-        setupAddToWifiConfigManager(suggestions[0].wns.wifiConfiguration,
-                suggestions[1].wns.wifiConfiguration, suggestions[2].wns.wifiConfiguration,
-                suggestions[3].wns.wifiConfiguration);
+        setupAddToWifiConfigManager(suggestions[0], suggestions[1], suggestions[2], suggestions[3]);
 
         List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
         mNetworkSuggestionNominator.nominateNetworks(
-                Arrays.asList(scanDetails), null, null, true, false,
+                Arrays.asList(scanDetails), false, false, false,
                 (ScanDetail scanDetail, WifiConfiguration configuration) -> {
                     connectableNetworks.add(Pair.create(scanDetail, configuration));
                 });
 
         validateConnectableNetworks(connectableNetworks, scanSsids[1], scanSsids[2], scanSsids[3]);
-
-        verifyAddToWifiConfigManager(suggestions[1].wns.wifiConfiguration,
-                suggestions[2].wns.wifiConfiguration, suggestions[3].wns.wifiConfiguration);
+        verify(mWifiMetrics, never())
+                .incrementNetworkSuggestionMoreThanOneSuggestionForSingleScanResult();
     }
 
     /**
@@ -410,11 +463,11 @@
      * Expected connectable Networks: {}
      */
     @Test
-    public void testSelectNetworkSuggestionForOneMatchButFailToAddToWifiConfigManager() {
+    public void testSelectNetworkSuggestionForOneMatchButInToWifiConfigManager() {
         String[] scanSsids = {"test1", "test2"};
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2470, 2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {-67, -76};
         String[] suggestionSsids = {"\"" + scanSsids[0] + "\""};
         int[] securities = {SECURITY_PSK};
@@ -425,35 +478,32 @@
         String[] packageNames = {TEST_PACKAGE};
         boolean[] autojoin = {true};
         boolean[] shareWithUser = {true};
+        int[] priorityGroup = {1};
 
         ScanDetail[] scanDetails =
                 buildScanDetails(scanSsids, bssids, freqs, caps, levels, mClock);
         ExtendedWifiNetworkSuggestion[] suggestions = buildNetworkSuggestions(suggestionSsids,
                 securities, appInteractions, meteredness, priorities, uids,
-                packageNames, autojoin, shareWithUser);
+                packageNames, autojoin, shareWithUser, priorityGroup);
         // Link the scan result with suggestions.
         linkScanDetailsWithNetworkSuggestions(scanDetails, suggestions);
-        // Fail add to WifiConfigManager
-        when(mWifiConfigManager.addOrUpdateNetwork(any(), anyInt(), anyString()))
-                .thenReturn(new NetworkUpdateResult(INVALID_NETWORK_ID));
 
         List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
         mNetworkSuggestionNominator.nominateNetworks(
-                Arrays.asList(scanDetails), null, null, true, false,
+                Arrays.asList(scanDetails), false, false, false,
                 (ScanDetail scanDetail, WifiConfiguration configuration) -> {
                     connectableNetworks.add(Pair.create(scanDetail, configuration));
                 });
 
         assertTrue(connectableNetworks.isEmpty());
 
-        verify(mWifiConfigManager, times(suggestionSsids.length))
-                .isNetworkTemporarilyDisabledByUser(anyString());
-        verify(mWifiConfigManager).getConfiguredNetwork(eq(
-                suggestions[0].wns.wifiConfiguration.getKey()));
-        verify(mWifiConfigManager).addOrUpdateNetwork(any(), anyInt(), anyString());
+        verify(mWifiConfigManager).getConfiguredNetwork(eq(suggestions[0]
+                .createInternalWifiConfiguration(mWifiCarrierInfoManager).getProfileKey()));
         // Verify we did not try to add any new networks or other interactions with
         // WifiConfigManager.
         verifyNoMoreInteractions(mWifiConfigManager);
+        verify(mWifiMetrics, never())
+                .incrementNetworkSuggestionMoreThanOneSuggestionForSingleScanResult();
     }
 
     /**
@@ -467,7 +517,7 @@
         String[] scanSsids = {"test1", "test2"};
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2470, 2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {-67, -76};
         String[] suggestionSsids = {"\"" + scanSsids[0] + "\""};
         int[] securities = {SECURITY_PSK};
@@ -478,25 +528,27 @@
         String[] packageNames = {TEST_PACKAGE};
         boolean[] autojoin = {true};
         boolean[] shareWithUser = {true};
+        int[] priorityGroup = {0};
 
         ScanDetail[] scanDetails =
                 buildScanDetails(scanSsids, bssids, freqs, caps, levels, mClock);
         ExtendedWifiNetworkSuggestion[] suggestions = buildNetworkSuggestions(suggestionSsids,
                 securities, appInteractions, meteredness, priorities, uids,
-                packageNames, autojoin, shareWithUser);
+                packageNames, autojoin, shareWithUser, priorityGroup);
         // Link the scan result with suggestions.
         linkScanDetailsWithNetworkSuggestions(scanDetails, suggestions);
         // setup config manager interactions.
         suggestions[0].wns.wifiConfiguration.fromWifiNetworkSuggestion = true;
         suggestions[0].wns.wifiConfiguration.ephemeral = true;
-        setupAddToWifiConfigManager(suggestions[0].wns.wifiConfiguration);
+        setupAddToWifiConfigManager(suggestions[0]);
         // Existing saved network matching the credentials.
-        when(mWifiConfigManager.getConfiguredNetwork(suggestions[0].wns.wifiConfiguration.getKey()))
+        when(mWifiConfigManager.getConfiguredNetwork(suggestions[0]
+                .createInternalWifiConfiguration(mWifiCarrierInfoManager).getKey()))
                 .thenReturn(suggestions[0].wns.wifiConfiguration);
 
         List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
         mNetworkSuggestionNominator.nominateNetworks(
-                Arrays.asList(scanDetails), null, null, true, false,
+                Arrays.asList(scanDetails), false, false, false,
                 (ScanDetail scanDetail, WifiConfiguration configuration) -> {
                     connectableNetworks.add(Pair.create(scanDetail, configuration));
                 });
@@ -506,11 +558,20 @@
         // check for any saved networks.
         verify(mWifiConfigManager, times(suggestionSsids.length))
                 .isNetworkTemporarilyDisabledByUser(anyString());
-        verify(mWifiConfigManager)
-                .getConfiguredNetwork(suggestions[0].wns.wifiConfiguration.getKey());
+        verify(mWifiConfigManager).getConfiguredNetwork(suggestions[0]
+                .createInternalWifiConfiguration(mWifiCarrierInfoManager).getProfileKey());
+        verify(mWifiConfigManager).isNonCarrierMergedNetworkTemporarilyDisabled(any());
+        if (SdkLevel.isAtLeastS()) {
+            verify(mWifiConfigManager).getConfiguredNetwork(suggestions[0]
+                    .createInternalWifiConfiguration(mWifiCarrierInfoManager).getKey());
+        }
         // Verify we did not try to add any new networks or other interactions with
         // WifiConfigManager.
         verifyNoMoreInteractions(mWifiConfigManager);
+        if (SdkLevel.isAtLeastS()) {
+            verify(mWifiMetrics).addSuggestionExistsForSavedNetwork(
+                    suggestions[0].wns.wifiConfiguration.getKey());
+        }
     }
 
     /**
@@ -524,7 +585,7 @@
         String[] scanSsids = {"test1", "test2"};
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2470, 2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {-67, -76};
         String[] suggestionSsids = {"\"" + scanSsids[0] + "\""};
         int[] securities = {SECURITY_PSK};
@@ -535,23 +596,24 @@
         String[] packageNames = {TEST_PACKAGE};
         boolean[] autojoin = {true};
         boolean[] shareWithUser = {true};
+        int[] priorityGroup = {0};
 
         ScanDetail[] scanDetails =
                 buildScanDetails(scanSsids, bssids, freqs, caps, levels, mClock);
         ExtendedWifiNetworkSuggestion[] suggestions = buildNetworkSuggestions(suggestionSsids,
                 securities, appInteractions, meteredness, priorities, uids,
-                packageNames, autojoin, shareWithUser);
+                packageNames, autojoin, shareWithUser, priorityGroup);
         // Link the scan result with suggestions.
         linkScanDetailsWithNetworkSuggestions(scanDetails, suggestions);
         // setup config manager interactions.
-        setupAddToWifiConfigManager(suggestions[0].wns.wifiConfiguration);
+        setupAddToWifiConfigManager(suggestions[0]);
         // Network was disabled by the user.
         when(mWifiConfigManager.isNetworkTemporarilyDisabledByUser(suggestionSsids[0]))
                 .thenReturn(true);
 
         List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
         mNetworkSuggestionNominator.nominateNetworks(
-                Arrays.asList(scanDetails), null, null, true, false,
+                Arrays.asList(scanDetails), false, false, false,
                 (ScanDetail scanDetail, WifiConfiguration configuration) ->
                         connectableNetworks.add(Pair.create(scanDetail, configuration)));
 
@@ -559,9 +621,8 @@
 
         verify(mWifiConfigManager, times(suggestionSsids.length))
                 .isNetworkTemporarilyDisabledByUser(anyString());
-        // Verify we did try to add any new networks or other interactions with
-        // WifiConfigManager.
-        verifyAddToWifiConfigManager(suggestions[0].wns.wifiConfiguration);
+        verify(mWifiMetrics, never())
+                .incrementNetworkSuggestionMoreThanOneSuggestionForSingleScanResult();
     }
 
     /**
@@ -576,7 +637,7 @@
         String[] scanSsids = {"test1", "test2"};
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2470, 2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {-67, -76};
         String[] suggestionSsids = {"\"" + scanSsids[0] + "\""};
         int[] securities = {SECURITY_PSK};
@@ -587,43 +648,47 @@
         String[] packageNames = {TEST_PACKAGE};
         boolean[] autojoin = {true};
         boolean[] shareWithUser = {true};
+        int[] priorityGroup = {0};
 
         ScanDetail[] scanDetails =
                 buildScanDetails(scanSsids, bssids, freqs, caps, levels, mClock);
         ExtendedWifiNetworkSuggestion[] suggestions = buildNetworkSuggestions(suggestionSsids,
                 securities, appInteractions, meteredness, priorities, uids,
-                packageNames, autojoin, shareWithUser);
+                packageNames, autojoin, shareWithUser, priorityGroup);
         // Link the scan result with suggestions.
         linkScanDetailsWithNetworkSuggestions(scanDetails, suggestions);
-        // setup config manager interactions.
-        suggestions[0].wns.wifiConfiguration.fromWifiNetworkSuggestion = true;
-        suggestions[0].wns.wifiConfiguration.ephemeral = true;
-        setupAddToWifiConfigManager(suggestions[0].wns.wifiConfiguration);
         // Mark the network disabled.
         suggestions[0].wns.wifiConfiguration.getNetworkSelectionStatus().setNetworkSelectionStatus(
                 NETWORK_SELECTION_TEMPORARY_DISABLED);
+        // setup config manager interactions.
+        setupAddToWifiConfigManager(suggestions[0]);
+
         // Existing network matching the credentials.
-        when(mWifiConfigManager.getConfiguredNetwork(suggestions[0].wns.wifiConfiguration.getKey()))
+        when(mWifiConfigManager.getConfiguredNetwork(suggestions[0]
+                .createInternalWifiConfiguration(mWifiCarrierInfoManager).getProfileKey()))
                 .thenReturn(suggestions[0].wns.wifiConfiguration);
 
         List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
         mNetworkSuggestionNominator.nominateNetworks(
-                Arrays.asList(scanDetails), null, null, true, false,
+                Arrays.asList(scanDetails), false, false, false,
                 (ScanDetail scanDetail, WifiConfiguration configuration) -> {
                     connectableNetworks.add(Pair.create(scanDetail, configuration));
                 });
 
         assertTrue(connectableNetworks.isEmpty());
-
-        verify(mWifiConfigManager, times(suggestionSsids.length))
-                .isNetworkTemporarilyDisabledByUser(anyString());
         verify(mWifiConfigManager).getConfiguredNetwork(eq(
-                suggestions[0].wns.wifiConfiguration.getKey()));
+                suggestions[0].wns.wifiConfiguration.getProfileKey()));
         verify(mWifiConfigManager).tryEnableNetwork(eq(
                 suggestions[0].wns.wifiConfiguration.networkId));
+        if (SdkLevel.isAtLeastS()) {
+            verify(mWifiConfigManager).getConfiguredNetwork(eq(
+                    suggestions[0].wns.wifiConfiguration.getKey()));
+        }
         // Verify we did not try to add any new networks or other interactions with
         // WifiConfigManager.
         verifyNoMoreInteractions(mWifiConfigManager);
+        verify(mWifiMetrics, never())
+                .incrementNetworkSuggestionMoreThanOneSuggestionForSingleScanResult();
     }
 
     /**
@@ -638,7 +703,7 @@
         String[] scanSsids = {"test1", "test2"};
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2470, 2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {-67, -76};
         String[] suggestionSsids = {"\"" + scanSsids[0] + "\""};
         int[] securities = {SECURITY_PSK};
@@ -649,30 +714,32 @@
         String[] packageNames = {TEST_PACKAGE};
         boolean[] autojoin = {true};
         boolean[] shareWithUser = {true};
+        int[] priorityGroup = {0};
 
         ScanDetail[] scanDetails =
                 buildScanDetails(scanSsids, bssids, freqs, caps, levels, mClock);
         ExtendedWifiNetworkSuggestion[] suggestions = buildNetworkSuggestions(suggestionSsids,
                 securities, appInteractions, meteredness, priorities, uids,
-                packageNames, autojoin, shareWithUser);
+                packageNames, autojoin, shareWithUser, priorityGroup);
         // Link the scan result with suggestions.
         linkScanDetailsWithNetworkSuggestions(scanDetails, suggestions);
         // setup config manager interactions.
         suggestions[0].wns.wifiConfiguration.fromWifiNetworkSuggestion = true;
         suggestions[0].wns.wifiConfiguration.ephemeral = true;
-        setupAddToWifiConfigManager(suggestions[0].wns.wifiConfiguration);
+        setupAddToWifiConfigManager(suggestions[0]);
         // Mark the network disabled.
         suggestions[0].wns.wifiConfiguration.getNetworkSelectionStatus().setNetworkSelectionStatus(
                 NETWORK_SELECTION_TEMPORARY_DISABLED);
         // Existing network matching the credentials.
-        when(mWifiConfigManager.getConfiguredNetwork(suggestions[0].wns.wifiConfiguration.getKey()))
+        when(mWifiConfigManager.getConfiguredNetwork(suggestions[0]
+                .createInternalWifiConfiguration(mWifiCarrierInfoManager).getProfileKey()))
                 .thenReturn(suggestions[0].wns.wifiConfiguration);
         when(mWifiConfigManager.tryEnableNetwork(suggestions[0].wns.wifiConfiguration.networkId))
                 .thenReturn(true);
 
         List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
         mNetworkSuggestionNominator.nominateNetworks(
-                Arrays.asList(scanDetails), null, null, true, false,
+                Arrays.asList(scanDetails), false, false, false,
                 (ScanDetail scanDetail, WifiConfiguration configuration) -> {
                     connectableNetworks.add(Pair.create(scanDetail, configuration));
                 });
@@ -681,13 +748,20 @@
 
         verify(mWifiConfigManager, times(suggestionSsids.length))
                 .isNetworkTemporarilyDisabledByUser(anyString());
-        verify(mWifiConfigManager).getConfiguredNetwork(eq(
-                suggestions[0].wns.wifiConfiguration.getKey()));
+        verify(mWifiConfigManager).getConfiguredNetwork(eq(suggestions[0]
+                .createInternalWifiConfiguration(mWifiCarrierInfoManager).getProfileKey()));
         verify(mWifiConfigManager).tryEnableNetwork(eq(
                 suggestions[0].wns.wifiConfiguration.networkId));
+        verify(mWifiConfigManager).isNonCarrierMergedNetworkTemporarilyDisabled(any());
+        if (SdkLevel.isAtLeastS()) {
+            verify(mWifiConfigManager).getConfiguredNetwork(eq(suggestions[0]
+                    .createInternalWifiConfiguration(mWifiCarrierInfoManager).getKey()));
+        }
         // Verify we did not try to add any new networks or other interactions with
         // WifiConfigManager.
         verifyNoMoreInteractions(mWifiConfigManager);
+        verify(mWifiMetrics, never())
+                .incrementNetworkSuggestionMoreThanOneSuggestionForSingleScanResult();
     }
 
     /**
@@ -699,7 +773,7 @@
         String[] scanSsids = {"test1", "test2"};
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2470, 2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {-67, -76};
         String[] suggestionSsids = {"\"" + scanSsids[0] + "\""};
         int[] securities = {SECURITY_PSK};
@@ -710,17 +784,21 @@
         String[] packageNames = {TEST_PACKAGE};
         boolean[] autojoin = {true};
         boolean[] shareWithUser = {true};
+        int[] priorityGroup = {0};
 
         ScanDetail[] scanDetails =
                 buildScanDetails(scanSsids, bssids, freqs, caps, levels, mClock);
         ExtendedWifiNetworkSuggestion[] suggestions = buildNetworkSuggestions(suggestionSsids,
                 securities, appInteractions, meteredness, priorities, uids, packageNames,
-                autojoin, shareWithUser);
+                autojoin, shareWithUser, priorityGroup);
         HashSet<ExtendedWifiNetworkSuggestion> matchedExtSuggestions = new HashSet<>();
         matchedExtSuggestions.add(suggestions[0]);
         List<Pair<ScanDetail, WifiConfiguration>> passpointCandidates = new ArrayList<>();
         suggestions[0].wns.wifiConfiguration.FQDN = TEST_FQDN;
-        passpointCandidates.add(Pair.create(scanDetails[0], suggestions[0].wns.wifiConfiguration));
+        suggestions[0].wns.wifiConfiguration.setPasspointUniqueId(PASSPOINT_UNIQUE_ID);
+
+        passpointCandidates.add(Pair.create(scanDetails[0],
+                suggestions[0].createInternalWifiConfiguration(mWifiCarrierInfoManager)));
         when(mPasspointNetworkNominateHelper
                 .getPasspointNetworkCandidates(Arrays.asList(scanDetails), true))
                 .thenReturn(passpointCandidates);
@@ -728,12 +806,67 @@
                 .thenReturn(matchedExtSuggestions);
         List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
         mNetworkSuggestionNominator.nominateNetworks(
-                Arrays.asList(scanDetails), null, null, true, false,
+                Arrays.asList(scanDetails), false, false, false,
                 (ScanDetail scanDetail, WifiConfiguration configuration) -> {
                     connectableNetworks.add(Pair.create(scanDetail, configuration));
                 });
         assertEquals(1, connectableNetworks.size());
         validateConnectableNetworks(connectableNetworks, new String[] {scanSsids[0]});
+        verify(mWifiMetrics, never())
+                .incrementNetworkSuggestionMoreThanOneSuggestionForSingleScanResult();
+    }
+
+    /**
+     * Ensure that we don't nominate the only matching passponit network suggestion when it's not
+     * approved.
+     * Expected connectable Networks: {}
+     */
+    @Test
+    public void testSuggestionPasspointNetworkCandidatesNoApprovedMatches() {
+        String[] scanSsids = {"test1", "test2"};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {2470, 2437};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
+        int[] levels = {-67, -76};
+        String[] suggestionSsids = {"\"" + scanSsids[0] + "\""};
+        int[] securities = {SECURITY_PSK};
+        boolean[] appInteractions = {true};
+        boolean[] meteredness = {true};
+        int[] priorities = {-1};
+        int[] uids = {TEST_UID};
+        String[] packageNames = {TEST_PACKAGE};
+        boolean[] autojoin = {true};
+        boolean[] shareWithUser = {true};
+        int[] priorityGroup = {0};
+
+        ScanDetail[] scanDetails =
+                buildScanDetails(scanSsids, bssids, freqs, caps, levels, mClock);
+        ExtendedWifiNetworkSuggestion[] suggestions = buildNetworkSuggestions(suggestionSsids,
+                securities, appInteractions, meteredness, priorities, uids, packageNames,
+                autojoin, shareWithUser, priorityGroup);
+        HashSet<ExtendedWifiNetworkSuggestion> matchedExtSuggestions = new HashSet<>();
+        matchedExtSuggestions.add(suggestions[0]);
+        List<Pair<ScanDetail, WifiConfiguration>> passpointCandidates = new ArrayList<>();
+        suggestions[0].wns.wifiConfiguration.FQDN = TEST_FQDN;
+        suggestions[0].wns.wifiConfiguration.setPasspointUniqueId(PASSPOINT_UNIQUE_ID);
+
+        passpointCandidates.add(Pair.create(scanDetails[0],
+                suggestions[0].createInternalWifiConfiguration(mWifiCarrierInfoManager)));
+        when(mPasspointNetworkNominateHelper
+                .getPasspointNetworkCandidates(Arrays.asList(scanDetails), true))
+                .thenReturn(passpointCandidates);
+        // As user haven't approved this suggestion, return null
+        when(mWifiNetworkSuggestionsManager.getNetworkSuggestionsForFqdn(TEST_FQDN))
+                .thenReturn(Set.of());
+        List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
+        mNetworkSuggestionNominator.nominateNetworks(
+                Arrays.asList(scanDetails), false, false, false,
+                (ScanDetail scanDetail, WifiConfiguration configuration) -> {
+                    connectableNetworks.add(Pair.create(scanDetail, configuration));
+                });
+        assertTrue(connectableNetworks.isEmpty());
+        verify(mWifiMetrics, never())
+                .incrementNetworkSuggestionMoreThanOneSuggestionForSingleScanResult();
     }
 
     /**
@@ -746,7 +879,7 @@
         String[] scanSsids = {"test1", "test2"};
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2470, 2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {-67, -76};
         String[] suggestionSsids = {"\"" + scanSsids[0] + "\"", "\"" + scanSsids[1] + "\""};
         int[] securities = {SECURITY_PSK, SECURITY_PSK};
@@ -757,40 +890,37 @@
         String[] packageNames = {TEST_PACKAGE, TEST_PACKAGE};
         boolean[] autojoin = {false, false};
         boolean[] shareWithUser = {true, false};
+        int[] priorityGroup = {0, 0};
 
         ScanDetail[] scanDetails =
                 buildScanDetails(scanSsids, bssids, freqs, caps, levels, mClock);
         ExtendedWifiNetworkSuggestion[] suggestions = buildNetworkSuggestions(suggestionSsids,
                 securities, appInteractions, meteredness, priorities, uids,
-                packageNames, autojoin, shareWithUser);
+                packageNames, autojoin, shareWithUser, priorityGroup);
         // Link the scan result with suggestions.
         linkScanDetailsWithNetworkSuggestions(scanDetails, suggestions);
         // setup config manager interactions.
-        setupAddToWifiConfigManager(suggestions[0].wns.wifiConfiguration,
-                suggestions[1].wns.wifiConfiguration);
+        setupAddToWifiConfigManager(suggestions[0], suggestions[1]);
 
         List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
         mNetworkSuggestionNominator.nominateNetworks(
-                Arrays.asList(scanDetails), null, null, true, false,
+                Arrays.asList(scanDetails), false, false, false,
                 (ScanDetail scanDetail, WifiConfiguration configuration) -> {
                     connectableNetworks.add(Pair.create(scanDetail, configuration));
                 });
 
         // Verify no network is nominated.
         assertTrue(connectableNetworks.isEmpty());
-        // Verify we only add network apps shared with user to WifiConfigManager
-        verifyAddToWifiConfigManager(suggestions[0].wns.wifiConfiguration);
-        verify(mWifiConfigManager, never())
-                .addOrUpdateNetwork(argThat(new WifiConfigMatcher(
-                        suggestions[1].wns.wifiConfiguration)), anyInt(), anyString());
+        verify(mWifiMetrics, never())
+                .incrementNetworkSuggestionMoreThanOneSuggestionForSingleScanResult();
     }
 
     @Test
-    public void testSelectNetworkSuggestionForOneMatchSimBasedWithNoSim() {
+    public void testSelectNetworkSuggestionForOneMatchCarrierNetworkWithNoSim() {
         String[] scanSsids = {"test1"};
         String[] bssids = {"6c:f3:7f:ae:8c:f3"};
         int[] freqs = {2470};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {-67};
         String[] suggestionSsids = {"\"" + scanSsids[0] + "\""};
         int[] securities = {SECURITY_EAP};
@@ -801,34 +931,34 @@
         String[] packageNames = {TEST_PACKAGE};
         boolean[] autojoin = {true};
         boolean[] shareWithUser = {true};
+        int[] priorityGroup = {0};
 
         ScanDetail[] scanDetails =
                 buildScanDetails(scanSsids, bssids, freqs, caps, levels, mClock);
         ExtendedWifiNetworkSuggestion[] suggestions = buildNetworkSuggestions(suggestionSsids,
                 securities, appInteractions, meteredness, priorities, uids,
-                packageNames, autojoin, shareWithUser);
-        WifiConfiguration eapSimConfig = suggestions[0].wns.wifiConfiguration;
-        eapSimConfig.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.SIM);
-        eapSimConfig.enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.NONE);
-        eapSimConfig.carrierId = TEST_CARRIER_ID;
-        when(mWifiCarrierInfoManager.getBestMatchSubscriptionId(eapSimConfig))
+                packageNames, autojoin, shareWithUser, priorityGroup);
+        suggestions[0].wns.wifiConfiguration.carrierId = TEST_CARRIER_ID;
+        when(mWifiCarrierInfoManager
+                .getBestMatchSubscriptionId(suggestions[0].wns.wifiConfiguration))
                 .thenReturn(TEST_SUB_ID);
-        when(mWifiCarrierInfoManager.isSimPresent(TEST_SUB_ID)).thenReturn(false);
+        when(mWifiCarrierInfoManager.isSimReady(TEST_SUB_ID)).thenReturn(false);
         // Link the scan result with suggestions.
         linkScanDetailsWithNetworkSuggestions(scanDetails, suggestions);
         // setup config manager interactions.
-        setupAddToWifiConfigManager(suggestions[0].wns.wifiConfiguration);
+        setupAddToWifiConfigManager(suggestions[0]);
 
         List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
         mNetworkSuggestionNominator.nominateNetworks(
-                Arrays.asList(scanDetails), null, null, true, false,
+                Arrays.asList(scanDetails), false, false, false,
                 (ScanDetail scanDetail, WifiConfiguration configuration) -> {
                     connectableNetworks.add(Pair.create(scanDetail, configuration));
                 });
 
         // Verify no network is nominated.
         assertTrue(connectableNetworks.isEmpty());
-        verifyAddToWifiConfigManager(suggestions[0].wns.wifiConfiguration);
+        verify(mWifiMetrics, never())
+                .incrementNetworkSuggestionMoreThanOneSuggestionForSingleScanResult();
     }
 
     @Test
@@ -836,7 +966,7 @@
         String[] scanSsids = {"test1"};
         String[] bssids = {"6c:f3:7f:ae:8c:f3"};
         int[] freqs = {2470};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {-67};
         String[] suggestionSsids = {"\"" + scanSsids[0] + "\""};
         int[] securities = {SECURITY_EAP};
@@ -847,40 +977,42 @@
         String[] packageNames = {TEST_PACKAGE};
         boolean[] autojoin = {true};
         boolean[] shareWithUser = {true};
+        int[] priorityGroup = {0};
 
         ScanDetail[] scanDetails =
                 buildScanDetails(scanSsids, bssids, freqs, caps, levels, mClock);
         ExtendedWifiNetworkSuggestion[] suggestions = buildNetworkSuggestions(suggestionSsids,
                 securities, appInteractions, meteredness, priorities, uids,
-                packageNames, autojoin, shareWithUser);
+                packageNames, autojoin, shareWithUser, priorityGroup);
         WifiConfiguration eapSimConfig = suggestions[0].wns.wifiConfiguration;
         eapSimConfig.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.SIM);
         eapSimConfig.enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.NONE);
         eapSimConfig.carrierId = TEST_CARRIER_ID;
         when(mWifiCarrierInfoManager.getBestMatchSubscriptionId(eapSimConfig))
                 .thenReturn(TEST_SUB_ID);
-        when(mWifiCarrierInfoManager.isSimPresent(TEST_SUB_ID)).thenReturn(true);
+        when(mWifiCarrierInfoManager.isSimReady(TEST_SUB_ID)).thenReturn(true);
         when(mWifiCarrierInfoManager.requiresImsiEncryption(TEST_SUB_ID)).thenReturn(true);
         when(mWifiCarrierInfoManager.isImsiEncryptionInfoAvailable(TEST_SUB_ID)).thenReturn(false);
         // Link the scan result with suggestions.
         linkScanDetailsWithNetworkSuggestions(scanDetails, suggestions);
         // setup config manager interactions.
-        setupAddToWifiConfigManager(suggestions[0].wns.wifiConfiguration);
+        setupAddToWifiConfigManager(suggestions[0]);
 
         List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
         mNetworkSuggestionNominator.nominateNetworks(
-                Arrays.asList(scanDetails), null, null, true, false,
+                Arrays.asList(scanDetails), false, false, false,
                 (ScanDetail scanDetail, WifiConfiguration configuration) -> {
                     connectableNetworks.add(Pair.create(scanDetail, configuration));
                 });
 
         // Verify no network is nominated.
         assertTrue(connectableNetworks.isEmpty());
-        verifyAddToWifiConfigManager(suggestions[0].wns.wifiConfiguration);
+        verify(mWifiMetrics, never())
+                .incrementNetworkSuggestionMoreThanOneSuggestionForSingleScanResult();
     }
 
     /**
-     * Ensure that we nominate the no matching network suggestion.
+     * Ensure that we nominate no matching network suggestion.
      * Because the only matched suggestion is untrusted and untrusted is not allowed
      * Expected connectable Networks: {}
      */
@@ -889,7 +1021,7 @@
         String[] scanSsids = {"test1", "test2"};
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2470, 2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {-67, -76};
         String[] suggestionSsids = {"\"" + scanSsids[0] + "\""};
         int[] securities = {SECURITY_PSK};
@@ -900,24 +1032,27 @@
         String[] packageNames = {TEST_PACKAGE};
         boolean[] autojoin = {true};
         boolean[] shareWithUser = {true};
+        int[] priorityGroup = {0};
 
         ScanDetail[] scanDetails =
                 buildScanDetails(scanSsids, bssids, freqs, caps, levels, mClock);
         ExtendedWifiNetworkSuggestion[] suggestions = buildNetworkSuggestions(suggestionSsids,
                 securities, appInteractions, meteredness, priorities, uids,
-                packageNames, autojoin, shareWithUser);
+                packageNames, autojoin, shareWithUser, priorityGroup);
         suggestions[0].wns.wifiConfiguration.trusted = false;
         // Link the scan result with suggestions.
         linkScanDetailsWithNetworkSuggestions(scanDetails, suggestions);
 
         List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
         mNetworkSuggestionNominator.nominateNetworks(
-                Arrays.asList(scanDetails), null, null, true, false,
+                Arrays.asList(scanDetails), false, false, false,
                 (ScanDetail scanDetail, WifiConfiguration configuration) -> {
                     connectableNetworks.add(Pair.create(scanDetail, configuration));
                 });
 
         assertTrue(connectableNetworks.isEmpty());
+        verify(mWifiMetrics, never())
+                .incrementNetworkSuggestionMoreThanOneSuggestionForSingleScanResult();
     }
 
     /**
@@ -930,7 +1065,7 @@
         String[] scanSsids = {"test1", "test2"};
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2470, 2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {-67, -76};
         String[] suggestionSsids = {"\"" + scanSsids[0] + "\""};
         int[] securities = {SECURITY_PSK};
@@ -941,34 +1076,416 @@
         String[] packageNames = {TEST_PACKAGE};
         boolean[] autojoin = {true};
         boolean[] shareWithUser = {true};
+        int[] priorityGroup = {0};
 
         ScanDetail[] scanDetails =
                 buildScanDetails(scanSsids, bssids, freqs, caps, levels, mClock);
         ExtendedWifiNetworkSuggestion[] suggestions = buildNetworkSuggestions(suggestionSsids,
                 securities, appInteractions, meteredness, priorities, uids,
-                packageNames, autojoin, shareWithUser);
+                packageNames, autojoin, shareWithUser, priorityGroup);
         suggestions[0].wns.wifiConfiguration.trusted = false;
         // Link the scan result with suggestions.
         linkScanDetailsWithNetworkSuggestions(scanDetails, suggestions);
 
-        setupAddToWifiConfigManager(suggestions[0].wns.wifiConfiguration);
+        setupAddToWifiConfigManager(suggestions[0]);
 
         List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
         mNetworkSuggestionNominator.nominateNetworks(
-                Arrays.asList(scanDetails), null, null, true, true,
+                Arrays.asList(scanDetails), true, false, false,
                 (ScanDetail scanDetail, WifiConfiguration configuration) -> {
                     connectableNetworks.add(Pair.create(scanDetail, configuration));
                 });
 
-
         validateConnectableNetworks(connectableNetworks, scanSsids[0]);
-
-        verifyAddToWifiConfigManager(suggestions[0].wns.wifiConfiguration);
+        verify(mWifiMetrics, never())
+                .incrementNetworkSuggestionMoreThanOneSuggestionForSingleScanResult();
     }
 
     /**
+     * Ensure that we nominate no matching network suggestion.
+     * Because the only matched suggestion is oem paid and oem paid is not allowed
+     * Expected connectable Networks: {}
+     */
+    @Test
+    public void testSelectNetworkSuggestionForOneMatchOemPaidNotAllow() {
+        String[] scanSsids = {"test1", "test2"};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {2470, 2437};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
+        int[] levels = {-67, -76};
+        String[] suggestionSsids = {"\"" + scanSsids[0] + "\""};
+        int[] securities = {SECURITY_PSK};
+        boolean[] appInteractions = {true};
+        boolean[] meteredness = {true};
+        int[] priorities = {-1};
+        int[] uids = {TEST_UID};
+        String[] packageNames = {TEST_PACKAGE};
+        boolean[] autojoin = {true};
+        boolean[] shareWithUser = {true};
+        int[] priorityGroup = {0};
+
+        ScanDetail[] scanDetails =
+                buildScanDetails(scanSsids, bssids, freqs, caps, levels, mClock);
+        ExtendedWifiNetworkSuggestion[] suggestions = buildNetworkSuggestions(suggestionSsids,
+                securities, appInteractions, meteredness, priorities, uids,
+                packageNames, autojoin, shareWithUser, priorityGroup);
+        suggestions[0].wns.wifiConfiguration.oemPaid = true;
+        // Link the scan result with suggestions.
+        linkScanDetailsWithNetworkSuggestions(scanDetails, suggestions);
+
+        List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
+        mNetworkSuggestionNominator.nominateNetworks(
+                Arrays.asList(scanDetails), false, false, false,
+                (ScanDetail scanDetail, WifiConfiguration configuration) -> {
+                    connectableNetworks.add(Pair.create(scanDetail, configuration));
+                });
+
+        assertTrue(connectableNetworks.isEmpty());
+        verify(mWifiMetrics, never())
+                .incrementNetworkSuggestionMoreThanOneSuggestionForSingleScanResult();
+    }
+
+
+    /**
+     * Ensure that we nominate the one matching network suggestion.
+     * Because the only matched suggestion is oem paid and oem paid is allowed
+     * Expected connectable Networks: {suggestionSsids[0]}
+     */
+    @Test
+    public void testSelectNetworkSuggestionForOneMatchOemPaidAllow() {
+        String[] scanSsids = {"test1", "test2"};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {2470, 2437};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
+        int[] levels = {-67, -76};
+        String[] suggestionSsids = {"\"" + scanSsids[0] + "\""};
+        int[] securities = {SECURITY_PSK};
+        boolean[] appInteractions = {true};
+        boolean[] meteredness = {true};
+        int[] priorities = {-1};
+        int[] uids = {TEST_UID};
+        String[] packageNames = {TEST_PACKAGE};
+        boolean[] autojoin = {true};
+        boolean[] shareWithUser = {true};
+        int[] priorityGroup = {0};
+
+        ScanDetail[] scanDetails =
+                buildScanDetails(scanSsids, bssids, freqs, caps, levels, mClock);
+        ExtendedWifiNetworkSuggestion[] suggestions = buildNetworkSuggestions(suggestionSsids,
+                securities, appInteractions, meteredness, priorities, uids,
+                packageNames, autojoin, shareWithUser, priorityGroup);
+        suggestions[0].wns.wifiConfiguration.oemPaid = true;
+        // Link the scan result with suggestions.
+        linkScanDetailsWithNetworkSuggestions(scanDetails, suggestions);
+
+        setupAddToWifiConfigManager(suggestions[0]);
+
+        List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
+        mNetworkSuggestionNominator.nominateNetworks(
+                Arrays.asList(scanDetails), false, true, false,
+                (ScanDetail scanDetail, WifiConfiguration configuration) -> {
+                    connectableNetworks.add(Pair.create(scanDetail, configuration));
+                });
+
+        validateConnectableNetworks(connectableNetworks, scanSsids[0]);
+        verify(mWifiMetrics, never())
+                .incrementNetworkSuggestionMoreThanOneSuggestionForSingleScanResult();
+    }
+
+    /**
+     * Ensure that we nominate no matching network suggestion.
+     * Because the only matched suggestion is oem private and oem private is not allowed
+     * Expected connectable Networks: {}
+     */
+    @Test
+    public void testSelectNetworkSuggestionForOneMatchOemPrivateNotAllow() {
+        String[] scanSsids = {"test1", "test2"};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {2470, 2437};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
+        int[] levels = {-67, -76};
+        String[] suggestionSsids = {"\"" + scanSsids[0] + "\""};
+        int[] securities = {SECURITY_PSK};
+        boolean[] appInteractions = {true};
+        boolean[] meteredness = {true};
+        int[] priorities = {-1};
+        int[] uids = {TEST_UID};
+        String[] packageNames = {TEST_PACKAGE};
+        boolean[] autojoin = {true};
+        boolean[] shareWithUser = {true};
+        int[] priorityGroup = {0};
+
+        ScanDetail[] scanDetails =
+                buildScanDetails(scanSsids, bssids, freqs, caps, levels, mClock);
+        ExtendedWifiNetworkSuggestion[] suggestions = buildNetworkSuggestions(suggestionSsids,
+                securities, appInteractions, meteredness, priorities, uids,
+                packageNames, autojoin, shareWithUser, priorityGroup);
+        suggestions[0].wns.wifiConfiguration.oemPrivate = true;
+        // Link the scan result with suggestions.
+        linkScanDetailsWithNetworkSuggestions(scanDetails, suggestions);
+
+        List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
+        mNetworkSuggestionNominator.nominateNetworks(
+                Arrays.asList(scanDetails), false, false, false,
+                (ScanDetail scanDetail, WifiConfiguration configuration) -> {
+                    connectableNetworks.add(Pair.create(scanDetail, configuration));
+                });
+
+        assertTrue(connectableNetworks.isEmpty());
+        verify(mWifiMetrics, never())
+                .incrementNetworkSuggestionMoreThanOneSuggestionForSingleScanResult();
+    }
+
+
+    /**
+     * Ensure that we nominate the one matching network suggestion.
+     * Because the only matched suggestion is oem private and oem private is allowed
+     * Expected connectable Networks: {suggestionSsids[0]}
+     */
+    @Test
+    public void testSelectNetworkSuggestionForOneMatchOemPrivateAllow() {
+        String[] scanSsids = {"test1", "test2"};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {2470, 2437};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
+        int[] levels = {-67, -76};
+        String[] suggestionSsids = {"\"" + scanSsids[0] + "\""};
+        int[] securities = {SECURITY_PSK};
+        boolean[] appInteractions = {true};
+        boolean[] meteredness = {true};
+        int[] priorities = {-1};
+        int[] uids = {TEST_UID};
+        String[] packageNames = {TEST_PACKAGE};
+        boolean[] autojoin = {true};
+        boolean[] shareWithUser = {true};
+        int[] priorityGroup = {0};
+
+        ScanDetail[] scanDetails =
+                buildScanDetails(scanSsids, bssids, freqs, caps, levels, mClock);
+        ExtendedWifiNetworkSuggestion[] suggestions = buildNetworkSuggestions(suggestionSsids,
+                securities, appInteractions, meteredness, priorities, uids,
+                packageNames, autojoin, shareWithUser, priorityGroup);
+        suggestions[0].wns.wifiConfiguration.oemPrivate = true;
+        // Link the scan result with suggestions.
+        linkScanDetailsWithNetworkSuggestions(scanDetails, suggestions);
+
+        setupAddToWifiConfigManager(suggestions[0]);
+
+        List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
+        mNetworkSuggestionNominator.nominateNetworks(
+                Arrays.asList(scanDetails), false, false, true,
+                (ScanDetail scanDetail, WifiConfiguration configuration) -> {
+                    connectableNetworks.add(Pair.create(scanDetail, configuration));
+                });
+
+        validateConnectableNetworks(connectableNetworks, scanSsids[0]);
+        verify(mWifiMetrics, never())
+                .incrementNetworkSuggestionMoreThanOneSuggestionForSingleScanResult();
+    }
+
+    /**
+     * Ensure that we nominate the one matching network suggestion.
+     * Because the only matched suggestion has oem private & oem paid flags and both oem private and
+     * oem paid is allowed.
+     * Ensure that only the oemPaid flag is set in the candidate chosen to connect.
+     * Expected connectable Networks: {suggestionSsids[0]}
+     */
+    @Test
+    public void
+    testSelectNetworkSuggestionForOneOemPaidAndOemPrivateMatchWithOemPaidAndOemPrivateAllow() {
+        String[] scanSsids = {"test1", "test2"};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {2470, 2437};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
+        int[] levels = {-67, -76};
+        String[] suggestionSsids = {"\"" + scanSsids[0] + "\""};
+        int[] securities = {SECURITY_PSK};
+        boolean[] appInteractions = {true};
+        boolean[] meteredness = {true};
+        int[] priorities = {-1};
+        int[] uids = {TEST_UID};
+        String[] packageNames = {TEST_PACKAGE};
+        boolean[] autojoin = {true};
+        boolean[] shareWithUser = {true};
+        int[] priorityGroup = {0};
+
+        ScanDetail[] scanDetails =
+                buildScanDetails(scanSsids, bssids, freqs, caps, levels, mClock);
+        ExtendedWifiNetworkSuggestion[] suggestions = buildNetworkSuggestions(suggestionSsids,
+                securities, appInteractions, meteredness, priorities, uids,
+                packageNames, autojoin, shareWithUser, priorityGroup);
+        suggestions[0].wns.wifiConfiguration.oemPaid = true;
+        suggestions[0].wns.wifiConfiguration.oemPrivate = true;
+        // Link the scan result with suggestions.
+        linkScanDetailsWithNetworkSuggestions(scanDetails, suggestions);
+
+        setupAddToWifiConfigManager(suggestions[0]);
+
+        List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
+        mNetworkSuggestionNominator.nominateNetworks(
+                Arrays.asList(scanDetails), false, true, true,
+                (ScanDetail scanDetail, WifiConfiguration configuration) -> {
+                    connectableNetworks.add(Pair.create(scanDetail, configuration));
+                });
+
+        validateConnectableNetworks(connectableNetworks, scanSsids[0]);
+        verify(mWifiMetrics, never())
+                .incrementNetworkSuggestionMoreThanOneSuggestionForSingleScanResult();
+    }
+
+    /**
+     * Ensure that we nominate no matching network suggestion.
+     * Because the only matched suggestion has oem private & oem paid flags and both oem private and
+     * oem paid is not allowed.
+     * Expected connectable Networks: {}
+     */
+    @Test
+    public void
+    testSelectNetworkSuggestionForOneOemPaidAndOemPrivateMatchWithOemPaidAndOemPrivateNotAllow() {
+        String[] scanSsids = {"test1", "test2"};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {2470, 2437};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
+        int[] levels = {-67, -76};
+        String[] suggestionSsids = {"\"" + scanSsids[0] + "\""};
+        int[] securities = {SECURITY_PSK};
+        boolean[] appInteractions = {true};
+        boolean[] meteredness = {true};
+        int[] priorities = {-1};
+        int[] uids = {TEST_UID};
+        String[] packageNames = {TEST_PACKAGE};
+        boolean[] autojoin = {true};
+        boolean[] shareWithUser = {true};
+        int[] priorityGroup = {0};
+
+        ScanDetail[] scanDetails =
+                buildScanDetails(scanSsids, bssids, freqs, caps, levels, mClock);
+        ExtendedWifiNetworkSuggestion[] suggestions = buildNetworkSuggestions(suggestionSsids,
+                securities, appInteractions, meteredness, priorities, uids,
+                packageNames, autojoin, shareWithUser, priorityGroup);
+        suggestions[0].wns.wifiConfiguration.oemPaid = true;
+        suggestions[0].wns.wifiConfiguration.oemPrivate = true;
+        // Link the scan result with suggestions.
+        linkScanDetailsWithNetworkSuggestions(scanDetails, suggestions);
+
+        setupAddToWifiConfigManager(suggestions[0]);
+
+        List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
+        mNetworkSuggestionNominator.nominateNetworks(
+                Arrays.asList(scanDetails), false, false, false,
+                (ScanDetail scanDetail, WifiConfiguration configuration) -> {
+                    connectableNetworks.add(Pair.create(scanDetail, configuration));
+                });
+
+        assertTrue(connectableNetworks.isEmpty());
+        verify(mWifiMetrics, never())
+                .incrementNetworkSuggestionMoreThanOneSuggestionForSingleScanResult();
+    }
+
+    /**
+     * Ensure that we nominate the one matching network suggestion.
+     * Because the only matched suggestion has oem private & oem paid flags and only oem paid is
+     * allowed.
+     * Ensure that only the oemPaid flag is set in the candidate chosen to connect.
+     * Expected connectable Networks: {suggestionSsids[0]}
+     */
+    @Test
+    public void
+            testSelectNetworkSuggestionForOneOemPaidAndOemPrivateMatchWithOemPaidAllow() {
+        String[] scanSsids = {"test1", "test2"};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {2470, 2437};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
+        int[] levels = {-67, -76};
+        String[] suggestionSsids = {"\"" + scanSsids[0] + "\""};
+        int[] securities = {SECURITY_PSK};
+        boolean[] appInteractions = {true};
+        boolean[] meteredness = {true};
+        int[] priorities = {-1};
+        int[] uids = {TEST_UID};
+        String[] packageNames = {TEST_PACKAGE};
+        boolean[] autojoin = {true};
+        boolean[] shareWithUser = {true};
+        int[] priorityGroup = {0};
+
+        ScanDetail[] scanDetails =
+                buildScanDetails(scanSsids, bssids, freqs, caps, levels, mClock);
+        ExtendedWifiNetworkSuggestion[] suggestions = buildNetworkSuggestions(suggestionSsids,
+                securities, appInteractions, meteredness, priorities, uids,
+                packageNames, autojoin, shareWithUser, priorityGroup);
+        suggestions[0].wns.wifiConfiguration.oemPaid = true;
+        suggestions[0].wns.wifiConfiguration.oemPrivate = true;
+        // Link the scan result with suggestions.
+        linkScanDetailsWithNetworkSuggestions(scanDetails, suggestions);
+
+        setupAddToWifiConfigManager(suggestions[0]);
+
+        List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
+        mNetworkSuggestionNominator.nominateNetworks(
+                Arrays.asList(scanDetails), false, true, false,
+                (ScanDetail scanDetail, WifiConfiguration configuration) -> {
+                    connectableNetworks.add(Pair.create(scanDetail, configuration));
+                });
+
+        validateConnectableNetworks(connectableNetworks, scanSsids[0]);
+        verify(mWifiMetrics, never())
+                .incrementNetworkSuggestionMoreThanOneSuggestionForSingleScanResult();
+    }
+
+    /**
+     * Ensure that we nominate the one matching network suggestion.
+     * Because the only matched suggestion has oem private & oem paid flags and only oem private is
+     * allowed.
+     * Ensure that only the oemPrivate flag is set in the candidate chosen to connect.
+     * Expected connectable Networks: {suggestionSsids[0]}
+     */
+    @Test
+    public void
+            testSelectNetworkSuggestionForOneOemPaidAndOemPrivateMatchWithOemPrivateAllow() {
+        String[] scanSsids = {"test1", "test2"};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {2470, 2437};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
+        int[] levels = {-67, -76};
+        String[] suggestionSsids = {"\"" + scanSsids[0] + "\""};
+        int[] securities = {SECURITY_PSK};
+        boolean[] appInteractions = {true};
+        boolean[] meteredness = {true};
+        int[] priorities = {-1};
+        int[] uids = {TEST_UID};
+        String[] packageNames = {TEST_PACKAGE};
+        boolean[] autojoin = {true};
+        boolean[] shareWithUser = {true};
+        int[] priorityGroup = {0};
+
+        ScanDetail[] scanDetails =
+                buildScanDetails(scanSsids, bssids, freqs, caps, levels, mClock);
+        ExtendedWifiNetworkSuggestion[] suggestions = buildNetworkSuggestions(suggestionSsids,
+                securities, appInteractions, meteredness, priorities, uids,
+                packageNames, autojoin, shareWithUser, priorityGroup);
+        suggestions[0].wns.wifiConfiguration.oemPaid = true;
+        suggestions[0].wns.wifiConfiguration.oemPrivate = true;
+        // Link the scan result with suggestions.
+        linkScanDetailsWithNetworkSuggestions(scanDetails, suggestions);
+
+        setupAddToWifiConfigManager(suggestions[0]);
+
+        List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
+        mNetworkSuggestionNominator.nominateNetworks(
+                Arrays.asList(scanDetails), false, false, true,
+                (ScanDetail scanDetail, WifiConfiguration configuration) -> {
+                    connectableNetworks.add(Pair.create(scanDetail, configuration));
+                });
+
+        validateConnectableNetworks(connectableNetworks, scanSsids[0]);
+        verify(mWifiMetrics, never())
+                .incrementNetworkSuggestionMoreThanOneSuggestionForSingleScanResult();
+    }
+
+
+    /**
      * Ensure that we nominate the no matching network suggestion.
-     * Because the only matched suggestion is untrusted and untrusted is not allowed
+     * Because the only matched suggestion is metered carrier network from Non data SIM
      * Expected connectable Networks: {}
      */
     @Test
@@ -976,7 +1493,7 @@
         String[] scanSsids = {"test1", "test2"};
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2470, 2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {-67, -76};
         String[] suggestionSsids = {"\"" + scanSsids[0] + "\""};
         int[] securities = {SECURITY_PSK};
@@ -987,58 +1504,95 @@
         String[] packageNames = {TEST_PACKAGE};
         boolean[] autojoin = {true};
         boolean[] shareWithUser = {true};
+        int[] priorityGroup = {0};
 
         ScanDetail[] scanDetails =
                 buildScanDetails(scanSsids, bssids, freqs, caps, levels, mClock);
         ExtendedWifiNetworkSuggestion[] suggestions = buildNetworkSuggestions(suggestionSsids,
                 securities, appInteractions, meteredness, priorities, uids,
-                packageNames, autojoin, shareWithUser);
+                packageNames, autojoin, shareWithUser, priorityGroup);
+        suggestions[0].wns.wifiConfiguration.carrierId = TEST_CARRIER_ID;
         suggestions[0].wns.wifiConfiguration.meteredHint = true;
         when(mWifiCarrierInfoManager.isCarrierNetworkFromNonDefaultDataSim(any())).thenReturn(true);
         // Link the scan result with suggestions.
         linkScanDetailsWithNetworkSuggestions(scanDetails, suggestions);
+        setupAddToWifiConfigManager(suggestions[0]);
 
         List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
         mNetworkSuggestionNominator.nominateNetworks(
-                Arrays.asList(scanDetails), null, null, true, false,
+                Arrays.asList(scanDetails), false, false, false,
                 (ScanDetail scanDetail, WifiConfiguration configuration) -> {
                     connectableNetworks.add(Pair.create(scanDetail, configuration));
                 });
 
         assertTrue(connectableNetworks.isEmpty());
+        verify(mWifiMetrics, never())
+                .incrementNetworkSuggestionMoreThanOneSuggestionForSingleScanResult();
     }
 
-    private void setupAddToWifiConfigManager(WifiConfiguration...candidates) {
+    /**
+     * Ensure that we nominate the no matching network suggestion.
+     * Because the only matched suggestion is carrier network which offloading is disabled.
+     * Expected connectable Networks: {}
+     */
+    @Test
+    public void testSelectNetworkSuggestionForOneMatchCarrierOffloadDisabled() {
+        String[] scanSsids = {"test1", "test2"};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {2470, 2437};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
+        int[] levels = {-67, -76};
+        String[] suggestionSsids = {"\"" + scanSsids[0] + "\""};
+        int[] securities = {SECURITY_PSK};
+        boolean[] appInteractions = {true};
+        boolean[] meteredness = {true};
+        int[] priorities = {-1};
+        int[] uids = {TEST_UID};
+        String[] packageNames = {TEST_PACKAGE};
+        boolean[] autojoin = {true};
+        boolean[] shareWithUser = {true};
+        int[] priorityGroup = {0};
+
+        ScanDetail[] scanDetails =
+                buildScanDetails(scanSsids, bssids, freqs, caps, levels, mClock);
+        ExtendedWifiNetworkSuggestion[] suggestions = buildNetworkSuggestions(suggestionSsids,
+                securities, appInteractions, meteredness, priorities, uids,
+                packageNames, autojoin, shareWithUser, priorityGroup);
+        suggestions[0].wns.wifiConfiguration.carrierId = TEST_CARRIER_ID;
+        when(mWifiCarrierInfoManager.isCarrierNetworkOffloadEnabled(anyInt(), anyBoolean()))
+                .thenReturn(false);
+        // Link the scan result with suggestions.
+        linkScanDetailsWithNetworkSuggestions(scanDetails, suggestions);
+        setupAddToWifiConfigManager(suggestions[0]);
+
+        List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
+        mNetworkSuggestionNominator.nominateNetworks(
+                Arrays.asList(scanDetails), false, false, false,
+                (ScanDetail scanDetail, WifiConfiguration configuration) -> {
+                    connectableNetworks.add(Pair.create(scanDetail, configuration));
+                });
+
+        assertTrue(connectableNetworks.isEmpty());
+        verify(mWifiMetrics, never())
+                .incrementNetworkSuggestionMoreThanOneSuggestionForSingleScanResult();
+    }
+
+    private void setupAddToWifiConfigManager(ExtendedWifiNetworkSuggestion...candidates) {
         for (int i = 0; i < candidates.length; i++) {
-            WifiConfiguration candidate = candidates[i];
-            // setup & verify the WifiConfigmanager interactions for adding/enabling the network.
-            when(mWifiConfigManager.addOrUpdateNetwork(
-                    argThat(new WifiConfigMatcher(candidate)), anyInt(), anyString()))
-                    .thenReturn(new NetworkUpdateResult(TEST_NETWORK_ID + i));
-            when(mWifiConfigManager.updateNetworkSelectionStatus(eq(TEST_NETWORK_ID + i), anyInt()))
-                    .thenReturn(true);
-            candidate.networkId = TEST_NETWORK_ID + i;
-            when(mWifiConfigManager.getConfiguredNetwork(TEST_NETWORK_ID + i))
+            WifiConfiguration candidate = candidates[i].createInternalWifiConfiguration(
+                    mWifiCarrierInfoManager);
+            WifiConfiguration.NetworkSelectionStatus status =
+                    mock(WifiConfiguration.NetworkSelectionStatus.class);
+            when(status.isNetworkEnabled()).thenReturn(true);
+            candidate.setNetworkSelectionStatus(status);
+            when(mWifiConfigManager.getConfiguredNetwork(candidate.getProfileKey()))
                     .thenReturn(candidate);
         }
     }
 
-    class WifiConfigMatcher implements ArgumentMatcher<WifiConfiguration> {
-        private final WifiConfiguration mConfig;
-
-        WifiConfigMatcher(WifiConfiguration config) {
-            assertNotNull(config);
-            mConfig = config;
-        }
-
-        @Override
-        public boolean matches(WifiConfiguration otherConfig) {
-            if (otherConfig == null) return false;
-            return mConfig.getKey().equals(otherConfig.getKey());
-        }
-    }
-
-    private void verifyAddToWifiConfigManager(WifiConfiguration...candidates) {
+    private void verifyAddToWifiConfigManager(
+            boolean trustedSet, boolean oemPaidSet, boolean oemPrivateSet,
+            WifiConfiguration...candidates) {
         // check for any saved networks.
         verify(mWifiConfigManager, atLeast(candidates.length)).getConfiguredNetwork(anyString());
 
@@ -1058,6 +1612,9 @@
             assertNotNull(addedWifiConfiguration);
             assertTrue(addedWifiConfiguration.ephemeral);
             assertTrue(addedWifiConfiguration.fromWifiNetworkSuggestion);
+            assertEquals(trustedSet, addedWifiConfiguration.trusted);
+            assertEquals(oemPaidSet, addedWifiConfiguration.oemPaid);
+            assertEquals(oemPrivateSet, addedWifiConfiguration.oemPrivate);
         }
 
         verify(mWifiConfigManager, times(candidates.length)).updateNetworkSelectionStatus(
@@ -1100,7 +1657,7 @@
     private ExtendedWifiNetworkSuggestion[] buildNetworkSuggestions(
             String[] ssids, int[] securities, boolean[] appInteractions, boolean[] meteredness,
             int[] priorities, int[] uids, String[] packageNames, boolean[] autojoin,
-            boolean[] shareWithUser) {
+            boolean[] shareWithUser, int[] priorityGroup) {
         WifiConfiguration[] configs = buildWifiConfigurations(ssids, securities);
         ExtendedWifiNetworkSuggestion[] suggestions =
                 new ExtendedWifiNetworkSuggestion[configs.length];
@@ -1116,7 +1673,7 @@
             PerAppInfo perAppInfo = new PerAppInfo(uids[i], packageNames[i], null);
             WifiNetworkSuggestion suggestion =
                     new WifiNetworkSuggestion(configs[i], null, appInteractions[i], false,
-                            shareWithUser[i], true);
+                            shareWithUser[i], true, priorityGroup[i]);
             suggestions[i] = new ExtendedWifiNetworkSuggestion(suggestion, perAppInfo, autojoin[i]);
         }
         return suggestions;
@@ -1155,14 +1712,12 @@
             for (int i = minLength; i < scanDetails.length; i++) {
                 ScanDetail scanDetail = scanDetails[i];
                 when(mWifiNetworkSuggestionsManager.getNetworkSuggestionsForScanDetail(
-                        eq(scanDetail))).thenReturn(null);
+                        eq(scanDetail))).thenReturn(Set.of());
             }
         } else if (suggestions.length > scanDetails.length) {
             // All the additional suggestions match the last scan detail.
-            HashSet<ExtendedWifiNetworkSuggestion> matchingSuggestions = new HashSet<>();
-            for (int i = minLength; i < suggestions.length; i++) {
-                matchingSuggestions.add(suggestions[i]);
-            }
+            HashSet<ExtendedWifiNetworkSuggestion> matchingSuggestions = new HashSet<>(
+                    Arrays.asList(suggestions).subList(minLength - 1, suggestions.length));
             ScanDetail lastScanDetail = scanDetails[minLength - 1];
             when(mWifiNetworkSuggestionsManager.getNetworkSuggestionsForScanDetail(
                     eq(lastScanDetail))).thenReturn((matchingSuggestions));
@@ -1171,15 +1726,15 @@
 
     private void validateConnectableNetworks(List<Pair<ScanDetail, WifiConfiguration>> actual,
                                              String...expectedSsids) {
-        Set<String> expectedSsidSet = new HashSet<>(Arrays.asList(expectedSsids));
-        assertEquals(expectedSsidSet.size(), actual.size());
+        assertEquals(expectedSsids.length, actual.size());
+        Set<String> actualSsids = new HashSet<>();
 
         for (Pair<ScanDetail, WifiConfiguration> candidate : actual) {
             // check if the scan detail matches the wificonfiguration.
             assertEquals("\"" + candidate.first.getSSID() + "\"", candidate.second.SSID);
-            // check if both match one of the expected ssid's.
-            assertTrue(expectedSsidSet.remove(candidate.first.getSSID()));
+            actualSsids.add(candidate.first.getSSID());
         }
-        assertTrue(expectedSsidSet.isEmpty());
+        // Verify actual matches the expected.
+        assertTrue(actualSsids.containsAll(Arrays.asList(expectedSsids)));
     }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/NetworkSuggestionStoreDataTest.java b/service/tests/wifitests/src/com/android/server/wifi/NetworkSuggestionStoreDataTest.java
index ad31533..de777a1 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/NetworkSuggestionStoreDataTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/NetworkSuggestionStoreDataTest.java
@@ -16,8 +16,12 @@
 
 package com.android.server.wifi;
 
-import static org.junit.Assert.*;
-import static org.mockito.Mockito.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.net.wifi.EAPConstants;
 import android.net.wifi.WifiConfiguration;
@@ -60,6 +64,10 @@
     private static final String TEST_FQDN = "FQDN";
     private static final String TEST_FRIENDLY_NAME = "test_friendly_name";
     private static final String TEST_REALM = "realm.test.com";
+    private static final String USER_CONNECT_CHOICE = "SomeNetworkProfileId";
+    private static final int TEST_RSSI = -50;
+    private static final int TEST_PRIORITY_GROUP =
+            WifiNetworkSuggestionsManager.DEFAULT_PRIORITY_GROUP;
     private static final String TEST_PRE_R_STORE_FORMAT_XML_STRING =
             "<NetworkSuggestionPerApp>\n"
                     + "<string name=\"SuggestorPackageName\">%1$s</string>\n"
@@ -291,16 +299,21 @@
         configuration.enterpriseConfig =
                 WifiConfigurationTestUtil.createPEAPWifiEnterpriseConfigWithGTCPhase2();
         WifiNetworkSuggestion networkSuggestion =
-                new WifiNetworkSuggestion(configuration, null, false, false, true, true);
+                new WifiNetworkSuggestion(configuration, null, false, false, true, true,
+                        TEST_PRIORITY_GROUP);
         appInfo.hasUserApproved = false;
-        appInfo.extNetworkSuggestions.add(
-                ExtendedWifiNetworkSuggestion.fromWns(networkSuggestion, appInfo, true));
+        ExtendedWifiNetworkSuggestion ewns = ExtendedWifiNetworkSuggestion
+                .fromWns(networkSuggestion, appInfo, true);
+        ewns.connectChoice = USER_CONNECT_CHOICE;
+        ewns.connectChoiceRssi = TEST_RSSI;
+        appInfo.extNetworkSuggestions.put(ewns.hashCode(), ewns);
         networkSuggestionsMap.put(TEST_PACKAGE_NAME_1, appInfo);
 
         Map<String, PerAppInfo> deserializedPerAppInfoMap =
                 assertSerializeDeserialize(networkSuggestionsMap);
         ExtendedWifiNetworkSuggestion deserializedSuggestion =
-                deserializedPerAppInfoMap.get(TEST_PACKAGE_NAME_1).extNetworkSuggestions.stream()
+                deserializedPerAppInfoMap.get(TEST_PACKAGE_NAME_1).extNetworkSuggestions.values()
+                        .stream()
                         .findAny()
                         .orElse(null);
 
@@ -309,6 +322,8 @@
         WifiConfigurationTestUtil.assertWifiEnterpriseConfigEqualForConfigStore(
                 configuration.enterpriseConfig,
                 deserializedSuggestion.wns.wifiConfiguration.enterpriseConfig);
+        assertEquals(USER_CONNECT_CHOICE, deserializedSuggestion.connectChoice);
+        assertEquals(TEST_RSSI, deserializedSuggestion.connectChoiceRssi);
     }
 
     /**
@@ -320,18 +335,22 @@
 
         PerAppInfo appInfo1 = new PerAppInfo(TEST_UID_1, TEST_PACKAGE_NAME_1, TEST_FEATURE_ID);
         WifiNetworkSuggestion networkSuggestion1 = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                TEST_PRIORITY_GROUP);
         appInfo1.hasUserApproved = false;
-        appInfo1.extNetworkSuggestions.add(
-                ExtendedWifiNetworkSuggestion.fromWns(networkSuggestion1, appInfo1, true));
+        ExtendedWifiNetworkSuggestion ewns =
+                ExtendedWifiNetworkSuggestion.fromWns(networkSuggestion1, appInfo1, true);
+        appInfo1.extNetworkSuggestions.put(ewns.hashCode(), ewns);
         networkSuggestionsMap.put(TEST_PACKAGE_NAME_1, appInfo1);
 
         PerAppInfo appInfo2 = new PerAppInfo(TEST_UID_2, TEST_PACKAGE_NAME_2, TEST_FEATURE_ID);
         WifiNetworkSuggestion networkSuggestion2 = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true);
+                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true,
+                TEST_PRIORITY_GROUP);
         appInfo2.hasUserApproved = true;
-        appInfo2.extNetworkSuggestions.add(
-                ExtendedWifiNetworkSuggestion.fromWns(networkSuggestion2, appInfo2, true));
+        ExtendedWifiNetworkSuggestion ewns2 =
+                ExtendedWifiNetworkSuggestion.fromWns(networkSuggestion2, appInfo2, true);
+        appInfo2.extNetworkSuggestions.put(ewns2.hashCode(), ewns2);
         networkSuggestionsMap.put(TEST_PACKAGE_NAME_2, appInfo2);
 
         assertSerializeDeserialize(networkSuggestionsMap);
@@ -346,26 +365,34 @@
 
         PerAppInfo appInfo1 = new PerAppInfo(TEST_UID_1, TEST_PACKAGE_NAME_1, TEST_FEATURE_ID);
         WifiNetworkSuggestion networkSuggestion1 = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, true, true, true);
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, true, true, true,
+                TEST_PRIORITY_GROUP);
         WifiNetworkSuggestion networkSuggestion2 = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true);
+                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true,
+                TEST_PRIORITY_GROUP);
         appInfo1.hasUserApproved = true;
-        appInfo1.extNetworkSuggestions.add(
-                ExtendedWifiNetworkSuggestion.fromWns(networkSuggestion1, appInfo1, true));
-        appInfo1.extNetworkSuggestions.add(
-                ExtendedWifiNetworkSuggestion.fromWns(networkSuggestion2, appInfo1, true));
+        ExtendedWifiNetworkSuggestion ewns1 =
+                ExtendedWifiNetworkSuggestion.fromWns(networkSuggestion1, appInfo1, true);
+        appInfo1.extNetworkSuggestions.put(ewns1.hashCode(), ewns1);
+        ExtendedWifiNetworkSuggestion ewns2 =
+                ExtendedWifiNetworkSuggestion.fromWns(networkSuggestion2, appInfo1, true);
+        appInfo1.extNetworkSuggestions.put(ewns2.hashCode(), ewns2);
         networkSuggestionsMap.put(TEST_PACKAGE_NAME_1, appInfo1);
 
         PerAppInfo appInfo2 = new PerAppInfo(TEST_UID_2, TEST_PACKAGE_NAME_2, TEST_FEATURE_ID);
         WifiNetworkSuggestion networkSuggestion3 = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true);
+                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true,
+                TEST_PRIORITY_GROUP);
         WifiNetworkSuggestion networkSuggestion4 = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, true, true, true);
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, true, true, true,
+                TEST_PRIORITY_GROUP);
         appInfo2.hasUserApproved = true;
-        appInfo2.extNetworkSuggestions.add(
-                ExtendedWifiNetworkSuggestion.fromWns(networkSuggestion3, appInfo2, true));
-        appInfo2.extNetworkSuggestions.add(
-                ExtendedWifiNetworkSuggestion.fromWns(networkSuggestion4, appInfo2, true));
+        ExtendedWifiNetworkSuggestion ewns3 =
+                ExtendedWifiNetworkSuggestion.fromWns(networkSuggestion3, appInfo2, true);
+        appInfo2.extNetworkSuggestions.put(ewns3.hashCode(), ewns3);
+        ExtendedWifiNetworkSuggestion ewns4 =
+                ExtendedWifiNetworkSuggestion.fromWns(networkSuggestion4, appInfo2, true);
+        appInfo2.extNetworkSuggestions.put(ewns4.hashCode(), ewns4);
         networkSuggestionsMap.put(TEST_PACKAGE_NAME_2, appInfo2);
 
         assertSerializeDeserialize(networkSuggestionsMap);
@@ -453,14 +480,16 @@
                 createTestConfigWithUserCredential(TEST_FQDN, TEST_FRIENDLY_NAME));
         WifiNetworkSuggestion networkSuggestion = builder.build();
         appInfo.hasUserApproved = false;
-        appInfo.extNetworkSuggestions.add(
-                ExtendedWifiNetworkSuggestion.fromWns(networkSuggestion, appInfo, true));
+        ExtendedWifiNetworkSuggestion ewns =
+                ExtendedWifiNetworkSuggestion.fromWns(networkSuggestion, appInfo, true);
+        appInfo.extNetworkSuggestions.put(ewns.hashCode(), ewns);
         networkSuggestionsMap.put(TEST_PACKAGE_NAME_1, appInfo);
 
         Map<String, PerAppInfo> deserializedPerAppInfoMap =
                 assertSerializeDeserialize(networkSuggestionsMap);
         ExtendedWifiNetworkSuggestion deserializedSuggestion =
-                deserializedPerAppInfoMap.get(TEST_PACKAGE_NAME_1).extNetworkSuggestions.stream()
+                deserializedPerAppInfoMap.get(TEST_PACKAGE_NAME_1).extNetworkSuggestions.values()
+                        .stream()
                         .findAny()
                         .orElse(null);
         assertEquals(networkSuggestion, deserializedSuggestion.wns);
diff --git a/service/tests/wifitests/src/com/android/server/wifi/NonCarrierMergedNetworksStatusTrackerTest.java b/service/tests/wifitests/src/com/android/server/wifi/NonCarrierMergedNetworksStatusTrackerTest.java
new file mode 100644
index 0000000..0dbc218
--- /dev/null
+++ b/service/tests/wifitests/src/com/android/server/wifi/NonCarrierMergedNetworksStatusTrackerTest.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import android.net.wifi.WifiConfiguration;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+@SmallTest
+public class NonCarrierMergedNetworksStatusTrackerTest extends WifiBaseTest {
+    private static final long TEST_MIN_DISABLE_ALL_DURATION = 1000;
+    private static final long TEST_MAX_DISABLE_ALL_DURATION = 100000;
+    private static final long TEST_MIN_DURATION_NOT_SEEN_IN_SCANS = 3000;
+    private static final int TEST_SUBSCRIPTION_ID = 1;
+    private static final int INVALID_SUBSCRIPTION_ID = -1;
+    private NonCarrierMergedNetworksStatusTracker mNonCarrierMergedNetworksStatusTracker;
+    private WifiConfiguration mTestNonCarrierMergedNetwork;
+    @Mock private Clock mClock;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mNonCarrierMergedNetworksStatusTracker = new NonCarrierMergedNetworksStatusTracker(mClock);
+        mTestNonCarrierMergedNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+    }
+
+    // Need this because MissingCounterTimerLockList uses getWallClockMillis()
+    private void setClockTime(long millis) {
+        when(mClock.getWallClockMillis()).thenReturn(millis);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(millis);
+    }
+
+    /**
+     * Verify that in the default state, networks are not disabled.
+     */
+    @Test
+    public void testNetworkIsEnabledByDefault() {
+        assertFalse(mNonCarrierMergedNetworksStatusTracker.isNetworkDisabled(
+                mTestNonCarrierMergedNetwork));
+    }
+
+    /**
+     * Verify that after disableAllNonCarrierMergedNetworks is called, a non-carrier-merged network
+     * that's not explicitly disabled by "temporarilyDisableNetwork" gets re-enabled when the min
+     * disable duration passes.
+     */
+    @Test
+    public void testDisableAllNonCarrierMergedNetworks() {
+        // start disabling non-carrier-merged networks.
+        mNonCarrierMergedNetworksStatusTracker.disableAllNonCarrierMergedNetworks(
+                TEST_SUBSCRIPTION_ID, TEST_MIN_DISABLE_ALL_DURATION, TEST_MAX_DISABLE_ALL_DURATION);
+
+        // verify the non-carrier-merged network is disabled before the disable duration is over.
+        setClockTime(TEST_MIN_DISABLE_ALL_DURATION - 1);
+        assertTrue(mNonCarrierMergedNetworksStatusTracker
+                .isNetworkDisabled(mTestNonCarrierMergedNetwork));
+
+        // verify the non-carrier-merged network is no longer disabled after the disable duration.
+        setClockTime(TEST_MIN_DISABLE_ALL_DURATION);
+        assertFalse(mNonCarrierMergedNetworksStatusTracker
+                .isNetworkDisabled(mTestNonCarrierMergedNetwork));
+    }
+
+    /**
+     * Verify that after disableAllNonCarrierMergedNetworks is called, a carrier-merged network
+     * with matching subscription ID is still enabled.
+     */
+    @Test
+    public void testCarrierMergedNetworkWithMatchingSubscriptionIdIsEnabled() {
+        mNonCarrierMergedNetworksStatusTracker.disableAllNonCarrierMergedNetworks(
+                TEST_SUBSCRIPTION_ID, TEST_MIN_DISABLE_ALL_DURATION, TEST_MAX_DISABLE_ALL_DURATION);
+        assertTrue(mNonCarrierMergedNetworksStatusTracker.isNetworkDisabled(
+                mTestNonCarrierMergedNetwork));
+
+        // verify a carrier-merged network with non-matching subscription ID is disabled.
+        WifiConfiguration testConfig = WifiConfigurationTestUtil.createOpenNetwork();
+        testConfig.carrierMerged = true;
+        testConfig.subscriptionId = INVALID_SUBSCRIPTION_ID;
+        assertTrue(mNonCarrierMergedNetworksStatusTracker.isNetworkDisabled(testConfig));
+
+        // verify a non-carrier-merged network with matching subscription ID is disabled.
+        testConfig.carrierMerged = false;
+        testConfig.subscriptionId = TEST_SUBSCRIPTION_ID;
+        assertTrue(mNonCarrierMergedNetworksStatusTracker.isNetworkDisabled(testConfig));
+
+        // verify a carrier-merged network with matching subscription ID is not disabled.
+        testConfig.carrierMerged = true;
+        testConfig.subscriptionId = TEST_SUBSCRIPTION_ID;
+        assertFalse(mNonCarrierMergedNetworksStatusTracker.isNetworkDisabled(testConfig));
+    }
+
+    /**
+     * Verify that after disableAllNonCarrierMergedNetworks is called, a non-carrier-merged network
+     * is disabled until clear() is called.
+     */
+    @Test
+    public void testClearWillUndoDisableAllNonCarrierMergedNetworks() {
+        // first verify that without doing anything, non-carrier-merged networks are enabled.
+        WifiConfiguration testConfig = WifiConfigurationTestUtil.createOpenNetwork();
+        // start disabling non-carrier-merged networks.
+        mNonCarrierMergedNetworksStatusTracker.disableAllNonCarrierMergedNetworks(
+                TEST_SUBSCRIPTION_ID, TEST_MIN_DISABLE_ALL_DURATION, TEST_MAX_DISABLE_ALL_DURATION);
+
+        // verify the non-carrier-merged network is disabled.
+        assertTrue(mNonCarrierMergedNetworksStatusTracker.isNetworkDisabled(testConfig));
+
+        // verify the non-carrier-merged network is no longer disabled after "clear" is called.
+        mNonCarrierMergedNetworksStatusTracker.clear();
+        assertFalse(mNonCarrierMergedNetworksStatusTracker.isNetworkDisabled(testConfig));
+    }
+
+    /**
+     * Verify that when a specific network is disabled through temporarilyDisableNetwork. It is
+     * re-enabled after it's not seen in scan results for the specified duration.
+     */
+    @Test
+    public void testTemporarilyDisableNetwork() {
+        mNonCarrierMergedNetworksStatusTracker.disableAllNonCarrierMergedNetworks(
+                TEST_SUBSCRIPTION_ID, TEST_MIN_DISABLE_ALL_DURATION, TEST_MAX_DISABLE_ALL_DURATION);
+        mNonCarrierMergedNetworksStatusTracker.temporarilyDisableNetwork(
+                mTestNonCarrierMergedNetwork, TEST_MIN_DURATION_NOT_SEEN_IN_SCANS,
+                TEST_MAX_DISABLE_ALL_DURATION);
+        assertTrue(mNonCarrierMergedNetworksStatusTracker.isNetworkDisabled(
+                mTestNonCarrierMergedNetwork));
+
+        mNonCarrierMergedNetworksStatusTracker.update(Collections.EMPTY_SET);
+        assertTrue(mNonCarrierMergedNetworksStatusTracker.isNetworkDisabled(
+                mTestNonCarrierMergedNetwork));
+
+        // verify that after the network is gone from scan results for long enough, the
+        // network is no longer disabled.
+        setClockTime(TEST_MIN_DURATION_NOT_SEEN_IN_SCANS + 1);
+        assertFalse(mNonCarrierMergedNetworksStatusTracker.isNetworkDisabled(
+                mTestNonCarrierMergedNetwork));
+    }
+
+    /**
+     * Verify that a non-carrier merged network disabled with temporarilyDisableNetwork gets
+     * re-enabled after the max disable duration passes even though the network is still seen in
+     * scan results.
+     */
+    @Test
+    public void testTemporarilyDisableNetworkWithMaxDisableDuration() {
+        mNonCarrierMergedNetworksStatusTracker.disableAllNonCarrierMergedNetworks(
+                TEST_SUBSCRIPTION_ID, TEST_MIN_DISABLE_ALL_DURATION, TEST_MAX_DISABLE_ALL_DURATION);
+        mNonCarrierMergedNetworksStatusTracker.temporarilyDisableNetwork(
+                mTestNonCarrierMergedNetwork, TEST_MIN_DURATION_NOT_SEEN_IN_SCANS,
+                TEST_MAX_DISABLE_ALL_DURATION);
+        assertTrue(mNonCarrierMergedNetworksStatusTracker.isNetworkDisabled(
+                mTestNonCarrierMergedNetwork));
+
+        // verify the network is still disabled after the network is seen in scan results 1ms
+        // before the max disable duration timeout.
+        setClockTime(TEST_MAX_DISABLE_ALL_DURATION - 1);
+        Set<String> networks = new HashSet<>();
+        networks.add(mTestNonCarrierMergedNetwork.SSID);
+        mNonCarrierMergedNetworksStatusTracker.update(networks);
+        assertTrue(mNonCarrierMergedNetworksStatusTracker.isNetworkDisabled(
+                mTestNonCarrierMergedNetwork));
+
+        // verify the network is re-enabled after the max disable duration.
+        setClockTime(TEST_MAX_DISABLE_ALL_DURATION);
+        assertFalse(mNonCarrierMergedNetworksStatusTracker.isNetworkDisabled(
+                mTestNonCarrierMergedNetwork));
+    }
+
+    /**
+     * Verify that a network disabled by temporarilyDisableNetwork is re-enabled when clear() is
+     * called.
+     */
+    @Test
+    public void testClearResetsTemporarilyDisableNetwork() {
+        mNonCarrierMergedNetworksStatusTracker.disableAllNonCarrierMergedNetworks(
+                TEST_SUBSCRIPTION_ID, TEST_MIN_DISABLE_ALL_DURATION, TEST_MAX_DISABLE_ALL_DURATION);
+        mNonCarrierMergedNetworksStatusTracker.temporarilyDisableNetwork(
+                mTestNonCarrierMergedNetwork, TEST_MIN_DURATION_NOT_SEEN_IN_SCANS,
+                TEST_MAX_DISABLE_ALL_DURATION);
+        assertTrue(mNonCarrierMergedNetworksStatusTracker.isNetworkDisabled(
+                mTestNonCarrierMergedNetwork));
+
+        mNonCarrierMergedNetworksStatusTracker.clear();
+        assertFalse(mNonCarrierMergedNetworksStatusTracker.isNetworkDisabled(
+                mTestNonCarrierMergedNetwork));
+    }
+
+    /**
+     * Verify that when a temporarily disabled network shows up in scan results, we reset the
+     * counter needed to re-enable it.
+     */
+    @Test
+    public void testNetworkAppearingWillResetCounter() {
+        mNonCarrierMergedNetworksStatusTracker.disableAllNonCarrierMergedNetworks(
+                TEST_SUBSCRIPTION_ID, TEST_MIN_DISABLE_ALL_DURATION, TEST_MAX_DISABLE_ALL_DURATION);
+        mNonCarrierMergedNetworksStatusTracker.temporarilyDisableNetwork(
+                mTestNonCarrierMergedNetwork, TEST_MIN_DURATION_NOT_SEEN_IN_SCANS,
+                TEST_MAX_DISABLE_ALL_DURATION);
+        assertTrue(mNonCarrierMergedNetworksStatusTracker.isNetworkDisabled(
+                mTestNonCarrierMergedNetwork));
+        mNonCarrierMergedNetworksStatusTracker.update(Collections.EMPTY_SET);
+
+        // simulate the network appearing from scan results after some time.
+        long networkAppearTime = 1200;
+        setClockTime(networkAppearTime);
+        Set<String> networks = new HashSet<>();
+        networks.add(mTestNonCarrierMergedNetwork.SSID);
+        mNonCarrierMergedNetworksStatusTracker.update(networks);
+
+        // simulate the network dissapearing from scan results again.
+        mNonCarrierMergedNetworksStatusTracker.update(Collections.EMPTY_SET);
+
+        // verify that the timer was reset properly
+        setClockTime(networkAppearTime + TEST_MIN_DURATION_NOT_SEEN_IN_SCANS);
+        assertTrue(mNonCarrierMergedNetworksStatusTracker.isNetworkDisabled(
+                mTestNonCarrierMergedNetwork));
+        setClockTime(networkAppearTime + TEST_MIN_DURATION_NOT_SEEN_IN_SCANS + 1);
+        assertFalse(mNonCarrierMergedNetworksStatusTracker.isNetworkDisabled(
+                mTestNonCarrierMergedNetwork));
+    }
+}
diff --git a/service/tests/wifitests/src/com/android/server/wifi/OemWifiNetworkFactoryTest.java b/service/tests/wifitests/src/com/android/server/wifi/OemWifiNetworkFactoryTest.java
new file mode 100644
index 0000000..63e811d
--- /dev/null
+++ b/service/tests/wifitests/src/com/android/server/wifi/OemWifiNetworkFactoryTest.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static org.junit.Assert.*;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.Mockito.*;
+
+import android.content.Context;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.os.WorkSource;
+import android.os.test.TestLooper;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.modules.utils.build.SdkLevel;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.OemWifiNetworkFactory}.
+ */
+@SmallTest
+public class OemWifiNetworkFactoryTest extends WifiBaseTest {
+    private static final int TEST_UID = 4556;
+    private static final String TEST_PACKAGE_NAME = "com.test";
+    private static final WorkSource TEST_WORKSOURCE = new WorkSource(TEST_UID, TEST_PACKAGE_NAME);
+
+    @Mock WifiConnectivityManager mWifiConnectivityManager;
+    @Mock Context mContext;
+    NetworkCapabilities mNetworkCapabilities;
+    TestLooper mLooper;
+    NetworkRequest mNetworkRequest;
+
+    private OemWifiNetworkFactory mOemWifiNetworkFactory;
+
+    /**
+     * Setup the mocks.
+     */
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        mLooper = new TestLooper();
+        mNetworkCapabilities = new NetworkCapabilities();
+        mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
+        mNetworkCapabilities.setRequestorUid(TEST_UID);
+        mNetworkCapabilities.setRequestorPackageName(TEST_PACKAGE_NAME);
+
+        mOemWifiNetworkFactory = new OemWifiNetworkFactory(
+                mLooper.getLooper(), mContext,
+                mNetworkCapabilities, mWifiConnectivityManager);
+
+        mNetworkRequest = new NetworkRequest.Builder()
+                .setCapabilities(mNetworkCapabilities)
+                .build();
+    }
+
+    /**
+     * Called after each test
+     */
+    @After
+    public void cleanup() {
+        validateMockitoUsage();
+    }
+
+    /**
+     * Validates handling of needNetworkFor.
+     */
+    @Test
+    public void testOemPaidHandleNetworkRequest() {
+        mNetworkRequest.networkCapabilities.addCapability(
+                NetworkCapabilities.NET_CAPABILITY_OEM_PAID);
+        assertFalse(mOemWifiNetworkFactory.hasConnectionRequests());
+        mOemWifiNetworkFactory.needNetworkFor(mNetworkRequest);
+
+        // First network request should turn on auto-join.
+        verify(mWifiConnectivityManager).setOemPaidConnectionAllowed(true, TEST_WORKSOURCE);
+        assertTrue(mOemWifiNetworkFactory.hasConnectionRequests());
+
+        // Subsequent ones should do nothing.
+        mOemWifiNetworkFactory.needNetworkFor(mNetworkRequest);
+        verifyNoMoreInteractions(mWifiConnectivityManager);
+    }
+
+    /**
+     * Validates handling of releaseNetwork.
+     */
+    @Test
+    public void testHandleOemPaidNetworkRelease() {
+        mNetworkRequest.networkCapabilities.addCapability(
+                NetworkCapabilities.NET_CAPABILITY_OEM_PAID);
+        // Release network without a corresponding request should be ignored.
+        mOemWifiNetworkFactory.releaseNetworkFor(mNetworkRequest);
+        assertFalse(mOemWifiNetworkFactory.hasConnectionRequests());
+
+        // Now request & then release the network request
+        mOemWifiNetworkFactory.needNetworkFor(mNetworkRequest);
+        assertTrue(mOemWifiNetworkFactory.hasConnectionRequests());
+        verify(mWifiConnectivityManager).setOemPaidConnectionAllowed(true, TEST_WORKSOURCE);
+
+        mOemWifiNetworkFactory.releaseNetworkFor(mNetworkRequest);
+        assertFalse(mOemWifiNetworkFactory.hasConnectionRequests());
+        verify(mWifiConnectivityManager).setOemPaidConnectionAllowed(false, null);
+    }
+
+    /**
+     * Validates handling of needNetworkFor.
+     */
+    @Test
+    public void testOemPrivateHandleNetworkRequest() {
+        mNetworkRequest.networkCapabilities.addCapability(
+                NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE);
+        assertFalse(mOemWifiNetworkFactory.hasConnectionRequests());
+        mOemWifiNetworkFactory.needNetworkFor(mNetworkRequest);
+
+        // First network request should turn on auto-join.
+        verify(mWifiConnectivityManager).setOemPrivateConnectionAllowed(true, TEST_WORKSOURCE);
+        assertTrue(mOemWifiNetworkFactory.hasConnectionRequests());
+
+        // Subsequent ones should do nothing.
+        mOemWifiNetworkFactory.needNetworkFor(mNetworkRequest);
+        verifyNoMoreInteractions(mWifiConnectivityManager);
+    }
+
+    /**
+     * Validates handling of releaseNetwork.
+     */
+    @Test
+    public void testHandleOemPrivateNetworkRelease() {
+        mNetworkRequest.networkCapabilities.addCapability(
+                NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE);
+        // Release network without a corresponding request should be ignored.
+        mOemWifiNetworkFactory.releaseNetworkFor(mNetworkRequest);
+        assertFalse(mOemWifiNetworkFactory.hasConnectionRequests());
+
+        // Now request & then release the network request
+        mOemWifiNetworkFactory.needNetworkFor(mNetworkRequest);
+        assertTrue(mOemWifiNetworkFactory.hasConnectionRequests());
+        verify(mWifiConnectivityManager).setOemPrivateConnectionAllowed(true, TEST_WORKSOURCE);
+
+        mOemWifiNetworkFactory.releaseNetworkFor(mNetworkRequest);
+        assertFalse(mOemWifiNetworkFactory.hasConnectionRequests());
+        verify(mWifiConnectivityManager).setOemPrivateConnectionAllowed(false, null);
+    }
+
+    /**
+     * Validates handling of releaseNetwork.
+     */
+    @Test
+    public void testHandleOemPaidAndOemPrivateNetworkRequestAndRelease() {
+        mNetworkRequest.networkCapabilities.addCapability(
+                NetworkCapabilities.NET_CAPABILITY_OEM_PAID);
+        mNetworkRequest.networkCapabilities.addCapability(
+                NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE);
+        // Release network without a corresponding request should be ignored.
+        mOemWifiNetworkFactory.releaseNetworkFor(mNetworkRequest);
+        assertFalse(mOemWifiNetworkFactory.hasConnectionRequests());
+
+        // Now request & then release the network request
+        mOemWifiNetworkFactory.needNetworkFor(mNetworkRequest);
+        assertTrue(mOemWifiNetworkFactory.hasConnectionRequests());
+        verify(mWifiConnectivityManager).setOemPaidConnectionAllowed(true, TEST_WORKSOURCE);
+        verify(mWifiConnectivityManager).setOemPrivateConnectionAllowed(true, TEST_WORKSOURCE);
+
+        mOemWifiNetworkFactory.releaseNetworkFor(mNetworkRequest);
+        assertFalse(mOemWifiNetworkFactory.hasConnectionRequests());
+        verify(mWifiConnectivityManager).setOemPaidConnectionAllowed(false, null);
+        verify(mWifiConnectivityManager).setOemPrivateConnectionAllowed(false, null);
+    }
+
+}
diff --git a/service/tests/wifitests/src/com/android/server/wifi/OpenNetworkNotifierTest.java b/service/tests/wifitests/src/com/android/server/wifi/OpenNetworkNotifierTest.java
index f09e434..6e56957 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/OpenNetworkNotifierTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/OpenNetworkNotifierTest.java
@@ -28,22 +28,20 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.NotificationManager;
+import android.app.test.MockAnswerUtil;
 import android.content.BroadcastReceiver;
-import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.net.Uri;
-import android.net.wifi.IActionListener;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiManager;
-import android.os.Binder;
 import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
@@ -56,6 +54,7 @@
 
 import com.android.server.wifi.proto.nano.WifiMetricsProto;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.ConnectToNetworkNotificationAndActionCount;
+import com.android.server.wifi.util.ActionListenerWrapper;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -78,30 +77,30 @@
     private static final String OPEN_NET_NOTIFIER_TAG = OpenNetworkNotifier.TAG;
     private static final int TEST_NETWORK_ID = 42;
 
-    @Mock private Context mContext;
+    @Mock private WifiContext mContext;
     @Mock private Resources mResources;
     @Mock private FrameworkFacade mFrameworkFacade;
     @Mock private WifiMetrics mWifiMetrics;
     @Mock private Clock mClock;
     @Mock private WifiConfigStore mWifiConfigStore;
     @Mock private WifiConfigManager mWifiConfigManager;
-    @Mock private NotificationManager mNotificationManager;
+    @Mock private WifiNotificationManager mWifiNotificationManager;
     @Mock private ClientModeImpl mClientModeImpl;
     @Mock private ConnectToNetworkNotificationBuilder mNotificationBuilder;
     @Mock private UserManager mUserManager;
+    @Mock private ConnectHelper mConnectHelper;
+    @Mock private MakeBeforeBreakManager mMakeBeforeBreakManager;
     private OpenNetworkNotifier mNotificationController;
     private TestLooper mLooper;
     private BroadcastReceiver mBroadcastReceiver;
     private ContentObserver mContentObserver;
-    private ScanResult mDummyNetwork;
+    private ScanResult mTestNetwork;
     private List<ScanDetail> mOpenNetworks;
 
     /** Initialize objects before each test run. */
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        when(mContext.getSystemService(Context.NOTIFICATION_SERVICE))
-                .thenReturn(mNotificationManager);
         when(mFrameworkFacade.getIntegerSetting(mContext,
                 Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1)).thenReturn(1);
         when(mFrameworkFacade.getIntegerSetting(mContext,
@@ -110,17 +109,18 @@
         when(mContext.getSystemService(UserManager.class))
                 .thenReturn(mUserManager);
         when(mContext.getResources()).thenReturn(mResources);
-        mDummyNetwork = new ScanResult();
-        mDummyNetwork.SSID = TEST_SSID_1;
-        mDummyNetwork.capabilities = "[ESS]";
-        mDummyNetwork.level = MIN_RSSI_LEVEL;
+        mTestNetwork = new ScanResult();
+        mTestNetwork.SSID = TEST_SSID_1;
+        mTestNetwork.capabilities = "[ESS]";
+        mTestNetwork.level = MIN_RSSI_LEVEL;
         mOpenNetworks = new ArrayList<>();
-        mOpenNetworks.add(new ScanDetail(mDummyNetwork, null /* networkDetail */));
+        mOpenNetworks.add(new ScanDetail(mTestNetwork, null /* networkDetail */));
 
         mLooper = new TestLooper();
         mNotificationController = new OpenNetworkNotifier(
                 mContext, mLooper.getLooper(), mFrameworkFacade, mClock, mWifiMetrics,
-                mWifiConfigManager, mWifiConfigStore, mClientModeImpl, mNotificationBuilder);
+                mWifiConfigManager, mWifiConfigStore, mConnectHelper, mNotificationBuilder,
+                mMakeBeforeBreakManager, mWifiNotificationManager);
         ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
         verify(mContext).registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
@@ -133,6 +133,11 @@
         mNotificationController.handleScreenStateChanged(true);
         when(mWifiConfigManager.addOrUpdateNetwork(any(), anyInt()))
                 .thenReturn(new NetworkUpdateResult(TEST_NETWORK_ID));
+        doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+            public void answer(Runnable onStoppedListener) throws Throwable {
+                onStoppedListener.run();
+            }
+        }).when(mMakeBeforeBreakManager).stopAllSecondaryTransientClientModeManagers(any());
     }
 
     /**
@@ -165,10 +170,10 @@
         mNotificationController.handleScanResults(mOpenNetworks);
 
         verify(mNotificationBuilder).createConnectToAvailableNetworkNotification(
-                OPEN_NET_NOTIFIER_TAG, mDummyNetwork);
+                OPEN_NET_NOTIFIER_TAG, mTestNetwork);
         verify(mWifiMetrics).incrementConnectToNetworkNotification(OPEN_NET_NOTIFIER_TAG,
                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
-        verify(mNotificationManager).notify(anyInt(), any());
+        verify(mWifiNotificationManager).notify(anyInt(), any());
     }
 
     /**
@@ -178,7 +183,7 @@
     public void handleScanResults_emptyList_notificationNotDisplayed() {
         mNotificationController.handleScanResults(new ArrayList<>());
 
-        verify(mNotificationManager, never()).notify(anyInt(), any());
+        verify(mWifiNotificationManager, never()).notify(anyInt(), any());
     }
 
     /**
@@ -191,7 +196,7 @@
         mContentObserver.onChange(false);
         mNotificationController.handleScanResults(new ArrayList<>());
 
-        verify(mNotificationManager, never()).notify(anyInt(), any());
+        verify(mWifiNotificationManager, never()).notify(anyInt(), any());
     }
 
     /**
@@ -203,14 +208,14 @@
         mNotificationController.handleScanResults(mOpenNetworks);
 
         verify(mNotificationBuilder).createConnectToAvailableNetworkNotification(
-                OPEN_NET_NOTIFIER_TAG, mDummyNetwork);
+                OPEN_NET_NOTIFIER_TAG, mTestNetwork);
         verify(mWifiMetrics).incrementConnectToNetworkNotification(OPEN_NET_NOTIFIER_TAG,
                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
-        verify(mNotificationManager).notify(anyInt(), any());
+        verify(mWifiNotificationManager).notify(anyInt(), any());
 
         mNotificationController.handleScanResults(new ArrayList<>());
 
-        verify(mNotificationManager).cancel(anyInt());
+        verify(mWifiNotificationManager).cancel(anyInt());
     }
 
     /**
@@ -222,15 +227,15 @@
         mNotificationController.handleScanResults(mOpenNetworks);
 
         verify(mNotificationBuilder).createConnectToAvailableNetworkNotification(
-                OPEN_NET_NOTIFIER_TAG, mDummyNetwork);
+                OPEN_NET_NOTIFIER_TAG, mTestNetwork);
         verify(mWifiMetrics).incrementConnectToNetworkNotification(OPEN_NET_NOTIFIER_TAG,
                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
-        verify(mNotificationManager).notify(anyInt(), any());
+        verify(mWifiNotificationManager).notify(anyInt(), any());
 
         mOpenNetworks.clear();
         mNotificationController.handleScanResults(mOpenNetworks);
 
-        verify(mNotificationManager).cancel(anyInt());
+        verify(mWifiNotificationManager).cancel(anyInt());
     }
 
     /**
@@ -242,15 +247,15 @@
         mNotificationController.handleScanResults(mOpenNetworks);
 
         verify(mNotificationBuilder).createConnectToAvailableNetworkNotification(
-                OPEN_NET_NOTIFIER_TAG, mDummyNetwork);
+                OPEN_NET_NOTIFIER_TAG, mTestNetwork);
         verify(mWifiMetrics).incrementConnectToNetworkNotification(OPEN_NET_NOTIFIER_TAG,
                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
-        verify(mNotificationManager).notify(anyInt(), any());
+        verify(mWifiNotificationManager).notify(anyInt(), any());
 
         mNotificationController.handleScreenStateChanged(false);
         mNotificationController.handleScanResults(new ArrayList<>());
 
-        verify(mNotificationManager).cancel(anyInt());
+        verify(mWifiNotificationManager).cancel(anyInt());
     }
 
     /**
@@ -262,14 +267,14 @@
         mNotificationController.handleScanResults(mOpenNetworks);
 
         verify(mNotificationBuilder).createConnectToAvailableNetworkNotification(
-                OPEN_NET_NOTIFIER_TAG, mDummyNetwork);
+                OPEN_NET_NOTIFIER_TAG, mTestNetwork);
         verify(mWifiMetrics).incrementConnectToNetworkNotification(OPEN_NET_NOTIFIER_TAG,
                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
-        verify(mNotificationManager).notify(anyInt(), any());
+        verify(mWifiNotificationManager).notify(anyInt(), any());
 
         mNotificationController.clearPendingNotification(true);
 
-        verify(mNotificationManager).cancel(anyInt());
+        verify(mWifiNotificationManager).cancel(anyInt());
     }
 
     /**
@@ -280,7 +285,7 @@
     public void clearPendingNotification_doesNotClearNotificationIfNoneShowing() {
         mNotificationController.clearPendingNotification(true);
 
-        verify(mNotificationManager, never()).cancel(anyInt());
+        verify(mWifiNotificationManager, never()).cancel(anyInt());
     }
 
     /**
@@ -292,7 +297,7 @@
         mNotificationController.handleScreenStateChanged(false);
         mNotificationController.handleScanResults(mOpenNetworks);
 
-        verify(mNotificationManager, never()).notify(anyInt(), any());
+        verify(mWifiNotificationManager, never()).notify(anyInt(), any());
     }
 
     /**
@@ -304,15 +309,15 @@
         mNotificationController.handleScanResults(mOpenNetworks);
 
         verify(mNotificationBuilder).createConnectToAvailableNetworkNotification(
-                OPEN_NET_NOTIFIER_TAG, mDummyNetwork);
+                OPEN_NET_NOTIFIER_TAG, mTestNetwork);
         verify(mWifiMetrics).incrementConnectToNetworkNotification(OPEN_NET_NOTIFIER_TAG,
                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
-        verify(mNotificationManager).notify(anyInt(), any());
+        verify(mWifiNotificationManager).notify(anyInt(), any());
 
         ScanResult newNetwork = new ScanResult();
         newNetwork.SSID = TEST_SSID_2;
-        mDummyNetwork.capabilities = "[ESS]";
-        mDummyNetwork.level = MIN_RSSI_LEVEL + 1;
+        mTestNetwork.capabilities = "[ESS]";
+        mTestNetwork.level = MIN_RSSI_LEVEL + 1;
         mOpenNetworks.add(new ScanDetail(newNetwork, null /* networkDetail */));
 
         mNotificationController.handleScreenStateChanged(false);
@@ -322,7 +327,7 @@
         verify(mNotificationBuilder).createConnectToAvailableNetworkNotification(
                 OPEN_NET_NOTIFIER_TAG, newNetwork);
         verify(mWifiMetrics).incrementNumNetworkRecommendationUpdates(OPEN_NET_NOTIFIER_TAG);
-        verify(mNotificationManager, times(2)).notify(anyInt(), any());
+        verify(mWifiNotificationManager, times(2)).notify(anyInt(), any());
     }
 
     /**
@@ -334,19 +339,19 @@
         mNotificationController.handleScanResults(mOpenNetworks);
 
         verify(mNotificationBuilder).createConnectToAvailableNetworkNotification(
-                OPEN_NET_NOTIFIER_TAG, mDummyNetwork);
+                OPEN_NET_NOTIFIER_TAG, mTestNetwork);
         verify(mWifiMetrics).incrementConnectToNetworkNotification(OPEN_NET_NOTIFIER_TAG,
                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
-        verify(mNotificationManager).notify(anyInt(), any());
+        verify(mWifiNotificationManager).notify(anyInt(), any());
 
         mNotificationController.clearPendingNotification(false);
 
-        verify(mNotificationManager).cancel(anyInt());
+        verify(mWifiNotificationManager).cancel(anyInt());
 
         mNotificationController.handleScanResults(mOpenNetworks);
 
         // no new notification posted
-        verify(mNotificationManager).notify(anyInt(), any());
+        verify(mWifiNotificationManager).notify(anyInt(), any());
     }
 
     /**
@@ -358,20 +363,20 @@
         mNotificationController.handleScanResults(mOpenNetworks);
 
         verify(mNotificationBuilder).createConnectToAvailableNetworkNotification(
-                OPEN_NET_NOTIFIER_TAG, mDummyNetwork);
+                OPEN_NET_NOTIFIER_TAG, mTestNetwork);
         verify(mWifiMetrics).incrementConnectToNetworkNotification(OPEN_NET_NOTIFIER_TAG,
                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
-        verify(mNotificationManager).notify(anyInt(), any());
+        verify(mWifiNotificationManager).notify(anyInt(), any());
 
         mNotificationController.clearPendingNotification(true);
 
         mNotificationController.handleScanResults(mOpenNetworks);
 
         verify(mNotificationBuilder, times(2)).createConnectToAvailableNetworkNotification(
-                OPEN_NET_NOTIFIER_TAG, mDummyNetwork);
+                OPEN_NET_NOTIFIER_TAG, mTestNetwork);
         verify(mWifiMetrics, times(2)).incrementConnectToNetworkNotification(OPEN_NET_NOTIFIER_TAG,
                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
-        verify(mNotificationManager, times(2)).notify(anyInt(), any());
+        verify(mWifiNotificationManager, times(2)).notify(anyInt(), any());
     }
 
     private Intent createIntent(String action) {
@@ -387,10 +392,10 @@
         mNotificationController.handleScanResults(mOpenNetworks);
 
         verify(mNotificationBuilder).createConnectToAvailableNetworkNotification(
-                OPEN_NET_NOTIFIER_TAG, mDummyNetwork);
+                OPEN_NET_NOTIFIER_TAG, mTestNetwork);
         verify(mWifiMetrics).incrementConnectToNetworkNotification(OPEN_NET_NOTIFIER_TAG,
                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
-        verify(mNotificationManager).notify(anyInt(), any());
+        verify(mWifiNotificationManager).notify(anyInt(), any());
 
         mBroadcastReceiver.onReceive(mContext, createIntent(ACTION_USER_DISMISSED_NOTIFICATION));
 
@@ -413,10 +418,10 @@
         mNotificationController.handleScanResults(mOpenNetworks);
 
         verify(mNotificationBuilder).createConnectToAvailableNetworkNotification(
-                OPEN_NET_NOTIFIER_TAG, mDummyNetwork);
+                OPEN_NET_NOTIFIER_TAG, mTestNetwork);
         verify(mWifiMetrics).incrementConnectToNetworkNotification(OPEN_NET_NOTIFIER_TAG,
                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
-        verify(mNotificationManager).notify(anyInt(), any());
+        verify(mWifiNotificationManager).notify(anyInt(), any());
 
         mBroadcastReceiver.onReceive(mContext, createIntent(ACTION_CONNECT_TO_NETWORK));
 
@@ -436,10 +441,10 @@
         mNotificationController.handleScanResults(mOpenNetworks);
 
         verify(mNotificationBuilder).createConnectToAvailableNetworkNotification(
-                OPEN_NET_NOTIFIER_TAG, mDummyNetwork);
+                OPEN_NET_NOTIFIER_TAG, mTestNetwork);
         verify(mWifiMetrics).incrementConnectToNetworkNotification(OPEN_NET_NOTIFIER_TAG,
                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
-        verify(mNotificationManager).notify(anyInt(), any());
+        verify(mWifiNotificationManager).notify(anyInt(), any());
 
         mNotificationController.clearPendingNotification(false);
 
@@ -449,10 +454,10 @@
         mNotificationController.handleScanResults(mOpenNetworks);
 
         verify(mNotificationBuilder, times(2)).createConnectToAvailableNetworkNotification(
-                OPEN_NET_NOTIFIER_TAG, mDummyNetwork);
+                OPEN_NET_NOTIFIER_TAG, mTestNetwork);
         verify(mWifiMetrics, times(2)).incrementConnectToNetworkNotification(OPEN_NET_NOTIFIER_TAG,
                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
-        verify(mNotificationManager, times(2)).notify(anyInt(), any());
+        verify(mWifiNotificationManager, times(2)).notify(anyInt(), any());
     }
 
     /** Verifies that {@link UserManager#DISALLOW_CONFIG_WIFI} disables the feature. */
@@ -464,7 +469,7 @@
 
         mNotificationController.handleScanResults(mOpenNetworks);
 
-        verify(mNotificationManager, never()).notify(anyInt(), any());
+        verify(mWifiNotificationManager, never()).notify(anyInt(), any());
     }
 
     /** Verifies that {@link UserManager#DISALLOW_CONFIG_WIFI} clears the showing notification. */
@@ -473,10 +478,10 @@
         mNotificationController.handleScanResults(mOpenNetworks);
 
         verify(mNotificationBuilder).createConnectToAvailableNetworkNotification(
-                OPEN_NET_NOTIFIER_TAG, mDummyNetwork);
+                OPEN_NET_NOTIFIER_TAG, mTestNetwork);
         verify(mWifiMetrics).incrementConnectToNetworkNotification(OPEN_NET_NOTIFIER_TAG,
                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
-        verify(mNotificationManager).notify(anyInt(), any());
+        verify(mWifiNotificationManager).notify(anyInt(), any());
 
         when(mUserManager.hasUserRestrictionForUser(UserManager.DISALLOW_CONFIG_WIFI,
               UserHandle.CURRENT))
@@ -484,7 +489,7 @@
 
         mNotificationController.handleScanResults(mOpenNetworks);
 
-        verify(mNotificationManager).cancel(anyInt());
+        verify(mWifiNotificationManager).cancel(anyInt());
     }
 
     /**
@@ -494,8 +499,7 @@
     @Test
     public void actionConnectToNetwork_notificationNotShowing_doesNothing() {
         mBroadcastReceiver.onReceive(mContext, createIntent(ACTION_CONNECT_TO_NETWORK));
-        verify(mClientModeImpl, never()).connect(any(), anyInt(), any(Binder.class),
-                any(IActionListener.class), anyInt(), eq(Process.SYSTEM_UID));
+        verify(mConnectHelper, never()).connectToNetwork(any(), any(), anyInt());
     }
 
     /**
@@ -508,24 +512,24 @@
 
         // Initial Notification
         verify(mNotificationBuilder).createConnectToAvailableNetworkNotification(
-                OPEN_NET_NOTIFIER_TAG, mDummyNetwork);
+                OPEN_NET_NOTIFIER_TAG, mTestNetwork);
         verify(mWifiMetrics).incrementConnectToNetworkNotification(OPEN_NET_NOTIFIER_TAG,
                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
-        verify(mNotificationManager).notify(anyInt(), any());
+        verify(mWifiNotificationManager).notify(anyInt(), any());
 
         mBroadcastReceiver.onReceive(mContext, createIntent(ACTION_CONNECT_TO_NETWORK));
 
-        verify(mClientModeImpl).connect(eq(null), eq(TEST_NETWORK_ID), any(Binder.class),
-                any(IActionListener.class), anyInt(), eq(Process.SYSTEM_UID));
+        verify(mConnectHelper).connectToNetwork(eq(new NetworkUpdateResult(TEST_NETWORK_ID)),
+                any(ActionListenerWrapper.class), eq(Process.SYSTEM_UID));
         // Connecting Notification
         verify(mNotificationBuilder).createNetworkConnectingNotification(OPEN_NET_NOTIFIER_TAG,
-                mDummyNetwork);
+                mTestNetwork);
         verify(mWifiMetrics).incrementConnectToNetworkNotification(OPEN_NET_NOTIFIER_TAG,
                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_CONNECTING_TO_NETWORK);
         verify(mWifiMetrics).incrementConnectToNetworkNotificationAction(OPEN_NET_NOTIFIER_TAG,
                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK,
                 ConnectToNetworkNotificationAndActionCount.ACTION_CONNECT_TO_NETWORK);
-        verify(mNotificationManager, times(2)).notify(anyInt(), any());
+        verify(mWifiNotificationManager, times(2)).notify(anyInt(), any());
     }
 
     /**
@@ -538,10 +542,10 @@
 
         // Initial Notification
         verify(mNotificationBuilder).createConnectToAvailableNetworkNotification(
-                OPEN_NET_NOTIFIER_TAG, mDummyNetwork);
+                OPEN_NET_NOTIFIER_TAG, mTestNetwork);
         verify(mWifiMetrics).incrementConnectToNetworkNotification(OPEN_NET_NOTIFIER_TAG,
                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
-        verify(mNotificationManager).notify(anyInt(), any());
+        verify(mWifiNotificationManager).notify(anyInt(), any());
 
         mBroadcastReceiver.onReceive(mContext, createIntent(ACTION_PICK_WIFI_NETWORK));
 
@@ -561,7 +565,7 @@
     public void networkConnectionSuccess_wasNotInConnectingFlow_doesNothing() {
         mNotificationController.handleWifiConnected(TEST_SSID_1);
 
-        verify(mNotificationManager, never()).notify(anyInt(), any());
+        verify(mWifiNotificationManager, never()).notify(anyInt(), any());
         verify(mWifiMetrics, never()).incrementConnectToNetworkNotification(
                 OPEN_NET_NOTIFIER_TAG,
                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_CONNECTED_TO_NETWORK);
@@ -577,14 +581,14 @@
 
         // Initial Notification
         verify(mNotificationBuilder).createConnectToAvailableNetworkNotification(
-                OPEN_NET_NOTIFIER_TAG, mDummyNetwork);
+                OPEN_NET_NOTIFIER_TAG, mTestNetwork);
         verify(mWifiMetrics).incrementConnectToNetworkNotification(OPEN_NET_NOTIFIER_TAG,
                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
-        verify(mNotificationManager).notify(anyInt(), any());
+        verify(mWifiNotificationManager).notify(anyInt(), any());
 
         mNotificationController.handleWifiConnected(TEST_SSID_1);
 
-        verify(mNotificationManager).cancel(anyInt());
+        verify(mWifiNotificationManager).cancel(anyInt());
     }
 
     /**
@@ -597,31 +601,31 @@
 
         // Initial Notification
         verify(mNotificationBuilder).createConnectToAvailableNetworkNotification(
-                OPEN_NET_NOTIFIER_TAG, mDummyNetwork);
+                OPEN_NET_NOTIFIER_TAG, mTestNetwork);
         verify(mWifiMetrics).incrementConnectToNetworkNotification(OPEN_NET_NOTIFIER_TAG,
                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
-        verify(mNotificationManager).notify(anyInt(), any());
+        verify(mWifiNotificationManager).notify(anyInt(), any());
 
         mBroadcastReceiver.onReceive(mContext, createIntent(ACTION_CONNECT_TO_NETWORK));
 
         // Connecting Notification
         verify(mNotificationBuilder).createNetworkConnectingNotification(OPEN_NET_NOTIFIER_TAG,
-                mDummyNetwork);
+                mTestNetwork);
         verify(mWifiMetrics).incrementConnectToNetworkNotification(OPEN_NET_NOTIFIER_TAG,
                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_CONNECTING_TO_NETWORK);
         verify(mWifiMetrics).incrementConnectToNetworkNotificationAction(OPEN_NET_NOTIFIER_TAG,
                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK,
                 ConnectToNetworkNotificationAndActionCount.ACTION_CONNECT_TO_NETWORK);
-        verify(mNotificationManager, times(2)).notify(anyInt(), any());
+        verify(mWifiNotificationManager, times(2)).notify(anyInt(), any());
 
         mNotificationController.handleWifiConnected(TEST_SSID_1);
 
         // Connected Notification
         verify(mNotificationBuilder).createNetworkConnectedNotification(OPEN_NET_NOTIFIER_TAG,
-                mDummyNetwork);
+                mTestNetwork);
         verify(mWifiMetrics).incrementConnectToNetworkNotification(OPEN_NET_NOTIFIER_TAG,
                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_CONNECTED_TO_NETWORK);
-        verify(mNotificationManager, times(3)).notify(anyInt(), any());
+        verify(mWifiNotificationManager, times(3)).notify(anyInt(), any());
     }
 
     /**
@@ -632,7 +636,7 @@
     public void networkConnectionFailure_wasNotInConnectingFlow_doesNothing() {
         mNotificationController.handleConnectionFailure();
 
-        verify(mNotificationManager, never()).notify(anyInt(), any());
+        verify(mWifiNotificationManager, never()).notify(anyInt(), any());
         verify(mWifiMetrics, never()).incrementConnectToNetworkNotification(OPEN_NET_NOTIFIER_TAG,
                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_FAILED_TO_CONNECT);
     }
@@ -647,22 +651,22 @@
 
         // Initial Notification
         verify(mNotificationBuilder).createConnectToAvailableNetworkNotification(
-                OPEN_NET_NOTIFIER_TAG, mDummyNetwork);
+                OPEN_NET_NOTIFIER_TAG, mTestNetwork);
         verify(mWifiMetrics).incrementConnectToNetworkNotification(OPEN_NET_NOTIFIER_TAG,
                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
-        verify(mNotificationManager).notify(anyInt(), any());
+        verify(mWifiNotificationManager).notify(anyInt(), any());
 
         mBroadcastReceiver.onReceive(mContext, createIntent(ACTION_CONNECT_TO_NETWORK));
 
         // Connecting Notification
         verify(mNotificationBuilder).createNetworkConnectingNotification(OPEN_NET_NOTIFIER_TAG,
-                mDummyNetwork);
+                mTestNetwork);
         verify(mWifiMetrics).incrementConnectToNetworkNotification(OPEN_NET_NOTIFIER_TAG,
                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_CONNECTING_TO_NETWORK);
         verify(mWifiMetrics).incrementConnectToNetworkNotificationAction(OPEN_NET_NOTIFIER_TAG,
                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK,
                 ConnectToNetworkNotificationAndActionCount.ACTION_CONNECT_TO_NETWORK);
-        verify(mNotificationManager, times(2)).notify(anyInt(), any());
+        verify(mWifiNotificationManager, times(2)).notify(anyInt(), any());
 
         mNotificationController.handleConnectionFailure();
 
@@ -670,7 +674,7 @@
         verify(mNotificationBuilder).createNetworkFailedNotification(OPEN_NET_NOTIFIER_TAG);
         verify(mWifiMetrics).incrementConnectToNetworkNotification(OPEN_NET_NOTIFIER_TAG,
                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_FAILED_TO_CONNECT);
-        verify(mNotificationManager, times(3)).notify(anyInt(), any());
+        verify(mWifiNotificationManager, times(3)).notify(anyInt(), any());
     }
 
     /**
@@ -684,33 +688,33 @@
 
         // Initial Notification
         verify(mNotificationBuilder).createConnectToAvailableNetworkNotification(
-                OPEN_NET_NOTIFIER_TAG, mDummyNetwork);
+                OPEN_NET_NOTIFIER_TAG, mTestNetwork);
         verify(mWifiMetrics).incrementConnectToNetworkNotification(OPEN_NET_NOTIFIER_TAG,
                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
-        verify(mNotificationManager).notify(anyInt(), any());
+        verify(mWifiNotificationManager).notify(anyInt(), any());
 
         mBroadcastReceiver.onReceive(mContext, createIntent(ACTION_CONNECT_TO_NETWORK));
 
         verify(mWifiMetrics).setNominatorForNetwork(TEST_NETWORK_ID,
                 WifiMetricsProto.ConnectionEvent.NOMINATOR_OPEN_NETWORK_AVAILABLE);
 
-        ArgumentCaptor<IActionListener> connectListenerCaptor =
-                ArgumentCaptor.forClass(IActionListener.class);
-        verify(mClientModeImpl).connect(eq(null), eq(TEST_NETWORK_ID), any(Binder.class),
-                connectListenerCaptor.capture(), anyInt(), eq(Process.SYSTEM_UID));
-        IActionListener connectListener = connectListenerCaptor.getValue();
+        ArgumentCaptor<ActionListenerWrapper> connectListenerCaptor =
+                ArgumentCaptor.forClass(ActionListenerWrapper.class);
+        verify(mConnectHelper).connectToNetwork(eq(new NetworkUpdateResult(TEST_NETWORK_ID)),
+                connectListenerCaptor.capture(), eq(Process.SYSTEM_UID));
+        ActionListenerWrapper connectListener = connectListenerCaptor.getValue();
 
         // Connecting Notification
         verify(mNotificationBuilder).createNetworkConnectingNotification(OPEN_NET_NOTIFIER_TAG,
-                mDummyNetwork);
+                mTestNetwork);
         verify(mWifiMetrics).incrementConnectToNetworkNotification(OPEN_NET_NOTIFIER_TAG,
                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_CONNECTING_TO_NETWORK);
         verify(mWifiMetrics).incrementConnectToNetworkNotificationAction(OPEN_NET_NOTIFIER_TAG,
                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK,
                 ConnectToNetworkNotificationAndActionCount.ACTION_CONNECT_TO_NETWORK);
-        verify(mNotificationManager, times(2)).notify(anyInt(), any());
+        verify(mWifiNotificationManager, times(2)).notify(anyInt(), any());
 
-        connectListener.onFailure(WifiManager.ERROR);
+        connectListener.sendFailure(WifiManager.ERROR);
         mLooper.dispatchAll();
 
         // Failed to Connect Notification
@@ -718,7 +722,7 @@
         verify(mWifiMetrics).incrementConnectToNetworkNotification(OPEN_NET_NOTIFIER_TAG,
                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_FAILED_TO_CONNECT);
         verify(mWifiMetrics).incrementNumNetworkConnectMessageFailedToSend(OPEN_NET_NOTIFIER_TAG);
-        verify(mNotificationManager, times(3)).notify(anyInt(), any());
+        verify(mWifiNotificationManager, times(3)).notify(anyInt(), any());
 
         mBroadcastReceiver.onReceive(mContext,
                 createIntent(ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE));
@@ -775,7 +779,7 @@
         userDismissedNotification_shouldBlacklistNetwork();
 
         // Scan result with blacklisted SSID
-        List<ScanDetail> scanResults = createOpenScanResults(mDummyNetwork.SSID, TEST_SSID_2);
+        List<ScanDetail> scanResults = createOpenScanResults(mTestNetwork.SSID, TEST_SSID_2);
         scanResults.get(0).getScanResult().level = MIN_RSSI_LEVEL + 1;
         scanResults.get(1).getScanResult().level = MIN_RSSI_LEVEL;
 
@@ -810,7 +814,7 @@
         userDismissedNotification_shouldBlacklistNetwork();
 
         // Simulate the user connecting to TEST_SSID_1 and verify it is removed from the blacklist
-        mNotificationController.handleWifiConnected(mDummyNetwork.SSID);
+        mNotificationController.handleWifiConnected(mTestNetwork.SSID);
         verify(mWifiConfigManager, times(2)).saveToStore(false /* forceWrite */);
         verify(mWifiMetrics).setNetworkRecommenderBlocklistSize(OPEN_NET_NOTIFIER_TAG, 0);
         ScanResult actual = mNotificationController.recommendNetwork(mOpenNetworks);
diff --git a/service/tests/wifitests/src/com/android/server/wifi/SarManagerTest.java b/service/tests/wifitests/src/com/android/server/wifi/SarManagerTest.java
index ce9c596..a616ff4 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/SarManagerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/SarManagerTest.java
@@ -28,6 +28,7 @@
 import android.content.pm.ApplicationInfo;
 import android.net.wifi.WifiManager;
 import android.os.Build;
+import android.os.PowerManager;
 import android.os.test.TestLooper;
 import android.telephony.PhoneStateListener;
 import android.telephony.TelephonyManager;
@@ -71,6 +72,7 @@
     @Mock TelephonyManager mTelephonyManager;
     @Mock private ApplicationInfo mMockApplInfo;
     @Mock WifiNative mWifiNative;
+    @Mock PowerManager mPowerManager;
 
     @Before
     public void setUp() throws Exception {
@@ -88,6 +90,7 @@
         mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.P;
         when(mContext.getApplicationInfo()).thenReturn(mMockApplInfo);
         when(mContext.getOpPackageName()).thenReturn(OP_PACKAGE_NAME);
+        when(mContext.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
     }
 
     @After
diff --git a/service/tests/wifitests/src/com/android/server/wifi/SavedNetworkNominatorTest.java b/service/tests/wifitests/src/com/android/server/wifi/SavedNetworkNominatorTest.java
index 33e61a4..e6e4769 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/SavedNetworkNominatorTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/SavedNetworkNominatorTest.java
@@ -16,12 +16,14 @@
 
 package com.android.server.wifi;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.server.wifi.WifiConfigurationTestUtil.SECURITY_NONE;
 import static com.android.server.wifi.WifiConfigurationTestUtil.SECURITY_PSK;
 
 import static org.mockito.Mockito.*;
 
 import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.LocalLog;
 import android.util.Pair;
@@ -37,6 +39,7 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
 
 import java.util.Arrays;
 import java.util.List;
@@ -51,11 +54,23 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        mStaticMockSession = mockitoSession()
+                .mockStatic(WifiInjector.class)
+                .startMocking();
+        lenient().when(WifiInjector.getInstance()).thenReturn(mWifiInjector);
+        when(mWifiInjector.getActiveModeWarden()).thenReturn(mActiveModeWarden);
+        when(mWifiInjector.getWifiGlobals()).thenReturn(mWifiGlobals);
+        when(mActiveModeWarden.getPrimaryClientModeManager()).thenReturn(mPrimaryClientModeManager);
+        when(mPrimaryClientModeManager.getSupportedFeatures()).thenReturn(
+                WifiManager.WIFI_FEATURE_WPA3_SAE | WifiManager.WIFI_FEATURE_OWE);
+        when(mWifiGlobals.isWpa3SaeUpgradeEnabled()).thenReturn(true);
+        when(mWifiGlobals.isOweUpgradeEnabled()).thenReturn(true);
+
         mLocalLog = new LocalLog(512);
         mSavedNetworkNominator = new SavedNetworkNominator(mWifiConfigManager,
                 mPasspointNetworkNominateHelper, mLocalLog, mWifiCarrierInfoManager,
                 mWifiPermissionsUtil, mWifiNetworkSuggestionsManager);
-        when(mWifiCarrierInfoManager.isSimPresent(anyInt())).thenReturn(true);
+        when(mWifiCarrierInfoManager.isSimReady(anyInt())).thenReturn(true);
         when(mWifiCarrierInfoManager.getBestMatchSubscriptionId(any())).thenReturn(VALID_SUBID);
         when(mWifiCarrierInfoManager.requiresImsiEncryption(VALID_SUBID)).thenReturn(true);
         when(mWifiCarrierInfoManager.isImsiEncryptionInfoAvailable(anyInt())).thenReturn(true);
@@ -70,6 +85,9 @@
     @After
     public void cleanup() {
         validateMockitoUsage();
+        if (null != mStaticMockSession) {
+            mStaticMockSession.finishMocking();
+        }
     }
 
     private ArgumentCaptor<WifiConfiguration> mWifiConfigurationArgumentCaptor =
@@ -88,6 +106,11 @@
     @Mock private PasspointNetworkNominateHelper mPasspointNetworkNominateHelper;
     @Mock private WifiPermissionsUtil mWifiPermissionsUtil;
     @Mock private WifiNetworkSuggestionsManager mWifiNetworkSuggestionsManager;
+    private @Mock WifiInjector mWifiInjector;
+    private @Mock ActiveModeWarden mActiveModeWarden;
+    private @Mock ClientModeManager mPrimaryClientModeManager;
+    private @Mock WifiGlobals mWifiGlobals;
+    private MockitoSession mStaticMockSession = null;
     private LocalLog mLocalLog;
 
     /**
@@ -111,8 +134,8 @@
             wifiConfiguration.useExternalScores = true;
         }
 
-        mSavedNetworkNominator.nominateNetworks(scanDetails,
-                null, null, true, false, mOnConnectableListener);
+        mSavedNetworkNominator.nominateNetworks(
+                scanDetails, false, true, true, mOnConnectableListener);
 
         verify(mOnConnectableListener, never()).onConnectable(any(), any());
     }
@@ -135,10 +158,10 @@
         savedConfigs[0].carrierId = TEST_CARRIER_ID;
         // SIM is absent
         when(mWifiCarrierInfoManager.getMatchingSubId(TEST_CARRIER_ID)).thenReturn(INVALID_SUBID);
-        when(mWifiCarrierInfoManager.isSimPresent(eq(INVALID_SUBID))).thenReturn(false);
+        when(mWifiCarrierInfoManager.isSimReady(eq(INVALID_SUBID))).thenReturn(false);
 
-        mSavedNetworkNominator.nominateNetworks(scanDetails,
-                null, null, true, false, mOnConnectableListener);
+        mSavedNetworkNominator.nominateNetworks(
+                scanDetails, false, true, true, mOnConnectableListener);
 
         verify(mOnConnectableListener, never()).onConnectable(any(), any());
     }
@@ -164,8 +187,8 @@
             wifiConfiguration.ephemeral = true;
         }
 
-        mSavedNetworkNominator.nominateNetworks(scanDetails,
-                null, null, true, false, mOnConnectableListener);
+        mSavedNetworkNominator.nominateNetworks(
+                scanDetails, false, true, true, mOnConnectableListener);
 
         verify(mOnConnectableListener, never()).onConnectable(any(), any());
     }
@@ -189,14 +212,14 @@
         List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
         WifiConfiguration[] savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
 
-        mSavedNetworkNominator.nominateNetworks(scanDetails,
-                null, null, true, false, mOnConnectableListener);
+        mSavedNetworkNominator.nominateNetworks(
+                scanDetails, false, true, true, mOnConnectableListener);
 
         verify(mOnConnectableListener, times(2)).onConnectable(any(), any());
         reset(mOnConnectableListener);
         savedConfigs[1].allowAutojoin = false;
-        mSavedNetworkNominator.nominateNetworks(scanDetails,
-                null, null, true, false, mOnConnectableListener);
+        mSavedNetworkNominator.nominateNetworks(
+                scanDetails, false, true, true, mOnConnectableListener);
         verify(mOnConnectableListener).onConnectable(any(),
                 mWifiConfigurationArgumentCaptor.capture());
         WifiConfigurationTestUtil.assertConfigurationEqual(savedConfigs[0],
@@ -223,8 +246,8 @@
         for (WifiConfiguration wifiConfiguration : savedConfigs) {
             wifiConfiguration.allowAutojoin = false;
         }
-        mSavedNetworkNominator.nominateNetworks(scanDetails,
-                null, null, true, false, mOnConnectableListener);
+        mSavedNetworkNominator.nominateNetworks(
+                scanDetails, false, true, true, mOnConnectableListener);
         verify(mOnConnectableListener, never()).onConnectable(any(), any());
     }
 
@@ -245,8 +268,8 @@
                         configuration2));
         when(mPasspointNetworkNominateHelper.getPasspointNetworkCandidates(scanDetails, false))
                 .thenReturn(passpointCandidates);
-        mSavedNetworkNominator.nominateNetworks(scanDetails, null, null,
-                false, false, mOnConnectableListener);
+        mSavedNetworkNominator.nominateNetworks(
+                scanDetails, false, true, true, mOnConnectableListener);
         verify(mOnConnectableListener).onConnectable(scanDetail1, configuration1);
         verify(mOnConnectableListener, never()).onConnectable(scanDetail2, configuration2);
     }
@@ -269,8 +292,8 @@
         savedConfigs[0].carrierId = TEST_CARRIER_ID;
         when(mWifiCarrierInfoManager.isCarrierNetworkFromNonDefaultDataSim(savedConfigs[0]))
                 .thenReturn(false);
-        mSavedNetworkNominator.nominateNetworks(scanDetails,
-                null, null, true, false, mOnConnectableListener);
+        mSavedNetworkNominator.nominateNetworks(
+                scanDetails, false, true, true, mOnConnectableListener);
         verify(mOnConnectableListener).onConnectable(any(), any());
         reset(mOnConnectableListener);
         when(mWifiCarrierInfoManager.isCarrierNetworkFromNonDefaultDataSim(savedConfigs[0]))
@@ -300,16 +323,16 @@
         when(mWifiCarrierInfoManager.requiresImsiEncryption(VALID_SUBID)).thenReturn(false);
         when(mWifiCarrierInfoManager.hasUserApprovedImsiPrivacyExemptionForCarrier(TEST_CARRIER_ID))
                 .thenReturn(false);
-        mSavedNetworkNominator.nominateNetworks(scanDetails,
-                null, null, true, false, mOnConnectableListener);
+        mSavedNetworkNominator.nominateNetworks(
+                scanDetails, false, true, true, mOnConnectableListener);
         verify(mOnConnectableListener, never()).onConnectable(any(), any());
         verify(mWifiCarrierInfoManager)
                 .sendImsiProtectionExemptionNotificationIfRequired(TEST_CARRIER_ID);
         // Simulate user approved
         when(mWifiCarrierInfoManager.hasUserApprovedImsiPrivacyExemptionForCarrier(TEST_CARRIER_ID))
                 .thenReturn(true);
-        mSavedNetworkNominator.nominateNetworks(scanDetails,
-                null, null, true, false, mOnConnectableListener);
+        mSavedNetworkNominator.nominateNetworks(
+                scanDetails, false, true, true, mOnConnectableListener);
         verify(mOnConnectableListener).onConnectable(any(), any());
         // If from settings app, will bypass the IMSI check.
         when(mWifiCarrierInfoManager.hasUserApprovedImsiPrivacyExemptionForCarrier(TEST_CARRIER_ID))
@@ -335,15 +358,15 @@
         when(mWifiNetworkSuggestionsManager
                 .shouldBeIgnoredBySecureSuggestionFromSameCarrier(any(), any()))
                 .thenReturn(true);
-        mSavedNetworkNominator.nominateNetworks(scanDetails,
-                null, null, true, false, mOnConnectableListener);
+        mSavedNetworkNominator.nominateNetworks(
+                scanDetails, false, true, true, mOnConnectableListener);
         verify(mOnConnectableListener, never()).onConnectable(any(), any());
 
         when(mWifiNetworkSuggestionsManager
                 .shouldBeIgnoredBySecureSuggestionFromSameCarrier(any(), any()))
                 .thenReturn(false);
-        mSavedNetworkNominator.nominateNetworks(scanDetails,
-                null, null, true, false, mOnConnectableListener);
+        mSavedNetworkNominator.nominateNetworks(
+                scanDetails, false, true, true, mOnConnectableListener);
         verify(mOnConnectableListener).onConnectable(any(), any());
     }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/ScanDetailCacheTest.java b/service/tests/wifitests/src/com/android/server/wifi/ScanDetailCacheTest.java
new file mode 100644
index 0000000..4369a9a
--- /dev/null
+++ b/service/tests/wifitests/src/com/android/server/wifi/ScanDetailCacheTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import android.net.wifi.WifiConfiguration;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.ScanDetailCache}.
+ */
+@SmallTest
+public class ScanDetailCacheTest {
+    private static final int TEST_MAX_SIZE = 5;
+    private static final int TEST_TRIM_SIZE = 2;
+    private static final String TEST_BSSID_1 = "0a:08:5c:67:89:01";
+    private static final String TEST_BSSID_2 = "0a:08:5c:67:89:02";
+    private static final String TEST_BSSID_3 = "0a:08:5c:67:89:03";
+    private static final String TEST_BSSID_4 = "0a:08:5c:67:89:04";
+    private static final int TEST_RSSI = -50;
+    private static final int TEST_RSSI_2 = -60;
+    private static final int TEST_FREQUENCY = 2412;
+    private ScanDetailCache mScanDetailCache;
+    WifiConfiguration mWifiConfiguration;
+
+    @Mock Clock mClock;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mWifiConfiguration = WifiConfigurationTestUtil.createOpenNetwork();
+        mScanDetailCache = new ScanDetailCache(mWifiConfiguration, TEST_MAX_SIZE, TEST_TRIM_SIZE);
+    }
+
+    @Test
+    public void testPut() {
+        ScanDetail scanDetail = createScanDetailForNetwork(mWifiConfiguration, TEST_BSSID_1,
+                TEST_RSSI, TEST_FREQUENCY);
+        mScanDetailCache.put(scanDetail);
+        assertEquals(scanDetail, mScanDetailCache.getScanDetail(TEST_BSSID_1));
+    }
+
+    @Test
+    public void testGetMostRecentScanResult() {
+        setClockTime(1000);
+        ScanDetail s1 = createScanDetailForNetwork(mWifiConfiguration, TEST_BSSID_1,
+                TEST_RSSI, TEST_FREQUENCY);
+        setClockTime(2000);
+        ScanDetail s2 = createScanDetailForNetwork(mWifiConfiguration, TEST_BSSID_2,
+                TEST_RSSI, TEST_FREQUENCY);
+        setClockTime(3000);
+        // s3 and s4 have the same timestamp but s3 has higher RSSI.
+        ScanDetail s3 = createScanDetailForNetwork(mWifiConfiguration, TEST_BSSID_3,
+                TEST_RSSI, TEST_FREQUENCY);
+        ScanDetail s4 = createScanDetailForNetwork(mWifiConfiguration, TEST_BSSID_4,
+                TEST_RSSI_2, TEST_FREQUENCY);
+
+        mScanDetailCache.put(s1);
+        mScanDetailCache.put(s2);
+        mScanDetailCache.put(s3);
+        mScanDetailCache.put(s4);
+
+        // verify getMostRecentScanResult() returns s3 because it's the most recent scan result,
+        // and has higher RSSI in comparison to s4.
+        assertEquals(s3.getScanResult(), mScanDetailCache.getMostRecentScanResult());
+
+        // verify that each individual ScanDetail also get retrieved correctly.
+        assertEquals(s1, mScanDetailCache.getScanDetail(TEST_BSSID_1));
+        assertEquals(s2, mScanDetailCache.getScanDetail(TEST_BSSID_2));
+        assertEquals(s3, mScanDetailCache.getScanDetail(TEST_BSSID_3));
+        assertEquals(s4, mScanDetailCache.getScanDetail(TEST_BSSID_4));
+    }
+
+    private void setClockTime(long millis) {
+        when(mClock.getUptimeSinceBootMillis()).thenReturn(millis);
+        when(mClock.getWallClockMillis()).thenReturn(millis);
+    }
+
+    /**
+     * Creates a scan detail corresponding to the provided network and given BSSID, level &frequency
+     * values.
+     */
+    private ScanDetail createScanDetailForNetwork(
+            WifiConfiguration configuration, String bssid, int level, int frequency) {
+        return WifiConfigurationTestUtil.createScanDetailForNetwork(configuration, bssid, level,
+                frequency, mClock.getUptimeSinceBootMillis(), mClock.getWallClockMillis());
+    }
+}
diff --git a/service/tests/wifitests/src/com/android/server/wifi/ScanRequestProxyTest.java b/service/tests/wifitests/src/com/android/server/wifi/ScanRequestProxyTest.java
index cc96602..5c0cd32 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/ScanRequestProxyTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/ScanRequestProxyTest.java
@@ -44,7 +44,9 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.util.WifiPermissionsUtil;
+import com.android.wifi.resources.R;
 
 import org.junit.After;
 import org.junit.Before;
@@ -86,6 +88,7 @@
     @Mock private WifiScanner mWifiScanner;
     @Mock private WifiPermissionsUtil mWifiPermissionsUtil;
     @Mock private WifiMetrics mWifiMetrics;
+    @Mock private WifiMetrics.ScanMetrics mScanMetrics;
     @Mock private Clock mClock;
     @Mock private WifiSettingsConfigStore mWifiSettingsConfigStore;
     @Mock private WifiNetworkSuggestionsManager mWifiNetworkSuggestionsManager;
@@ -108,17 +111,26 @@
     private InOrder mInOrder;
 
     private ScanRequestProxy mScanRequestProxy;
+    private MockResources mResources;
+
+    private MockResources getMockResources() {
+        MockResources resources = new MockResources();
+        return resources;
+    }
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
+        mResources = getMockResources();
+        when(mContext.getResources()).thenReturn(mResources);
         when(mWifiInjector.getWifiScanner()).thenReturn(mWifiScanner);
         when(mWifiInjector.getWifiNetworkSuggestionsManager())
                 .thenReturn(mWifiNetworkSuggestionsManager);
         when(mWifiConfigManager.retrieveHiddenNetworkList()).thenReturn(TEST_HIDDEN_NETWORKS_LIST);
         when(mWifiNetworkSuggestionsManager.retrieveHiddenNetworkList())
                 .thenReturn(TEST_HIDDEN_NETWORKS_LIST_NS);
+        when(mWifiMetrics.getScanMetrics()).thenReturn(mScanMetrics);
         doNothing().when(mWifiScanner).registerScanListener(
                 any(),
                 mGlobalScanListenerArgumentCaptor.capture());
@@ -131,7 +143,7 @@
         mInOrder = inOrder(mWifiScanner, mWifiConfigManager,
                 mContext, mWifiNetworkSuggestionsManager);
         mTestScanDatas1 =
-                ScanTestUtil.createScanDatas(new int[][]{{ 2417, 2427, 5180, 5170 }},
+                ScanTestUtil.createScanDatas(new int[][]{{ 2417, 2427, 5180, 5170, 58320, 60480 }},
                         new int[]{0},
                         new int[]{WifiScanner.WIFI_BAND_ALL});
         mTestScanDatas2 =
@@ -152,7 +164,7 @@
 
     @After
     public void cleanUp() throws Exception {
-        verifyNoMoreInteractions(mWifiScanner, mWifiConfigManager, mContext, mWifiMetrics);
+        verifyNoMoreInteractions(mWifiScanner, mWifiConfigManager, mWifiMetrics);
         validateMockitoUsage();
     }
 
@@ -166,6 +178,22 @@
         when(mClock.getElapsedSinceBootMillis()).thenReturn(782L);
     }
 
+    private void verifyScanMetricsDataWasSet() {
+        verifyScanMetricsDataWasSet(1);
+    }
+
+    private void verifyScanMetricsDataWasSet(int times) {
+        verifyScanMetricsDataWasSet(times, times);
+    }
+
+    private void verifyScanMetricsDataWasSet(int timesExternalRequest, int timesDataSet) {
+        verify(mWifiMetrics,
+                times(timesExternalRequest)).incrementExternalAppOneshotScanRequestsCount();
+        verify(mWifiMetrics, times(timesDataSet * 2)).getScanMetrics();
+        verify(mScanMetrics, times(timesDataSet)).setWorkSource(any());
+        verify(mScanMetrics, times(timesDataSet)).setImportance(anyInt());
+    }
+
     /**
      * Verify scan enable sequence.
      */
@@ -211,7 +239,7 @@
                 new WorkSource(TEST_UID, TEST_PACKAGE_NAME_1)));
         validateScanSettings(mScanSettingsArgumentCaptor.getValue(), false);
 
-        verify(mWifiMetrics).incrementExternalAppOneshotScanRequestsCount();
+        verifyScanMetricsDataWasSet();
     }
 
     /**
@@ -227,6 +255,11 @@
         assertTrue(mWorkSourceArgumentCaptor.getValue().equals(
                 new WorkSource(TEST_UID, TEST_PACKAGE_NAME_1)));
         validateScanSettings(mScanSettingsArgumentCaptor.getValue(), false, true);
+        verifyScanMetricsDataWasSet(0, 1);
+        if (SdkLevel.isAtLeastS()) {
+            assertFalse("6Ghz PSC should not enabled for scan requests from the settings app.",
+                    mScanSettingsArgumentCaptor.getValue().is6GhzPscOnlyEnabled());
+        }
     }
 
     /**
@@ -242,6 +275,7 @@
         assertEquals(mWorkSourceArgumentCaptor.getValue(),
                 new WorkSource(TEST_UID, TEST_PACKAGE_NAME_1));
         validateScanSettings(mScanSettingsArgumentCaptor.getValue(), false, true);
+        verifyScanMetricsDataWasSet(0, 1);
     }
 
     /**
@@ -263,7 +297,7 @@
                 new WorkSource(TEST_UID, TEST_PACKAGE_NAME_1));
         validateScanSettings(mScanSettingsArgumentCaptor.getValue(), false);
 
-        verify(mWifiMetrics).incrementExternalAppOneshotScanRequestsCount();
+        verifyScanMetricsDataWasSet();
     }
 
     /**
@@ -286,7 +320,7 @@
                 new WorkSource(TEST_UID, TEST_PACKAGE_NAME_1));
         validateScanSettings(mScanSettingsArgumentCaptor.getValue(), true);
 
-        verify(mWifiMetrics).incrementExternalAppOneshotScanRequestsCount();
+        verifyScanMetricsDataWasSet();
     }
 
     /**
@@ -302,11 +336,13 @@
         validateScanResultsAvailableBroadcastSent(true);
 
         // Validate the scan results in the cache.
-        ScanTestUtil.assertScanResultsEquals(
+        ScanTestUtil.assertScanResultsEqualsAnyOrder(
                 mTestScanDatas1[0].getResults(),
                 mScanRequestProxy.getScanResults().stream().toArray(ScanResult[]::new));
+        ScanTestUtil.assertScanResultEquals(mTestScanDatas1[0].getResults()[0],
+                mScanRequestProxy.getScanResult(mTestScanDatas1[0].getResults()[0].BSSID));
 
-        verify(mWifiMetrics).incrementExternalAppOneshotScanRequestsCount();
+        verifyScanMetricsDataWasSet();
     }
 
     /**
@@ -323,8 +359,9 @@
 
         // Ensure scan results in the cache is empty.
         assertTrue(mScanRequestProxy.getScanResults().isEmpty());
+        assertNull(mScanRequestProxy.getScanResult(mTestScanDatas1[0].getResults()[0].BSSID));
 
-        verify(mWifiMetrics).incrementExternalAppOneshotScanRequestsCount();
+        verifyScanMetricsDataWasSet();
     }
 
     /**
@@ -340,9 +377,11 @@
         mGlobalScanListenerArgumentCaptor.getValue().onResults(mTestScanDatas1);
         validateScanResultsAvailableBroadcastSent(true);
         // Validate the scan results in the cache.
-        ScanTestUtil.assertScanResultsEquals(
+        ScanTestUtil.assertScanResultsEqualsAnyOrder(
                 mTestScanDatas1[0].getResults(),
                 mScanRequestProxy.getScanResults().stream().toArray(ScanResult[]::new));
+        ScanTestUtil.assertScanResultEquals(mTestScanDatas1[0].getResults()[0],
+                mScanRequestProxy.getScanResult(mTestScanDatas1[0].getResults()[0].BSSID));
 
         // Make scan request 2.
         assertTrue(mScanRequestProxy.startScan(TEST_UID, TEST_PACKAGE_NAME_2));
@@ -351,11 +390,13 @@
         mGlobalScanListenerArgumentCaptor.getValue().onResults(mTestScanDatas2);
         validateScanResultsAvailableBroadcastSent(true);
         // Validate the scan results in the cache.
-        ScanTestUtil.assertScanResultsEquals(
+        ScanTestUtil.assertScanResultsEqualsAnyOrder(
                 mTestScanDatas2[0].getResults(),
                 mScanRequestProxy.getScanResults().stream().toArray(ScanResult[]::new));
+        ScanTestUtil.assertScanResultEquals(mTestScanDatas2[0].getResults()[0],
+                mScanRequestProxy.getScanResult(mTestScanDatas2[0].getResults()[0].BSSID));
 
-        verify(mWifiMetrics, times(2)).incrementExternalAppOneshotScanRequestsCount();
+        verifyScanMetricsDataWasSet(2);
     }
 
     /**
@@ -371,9 +412,11 @@
         mGlobalScanListenerArgumentCaptor.getValue().onResults(mTestScanDatas1);
         validateScanResultsAvailableBroadcastSent(true);
         // Validate the scan results in the cache.
-        ScanTestUtil.assertScanResultsEquals(
+        ScanTestUtil.assertScanResultsEqualsAnyOrder(
                 mTestScanDatas1[0].getResults(),
                 mScanRequestProxy.getScanResults().stream().toArray(ScanResult[]::new));
+        ScanTestUtil.assertScanResultEquals(mTestScanDatas1[0].getResults()[0],
+                mScanRequestProxy.getScanResult(mTestScanDatas1[0].getResults()[0].BSSID));
 
         // Make scan request 2.
         assertTrue(mScanRequestProxy.startScan(TEST_UID, TEST_PACKAGE_NAME_2));
@@ -382,11 +425,13 @@
         mScanRequestListenerArgumentCaptor.getValue().onFailure(0, "failed");
         validateScanResultsAvailableBroadcastSent(false);
         // Validate the scan results from a previous successful scan in the cache.
-        ScanTestUtil.assertScanResultsEquals(
+        ScanTestUtil.assertScanResultsEqualsAnyOrder(
                 mTestScanDatas1[0].getResults(),
                 mScanRequestProxy.getScanResults().stream().toArray(ScanResult[]::new));
+        ScanTestUtil.assertScanResultEquals(mTestScanDatas1[0].getResults()[0],
+                mScanRequestProxy.getScanResult(mTestScanDatas1[0].getResults()[0].BSSID));
 
-        verify(mWifiMetrics, times(2)).incrementExternalAppOneshotScanRequestsCount();
+        verifyScanMetricsDataWasSet(2);
     }
 
     /**
@@ -403,9 +448,11 @@
         mGlobalScanListenerArgumentCaptor.getValue().onResults(mTestScanDatas1);
         validateScanResultsAvailableBroadcastSent(true);
         // Validate the scan results in the cache.
-        ScanTestUtil.assertScanResultsEquals(
+        ScanTestUtil.assertScanResultsEqualsAnyOrder(
                 mTestScanDatas1[0].getResults(),
                 mScanRequestProxy.getScanResults().stream().toArray(ScanResult[]::new));
+        ScanTestUtil.assertScanResultEquals(mTestScanDatas1[0].getResults()[0],
+                mScanRequestProxy.getScanResult(mTestScanDatas1[0].getResults()[0].BSSID));
 
         // Make scan request 2.
         assertTrue(mScanRequestProxy.startScan(TEST_UID, TEST_PACKAGE_NAME_2));
@@ -415,11 +462,13 @@
         mGlobalScanListenerArgumentCaptor.getValue().onResults(mTestScanDatas2);
         validateScanResultsAvailableBroadcastSent(true);
         // Validate the scan results in the cache.
-        ScanTestUtil.assertScanResultsEquals(
+        ScanTestUtil.assertScanResultsEqualsAnyOrder(
                 mTestScanDatas2[0].getResults(),
                 mScanRequestProxy.getScanResults().stream().toArray(ScanResult[]::new));
+        ScanTestUtil.assertScanResultEquals(mTestScanDatas2[0].getResults()[0],
+                mScanRequestProxy.getScanResult(mTestScanDatas2[0].getResults()[0].BSSID));
 
-        verify(mWifiMetrics, times(2)).incrementExternalAppOneshotScanRequestsCount();
+        verifyScanMetricsDataWasSet(2);
     }
 
     /**
@@ -440,6 +489,7 @@
         validateScanResultsAvailableBroadcastSent(false);
         // Validate the scan results in the cache.
         assertTrue(mScanRequestProxy.getScanResults().isEmpty());
+        assertNull(mScanRequestProxy.getScanResult(mTestScanDatas1[0].getResults()[0].BSSID));
 
         // Make scan request 2.
         assertTrue(mScanRequestProxy.startScan(TEST_UID, TEST_PACKAGE_NAME_2));
@@ -449,11 +499,13 @@
         mGlobalScanListenerArgumentCaptor.getValue().onResults(mTestScanDatas2);
         validateScanResultsAvailableBroadcastSent(true);
         // Validate the scan results in the cache.
-        ScanTestUtil.assertScanResultsEquals(
+        ScanTestUtil.assertScanResultsEqualsAnyOrder(
                 mTestScanDatas2[0].getResults(),
                 mScanRequestProxy.getScanResults().stream().toArray(ScanResult[]::new));
+        ScanTestUtil.assertScanResultEquals(mTestScanDatas2[0].getResults()[0],
+                mScanRequestProxy.getScanResult(mTestScanDatas2[0].getResults()[0].BSSID));
 
-        verify(mWifiMetrics, times(2)).incrementExternalAppOneshotScanRequestsCount();
+        verifyScanMetricsDataWasSet(2);
     }
 
 
@@ -473,6 +525,7 @@
         validateScanResultsAvailableBroadcastSent(false);
         // Validate the scan results in the cache.
         assertTrue(mScanRequestProxy.getScanResults().isEmpty());
+        assertNull(mScanRequestProxy.getScanResult(mTestScanDatas1[0].getResults()[0].BSSID));
 
         // Make scan request 2.
         assertTrue(mScanRequestProxy.startScan(TEST_UID, TEST_PACKAGE_NAME_2));
@@ -482,11 +535,13 @@
         mGlobalScanListenerArgumentCaptor.getValue().onResults(mTestScanDatas2);
         validateScanResultsAvailableBroadcastSent(true);
         // Validate the scan results in the cache.
-        ScanTestUtil.assertScanResultsEquals(
+        ScanTestUtil.assertScanResultsEqualsAnyOrder(
                 mTestScanDatas2[0].getResults(),
                 mScanRequestProxy.getScanResults().stream().toArray(ScanResult[]::new));
+        ScanTestUtil.assertScanResultEquals(mTestScanDatas2[0].getResults()[0],
+                mScanRequestProxy.getScanResult(mTestScanDatas2[0].getResults()[0].BSSID));
 
-        verify(mWifiMetrics, times(2)).incrementExternalAppOneshotScanRequestsCount();
+        verifyScanMetricsDataWasSet(2);
     }
 
     /**
@@ -507,18 +562,33 @@
         mGlobalScanListenerArgumentCaptor.getValue().onResults(mTestScanDatas1);
         validateScanResultsAvailableBroadcastSent(true);
         // Validate the scan results in the cache.
-        ScanTestUtil.assertScanResultsEquals(
+        ScanTestUtil.assertScanResultsEqualsAnyOrder(
                 mTestScanDatas1[0].getResults(),
                 mScanRequestProxy.getScanResults().stream().toArray(ScanResult[]::new));
+        ScanTestUtil.assertScanResultEquals(mTestScanDatas1[0].getResults()[0],
+                mScanRequestProxy.getScanResult(mTestScanDatas1[0].getResults()[0].BSSID));
+
+        // Enable scanning again (a new iface was added/removed).
+        mScanRequestProxy.enableScanning(true, false);
+        mInOrder.verify(mWifiScanner).setScanningEnabled(true);
+        validateScanAvailableBroadcastSent(true);
+        // Validate the scan results in the cache (should not be cleared).
+        ScanTestUtil.assertScanResultsEqualsAnyOrder(
+                mTestScanDatas1[0].getResults(),
+                mScanRequestProxy.getScanResults().stream().toArray(ScanResult[]::new));
+        ScanTestUtil.assertScanResultEquals(mTestScanDatas1[0].getResults()[0],
+                mScanRequestProxy.getScanResult(mTestScanDatas1[0].getResults()[0].BSSID));
 
         // Disable scanning
         mScanRequestProxy.enableScanning(false, false);
         verify(mWifiScanner).setScanningEnabled(false);
         validateScanAvailableBroadcastSent(false);
 
+        // Validate the scan results in the cache (should be cleared).
         assertTrue(mScanRequestProxy.getScanResults().isEmpty());
+        assertNull(mScanRequestProxy.getScanResult(mTestScanDatas1[0].getResults()[0].BSSID));
 
-        verify(mWifiMetrics).incrementExternalAppOneshotScanRequestsCount();
+        verifyScanMetricsDataWasSet();
     }
 
     /**
@@ -542,7 +612,33 @@
 
         assertNotEquals(listener1, listener2);
 
-        verify(mWifiMetrics, times(2)).incrementExternalAppOneshotScanRequestsCount();
+        verifyScanMetricsDataWasSet(2);
+    }
+
+    /**
+     * Verify that a scan request from a App in the foreground scanning throttle exception list
+     * is not throttled.
+     */
+    @Test
+    public void testForegroundScanForPackageInExceptionListNotThrottled() {
+        enableScanning();
+        long firstRequestMs = 782;
+        mResources.setStringArray(R.array.config_wifiForegroundScanThrottleExceptionList,
+                new String[]{TEST_PACKAGE_NAME_1});
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(firstRequestMs);
+        for (int i = 0; i < SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS; i++) {
+            when(mClock.getElapsedSinceBootMillis()).thenReturn(firstRequestMs + i);
+            assertTrue(mScanRequestProxy.startScan(TEST_UID, TEST_PACKAGE_NAME_1));
+            mInOrder.verify(mWifiScanner).startScan(any(), any(), any(), any());
+        }
+        // Make next scan request from the same package name & ensure that it is not throttled.
+        assertTrue(mScanRequestProxy.startScan(TEST_UID, TEST_PACKAGE_NAME_1));
+        mInOrder.verify(mWifiScanner).startScan(any(), any(), any(), any());
+        verifyScanMetricsDataWasSet(SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS + 1);
+        if (SdkLevel.isAtLeastS()) {
+            assertTrue("6Ghz PSC should be enabled for scan requests from normal apps.",
+                    mScanSettingsArgumentCaptor.getValue().is6GhzPscOnlyEnabled());
+        }
     }
 
     /**
@@ -564,9 +660,9 @@
         assertFalse(mScanRequestProxy.startScan(TEST_UID, TEST_PACKAGE_NAME_1));
         validateScanResultsFailureBroadcastSent(TEST_PACKAGE_NAME_1);
 
-        verify(mWifiMetrics, times(SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS + 1))
-                .incrementExternalAppOneshotScanRequestsCount();
         verify(mWifiMetrics).incrementExternalForegroundAppOneshotScanRequestsThrottledCount();
+        verifyScanMetricsDataWasSet(SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS + 1,
+                SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS);
     }
 
     /**
@@ -590,8 +686,7 @@
         assertTrue(mScanRequestProxy.startScan(TEST_UID, TEST_PACKAGE_NAME_1));
         mInOrder.verify(mWifiScanner).startScan(any(), any(), any(), any());
 
-        verify(mWifiMetrics, times(SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS + 1))
-                .incrementExternalAppOneshotScanRequestsCount();
+        verifyScanMetricsDataWasSet(SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS + 1);
     }
 
     /**
@@ -613,6 +708,8 @@
         // Make next scan request from the same package name & ensure that it is not throttled.
         assertTrue(mScanRequestProxy.startScan(TEST_UID, TEST_PACKAGE_NAME_1));
         mInOrder.verify(mWifiScanner).startScan(any(), any(), any(), any());
+
+        verifyScanMetricsDataWasSet(0, SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS + 1);
     }
 
     /**
@@ -634,6 +731,8 @@
         // Make next scan request from the same package name & ensure that it is not throttled.
         assertTrue(mScanRequestProxy.startScan(TEST_UID, TEST_PACKAGE_NAME_1));
         mInOrder.verify(mWifiScanner).startScan(any(), any(), any(), any());
+
+        verifyScanMetricsDataWasSet(0, SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS + 1);
     }
 
     /**
@@ -657,6 +756,8 @@
         // Make next scan request from the same package name & ensure that it is not throttled.
         assertTrue(mScanRequestProxy.startScan(TEST_UID, TEST_PACKAGE_NAME_1));
         mInOrder.verify(mWifiScanner).startScan(any(), any(), any(), any());
+
+        verifyScanMetricsDataWasSet(0, SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS + 1);
     }
 
     /**
@@ -683,8 +784,7 @@
         assertTrue(mScanRequestProxy.startScan(TEST_UID, TEST_PACKAGE_NAME_2));
         mInOrder.verify(mWifiScanner).startScan(any(), any(), any(), any());
 
-        verify(mWifiMetrics, times(SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS + 2))
-                .incrementExternalAppOneshotScanRequestsCount();
+        verifyScanMetricsDataWasSet(SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS + 2);
     }
 
     /**
@@ -710,8 +810,7 @@
         assertTrue(mScanRequestProxy.startScan(TEST_UID, TEST_PACKAGE_NAME_1));
         mInOrder.verify(mWifiScanner).startScan(any(), any(), any(), any());
 
-        verify(mWifiMetrics, times(SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS + 1))
-                .incrementExternalAppOneshotScanRequestsCount();
+        verifyScanMetricsDataWasSet(SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS + 1);
     }
 
     /**
@@ -741,6 +840,38 @@
         verify(mWifiMetrics, times(SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS + 1))
                 .incrementExternalAppOneshotScanRequestsCount();
         verify(mWifiMetrics).incrementExternalForegroundAppOneshotScanRequestsThrottledCount();
+        verify(mWifiMetrics,
+                times(SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS * 2)).getScanMetrics();
+        verify(mScanMetrics, times(SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS)).setWorkSource(
+                any());
+        verify(mScanMetrics, times(SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS)).setImportance(
+                anyInt());
+    }
+
+    /**
+     * Verify that a scan request from a App in the background scanning throttle exception list
+     * is not throttled.
+     */
+    @Test
+    public void testBackgroundScanForPackageInExceptionListNotThrottled() {
+        enableScanning();
+        mResources.setStringArray(R.array.config_wifiForegroundScanThrottleExceptionList,
+                new String[]{TEST_PACKAGE_NAME_2});
+        when(mActivityManager.getPackageImportance(TEST_PACKAGE_NAME_1))
+                .thenReturn(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND + 1);
+        when(mActivityManager.getPackageImportance(TEST_PACKAGE_NAME_2))
+                .thenReturn(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND + 1);
+
+        long firstRequestMs = 782;
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(firstRequestMs);
+        // Make scan request 1.
+        assertTrue(mScanRequestProxy.startScan(TEST_UID, TEST_PACKAGE_NAME_1));
+        mInOrder.verify(mWifiScanner).startScan(any(), any(), any(), any());
+
+        // Make scan request 2 from the different package name & ensure that it is not throttled.
+        assertTrue(mScanRequestProxy.startScan(TEST_UID, TEST_PACKAGE_NAME_2));
+        mInOrder.verify(mWifiScanner).startScan(any(), any(), any(), any());
+        verifyScanMetricsDataWasSet(2);
     }
 
     /**
@@ -765,8 +896,8 @@
         assertFalse(mScanRequestProxy.startScan(TEST_UID, TEST_PACKAGE_NAME_2));
         validateScanResultsFailureBroadcastSent(TEST_PACKAGE_NAME_2);
 
-        verify(mWifiMetrics, times(2)).incrementExternalAppOneshotScanRequestsCount();
         verify(mWifiMetrics).incrementExternalBackgroundAppOneshotScanRequestsThrottledCount();
+        verifyScanMetricsDataWasSet(2, 1);
     }
 
     /**
@@ -794,7 +925,7 @@
         assertTrue(mScanRequestProxy.startScan(TEST_UID, TEST_PACKAGE_NAME_2));
         mInOrder.verify(mWifiScanner).startScan(any(), any(), any(), any());
 
-        verify(mWifiMetrics, times(2)).incrementExternalAppOneshotScanRequestsCount();
+        verifyScanMetricsDataWasSet(2);
     }
 
     /**
@@ -811,7 +942,7 @@
         mGlobalScanListenerArgumentCaptor.getValue().onResults(mTestScanDatas1);
         validateScanResultsAvailableBroadcastSent(true);
         // Validate the scan results in the cache.
-        ScanTestUtil.assertScanResultsEquals(
+        ScanTestUtil.assertScanResultsEqualsAnyOrder(
                 mTestScanDatas1[0].getResults(),
                 mScanRequestProxy.getScanResults().stream().toArray(ScanResult[]::new));
 
@@ -820,11 +951,11 @@
         mGlobalScanListenerArgumentCaptor.getValue().onResults(mTestScanDatas2);
         validateScanResultsAvailableBroadcastSent(true);
         // Validate the scan results in the cache.
-        ScanTestUtil.assertScanResultsEquals(
+        ScanTestUtil.assertScanResultsEqualsAnyOrder(
                 mTestScanDatas2[0].getResults(),
                 mScanRequestProxy.getScanResults().stream().toArray(ScanResult[]::new));
 
-        verify(mWifiMetrics).incrementExternalAppOneshotScanRequestsCount();
+        verifyScanMetricsDataWasSet();
     }
 
     /**
@@ -841,22 +972,22 @@
         mGlobalScanListenerArgumentCaptor.getValue().onResults(mTestScanDatas1);
         validateScanResultsAvailableBroadcastSent(true);
         // Validate the scan results in the cache.
-        ScanTestUtil.assertScanResultsEquals(
+        ScanTestUtil.assertScanResultsEqualsAnyOrder(
                 mTestScanDatas1[0].getResults(),
                 mScanRequestProxy.getScanResults().stream().toArray(ScanResult[]::new));
 
         // Now send results from an internal partial scan request.
-        mTestScanDatas2 = ScanTestUtil.createScanDatas(new int[][]{{ 2412, 2422, 5200, 5210 }},
+        mTestScanDatas2 = ScanTestUtil.createScanDatas(new int[][]{{ 2412, 2422 }},
                 new int[]{0},
-                new int[]{WifiScanner.WIFI_BAND_BOTH});
+                new int[]{WifiScanner.WIFI_BAND_24_GHZ});
         // Verify the scan failure processing.
         mGlobalScanListenerArgumentCaptor.getValue().onResults(mTestScanDatas2);
         // Validate the scan results from a previous successful scan in the cache.
-        ScanTestUtil.assertScanResultsEquals(
+        ScanTestUtil.assertScanResultsEqualsAnyOrder(
                 mTestScanDatas1[0].getResults(),
                 mScanRequestProxy.getScanResults().stream().toArray(ScanResult[]::new));
 
-        verify(mWifiMetrics).incrementExternalAppOneshotScanRequestsCount();
+        verifyScanMetricsDataWasSet();
     }
 
     private void validateScanSettings(WifiScanner.ScanSettings scanSettings,
diff --git a/service/tests/wifitests/src/com/android/server/wifi/ScanResultMatchInfoTest.java b/service/tests/wifitests/src/com/android/server/wifi/ScanResultMatchInfoTest.java
index 08addcd..7df3f51 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/ScanResultMatchInfoTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/ScanResultMatchInfoTest.java
@@ -15,22 +15,80 @@
  */
 package com.android.server.wifi;
 
+import static android.net.wifi.WifiManager.WIFI_FEATURE_OWE;
+import static android.net.wifi.WifiManager.WIFI_FEATURE_WPA3_SAE;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.validateMockitoUsage;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
 
+import android.net.wifi.ScanResult;
+import android.net.wifi.SecurityParams;
 import android.net.wifi.WifiConfiguration;
 
 import androidx.test.filters.SmallTest;
 
-import org.junit.Test;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
 
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Unit tests for {@link com.android.server.wifi.ScanResultMatchInfoTest}.
  */
 @SmallTest
 public class ScanResultMatchInfoTest extends WifiBaseTest {
+    private static final String TEST_BSSID = "0a:08:5c:67:89:01";
+    private static final String TEST_SSID = "\"ScanResultMatchInfoSSID\"";
+
+    @Mock WifiInjector mWifiInjector;
+    @Mock WifiGlobals mWifiGlobals;
+    @Mock ActiveModeWarden mActiveModeWarden;
+    @Mock ClientModeManager mClientModeManager;
+    private MockitoSession mSession;
+    /**
+     * Sets up for unit test
+     */
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        // static mocking
+        mSession = ExtendedMockito.mockitoSession()
+                .mockStatic(WifiInjector.class, withSettings().lenient())
+                .startMocking();
+        when(WifiInjector.getInstance()).thenReturn(mWifiInjector);
+        when(mWifiInjector.getWifiGlobals()).thenReturn(mWifiGlobals);
+        when(mWifiInjector.getActiveModeWarden()).thenReturn(mActiveModeWarden);
+        when(mActiveModeWarden.getPrimaryClientModeManager()).thenReturn(mClientModeManager);
+        when(mClientModeManager.getSupportedFeatures()).thenReturn(
+                WIFI_FEATURE_OWE | WIFI_FEATURE_WPA3_SAE);
+        when(mWifiGlobals.isWpa3SaeUpgradeEnabled()).thenReturn(true);
+        when(mWifiGlobals.isOweUpgradeEnabled()).thenReturn(true);
+    }
+
+    /**
+     * Called after each test
+     */
+    @After
+    public void cleanup() {
+        validateMockitoUsage();
+        if (mSession != null) {
+            mSession.finishMocking();
+        }
+    }
+
     /**
      * Tests that equivalent ScanResultMatchInfo objects are created for WifiConfigurations and
      * their associated ScanResult
@@ -47,15 +105,16 @@
         scan = createScanDetailForNetwork(conf, "BB:BB:BB:BB:BB:BB");
         assertEquals(ScanResultMatchInfo.fromWifiConfiguration(conf),
                 ScanResultMatchInfo.fromScanResult(scan.getScanResult()));
+        assertTrue(ScanResultMatchInfo.fromWifiConfiguration(conf)
+                .equals(ScanResultMatchInfo.fromScanResult(
+                        scan.getScanResult())));
 
-        conf =
-                WifiConfigurationTestUtil.createWapiPskNetwork();
+        conf = WifiConfigurationTestUtil.createWapiPskNetwork();
         scan = createScanDetailForNetwork(conf, "AA:AA:AA:AA:AA:AA");
         assertEquals(ScanResultMatchInfo.fromWifiConfiguration(conf),
                 ScanResultMatchInfo.fromScanResult(scan.getScanResult()));
 
-        conf =
-                WifiConfigurationTestUtil.createWapiPskNetwork();
+        conf = WifiConfigurationTestUtil.createWapiPskNetwork();
         scan = createScanDetailForNetwork(conf, "BB:BB:BB:BB:BB:BB");
         assertEquals(ScanResultMatchInfo.fromWifiConfiguration(conf),
                 ScanResultMatchInfo.fromScanResult(scan.getScanResult()));
@@ -238,39 +297,54 @@
                 configuration, bssid, -40, 2402, 0, 0);
     }
 
+    private void verifyUpgradeMatching(
+            WifiConfiguration baseTypeConfig,
+            WifiConfiguration upgradeTypeConfig,
+            boolean isUpgradeEnabled) {
+
+        ScanDetail scanDetailUpgrade = createScanDetailForNetwork(upgradeTypeConfig,
+                "AC:AB:AD:AE:AF:FC");
+
+        ScanResultMatchInfo baseTypeKey = ScanResultMatchInfo
+                .fromWifiConfiguration(baseTypeConfig);
+        ScanResultMatchInfo upgradeTypeScanResultKey = ScanResultMatchInfo
+                .fromScanResult(scanDetailUpgrade.getScanResult());
+        ScanResultMatchInfo upgradeTypeKey = ScanResultMatchInfo
+                .fromWifiConfiguration(upgradeTypeConfig);
+
+        // Test a.equals(a)
+        assertTrue(baseTypeKey.equals(baseTypeKey));
+
+        // Test if a.equals(b) then b.equals(a)
+        assertEquals(isUpgradeEnabled,
+                baseTypeKey.equals(upgradeTypeScanResultKey));
+        assertEquals(isUpgradeEnabled,
+                upgradeTypeScanResultKey.equals(baseTypeKey));
+
+        // Test consistency
+        assertEquals(isUpgradeEnabled,
+                baseTypeKey.equals(upgradeTypeScanResultKey));
+        assertEquals(isUpgradeEnabled,
+                baseTypeKey.equals(upgradeTypeScanResultKey));
+        assertEquals(isUpgradeEnabled,
+                baseTypeKey.equals(upgradeTypeScanResultKey));
+        assertEquals(isUpgradeEnabled,
+                baseTypeKey.equals(upgradeTypeScanResultKey));
+
+        // Test WifiConfiguration objects are not equal
+        assertFalse(baseTypeKey.equals(upgradeTypeKey));
+        assertFalse(upgradeTypeKey.equals(baseTypeKey));
+    }
+
     /**
      * Tests equality properties for PSK to SAE upgrades
      */
     @Test
     public void testEqualityRulesForPskToSaeUpgrade() {
-        WifiConfiguration wifiConfigurationSae =
-                WifiConfigurationTestUtil.createSaeNetwork("\"Upgrade\"");
-        WifiConfiguration wifiConfigurationPsk =
-                WifiConfigurationTestUtil.createPskNetwork("\"Upgrade\"");
-        ScanDetail scanDetailSae = createScanDetailForNetwork(wifiConfigurationSae,
-                "AC:AB:AD:AE:AF:FC");
-
-        ScanResultMatchInfo key1 = ScanResultMatchInfo.fromWifiConfiguration(wifiConfigurationPsk);
-        ScanResultMatchInfo key2 = ScanResultMatchInfo
-                .fromScanResult(scanDetailSae.getScanResult());
-        ScanResultMatchInfo key3 = ScanResultMatchInfo.fromWifiConfiguration(wifiConfigurationSae);
-
-        // Test a.equals(a)
-        assertTrue(key1.matchForNetworkSelection(key1, true));
-
-        // Test if a.equals(b) then b.equals(a)
-        assertTrue(key1.matchForNetworkSelection(key2, true));
-        assertTrue(key2.matchForNetworkSelection(key1, true));
-
-        // Test consistency
-        assertTrue(key1.matchForNetworkSelection(key2, true));
-        assertTrue(key1.matchForNetworkSelection(key2, true));
-        assertTrue(key1.matchForNetworkSelection(key2, true));
-        assertTrue(key1.matchForNetworkSelection(key2, true));
-
-        // Test WifiConfiguration objects are not equal
-        assertFalse(key1.matchForNetworkSelection(key3, true));
-        assertFalse(key3.matchForNetworkSelection(key1, true));
+        verifyUpgradeMatching(
+                WifiConfigurationTestUtil.createPskSaeNetwork("\"Upgrade\""),
+                WifiConfigurationTestUtil.createSaeNetwork("\"Upgrade\""),
+                true);
     }
 
     /**
@@ -278,35 +352,14 @@
      */
     @Test
     public void testEqualityRulesForPskToSaeUpgradeWithOverlayDisable() {
-        WifiConfiguration wifiConfigurationSae =
-                WifiConfigurationTestUtil.createSaeNetwork("\"Upgrade\"");
-        WifiConfiguration wifiConfigurationPsk =
-                WifiConfigurationTestUtil.createPskNetwork("\"Upgrade\"");
-        ScanDetail scanDetailSae = createScanDetailForNetwork(wifiConfigurationSae,
-                "AC:AB:AD:AE:AF:FC");
-
-        ScanResultMatchInfo key1 = ScanResultMatchInfo.fromWifiConfiguration(wifiConfigurationPsk);
-        ScanResultMatchInfo key2 = ScanResultMatchInfo
-                .fromScanResult(scanDetailSae.getScanResult());
-        ScanResultMatchInfo key3 = ScanResultMatchInfo.fromWifiConfiguration(wifiConfigurationSae);
-
-        // Test a.equals(a)
-        assertTrue(key1.matchForNetworkSelection(key1, false));
-
-        // Test if a.equals(b) then b.equals(a)
-        assertFalse(key1.matchForNetworkSelection(key2, false));
-        assertFalse(key2.matchForNetworkSelection(key1, false));
-
-        // Test consistency
-        assertFalse(key1.matchForNetworkSelection(key2, false));
-        assertFalse(key1.matchForNetworkSelection(key2, false));
-        assertFalse(key1.matchForNetworkSelection(key2, false));
-        assertFalse(key1.matchForNetworkSelection(key2, false));
-
-        // Test WifiConfiguration objects are not equal
-        assertFalse(key1.matchForNetworkSelection(key3, false));
-        assertFalse(key3.matchForNetworkSelection(key1, false));
+        when(mWifiGlobals.isWpa3SaeUpgradeEnabled()).thenReturn(false);
+        // Note: createPskSaeNetwork sets setIsAddedByAutoUpgrade
+        verifyUpgradeMatching(
+                WifiConfigurationTestUtil.createPskSaeNetwork("\"Upgrade\""),
+                WifiConfigurationTestUtil.createSaeNetwork("\"Upgrade\""),
+                /* isUpgradeEnabled */ false);
     }
+
     /**
      * Test that SAE saved network will never downgrade to a PSK AP (from scan result)
      */
@@ -316,6 +369,7 @@
                 WifiConfigurationTestUtil.createSaeNetwork("\"Downgrade\"");
         WifiConfiguration wifiConfigurationPsk =
                 WifiConfigurationTestUtil.createPskNetwork("\"Downgrade\"");
+        wifiConfigurationPsk.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
         ScanDetail scanDetailPsk = createScanDetailForNetwork(wifiConfigurationPsk,
                 "AC:AB:AD:AE:AF:FC");
 
@@ -328,4 +382,162 @@
         assertFalse(key1.equals(key2));
         assertFalse(key2.equals(key1));
     }
+
+    /**
+     * Tests equality properties for OPEN to OWE upgrades
+     */
+    @Test
+    public void testEqualityRulesForOpenToOweUpgrade() {
+        verifyUpgradeMatching(
+                WifiConfigurationTestUtil.createOpenOweNetwork("\"Upgrade\""),
+                WifiConfigurationTestUtil.createOweNetwork("\"Upgrade\""),
+                true);
+    }
+
+    /**
+     * Tests equality properties for OPEN to OWE upgrades when feature is disabled
+     */
+    @Test
+    public void testEqualityRulesForOpenToOweUpgradeWithOverlayDisable() {
+        when(mWifiGlobals.isOweUpgradeEnabled()).thenReturn(false);
+        // Note: createOpenOweNetwork sets setIsAddedByAutoUpgrade
+        verifyUpgradeMatching(
+                WifiConfigurationTestUtil.createOpenOweNetwork("\"Upgrade\""),
+                WifiConfigurationTestUtil.createOweNetwork("\"Upgrade\""),
+                /* isUpgradeEnabled */ false);
+    }
+
+    /**
+     * Test that OWE saved network will never downgrade to a OPEN AP (from scan result)
+     */
+    @Test
+    public void testOweToOpenDoesNotDowngrade() {
+        WifiConfiguration wifiConfigurationOwe =
+                WifiConfigurationTestUtil.createOweNetwork("\"Downgrade\"");
+        WifiConfiguration wifiConfigurationOpen =
+                WifiConfigurationTestUtil.createOpenNetwork("\"Downgrade\"");
+        wifiConfigurationOpen.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN);
+        ScanDetail scanDetailOpen = createScanDetailForNetwork(wifiConfigurationOpen,
+                "AC:AB:AD:AE:AF:FC");
+
+        ScanResultMatchInfo key1 = ScanResultMatchInfo
+                .fromWifiConfiguration(wifiConfigurationOwe);
+        ScanResultMatchInfo key2 = ScanResultMatchInfo
+                .fromScanResult(scanDetailOpen.getScanResult());
+
+        // Test both a.equals(b) and b.equals(a) are false:
+        // i.e. OWE saved network will never downgrade to a OPEN AP (from scan result)
+        assertFalse(key1.equals(key2));
+        assertFalse(key2.equals(key1));
+    }
+
+    @Test
+    public void testEqualityRulesForWpa2EnterpriseToWpa3EnterpriseUpgrade() {
+        verifyUpgradeMatching(
+                WifiConfigurationTestUtil.createWpa2Wpa3EnterpriseNetwork("\"Upgrade\""),
+                WifiConfigurationTestUtil.createWpa3EnterpriseNetwork("\"Upgrade\""),
+                true);
+    }
+
+    /**
+     * Test that WPA3 Enterprise saved network will never downgrade to a
+     * WPA2 Enterprise AP (from scan result)
+     */
+    @Test
+    public void testWpa3EnterpriseToWpa2EnterpriseDoesNotDowngrade() {
+        WifiConfiguration wifiConfigurationWpa2Enterprise =
+                WifiConfigurationTestUtil.createEapNetwork("\"Downgrade\"");
+        WifiConfiguration wifiConfigurationWpa3Enterprise =
+                WifiConfigurationTestUtil.createWpa3EnterpriseNetwork("\"Downgrade\"");
+        ScanDetail scanDetailWpa2Enterprise = createScanDetailForNetwork(
+                wifiConfigurationWpa2Enterprise,
+                "AC:AB:AD:AE:AF:FC");
+
+        ScanResultMatchInfo key1 = ScanResultMatchInfo
+                .fromWifiConfiguration(wifiConfigurationWpa3Enterprise);
+        ScanResultMatchInfo key2 = ScanResultMatchInfo
+                .fromScanResult(scanDetailWpa2Enterprise.getScanResult());
+
+        // Test both a.equals(b) and b.equals(a) are false:
+        // i.e. WPA3 Enterprise saved network will never downgrade to
+        // a WPA2 Enterprise AP (from scan result)
+        assertFalse(key1.equals(key2));
+        assertFalse(key2.equals(key1));
+    }
+
+    @Test
+    public void testMatchBehaviorWithWpa3AutoUpgradeFlagStates() {
+        // Add a PSK network and manually add SAE type (added by the framework)
+        WifiConfiguration configPskWithSaeSecurityType =
+                WifiConfigurationTestUtil.createPskSaeNetwork(TEST_SSID);
+
+        // Create a WPA3 SAE only scan result
+        WifiConfiguration configSaeOnly =
+                WifiConfigurationTestUtil.createSaeNetwork(TEST_SSID);
+        ScanResult scanResult = WifiConfigurationTestUtil.createScanDetailForNetwork(configSaeOnly,
+                TEST_BSSID, 0, 0, 0, 0).getScanResult();
+
+        // Default setting of the auto-upgrade flag is enabled.
+        assertNotNull(ScanResultMatchInfo
+                .getBestMatchingSecurityParams(configPskWithSaeSecurityType, scanResult));
+        SecurityParams params = SecurityParams.createSecurityParamsBySecurityType(
+                WifiConfiguration.SECURITY_TYPE_SAE);
+        List<SecurityParams> securityParamsList = new ArrayList<>();
+        securityParamsList.add(params);
+        assertNotNull(ScanResultMatchInfo
+                .getBestMatchingSecurityParams(configPskWithSaeSecurityType, securityParamsList));
+
+        ScanResultMatchInfo fromScanResult = ScanResultMatchInfo.fromScanResult(scanResult);
+        ScanResultMatchInfo fromWifiConfig = ScanResultMatchInfo.fromWifiConfiguration(
+                configPskWithSaeSecurityType);
+        assertNotNull(fromWifiConfig.matchForNetworkSelection(fromScanResult));
+        assertTrue(fromWifiConfig.networkTypeEquals(fromScanResult));
+
+        // Now change the auto-upgrade flag to disabled.
+        when(mWifiGlobals.isWpa3SaeUpgradeEnabled()).thenReturn(false);
+        assertNull(ScanResultMatchInfo
+                .getBestMatchingSecurityParams(configPskWithSaeSecurityType, scanResult));
+        assertNull(ScanResultMatchInfo
+                .getBestMatchingSecurityParams(configPskWithSaeSecurityType, securityParamsList));
+        assertNull(fromWifiConfig.matchForNetworkSelection(fromScanResult));
+        assertFalse(fromWifiConfig.networkTypeEquals(fromScanResult));
+    }
+
+    @Test
+    public void testMatchBehaviorWithOweAutoUpgradeFlagStates() {
+        // Add a PSK network and manually add OWE type (added by the framework)
+        WifiConfiguration configOpenWithOweSecurityType =
+                WifiConfigurationTestUtil.createOpenOweNetwork(TEST_SSID);
+
+        // Create an OWE only scan result
+        WifiConfiguration configOweOnly =
+                WifiConfigurationTestUtil.createOweNetwork(TEST_SSID);
+        ScanResult scanResult = WifiConfigurationTestUtil.createScanDetailForNetwork(configOweOnly,
+                TEST_BSSID, 0, 0, 0, 0).getScanResult();
+
+        // Default setting of the auto-upgrade flag is enabled.
+        assertNotNull(ScanResultMatchInfo
+                .getBestMatchingSecurityParams(configOpenWithOweSecurityType, scanResult));
+        SecurityParams params = SecurityParams.createSecurityParamsBySecurityType(
+                WifiConfiguration.SECURITY_TYPE_OWE);
+        List<SecurityParams> securityParamsList = new ArrayList<>();
+        securityParamsList.add(params);
+        assertNotNull(ScanResultMatchInfo
+                .getBestMatchingSecurityParams(configOpenWithOweSecurityType, securityParamsList));
+
+        ScanResultMatchInfo fromScanResult = ScanResultMatchInfo.fromScanResult(scanResult);
+        ScanResultMatchInfo fromWifiConfig = ScanResultMatchInfo.fromWifiConfiguration(
+                configOpenWithOweSecurityType);
+        assertNotNull(fromWifiConfig.matchForNetworkSelection(fromScanResult));
+        assertTrue(fromWifiConfig.networkTypeEquals(fromScanResult));
+
+        // Now change the auto-upgrade flag to disabled.
+        when(mWifiGlobals.isOweUpgradeEnabled()).thenReturn(false);
+        assertNull(ScanResultMatchInfo
+                .getBestMatchingSecurityParams(configOpenWithOweSecurityType, scanResult));
+        assertNull(ScanResultMatchInfo
+                .getBestMatchingSecurityParams(configOpenWithOweSecurityType, securityParamsList));
+        assertNull(fromWifiConfig.matchForNetworkSelection(fromScanResult));
+        assertFalse(fromWifiConfig.networkTypeEquals(fromScanResult));
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/ScanResults.java b/service/tests/wifitests/src/com/android/server/wifi/ScanResults.java
index 052b4dd..78d5e5c 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/ScanResults.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/ScanResults.java
@@ -134,8 +134,9 @@
             NetworkDetail nd = new NetworkDetail(bssid, ie, anqpLines, freq);
             ScanDetail detail = new ScanDetail(nd, WifiSsid.createFromAsciiEncoded(ssid),
                     bssid, "", rssi, freq,
-                    Long.MAX_VALUE, /* needed so that scan results aren't rejected because
-                                        they are older than scan start */
+                    // needed so that scan results aren't rejected because they are older than scan
+                    // start.
+                    Long.MAX_VALUE,
                     ie, anqpLines, generateIERawDatafromScanResultIE(ie));
             results[i] = detail;
         }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/ScanTestUtil.java b/service/tests/wifitests/src/com/android/server/wifi/ScanTestUtil.java
index f3d8348..45cc482 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/ScanTestUtil.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/ScanTestUtil.java
@@ -25,11 +25,17 @@
 import android.net.wifi.WifiScanner.ScanData;
 import android.net.wifi.WifiSsid;
 
+import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.MacAddressUtils;
+import com.android.server.wifi.scanner.ChannelHelper;
+import com.android.server.wifi.scanner.ChannelHelper.ChannelCollection;
+
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
 import org.hamcrest.TypeSafeDiagnosingMatcher;
 
 import java.util.Arrays;
+import java.util.Comparator;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -39,7 +45,7 @@
 public class ScanTestUtil {
 
     public static void setupMockChannels(WifiNative wifiNative, int[] channels24, int[] channels5,
-            int[] channelsDfs, int[] channels6) throws Exception {
+            int[] channelsDfs, int[] channels6, int[] channels60) throws Exception {
         when(wifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_24_GHZ))
                 .thenReturn(channels24);
         when(wifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ))
@@ -48,6 +54,8 @@
                 .thenReturn(channelsDfs);
         when(wifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_6_GHZ))
                 .thenReturn(channels6);
+        when(wifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_60_GHZ))
+                .thenReturn(channels60);
     }
 
     public static WifiScanner.ScanSettings createRequest(WifiScanner.ChannelSpec[] channels,
@@ -124,6 +132,10 @@
             mSettings.report_threshold_percent = percent;
             return this;
         }
+        public NativeScanSettingsBuilder withEnable6GhzRnr(boolean enable) {
+            mSettings.enable6GhzRnr = enable;
+            return this;
+        }
 
         /**
          * Add the provided hidden network SSIDs to scan request.
@@ -139,6 +151,16 @@
             return this;
         }
 
+        public NativeScanSettingsBuilder addBucketWithChannelCollection(
+                int period, int reportEvents, ChannelCollection channelCollection) {
+            WifiNative.BucketSettings bucket = new WifiNative.BucketSettings();
+            bucket.bucket = mSettings.num_buckets;
+            bucket.period_ms = period;
+            bucket.report_events = reportEvents;
+            channelCollection.fillBucketSettings(bucket, Integer.MAX_VALUE);
+            return addBucket(bucket);
+        }
+
         public NativeScanSettingsBuilder addBucketWithBand(
                 int period, int reportEvents, int band) {
             WifiNative.BucketSettings bucket = new WifiNative.BucketSettings();
@@ -185,6 +207,33 @@
 
     /**
      * Compute the expected native scan settings that are expected for the given
+     * WifiScanner.ScanSettings using the given ChannelHelper.
+     * This method is created to test 6Ghz PSC scanning.
+     */
+    public static WifiNative.ScanSettings computeSingleScanNativeSettingsWithChannelHelper(
+            WifiScanner.ScanSettings requestSettings, ChannelHelper channelHelper) {
+        int reportEvents = requestSettings.reportEvents | WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
+        NativeScanSettingsBuilder builder = new NativeScanSettingsBuilder()
+                .withBasePeriod(0)
+                .withMaxApPerScan(0)
+                .withMaxPercentToCache(0)
+                .withMaxScansToCache(0)
+                .withType(requestSettings.type);
+        if (SdkLevel.isAtLeastS()) {
+            builder.withEnable6GhzRnr(requestSettings.getRnrSetting()
+                    == WifiScanner.WIFI_RNR_ENABLED
+                    || (requestSettings.getRnrSetting()
+                    == WifiScanner.WIFI_RNR_ENABLED_IF_WIFI_BAND_6_GHZ_SCANNED
+                    && ChannelHelper.is6GhzBandIncluded(requestSettings.band)));
+        }
+        ChannelCollection channelCollection = channelHelper.createChannelCollection();
+        channelCollection.addChannels(requestSettings);
+        builder.addBucketWithChannelCollection(0, reportEvents, channelCollection);
+        return builder.build();
+    }
+
+    /**
+     * Compute the expected native scan settings that are expected for the given
      * WifiScanner.ScanSettings.
      */
     public static WifiNative.ScanSettings computeSingleScanNativeSettings(
@@ -196,6 +245,13 @@
                 .withMaxPercentToCache(0)
                 .withMaxScansToCache(0)
                 .withType(requestSettings.type);
+        if (SdkLevel.isAtLeastS()) {
+            builder.withEnable6GhzRnr(requestSettings.getRnrSetting()
+                    == WifiScanner.WIFI_RNR_ENABLED
+                    || (requestSettings.getRnrSetting()
+                    == WifiScanner.WIFI_RNR_ENABLED_IF_WIFI_BAND_6_GHZ_SCANNED
+                    && ChannelHelper.is6GhzBandIncluded(requestSettings.band)));
+        }
         if (requestSettings.band == WifiScanner.WIFI_BAND_UNSPECIFIED) {
             builder.addBucketWithChannels(0, reportEvents, requestSettings.channels);
         } else {
@@ -239,7 +295,8 @@
     }
 
     public static ScanResult createScanResult(int freq) {
-        return new ScanResult(WifiSsid.createFromAsciiEncoded("AN SSID"), "00:00:00:00:00:00", 0L,
+        return new ScanResult(WifiSsid.createFromAsciiEncoded("AN SSID"),
+                MacAddressUtils.createRandomUnicastAddress().toString(), 0L,
                 -1, null, "", 0, freq, 0);
     }
 
@@ -303,6 +360,24 @@
         }
     }
 
+    private static void assertScanResultsEqualsAnyOrder(String prefix, ScanResult[] expected,
+            ScanResult[] actual) {
+        assertNotNull(prefix + "expected ScanResults was null", expected);
+        assertNotNull(prefix + "actual ScanResults was null", actual);
+        assertEquals(prefix + "results.length", expected.length, actual.length);
+
+        // Sort using the bssids.
+        ScanResult[] sortedExpected = Arrays
+                .stream(expected)
+                .sorted(Comparator.comparing(s -> s.BSSID))
+                .toArray(ScanResult[]::new);
+        ScanResult[] sortedActual = Arrays
+                .stream(actual)
+                .sorted(Comparator.comparing(s -> s.BSSID))
+                .toArray(ScanResult[]::new);
+        assertScanResultsEquals(prefix, sortedExpected, sortedActual);
+    }
+
     /**
      * Asserts if the provided scan results are the same.
      */
@@ -317,13 +392,20 @@
         assertScanResultsEquals("", expected, actual);
     }
 
+    /**
+     * Asserts if the provided scan result arrays are the same.
+     */
+    public static void assertScanResultsEqualsAnyOrder(ScanResult[] expected, ScanResult[] actual) {
+        assertScanResultsEqualsAnyOrder("", expected, actual);
+    }
+
     private static void assertScanDataEquals(String prefix, ScanData expected, ScanData actual) {
         assertNotNull(prefix + "expected ScanData was null", expected);
         assertNotNull(prefix + "actual ScanData was null", actual);
         assertEquals(prefix + "id", expected.getId(), actual.getId());
         assertEquals(prefix + "flags", expected.getFlags(), actual.getFlags());
-        assertEquals(prefix + "band", expected.getBandScanned(),
-                actual.getBandScanned());
+        assertEquals(prefix + "band", expected.getScannedBandsInternal(),
+                actual.getScannedBandsInternal());
         assertScanResultsEquals(prefix, expected.getResults(), actual.getResults());
     }
 
@@ -361,6 +443,7 @@
         assertEquals("percent to cache", expected.report_threshold_percent,
                 actual.report_threshold_percent);
         assertEquals("base period", expected.base_period_ms, actual.base_period_ms);
+        assertEquals("enable 6Ghz RNR", expected.enable6GhzRnr, actual.enable6GhzRnr);
 
         assertEquals("number of buckets", expected.num_buckets, actual.num_buckets);
         assertNotNull("buckets was null", actual.buckets);
diff --git a/service/tests/wifitests/src/com/android/server/wifi/ScoredNetworkNominatorTest.java b/service/tests/wifitests/src/com/android/server/wifi/ScoredNetworkNominatorTest.java
index c9cad7d..e50171a 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/ScoredNetworkNominatorTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/ScoredNetworkNominatorTest.java
@@ -16,6 +16,9 @@
 
 package com.android.server.wifi;
 
+import static android.net.wifi.WifiManager.WIFI_FEATURE_OWE;
+import static android.net.wifi.WifiManager.WIFI_FEATURE_WPA3_SAE;
+
 import static com.android.server.wifi.ScoredNetworkNominator.SETTINGS_GLOBAL_USE_OPEN_WIFI_PACKAGE;
 import static com.android.server.wifi.WifiConfigurationTestUtil.SECURITY_NONE;
 import static com.android.server.wifi.WifiConfigurationTestUtil.SECURITY_PSK;
@@ -39,6 +42,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.server.wifi.WifiNetworkSelector.NetworkNominator.OnConnectableListener;
 import com.android.server.wifi.WifiNetworkSelectorTestUtil.ScanDetailsAndWifiConfigs;
 import com.android.server.wifi.util.WifiPermissionsUtil;
@@ -50,6 +54,7 @@
 import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -74,11 +79,16 @@
     @Mock private WifiConfigManager mWifiConfigManager;
     @Mock private WifiPermissionsUtil mWifiPermissionsUtil;
     @Mock private OnConnectableListener mOnConnectableListener;
+    @Mock private WifiInjector mWifiInjector;
+    @Mock private WifiGlobals mWifiGlobals;
+    @Mock private ActiveModeWarden mActiveModeWarden;
+    @Mock private ClientModeManager mClientModeManager;
     @Captor private ArgumentCaptor<Collection<NetworkKey>> mNetworkKeyCollectionCaptor;
     @Captor private ArgumentCaptor<WifiConfiguration> mWifiConfigCaptor;
 
     private WifiNetworkScoreCache mScoreCache;
     private ScoredNetworkNominator mScoredNetworkNominator;
+    private MockitoSession mSession;
 
     @Before
     public void setUp() throws Exception {
@@ -86,6 +96,17 @@
         mThresholdQualifiedRssi5G = -70;
 
         MockitoAnnotations.initMocks(this);
+        mSession = ExtendedMockito.mockitoSession()
+                .mockStatic(WifiInjector.class, withSettings().lenient())
+                .startMocking();
+        when(WifiInjector.getInstance()).thenReturn(mWifiInjector);
+        when(mWifiInjector.getWifiGlobals()).thenReturn(mWifiGlobals);
+        when(mWifiInjector.getActiveModeWarden()).thenReturn(mActiveModeWarden);
+        when(mActiveModeWarden.getPrimaryClientModeManager()).thenReturn(mClientModeManager);
+        when(mClientModeManager.getSupportedFeatures()).thenReturn(
+                WIFI_FEATURE_OWE | WIFI_FEATURE_WPA3_SAE);
+        when(mWifiGlobals.isWpa3SaeUpgradeEnabled()).thenReturn(true);
+        when(mWifiGlobals.isOweUpgradeEnabled()).thenReturn(true);
 
         when(mFrameworkFacade.getStringSetting(mContext,
                 SETTINGS_GLOBAL_USE_OPEN_WIFI_PACKAGE))
@@ -114,6 +135,9 @@
     @After
     public void tearDown() {
         validateMockitoUsage();
+        if (mSession != null) {
+            mSession.finishMocking();
+        }
     }
 
     @Test
@@ -224,8 +248,8 @@
 
         mContentObserver.onChange(false /* unused */);
 
-        mScoredNetworkNominator.nominateNetworks(null, null, null, false, false,
-                mOnConnectableListener);
+        mScoredNetworkNominator.nominateNetworks(
+                null, false, true, true, mOnConnectableListener);
 
         verifyZeroInteractions(mWifiConfigManager, mNetworkScoreManager);
     }
@@ -250,7 +274,7 @@
 
         verify(mNetworkScoreManager, never()).requestScores(anyCollection());
         verify(mWifiPermissionsUtil).enforceCanAccessScanResults(
-                eq(TEST_PACKAGE_NAME), eq(null), eq(TEST_UID), nullable(String.class));
+                eq(TEST_PACKAGE_NAME), any(), eq(TEST_UID), nullable(String.class));
     }
 
     @Test
@@ -273,7 +297,7 @@
 
         verify(mNetworkScoreManager, never()).requestScores(anyCollection());
         verify(mWifiPermissionsUtil).enforceCanAccessScanResults(
-                eq(TEST_PACKAGE_NAME), eq(null), eq(TEST_UID), nullable(String.class));
+                eq(TEST_PACKAGE_NAME), any(), eq(TEST_UID), nullable(String.class));
     }
 
     /**
@@ -297,14 +321,14 @@
         WifiConfiguration ephemeralNetworkConfig = WifiNetworkSelectorTestUtil
                 .setupEphemeralNetwork(mWifiConfigManager, 1, scanDetails.get(1), meteredHints[1]);
         // No saved networks.
-        when(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(any(ScanDetail.class)))
+        when(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(any(ScanDetail.class)))
                 .thenReturn(null);
         // But when we create one, this is should be it.
         when(mWifiConfigManager.addOrUpdateNetwork(any(), eq(TEST_UID), eq(TEST_PACKAGE_NAME)))
                 .thenReturn(new NetworkUpdateResult(1));
         // Untrusted networks allowed.
-        mScoredNetworkNominator.nominateNetworks(scanDetails,
-                null, null, false, true, mOnConnectableListener);
+        mScoredNetworkNominator.nominateNetworks(
+                scanDetails, true, true, true, mOnConnectableListener);
         verify(mOnConnectableListener, atLeastOnce())
                 .onConnectable(any(), mWifiConfigCaptor.capture());
         assertTrue(mWifiConfigCaptor.getAllValues().stream()
@@ -331,15 +355,15 @@
                 scanDetails, scores, meteredHints);
 
         // No saved networks.
-        when(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(any(ScanDetail.class)))
+        when(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(any(ScanDetail.class)))
                 .thenReturn(null);
 
         WifiNetworkSelectorTestUtil.setupEphemeralNetwork(
                 mWifiConfigManager, 1, scanDetails.get(1), meteredHints[1]);
 
         // Untrusted networks not allowed.
-        mScoredNetworkNominator.nominateNetworks(scanDetails,
-                null, null, false, false, mOnConnectableListener);
+        mScoredNetworkNominator.nominateNetworks(
+                scanDetails, false, true, true, mOnConnectableListener);
 
         verify(mOnConnectableListener, never()).onConnectable(any(), any());
     }
@@ -368,8 +392,8 @@
         WifiNetworkSelectorTestUtil.configureScoreCache(mScoreCache,
                 scanDetails, scores, meteredHints);
 
-        mScoredNetworkNominator.nominateNetworks(scanDetails,
-                null, null, false, true, mOnConnectableListener);
+        mScoredNetworkNominator.nominateNetworks(
+                scanDetails, true, true, true, mOnConnectableListener);
 
         verify(mOnConnectableListener).onConnectable(any(), mWifiConfigCaptor.capture());
         assertEquals(mWifiConfigCaptor.getValue().networkId, savedConfigs[0].networkId);
@@ -400,8 +424,8 @@
         WifiNetworkSelectorTestUtil.configureScoreCache(mScoreCache,
                 scanDetails, scores, meteredHints);
 
-        mScoredNetworkNominator.nominateNetworks(scanDetails,
-                null, null, false, true, mOnConnectableListener);
+        mScoredNetworkNominator.nominateNetworks(
+                scanDetails, true, true, true, mOnConnectableListener);
 
         verify(mOnConnectableListener).onConnectable(any(), mWifiConfigCaptor.capture());
         assertEquals(mWifiConfigCaptor.getValue().networkId, savedConfigs[0].networkId);
diff --git a/service/tests/wifitests/src/com/android/server/wifi/ScoringParamsTest.java b/service/tests/wifitests/src/com/android/server/wifi/ScoringParamsTest.java
index bea0a1a..905fb29 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/ScoringParamsTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/ScoringParamsTest.java
@@ -255,6 +255,7 @@
     int mBad2GHz, mEntry2GHz, mSufficient2GHz, mGood2GHz;
     int mBad5GHz, mEntry5GHz, mSufficient5GHz, mGood5GHz;
     int mBad6GHz, mEntry6GHz, mSufficient6GHz, mGood6GHz;
+    int mEstimateRssiErrorMargin;
 
     @Mock Context mContext;
     @Spy private MockResources mResources = new MockResources();
@@ -294,6 +295,8 @@
                 R.integer.config_wifiFrameworkScoreLowRssiThreshold6ghz, -60);
         mGood6GHz = setupIntegerResource(
                 R.integer.config_wifiFrameworkScoreGoodRssiThreshold6ghz, -50);
+        mEstimateRssiErrorMargin = setupIntegerResource(
+                R.integer.config_wifiEstimateRssiErrorMarginDb, 5);
     }
 
     /**
@@ -320,5 +323,6 @@
         assertEquals(mSufficient6GHz, mScoringParams.getSufficientRssi(6255));
         assertEquals(mGood6GHz, mScoringParams.getGoodRssi(6275));
         assertEquals(mGood6GHz, mScoringParams.getGoodRssi(ScanResult.BAND_6_GHZ_START_FREQ_MHZ));
+        assertEquals(mEstimateRssiErrorMargin, mScoringParams.getEstimateRssiErrorMargin());
     }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/SelfRecoveryTest.java b/service/tests/wifitests/src/com/android/server/wifi/SelfRecoveryTest.java
index 3538598..9c623d5 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/SelfRecoveryTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/SelfRecoveryTest.java
@@ -42,6 +42,7 @@
     @Mock Context mContext;
     @Mock ActiveModeWarden mActiveModeWarden;
     @Mock Clock mClock;
+    @Mock WifiNative mWifiNative;
 
     @Before
     public void setUp() throws Exception {
@@ -51,7 +52,7 @@
         mResources.setInteger(R.integer.config_wifiMaxNativeFailureSelfRecoveryPerHour,
                 DEFAULT_MAX_RECOVERY_PER_HOUR);
         when(mContext.getResources()).thenReturn(mResources);
-        mSelfRecovery = new SelfRecovery(mContext, mActiveModeWarden, mClock);
+        mSelfRecovery = new SelfRecovery(mContext, mActiveModeWarden, mClock, mWifiNative);
     }
 
     /**
@@ -61,13 +62,15 @@
     @Test
     public void testValidTriggerReasonsSendMessageToWifiController() {
         mSelfRecovery.trigger(SelfRecovery.REASON_LAST_RESORT_WATCHDOG);
-        verify(mActiveModeWarden).recoveryRestartWifi(SelfRecovery.REASON_LAST_RESORT_WATCHDOG);
+        verify(mActiveModeWarden).recoveryRestartWifi(SelfRecovery.REASON_LAST_RESORT_WATCHDOG,
+                SelfRecovery.REASON_STRINGS[SelfRecovery.REASON_LAST_RESORT_WATCHDOG], false);
         reset(mActiveModeWarden);
 
         when(mClock.getElapsedSinceBootMillis())
                 .thenReturn(TimeUnit.HOURS.toMillis(1) + 1);
         mSelfRecovery.trigger(SelfRecovery.REASON_WIFINATIVE_FAILURE);
-        verify(mActiveModeWarden).recoveryRestartWifi(SelfRecovery.REASON_WIFINATIVE_FAILURE);
+        verify(mActiveModeWarden).recoveryRestartWifi(SelfRecovery.REASON_WIFINATIVE_FAILURE,
+                SelfRecovery.REASON_STRINGS[SelfRecovery.REASON_WIFINATIVE_FAILURE], true);
         reset(mActiveModeWarden);
     }
 
@@ -105,25 +108,30 @@
         // aren't ignored
         for (int i = 0; i < DEFAULT_MAX_RECOVERY_PER_HOUR; i++) {
             mSelfRecovery.trigger(SelfRecovery.REASON_WIFINATIVE_FAILURE);
-            verify(mActiveModeWarden).recoveryRestartWifi(SelfRecovery.REASON_WIFINATIVE_FAILURE);
+            verify(mActiveModeWarden).recoveryRestartWifi(SelfRecovery.REASON_WIFINATIVE_FAILURE,
+                    SelfRecovery.REASON_STRINGS[SelfRecovery.REASON_WIFINATIVE_FAILURE], true);
             reset(mActiveModeWarden);
         }
 
         // Verify that further attempts to trigger restarts disable wifi
         mSelfRecovery.trigger(SelfRecovery.REASON_WIFINATIVE_FAILURE);
-        verify(mActiveModeWarden, never())
-                .recoveryRestartWifi(SelfRecovery.REASON_WIFINATIVE_FAILURE);
+        verify(mActiveModeWarden, never()).recoveryRestartWifi(
+                SelfRecovery.REASON_WIFINATIVE_FAILURE,
+                SelfRecovery.REASON_STRINGS[SelfRecovery.REASON_WIFINATIVE_FAILURE], true);
         verify(mActiveModeWarden).recoveryDisableWifi();
         reset(mActiveModeWarden);
 
         mSelfRecovery.trigger(SelfRecovery.REASON_WIFINATIVE_FAILURE);
-        verify(mActiveModeWarden, never()).recoveryRestartWifi(anyInt());
+        verify(mActiveModeWarden, never()).recoveryRestartWifi(
+                SelfRecovery.REASON_WIFINATIVE_FAILURE,
+                SelfRecovery.REASON_STRINGS[SelfRecovery.REASON_WIFINATIVE_FAILURE], true);
         verify(mActiveModeWarden).recoveryDisableWifi();
         reset(mActiveModeWarden);
 
         // Verify L.R.Watchdog can still restart things (It has its own complex limiter)
         mSelfRecovery.trigger(SelfRecovery.REASON_LAST_RESORT_WATCHDOG);
-        verify(mActiveModeWarden).recoveryRestartWifi(SelfRecovery.REASON_LAST_RESORT_WATCHDOG);
+        verify(mActiveModeWarden).recoveryRestartWifi(SelfRecovery.REASON_LAST_RESORT_WATCHDOG,
+                SelfRecovery.REASON_STRINGS[SelfRecovery.REASON_LAST_RESORT_WATCHDOG], false);
         reset(mActiveModeWarden);
 
         // Verify Sta Interface Down will still disable wifi
@@ -135,13 +143,15 @@
         when(mClock.getElapsedSinceBootMillis())
                 .thenReturn(TimeUnit.HOURS.toMillis(1) + 1);
         mSelfRecovery.trigger(SelfRecovery.REASON_LAST_RESORT_WATCHDOG);
-        verify(mActiveModeWarden).recoveryRestartWifi(SelfRecovery.REASON_LAST_RESORT_WATCHDOG);
+        verify(mActiveModeWarden).recoveryRestartWifi(SelfRecovery.REASON_LAST_RESORT_WATCHDOG,
+                SelfRecovery.REASON_STRINGS[SelfRecovery.REASON_LAST_RESORT_WATCHDOG], false);
         reset(mActiveModeWarden);
 
         when(mClock.getElapsedSinceBootMillis())
                 .thenReturn(TimeUnit.HOURS.toMillis(1) + 1);
         mSelfRecovery.trigger(SelfRecovery.REASON_WIFINATIVE_FAILURE);
-        verify(mActiveModeWarden).recoveryRestartWifi(SelfRecovery.REASON_WIFINATIVE_FAILURE);
+        verify(mActiveModeWarden).recoveryRestartWifi(SelfRecovery.REASON_WIFINATIVE_FAILURE,
+                SelfRecovery.REASON_STRINGS[SelfRecovery.REASON_WIFINATIVE_FAILURE], true);
         reset(mActiveModeWarden);
     }
 
@@ -155,14 +165,16 @@
         when(mClock.getElapsedSinceBootMillis()).thenReturn(0L);
         mResources.setInteger(R.integer.config_wifiMaxNativeFailureSelfRecoveryPerHour, 0);
         mSelfRecovery.trigger(SelfRecovery.REASON_WIFINATIVE_FAILURE);
-        verify(mActiveModeWarden, never())
-                .recoveryRestartWifi(SelfRecovery.REASON_WIFINATIVE_FAILURE);
+        verify(mActiveModeWarden, never()).recoveryRestartWifi(
+                SelfRecovery.REASON_WIFINATIVE_FAILURE,
+                SelfRecovery.REASON_STRINGS[SelfRecovery.REASON_WIFINATIVE_FAILURE], true);
         verify(mActiveModeWarden).recoveryDisableWifi();
         reset(mActiveModeWarden);
 
         // Verify L.R.Watchdog can still restart things (It has its own complex limiter)
         mSelfRecovery.trigger(SelfRecovery.REASON_LAST_RESORT_WATCHDOG);
-        verify(mActiveModeWarden).recoveryRestartWifi(SelfRecovery.REASON_LAST_RESORT_WATCHDOG);
+        verify(mActiveModeWarden).recoveryRestartWifi(SelfRecovery.REASON_LAST_RESORT_WATCHDOG,
+                SelfRecovery.REASON_STRINGS[SelfRecovery.REASON_LAST_RESORT_WATCHDOG], false);
     }
 
     /**
@@ -175,7 +187,8 @@
         for (int i = 0; i < DEFAULT_MAX_RECOVERY_PER_HOUR * 2; i++) {
             // Verify L.R.Watchdog can still restart things (It has it's own complex limiter)
             mSelfRecovery.trigger(SelfRecovery.REASON_LAST_RESORT_WATCHDOG);
-            verify(mActiveModeWarden).recoveryRestartWifi(SelfRecovery.REASON_LAST_RESORT_WATCHDOG);
+            verify(mActiveModeWarden).recoveryRestartWifi(SelfRecovery.REASON_LAST_RESORT_WATCHDOG,
+                    SelfRecovery.REASON_STRINGS[SelfRecovery.REASON_LAST_RESORT_WATCHDOG], false);
             reset(mActiveModeWarden);
         }
     }
@@ -190,7 +203,9 @@
         for (int i = 0; i < DEFAULT_MAX_RECOVERY_PER_HOUR * 2; i++) {
             mSelfRecovery.trigger(SelfRecovery.REASON_STA_IFACE_DOWN);
             verify(mActiveModeWarden).recoveryDisableWifi();
-            verify(mActiveModeWarden, never()).recoveryRestartWifi(anyInt());
+            verify(mActiveModeWarden, never()).recoveryRestartWifi(
+                    SelfRecovery.REASON_STA_IFACE_DOWN,
+                    SelfRecovery.REASON_STRINGS[SelfRecovery.REASON_STA_IFACE_DOWN], true);
             reset(mActiveModeWarden);
         }
     }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/SoftApBackupRestoreTest.java b/service/tests/wifitests/src/com/android/server/wifi/SoftApBackupRestoreTest.java
index a1b9174..5e767c5 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/SoftApBackupRestoreTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/SoftApBackupRestoreTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.*;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.Mockito.*;
 
 import android.content.Context;
@@ -27,9 +28,11 @@
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiMigration;
 import android.util.BackupUtils;
+import android.util.SparseIntArray;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.util.ApConfigUtil;
 import com.android.server.wifi.util.SettingsMigrationDataHolder;
 
@@ -70,6 +73,18 @@
     private static final int TEST_BAND = SoftApConfiguration.BAND_5GHZ;
     private static final int TEST_CHANNEL = 40;
     private static final boolean TEST_HIDDEN = false;
+    private static final int TEST_CHANNEL_2G = 1;
+    private static final int TEST_CHANNEL_5G = 149;
+    private static final int TEST_BAND_2G = SoftApConfiguration.BAND_2GHZ;
+    private static final int TEST_BAND_5G = SoftApConfiguration.BAND_5GHZ;
+    private static final boolean TEST_BRIDGED_OPPORTUNISTIC_SHUTDOWN_ENABLED = false;
+    private static final int TEST_MAC_RANDOMIZATIONSETTING =
+            SoftApConfiguration.RANDOMIZATION_NONE;
+    private static final SparseIntArray TEST_CHANNELS = new SparseIntArray() {{
+            put(TEST_BAND_2G, TEST_CHANNEL_2G);
+            put(TEST_BAND_5G, TEST_CHANNEL_5G);
+            }};
+    private static final boolean TEST_80211AX_ENABLED = false;
 
     /**
      * Asserts that the WifiConfigurations equal to SoftApConfiguration.
@@ -230,7 +245,7 @@
      * Verifies that the serialization/de-serialization for wpa3-sae-transition softap config.
      */
     @Test
-    public void testSoftApConfigBackupAndRestoreWithMaxShutDownClientList() throws Exception {
+    public void testSoftApConfigBackupAndRestoreWithMaxShutdownClientList() throws Exception {
         mTestBlockedList.add(MacAddress.fromString(TEST_BLOCKED_CLIENT));
         mTestAllowedList.add(MacAddress.fromString(TEST_ALLOWED_CLIENT));
         SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder();
@@ -254,6 +269,39 @@
     }
 
     /**
+     * Verifies that the serialization/de-serialization for all customized configure field in .
+     */
+    @Test
+    public void testSoftApConfigBackupAndRestoreWithAllConfigInS() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        mTestBlockedList.add(MacAddress.fromString(TEST_BLOCKED_CLIENT));
+        mTestAllowedList.add(MacAddress.fromString(TEST_ALLOWED_CLIENT));
+        SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder();
+        configBuilder.setSsid(TEST_SSID);
+        configBuilder.setBand(SoftApConfiguration.BAND_5GHZ);
+        configBuilder.setChannel(40, SoftApConfiguration.BAND_5GHZ);
+        configBuilder.setPassphrase(TEST_PASSPHRASE, TEST_SECURITY);
+        configBuilder.setHiddenSsid(TEST_HIDDEN);
+        configBuilder.setMaxNumberOfClients(TEST_MAXNUMBEROFCLIENTS);
+        configBuilder.setShutdownTimeoutMillis(TEST_SHUTDOWNTIMEOUTMILLIS);
+        configBuilder.setClientControlByUserEnabled(TEST_CLIENTCONTROLENABLE);
+        configBuilder.setBlockedClientList(mTestBlockedList);
+        configBuilder.setAllowedClientList(mTestAllowedList);
+        configBuilder.setChannels(TEST_CHANNELS);
+        configBuilder.setMacRandomizationSetting(TEST_MAC_RANDOMIZATIONSETTING);
+        configBuilder.setBridgedModeOpportunisticShutdownEnabled(
+                TEST_BRIDGED_OPPORTUNISTIC_SHUTDOWN_ENABLED);
+        configBuilder.setIeee80211axEnabled(TEST_80211AX_ENABLED);
+        SoftApConfiguration config = configBuilder.build();
+
+        byte[] data = mSoftApBackupRestore.retrieveBackupDataFromSoftApConfiguration(config);
+        SoftApConfiguration restoredConfig =
+                mSoftApBackupRestore.retrieveSoftApConfigurationFromBackupData(data);
+
+        assertThat(config).isEqualTo(restoredConfig);
+    }
+
+    /**
      * Verifies that the restore of version 5 backup data will read the auto shutdown enable/disable
      * tag from {@link WifiMigration#loadFromSettings(Context)}
      */
@@ -342,6 +390,32 @@
     }
 
     /**
+     * Verifies that the restore of version 7
+     */
+    @Test
+    public void testSoftApConfigRestoreFromVersion7() throws Exception {
+        mTestBlockedList.add(MacAddress.fromString(TEST_BLOCKED_CLIENT));
+        mTestAllowedList.add(MacAddress.fromString(TEST_ALLOWED_CLIENT));
+        SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder();
+        configBuilder.setSsid(TEST_SSID);
+        configBuilder.setBand(TEST_BAND);
+        configBuilder.setChannel(TEST_CHANNEL, TEST_BAND);
+        configBuilder.setPassphrase(TEST_PASSPHRASE, TEST_SECURITY);
+        configBuilder.setHiddenSsid(TEST_HIDDEN);
+        configBuilder.setMaxNumberOfClients(TEST_MAXNUMBEROFCLIENTS);
+        configBuilder.setShutdownTimeoutMillis(TEST_SHUTDOWNTIMEOUTMILLIS);
+        configBuilder.setClientControlByUserEnabled(TEST_CLIENTCONTROLENABLE);
+        configBuilder.setBlockedClientList(mTestBlockedList);
+        configBuilder.setAllowedClientList(mTestAllowedList);
+
+        SoftApConfiguration expectedConfig = configBuilder.build();
+        SoftApConfiguration restoredConfig = mSoftApBackupRestore
+                .retrieveSoftApConfigurationFromBackupData(
+                retrieveVersion7BackupDataFromSoftApConfiguration(expectedConfig));
+        assertEquals(expectedConfig, restoredConfig);
+    }
+
+    /**
      * This is a copy of the old serialization code (version 6)
      *
      * Changes: Version = 6, AutoShutdown use int type
@@ -367,6 +441,31 @@
         return baos.toByteArray();
     }
 
+    /**
+     * This is a copy of the old serialization code (version 6)
+     *
+     * Changes: Version = 7
+     */
+    private byte[] retrieveVersion7BackupDataFromSoftApConfiguration(SoftApConfiguration config)
+            throws Exception {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        DataOutputStream out = new DataOutputStream(baos);
+
+        out.writeInt(7);
+        BackupUtils.writeString(out, config.getSsid());
+        out.writeInt(config.getBand());
+        out.writeInt(config.getChannel());
+        BackupUtils.writeString(out, config.getPassphrase());
+        out.writeInt(config.getSecurityType());
+        out.writeBoolean(config.isHiddenSsid());
+        out.writeInt(config.getMaxNumberOfClients());
+        out.writeLong(config.getShutdownTimeoutMillis());
+        out.writeBoolean(config.isClientControlByUserEnabled());
+        writeMacAddressList(out, config.getBlockedClientList());
+        writeMacAddressList(out, config.getAllowedClientList());
+        out.writeBoolean(config.isAutoShutdownEnabled());
+        return baos.toByteArray();
+    }
 
     private void writeMacAddressList(DataOutputStream out, List<MacAddress> macList)
             throws IOException {
diff --git a/service/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java b/service/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
index c4c0093..5006fc9 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
@@ -1,5 +1,4 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
+/* Copyright (C) 2016 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -30,18 +29,18 @@
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING;
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED;
 
+import static com.android.server.wifi.ActiveModeManager.ROLE_SOFTAP_LOCAL_ONLY;
+import static com.android.server.wifi.ActiveModeManager.ROLE_SOFTAP_TETHERED;
 import static com.android.server.wifi.LocalOnlyHotspotRequestInfo.HOTSPOT_NO_ERROR;
-import static com.android.server.wifi.util.ApConfigUtil.DEFAULT_AP_CHANNEL;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyLong;
-import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.inOrder;
@@ -52,27 +51,32 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
-import android.app.NotificationManager;
 import android.app.test.TestAlarmManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
 import android.net.MacAddress;
+import android.net.wifi.CoexUnsafeChannel;
+import android.net.wifi.ScanResult;
 import android.net.wifi.SoftApCapability;
 import android.net.wifi.SoftApConfiguration;
 import android.net.wifi.SoftApConfiguration.Builder;
 import android.net.wifi.SoftApInfo;
 import android.net.wifi.WifiClient;
+import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiScanner;
 import android.net.wifi.nl80211.NativeWifiClient;
 import android.os.UserHandle;
+import android.os.WorkSource;
 import android.os.test.TestLooper;
 import android.provider.Settings;
+import android.util.SparseIntArray;
 
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.util.WakeupMessage;
+import com.android.modules.utils.build.SdkLevel;
+import com.android.server.wifi.coex.CoexManager;
 import com.android.wifi.resources.R;
 
 import org.junit.Before;
@@ -80,12 +84,14 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
-import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 
 /** Unit tests for {@link SoftApManager}. */
 @SmallTest
@@ -93,90 +99,215 @@
 
     private static final String TAG = "SoftApManagerTest";
 
+    private static final int TEST_MANAGER_ID = 1000;
     private static final String DEFAULT_SSID = "DefaultTestSSID";
     private static final String TEST_SSID = "TestSSID";
     private static final String TEST_PASSWORD = "TestPassword";
     private static final String TEST_COUNTRY_CODE = "TestCountry";
     private static final String TEST_INTERFACE_NAME = "testif0";
+    private static final String TEST_SECOND_INTERFACE_NAME = "testif1";
     private static final String OTHER_INTERFACE_NAME = "otherif";
-    private static final long TEST_DEFAULT_SHUTDOWN_TIMEOUT_MILLS = 600_000;
-    private static final MacAddress TEST_MAC_ADDRESS = MacAddress.fromString("22:33:44:55:66:77");
-    private static final MacAddress TEST_MAC_ADDRESS_2 = MacAddress.fromString("aa:bb:cc:dd:ee:ff");
-    private static final WifiClient TEST_CONNECTED_CLIENT = new WifiClient(TEST_MAC_ADDRESS);
+    private static final long TEST_DEFAULT_SHUTDOWN_TIMEOUT_MILLIS = 600_000;
+    private static final long TEST_DEFAULT_SHUTDOWN_IDLE_INSTANCE_IN_BRIDGED_MODE_TIMEOUT_MILLIS =
+            300_000;
+    private static final MacAddress TEST_INTERFACE_MAC_ADDRESS =
+            MacAddress.fromString("22:12:11:11:11:11");
+    private static final MacAddress TEST_SECOND_INTERFACE_MAC_ADDRESS =
+            MacAddress.fromString("22:22:22:22:22:22");
+    private static final MacAddress TEST_CLIENT_MAC_ADDRESS =
+            MacAddress.fromString("22:33:44:55:66:77");
+    private static final MacAddress TEST_CLIENT_MAC_ADDRESS_2 =
+            MacAddress.fromString("aa:bb:cc:dd:ee:ff");
+    private static final MacAddress TEST_CLIENT_MAC_ADDRESS_ON_SECOND_IFACE =
+            MacAddress.fromString("aa:bb:cc:11:22:33");
+    private static final WifiClient TEST_CONNECTED_CLIENT = new WifiClient(TEST_CLIENT_MAC_ADDRESS,
+            TEST_INTERFACE_NAME);
     private static final NativeWifiClient TEST_NATIVE_CLIENT = new NativeWifiClient(
-            TEST_MAC_ADDRESS);
-    private static final WifiClient TEST_CONNECTED_CLIENT_2 = new WifiClient(TEST_MAC_ADDRESS_2);
+            TEST_CLIENT_MAC_ADDRESS);
+    private static final WifiClient TEST_CONNECTED_CLIENT_2 =
+            new WifiClient(TEST_CLIENT_MAC_ADDRESS_2, TEST_INTERFACE_NAME);
     private static final NativeWifiClient TEST_NATIVE_CLIENT_2 = new NativeWifiClient(
-            TEST_MAC_ADDRESS_2);
+            TEST_CLIENT_MAC_ADDRESS_2);
+    private static final WifiClient TEST_CONNECTED_CLIENT_ON_SECOND_IFACE =
+            new WifiClient(TEST_CLIENT_MAC_ADDRESS_ON_SECOND_IFACE, TEST_SECOND_INTERFACE_NAME);
     private static final int TEST_AP_FREQUENCY = 2412;
+    private static final int TEST_AP_FREQUENCY_5G = 5220;
     private static final int TEST_AP_BANDWIDTH_FROM_IFACE_CALLBACK =
             SoftApInfo.CHANNEL_WIDTH_20MHZ_NOHT;
     private static final int TEST_AP_BANDWIDTH_IN_SOFTAPINFO = SoftApInfo.CHANNEL_WIDTH_20MHZ_NOHT;
     private static final int[] EMPTY_CHANNEL_ARRAY = {};
+    private static final int[] ALLOWED_2G_FREQS = {2462}; //ch# 11
+    private static final int[] ALLOWED_5G_FREQS = {5745, 5765}; //ch# 149, 153
+    private static final int[] ALLOWED_6G_FREQS = {5945, 5965};
+    private static final int[] ALLOWED_60G_FREQS = {58320, 60480}; // ch# 1, 2
+    private static final WorkSource TEST_WORKSOURCE = new WorkSource();
     private SoftApConfiguration mDefaultApConfig = createDefaultApConfig();
+    private final int mBand256G = SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ
+            | SoftApConfiguration.BAND_6GHZ;
+    private static final int[] TEST_SUPPORTED_24G_CHANNELS = new int[] {1, 2};
+    private static final int[] TEST_SUPPORTED_5G_CHANNELS = new int[] {36, 149};
 
     private TestLooper mLooper;
     private TestAlarmManager mAlarmManager;
     private SoftApInfo mTestSoftApInfo;
+    private SoftApInfo mTestSoftApInfoOnSecondInterface;
+    private Map<String, SoftApInfo> mTestSoftApInfoMap = new HashMap<>();
+    private Map<String, List<WifiClient>> mTestWifiClientsMap = new HashMap<>();
+    private List<WifiClient> mCurrentConnectedTestedClientListOnTestInterface = new ArrayList();
+    private List<WifiClient> mCurrentConnectedTestedClientListOnSecondInterface = new ArrayList();
     private SoftApCapability mTestSoftApCapability;
+    private List<ClientModeManager> mTestClientModeManagers = new ArrayList<>();
 
     @Mock WifiContext mContext;
     @Mock Resources mResources;
     @Mock WifiNative mWifiNative;
-    @Mock WifiManager.SoftApCallback mCallback;
-    @Mock ActiveModeManager.Listener mListener;
+    @Mock CoexManager mCoexManager;
+    @Mock WifiServiceImpl.SoftApCallbackInternal mCallback;
+    @Mock ActiveModeManager.Listener<SoftApManager> mListener;
     @Mock FrameworkFacade mFrameworkFacade;
     @Mock WifiApConfigStore mWifiApConfigStore;
-    @Mock WifiMetrics mWifiMetrics;
     @Mock SarManager mSarManager;
-    @Mock BaseWifiDiagnostics mWifiDiagnostics;
-    @Mock NotificationManager mNotificationManager;
+    @Mock WifiMetrics mWifiMetrics;
+    @Mock WifiDiagnostics mWifiDiagnostics;
+    @Mock WifiNotificationManager mWifiNotificationManager;
     @Mock SoftApNotifier mFakeSoftApNotifier;
+    @Mock ClientModeImplMonitor mCmiMonitor;
+    @Mock ActiveModeWarden mActiveModeWarden;
+    @Mock ClientModeManager mPrimaryConcreteClientModeManager;
+    @Mock ClientModeManager mSecondConcreteClientModeManager;
+    @Mock ConcreteClientModeManager mConcreteClientModeManager;
+    @Mock WifiInfo mPrimaryWifiInfo;
+    @Mock WifiInfo mSecondWifiInfo;
 
     final ArgumentCaptor<WifiNative.InterfaceCallback> mWifiNativeInterfaceCallbackCaptor =
             ArgumentCaptor.forClass(WifiNative.InterfaceCallback.class);
+
     final ArgumentCaptor<WifiNative.SoftApListener> mSoftApListenerCaptor =
             ArgumentCaptor.forClass(WifiNative.SoftApListener.class);
 
+    // CoexListener will only be captured if SdkLevel is at least S
+    private final ArgumentCaptor<CoexManager.CoexListener> mCoexListenerCaptor =
+            ArgumentCaptor.forClass(CoexManager.CoexListener.class);
+
+    private final ArgumentCaptor<ClientModeImplListener> mCmiListenerCaptor =
+            ArgumentCaptor.forClass(ClientModeImplListener.class);
+
     SoftApManager mSoftApManager;
 
+    /** Old callback event from wificond */
+    private void mockChannelSwitchEvent(int frequency, int bandwidth) {
+        mSoftApListenerCaptor.getValue().onInfoChanged(
+                TEST_INTERFACE_NAME, frequency, bandwidth, 0, null);
+    }
+
+    /** New callback event from hostapd */
+    private void mockApInfoChangedEvent(SoftApInfo apInfo) {
+        mSoftApListenerCaptor.getValue().onInfoChanged(
+                apInfo.getApInstanceIdentifier(), apInfo.getFrequency(), apInfo.getBandwidth(),
+                apInfo.getWifiStandardInternal(), apInfo.getBssidInternal());
+        mTestSoftApInfoMap.put(apInfo.getApInstanceIdentifier(), apInfo);
+        mTestWifiClientsMap.put(apInfo.getApInstanceIdentifier(), new ArrayList<WifiClient>());
+    }
+
+    private void mockClientConnectedEvent(MacAddress mac, boolean isConnected,
+            String apIfaceInstance, boolean updateTheTestMap) {
+        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
+                apIfaceInstance, mac, isConnected);
+        if (mac == null || !updateTheTestMap) return;
+        WifiClient client = new WifiClient(mac, apIfaceInstance);
+        List<WifiClient> targetList;
+        if (apIfaceInstance.equals(TEST_INTERFACE_NAME)) {
+            targetList = mCurrentConnectedTestedClientListOnTestInterface;
+        } else {
+            targetList = mCurrentConnectedTestedClientListOnSecondInterface;
+        }
+        if (isConnected) {
+            targetList.add(client);
+        } else {
+            targetList.remove(client);
+        }
+        mTestWifiClientsMap.put(apIfaceInstance, targetList);
+    }
+
     /** Sets up test. */
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mLooper = new TestLooper();
 
-        when(mWifiNative.isSetMacAddressSupported(any())).thenReturn(true);
-        when(mWifiNative.setMacAddress(any(), any())).thenReturn(true);
-        when(mWifiNative.startSoftAp(eq(TEST_INTERFACE_NAME), any(), any())).thenReturn(true);
-
+        when(mWifiNative.isApSetMacAddressSupported(any())).thenReturn(true);
+        when(mWifiNative.setApMacAddress(any(), any())).thenReturn(true);
+        when(mWifiNative.startSoftAp(eq(TEST_INTERFACE_NAME), any(), anyBoolean(),
+                any(WifiNative.SoftApListener.class))).thenReturn(true);
+        when(mWifiNative.setupInterfaceForSoftApMode(any(), any(), anyInt(), anyBoolean()))
+                .thenReturn(TEST_INTERFACE_NAME);
         when(mFrameworkFacade.getIntegerSetting(
                 mContext, Settings.Global.SOFT_AP_TIMEOUT_ENABLED, 1)).thenReturn(1);
         mAlarmManager = new TestAlarmManager();
         when(mContext.getSystemService(Context.ALARM_SERVICE))
                 .thenReturn(mAlarmManager.getAlarmManager());
         when(mContext.getResources()).thenReturn(mResources);
-        when(mContext.getSystemService(NotificationManager.class))
-                .thenReturn(mNotificationManager);
         when(mContext.getWifiOverlayApkPkgName()).thenReturn("test.com.android.wifi.resources");
 
         when(mResources.getInteger(R.integer.config_wifiFrameworkSoftApShutDownTimeoutMilliseconds))
-                .thenReturn((int) TEST_DEFAULT_SHUTDOWN_TIMEOUT_MILLS);
-        when(mWifiNative.setCountryCodeHal(
+                .thenReturn((int) TEST_DEFAULT_SHUTDOWN_TIMEOUT_MILLIS);
+        when(mResources.getInteger(R.integer
+                .config_wifiFrameworkSoftApShutDownIdleInstanceInBridgedModeTimeoutMillisecond))
+                .thenReturn(
+                (int) TEST_DEFAULT_SHUTDOWN_IDLE_INSTANCE_IN_BRIDGED_MODE_TIMEOUT_MILLIS);
+        when(mWifiNative.setApCountryCode(
                 TEST_INTERFACE_NAME, TEST_COUNTRY_CODE.toUpperCase(Locale.ROOT)))
                 .thenReturn(true);
-        when(mWifiNative.getFactoryMacAddress(any())).thenReturn(TEST_MAC_ADDRESS);
+        when(mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_24_GHZ))
+                .thenReturn(ALLOWED_2G_FREQS);
+        when(mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ))
+                .thenReturn(ALLOWED_5G_FREQS);
+        when(mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_6_GHZ))
+                .thenReturn(ALLOWED_6G_FREQS);
+        when(mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_60_GHZ))
+                .thenReturn(ALLOWED_60G_FREQS);
+        when(mWifiNative.getApFactoryMacAddress(any())).thenReturn(TEST_INTERFACE_MAC_ADDRESS);
         when(mWifiApConfigStore.randomizeBssidIfUnset(any(), any())).thenAnswer(
                 (invocation) -> invocation.getArgument(1));
+        when(mActiveModeWarden.getClientModeManagers())
+                .thenReturn(mTestClientModeManagers);
+        mTestClientModeManagers.add(mPrimaryConcreteClientModeManager);
+        when(mPrimaryConcreteClientModeManager.syncRequestConnectionInfo())
+                .thenReturn(mPrimaryWifiInfo);
+        when(mConcreteClientModeManager.syncRequestConnectionInfo())
+                .thenReturn(mPrimaryWifiInfo);
+        when(mWifiNative.forceClientDisconnect(any(), any(), anyInt())).thenReturn(true);
         mTestSoftApInfo = new SoftApInfo();
         mTestSoftApInfo.setFrequency(TEST_AP_FREQUENCY);
         mTestSoftApInfo.setBandwidth(TEST_AP_BANDWIDTH_IN_SOFTAPINFO);
+        mTestSoftApInfo.setBssid(TEST_INTERFACE_MAC_ADDRESS);
+        mTestSoftApInfo.setApInstanceIdentifier(TEST_INTERFACE_NAME);
+        mTestSoftApInfo.setAutoShutdownTimeoutMillis(TEST_DEFAULT_SHUTDOWN_TIMEOUT_MILLIS);
+        mTestSoftApInfoOnSecondInterface = new SoftApInfo();
+        mTestSoftApInfoOnSecondInterface.setFrequency(TEST_AP_FREQUENCY_5G);
+        mTestSoftApInfoOnSecondInterface.setBandwidth(TEST_AP_BANDWIDTH_IN_SOFTAPINFO);
+        mTestSoftApInfoOnSecondInterface.setBssid(TEST_SECOND_INTERFACE_MAC_ADDRESS);
+        mTestSoftApInfoOnSecondInterface.setApInstanceIdentifier(TEST_SECOND_INTERFACE_NAME);
+        mTestSoftApInfoOnSecondInterface.setAutoShutdownTimeoutMillis(
+                TEST_DEFAULT_SHUTDOWN_TIMEOUT_MILLIS);
         // Default set up all features support.
         long testSoftApFeature = SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT
                 | SoftApCapability.SOFTAP_FEATURE_ACS_OFFLOAD
-                | SoftApCapability.SOFTAP_FEATURE_WPA3_SAE;
+                | SoftApCapability.SOFTAP_FEATURE_WPA3_SAE
+                | SoftApCapability.SOFTAP_FEATURE_MAC_ADDRESS_CUSTOMIZATION;
         mTestSoftApCapability = new SoftApCapability(testSoftApFeature);
         mTestSoftApCapability.setMaxSupportedClients(10);
+        mTestSoftApCapability.setSupportedChannelList(
+                SoftApConfiguration.BAND_2GHZ, TEST_SUPPORTED_24G_CHANNELS);
+        mTestSoftApCapability.setSupportedChannelList(
+                SoftApConfiguration.BAND_5GHZ, TEST_SUPPORTED_5G_CHANNELS);
+        when(mWifiApConfigStore.getApConfiguration()).thenReturn(mDefaultApConfig);
+        when(mWifiNative.isHalStarted()).thenReturn(true);
+
+        mTestSoftApInfoMap.clear();
+        mTestWifiClientsMap.clear();
+        mCurrentConnectedTestedClientListOnTestInterface.clear();
+        mCurrentConnectedTestedClientListOnSecondInterface.clear();
     }
 
     private SoftApConfiguration createDefaultApConfig() {
@@ -185,22 +316,29 @@
         return defaultConfigBuilder.build();
     }
 
-    private SoftApManager createSoftApManager(SoftApModeConfiguration config, String countryCode) {
-        if (config.getSoftApConfiguration() == null) {
-            when(mWifiApConfigStore.getApConfiguration()).thenReturn(mDefaultApConfig);
-        }
-        SoftApManager newSoftApManager = new SoftApManager(mContext,
-                                                           mLooper.getLooper(),
-                                                           mFrameworkFacade,
-                                                           mWifiNative,
-                                                           countryCode,
-                                                           mListener,
-                                                           mCallback,
-                                                           mWifiApConfigStore,
-                                                           config,
-                                                           mWifiMetrics,
-                                                           mSarManager,
-                                                           mWifiDiagnostics);
+    private SoftApManager createSoftApManager(SoftApModeConfiguration config, String countryCode,
+            ActiveModeManager.SoftApRole role) {
+        SoftApManager newSoftApManager = new SoftApManager(
+                mContext,
+                mLooper.getLooper(),
+                mFrameworkFacade,
+                mWifiNative,
+                mCoexManager,
+                countryCode,
+                mListener,
+                mCallback,
+                mWifiApConfigStore,
+                config,
+                mWifiMetrics,
+                mSarManager,
+                mWifiDiagnostics,
+                mFakeSoftApNotifier,
+                mCmiMonitor,
+                mActiveModeWarden,
+                TEST_MANAGER_ID,
+                TEST_WORKSOURCE,
+                role,
+                false);
         mLooper.dispatchAll();
 
         return newSoftApManager;
@@ -263,40 +401,21 @@
     /** Tests softap startup if default config fails to load. **/
     @Test
     public void startSoftApDefaultConfigFailedToLoad() throws Exception {
-        when(mWifiNative.setupInterfaceForSoftApMode(any())).thenReturn(TEST_INTERFACE_NAME);
-
         when(mWifiApConfigStore.getApConfiguration()).thenReturn(null);
         SoftApModeConfiguration nullApConfig =
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mTestSoftApCapability);
-        SoftApManager newSoftApManager = new SoftApManager(mContext,
-                                                           mLooper.getLooper(),
-                                                           mFrameworkFacade,
-                                                           mWifiNative,
-                                                           TEST_COUNTRY_CODE,
-                                                           mListener,
-                                                           mCallback,
-                                                           mWifiApConfigStore,
-                                                           nullApConfig,
-                                                           mWifiMetrics,
-                                                           mSarManager,
-                                                           mWifiDiagnostics);
-        mLooper.dispatchAll();
-        newSoftApManager.start();
-        mLooper.dispatchAll();
+        mSoftApManager = createSoftApManager(nullApConfig, TEST_COUNTRY_CODE, ROLE_SOFTAP_TETHERED);
         verify(mCallback).onStateChanged(WifiManager.WIFI_AP_STATE_FAILED,
                 WifiManager.SAP_START_FAILURE_GENERAL);
-        verify(mListener).onStartFailure();
+        verify(mListener).onStartFailure(mSoftApManager);
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
-        verify(mContext, times(2)).sendStickyBroadcastAsUser(intentCaptor.capture(),
+        verify(mContext).sendStickyBroadcastAsUser(intentCaptor.capture(),
                 eq(UserHandle.ALL));
 
         List<Intent> capturedIntents = intentCaptor.getAllValues();
-        checkApStateChangedBroadcast(capturedIntents.get(0), WIFI_AP_STATE_ENABLING,
-                WIFI_AP_STATE_DISABLED, HOTSPOT_NO_ERROR, TEST_INTERFACE_NAME,
-                nullApConfig.getTargetMode());
-        checkApStateChangedBroadcast(capturedIntents.get(1), WIFI_AP_STATE_FAILED,
-                WIFI_AP_STATE_ENABLING, WifiManager.SAP_START_FAILURE_GENERAL, TEST_INTERFACE_NAME,
+        checkApStateChangedBroadcast(capturedIntents.get(0), WIFI_AP_STATE_FAILED,
+                WIFI_AP_STATE_DISABLED, WifiManager.SAP_START_FAILURE_GENERAL, null,
                 nullApConfig.getTargetMode());
     }
 
@@ -307,34 +426,16 @@
     @Test
     public void testSetupForSoftApModeNullApInterfaceNameFailureIncrementsMetrics()
             throws Exception {
-        when(mWifiNative.setupInterfaceForSoftApMode(any())).thenReturn(null);
-
-        SoftApModeConfiguration config = new SoftApModeConfiguration(
-                WifiManager.IFACE_IP_MODE_TETHERED, new SoftApConfiguration.Builder().build(),
-                mTestSoftApCapability);
-
+        when(mWifiNative.setupInterfaceForSoftApMode(
+                    any(), any(), anyInt(), anyBoolean())).thenReturn(null);
         when(mWifiApConfigStore.getApConfiguration()).thenReturn(null);
         SoftApModeConfiguration nullApConfig =
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mTestSoftApCapability);
-        SoftApManager newSoftApManager = new SoftApManager(mContext,
-                                                           mLooper.getLooper(),
-                                                           mFrameworkFacade,
-                                                           mWifiNative,
-                                                           TEST_COUNTRY_CODE,
-                                                           mListener,
-                                                           mCallback,
-                                                           mWifiApConfigStore,
-                                                           nullApConfig,
-                                                           mWifiMetrics,
-                                                           mSarManager,
-                                                           mWifiDiagnostics);
-        mLooper.dispatchAll();
-        newSoftApManager.start();
-        mLooper.dispatchAll();
+        mSoftApManager = createSoftApManager(nullApConfig, TEST_COUNTRY_CODE, ROLE_SOFTAP_TETHERED);
         verify(mCallback).onStateChanged(WifiManager.WIFI_AP_STATE_FAILED,
                 WifiManager.SAP_START_FAILURE_GENERAL);
-        verify(mListener).onStartFailure();
+        verify(mListener).onStartFailure(mSoftApManager);
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mContext).sendStickyBroadcastAsUser(intentCaptor.capture(),
                 eq(UserHandle.ALL));
@@ -354,34 +455,15 @@
     @Test
     public void testSetupForSoftApModeEmptyInterfaceNameFailureIncrementsMetrics()
             throws Exception {
-        when(mWifiNative.setupInterfaceForSoftApMode(any())).thenReturn("");
-
-        SoftApModeConfiguration config = new SoftApModeConfiguration(
-                WifiManager.IFACE_IP_MODE_TETHERED, new SoftApConfiguration.Builder().build(),
-                mTestSoftApCapability);
-
-        when(mWifiApConfigStore.getApConfiguration()).thenReturn(null);
+        when(mWifiNative.setupInterfaceForSoftApMode(
+                    any(), any(), anyInt(), anyBoolean())).thenReturn("");
         SoftApModeConfiguration nullApConfig =
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mTestSoftApCapability);
-        SoftApManager newSoftApManager = new SoftApManager(mContext,
-                                                           mLooper.getLooper(),
-                                                           mFrameworkFacade,
-                                                           mWifiNative,
-                                                           TEST_COUNTRY_CODE,
-                                                           mListener,
-                                                           mCallback,
-                                                           mWifiApConfigStore,
-                                                           nullApConfig,
-                                                           mWifiMetrics,
-                                                           mSarManager,
-                                                           mWifiDiagnostics);
-        mLooper.dispatchAll();
-        newSoftApManager.start();
-        mLooper.dispatchAll();
+        mSoftApManager = createSoftApManager(nullApConfig, TEST_COUNTRY_CODE, ROLE_SOFTAP_TETHERED);
         verify(mCallback).onStateChanged(WifiManager.WIFI_AP_STATE_FAILED,
                 WifiManager.SAP_START_FAILURE_GENERAL);
-        verify(mListener).onStartFailure();
+        verify(mListener).onStartFailure(mSoftApManager);
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mContext).sendStickyBroadcastAsUser(intentCaptor.capture(),
                 eq(UserHandle.ALL));
@@ -407,25 +489,9 @@
                 WifiManager.IFACE_IP_MODE_TETHERED, configBuilder.build(),
                 mTestSoftApCapability);
 
-        when(mWifiNative.setupInterfaceForSoftApMode(any())).thenReturn(TEST_INTERFACE_NAME);
+        mSoftApManager = createSoftApManager(softApConfig, null, ROLE_SOFTAP_TETHERED);
 
-        SoftApManager newSoftApManager = new SoftApManager(mContext,
-                mLooper.getLooper(),
-                mFrameworkFacade,
-                mWifiNative,
-                null,
-                mListener,
-                mCallback,
-                mWifiApConfigStore,
-                softApConfig,
-                mWifiMetrics,
-                mSarManager,
-                mWifiDiagnostics);
-        mLooper.dispatchAll();
-        newSoftApManager.start();
-        mLooper.dispatchAll();
-
-        verify(mWifiNative, never()).setCountryCodeHal(eq(TEST_INTERFACE_NAME), any());
+        verify(mWifiNative, never()).setApCountryCode(eq(TEST_INTERFACE_NAME), any());
 
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mContext, times(2)).sendStickyBroadcastAsUser(intentCaptor.capture(),
@@ -453,28 +519,14 @@
                 WifiManager.IFACE_IP_MODE_TETHERED, configBuilder.build(),
                 mTestSoftApCapability);
 
-        when(mWifiNative.setupInterfaceForSoftApMode(any())).thenReturn(TEST_INTERFACE_NAME);
-        when(mWifiNative.setCountryCodeHal(
+        when(mWifiNative.setApCountryCode(
                 TEST_INTERFACE_NAME, TEST_COUNTRY_CODE.toUpperCase(Locale.ROOT)))
                 .thenReturn(false);
 
-        SoftApManager newSoftApManager = new SoftApManager(mContext,
-                                                           mLooper.getLooper(),
-                                                           mFrameworkFacade,
-                                                           mWifiNative,
-                                                           TEST_COUNTRY_CODE,
-                                                           mListener,
-                                                           mCallback,
-                                                           mWifiApConfigStore,
-                                                           softApConfig,
-                                                           mWifiMetrics,
-                                                           mSarManager,
-                                                           mWifiDiagnostics);
-        mLooper.dispatchAll();
-        newSoftApManager.start();
-        mLooper.dispatchAll();
 
-        verify(mWifiNative).setCountryCodeHal(
+        mSoftApManager = createSoftApManager(softApConfig, TEST_COUNTRY_CODE, ROLE_SOFTAP_TETHERED);
+
+        verify(mWifiNative).setApCountryCode(
                 TEST_INTERFACE_NAME, TEST_COUNTRY_CODE.toUpperCase(Locale.ROOT));
 
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -504,7 +556,7 @@
                 mTestSoftApCapability);
 
         startSoftApAndVerifyEnabled(softApConfig, null);
-        verify(mWifiNative, never()).setCountryCodeHal(eq(TEST_INTERFACE_NAME), any());
+        verify(mWifiNative, never()).setApCountryCode(eq(TEST_INTERFACE_NAME), any());
     }
 
     /**
@@ -514,14 +566,14 @@
     @Test
     public void startSoftApOnAnyGhzNoFailForNoCountryCode() throws Exception {
         Builder configBuilder = new SoftApConfiguration.Builder();
-        configBuilder.setBand(SoftApConfiguration.BAND_ANY);
+        configBuilder.setBand(mBand256G);
         configBuilder.setSsid(TEST_SSID);
         SoftApModeConfiguration softApConfig = new SoftApModeConfiguration(
                 WifiManager.IFACE_IP_MODE_TETHERED, configBuilder.build(),
                 mTestSoftApCapability);
 
         startSoftApAndVerifyEnabled(softApConfig, null);
-        verify(mWifiNative, never()).setCountryCodeHal(eq(TEST_INTERFACE_NAME), any());
+        verify(mWifiNative, never()).setApCountryCode(eq(TEST_INTERFACE_NAME), any());
     }
 
     /**
@@ -537,10 +589,10 @@
                 WifiManager.IFACE_IP_MODE_TETHERED, configBuilder.build(),
                 mTestSoftApCapability);
 
-        when(mWifiNative.setCountryCodeHal(eq(TEST_INTERFACE_NAME), any())).thenReturn(false);
+        when(mWifiNative.setApCountryCode(eq(TEST_INTERFACE_NAME), any())).thenReturn(false);
 
         startSoftApAndVerifyEnabled(softApConfig, TEST_COUNTRY_CODE);
-        verify(mWifiNative).setCountryCodeHal(
+        verify(mWifiNative).setApCountryCode(
                 TEST_INTERFACE_NAME, TEST_COUNTRY_CODE.toUpperCase(Locale.ROOT));
     }
 
@@ -551,22 +603,22 @@
     @Test
     public void startSoftApOnAnyNoFailForCountryCodeSetFailure() throws Exception {
         Builder configBuilder = new SoftApConfiguration.Builder();
-        configBuilder.setBand(SoftApConfiguration.BAND_ANY);
+        configBuilder.setBand(mBand256G);
         configBuilder.setSsid(TEST_SSID);
         SoftApModeConfiguration softApConfig = new SoftApModeConfiguration(
                 WifiManager.IFACE_IP_MODE_TETHERED, configBuilder.build(),
                 mTestSoftApCapability);
 
-        when(mWifiNative.setCountryCodeHal(eq(TEST_INTERFACE_NAME), any())).thenReturn(false);
+        when(mWifiNative.setApCountryCode(eq(TEST_INTERFACE_NAME), any())).thenReturn(false);
 
         startSoftApAndVerifyEnabled(softApConfig, TEST_COUNTRY_CODE);
-        verify(mWifiNative).setCountryCodeHal(
+        verify(mWifiNative).setApCountryCode(
                 TEST_INTERFACE_NAME, TEST_COUNTRY_CODE.toUpperCase(Locale.ROOT));
     }
 
     /**
      * Tests that the NO_CHANNEL error is propagated and properly reported when starting softap and
-     * a valid channel cannot be determined.
+     * a valid channel cannot be determined from WifiNative.
      */
     @Test
     public void startSoftApFailNoChannel() throws Exception {
@@ -580,24 +632,8 @@
 
         when(mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ))
                 .thenReturn(EMPTY_CHANNEL_ARRAY);
-        when(mWifiNative.setupInterfaceForSoftApMode(any())).thenReturn(TEST_INTERFACE_NAME);
-        when(mWifiNative.isHalStarted()).thenReturn(true);
 
-        SoftApManager newSoftApManager = new SoftApManager(mContext,
-                                                           mLooper.getLooper(),
-                                                           mFrameworkFacade,
-                                                           mWifiNative,
-                                                           TEST_COUNTRY_CODE,
-                                                           mListener,
-                                                           mCallback,
-                                                           mWifiApConfigStore,
-                                                           softApConfig,
-                                                           mWifiMetrics,
-                                                           mSarManager,
-                                                           mWifiDiagnostics);
-        mLooper.dispatchAll();
-        newSoftApManager.start();
-        mLooper.dispatchAll();
+        mSoftApManager = createSoftApManager(softApConfig, TEST_COUNTRY_CODE, ROLE_SOFTAP_TETHERED);
 
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mContext, times(2)).sendStickyBroadcastAsUser(intentCaptor.capture(),
@@ -617,54 +653,23 @@
      */
     @Test
     public void startSoftApApInterfaceFailedToStart() throws Exception {
-        when(mWifiNative.setupInterfaceForSoftApMode(any())).thenReturn(TEST_INTERFACE_NAME);
-        when(mWifiNative.startSoftAp(eq(TEST_INTERFACE_NAME), any(), any())).thenReturn(false);
+        when(mWifiNative.startSoftAp(eq(TEST_INTERFACE_NAME), any(), anyBoolean(),
+                any(WifiNative.SoftApListener.class))).thenReturn(false);
 
         SoftApModeConfiguration softApModeConfig =
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, mDefaultApConfig,
                 mTestSoftApCapability);
 
-        SoftApManager newSoftApManager = new SoftApManager(mContext,
-                                                           mLooper.getLooper(),
-                                                           mFrameworkFacade,
-                                                           mWifiNative,
-                                                           TEST_COUNTRY_CODE,
-                                                           mListener,
-                                                           mCallback,
-                                                           mWifiApConfigStore,
-                                                           softApModeConfig,
-                                                           mWifiMetrics,
-                                                           mSarManager,
-                                                           mWifiDiagnostics);
+        mSoftApManager = createSoftApManager(
+                softApModeConfig, TEST_COUNTRY_CODE, ROLE_SOFTAP_TETHERED);
 
-        mLooper.dispatchAll();
-        newSoftApManager.start();
-        mLooper.dispatchAll();
         verify(mCallback).onStateChanged(WifiManager.WIFI_AP_STATE_FAILED,
                 WifiManager.SAP_START_FAILURE_GENERAL);
-        verify(mListener).onStartFailure();
+        verify(mListener).onStartFailure(mSoftApManager);
         verify(mWifiNative).teardownInterface(TEST_INTERFACE_NAME);
     }
 
     /**
-     * Tests the handling of stop command when soft AP is not started.
-     */
-    @Test
-    public void stopWhenNotStarted() throws Exception {
-        mSoftApManager = createSoftApManager(
-                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
-                mTestSoftApCapability), TEST_COUNTRY_CODE);
-        mSoftApManager.stop();
-        mLooper.dispatchAll();
-        /* Verify no state changes. */
-        verify(mCallback, never()).onStateChanged(anyInt(), anyInt());
-        verifyNoMoreInteractions(mListener);
-        verify(mSarManager, never()).setSapWifiState(anyInt());
-        verify(mContext, never()).sendStickyBroadcastAsUser(any(), any());
-        verify(mWifiNative, never()).teardownInterface(anyString());
-    }
-
-    /**
      * Tests the handling of stop command when soft AP is started.
      */
     @Test
@@ -680,7 +685,6 @@
         InOrder order = inOrder(mCallback, mListener, mContext);
 
         mSoftApManager.stop();
-        assertTrue(mSoftApManager.isStopping());
         mLooper.dispatchAll();
 
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -699,8 +703,7 @@
         checkApStateChangedBroadcast(intentCaptor.getValue(), WIFI_AP_STATE_DISABLED,
                 WIFI_AP_STATE_DISABLING, HOTSPOT_NO_ERROR, TEST_INTERFACE_NAME,
                 softApModeConfig.getTargetMode());
-        order.verify(mListener).onStopped();
-        assertFalse(mSoftApManager.isStopping());
+        order.verify(mListener).onStopped(mSoftApManager);
     }
 
     /**
@@ -735,8 +738,7 @@
         checkApStateChangedBroadcast(intentCaptor.getValue(), WIFI_AP_STATE_DISABLED,
                 WIFI_AP_STATE_DISABLING, HOTSPOT_NO_ERROR, TEST_INTERFACE_NAME,
                 softApModeConfig.getTargetMode());
-        order.verify(mListener).onStopped();
-        assertFalse(mSoftApManager.isStopping());
+        order.verify(mListener).onStopped(mSoftApManager);
     }
 
     /**
@@ -751,7 +753,7 @@
 
         mSoftApManager.stop();
         mLooper.dispatchAll();
-        verify(mListener).onStopped();
+        verify(mListener).onStopped(mSoftApManager);
 
         verify(mCallback).onStateChanged(WifiManager.WIFI_AP_STATE_DISABLING, 0);
         verify(mCallback).onStateChanged(WifiManager.WIFI_AP_STATE_DISABLED, 0);
@@ -786,7 +788,7 @@
 
         order.verify(mCallback).onStateChanged(WifiManager.WIFI_AP_STATE_FAILED,
                 WifiManager.SAP_START_FAILURE_GENERAL);
-        order.verify(mListener).onStopped();
+        order.verify(mListener).onStopped(mSoftApManager);
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mContext, times(3)).sendStickyBroadcastAsUser(intentCaptor.capture(),
                 eq(UserHandle.ALL));
@@ -816,8 +818,6 @@
         // reset to clear verified Intents for ap state change updates
         reset(mContext, mCallback, mWifiNative);
 
-        InOrder order = inOrder(mCallback, mContext);
-
         mWifiNativeInterfaceCallbackCaptor.getValue().onDown(OTHER_INTERFACE_NAME);
 
         mLooper.dispatchAll();
@@ -839,13 +839,12 @@
         reset(mContext, mCallback, mWifiNative);
 
         InOrder order = inOrder(mCallback, mListener, mContext);
-
         mSoftApListenerCaptor.getValue().onFailure();
         mLooper.dispatchAll();
 
         order.verify(mCallback).onStateChanged(WifiManager.WIFI_AP_STATE_FAILED,
                 WifiManager.SAP_START_FAILURE_GENERAL);
-        order.verify(mListener).onStopped();
+        order.verify(mListener).onStopped(mSoftApManager);
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mContext, times(3)).sendStickyBroadcastAsUser(intentCaptor.capture(),
                 eq(UserHandle.ALL));
@@ -869,14 +868,12 @@
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
 
-        final int channelFrequency = 2437;
-        final int channelBandwidth = SoftApInfo.CHANNEL_WIDTH_20MHZ_NOHT;
-        mSoftApListenerCaptor.getValue().onSoftApChannelSwitched(channelFrequency,
-                channelBandwidth);
+        mockApInfoChangedEvent(mTestSoftApInfo);
         mLooper.dispatchAll();
 
-        verify(mWifiMetrics).addSoftApChannelSwitchedEvent(channelFrequency, channelBandwidth,
-                apConfig.getTargetMode());
+        verify(mWifiMetrics).addSoftApChannelSwitchedEvent(
+                new ArrayList<>(mTestSoftApInfoMap.values()),
+                apConfig.getTargetMode(), false);
     }
 
     @Test
@@ -890,15 +887,15 @@
                 WifiManager.IFACE_IP_MODE_TETHERED, configBuilder.build(),
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-
-        final int channelFrequency = 5180;
-        final int channelBandwidth = SoftApInfo.CHANNEL_WIDTH_20MHZ_NOHT;
-        mSoftApListenerCaptor.getValue().onSoftApChannelSwitched(channelFrequency,
-                channelBandwidth);
+        SoftApInfo testSoftApInfo = new SoftApInfo(mTestSoftApInfo);
+        testSoftApInfo.setFrequency(5220);
+        testSoftApInfo.setBandwidth(SoftApInfo.CHANNEL_WIDTH_20MHZ_NOHT);
+        mockApInfoChangedEvent(testSoftApInfo);
         mLooper.dispatchAll();
 
-        verify(mWifiMetrics).addSoftApChannelSwitchedEvent(channelFrequency, channelBandwidth,
-                apConfig.getTargetMode());
+        verify(mWifiMetrics).addSoftApChannelSwitchedEvent(
+                new ArrayList<>(mTestSoftApInfoMap.values()),
+                apConfig.getTargetMode(), false);
         verify(mWifiMetrics).incrementNumSoftApUserBandPreferenceUnsatisfied();
     }
 
@@ -913,15 +910,12 @@
                 WifiManager.IFACE_IP_MODE_TETHERED, configBuilder.build(),
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-
-        final int channelFrequency = 2437;
-        final int channelBandwidth = SoftApInfo.CHANNEL_WIDTH_20MHZ_NOHT;
-        mSoftApListenerCaptor.getValue().onSoftApChannelSwitched(channelFrequency,
-                channelBandwidth);
+        mockApInfoChangedEvent(mTestSoftApInfo);
         mLooper.dispatchAll();
 
-        verify(mWifiMetrics).addSoftApChannelSwitchedEvent(channelFrequency, channelBandwidth,
-                apConfig.getTargetMode());
+        verify(mWifiMetrics).addSoftApChannelSwitchedEvent(
+                new ArrayList<>(mTestSoftApInfoMap.values()),
+                apConfig.getTargetMode(), false);
         verify(mWifiMetrics).incrementNumSoftApUserBandPreferenceUnsatisfied();
     }
 
@@ -930,21 +924,18 @@
             throws Exception {
         SoftApConfiguration config = createDefaultApConfig();
         Builder configBuilder = new SoftApConfiguration.Builder(config);
-        configBuilder.setBand(SoftApConfiguration.BAND_ANY);
+        configBuilder.setBand(mBand256G);
 
         SoftApModeConfiguration apConfig = new SoftApModeConfiguration(
                 WifiManager.IFACE_IP_MODE_TETHERED, configBuilder.build(),
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-
-        final int channelFrequency = 5220;
-        final int channelBandwidth = SoftApInfo.CHANNEL_WIDTH_20MHZ_NOHT;
-        mSoftApListenerCaptor.getValue().onSoftApChannelSwitched(channelFrequency,
-                channelBandwidth);
+        mockApInfoChangedEvent(mTestSoftApInfo);
         mLooper.dispatchAll();
 
-        verify(mWifiMetrics).addSoftApChannelSwitchedEvent(channelFrequency, channelBandwidth,
-                apConfig.getTargetMode());
+        verify(mWifiMetrics).addSoftApChannelSwitchedEvent(
+                new ArrayList<>(mTestSoftApInfoMap.values()),
+                apConfig.getTargetMode(), false);
         verify(mWifiMetrics, never()).incrementNumSoftApUserBandPreferenceUnsatisfied();
     }
 
@@ -958,14 +949,16 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-
-        mSoftApListenerCaptor.getValue().onSoftApChannelSwitched(
-                TEST_AP_FREQUENCY, TEST_AP_BANDWIDTH_FROM_IFACE_CALLBACK);
+        reset(mCallback);
+        mockApInfoChangedEvent(mTestSoftApInfo);
         mLooper.dispatchAll();
 
-        verify(mCallback).onInfoChanged(mTestSoftApInfo);
-        verify(mWifiMetrics).addSoftApChannelSwitchedEvent(TEST_AP_FREQUENCY,
-                TEST_AP_BANDWIDTH_IN_SOFTAPINFO, apConfig.getTargetMode());
+        verify(mCallback).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, false);
+
+        verify(mWifiMetrics).addSoftApChannelSwitchedEvent(
+                new ArrayList<>(mTestSoftApInfoMap.values()),
+                apConfig.getTargetMode(), false);
     }
 
     /**
@@ -978,19 +971,20 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-
-        mSoftApListenerCaptor.getValue().onSoftApChannelSwitched(
-                TEST_AP_FREQUENCY, TEST_AP_BANDWIDTH_FROM_IFACE_CALLBACK);
+        reset(mCallback);
+        mockApInfoChangedEvent(mTestSoftApInfo);
         mLooper.dispatchAll();
+        verify(mCallback).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, false);
+        verify(mWifiMetrics).addSoftApChannelSwitchedEvent(
+                new ArrayList<>(mTestSoftApInfoMap.values()),
+                apConfig.getTargetMode(), false);
 
+        reset(mCallback);
         // now trigger callback again, but we should have each method only called once
-        mSoftApListenerCaptor.getValue().onSoftApChannelSwitched(
-                TEST_AP_FREQUENCY, TEST_AP_BANDWIDTH_FROM_IFACE_CALLBACK);
+        mockApInfoChangedEvent(mTestSoftApInfo);
         mLooper.dispatchAll();
-
-        verify(mCallback).onInfoChanged(mTestSoftApInfo);
-        verify(mWifiMetrics).addSoftApChannelSwitchedEvent(TEST_AP_FREQUENCY,
-                TEST_AP_BANDWIDTH_IN_SOFTAPINFO, apConfig.getTargetMode());
+        verify(mCallback, never()).onConnectedClientsOrInfoChanged(any(), any(), anyBoolean());
     }
 
     /**
@@ -1003,14 +997,12 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-
-        mSoftApListenerCaptor.getValue().onSoftApChannelSwitched(
-                -1, TEST_AP_BANDWIDTH_FROM_IFACE_CALLBACK);
+        reset(mCallback);
+        mockChannelSwitchEvent(-1, TEST_AP_BANDWIDTH_FROM_IFACE_CALLBACK);
         mLooper.dispatchAll();
-
-        verify(mCallback, never()).onInfoChanged(any());
-        verify(mWifiMetrics, never()).addSoftApChannelSwitchedEvent(anyInt(), anyInt(),
-                anyInt());
+        verify(mCallback, never()).onConnectedClientsOrInfoChanged(any(), any(), anyBoolean());
+        verify(mWifiMetrics, never()).addSoftApChannelSwitchedEvent(any(),
+                anyInt(), anyBoolean());
     }
 
     /**
@@ -1024,24 +1016,23 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-
-        mSoftApListenerCaptor.getValue().onSoftApChannelSwitched(
-                TEST_AP_FREQUENCY, TEST_AP_BANDWIDTH_FROM_IFACE_CALLBACK);
+        reset(mCallback);
+        mockApInfoChangedEvent(mTestSoftApInfo);
         mLooper.dispatchAll();
-
-        order.verify(mCallback).onInfoChanged(mTestSoftApInfo);
-        order.verify(mWifiMetrics).addSoftApChannelSwitchedEvent(TEST_AP_FREQUENCY,
-                TEST_AP_BANDWIDTH_IN_SOFTAPINFO, apConfig.getTargetMode());
+        verify(mCallback).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, false);
+        order.verify(mWifiMetrics).addSoftApChannelSwitchedEvent(
+                new ArrayList<>(mTestSoftApInfoMap.values()),
+                apConfig.getTargetMode(), false);
 
         mSoftApManager.stop();
         mLooper.dispatchAll();
-
-        mTestSoftApInfo.setFrequency(0);
-        mTestSoftApInfo.setBandwidth(SoftApInfo.CHANNEL_WIDTH_INVALID);
-
-        order.verify(mCallback).onInfoChanged(mTestSoftApInfo);
-        order.verify(mWifiMetrics, never()).addSoftApChannelSwitchedEvent(0,
-                SoftApInfo.CHANNEL_WIDTH_INVALID, apConfig.getTargetMode());
+        mTestSoftApInfoMap.clear();
+        mTestWifiClientsMap.clear();
+        verify(mCallback, times(2)).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, false);
+        order.verify(mWifiMetrics, never()).addSoftApChannelSwitchedEvent(any(),
+                eq(apConfig.getTargetMode()), anyBoolean());
     }
 
     @Test
@@ -1051,19 +1042,20 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-
-        order.verify(mCallback).onConnectedClientsChanged(new ArrayList<>());
-
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT, true);
+        reset(mCallback);
+        mockApInfoChangedEvent(mTestSoftApInfo);
+        mLooper.dispatchAll();
+        verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
+        reset(mCallback);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
 
-        order.verify(mCallback).onConnectedClientsChanged(
-                Mockito.argThat((List<WifiClient> clients) ->
-                        clients.contains(TEST_CONNECTED_CLIENT))
-        );
-        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(
-                1, apConfig.getTargetMode());
+        verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
+
+        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(1, 1,
+                apConfig.getTargetMode(), mTestSoftApInfo);
     }
 
     /**
@@ -1076,41 +1068,37 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
+        reset(mCallback);
+        mockApInfoChangedEvent(mTestSoftApInfo);
+        mLooper.dispatchAll();
+        verify(mCallback).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, false);
 
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT, true);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
 
         // now trigger callback again, but we should have each method only called once
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT, true);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, false);
         mLooper.dispatchAll();
 
         // Should just trigger 1 time callback, the first time will be happen when softap enable
-        verify(mCallback, times(2)).onConnectedClientsChanged(
-                Mockito.argThat((List<WifiClient> clients) ->
-                        clients.contains(TEST_CONNECTED_CLIENT))
-        );
-
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT, false);
+        verify(mCallback, times(2)).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, false, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
 
         // now trigger callback again, but we should have each method only called once
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT, false);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, false, TEST_INTERFACE_NAME, false);
         mLooper.dispatchAll();
 
         // Should just trigger 1 time callback to update to zero client.
         // Should just trigger 1 time callback, the first time will be happen when softap enable
-        verify(mCallback, times(3)).onConnectedClientsChanged(
-                Mockito.argThat((List<WifiClient> clients) ->
-                        clients.size() == 0)
-        );
+        verify(mCallback, times(3)).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
 
         verify(mWifiMetrics)
-                .addSoftApNumAssociatedStationsChangedEvent(
-                0, apConfig.getTargetMode());
+                .addSoftApNumAssociatedStationsChangedEvent(0, 0,
+                apConfig.getTargetMode(), mTestSoftApInfo);
 
     }
 
@@ -1121,24 +1109,21 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                         mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-
-        order.verify(mCallback).onConnectedClientsChanged(new ArrayList<>());
-
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT, true);
+        mockApInfoChangedEvent(mTestSoftApInfo);
         mLooper.dispatchAll();
-
-        order.verify(mCallback).onConnectedClientsChanged(
-                Mockito.argThat((List<WifiClient> clients) ->
-                        clients.contains(TEST_CONNECTED_CLIENT))
-        );
-        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(
-                1, apConfig.getTargetMode());
+        order.verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
+        mLooper.dispatchAll();
+        order.verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
+        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(1, 1,
+                apConfig.getTargetMode(), mTestSoftApInfo);
 
         mSoftApManager.stop();
         mLooper.dispatchAll();
 
-        verify(mWifiNative).forceClientDisconnect(TEST_INTERFACE_NAME, TEST_MAC_ADDRESS,
+        verify(mWifiNative).forceClientDisconnect(TEST_INTERFACE_NAME, TEST_CLIENT_MAC_ADDRESS,
                 SAP_CLIENT_DISCONNECT_REASON_CODE_UNSPECIFIED);
     }
 
@@ -1148,14 +1133,14 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-
+        reset(mCallback);
         /* Invalid values should be ignored */
-        final NativeWifiClient mInvalidClient = null;
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(mInvalidClient, true);
+        mockClientConnectedEvent(null, true, TEST_INTERFACE_NAME, false);
         mLooper.dispatchAll();
-        verify(mCallback, never()).onConnectedClientsChanged(null);
+        verify(mCallback, never()).onConnectedClientsOrInfoChanged(any(),
+                  any(), anyBoolean());
         verify(mWifiMetrics, never()).addSoftApNumAssociatedStationsChangedEvent(anyInt(),
-                anyInt());
+                anyInt(), anyInt(), any());
     }
 
     @Test
@@ -1165,30 +1150,30 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-
-        order.verify(mCallback).onConnectedClientsChanged(new ArrayList<>());
-
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT, true);
+        reset(mCallback);
+        mockApInfoChangedEvent(mTestSoftApInfo);
         mLooper.dispatchAll();
-
-        order.verify(mCallback).onConnectedClientsChanged(
-                Mockito.argThat((List<WifiClient> clients) ->
-                        clients.contains(TEST_CONNECTED_CLIENT))
-        );
-        order.verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(
-                1, apConfig.getTargetMode());
+        order.verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
+        mLooper.dispatchAll();
+        order.verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
+        order.verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(1, 1,
+                apConfig.getTargetMode(), mTestSoftApInfo);
         // Verify timer is canceled at this point
-        verify(mAlarmManager.getAlarmManager()).cancel(any(WakeupMessage.class));
+        verify(mAlarmManager.getAlarmManager()).cancel(eq(mSoftApManager.mSoftApTimeoutMessage));
 
         mSoftApManager.stop();
         mLooper.dispatchAll();
-
-        order.verify(mCallback).onConnectedClientsChanged(new ArrayList<>());
-        order.verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(0,
-                apConfig.getTargetMode());
+        order.verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(0, 0,
+                apConfig.getTargetMode(), mTestSoftApInfo);
+        mTestWifiClientsMap.clear();
+        mTestSoftApInfoMap.clear();
+        order.verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
         // Verify timer is canceled after stop softap
-        verify(mAlarmManager.getAlarmManager()).cancel(any(WakeupMessage.class));
+        verify(mAlarmManager.getAlarmManager()).cancel(eq(mSoftApManager.mSoftApTimeoutMessage));
     }
 
     @Test
@@ -1201,29 +1186,25 @@
         configBuilder.setSsid(TEST_SSID);
         configBuilder.setClientControlByUserEnabled(false);
         // Client in blocked list
-        blockedClientList.add(TEST_MAC_ADDRESS);
+        blockedClientList.add(TEST_CLIENT_MAC_ADDRESS);
         configBuilder.setBlockedClientList(blockedClientList);
         SoftApModeConfiguration apConfig =
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED,
                 configBuilder.build(), mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-
-        verify(mCallback).onConnectedClientsChanged(new ArrayList<>());
-
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT, true);
+        verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, false);
         mLooper.dispatchAll();
 
         // Client is not allow verify
         verify(mWifiNative).forceClientDisconnect(
-                        TEST_INTERFACE_NAME, TEST_MAC_ADDRESS,
+                        TEST_INTERFACE_NAME, TEST_CLIENT_MAC_ADDRESS,
                         WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER);
         verify(mWifiMetrics, never()).addSoftApNumAssociatedStationsChangedEvent(
-                1, apConfig.getTargetMode());
-        verify(mCallback, never()).onConnectedClientsChanged(
-                Mockito.argThat((List<WifiClient> clients) ->
-                        clients.contains(TEST_CONNECTED_CLIENT))
-        );
+                anyInt(), anyInt(), eq(apConfig.getTargetMode()), any());
+        verify(mCallback, never()).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
 
     }
 
@@ -1240,34 +1221,33 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED,
                 configBuilder.build(), mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-
-        verify(mCallback).onConnectedClientsChanged(new ArrayList<>());
-
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT, true);
+        reset(mCallback);
+        mockApInfoChangedEvent(mTestSoftApInfo);
+        mLooper.dispatchAll();
+        verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
 
         // Client connected check
         verify(mWifiNative, never()).forceClientDisconnect(
-                        TEST_INTERFACE_NAME, TEST_MAC_ADDRESS,
+                        TEST_INTERFACE_NAME, TEST_CLIENT_MAC_ADDRESS,
                         WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER);
-        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(
-                1, apConfig.getTargetMode());
-        verify(mCallback, times(2)).onConnectedClientsChanged(
-                Mockito.argThat((List<WifiClient> clients) ->
-                        clients.contains(TEST_CONNECTED_CLIENT))
-        );
+        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(1, 1,
+                apConfig.getTargetMode(), mTestSoftApInfo);
+        verify(mCallback, times(2)).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
 
         reset(mCallback);
         reset(mWifiNative);
         // Update configuration
-        blockedClientList.add(TEST_MAC_ADDRESS);
+        blockedClientList.add(TEST_CLIENT_MAC_ADDRESS);
         configBuilder.setBlockedClientList(blockedClientList);
         mSoftApManager.updateConfiguration(configBuilder.build());
         mLooper.dispatchAll();
         // Client difconnected
         verify(mWifiNative).forceClientDisconnect(
-                        TEST_INTERFACE_NAME, TEST_MAC_ADDRESS,
+                        TEST_INTERFACE_NAME, TEST_CLIENT_MAC_ADDRESS,
                         WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER);
         // The callback should not trigger in configuration update case.
         verify(mCallback, never()).onBlockedClientConnecting(TEST_CONNECTED_CLIENT,
@@ -1275,8 +1255,6 @@
 
     }
 
-
-
     @Test
     public void testForceClientDisconnectInvokeBecauseClientAuthorizationEnabled()
             throws Exception {
@@ -1289,23 +1267,23 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED,
                 configBuilder.build(), mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-
-        verify(mCallback).onConnectedClientsChanged(new ArrayList<>());
-
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT, true);
+        reset(mCallback);
+        mockApInfoChangedEvent(mTestSoftApInfo);
+        mLooper.dispatchAll();
+        verify(mCallback).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, false);
+        reset(mCallback);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, false);
         mLooper.dispatchAll();
 
         // Client is not allow verify
         verify(mWifiNative).forceClientDisconnect(
-                        TEST_INTERFACE_NAME, TEST_MAC_ADDRESS,
+                        TEST_INTERFACE_NAME, TEST_CLIENT_MAC_ADDRESS,
                         WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER);
-        verify(mWifiMetrics, never()).addSoftApNumAssociatedStationsChangedEvent(
-                1, apConfig.getTargetMode());
-        verify(mCallback, never()).onConnectedClientsChanged(
-                Mockito.argThat((List<WifiClient> clients) ->
-                        clients.contains(TEST_CONNECTED_CLIENT))
-        );
+        verify(mWifiMetrics, never()).addSoftApNumAssociatedStationsChangedEvent(anyInt(), anyInt(),
+                anyInt(), any());
+        verify(mCallback, never()).onConnectedClientsOrInfoChanged(any(),
+                any(), anyBoolean());
 
     }
 
@@ -1322,45 +1300,43 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED,
                 configBuilder.build(), mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-
-        verify(mCallback).onConnectedClientsChanged(new ArrayList<>());
-
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT, true);
+        reset(mCallback);
+        reset(mWifiMetrics);
+        mockApInfoChangedEvent(mTestSoftApInfo);
+        mLooper.dispatchAll();
+        verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
+        reset(mCallback);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, false);
         mLooper.dispatchAll();
 
         // Client is not allow verify
         verify(mWifiNative).forceClientDisconnect(
-                        TEST_INTERFACE_NAME, TEST_MAC_ADDRESS,
+                        TEST_INTERFACE_NAME, TEST_CLIENT_MAC_ADDRESS,
                         WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER);
-        verify(mWifiMetrics, never()).addSoftApNumAssociatedStationsChangedEvent(
-                1, apConfig.getTargetMode());
-        verify(mCallback, never()).onConnectedClientsChanged(
-                Mockito.argThat((List<WifiClient> clients) ->
-                        clients.contains(TEST_CONNECTED_CLIENT))
-        );
+        verify(mWifiMetrics, never()).addSoftApNumAssociatedStationsChangedEvent(anyInt(), anyInt(),
+                anyInt(), any());
+        verify(mCallback, never()).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
         verify(mCallback).onBlockedClientConnecting(TEST_CONNECTED_CLIENT,
                 WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER);
         reset(mCallback);
         reset(mWifiNative);
         // Update configuration
-        allowedClientList.add(TEST_MAC_ADDRESS);
+        allowedClientList.add(TEST_CLIENT_MAC_ADDRESS);
         configBuilder.setAllowedClientList(allowedClientList);
         mSoftApManager.updateConfiguration(configBuilder.build());
         mLooper.dispatchAll();
         // Client connected again
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT, true);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
         verify(mWifiNative, never()).forceClientDisconnect(
-                        TEST_INTERFACE_NAME, TEST_MAC_ADDRESS,
+                        TEST_INTERFACE_NAME, TEST_CLIENT_MAC_ADDRESS,
                         WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER);
-        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(
-                1, apConfig.getTargetMode());
-        verify(mCallback).onConnectedClientsChanged(
-                Mockito.argThat((List<WifiClient> clients) ->
-                        clients.contains(TEST_CONNECTED_CLIENT))
-        );
+        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(1, 1,
+                apConfig.getTargetMode(), mTestSoftApInfo);
+        verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
         verify(mCallback, never()).onBlockedClientConnecting(TEST_CONNECTED_CLIENT,
                 WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER);
     }
@@ -1378,45 +1354,43 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED,
                 configBuilder.build(), mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-
-        verify(mCallback).onConnectedClientsChanged(new ArrayList<>());
-
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT, true);
+        reset(mCallback);
+        mockApInfoChangedEvent(mTestSoftApInfo);
+        mLooper.dispatchAll();
+        verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
+        reset(mWifiMetrics);
+        reset(mCallback);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, false);
         mLooper.dispatchAll();
 
         // Client is not allow verify
         verify(mWifiNative).forceClientDisconnect(
-                        TEST_INTERFACE_NAME, TEST_MAC_ADDRESS,
+                        TEST_INTERFACE_NAME, TEST_CLIENT_MAC_ADDRESS,
                         WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER);
-        verify(mWifiMetrics, never()).addSoftApNumAssociatedStationsChangedEvent(
-                1, apConfig.getTargetMode());
-        verify(mCallback, never()).onConnectedClientsChanged(
-                Mockito.argThat((List<WifiClient> clients) ->
-                        clients.contains(TEST_CONNECTED_CLIENT))
-        );
+        verify(mWifiMetrics, never()).addSoftApNumAssociatedStationsChangedEvent(1, 1,
+                apConfig.getTargetMode(), mTestSoftApInfo);
+        verify(mCallback, never()).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                mTestWifiClientsMap, false);
         verify(mCallback).onBlockedClientConnecting(TEST_CONNECTED_CLIENT,
                 WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER);
         reset(mCallback);
         reset(mWifiNative);
         // Update configuration
-        blockedClientList.add(TEST_MAC_ADDRESS);
+        blockedClientList.add(TEST_CLIENT_MAC_ADDRESS);
         configBuilder.setBlockedClientList(blockedClientList);
         mSoftApManager.updateConfiguration(configBuilder.build());
         mLooper.dispatchAll();
         // Client connected again
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT, true);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, false);
         mLooper.dispatchAll();
         verify(mWifiNative).forceClientDisconnect(
-                        TEST_INTERFACE_NAME, TEST_MAC_ADDRESS,
+                        TEST_INTERFACE_NAME, TEST_CLIENT_MAC_ADDRESS,
                         WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER);
         verify(mWifiMetrics, never()).addSoftApNumAssociatedStationsChangedEvent(
-                1, apConfig.getTargetMode());
-        verify(mCallback, never()).onConnectedClientsChanged(
-                Mockito.argThat((List<WifiClient> clients) ->
-                        clients.contains(TEST_CONNECTED_CLIENT))
-        );
+                anyInt(), anyInt(), anyInt(), any());
+        verify(mCallback, never()).onConnectedClientsOrInfoChanged(any(),
+                any(), anyBoolean());
         verify(mCallback, never()).onBlockedClientConnecting(TEST_CONNECTED_CLIENT,
                 WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER);
     }
@@ -1426,8 +1400,8 @@
             throws Exception {
         mTestSoftApCapability.setMaxSupportedClients(10);
         ArrayList<MacAddress> allowedClientList = new ArrayList<>();
-        allowedClientList.add(TEST_MAC_ADDRESS);
-        allowedClientList.add(TEST_MAC_ADDRESS_2);
+        allowedClientList.add(TEST_CLIENT_MAC_ADDRESS);
+        allowedClientList.add(TEST_CLIENT_MAC_ADDRESS_2);
         ArrayList<MacAddress> blockedClientList = new ArrayList<>();
 
         Builder configBuilder = new SoftApConfiguration.Builder();
@@ -1440,51 +1414,46 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED,
                 configBuilder.build(), mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-
-        verify(mCallback).onConnectedClientsChanged(new ArrayList<>());
-
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT, true);
+        reset(mCallback);
+        mockApInfoChangedEvent(mTestSoftApInfo);
         mLooper.dispatchAll();
+        verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
+        mLooper.dispatchAll();
+        verify(mCallback, times(2)).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
 
-        verify(mCallback, times(2)).onConnectedClientsChanged(
-                Mockito.argThat((List<WifiClient> clients) ->
-                        clients.contains(TEST_CONNECTED_CLIENT))
-        );
-
-        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(
-                1, apConfig.getTargetMode());
+        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(1, 1,
+                apConfig.getTargetMode(), mTestSoftApInfo);
         // Verify timer is canceled at this point
-        verify(mAlarmManager.getAlarmManager()).cancel(any(WakeupMessage.class));
+        verify(mAlarmManager.getAlarmManager()).cancel(eq(mSoftApManager.mSoftApTimeoutMessage));
 
         // Second client connect and max client set is 1.
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT_2, true);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS_2, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
 
-        verify(mCallback, times(3)).onConnectedClientsChanged(
-                Mockito.argThat((List<WifiClient> clients) ->
-                        clients.contains(TEST_CONNECTED_CLIENT_2))
-        );
-        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(
-                2, apConfig.getTargetMode());
+        verify(mCallback, times(3)).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
+        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(2, 2,
+                apConfig.getTargetMode(), mTestSoftApInfo);
         reset(mCallback);
         reset(mWifiNative);
         // Update configuration
         allowedClientList.clear();
-        allowedClientList.add(TEST_MAC_ADDRESS_2);
+        allowedClientList.add(TEST_CLIENT_MAC_ADDRESS_2);
 
-        blockedClientList.add(TEST_MAC_ADDRESS);
+        blockedClientList.add(TEST_CLIENT_MAC_ADDRESS);
         configBuilder.setBlockedClientList(blockedClientList);
         configBuilder.setAllowedClientList(allowedClientList);
         configBuilder.setMaxNumberOfClients(1);
         mSoftApManager.updateConfiguration(configBuilder.build());
         mLooper.dispatchAll();
         verify(mWifiNative).forceClientDisconnect(
-                        TEST_INTERFACE_NAME, TEST_MAC_ADDRESS,
+                        TEST_INTERFACE_NAME, TEST_CLIENT_MAC_ADDRESS,
                         WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER);
         verify(mWifiNative, never()).forceClientDisconnect(
-                        TEST_INTERFACE_NAME, TEST_MAC_ADDRESS,
+                        TEST_INTERFACE_NAME, TEST_CLIENT_MAC_ADDRESS,
                         WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS);
     }
 
@@ -1497,10 +1466,19 @@
         startSoftApAndVerifyEnabled(apConfig);
         verify(mResources)
                 .getInteger(R.integer.config_wifiFrameworkSoftApShutDownTimeoutMilliseconds);
+        verify(mResources)
+                .getInteger(R.integer
+                .config_wifiFrameworkSoftApShutDownIdleInstanceInBridgedModeTimeoutMillisecond);
+
 
         // Verify timer is scheduled
         verify(mAlarmManager.getAlarmManager()).setExact(anyInt(), anyLong(),
                 eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG), any(), any());
+
+        // The single AP should not start the bridged mode timer
+        verify(mAlarmManager.getAlarmManager(), never()).setExact(anyInt(), anyLong(),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_IDLE_IN_BRIDGED_MODE_TIMEOUT_TAG),
+                any(), any());
     }
 
     @Test
@@ -1514,6 +1492,16 @@
                 configBuilder.build(), mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
 
+        reset(mCallback);
+        mockApInfoChangedEvent(mTestSoftApInfo);
+        mLooper.dispatchAll();
+
+        SoftApInfo expectedInfo = new SoftApInfo(mTestSoftApInfo);
+        expectedInfo.setAutoShutdownTimeoutMillis(50000);
+        mTestSoftApInfoMap.put(TEST_INTERFACE_NAME, expectedInfo);
+        verify(mCallback).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, false);
+
         // Verify timer is scheduled
         verify(mAlarmManager.getAlarmManager()).setExact(anyInt(), anyLong(),
                 eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG), any(), any());
@@ -1529,7 +1517,7 @@
         mLooper.dispatchAll();
 
         // Verify timer is canceled
-        verify(mAlarmManager.getAlarmManager()).cancel(any(WakeupMessage.class));
+        verify(mAlarmManager.getAlarmManager()).cancel(eq(mSoftApManager.mSoftApTimeoutMessage));
     }
 
     @Test
@@ -1538,12 +1526,11 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT, true);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
 
         // Verify timer is canceled
-        verify(mAlarmManager.getAlarmManager()).cancel(any(WakeupMessage.class));
+        verify(mAlarmManager.getAlarmManager()).cancel(eq(mSoftApManager.mSoftApTimeoutMessage));
     }
 
     @Test
@@ -1553,19 +1540,15 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-        order.verify(mCallback).onConnectedClientsChanged(new ArrayList<>());
-
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT, true);
+        order.verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
-        order.verify(mCallback).onConnectedClientsChanged(
-                Mockito.argThat((List<WifiClient> clients) ->
-                        clients.contains(TEST_CONNECTED_CLIENT))
-        );
+        order.verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
         // Verify timer is canceled at this point
-        verify(mAlarmManager.getAlarmManager()).cancel(any(WakeupMessage.class));
-
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(TEST_NATIVE_CLIENT, false);
+        verify(mAlarmManager.getAlarmManager()).cancel(eq(mSoftApManager.mSoftApTimeoutMessage));
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, false, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
         // Verify timer is scheduled again
         verify(mAlarmManager.getAlarmManager(), times(2)).setExact(anyInt(), anyLong(),
@@ -1579,12 +1562,12 @@
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
         doNothing().when(mFakeSoftApNotifier)
-                .showSoftApShutDownTimeoutExpiredNotification();
+                .showSoftApShutdownTimeoutExpiredNotification();
         mAlarmManager.dispatch(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG);
         mLooper.dispatchAll();
 
         verify(mWifiNative).teardownInterface(TEST_INTERFACE_NAME);
-        verify(mFakeSoftApNotifier).showSoftApShutDownTimeoutExpiredNotification();
+        verify(mFakeSoftApNotifier).showSoftApShutdownTimeoutExpiredNotification();
     }
 
     @Test
@@ -1601,7 +1584,7 @@
         mLooper.dispatchAll();
 
         // Verify timer is canceled
-        verify(mAlarmManager.getAlarmManager()).cancel(any(WakeupMessage.class));
+        verify(mAlarmManager.getAlarmManager()).cancel(eq(mSoftApManager.mSoftApTimeoutMessage));
     }
 
     @Test
@@ -1654,12 +1637,10 @@
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
         // add client
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT, true);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
         // remove client
-        mSoftApListenerCaptor.getValue()
-                .onConnectedClientsChanged(TEST_NATIVE_CLIENT, false);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, false, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
         // Verify timer is not scheduled
         verify(mAlarmManager.getAlarmManager(), never()).setExact(anyInt(), anyLong(),
@@ -1676,12 +1657,29 @@
         SoftApModeConfiguration apConfig = new SoftApModeConfiguration(
                 WifiManager.IFACE_IP_MODE_TETHERED, configBuilder.build(), mTestSoftApCapability);
         ArgumentCaptor<MacAddress> mac = ArgumentCaptor.forClass(MacAddress.class);
-        when(mWifiNative.getFactoryMacAddress(TEST_INTERFACE_NAME)).thenReturn(TEST_MAC_ADDRESS);
-        when(mWifiNative.setMacAddress(eq(TEST_INTERFACE_NAME), mac.capture())).thenReturn(true);
 
         startSoftApAndVerifyEnabled(apConfig);
+        verify(mWifiNative).resetApMacToFactoryMacAddress(eq(TEST_INTERFACE_NAME));
+    }
 
-        assertThat(mac.getValue()).isEqualTo(TEST_MAC_ADDRESS);
+    @Test
+    public void resetsFactoryMacWhenRandomizationDoesntSupport() throws Exception {
+        long testSoftApFeature = SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT
+                | SoftApCapability.SOFTAP_FEATURE_ACS_OFFLOAD
+                | SoftApCapability.SOFTAP_FEATURE_WPA3_SAE;
+        SoftApCapability testSoftApCapability = new SoftApCapability(testSoftApFeature);
+        Builder configBuilder = new SoftApConfiguration.Builder();
+        configBuilder.setBand(SoftApConfiguration.BAND_2GHZ);
+        configBuilder.setSsid(TEST_SSID);
+        configBuilder.setBssid(null);
+
+        SoftApModeConfiguration apConfig = new SoftApModeConfiguration(
+                WifiManager.IFACE_IP_MODE_TETHERED, configBuilder.build(), testSoftApCapability);
+        ArgumentCaptor<MacAddress> mac = ArgumentCaptor.forClass(MacAddress.class);
+
+        startSoftApAndVerifyEnabled(apConfig);
+        verify(mWifiNative).resetApMacToFactoryMacAddress(eq(TEST_INTERFACE_NAME));
+        verify(mWifiApConfigStore, never()).randomizeBssidIfUnset(any(), any());
     }
 
     @Test
@@ -1689,74 +1687,67 @@
         Builder configBuilder = new SoftApConfiguration.Builder();
         configBuilder.setBand(SoftApConfiguration.BAND_2GHZ);
         configBuilder.setSsid(TEST_SSID);
-        configBuilder.setBssid(MacAddress.fromString("23:34:45:56:67:78"));
+        configBuilder.setBssid(TEST_CLIENT_MAC_ADDRESS);
         SoftApModeConfiguration apConfig = new SoftApModeConfiguration(
                 IFACE_IP_MODE_LOCAL_ONLY, configBuilder.build(), mTestSoftApCapability);
         ArgumentCaptor<MacAddress> mac = ArgumentCaptor.forClass(MacAddress.class);
-        when(mWifiNative.setMacAddress(eq(TEST_INTERFACE_NAME), mac.capture())).thenReturn(true);
+        when(mWifiNative.setApMacAddress(eq(TEST_INTERFACE_NAME), mac.capture())).thenReturn(true);
 
         startSoftApAndVerifyEnabled(apConfig);
 
-        assertThat(mac.getValue()).isEqualTo(MacAddress.fromString("23:34:45:56:67:78"));
+        assertThat(mac.getValue()).isEqualTo(TEST_CLIENT_MAC_ADDRESS);
     }
 
     @Test
     public void setsCustomMacWhenSetMacNotSupport() throws Exception {
-        when(mWifiNative.setupInterfaceForSoftApMode(any())).thenReturn(TEST_INTERFACE_NAME);
-        when(mWifiNative.isSetMacAddressSupported(any())).thenReturn(false);
+        when(mWifiNative.isApSetMacAddressSupported(any())).thenReturn(false);
         Builder configBuilder = new SoftApConfiguration.Builder();
         configBuilder.setBand(SoftApConfiguration.BAND_2GHZ);
         configBuilder.setSsid(TEST_SSID);
-        configBuilder.setBssid(MacAddress.fromString("23:34:45:56:67:78"));
+        configBuilder.setBssid(TEST_CLIENT_MAC_ADDRESS);
         SoftApModeConfiguration apConfig = new SoftApModeConfiguration(
                 IFACE_IP_MODE_LOCAL_ONLY, configBuilder.build(), mTestSoftApCapability);
         ArgumentCaptor<MacAddress> mac = ArgumentCaptor.forClass(MacAddress.class);
 
-        mSoftApManager = createSoftApManager(apConfig, TEST_COUNTRY_CODE);
-        mSoftApManager.start();
+        mSoftApManager = createSoftApManager(apConfig, TEST_COUNTRY_CODE, ROLE_SOFTAP_LOCAL_ONLY);
         mLooper.dispatchAll();
         verify(mCallback).onStateChanged(WifiManager.WIFI_AP_STATE_ENABLING, 0);
         verify(mCallback).onStateChanged(WifiManager.WIFI_AP_STATE_FAILED,
                 WifiManager.SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION);
-        verify(mWifiNative, never()).setMacAddress(any(), any());
+        verify(mWifiNative, never()).setApMacAddress(any(), any());
     }
 
     @Test
     public void setMacFailureWhenCustomMac() throws Exception {
-        when(mWifiNative.setupInterfaceForSoftApMode(any())).thenReturn(TEST_INTERFACE_NAME);
         Builder configBuilder = new SoftApConfiguration.Builder();
         configBuilder.setBand(SoftApConfiguration.BAND_2GHZ);
         configBuilder.setSsid(TEST_SSID);
-        configBuilder.setBssid(MacAddress.fromString("23:34:45:56:67:78"));
+        configBuilder.setBssid(TEST_CLIENT_MAC_ADDRESS);
         SoftApModeConfiguration apConfig = new SoftApModeConfiguration(
                 IFACE_IP_MODE_LOCAL_ONLY, configBuilder.build(), mTestSoftApCapability);
         ArgumentCaptor<MacAddress> mac = ArgumentCaptor.forClass(MacAddress.class);
-        when(mWifiNative.setMacAddress(eq(TEST_INTERFACE_NAME), mac.capture())).thenReturn(false);
+        when(mWifiNative.setApMacAddress(eq(TEST_INTERFACE_NAME), mac.capture())).thenReturn(false);
 
-        mSoftApManager = createSoftApManager(apConfig, TEST_COUNTRY_CODE);
-        mSoftApManager.start();
+        mSoftApManager = createSoftApManager(apConfig, TEST_COUNTRY_CODE, ROLE_SOFTAP_LOCAL_ONLY);
         mLooper.dispatchAll();
         verify(mCallback).onStateChanged(WifiManager.WIFI_AP_STATE_ENABLING, 0);
         verify(mCallback).onStateChanged(WifiManager.WIFI_AP_STATE_FAILED,
                 WifiManager.SAP_START_FAILURE_GENERAL);
-        assertThat(mac.getValue()).isEqualTo(MacAddress.fromString("23:34:45:56:67:78"));
+        assertThat(mac.getValue()).isEqualTo(TEST_CLIENT_MAC_ADDRESS);
     }
 
     @Test
     public void setMacFailureWhenRandomMac() throws Exception {
-        when(mWifiNative.setupInterfaceForSoftApMode(any())).thenReturn(TEST_INTERFACE_NAME);
-        when(mWifiApConfigStore.getApConfiguration()).thenReturn(mDefaultApConfig);
         SoftApConfiguration randomizedBssidConfig =
                 new SoftApConfiguration.Builder(mDefaultApConfig)
-                .setBssid(TEST_MAC_ADDRESS).build();
+                .setBssid(TEST_CLIENT_MAC_ADDRESS).build();
         when(mWifiApConfigStore.randomizeBssidIfUnset(any(), any())).thenReturn(
                 randomizedBssidConfig);
         SoftApModeConfiguration apConfig = new SoftApModeConfiguration(
                 IFACE_IP_MODE_LOCAL_ONLY, null, mTestSoftApCapability);
         ArgumentCaptor<MacAddress> mac = ArgumentCaptor.forClass(MacAddress.class);
-        when(mWifiNative.setMacAddress(eq(TEST_INTERFACE_NAME), mac.capture())).thenReturn(false);
-        mSoftApManager = createSoftApManager(apConfig, TEST_COUNTRY_CODE);
-        mSoftApManager.start();
+        when(mWifiNative.setApMacAddress(eq(TEST_INTERFACE_NAME), mac.capture())).thenReturn(false);
+        mSoftApManager = createSoftApManager(apConfig, TEST_COUNTRY_CODE, ROLE_SOFTAP_LOCAL_ONLY);
         mLooper.dispatchAll();
         verify(mCallback).onStateChanged(WifiManager.WIFI_AP_STATE_ENABLING, 0);
         verify(mCallback).onStateChanged(WifiManager.WIFI_AP_STATE_FAILED,
@@ -1765,11 +1756,11 @@
 
     @Test
     public void setRandomMacWhenSetMacNotsupport() throws Exception {
-        when(mWifiNative.isSetMacAddressSupported(any())).thenReturn(false);
+        when(mWifiNative.isApSetMacAddressSupported(any())).thenReturn(false);
         SoftApModeConfiguration apConfig = new SoftApModeConfiguration(
                 IFACE_IP_MODE_LOCAL_ONLY, null, mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-        verify(mWifiNative, never()).setMacAddress(any(), any());
+        verify(mWifiNative, never()).setApMacAddress(any(), any());
     }
 
     @Test
@@ -1779,35 +1770,32 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-
-        verify(mCallback).onConnectedClientsChanged(new ArrayList<>());
-
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT, true);
+        reset(mCallback);
+        mockApInfoChangedEvent(mTestSoftApInfo);
         mLooper.dispatchAll();
+        verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
+        mLooper.dispatchAll();
+        verify(mCallback, times(2)).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
 
-        verify(mCallback, times(2)).onConnectedClientsChanged(
-                Mockito.argThat((List<WifiClient> clients) ->
-                        clients.contains(TEST_CONNECTED_CLIENT))
-        );
-
-        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(
-                1, apConfig.getTargetMode());
+        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(1, 1,
+                apConfig.getTargetMode(), mTestSoftApInfo);
         // Verify timer is canceled at this point
-        verify(mAlarmManager.getAlarmManager()).cancel(any(WakeupMessage.class));
+        verify(mAlarmManager.getAlarmManager()).cancel(eq(mSoftApManager.mSoftApTimeoutMessage));
 
+        reset(mWifiMetrics);
         // Second client connect and max client set is 1.
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT_2, true);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS_2, true, TEST_INTERFACE_NAME, false);
         mLooper.dispatchAll();
         verify(mWifiNative).forceClientDisconnect(
-                        TEST_INTERFACE_NAME, TEST_MAC_ADDRESS_2,
+                        TEST_INTERFACE_NAME, TEST_CLIENT_MAC_ADDRESS_2,
                         WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS);
         verify(mWifiMetrics, never()).addSoftApNumAssociatedStationsChangedEvent(
-                2, apConfig.getTargetMode());
+                anyInt(), anyInt(), anyInt(), any());
         // Trigger connection again
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT_2, true);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS_2, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
         // Verify just update metrics one time
         verify(mWifiMetrics).noteSoftApClientBlocked(1);
@@ -1820,34 +1808,29 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-
-        verify(mCallback).onConnectedClientsChanged(new ArrayList<>());
-
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT, true);
+        reset(mCallback);
+        mockApInfoChangedEvent(mTestSoftApInfo);
+        mLooper.dispatchAll();
+        verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
 
-        verify(mCallback, times(2)).onConnectedClientsChanged(
-                Mockito.argThat((List<WifiClient> clients) ->
-                        clients.contains(TEST_CONNECTED_CLIENT))
-        );
+        verify(mCallback, times(2)).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
 
-        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(
-                1, apConfig.getTargetMode());
+        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(1, 1,
+                apConfig.getTargetMode(), mTestSoftApInfo);
         // Verify timer is canceled at this point
-        verify(mAlarmManager.getAlarmManager()).cancel(any(WakeupMessage.class));
+        verify(mAlarmManager.getAlarmManager()).cancel(eq(mSoftApManager.mSoftApTimeoutMessage));
 
         // Second client connect and max client set is 1.
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT_2, true);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS_2, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
-
-        verify(mCallback, times(3)).onConnectedClientsChanged(
-                Mockito.argThat((List<WifiClient> clients) ->
-                        clients.contains(TEST_CONNECTED_CLIENT_2))
-        );
-        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(
-                2, apConfig.getTargetMode());
+        verify(mCallback, times(3)).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
+        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(2, 2,
+                apConfig.getTargetMode(), mTestSoftApInfo);
 
         // Trigger Capability Change
         mTestSoftApCapability.setMaxSupportedClients(1);
@@ -1865,34 +1848,30 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_LOCAL_ONLY, null,
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-
-        verify(mCallback).onConnectedClientsChanged(new ArrayList<>());
-
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT, true);
+        reset(mCallback);
+        mockApInfoChangedEvent(mTestSoftApInfo);
+        mLooper.dispatchAll();
+        verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
 
-        verify(mCallback, times(2)).onConnectedClientsChanged(
-                Mockito.argThat((List<WifiClient> clients) ->
-                        clients.contains(TEST_CONNECTED_CLIENT))
-        );
+        verify(mCallback, times(2)).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
 
-        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(
-                1, apConfig.getTargetMode());
+        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(1, 1,
+                apConfig.getTargetMode(), mTestSoftApInfo);
         // Verify timer is canceled at this point
-        verify(mAlarmManager.getAlarmManager()).cancel(any(WakeupMessage.class));
+        verify(mAlarmManager.getAlarmManager()).cancel(eq(mSoftApManager.mSoftApTimeoutMessage));
 
         // Second client connect and max client set is 1.
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT_2, true);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS_2, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
 
-        verify(mCallback, times(3)).onConnectedClientsChanged(
-                Mockito.argThat((List<WifiClient> clients) ->
-                        clients.contains(TEST_CONNECTED_CLIENT_2))
-        );
-        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(
-                2, apConfig.getTargetMode());
+        verify(mCallback, times(3)).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
+        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(2, 2,
+                apConfig.getTargetMode(), mTestSoftApInfo);
 
         // Trigger Capability Change
         mTestSoftApCapability.setMaxSupportedClients(1);
@@ -1906,59 +1885,78 @@
     /** Starts soft AP and verifies that it is enabled successfully. */
     protected void startSoftApAndVerifyEnabled(
             SoftApModeConfiguration softApConfig) throws Exception {
-        startSoftApAndVerifyEnabled(softApConfig, TEST_COUNTRY_CODE);
+        startSoftApAndVerifyEnabled(softApConfig, TEST_COUNTRY_CODE, null);
     }
 
     /** Starts soft AP and verifies that it is enabled successfully. */
     protected void startSoftApAndVerifyEnabled(
             SoftApModeConfiguration softApConfig, String countryCode) throws Exception {
-        // The expected config to pass to Native
-        SoftApConfiguration expectedConfig = null;
+        startSoftApAndVerifyEnabled(softApConfig, countryCode, null);
+    }
+
+    /** Starts soft AP and verifies that it is enabled successfully. */
+    protected void startSoftApAndVerifyEnabled(
+            SoftApModeConfiguration softApConfig, String countryCode,
+            SoftApConfiguration expectedConfig) throws Exception {
         // The config which base on mDefaultApConfig and generate ramdonized mac address
         SoftApConfiguration randomizedBssidConfig = null;
         InOrder order = inOrder(mCallback, mWifiNative);
 
         SoftApConfiguration config = softApConfig.getSoftApConfiguration();
-        if (config == null) {
-            // Only generate randomized mac for default config since test case doesn't care it.
-            when(mWifiApConfigStore.getApConfiguration()).thenReturn(mDefaultApConfig);
-            randomizedBssidConfig =
-                    new SoftApConfiguration.Builder(mDefaultApConfig)
-                    .setBssid(TEST_MAC_ADDRESS).build();
-            when(mWifiApConfigStore.randomizeBssidIfUnset(any(), any())).thenReturn(
-                    randomizedBssidConfig);
-            expectedConfig = new SoftApConfiguration.Builder(randomizedBssidConfig)
-                .setChannel(DEFAULT_AP_CHANNEL, SoftApConfiguration.BAND_2GHZ)
-                .build();
-        } else {
-            expectedConfig = new SoftApConfiguration.Builder(config)
-                .setChannel(DEFAULT_AP_CHANNEL, SoftApConfiguration.BAND_2GHZ)
-                .build();
+        if (expectedConfig == null) {
+            if (config == null) {
+                // Only generate randomized mac for default config since test case doesn't care it.
+                randomizedBssidConfig =
+                        new SoftApConfiguration.Builder(mDefaultApConfig)
+                        .setBssid(TEST_INTERFACE_MAC_ADDRESS).build();
+                when(mWifiApConfigStore.randomizeBssidIfUnset(any(), any())).thenReturn(
+                        randomizedBssidConfig);
+                expectedConfig = new SoftApConfiguration.Builder(randomizedBssidConfig)
+                        .build();
+            } else {
+                expectedConfig = new SoftApConfiguration.Builder(config)
+                        .build();
+            }
         }
-        mSoftApManager = createSoftApManager(softApConfig, countryCode);
-        mSoftApManager.mSoftApNotifier = mFakeSoftApNotifier;
-        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
 
-        when(mWifiNative.setupInterfaceForSoftApMode(any()))
-                .thenReturn(TEST_INTERFACE_NAME);
+        SoftApConfiguration expectedConfigWithFrameworkACS = null;
+        if (!softApConfig.getCapability().areFeaturesSupported(
+                SoftApCapability.SOFTAP_FEATURE_ACS_OFFLOAD)) {
+            if (expectedConfig.getChannel() == 0 && expectedConfig.getBands().length == 1) {
+                // Reset channel to 2.4G channel 11 for expected configuration
+                // Reason:The test 2G freq is "ALLOWED_2G_FREQS = {2462}; //ch# 11"
+                expectedConfigWithFrameworkACS = new SoftApConfiguration.Builder(expectedConfig)
+                        .setChannel(11, SoftApConfiguration.BAND_2GHZ)
+                        .build();
+            }
+        }
 
 
-        mSoftApManager.start();
-        mSoftApManager.setRole(ActiveModeManager.ROLE_SOFTAP_TETHERED);
+        mSoftApManager = createSoftApManager(softApConfig, countryCode,
+                softApConfig.getTargetMode() == IFACE_IP_MODE_LOCAL_ONLY
+                        ? ROLE_SOFTAP_LOCAL_ONLY : ROLE_SOFTAP_TETHERED);
+        verify(mCmiMonitor).registerListener(mCmiListenerCaptor.capture());
         mLooper.dispatchAll();
-        verify(mFakeSoftApNotifier).dismissSoftApShutDownTimeoutExpiredNotification();
+
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mFakeSoftApNotifier).dismissSoftApShutdownTimeoutExpiredNotification();
         order.verify(mWifiNative).setupInterfaceForSoftApMode(
-                mWifiNativeInterfaceCallbackCaptor.capture());
+                mWifiNativeInterfaceCallbackCaptor.capture(), eq(TEST_WORKSOURCE),
+                eq(expectedConfig.getBand()), eq(expectedConfig.getBands().length > 1));
         ArgumentCaptor<SoftApConfiguration> configCaptor =
                 ArgumentCaptor.forClass(SoftApConfiguration.class);
         order.verify(mCallback).onStateChanged(WifiManager.WIFI_AP_STATE_ENABLING, 0);
         order.verify(mWifiNative).startSoftAp(eq(TEST_INTERFACE_NAME),
-                configCaptor.capture(), mSoftApListenerCaptor.capture());
-        assertThat(configCaptor.getValue()).isEqualTo(expectedConfig);
+                configCaptor.capture(),
+                eq(softApConfig.getTargetMode() ==  WifiManager.IFACE_IP_MODE_TETHERED),
+                mSoftApListenerCaptor.capture());
+        assertThat(configCaptor.getValue()).isEqualTo(expectedConfigWithFrameworkACS != null
+                ? expectedConfigWithFrameworkACS : expectedConfig);
         mWifiNativeInterfaceCallbackCaptor.getValue().onUp(TEST_INTERFACE_NAME);
         mLooper.dispatchAll();
         order.verify(mCallback).onStateChanged(WifiManager.WIFI_AP_STATE_ENABLED, 0);
-        order.verify(mCallback).onConnectedClientsChanged(new ArrayList<>());
+        order.verify(mCallback).onConnectedClientsOrInfoChanged(eq(mTestSoftApInfoMap),
+                  eq(mTestWifiClientsMap), eq(expectedConfig.getBands().length > 1));
         verify(mSarManager).setSapWifiState(WifiManager.WIFI_AP_STATE_ENABLED);
         verify(mWifiDiagnostics).startLogging(TEST_INTERFACE_NAME);
         verify(mContext, times(2)).sendStickyBroadcastAsUser(intentCaptor.capture(),
@@ -1970,13 +1968,17 @@
         checkApStateChangedBroadcast(capturedIntents.get(1), WIFI_AP_STATE_ENABLED,
                 WIFI_AP_STATE_ENABLING, HOTSPOT_NO_ERROR, TEST_INTERFACE_NAME,
                 softApConfig.getTargetMode());
-        verify(mListener).onStarted();
+        verify(mListener).onStarted(mSoftApManager);
         verify(mWifiMetrics).addSoftApUpChangedEvent(true, softApConfig.getTargetMode(),
-                TEST_DEFAULT_SHUTDOWN_TIMEOUT_MILLS);
+                TEST_DEFAULT_SHUTDOWN_TIMEOUT_MILLIS, expectedConfig.getBands().length > 1);
         verify(mWifiMetrics).updateSoftApConfiguration(config == null
-                ? randomizedBssidConfig : config, softApConfig.getTargetMode());
+                ? randomizedBssidConfig : expectedConfig, softApConfig.getTargetMode(),
+                expectedConfig.getBands().length > 1);
         verify(mWifiMetrics).updateSoftApCapability(softApConfig.getCapability(),
-                softApConfig.getTargetMode());
+                softApConfig.getTargetMode(), expectedConfig.getBands().length > 1);
+        if (SdkLevel.isAtLeastS()) {
+            verify(mCoexManager).registerCoexListener(mCoexListenerCaptor.capture());
+        }
     }
 
     private void checkApStateChangedBroadcast(Intent intent, int expectedCurrentState,
@@ -1997,47 +1999,45 @@
     @Test
     public void testForceClientDisconnectNotInvokeWhenNotSupport() throws Exception {
         long testSoftApFeature = SoftApCapability.SOFTAP_FEATURE_WPA3_SAE
-                | SoftApCapability.SOFTAP_FEATURE_ACS_OFFLOAD;
+                | SoftApCapability.SOFTAP_FEATURE_ACS_OFFLOAD
+                | SoftApCapability.SOFTAP_FEATURE_MAC_ADDRESS_CUSTOMIZATION;
         SoftApCapability noClientControlCapability = new SoftApCapability(testSoftApFeature);
         noClientControlCapability.setMaxSupportedClients(1);
         SoftApModeConfiguration apConfig =
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 noClientControlCapability);
         startSoftApAndVerifyEnabled(apConfig);
-
-        verify(mCallback).onConnectedClientsChanged(new ArrayList<>());
-
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT, true);
+        reset(mCallback);
+        mockApInfoChangedEvent(mTestSoftApInfo);
+        mLooper.dispatchAll();
+        verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
 
-        verify(mCallback, times(2)).onConnectedClientsChanged(
-                Mockito.argThat((List<WifiClient> clients) ->
-                        clients.contains(TEST_CONNECTED_CLIENT))
-        );
+        verify(mCallback, times(2)).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
 
-        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(
-                1, apConfig.getTargetMode());
+        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(1, 1,
+                apConfig.getTargetMode(), mTestSoftApInfo);
         // Verify timer is canceled at this point
-        verify(mAlarmManager.getAlarmManager()).cancel(any(WakeupMessage.class));
+        verify(mAlarmManager.getAlarmManager()).cancel(eq(mSoftApManager.mSoftApTimeoutMessage));
 
         // Second client connect and max client set is 1.
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT_2, true);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS_2, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
         // feature not support thus it should not trigger disconnect
         verify(mWifiNative, never()).forceClientDisconnect(
                         any(), any(), anyInt());
         // feature not support thus client still allow connected.
-        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(
-                2, apConfig.getTargetMode());
+        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(2, 2,
+                apConfig.getTargetMode(), mTestSoftApInfo);
     }
 
     @Test
     public void testSoftApEnableFailureBecauseSetMaxClientWhenNotSupport() throws Exception {
         long testSoftApFeature = SoftApCapability.SOFTAP_FEATURE_WPA3_SAE
                 | SoftApCapability.SOFTAP_FEATURE_ACS_OFFLOAD;
-        when(mWifiNative.setupInterfaceForSoftApMode(any())).thenReturn(TEST_INTERFACE_NAME);
         SoftApCapability noClientControlCapability = new SoftApCapability(testSoftApFeature);
         noClientControlCapability.setMaxSupportedClients(1);
         SoftApConfiguration softApConfig = new SoftApConfiguration.Builder(
@@ -2046,27 +2046,14 @@
         SoftApModeConfiguration apConfig =
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, softApConfig,
                 noClientControlCapability);
-        SoftApManager newSoftApManager = new SoftApManager(mContext,
-                                                           mLooper.getLooper(),
-                                                           mFrameworkFacade,
-                                                           mWifiNative,
-                                                           TEST_COUNTRY_CODE,
-                                                           mListener,
-                                                           mCallback,
-                                                           mWifiApConfigStore,
-                                                           apConfig,
-                                                           mWifiMetrics,
-                                                           mSarManager,
-                                                           mWifiDiagnostics);
-        mLooper.dispatchAll();
-        newSoftApManager.start();
-        mLooper.dispatchAll();
+        mSoftApManager = createSoftApManager(apConfig, TEST_COUNTRY_CODE, ROLE_SOFTAP_TETHERED);
+
         verify(mCallback).onStateChanged(WifiManager.WIFI_AP_STATE_ENABLING, 0);
         verify(mCallback).onStateChanged(WifiManager.WIFI_AP_STATE_FAILED,
                 WifiManager.SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION);
         verify(mWifiMetrics).incrementSoftApStartResult(false,
                 WifiManager.SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION);
-        verify(mListener).onStartFailure();
+        verify(mListener).onStartFailure(mSoftApManager);
     }
 
     @Test
@@ -2074,7 +2061,6 @@
             throws Exception {
         long testSoftApFeature = SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT
                 | SoftApCapability.SOFTAP_FEATURE_ACS_OFFLOAD;
-        when(mWifiNative.setupInterfaceForSoftApMode(any())).thenReturn(TEST_INTERFACE_NAME);
         SoftApCapability noSaeCapability = new SoftApCapability(testSoftApFeature);
         SoftApConfiguration softApConfig = new SoftApConfiguration.Builder(
                 mDefaultApConfig).setPassphrase(TEST_PASSWORD,
@@ -2083,29 +2069,69 @@
         SoftApModeConfiguration apConfig =
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, softApConfig,
                 noSaeCapability);
-        SoftApManager newSoftApManager = new SoftApManager(mContext,
-                                                           mLooper.getLooper(),
-                                                           mFrameworkFacade,
-                                                           mWifiNative,
-                                                           TEST_COUNTRY_CODE,
-                                                           mListener,
-                                                           mCallback,
-                                                           mWifiApConfigStore,
-                                                           apConfig,
-                                                           mWifiMetrics,
-                                                           mSarManager,
-                                                           mWifiDiagnostics);
-        mLooper.dispatchAll();
-        newSoftApManager.start();
-        mLooper.dispatchAll();
+        mSoftApManager = createSoftApManager(apConfig, TEST_COUNTRY_CODE, ROLE_SOFTAP_TETHERED);
+
         verify(mCallback).onStateChanged(WifiManager.WIFI_AP_STATE_ENABLING, 0);
         verify(mCallback).onStateChanged(WifiManager.WIFI_AP_STATE_FAILED,
                 WifiManager.SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION);
-        verify(mListener).onStartFailure();
+        verify(mListener).onStartFailure(mSoftApManager);
+    }
+
+    @Test
+    public void testSoftApEnableFailureBecauseDaulBandConfigSetWhenACSNotSupport()
+            throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        long testSoftApFeature = SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT
+                | SoftApCapability.SOFTAP_FEATURE_WPA3_SAE;
+        int[] dual_bands = {SoftApConfiguration.BAND_2GHZ, SoftApConfiguration.BAND_5GHZ};
+        SoftApCapability testCapability = new SoftApCapability(testSoftApFeature);
+        testCapability.setSupportedChannelList(
+                SoftApConfiguration.BAND_2GHZ, TEST_SUPPORTED_24G_CHANNELS);
+        testCapability.setSupportedChannelList(
+                SoftApConfiguration.BAND_5GHZ, TEST_SUPPORTED_5G_CHANNELS);
+        SoftApConfiguration softApConfig = new SoftApConfiguration.Builder(mDefaultApConfig)
+                .setBands(dual_bands)
+                .build();
+        SoftApModeConfiguration apConfig = new SoftApModeConfiguration(
+                WifiManager.IFACE_IP_MODE_TETHERED, softApConfig,
+                testCapability);
+
+        mSoftApManager = createSoftApManager(apConfig, TEST_COUNTRY_CODE, ROLE_SOFTAP_TETHERED);
+
+        verify(mCallback).onStateChanged(WifiManager.WIFI_AP_STATE_ENABLING, 0);
+        verify(mCallback).onStateChanged(WifiManager.WIFI_AP_STATE_FAILED,
+                WifiManager.SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION);
+        verify(mWifiMetrics).incrementSoftApStartResult(false,
+                WifiManager.SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION);
+        verify(mListener).onStartFailure(mSoftApManager);
+    }
+
+    @Test
+    public void testSoftApEnableWhenDaulBandConfigwithChannelSetWhenACSNotSupport()
+            throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        long testSoftApFeature = SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT
+                | SoftApCapability.SOFTAP_FEATURE_WPA3_SAE;
+        SparseIntArray dual_channels = new SparseIntArray(2);
+        dual_channels.put(SoftApConfiguration.BAND_5GHZ, 149);
+        dual_channels.put(SoftApConfiguration.BAND_2GHZ, 2);
+        SoftApCapability testCapability = new SoftApCapability(testSoftApFeature);
+        testCapability.setSupportedChannelList(
+                SoftApConfiguration.BAND_2GHZ, TEST_SUPPORTED_24G_CHANNELS);
+        testCapability.setSupportedChannelList(
+                SoftApConfiguration.BAND_5GHZ, TEST_SUPPORTED_5G_CHANNELS);
+        SoftApConfiguration softApConfig = new SoftApConfiguration.Builder(mDefaultApConfig)
+                .setChannels(dual_channels)
+                .build();
+        SoftApModeConfiguration apConfig = new SoftApModeConfiguration(
+                WifiManager.IFACE_IP_MODE_TETHERED, softApConfig,
+                testCapability);
+        startSoftApAndVerifyEnabled(apConfig, TEST_COUNTRY_CODE, null);
     }
 
     @Test
     public void testConfigurationChangedApplySinceDoesNotNeedToRestart() throws Exception {
+        long testShutdownTimeout = 50000;
         Builder configBuilder = new SoftApConfiguration.Builder();
         configBuilder.setBand(SoftApConfiguration.BAND_2GHZ);
         configBuilder.setSsid(TEST_SSID);
@@ -2117,24 +2143,35 @@
         // Verify timer is scheduled
         verify(mAlarmManager.getAlarmManager()).setExact(anyInt(), anyLong(),
                 eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG), any(), any());
-        verify(mCallback).onConnectedClientsChanged(new ArrayList<>());
+        verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
         verify(mWifiMetrics).updateSoftApConfiguration(configBuilder.build(),
-                WifiManager.IFACE_IP_MODE_TETHERED);
+                WifiManager.IFACE_IP_MODE_TETHERED, false);
 
+        reset(mCallback);
+        mockApInfoChangedEvent(mTestSoftApInfo);
         mLooper.dispatchAll();
+        verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
 
+        reset(mCallback);
         // Trigger Configuration Change
-        configBuilder.setShutdownTimeoutMillis(500000);
+        configBuilder.setShutdownTimeoutMillis(testShutdownTimeout);
         mSoftApManager.updateConfiguration(configBuilder.build());
+        SoftApInfo expectedInfo = new SoftApInfo(mTestSoftApInfo);
+        expectedInfo.setAutoShutdownTimeoutMillis(testShutdownTimeout);
+        mTestSoftApInfoMap.put(TEST_INTERFACE_NAME, expectedInfo);
         mLooper.dispatchAll();
+        // Verify the info changed
+        verify(mCallback).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, false);
         // Verify timer is canceled at this point since timeout changed
-        verify(mAlarmManager.getAlarmManager()).cancel(any(WakeupMessage.class));
+        verify(mAlarmManager.getAlarmManager()).cancel(eq(mSoftApManager.mSoftApTimeoutMessage));
         // Verify timer setup again
         verify(mAlarmManager.getAlarmManager(), times(2)).setExact(anyInt(), anyLong(),
                 eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG), any(), any());
         verify(mWifiMetrics).updateSoftApConfiguration(configBuilder.build(),
-                WifiManager.IFACE_IP_MODE_TETHERED);
-
+                WifiManager.IFACE_IP_MODE_TETHERED, false);
     }
 
     @Test
@@ -2159,7 +2196,8 @@
         mSoftApManager.updateConfiguration(configBuilder.build());
         mLooper.dispatchAll();
         // Verify timer cancel will not apply since changed config need to apply via restart.
-        verify(mAlarmManager.getAlarmManager(), never()).cancel(any(WakeupMessage.class));
+        verify(mAlarmManager.getAlarmManager(), never()).cancel(
+                eq(mSoftApManager.mSoftApTimeoutMessage));
     }
 
     @Test
@@ -2172,34 +2210,30 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED,
                 configBuilder.build(), mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-
-        verify(mCallback).onConnectedClientsChanged(new ArrayList<>());
-
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT, true);
+        reset(mCallback);
+        mockApInfoChangedEvent(mTestSoftApInfo);
+        mLooper.dispatchAll();
+        verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
 
-        verify(mCallback, times(2)).onConnectedClientsChanged(
-                Mockito.argThat((List<WifiClient> clients) ->
-                        clients.contains(TEST_CONNECTED_CLIENT))
-        );
+        verify(mCallback, times(2)).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
 
-        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(
-                1, apConfig.getTargetMode());
+        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(1, 1,
+                apConfig.getTargetMode(), mTestSoftApInfo);
         // Verify timer is canceled at this point
-        verify(mAlarmManager.getAlarmManager()).cancel(any(WakeupMessage.class));
+        verify(mAlarmManager.getAlarmManager()).cancel(eq(mSoftApManager.mSoftApTimeoutMessage));
 
         // Second client connect and max client set is 2.
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT_2, true);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS_2, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
 
-        verify(mCallback, times(3)).onConnectedClientsChanged(
-                Mockito.argThat((List<WifiClient> clients) ->
-                        clients.contains(TEST_CONNECTED_CLIENT_2))
-        );
-        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(
-                2, apConfig.getTargetMode());
+        verify(mCallback, times(3)).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
+        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(2, 2,
+                  apConfig.getTargetMode(), mTestSoftApInfo);
 
         // Trigger Configuration Change
         configBuilder.setMaxNumberOfClients(1);
@@ -2220,29 +2254,28 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED,
                 configBuilder.build(), mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-
-        verify(mCallback).onConnectedClientsChanged(new ArrayList<>());
-
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT, true);
+        reset(mCallback);
+        mockApInfoChangedEvent(mTestSoftApInfo);
+        mLooper.dispatchAll();
+        verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
 
-        verify(mCallback, times(2)).onConnectedClientsChanged(
-                Mockito.argThat((List<WifiClient> clients) ->
-                        clients.contains(TEST_CONNECTED_CLIENT))
-        );
 
-        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(
-                1, apConfig.getTargetMode());
+        verify(mCallback, times(2)).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
+
+        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(1, 1,
+                apConfig.getTargetMode(), mTestSoftApInfo);
         // Verify timer is canceled at this point
-        verify(mAlarmManager.getAlarmManager()).cancel(any(WakeupMessage.class));
+        verify(mAlarmManager.getAlarmManager()).cancel(eq(mSoftApManager.mSoftApTimeoutMessage));
 
         // Second client connect and max client set is 1.
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT_2, true);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS_2, true, TEST_INTERFACE_NAME, false);
         mLooper.dispatchAll();
         verify(mWifiNative).forceClientDisconnect(
-                        TEST_INTERFACE_NAME, TEST_MAC_ADDRESS_2,
+                        TEST_INTERFACE_NAME, TEST_CLIENT_MAC_ADDRESS_2,
                         WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS);
 
         // Verify update metrics
@@ -2254,28 +2287,864 @@
         mLooper.dispatchAll();
 
         // Second client connect and max client set is 2.
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT_2, true);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS_2, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
-        verify(mCallback, times(3)).onConnectedClientsChanged(
-                Mockito.argThat((List<WifiClient> clients) ->
-                        clients.contains(TEST_CONNECTED_CLIENT_2))
-        );
+        verify(mCallback, times(3)).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
 
         // Trigger Configuration Change
         configBuilder.setMaxNumberOfClients(1);
         mSoftApManager.updateConfiguration(configBuilder.build());
         mLooper.dispatchAll();
         // Let client disconnect due to maximum number change to small.
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT, false);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, false, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
 
         // Trigger connection again
-        mSoftApListenerCaptor.getValue().onConnectedClientsChanged(
-                TEST_NATIVE_CLIENT, true);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
         // Verify just update metrics one time
         verify(mWifiMetrics, times(2)).noteSoftApClientBlocked(1);
     }
+
+    /**
+     * If SoftApManager gets an update for the ap channal and the frequency, it will trigger
+     * callbacks to update softap information with bssid field.
+     */
+    @Test
+    public void testBssidUpdatedWhenSoftApInfoUpdate() throws Exception {
+        MacAddress testBssid = MacAddress.fromString("aa:bb:cc:11:22:33");
+        SoftApConfiguration customizedBssidConfig = new SoftApConfiguration
+                .Builder(mDefaultApConfig).setBssid(testBssid).build();
+        when(mWifiNative.setApMacAddress(eq(TEST_INTERFACE_NAME), eq(testBssid))).thenReturn(true);
+        mTestSoftApInfo.setBssid(testBssid);
+        InOrder order = inOrder(mCallback, mWifiMetrics);
+        SoftApModeConfiguration apConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED,
+                customizedBssidConfig, mTestSoftApCapability);
+        startSoftApAndVerifyEnabled(apConfig);
+        mockApInfoChangedEvent(mTestSoftApInfo);
+        mLooper.dispatchAll();
+        order.verify(mCallback).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, false);
+        order.verify(mWifiMetrics).addSoftApChannelSwitchedEvent(
+                new ArrayList<>(mTestSoftApInfoMap.values()),
+                apConfig.getTargetMode(), false);
+
+        // Verify stop will set bssid back to null
+        mSoftApManager.stop();
+        mLooper.dispatchAll();
+        mTestSoftApInfoMap.clear();
+        mTestWifiClientsMap.clear();
+        order.verify(mCallback).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, false);
+        order.verify(mWifiMetrics, never()).addSoftApChannelSwitchedEvent(any(),
+                eq(apConfig.getTargetMode()), anyBoolean());
+    }
+
+    /**
+     * If SoftApManager gets an update for the invalid ap frequency, it will not
+     * trigger callbacks
+     */
+    @Test
+    public void testHandleCallbackFromWificond() throws Exception {
+        SoftApModeConfiguration apConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
+                mTestSoftApCapability);
+        startSoftApAndVerifyEnabled(apConfig);
+        reset(mCallback);
+        mockChannelSwitchEvent(mTestSoftApInfo.getFrequency(), mTestSoftApInfo.getBandwidth());
+        mLooper.dispatchAll();
+
+        mTestSoftApInfoMap.clear();
+        SoftApInfo expectedInfo = new SoftApInfo(mTestSoftApInfo);
+        // Old callback should doesn't include the wifiStandard and bssid.
+        expectedInfo.setBssid(null);
+        expectedInfo.setWifiStandard(ScanResult.WIFI_STANDARD_UNKNOWN);
+        mTestSoftApInfoMap.put(TEST_INTERFACE_NAME, expectedInfo);
+        mTestWifiClientsMap.put(TEST_INTERFACE_NAME, new ArrayList<WifiClient>());
+        verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                mTestWifiClientsMap, false);
+        verify(mWifiMetrics).addSoftApChannelSwitchedEvent(
+                new ArrayList<>(mTestSoftApInfoMap.values()),
+                apConfig.getTargetMode(), false);
+    }
+
+    @Test
+    public void testForceClientFailureWillTriggerForceDisconnectAgain() throws Exception {
+        when(mWifiNative.forceClientDisconnect(any(), any(), anyInt())).thenReturn(false);
+
+        mTestSoftApCapability.setMaxSupportedClients(1);
+        SoftApModeConfiguration apConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
+                mTestSoftApCapability);
+        startSoftApAndVerifyEnabled(apConfig);
+        reset(mCallback);
+        mockApInfoChangedEvent(mTestSoftApInfo);
+        mLooper.dispatchAll();
+        verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
+
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
+        mLooper.dispatchAll();
+
+        verify(mCallback, times(2)).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
+
+        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(1, 1,
+                apConfig.getTargetMode(), mTestSoftApInfo);
+        // Verify timer is canceled at this point
+        verify(mAlarmManager.getAlarmManager()).cancel(eq(mSoftApManager.mSoftApTimeoutMessage));
+
+        reset(mWifiMetrics);
+        // Second client connect and max client set is 1.
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS_2, true, TEST_INTERFACE_NAME, false);
+        mLooper.dispatchAll();
+        verify(mWifiNative).forceClientDisconnect(
+                        TEST_INTERFACE_NAME, TEST_CLIENT_MAC_ADDRESS_2,
+                        WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS);
+        assertEquals(1, mSoftApManager.mPendingDisconnectClients.size());
+        verify(mWifiMetrics, never()).addSoftApNumAssociatedStationsChangedEvent(
+                anyInt(), anyInt(), anyInt(), any());
+
+        // Let force disconnect succeed on next time.
+        when(mWifiNative.forceClientDisconnect(any(), any(), anyInt())).thenReturn(true);
+
+        mLooper.moveTimeForward(mSoftApManager.SOFT_AP_PENDING_DISCONNECTION_CHECK_DELAY_MS);
+        mLooper.dispatchAll();
+        verify(mWifiNative, times(2)).forceClientDisconnect(
+                        TEST_INTERFACE_NAME, TEST_CLIENT_MAC_ADDRESS_2,
+                        WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS);
+
+        // The pending list doesn't clean, it needs to wait client connection update event.
+        assertEquals(1, mSoftApManager.mPendingDisconnectClients.size());
+
+    }
+
+    @Test
+    public void testForceClientFailureButClientDisconnectSelf() throws Exception {
+        when(mWifiNative.forceClientDisconnect(any(), any(), anyInt())).thenReturn(false);
+
+        mTestSoftApCapability.setMaxSupportedClients(1);
+        SoftApModeConfiguration apConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
+                mTestSoftApCapability);
+        startSoftApAndVerifyEnabled(apConfig);
+        reset(mCallback);
+        mockApInfoChangedEvent(mTestSoftApInfo);
+        mLooper.dispatchAll();
+        verify(mCallback).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, false);
+        reset(mCallback);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
+        mLooper.dispatchAll();
+
+        verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
+                  mTestWifiClientsMap, false);
+
+        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(1, 1,
+                apConfig.getTargetMode(), mTestSoftApInfo);
+        // Verify timer is canceled at this point
+        verify(mAlarmManager.getAlarmManager()).cancel(eq(mSoftApManager.mSoftApTimeoutMessage));
+
+        reset(mWifiMetrics);
+        // Second client connect and max client set is 1.
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS_2, true, TEST_INTERFACE_NAME, false);
+        mLooper.dispatchAll();
+        verify(mWifiNative).forceClientDisconnect(
+                        TEST_INTERFACE_NAME, TEST_CLIENT_MAC_ADDRESS_2,
+                        WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS);
+        verify(mWifiMetrics, never()).addSoftApNumAssociatedStationsChangedEvent(
+                anyInt(), anyInt(), anyInt(), any());
+        // Receive second client disconnection.
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS_2, false, TEST_INTERFACE_NAME, false);
+        mLooper.dispatchAll();
+        // Sleep to wait execute pending list check
+        reset(mWifiNative);
+        mLooper.moveTimeForward(mSoftApManager.SOFT_AP_PENDING_DISCONNECTION_CHECK_DELAY_MS);
+        mLooper.dispatchAll();
+        verify(mWifiNative, never()).forceClientDisconnect(any(), any(), anyInt());
+    }
+
+    /**
+     * Test that dual interfaces will be setup when dual band config.
+     */
+    @Test
+    public void testSetupDualBandForSoftApModeApInterfaceName() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        int[] dual_bands = new int[] {
+                SoftApConfiguration.BAND_2GHZ, SoftApConfiguration.BAND_5GHZ};
+        Builder configBuilder = new SoftApConfiguration.Builder();
+        configBuilder.setBands(dual_bands);
+        configBuilder.setSsid(TEST_SSID);
+        SoftApModeConfiguration dualBandConfig = new SoftApModeConfiguration(
+                WifiManager.IFACE_IP_MODE_TETHERED, configBuilder.build(),
+                mTestSoftApCapability);
+        mSoftApManager = createSoftApManager(dualBandConfig,
+                TEST_COUNTRY_CODE, ROLE_SOFTAP_TETHERED);
+        verify(mWifiNative).setupInterfaceForSoftApMode(
+                any(), any(), eq(SoftApConfiguration.BAND_2GHZ), eq(true));
+    }
+
+    @Test
+    public void testOnInfoChangedFromDifferentInstancesTriggerSoftApInfoUpdate() throws Exception {
+        SoftApModeConfiguration apConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
+                mTestSoftApCapability);
+        startSoftApAndVerifyEnabled(apConfig);
+        reset(mCallback);
+        mockApInfoChangedEvent(mTestSoftApInfo);
+        mLooper.dispatchAll();
+        verify(mCallback).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, false);
+
+        mockApInfoChangedEvent(mTestSoftApInfoOnSecondInterface);
+        mLooper.dispatchAll();
+        verify(mCallback, times(2)).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, false);
+    }
+
+    @Test
+    public void schedulesTimeoutTimerOnStartInBridgedMode() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        int[] dual_bands = {SoftApConfiguration.BAND_2GHZ ,
+                SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ};
+        Builder configBuilder = new SoftApConfiguration.Builder();
+        configBuilder.setSsid(TEST_SSID);
+        configBuilder.setBands(dual_bands);
+        SoftApModeConfiguration apConfig = new SoftApModeConfiguration(
+                WifiManager.IFACE_IP_MODE_TETHERED, configBuilder.build(),
+                mTestSoftApCapability);
+        startSoftApAndVerifyEnabled(apConfig);
+
+        verify(mResources)
+                .getInteger(R.integer.config_wifiFrameworkSoftApShutDownTimeoutMilliseconds);
+        verify(mResources)
+                .getInteger(R.integer
+                .config_wifiFrameworkSoftApShutDownIdleInstanceInBridgedModeTimeoutMillisecond);
+
+
+        // Verify timer is scheduled
+        verify(mAlarmManager.getAlarmManager()).setExact(anyInt(), anyLong(),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG), any(), any());
+
+        // Verify the bridged mode timer is scheduled
+        verify(mAlarmManager.getAlarmManager()).setExact(anyInt(), anyLong(),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_IDLE_IN_BRIDGED_MODE_TIMEOUT_TAG),
+                any(), any());
+
+        // SoftApInfo updated
+        mockApInfoChangedEvent(mTestSoftApInfo);
+        mLooper.dispatchAll();
+        verify(mCallback).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, true);
+
+        mockApInfoChangedEvent(mTestSoftApInfoOnSecondInterface);
+        mLooper.dispatchAll();
+        verify(mCallback, times(2)).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, true);
+
+        // Trigger the alarm
+        mSoftApManager.mSoftApBridgedModeIdleInstanceTimeoutMessage.onAlarm();
+        mLooper.dispatchAll();
+        // Verify the remove correct iface and instance
+        verify(mWifiNative).removeIfaceInstanceFromBridgedApIface(eq(TEST_INTERFACE_NAME),
+                eq(TEST_SECOND_INTERFACE_NAME));
+        mLooper.dispatchAll();
+        mTestSoftApInfoMap.clear();
+        mTestWifiClientsMap.clear();
+
+        mTestSoftApInfoMap.put(mTestSoftApInfo.getApInstanceIdentifier(), mTestSoftApInfo);
+        mTestWifiClientsMap.put(mTestSoftApInfo.getApInstanceIdentifier(),
+                new ArrayList<WifiClient>());
+
+        verify(mCallback, times(3)).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, true);
+    }
+
+    @Test
+    public void schedulesTimeoutTimerWorkFlowInBridgedMode() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        int[] dual_bands = new int[] {
+                SoftApConfiguration.BAND_2GHZ, SoftApConfiguration.BAND_5GHZ};
+        Builder configBuilder = new SoftApConfiguration.Builder();
+        configBuilder.setSsid(TEST_SSID);
+        configBuilder.setBands(dual_bands);
+        SoftApModeConfiguration apConfig = new SoftApModeConfiguration(
+                WifiManager.IFACE_IP_MODE_TETHERED, configBuilder.build(),
+                mTestSoftApCapability);
+        startSoftApAndVerifyEnabled(apConfig);
+
+        verify(mResources)
+                .getInteger(R.integer.config_wifiFrameworkSoftApShutDownTimeoutMilliseconds);
+        verify(mResources)
+                .getInteger(R.integer
+                .config_wifiFrameworkSoftApShutDownIdleInstanceInBridgedModeTimeoutMillisecond);
+
+
+        // Verify timer is scheduled
+        verify(mAlarmManager.getAlarmManager()).setExact(anyInt(), anyLong(),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG), any(), any());
+
+        // Verify idle timer in bridged mode is scheduled
+        verify(mAlarmManager.getAlarmManager()).setExact(anyInt(), anyLong(),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_IDLE_IN_BRIDGED_MODE_TIMEOUT_TAG),
+                any(), any());
+
+        // SoftApInfo updated
+        mockApInfoChangedEvent(mTestSoftApInfo);
+        mLooper.dispatchAll();
+        verify(mCallback).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, true);
+
+        mockApInfoChangedEvent(mTestSoftApInfoOnSecondInterface);
+        mLooper.dispatchAll();
+        verify(mCallback, times(2)).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, true);
+
+
+        // One Client connected
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
+        mLooper.dispatchAll();
+        verify(mCallback, times(3)).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, true);
+        // Verify original timer is canceled at this point
+        verify(mAlarmManager.getAlarmManager()).cancel(eq(mSoftApManager.mSoftApTimeoutMessage));
+        // Verify idle timer is NOT canceled at this point
+        verify(mAlarmManager.getAlarmManager(), never())
+                .cancel(eq(mSoftApManager.mSoftApBridgedModeIdleInstanceTimeoutMessage));
+
+        // Second client connected to same interface
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS_2, true, TEST_INTERFACE_NAME, true);
+        mLooper.dispatchAll();
+        verify(mCallback, times(4)).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, true);
+        // Verify idle timer is NOT canceled at this point
+        verify(mAlarmManager.getAlarmManager(), never())
+                .cancel(eq(mSoftApManager.mSoftApBridgedModeIdleInstanceTimeoutMessage));
+
+        // Second client disconnected from the current interface and connected to another one
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS_2, false, TEST_INTERFACE_NAME, true);
+        mLooper.dispatchAll();
+        verify(mCallback, times(5)).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, true);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS_2, true, TEST_SECOND_INTERFACE_NAME, true);
+        mLooper.dispatchAll();
+        verify(mCallback, times(6)).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, true);
+        // Verify idle timer is canceled at this point
+        verify(mAlarmManager.getAlarmManager())
+                .cancel(eq(mSoftApManager.mSoftApBridgedModeIdleInstanceTimeoutMessage));
+        // Second client disconnect
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS_2, false,
+                TEST_SECOND_INTERFACE_NAME, true);
+        mLooper.dispatchAll();
+        verify(mCallback, times(7)).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, true);
+        // Verify idle timer in bridged mode is scheduled again
+        verify(mAlarmManager.getAlarmManager(), times(2)).setExact(anyInt(), anyLong(),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_IDLE_IN_BRIDGED_MODE_TIMEOUT_TAG),
+                any(), any());
+        // Trigger the alarm
+        mSoftApManager.mSoftApBridgedModeIdleInstanceTimeoutMessage.onAlarm();
+        mLooper.dispatchAll();
+        // Verify the remove correct iface and instance
+        verify(mWifiNative).removeIfaceInstanceFromBridgedApIface(eq(TEST_INTERFACE_NAME),
+                eq(TEST_SECOND_INTERFACE_NAME));
+
+        mTestSoftApInfoMap.clear();
+        mTestWifiClientsMap.clear();
+        WifiClient client = new WifiClient(TEST_CLIENT_MAC_ADDRESS, TEST_INTERFACE_NAME);
+        List<WifiClient> targetList = new ArrayList();
+        targetList.add(client);
+
+        mTestSoftApInfoMap.put(mTestSoftApInfo.getApInstanceIdentifier(), mTestSoftApInfo);
+        mTestWifiClientsMap.put(mTestSoftApInfo.getApInstanceIdentifier(), targetList);
+        mLooper.dispatchAll();
+        verify(mCallback, times(8)).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, true);
+
+        // Force all client disconnected
+        // reset the alarm mock
+        reset(mAlarmManager.getAlarmManager());
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, false, TEST_INTERFACE_NAME, true);
+        mLooper.dispatchAll();
+        // Verify timer is scheduled
+        verify(mAlarmManager.getAlarmManager()).setExact(anyInt(), anyLong(),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG), any(), any());
+        // The single AP should not start the bridged mode timer.
+        verify(mAlarmManager.getAlarmManager(), never()).setExact(anyInt(), anyLong(),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_IDLE_IN_BRIDGED_MODE_TIMEOUT_TAG),
+                any(), any());
+    }
+
+    @Test
+    public void schedulesTimeoutTimerOnStartInBridgedModeWhenOpportunisticShutdownDisabled()
+            throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        int[] dual_bands = {SoftApConfiguration.BAND_2GHZ ,
+                SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ};
+        Builder configBuilder = new SoftApConfiguration.Builder();
+        configBuilder.setSsid(TEST_SSID);
+        configBuilder.setBands(dual_bands);
+        configBuilder.setBridgedModeOpportunisticShutdownEnabled(false);
+        SoftApModeConfiguration apConfig = new SoftApModeConfiguration(
+                WifiManager.IFACE_IP_MODE_TETHERED, configBuilder.build(),
+                mTestSoftApCapability);
+        startSoftApAndVerifyEnabled(apConfig);
+
+        verify(mResources)
+                .getInteger(R.integer.config_wifiFrameworkSoftApShutDownTimeoutMilliseconds);
+        verify(mResources)
+                .getInteger(R.integer
+                .config_wifiFrameworkSoftApShutDownIdleInstanceInBridgedModeTimeoutMillisecond);
+
+
+        // Verify timer is scheduled
+        verify(mAlarmManager.getAlarmManager()).setExact(anyInt(), anyLong(),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG), any(), any());
+
+        // Verify the bridged mode timer is scheduled
+        verify(mAlarmManager.getAlarmManager(), never()).setExact(anyInt(), anyLong(),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_IDLE_IN_BRIDGED_MODE_TIMEOUT_TAG),
+                any(), any());
+    }
+
+    @Test
+    public void testBridgedModeOpportunisticShutdownConfigureChanged()
+            throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        int[] dual_bands = {SoftApConfiguration.BAND_2GHZ ,
+                SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ};
+        Builder configBuilder = new SoftApConfiguration.Builder();
+        configBuilder.setSsid(TEST_SSID);
+        configBuilder.setBands(dual_bands);
+        configBuilder.setBridgedModeOpportunisticShutdownEnabled(false);
+        SoftApModeConfiguration apConfig = new SoftApModeConfiguration(
+                WifiManager.IFACE_IP_MODE_TETHERED, configBuilder.build(),
+                mTestSoftApCapability);
+        startSoftApAndVerifyEnabled(apConfig);
+
+        verify(mResources)
+                .getInteger(R.integer.config_wifiFrameworkSoftApShutDownTimeoutMilliseconds);
+        verify(mResources)
+                .getInteger(R.integer
+                .config_wifiFrameworkSoftApShutDownIdleInstanceInBridgedModeTimeoutMillisecond);
+
+
+        // Verify timer is scheduled
+        verify(mAlarmManager.getAlarmManager()).setExact(anyInt(), anyLong(),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG), any(), any());
+
+        // Verify the bridged mode timer is scheduled
+        verify(mAlarmManager.getAlarmManager(), never()).setExact(anyInt(), anyLong(),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_IDLE_IN_BRIDGED_MODE_TIMEOUT_TAG),
+                any(), any());
+        configBuilder.setBridgedModeOpportunisticShutdownEnabled(true);
+        mSoftApManager.updateConfiguration(configBuilder.build());
+        mLooper.dispatchAll();
+        // Verify the bridged mode timer is scheduled
+        verify(mAlarmManager.getAlarmManager()).setExact(anyInt(), anyLong(),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_IDLE_IN_BRIDGED_MODE_TIMEOUT_TAG),
+                any(), any());
+    }
+
+    @Test
+    public void testBridgedModeFallbackToSingleModeDueToUnavailableBand()
+            throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        int[] dual_bands = {SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_6GHZ,
+                SoftApConfiguration.BAND_5GHZ};
+        SoftApCapability testCapability = new SoftApCapability(mTestSoftApCapability);
+        testCapability.setSupportedChannelList(SoftApConfiguration.BAND_6GHZ, new int[0]);
+        Builder configBuilder = new SoftApConfiguration.Builder();
+        configBuilder.setSsid(TEST_SSID);
+        configBuilder.setBands(dual_bands);
+
+        SoftApModeConfiguration apConfig = new SoftApModeConfiguration(
+                WifiManager.IFACE_IP_MODE_TETHERED, configBuilder.build(),
+                testCapability);
+        // Reset band to 2.4G | 5G to generate expected configuration
+        configBuilder.setBand(SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ);
+        startSoftApAndVerifyEnabled(apConfig, TEST_COUNTRY_CODE, configBuilder.build());
+    }
+
+    @Test
+    public void testBridgedModeFallbackToSingleModeDueToPrimaryWifiConnectToUnavailableChannel()
+            throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        // TEST_SUPPORTED_5G_CHANNELS = 36, 149, mark to unsafe. Let Wifi connect to 5200 (CH40)
+        when(mPrimaryWifiInfo.getFrequency()).thenReturn(5200);
+        int[] dual_bands = {SoftApConfiguration.BAND_2GHZ,
+                SoftApConfiguration.BAND_5GHZ};
+        SoftApCapability testCapability = new SoftApCapability(mTestSoftApCapability);
+        Builder configBuilder = new SoftApConfiguration.Builder();
+        configBuilder.setSsid(TEST_SSID);
+        configBuilder.setBands(dual_bands);
+        SoftApModeConfiguration apConfig = new SoftApModeConfiguration(
+                WifiManager.IFACE_IP_MODE_TETHERED, configBuilder.build(),
+                testCapability);
+        // Reset band to 2.4G | 5G to generate expected configuration
+        configBuilder.setBand(SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ);
+        startSoftApAndVerifyEnabled(apConfig, TEST_COUNTRY_CODE, configBuilder.build());
+    }
+
+    @Test
+    public void testBridgedModeFallbackToSingleModeDueToSecondWifiConnectToUnavailableChannel()
+            throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        // Prepare second ClientModeManager
+        List<ClientModeManager> testClientModeManagers = new ArrayList<>(mTestClientModeManagers);
+        testClientModeManagers.add(mSecondConcreteClientModeManager);
+        when(mSecondConcreteClientModeManager.syncRequestConnectionInfo())
+                .thenReturn(mSecondWifiInfo);
+        when(mActiveModeWarden.getClientModeManagers()).thenReturn(testClientModeManagers);
+        // TEST_SUPPORTED_5G_CHANNELS = 36, 149, mark to unsafe. Let Wifi connect to 5200 (CH40)
+        when(mPrimaryWifiInfo.getFrequency()).thenReturn(5180);
+        when(mSecondWifiInfo.getFrequency()).thenReturn(5200);
+        int[] dual_bands = {SoftApConfiguration.BAND_2GHZ,
+                SoftApConfiguration.BAND_5GHZ};
+        SoftApCapability testCapability = new SoftApCapability(mTestSoftApCapability);
+        Builder configBuilder = new SoftApConfiguration.Builder();
+        configBuilder.setSsid(TEST_SSID);
+        configBuilder.setBands(dual_bands);
+        SoftApModeConfiguration apConfig = new SoftApModeConfiguration(
+                WifiManager.IFACE_IP_MODE_TETHERED, configBuilder.build(),
+                testCapability);
+        // Reset band to 2.4G | 5G to generate expected configuration
+        configBuilder.setBand(SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ);
+        startSoftApAndVerifyEnabled(apConfig, TEST_COUNTRY_CODE, configBuilder.build());
+    }
+
+    @Test
+    public void testKeepBridgedModeWhenWifiConnectToAvailableChannel()
+            throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        // TEST_SUPPORTED_5G_CHANNELS = 36, 149, mark to unsafe. Let Wifi connect to 5180 (CH36)
+        when(mPrimaryWifiInfo.getFrequency()).thenReturn(5180);
+        int[] dual_bands = {SoftApConfiguration.BAND_2GHZ,
+                SoftApConfiguration.BAND_5GHZ};
+        SoftApCapability testCapability = new SoftApCapability(mTestSoftApCapability);
+        Builder configBuilder = new SoftApConfiguration.Builder();
+        configBuilder.setSsid(TEST_SSID);
+        configBuilder.setBands(dual_bands);
+        SoftApModeConfiguration apConfig = new SoftApModeConfiguration(
+                WifiManager.IFACE_IP_MODE_TETHERED, configBuilder.build(),
+                testCapability);
+        startSoftApAndVerifyEnabled(apConfig, TEST_COUNTRY_CODE, configBuilder.build());
+    }
+
+    @Test
+    public void testBridgedModeKeepDueToCoexIsSoftUnsafeWhenStartingSAP()
+            throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        int[] dual_bands = {SoftApConfiguration.BAND_2GHZ,
+                SoftApConfiguration.BAND_5GHZ};
+        SoftApCapability testCapability = new SoftApCapability(mTestSoftApCapability);
+        Builder configBuilder = new SoftApConfiguration.Builder();
+        configBuilder.setSsid(TEST_SSID);
+        configBuilder.setBands(dual_bands);
+
+        // TEST_SUPPORTED_5G_CHANNELS = 36, 149,
+        // mark to unsafe but it doesn't change to hard unsafe.
+        when(mCoexManager.getCoexUnsafeChannels()).thenReturn(Arrays.asList(
+                new CoexUnsafeChannel(WifiScanner.WIFI_BAND_5_GHZ, 36),
+                new CoexUnsafeChannel(WifiScanner.WIFI_BAND_5_GHZ, 149)
+        ));
+
+        SoftApModeConfiguration apConfig = new SoftApModeConfiguration(
+                WifiManager.IFACE_IP_MODE_TETHERED, configBuilder.build(),
+                testCapability);
+        startSoftApAndVerifyEnabled(apConfig, TEST_COUNTRY_CODE, configBuilder.build());
+    }
+
+    @Test
+    public void testBridgedModeFallbackToSingleModeDueToCoexIsHardUnsafe()
+            throws Exception {
+        //leslee
+        assumeTrue(SdkLevel.isAtLeastS());
+        int[] dual_bands = {SoftApConfiguration.BAND_2GHZ,
+                SoftApConfiguration.BAND_5GHZ};
+        SoftApCapability testCapability = new SoftApCapability(mTestSoftApCapability);
+        Builder configBuilder = new SoftApConfiguration.Builder();
+        configBuilder.setSsid(TEST_SSID);
+        configBuilder.setBands(dual_bands);
+
+        // TEST_SUPPORTED_5G_CHANNELS = 36, 149, mark to unsafe.
+        when(mCoexManager.getCoexRestrictions()).thenReturn(WifiManager.COEX_RESTRICTION_SOFTAP);
+        when(mCoexManager.getCoexUnsafeChannels()).thenReturn(Arrays.asList(
+                new CoexUnsafeChannel(WifiScanner.WIFI_BAND_5_GHZ, 36),
+                new CoexUnsafeChannel(WifiScanner.WIFI_BAND_5_GHZ, 149)
+        ));
+
+        SoftApModeConfiguration apConfig = new SoftApModeConfiguration(
+                WifiManager.IFACE_IP_MODE_TETHERED, configBuilder.build(),
+                testCapability);
+        // Reset band to 2.4G to generate expected configuration
+        configBuilder.setBand(SoftApConfiguration.BAND_2GHZ);
+        startSoftApAndVerifyEnabled(apConfig, TEST_COUNTRY_CODE, configBuilder.build());
+    }
+
+    @Test
+    public void testBridgedModeKeepWhenCoexChangedToSoftUnsafe()
+            throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        // TEST_SUPPORTED_5G_CHANNELS = 36, 149, mark to safe. Let Wifi connect to 5180 (CH36)
+        when(mPrimaryWifiInfo.getFrequency()).thenReturn(5180);
+        int[] dual_bands = {SoftApConfiguration.BAND_2GHZ,
+                SoftApConfiguration.BAND_5GHZ};
+        SoftApCapability testCapability = new SoftApCapability(mTestSoftApCapability);
+        Builder configBuilder = new SoftApConfiguration.Builder();
+        configBuilder.setSsid(TEST_SSID);
+        configBuilder.setBands(dual_bands);
+        SoftApModeConfiguration apConfig = new SoftApModeConfiguration(
+                WifiManager.IFACE_IP_MODE_TETHERED, configBuilder.build(),
+                testCapability);
+        startSoftApAndVerifyEnabled(apConfig, TEST_COUNTRY_CODE, configBuilder.build());
+
+        // SoftApInfo updated
+        mockApInfoChangedEvent(mTestSoftApInfo);
+        mLooper.dispatchAll();
+        verify(mCallback).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, true);
+
+        mockApInfoChangedEvent(mTestSoftApInfoOnSecondInterface);
+        mLooper.dispatchAll();
+        verify(mCallback, times(2)).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, true);
+
+        // Test with soft unsafe channels
+        // TEST_SUPPORTED_5G_CHANNELS = 36, 149, mark to unsafe.
+        when(mCoexManager.getCoexUnsafeChannels()).thenReturn(Arrays.asList(
+                new CoexUnsafeChannel(WifiScanner.WIFI_BAND_5_GHZ, 36),
+                new CoexUnsafeChannel(WifiScanner.WIFI_BAND_5_GHZ, 149)
+        ));
+
+        // Trigger coex unsafe channel changed
+        mCoexListenerCaptor.getValue().onCoexUnsafeChannelsChanged();
+        mLooper.dispatchAll();
+        // Verify the remove correct iface and instance
+        verify(mWifiNative, never()).removeIfaceInstanceFromBridgedApIface(any(),
+                any());
+    }
+
+
+    @Test
+    public void testBridgedModeShutDownInstanceDueToCoexIsHardUnsafe()
+            throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        // TEST_SUPPORTED_5G_CHANNELS = 36, 149, mark to safe. Let Wifi connect to 5180 (CH36)
+        when(mPrimaryWifiInfo.getFrequency()).thenReturn(5180);
+        int[] dual_bands = {SoftApConfiguration.BAND_2GHZ,
+                SoftApConfiguration.BAND_5GHZ};
+        SoftApCapability testCapability = new SoftApCapability(mTestSoftApCapability);
+        Builder configBuilder = new SoftApConfiguration.Builder();
+        configBuilder.setSsid(TEST_SSID);
+        configBuilder.setBands(dual_bands);
+        SoftApModeConfiguration apConfig = new SoftApModeConfiguration(
+                WifiManager.IFACE_IP_MODE_TETHERED, configBuilder.build(),
+                testCapability);
+        startSoftApAndVerifyEnabled(apConfig, TEST_COUNTRY_CODE, configBuilder.build());
+
+        // SoftApInfo updated
+        mockApInfoChangedEvent(mTestSoftApInfo);
+        mLooper.dispatchAll();
+        verify(mCallback).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, true);
+
+        mockApInfoChangedEvent(mTestSoftApInfoOnSecondInterface);
+        mLooper.dispatchAll();
+        verify(mCallback, times(2)).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, true);
+
+        // Test with hard unsafe channels
+        when(mCoexManager.getCoexRestrictions()).thenReturn(WifiManager.COEX_RESTRICTION_SOFTAP);
+        // TEST_SUPPORTED_5G_CHANNELS = 36, 149, mark to unsafe.
+        when(mCoexManager.getCoexUnsafeChannels()).thenReturn(Arrays.asList(
+                new CoexUnsafeChannel(WifiScanner.WIFI_BAND_5_GHZ, 36),
+                new CoexUnsafeChannel(WifiScanner.WIFI_BAND_5_GHZ, 149)
+        ));
+
+        // Trigger coex unsafe channel changed
+        mCoexListenerCaptor.getValue().onCoexUnsafeChannelsChanged();
+        mLooper.dispatchAll();
+        // Verify the remove correct iface and instance
+        verify(mWifiNative).removeIfaceInstanceFromBridgedApIface(eq(TEST_INTERFACE_NAME),
+                eq(TEST_SECOND_INTERFACE_NAME));
+        mLooper.dispatchAll();
+        mTestSoftApInfoMap.clear();
+        mTestWifiClientsMap.clear();
+
+        mTestSoftApInfoMap.put(mTestSoftApInfo.getApInstanceIdentifier(), mTestSoftApInfo);
+        mTestWifiClientsMap.put(mTestSoftApInfo.getApInstanceIdentifier(),
+                new ArrayList<WifiClient>());
+
+        verify(mCallback, times(3)).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, true);
+    }
+
+    @Test
+    public void testBridgedModeKeepWhenCoexChangedButAvailableChannelExist()
+            throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        // TEST_SUPPORTED_5G_CHANNELS = 36, 149, mark to unsafe. Let Wifi connect to 5180 (CH36)
+        when(mPrimaryWifiInfo.getFrequency()).thenReturn(5180);
+        int[] dual_bands = {SoftApConfiguration.BAND_2GHZ,
+                SoftApConfiguration.BAND_5GHZ};
+        SoftApCapability testCapability = new SoftApCapability(mTestSoftApCapability);
+        Builder configBuilder = new SoftApConfiguration.Builder();
+        configBuilder.setSsid(TEST_SSID);
+        configBuilder.setBands(dual_bands);
+        SoftApModeConfiguration apConfig = new SoftApModeConfiguration(
+                WifiManager.IFACE_IP_MODE_TETHERED, configBuilder.build(),
+                testCapability);
+        startSoftApAndVerifyEnabled(apConfig, TEST_COUNTRY_CODE, configBuilder.build());
+
+        // SoftApInfo updated
+        mockApInfoChangedEvent(mTestSoftApInfo);
+        mLooper.dispatchAll();
+        verify(mCallback).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, true);
+
+        mockApInfoChangedEvent(mTestSoftApInfoOnSecondInterface);
+        mLooper.dispatchAll();
+        verify(mCallback, times(2)).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, true);
+
+
+        // TEST_SUPPORTED_5G_CHANNELS = 36, 149, only mark 36 is unsafe.
+        when(mCoexManager.getCoexUnsafeChannels()).thenReturn(Arrays.asList(
+                new CoexUnsafeChannel(WifiScanner.WIFI_BAND_5_GHZ, 36)
+        ));
+
+        // Trigger coex unsafe channel changed
+        mCoexListenerCaptor.getValue().onCoexUnsafeChannelsChanged();
+        mLooper.dispatchAll();
+        // Verify the remove correct iface and instance
+        verify(mWifiNative, never()).removeIfaceInstanceFromBridgedApIface(any(), any());
+    }
+
+    @Test
+    public void testBridgedModeKeepWhenWifiConnectedToAvailableChannel()
+            throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        int[] dual_bands = {SoftApConfiguration.BAND_2GHZ,
+                SoftApConfiguration.BAND_5GHZ};
+        SoftApCapability testCapability = new SoftApCapability(mTestSoftApCapability);
+        Builder configBuilder = new SoftApConfiguration.Builder();
+        configBuilder.setSsid(TEST_SSID);
+        configBuilder.setBands(dual_bands);
+        SoftApModeConfiguration apConfig = new SoftApModeConfiguration(
+                WifiManager.IFACE_IP_MODE_TETHERED, configBuilder.build(),
+                testCapability);
+        startSoftApAndVerifyEnabled(apConfig, TEST_COUNTRY_CODE, configBuilder.build());
+
+        // SoftApInfo updated
+        mockApInfoChangedEvent(mTestSoftApInfo);
+        mLooper.dispatchAll();
+        verify(mCallback).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, true);
+
+        mockApInfoChangedEvent(mTestSoftApInfoOnSecondInterface);
+        mLooper.dispatchAll();
+        verify(mCallback, times(2)).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, true);
+
+        // TEST_SUPPORTED_5G_CHANNELS = 36, 149, mark to unsafe. Let Wifi connect to 5180 (CH36)
+        when(mPrimaryWifiInfo.getFrequency()).thenReturn(5180);
+
+        // Trigger wifi connected
+        mCmiListenerCaptor.getValue().onL2Connected(mConcreteClientModeManager);
+        mLooper.dispatchAll();
+        // Verify the remove correct iface and instance
+        verify(mWifiNative, never()).removeIfaceInstanceFromBridgedApIface(any(), any());
+    }
+
+    @Test
+    public void testBridgedModeShutDownInstanceDueToWifiConnectedToUnavailableChannel()
+            throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        int[] dual_bands = {SoftApConfiguration.BAND_2GHZ, SoftApConfiguration.BAND_5GHZ};
+        SoftApCapability testCapability = new SoftApCapability(mTestSoftApCapability);
+        Builder configBuilder = new SoftApConfiguration.Builder();
+        configBuilder.setSsid(TEST_SSID);
+        configBuilder.setBands(dual_bands);
+        SoftApModeConfiguration apConfig = new SoftApModeConfiguration(
+                WifiManager.IFACE_IP_MODE_TETHERED, configBuilder.build(),
+                testCapability);
+        startSoftApAndVerifyEnabled(apConfig, TEST_COUNTRY_CODE, configBuilder.build());
+
+        // SoftApInfo updated
+        mockApInfoChangedEvent(mTestSoftApInfo);
+        mLooper.dispatchAll();
+        verify(mCallback).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, true);
+
+        mockApInfoChangedEvent(mTestSoftApInfoOnSecondInterface);
+        mLooper.dispatchAll();
+        verify(mCallback, times(2)).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, true);
+
+        // TEST_SUPPORTED_5G_CHANNELS = 36, 149, mark to unsafe. Let Wifi connect to 5945 (6G)
+        when(mPrimaryWifiInfo.getFrequency()).thenReturn(5945);
+
+        // Trigger wifi connected
+        mCmiListenerCaptor.getValue().onL2Connected(mConcreteClientModeManager);
+        mLooper.dispatchAll();
+        // Verify the remove correct iface and instance
+        verify(mWifiNative).removeIfaceInstanceFromBridgedApIface(eq(TEST_INTERFACE_NAME),
+                eq(TEST_SECOND_INTERFACE_NAME));
+        mLooper.dispatchAll();
+        mTestSoftApInfoMap.clear();
+        mTestWifiClientsMap.clear();
+
+        mTestSoftApInfoMap.put(mTestSoftApInfo.getApInstanceIdentifier(), mTestSoftApInfo);
+        mTestWifiClientsMap.put(mTestSoftApInfo.getApInstanceIdentifier(),
+                new ArrayList<WifiClient>());
+
+        verify(mCallback, times(3)).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, true);
+    }
+
+    @Test
+    public void testUpdateCountryCodeWhenConfigDisabled() throws Exception {
+        when(mResources.getBoolean(R.bool.config_wifiSoftApDynamicCountryCodeUpdateSupported))
+                .thenReturn(false);
+        SoftApModeConfiguration apConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
+                mTestSoftApCapability);
+        startSoftApAndVerifyEnabled(apConfig);
+        reset(mWifiNative);
+        mSoftApManager.updateCountryCode(TEST_COUNTRY_CODE + "TW");
+        mLooper.dispatchAll();
+        verify(mWifiNative, never()).setApCountryCode(any(), any());
+    }
+
+    @Test
+    public void testUpdateCountryCodeWhenConfigEnabled() throws Exception {
+        when(mResources.getBoolean(R.bool.config_wifiSoftApDynamicCountryCodeUpdateSupported))
+                .thenReturn(true);
+        SoftApModeConfiguration apConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
+                mTestSoftApCapability);
+        startSoftApAndVerifyEnabled(apConfig);
+        reset(mWifiNative);
+        mSoftApManager.updateCountryCode(TEST_COUNTRY_CODE + "TW");
+        mLooper.dispatchAll();
+        verify(mWifiNative).setApCountryCode(any(), any());
+    }
+
+    @Test
+    public void testUpdateSameCountryCodeWhenConfigEnabled() throws Exception {
+        when(mResources.getBoolean(R.bool.config_wifiSoftApDynamicCountryCodeUpdateSupported))
+                .thenReturn(true);
+        SoftApModeConfiguration apConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
+                mTestSoftApCapability);
+        startSoftApAndVerifyEnabled(apConfig);
+        reset(mWifiNative);
+        mSoftApManager.updateCountryCode(TEST_COUNTRY_CODE);
+        mLooper.dispatchAll();
+        verify(mWifiNative, never()).setApCountryCode(any(), any());
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/SoftApNotifierTest.java b/service/tests/wifitests/src/com/android/server/wifi/SoftApNotifierTest.java
index 3d4d239..1e02f60 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/SoftApNotifierTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/SoftApNotifierTest.java
@@ -24,7 +24,6 @@
 import static org.mockito.Mockito.when;
 
 import android.app.Notification;
-import android.app.NotificationManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
@@ -47,7 +46,7 @@
 
     @Mock WifiContext mContext;
     @Mock Resources mResources;
-    @Mock NotificationManager mNotificationManager;
+    @Mock WifiNotificationManager mWifiNotificationManager;
     @Mock FrameworkFacade mFrameworkFacade;
     @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Notification.Builder mNotificationBuilder;
     SoftApNotifier mSoftApNotifier;
@@ -58,11 +57,9 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        when(mContext.getSystemService(NotificationManager.class))
-                .thenReturn(mNotificationManager);
         when(mContext.getResources()).thenReturn(mResources);
         when(mContext.getWifiOverlayApkPkgName()).thenReturn("test.com.android.wifi.resources");
-        mSoftApNotifier = new SoftApNotifier(mContext, mFrameworkFacade);
+        mSoftApNotifier = new SoftApNotifier(mContext, mFrameworkFacade, mWifiNotificationManager);
     }
 
     /**
@@ -71,11 +68,11 @@
      * @throws Exception
      */
     @Test
-    public void showSoftApShutDownTimeoutExpiredNotification() throws Exception {
+    public void showSoftApShutdownTimeoutExpiredNotification() throws Exception {
         when(mFrameworkFacade.makeNotificationBuilder(any(),
                 eq(WifiService.NOTIFICATION_NETWORK_STATUS))).thenReturn(mNotificationBuilder);
-        mSoftApNotifier.showSoftApShutDownTimeoutExpiredNotification();
-        verify(mNotificationManager).notify(
+        mSoftApNotifier.showSoftApShutdownTimeoutExpiredNotification();
+        verify(mWifiNotificationManager).notify(
                 eq(mSoftApNotifier.NOTIFICATION_ID_SOFTAP_AUTO_DISABLED), any());
         ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
         verify(mFrameworkFacade).getActivity(
@@ -90,9 +87,9 @@
      * @throws Exception
      */
     @Test
-    public void dismissSoftApShutDownTimeoutExpiredNotification() throws Exception {
-        mSoftApNotifier.dismissSoftApShutDownTimeoutExpiredNotification();
-        verify(mNotificationManager).cancel(any(),
+    public void dismissSoftApShutdownTimeoutExpiredNotification() throws Exception {
+        mSoftApNotifier.dismissSoftApShutdownTimeoutExpiredNotification();
+        verify(mWifiNotificationManager).cancel(
                 eq(mSoftApNotifier.NOTIFICATION_ID_SOFTAP_AUTO_DISABLED));
     }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/SoftApStoreDataTest.java b/service/tests/wifitests/src/com/android/server/wifi/SoftApStoreDataTest.java
index 7399871..7b0044a 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/SoftApStoreDataTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/SoftApStoreDataTest.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -32,11 +33,13 @@
 import android.net.wifi.SoftApConfiguration;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiMigration;
+import android.util.SparseIntArray;
 import android.util.Xml;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.util.FastXmlSerializer;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.util.SettingsMigrationDataHolder;
 import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
 
@@ -60,13 +63,17 @@
 @SmallTest
 public class SoftApStoreDataTest extends WifiBaseTest {
     private static final String TEST_SSID = "SSID";
-    private static final String TEST_BSSID = "11:22:33:aa:bb:cc";
+    private static final String TEST_BSSID = "aa:22:33:aa:bb:cc";
     private static final String TEST_PASSPHRASE = "TestPassphrase";
     private static final String TEST_WPA2_PASSPHRASE = "Wpa2Test";
     private static final int TEST_CHANNEL = 0;
+    private static final int TEST_CHANNEL_2G = 1;
+    private static final int TEST_CHANNEL_5G = 149;
     private static final boolean TEST_HIDDEN = false;
     private static final int TEST_BAND = SoftApConfiguration.BAND_2GHZ
             | SoftApConfiguration.BAND_5GHZ;
+    private static final int TEST_BAND_2G = SoftApConfiguration.BAND_2GHZ;
+    private static final int TEST_BAND_5G = SoftApConfiguration.BAND_5GHZ;
     private static final int TEST_OLD_BAND = WifiConfiguration.AP_BAND_ANY;
     private static final int TEST_SECURITY = SoftApConfiguration.SECURITY_TYPE_WPA2_PSK;
     private static final boolean TEST_CLIENT_CONTROL_BY_USER = false;
@@ -77,8 +84,21 @@
     private static final String TEST_BLOCKED_CLIENT = "11:22:33:44:55:66";
     private static final ArrayList<MacAddress> TEST_ALLOWEDLIST = new ArrayList<>();
     private static final String TEST_ALLOWED_CLIENT = "aa:bb:cc:dd:ee:ff";
+    private static final boolean TEST_BRIDGED_OPPORTUNISTIC_SHUTDOWN_ENABLED = false;
+    private static final int TEST_MAC_RANDOMIZATIONSETTING =
+            SoftApConfiguration.RANDOMIZATION_NONE;
+    private static final SparseIntArray TEST_CHANNELS = new SparseIntArray() {{
+            put(TEST_BAND_2G, TEST_CHANNEL_2G);
+            put(TEST_BAND_5G, TEST_CHANNEL_5G);
+            }};
+    private static final SparseIntArray TEST_CHANNELS_IN_R_CONFIG = new SparseIntArray() {{
+            put(TEST_BAND, TEST_CHANNEL);
+            }};
 
-    private static final String TEST_SOFTAP_CONFIG_XML_STRING =
+    private static final boolean TEST_80211AX_ENABLED = false;
+    private static final boolean TEST_USER_CONFIGURATION = false;
+
+    private static final String TEST_CONFIG_STRING_FROM_WIFICONFIGURATION =
             "<string name=\"SSID\">" + TEST_SSID + "</string>\n"
                     + "<int name=\"Band\" value=\"" + TEST_OLD_BAND + "\" />\n"
                     + "<int name=\"Channel\" value=\"" + TEST_CHANNEL + "\" />\n"
@@ -86,7 +106,7 @@
                     + "<int name=\"SecurityType\" value=\"" + TEST_SECURITY + "\" />\n"
                     + "<string name=\"Wpa2Passphrase\">" + TEST_WPA2_PASSPHRASE + "</string>\n";
 
-    private static final String TEST_SOFTAP_CONFIG_XML_STRING_WITH_NEW_BAND_DESIGN =
+    private static final String TEST_CONFIG_STRING_WITH_NEW_BAND_DESIGN_IN_R =
             "<string name=\"SSID\">" + TEST_SSID + "</string>\n"
                     + "<int name=\"ApBand\" value=\"" + TEST_BAND + "\" />\n"
                     + "<int name=\"Channel\" value=\"" + TEST_CHANNEL + "\" />\n"
@@ -94,7 +114,7 @@
                     + "<int name=\"SecurityType\" value=\"" + TEST_SECURITY + "\" />\n"
                     + "<string name=\"Passphrase\">" + TEST_PASSPHRASE + "</string>\n";
 
-    private static final String TEST_SOFTAP_CONFIG_XML_STRING_WITH_ALL_CONFIG =
+    private static final String TEST_CONFIG_STRING_WITH_ALL_CONFIG_IN_R =
             "<string name=\"SSID\">" + TEST_SSID + "</string>\n"
                     + "<string name=\"Bssid\">" + TEST_BSSID + "</string>\n"
                     + "<int name=\"ApBand\" value=\"" + TEST_BAND + "\" />\n"
@@ -117,7 +137,7 @@
                     + "<string name=\"ClientMacAddress\">" + TEST_ALLOWED_CLIENT + "</string>\n"
                     + "</AllowedClientList>\n";
 
-    private static final String TEST_SOFTAP_CONFIG_XML_STRING_WITH_ALL_CONFIG_EXCEPT_AUTO_SHUTDOWN =
+    private static final String TEST_CONFIG_STRING_WITH_ALL_CONFIG_IN_R_EXCEPT_AUTO_SHUTDOWN =
             "<string name=\"SSID\">" + TEST_SSID + "</string>\n"
                     + "<int name=\"ApBand\" value=\"" + TEST_BAND + "\" />\n"
                     + "<int name=\"Channel\" value=\"" + TEST_CHANNEL + "\" />\n"
@@ -137,7 +157,7 @@
                     + "<string name=\"ClientMacAddress\">" + TEST_ALLOWED_CLIENT + "</string>\n"
                     + "</AllowedClientList>\n";
 
-    private static final String TEST_SOFTAP_CONFIG_XML_STRING_WITH_INT_TYPE_SHUTDOWNTIMOUTMILLIS =
+    private static final String TEST_CONFIG_STRING_WITH_INT_TYPE_SHUTDOWNTIMOUTMILLIS =
             "<string name=\"SSID\">" + TEST_SSID + "</string>\n"
                     + "<int name=\"ApBand\" value=\"" + TEST_BAND + "\" />\n"
                     + "<int name=\"Channel\" value=\"" + TEST_CHANNEL + "\" />\n"
@@ -159,7 +179,7 @@
                     + "<string name=\"ClientMacAddress\">" + TEST_ALLOWED_CLIENT + "</string>\n"
                     + "</AllowedClientList>\n";
 
-    private static final String TEST_SOFTAP_CONFIG_XML_STRING_WITH_ALL_CONFIG_EXCEPT_BSSID =
+    private static final String TEST_CONFIG_STRING_WITH_ALL_CONFIG_IN_R_EXCEPT_BSSID =
             "<string name=\"SSID\">" + TEST_SSID + "</string>\n"
                     + "<int name=\"ApBand\" value=\"" + TEST_BAND + "\" />\n"
                     + "<int name=\"Channel\" value=\"" + TEST_CHANNEL + "\" />\n"
@@ -181,6 +201,48 @@
                     + "<string name=\"ClientMacAddress\">" + TEST_ALLOWED_CLIENT + "</string>\n"
                     + "</AllowedClientList>\n";
 
+    private static final String TEST_CONFIG_STRING_WITH_ALL_CONFIG_IN_S_EXCEPT_USER_CONFIGURATION =
+            "<string name=\"SSID\">" + TEST_SSID + "</string>\n"
+                    + "<string name=\"Bssid\">" + TEST_BSSID + "</string>\n"
+                    + "<boolean name=\"HiddenSSID\" value=\"" + TEST_HIDDEN + "\" />\n"
+                    + "<int name=\"SecurityType\" value=\"" + TEST_SECURITY + "\" />\n"
+                    + "<string name=\"Passphrase\">" + TEST_PASSPHRASE + "</string>\n"
+                    + "<int name=\"MaxNumberOfClients\" value=\""
+                    + TEST_MAX_NUMBER_OF_CLIENTS + "\" />\n"
+                    + "<boolean name=\"ClientControlByUser\" value=\""
+                    + TEST_CLIENT_CONTROL_BY_USER + "\" />\n"
+                    + "<boolean name=\"AutoShutdownEnabled\" value=\""
+                    + TEST_AUTO_SHUTDOWN_ENABLED + "\" />\n"
+                    + "<long name=\"ShutdownTimeoutMillis\" value=\""
+                    + TEST_SHUTDOWN_TIMEOUT_MILLIS + "\" />\n"
+                    + "<BlockedClientList>\n"
+                    + "<string name=\"ClientMacAddress\">" + TEST_BLOCKED_CLIENT + "</string>\n"
+                    + "</BlockedClientList>\n"
+                    + "<AllowedClientList>\n"
+                    + "<string name=\"ClientMacAddress\">" + TEST_ALLOWED_CLIENT + "</string>\n"
+                    + "</AllowedClientList>\n"
+                    + "<boolean name=\"BridgedModeOpportunisticShutdownEnabled\" value=\""
+                    + TEST_BRIDGED_OPPORTUNISTIC_SHUTDOWN_ENABLED + "\" />\n"
+                    + "<int name=\"MacRandomizationSetting\" value=\""
+                    + TEST_MAC_RANDOMIZATIONSETTING + "\" />\n"
+                    + "<BandChannelMap>\n"
+                    + "<BandChannel>\n"
+                    + "<int name=\"Band\" value=\"" + TEST_BAND_2G + "\" />\n"
+                    + "<int name=\"Channel\" value=\"" + TEST_CHANNEL_2G + "\" />\n"
+                    + "</BandChannel>\n"
+                    + "<BandChannel>\n"
+                    + "<int name=\"Band\" value=\"" + TEST_BAND_5G + "\" />\n"
+                    + "<int name=\"Channel\" value=\"" + TEST_CHANNEL_5G + "\" />\n"
+                    + "</BandChannel>\n"
+                    + "</BandChannelMap>\n"
+                    + "<boolean name=\"80211axEnabled\" value=\""
+                    + TEST_80211AX_ENABLED + "\" />\n";
+
+    private static final String TEST_CONFIG_STRING_WITH_ALL_CONFIG_LAST_VERSION =
+            TEST_CONFIG_STRING_WITH_ALL_CONFIG_IN_S_EXCEPT_USER_CONFIGURATION
+                    + "<boolean name=\"UserConfiguration\" value=\""
+                    + TEST_USER_CONFIGURATION + "\" />\n";
+
     @Mock private Context mContext;
     @Mock SoftApStoreData.DataSource mDataSource;
     @Mock private WifiMigration.SettingsMigrationData mOemMigrationData;
@@ -274,17 +336,31 @@
         softApConfigBuilder.setBssid(MacAddress.fromString(TEST_BSSID));
         softApConfigBuilder.setPassphrase(TEST_PASSPHRASE,
                 SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
-        softApConfigBuilder.setBand(TEST_BAND);
+        if (SdkLevel.isAtLeastS()) {
+            softApConfigBuilder.setChannels(TEST_CHANNELS);
+        } else {
+            softApConfigBuilder.setBand(TEST_BAND);
+        }
         softApConfigBuilder.setClientControlByUserEnabled(TEST_CLIENT_CONTROL_BY_USER);
         softApConfigBuilder.setMaxNumberOfClients(TEST_MAX_NUMBER_OF_CLIENTS);
         softApConfigBuilder.setAutoShutdownEnabled(true);
         softApConfigBuilder.setShutdownTimeoutMillis(TEST_SHUTDOWN_TIMEOUT_MILLIS);
         softApConfigBuilder.setAllowedClientList(TEST_ALLOWEDLIST);
         softApConfigBuilder.setBlockedClientList(TEST_BLOCKEDLIST);
-
+        if (SdkLevel.isAtLeastS()) {
+            softApConfigBuilder.setMacRandomizationSetting(TEST_MAC_RANDOMIZATIONSETTING);
+            softApConfigBuilder.setBridgedModeOpportunisticShutdownEnabled(
+                    TEST_BRIDGED_OPPORTUNISTIC_SHUTDOWN_ENABLED);
+            softApConfigBuilder.setIeee80211axEnabled(TEST_80211AX_ENABLED);
+            softApConfigBuilder.setUserConfiguration(TEST_USER_CONFIGURATION);
+        }
         when(mDataSource.toSerialize()).thenReturn(softApConfigBuilder.build());
         byte[] actualData = serializeData();
-        assertEquals(TEST_SOFTAP_CONFIG_XML_STRING_WITH_ALL_CONFIG, new String(actualData));
+        if (SdkLevel.isAtLeastS()) {
+            assertEquals(TEST_CONFIG_STRING_WITH_ALL_CONFIG_LAST_VERSION, new String(actualData));
+        } else {
+            assertEquals(TEST_CONFIG_STRING_WITH_ALL_CONFIG_IN_R, new String(actualData));
+        }
     }
 
     /**
@@ -294,7 +370,11 @@
      */
     @Test
     public void deserializeSoftAp() throws Exception {
-        deserializeData(TEST_SOFTAP_CONFIG_XML_STRING_WITH_ALL_CONFIG.getBytes());
+        if (SdkLevel.isAtLeastS()) {
+            deserializeData(TEST_CONFIG_STRING_WITH_ALL_CONFIG_LAST_VERSION.getBytes());
+        } else {
+            deserializeData(TEST_CONFIG_STRING_WITH_ALL_CONFIG_IN_R.getBytes());
+        }
 
         ArgumentCaptor<SoftApConfiguration> softapConfigCaptor =
                 ArgumentCaptor.forClass(SoftApConfiguration.class);
@@ -306,13 +386,27 @@
         assertEquals(softApConfig.getPassphrase(), TEST_PASSPHRASE);
         assertEquals(softApConfig.getSecurityType(), SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
         assertEquals(softApConfig.isHiddenSsid(), TEST_HIDDEN);
-        assertEquals(softApConfig.getBand(), TEST_BAND);
+        if (SdkLevel.isAtLeastS()) {
+            assertEquals(softApConfig.getBand(), TEST_BAND_2G);
+            assertEquals(softApConfig.getChannel(), TEST_CHANNEL_2G);
+        } else {
+            assertEquals(softApConfig.getBand(), TEST_BAND);
+            assertEquals(softApConfig.getChannel(), TEST_CHANNEL);
+        }
         assertEquals(softApConfig.isClientControlByUserEnabled(), TEST_CLIENT_CONTROL_BY_USER);
         assertEquals(softApConfig.getMaxNumberOfClients(), TEST_MAX_NUMBER_OF_CLIENTS);
         assertTrue(softApConfig.isAutoShutdownEnabled());
         assertEquals(softApConfig.getShutdownTimeoutMillis(), TEST_SHUTDOWN_TIMEOUT_MILLIS);
         assertEquals(softApConfig.getBlockedClientList(), TEST_BLOCKEDLIST);
         assertEquals(softApConfig.getAllowedClientList(), TEST_ALLOWEDLIST);
+        if (SdkLevel.isAtLeastS()) {
+            assertEquals(softApConfig.getChannels().toString(), TEST_CHANNELS.toString());
+            assertEquals(softApConfig.getMacRandomizationSetting(), TEST_MAC_RANDOMIZATIONSETTING);
+            assertEquals(softApConfig.isBridgedModeOpportunisticShutdownEnabled(),
+                    TEST_BRIDGED_OPPORTUNISTIC_SHUTDOWN_ENABLED);
+            assertEquals(softApConfig.isIeee80211axEnabled(), TEST_80211AX_ENABLED);
+            assertEquals(softApConfig.isUserConfiguration(), TEST_USER_CONFIGURATION);
+        }
     }
 
     /**
@@ -322,7 +416,7 @@
      */
     @Test
     public void deserializeOldSoftApXMLWhichShutdownTimeoutIsInt() throws Exception {
-        deserializeData(TEST_SOFTAP_CONFIG_XML_STRING_WITH_INT_TYPE_SHUTDOWNTIMOUTMILLIS
+        deserializeData(TEST_CONFIG_STRING_WITH_INT_TYPE_SHUTDOWNTIMOUTMILLIS
                 .getBytes());
 
         ArgumentCaptor<SoftApConfiguration> softapConfigCaptor =
@@ -351,7 +445,7 @@
     @Test
     public void deserializeOldBandSoftAp() throws Exception {
         // Start with the old serialized data
-        deserializeData(TEST_SOFTAP_CONFIG_XML_STRING.getBytes());
+        deserializeData(TEST_CONFIG_STRING_FROM_WIFICONFIGURATION.getBytes());
 
         ArgumentCaptor<SoftApConfiguration> softapConfigCaptor =
                 ArgumentCaptor.forClass(SoftApConfiguration.class);
@@ -373,7 +467,7 @@
     @Test
     public void deserializeNewBandSoftApButNoNewConfig() throws Exception {
         // Start with the old serialized data
-        deserializeData(TEST_SOFTAP_CONFIG_XML_STRING_WITH_NEW_BAND_DESIGN.getBytes());
+        deserializeData(TEST_CONFIG_STRING_WITH_NEW_BAND_DESIGN_IN_R.getBytes());
 
         ArgumentCaptor<SoftApConfiguration> softapConfigCaptor =
                 ArgumentCaptor.forClass(SoftApConfiguration.class);
@@ -510,7 +604,7 @@
         // Toggle on when migrating.
         when(mOemMigrationData.isSoftApTimeoutEnabled()).thenReturn(true);
         deserializeData(
-                TEST_SOFTAP_CONFIG_XML_STRING_WITH_ALL_CONFIG_EXCEPT_AUTO_SHUTDOWN.getBytes());
+                TEST_CONFIG_STRING_WITH_ALL_CONFIG_IN_R_EXCEPT_AUTO_SHUTDOWN.getBytes());
         ArgumentCaptor<SoftApConfiguration> softapConfigCaptor =
                 ArgumentCaptor.forClass(SoftApConfiguration.class);
         verify(mDataSource).fromDeserialized(softapConfigCaptor.capture());
@@ -522,7 +616,7 @@
         // Toggle off when migrating.
         when(mOemMigrationData.isSoftApTimeoutEnabled()).thenReturn(false);
         deserializeData(
-                TEST_SOFTAP_CONFIG_XML_STRING_WITH_ALL_CONFIG_EXCEPT_AUTO_SHUTDOWN.getBytes());
+                TEST_CONFIG_STRING_WITH_ALL_CONFIG_IN_R_EXCEPT_AUTO_SHUTDOWN.getBytes());
         verify(mDataSource, times(2)).fromDeserialized(softapConfigCaptor.capture());
         softApConfig = softapConfigCaptor.getValue();
         assertNotNull(softApConfig);
@@ -538,7 +632,8 @@
     @Test
     public void deserializeSoftApWithNoBssidTag() throws Exception {
         // Start with the old serialized data
-        deserializeData(TEST_SOFTAP_CONFIG_XML_STRING_WITH_ALL_CONFIG_EXCEPT_BSSID.getBytes());
+        deserializeData(TEST_CONFIG_STRING_WITH_ALL_CONFIG_IN_R_EXCEPT_BSSID
+                .getBytes());
         ArgumentCaptor<SoftApConfiguration> softapConfigCaptor =
                 ArgumentCaptor.forClass(SoftApConfiguration.class);
         verify(mDataSource).fromDeserialized(softapConfigCaptor.capture());
@@ -556,4 +651,74 @@
         assertEquals(softApConfig.getBlockedClientList(), TEST_BLOCKEDLIST);
         assertEquals(softApConfig.getAllowedClientList(), TEST_ALLOWEDLIST);
     }
+
+    /**
+     * Verify that the old format is deserialized correctly.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void deserializeSoftApWithAllConfigInR() throws Exception {
+        // Start with the old serialized data
+        deserializeData(TEST_CONFIG_STRING_WITH_ALL_CONFIG_IN_R
+                .getBytes());
+        ArgumentCaptor<SoftApConfiguration> softapConfigCaptor =
+                ArgumentCaptor.forClass(SoftApConfiguration.class);
+        verify(mDataSource).fromDeserialized(softapConfigCaptor.capture());
+        SoftApConfiguration softApConfig = softapConfigCaptor.getValue();
+        assertNotNull(softApConfig);
+        assertEquals(softApConfig.getSsid(), TEST_SSID);
+        assertEquals(softApConfig.getBssid().toString(), TEST_BSSID);
+        assertEquals(softApConfig.getPassphrase(), TEST_PASSPHRASE);
+        assertEquals(softApConfig.getSecurityType(), SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
+        assertEquals(softApConfig.isHiddenSsid(), TEST_HIDDEN);
+        assertEquals(softApConfig.getBand(), TEST_BAND);
+        assertEquals(softApConfig.isClientControlByUserEnabled(), TEST_CLIENT_CONTROL_BY_USER);
+        assertEquals(softApConfig.getMaxNumberOfClients(), TEST_MAX_NUMBER_OF_CLIENTS);
+        assertTrue(softApConfig.isAutoShutdownEnabled());
+        assertEquals(softApConfig.getShutdownTimeoutMillis(), TEST_SHUTDOWN_TIMEOUT_MILLIS);
+        assertEquals(softApConfig.getBlockedClientList(), TEST_BLOCKEDLIST);
+        assertEquals(softApConfig.getAllowedClientList(), TEST_ALLOWEDLIST);
+        if (SdkLevel.isAtLeastS()) {
+            assertEquals(softApConfig.getChannels().toString(),
+                    TEST_CHANNELS_IN_R_CONFIG.toString());
+        }
+    }
+
+    /**
+     * Verify that the old format is deserialized correctly.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void deserializeSoftApWithAllConfigInSExceptUserConfiguration() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        // Start with the old serialized data
+        deserializeData(TEST_CONFIG_STRING_WITH_ALL_CONFIG_IN_S_EXCEPT_USER_CONFIGURATION
+                .getBytes());
+        ArgumentCaptor<SoftApConfiguration> softapConfigCaptor =
+                ArgumentCaptor.forClass(SoftApConfiguration.class);
+        verify(mDataSource).fromDeserialized(softapConfigCaptor.capture());
+        SoftApConfiguration softApConfig = softapConfigCaptor.getValue();
+        assertNotNull(softApConfig);
+        assertEquals(softApConfig.getSsid(), TEST_SSID);
+        assertEquals(softApConfig.getBssid().toString(), TEST_BSSID);
+        assertEquals(softApConfig.getPassphrase(), TEST_PASSPHRASE);
+        assertEquals(softApConfig.getSecurityType(), SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
+        assertEquals(softApConfig.isHiddenSsid(), TEST_HIDDEN);
+        assertEquals(softApConfig.getBand(), TEST_BAND_2G);
+        assertEquals(softApConfig.getChannel(), TEST_CHANNEL_2G);
+        assertEquals(softApConfig.getChannels().toString(), TEST_CHANNELS.toString());
+        assertEquals(softApConfig.isClientControlByUserEnabled(), TEST_CLIENT_CONTROL_BY_USER);
+        assertEquals(softApConfig.getMaxNumberOfClients(), TEST_MAX_NUMBER_OF_CLIENTS);
+        assertTrue(softApConfig.isAutoShutdownEnabled());
+        assertEquals(softApConfig.getShutdownTimeoutMillis(), TEST_SHUTDOWN_TIMEOUT_MILLIS);
+        assertEquals(softApConfig.getBlockedClientList(), TEST_BLOCKEDLIST);
+        assertEquals(softApConfig.getAllowedClientList(), TEST_ALLOWEDLIST);
+        assertEquals(softApConfig.getMacRandomizationSetting(), TEST_MAC_RANDOMIZATIONSETTING);
+        assertEquals(softApConfig.isBridgedModeOpportunisticShutdownEnabled(),
+                TEST_BRIDGED_OPPORTUNISTIC_SHUTDOWN_ENABLED);
+        assertEquals(softApConfig.isIeee80211axEnabled(), TEST_80211AX_ENABLED);
+        assertEquals(softApConfig.isUserConfiguration(), true);
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/SupplicantStaIfaceHalTest.java b/service/tests/wifitests/src/com/android/server/wifi/SupplicantStaIfaceHalTest.java
index 7e0c20f..7d429b3 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/SupplicantStaIfaceHalTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/SupplicantStaIfaceHalTest.java
@@ -16,6 +16,7 @@
 package com.android.server.wifi;
 
 import static android.net.wifi.WifiManager.WIFI_FEATURE_DPP;
+import static android.net.wifi.WifiManager.WIFI_FEATURE_DPP_ENROLLEE_RESPONDER;
 import static android.net.wifi.WifiManager.WIFI_FEATURE_FILS_SHA256;
 import static android.net.wifi.WifiManager.WIFI_FEATURE_FILS_SHA384;
 import static android.net.wifi.WifiManager.WIFI_FEATURE_MBO;
@@ -65,9 +66,12 @@
 import android.hardware.wifi.supplicant.V1_0.SupplicantStatus;
 import android.hardware.wifi.supplicant.V1_0.SupplicantStatusCode;
 import android.hardware.wifi.supplicant.V1_0.WpsConfigMethods;
-import android.hardware.wifi.supplicant.V1_3.ConnectionCapabilities;
 import android.hardware.wifi.supplicant.V1_3.ISupplicantStaIfaceCallback.BssTmData;
 import android.hardware.wifi.supplicant.V1_3.WifiTechnology;
+import android.hardware.wifi.supplicant.V1_4.ConnectionCapabilities;
+import android.hardware.wifi.supplicant.V1_4.DppCurve;
+import android.hardware.wifi.supplicant.V1_4.ISupplicantStaIfaceCallback.AssociationRejectionData;
+import android.hardware.wifi.supplicant.V1_4.LegacyMode;
 import android.hidl.manager.V1_0.IServiceManager;
 import android.hidl.manager.V1_0.IServiceNotification;
 import android.net.MacAddress;
@@ -136,6 +140,7 @@
     private android.hardware.wifi.supplicant.V1_1.ISupplicant mISupplicantMockV1_1;
     private android.hardware.wifi.supplicant.V1_2.ISupplicant mISupplicantMockV1_2;
     private android.hardware.wifi.supplicant.V1_3.ISupplicant mISupplicantMockV13;
+    private android.hardware.wifi.supplicant.V1_3.ISupplicant mISupplicantMockV14;
     private @Mock ISupplicantIface mISupplicantIfaceMock;
     private @Mock ISupplicantStaIface mISupplicantStaIfaceMock;
     private @Mock android.hardware.wifi.supplicant.V1_1.ISupplicantStaIface
@@ -144,6 +149,8 @@
             mISupplicantStaIfaceMockV1_2;
     private @Mock android.hardware.wifi.supplicant.V1_3.ISupplicantStaIface
             mISupplicantStaIfaceMockV13;
+    private @Mock android.hardware.wifi.supplicant.V1_4.ISupplicantStaIface
+            mISupplicantStaIfaceMockV14;
     private @Mock Context mContext;
     private @Mock WifiMonitor mWifiMonitor;
     private @Mock FrameworkFacade mFrameworkFacade;
@@ -151,9 +158,12 @@
     private @Mock WifiNative.SupplicantDeathEventHandler mSupplicantHalDeathHandler;
     private @Mock Clock mClock;
     private @Mock WifiMetrics mWifiMetrics;
+    private @Mock WifiGlobals mWifiGlobals;
 
     SupplicantStatus mStatusSuccess;
     SupplicantStatus mStatusFailure;
+    android.hardware.wifi.supplicant.V1_4.SupplicantStatus mStatusSuccessV14;
+    android.hardware.wifi.supplicant.V1_4.SupplicantStatus mStatusFailureV14;
     ISupplicant.IfaceInfo mStaIface0;
     ISupplicant.IfaceInfo mStaIface1;
     ISupplicant.IfaceInfo mP2pIface;
@@ -165,9 +175,12 @@
             mISupplicantStaIfaceCallbackV1_2;
     android.hardware.wifi.supplicant.V1_3.ISupplicantStaIfaceCallback
             mISupplicantStaIfaceCallbackV13 = null;
+    android.hardware.wifi.supplicant.V1_4.ISupplicantStaIfaceCallback
+            mISupplicantStaIfaceCallbackV14 = null;
+
     private TestLooper mLooper = new TestLooper();
     private Handler mHandler = null;
-    private SupplicantStaIfaceHal mDut;
+    private SupplicantStaIfaceHalSpy mDut;
     private ArgumentCaptor<IHwBinder.DeathRecipient> mServiceManagerDeathCaptor =
             ArgumentCaptor.forClass(IHwBinder.DeathRecipient.class);
     private ArgumentCaptor<IHwBinder.DeathRecipient> mSupplicantDeathCaptor =
@@ -180,9 +193,12 @@
     private InOrder mInOrder;
 
     private class SupplicantStaIfaceHalSpy extends SupplicantStaIfaceHal {
+        SupplicantStaNetworkHal mStaNetwork;
+
         SupplicantStaIfaceHalSpy() {
             super(mContext, mWifiMonitor, mFrameworkFacade,
-                    mHandler, mClock, mWifiMetrics);
+                    mHandler, mClock, mWifiMetrics, mWifiGlobals);
+            mStaNetwork = mSupplicantStaNetworkMock;
         }
 
         @Override
@@ -227,10 +243,22 @@
         }
 
         @Override
+        protected android.hardware.wifi.supplicant.V1_4.ISupplicantStaIface
+                getStaIfaceMockableV1_4(ISupplicantIface iface) {
+            return (mISupplicantMockV14 != null)
+                    ? mISupplicantStaIfaceMockV14
+                    : null;
+        }
+
+        @Override
         protected SupplicantStaNetworkHal getStaNetworkMockable(
                 @NonNull String ifaceName,
                 ISupplicantStaNetwork iSupplicantStaNetwork) {
-            return mSupplicantStaNetworkMock;
+            return mStaNetwork;
+        }
+
+        private void setStaNetworkMockable(SupplicantStaNetworkHal network) {
+            mStaNetwork = network;
         }
     }
 
@@ -239,6 +267,10 @@
         MockitoAnnotations.initMocks(this);
         mStatusSuccess = createSupplicantStatus(SupplicantStatusCode.SUCCESS);
         mStatusFailure = createSupplicantStatus(SupplicantStatusCode.FAILURE_UNKNOWN);
+        mStatusSuccessV14 = createSupplicantStatusV1_4(
+                android.hardware.wifi.supplicant.V1_4.SupplicantStatusCode.SUCCESS);
+        mStatusFailureV14 = createSupplicantStatusV1_4(
+                android.hardware.wifi.supplicant.V1_4.SupplicantStatusCode.FAILURE_UNKNOWN);
         mStaIface0 = createIfaceInfo(IfaceType.STA, WLAN0_IFACE_NAME);
         mStaIface1 = createIfaceInfo(IfaceType.STA, WLAN1_IFACE_NAME);
         mP2pIface = createIfaceInfo(IfaceType.P2P, P2P_IFACE_NAME);
@@ -262,6 +294,7 @@
         })
         .when(mISupplicantStaIfaceMock)
                 .getMacAddress(any(ISupplicantStaIface.getMacAddressCallback.class));
+        when(mFrameworkFacade.startSupplicant()).thenReturn(true);
         mHandler = spy(new Handler(mLooper.getLooper()));
         mDut = new SupplicantStaIfaceHalSpy();
     }
@@ -342,6 +375,16 @@
     }
 
     /**
+     * Sunny day scenario for SupplicantStaIfaceHal initialization
+     * Asserts successful initialization
+     */
+    @Test
+    public void testInitialize_successV1_4() throws Exception {
+        setupMocksForHalV1_4();
+        executeAndValidateInitializationSequenceV1_4();
+    }
+
+    /**
      * Tests the initialization flow, with a RemoteException occurring when 'getInterface' is called
      * Ensures initialization fails.
      */
@@ -589,7 +632,7 @@
     @Test
     public void testRoamToSameNetwork() throws Exception {
         executeAndValidateInitializationSequence();
-        executeAndValidateRoamSequence(true);
+        executeAndValidateRoamSequence(true, false);
         assertTrue(mDut.connectToNetwork(WLAN0_IFACE_NAME, createTestWifiConfiguration()));
     }
 
@@ -599,7 +642,16 @@
     @Test
     public void testRoamToDifferentNetwork() throws Exception {
         executeAndValidateInitializationSequence();
-        executeAndValidateRoamSequence(false);
+        executeAndValidateRoamSequence(false, false);
+    }
+
+    /**
+     * Tests roaming to a linked network.
+     */
+    @Test
+    public void testRoamToLinkedNetwork() throws Exception {
+        executeAndValidateInitializationSequence();
+        executeAndValidateRoamSequence(false, true);
     }
 
     /**
@@ -890,6 +942,32 @@
     }
 
     /**
+     * Tests the handling of ANQP done callback.
+     * Note: Since the ANQP element parsing methods are static, this can only test the negative test
+     * where all the parsing fails because the data is empty. It'll be non-trivial and unnecessary
+     * to test out the parsing logic here.
+     */
+    @Test
+    public void testAnqpDoneCallback_1_4() throws Exception {
+        setupMocksForHalV1_4();
+        executeAndValidateInitializationSequenceV1_4();
+        assertNotNull(mISupplicantStaIfaceCallbackV14);
+        byte[] bssid = NativeUtil.macAddressToByteArray(BSSID);
+        mISupplicantStaIfaceCallbackV14.onAnqpQueryDone_1_4(
+                bssid,
+                new android.hardware.wifi.supplicant.V1_4.ISupplicantStaIfaceCallback.AnqpData(),
+                new ISupplicantStaIfaceCallback.Hs20AnqpData());
+
+        ArgumentCaptor<AnqpEvent> anqpEventCaptor = ArgumentCaptor.forClass(AnqpEvent.class);
+        verify(mWifiMonitor).broadcastAnqpDoneEvent(
+                eq(WLAN0_IFACE_NAME), anqpEventCaptor.capture());
+        assertEquals(
+                ByteBufferReader.readInteger(
+                        ByteBuffer.wrap(bssid), ByteOrder.BIG_ENDIAN, bssid.length),
+                anqpEventCaptor.getValue().getBssid());
+    }
+
+    /**
      * Tests the handling of Icon done callback.
      */
     @Test
@@ -951,7 +1029,14 @@
     @Test
     public void testHs20DeauthImminentCallbackWithNonEssReasonCode() throws Exception {
         executeAndValidateHs20DeauthImminentCallback(false);
+    }
 
+    /**
+     * Tests the handling of HS20 Terms & Conditions acceptance callback.
+     */
+    @Test
+    public void testHs20TermsAndConditionsAcceptance() throws Exception {
+        executeAndValidateHs20TermsAndConditionsCallback();
     }
 
     /**
@@ -1009,8 +1094,11 @@
                 NativeUtil.macAddressToByteArray(BSSID), SUPPLICANT_NETWORK_ID,
                 NativeUtil.decodeSsid(SUPPLICANT_SSID));
 
+        WifiSsid wifiSsid = WifiSsid.createFromByteArray(
+                NativeUtil.byteArrayFromArrayList(NativeUtil.decodeSsid(SUPPLICANT_SSID)));
+
         wifiMonitorInOrder.verify(mWifiMonitor).broadcastNetworkConnectionEvent(
-                eq(WLAN0_IFACE_NAME), eq(frameworkNetworkId), eq(false), eq(BSSID));
+                eq(WLAN0_IFACE_NAME), eq(frameworkNetworkId), eq(false), eq(wifiSsid), eq(BSSID));
         wifiMonitorInOrder.verify(mWifiMonitor).broadcastSupplicantStateChangeEvent(
                 eq(WLAN0_IFACE_NAME), eq(frameworkNetworkId),
                 any(WifiSsid.class), eq(BSSID), eq(SupplicantState.COMPLETED));
@@ -1024,16 +1112,22 @@
         executeAndValidateInitializationSequence();
         assertNotNull(mISupplicantStaIfaceCallback);
 
+        // Set the SSID for the current connection.
+        mISupplicantStaIfaceCallback.onStateChanged(
+                ISupplicantStaIfaceCallback.State.ASSOCIATING,
+                NativeUtil.macAddressToByteArray(BSSID),
+                SUPPLICANT_NETWORK_ID,
+                NativeUtil.decodeSsid(SUPPLICANT_SSID));
         int reasonCode = 5;
         mISupplicantStaIfaceCallback.onDisconnected(
                 NativeUtil.macAddressToByteArray(BSSID), true, reasonCode);
         verify(mWifiMonitor).broadcastNetworkDisconnectionEvent(
-                eq(WLAN0_IFACE_NAME), eq(1), eq(reasonCode), eq(BSSID));
+                eq(WLAN0_IFACE_NAME), eq(true), eq(reasonCode), eq(SUPPLICANT_SSID), eq(BSSID));
 
         mISupplicantStaIfaceCallback.onDisconnected(
                 NativeUtil.macAddressToByteArray(BSSID), false, reasonCode);
         verify(mWifiMonitor).broadcastNetworkDisconnectionEvent(
-                eq(WLAN0_IFACE_NAME), eq(0), eq(reasonCode), eq(BSSID));
+                eq(WLAN0_IFACE_NAME), eq(false), eq(reasonCode), eq(SUPPLICANT_SSID), eq(BSSID));
     }
 
     /**
@@ -1044,7 +1138,7 @@
         executeAndValidateInitializationSequence();
         assertNotNull(mISupplicantStaIfaceCallback);
         executeAndValidateConnectSequenceWithKeyMgmt(
-                0, false, WifiConfiguration.KeyMgmt.WPA_PSK, null);
+                0, false, WifiConfiguration.SECURITY_TYPE_PSK, null);
 
         int reasonCode = 3;
         mISupplicantStaIfaceCallback.onDisconnected(
@@ -1077,7 +1171,7 @@
         executeAndValidateInitializationSequence();
         assertNotNull(mISupplicantStaIfaceCallback);
         executeAndValidateConnectSequenceWithKeyMgmt(
-                0, false, WifiConfiguration.KeyMgmt.WPA_EAP, null);
+                0, false, WifiConfiguration.SECURITY_TYPE_EAP, null);
 
         int reasonCode = 3;
         mISupplicantStaIfaceCallback.onDisconnected(
@@ -1109,6 +1203,33 @@
     }
 
     /**
+     * Tests the handling of EAP failure disconnects.
+     */
+    @Test
+    public void testOnlyOneAuthFailureEap() throws Exception {
+        executeAndValidateInitializationSequence();
+        assertNotNull(mISupplicantStaIfaceCallback);
+        executeAndValidateConnectSequenceWithKeyMgmt(
+                0, false, WifiConfiguration.SECURITY_TYPE_EAP, null);
+
+        int reasonCode = 3;
+        mISupplicantStaIfaceCallback.onStateChanged(
+                ISupplicantStaIfaceCallback.State.ASSOCIATED,
+                NativeUtil.macAddressToByteArray(BSSID),
+                SUPPLICANT_NETWORK_ID,
+                NativeUtil.decodeSsid(SUPPLICANT_SSID));
+        mISupplicantStaIfaceCallback.onEapFailure();
+        verify(mWifiMonitor).broadcastAuthenticationFailureEvent(
+                eq(WLAN0_IFACE_NAME), eq(WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE), eq(-1));
+
+        // Ensure that the disconnect is ignored.
+        mISupplicantStaIfaceCallback.onDisconnected(
+                NativeUtil.macAddressToByteArray(BSSID), false, reasonCode);
+        verify(mWifiMonitor, times(1)).broadcastAuthenticationFailureEvent(
+                eq(WLAN0_IFACE_NAME), eq(WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE), eq(-1));
+    }
+
+    /**
      * Tests the handling of incorrect network passwords for WPA3-Personal networks
      */
     @Test
@@ -1117,16 +1238,31 @@
         assertNotNull(mISupplicantStaIfaceCallback);
 
         executeAndValidateConnectSequenceWithKeyMgmt(SUPPLICANT_NETWORK_ID, false,
-                WifiConfiguration.KeyMgmt.SAE, null);
+                WifiConfiguration.SECURITY_TYPE_SAE, null);
 
+        mISupplicantStaIfaceCallback.onStateChanged(
+                ISupplicantStaIfaceCallback.State.ASSOCIATING,
+                NativeUtil.macAddressToByteArray(BSSID),
+                SUPPLICANT_NETWORK_ID,
+                NativeUtil.decodeSsid(SUPPLICANT_SSID));
         int statusCode = ISupplicantStaIfaceCallback.StatusCode.UNSPECIFIED_FAILURE;
-
         mISupplicantStaIfaceCallback.onAssociationRejected(
                 NativeUtil.macAddressToByteArray(BSSID), statusCode, false);
         verify(mWifiMonitor).broadcastAuthenticationFailureEvent(eq(WLAN0_IFACE_NAME),
                 eq(WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD), eq(-1));
+        ArgumentCaptor<AssocRejectEventInfo> assocRejectEventInfoCaptor =
+                ArgumentCaptor.forClass(AssocRejectEventInfo.class);
         verify(mWifiMonitor).broadcastAssociationRejectionEvent(
-                eq(WLAN0_IFACE_NAME), eq(statusCode), eq(false), eq(BSSID));
+                eq(WLAN0_IFACE_NAME), assocRejectEventInfoCaptor.capture());
+        AssocRejectEventInfo assocRejectEventInfo =
+                (AssocRejectEventInfo) assocRejectEventInfoCaptor.getValue();
+        assertNotNull(assocRejectEventInfo);
+        assertEquals(SUPPLICANT_SSID, assocRejectEventInfo.ssid);
+        assertEquals(BSSID, assocRejectEventInfo.bssid);
+        assertEquals(statusCode, assocRejectEventInfo.statusCode);
+        assertFalse(assocRejectEventInfo.timedOut);
+        assertNull(assocRejectEventInfo.oceRssiBasedAssocRejectInfo);
+        assertNull(assocRejectEventInfo.mboAssocDisallowedInfo);
     }
 
     /**
@@ -1138,16 +1274,31 @@
         assertNotNull(mISupplicantStaIfaceCallback);
 
         executeAndValidateConnectSequenceWithKeyMgmt(SUPPLICANT_NETWORK_ID, false,
-                WifiConfiguration.KeyMgmt.NONE, "97CA326539");
+                WifiConfiguration.SECURITY_TYPE_WEP, "97CA326539");
 
+        mISupplicantStaIfaceCallback.onStateChanged(
+                ISupplicantStaIfaceCallback.State.ASSOCIATING,
+                NativeUtil.macAddressToByteArray(BSSID),
+                SUPPLICANT_NETWORK_ID,
+                NativeUtil.decodeSsid(SUPPLICANT_SSID));
         int statusCode = ISupplicantStaIfaceCallback.StatusCode.CHALLENGE_FAIL;
-
         mISupplicantStaIfaceCallback.onAssociationRejected(
                 NativeUtil.macAddressToByteArray(BSSID), statusCode, false);
         verify(mWifiMonitor).broadcastAuthenticationFailureEvent(eq(WLAN0_IFACE_NAME),
                 eq(WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD), eq(-1));
+        ArgumentCaptor<AssocRejectEventInfo> assocRejectEventInfoCaptor =
+                ArgumentCaptor.forClass(AssocRejectEventInfo.class);
         verify(mWifiMonitor).broadcastAssociationRejectionEvent(
-                eq(WLAN0_IFACE_NAME), eq(statusCode), eq(false), eq(BSSID));
+                eq(WLAN0_IFACE_NAME), assocRejectEventInfoCaptor.capture());
+        AssocRejectEventInfo assocRejectEventInfo =
+                (AssocRejectEventInfo) assocRejectEventInfoCaptor.getValue();
+        assertNotNull(assocRejectEventInfo);
+        assertEquals(SUPPLICANT_SSID, assocRejectEventInfo.ssid);
+        assertEquals(BSSID, assocRejectEventInfo.bssid);
+        assertEquals(statusCode, assocRejectEventInfo.statusCode);
+        assertFalse(assocRejectEventInfo.timedOut);
+        assertNull(assocRejectEventInfo.oceRssiBasedAssocRejectInfo);
+        assertNull(assocRejectEventInfo.mboAssocDisallowedInfo);
     }
 
     /**
@@ -1220,11 +1371,27 @@
         executeAndValidateInitializationSequence();
         assertNotNull(mISupplicantStaIfaceCallback);
 
+        mISupplicantStaIfaceCallback.onStateChanged(
+                ISupplicantStaIfaceCallback.State.ASSOCIATING,
+                NativeUtil.macAddressToByteArray(BSSID),
+                SUPPLICANT_NETWORK_ID,
+                NativeUtil.decodeSsid(SUPPLICANT_SSID));
         int statusCode = 7;
         mISupplicantStaIfaceCallback.onAssociationRejected(
                 NativeUtil.macAddressToByteArray(BSSID), statusCode, false);
+        ArgumentCaptor<AssocRejectEventInfo> assocRejectEventInfoCaptor =
+                ArgumentCaptor.forClass(AssocRejectEventInfo.class);
         verify(mWifiMonitor).broadcastAssociationRejectionEvent(
-                eq(WLAN0_IFACE_NAME), eq(statusCode), eq(false), eq(BSSID));
+                eq(WLAN0_IFACE_NAME), assocRejectEventInfoCaptor.capture());
+        AssocRejectEventInfo assocRejectEventInfo =
+                (AssocRejectEventInfo) assocRejectEventInfoCaptor.getValue();
+        assertNotNull(assocRejectEventInfo);
+        assertEquals(SUPPLICANT_SSID, assocRejectEventInfo.ssid);
+        assertEquals(BSSID, assocRejectEventInfo.bssid);
+        assertEquals(statusCode, assocRejectEventInfo.statusCode);
+        assertFalse(assocRejectEventInfo.timedOut);
+        assertNull(assocRejectEventInfo.oceRssiBasedAssocRejectInfo);
+        assertNull(assocRejectEventInfo.mboAssocDisallowedInfo);
     }
 
     /**
@@ -1368,7 +1535,6 @@
         mLooper.dispatchAll();
 
         assertFalse(mDut.isInitializationComplete());
-        verify(mWifiMonitor).broadcastSupplicantDisconnectionEvent(eq(WLAN0_IFACE_NAME));
         verify(mSupplicantHalDeathHandler).onDeath();
     }
 
@@ -1386,7 +1552,6 @@
         mLooper.dispatchAll();
 
         assertFalse(mDut.isInitializationComplete());
-        verify(mWifiMonitor).broadcastSupplicantDisconnectionEvent(eq(WLAN0_IFACE_NAME));
         verify(mSupplicantHalDeathHandler).onDeath();
     }
 
@@ -1404,7 +1569,6 @@
         mLooper.dispatchAll();
 
         assertTrue(mDut.isInitializationComplete());
-        verify(mWifiMonitor, never()).broadcastSupplicantDisconnectionEvent(eq(WLAN0_IFACE_NAME));
         verify(mSupplicantHalDeathHandler, never()).onDeath();
     }
 
@@ -1460,6 +1624,42 @@
     }
 
     /**
+     * Tests the setting of log level with show key enabled.
+     */
+    @Test
+    public void testSetLogLevelWithShowKeyEnabled() throws Exception {
+        when(mWifiGlobals.getShowKeyVerboseLoggingModeEnabled())
+                .thenReturn(true);
+        when(mISupplicantMock.setDebugParams(anyInt(), anyBoolean(), anyBoolean()))
+                .thenReturn(mStatusSuccess);
+
+        executeAndValidateInitializationSequence();
+
+        // This should work.
+        assertTrue(mDut.setLogLevel(true));
+        verify(mISupplicantMock)
+                .setDebugParams(eq(ISupplicant.DebugLevel.DEBUG), eq(false), eq(true));
+    }
+
+    /**
+     * Tests that show key is not enabled when verbose logging is not enabled.
+     */
+    @Test
+    public void testVerboseLoggingDisabledWithShowKeyEnabled() throws Exception {
+        when(mWifiGlobals.getShowKeyVerboseLoggingModeEnabled())
+                .thenReturn(true);
+        when(mISupplicantMock.setDebugParams(anyInt(), anyBoolean(), anyBoolean()))
+                .thenReturn(mStatusSuccess);
+
+        executeAndValidateInitializationSequence();
+
+        // If verbose logging is not enabled, show key should not be enabled.
+        assertTrue(mDut.setLogLevel(false));
+        verify(mISupplicantMock)
+                .setDebugParams(eq(ISupplicant.DebugLevel.INFO), eq(false), eq(false));
+    }
+
+    /**
      * Tests the setting of concurrency priority.
      */
     @Test
@@ -1579,8 +1779,19 @@
     @Test
     public void testTerminateV1_0() throws Exception {
         executeAndValidateInitializationSequence();
+
+        doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+            public boolean answer(IHwBinder.DeathRecipient cb, long cookie) throws RemoteException {
+                mHandler.post(() -> cb.serviceDied(cookie));
+                return true;
+            }
+        }).when(mISupplicantMock).linkToDeath(any(IHwBinder.DeathRecipient.class), any(long.class));
         mDut.terminate();
+        mLooper.dispatchAll();
         verify(mFrameworkFacade).stopSupplicant();
+
+        // Check that terminate cleared all internal state.
+        assertFalse(mDut.isInitializationComplete());
     }
 
     /**
@@ -1591,9 +1802,21 @@
         setupMocksForHalV1_1();
 
         executeAndValidateInitializationSequenceV1_1(false, false);
+
+        doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+            public boolean answer(IHwBinder.DeathRecipient cb, long cookie) throws RemoteException {
+                mHandler.post(() -> cb.serviceDied(cookie));
+                return true;
+            }
+        }).when(mISupplicantMock).linkToDeath(any(IHwBinder.DeathRecipient.class), any(long.class));
+
         mDut.terminate();
+        mLooper.dispatchAll();
         verify(mFrameworkFacade, never()).stopSupplicant();
         verify(mISupplicantMockV1_1).terminate();
+
+        // Check that terminate cleared all internal state.
+        assertFalse(mDut.isInitializationComplete());
     }
 
     private class GetKeyMgmtCapabilitiesAnswer extends MockAnswerUtil.AnswerWithArguments {
@@ -1623,7 +1846,7 @@
     }
 
     /**
-     * Test get key management capabilities API on old HAL, should return 0 (not supported)
+     * Test get advanced capabilities API on old HAL, should return 0 (not supported)
      */
     @Test
     public void testGetKeyMgmtCapabilitiesOldHal() throws Exception {
@@ -1631,7 +1854,7 @@
 
         executeAndValidateInitializationSequenceV1_1(false, false);
 
-        assertTrue(mDut.getAdvancedKeyMgmtCapabilities(WLAN0_IFACE_NAME) == 0);
+        assertTrue(mDut.getAdvancedCapabilities(WLAN0_IFACE_NAME) == 0);
     }
 
     /**
@@ -1649,7 +1872,7 @@
                 android.hardware.wifi.supplicant.V1_2.ISupplicantStaIface
                         .getKeyMgmtCapabilitiesCallback.class));
 
-        assertEquals(WIFI_FEATURE_WPA3_SAE, mDut.getAdvancedKeyMgmtCapabilities(WLAN0_IFACE_NAME));
+        assertEquals(WIFI_FEATURE_WPA3_SAE, mDut.getAdvancedCapabilities(WLAN0_IFACE_NAME));
     }
 
     /**
@@ -1668,7 +1891,7 @@
                         .getKeyMgmtCapabilitiesCallback.class));
 
         assertEquals(WIFI_FEATURE_WPA3_SUITE_B,
-                mDut.getAdvancedKeyMgmtCapabilities(WLAN0_IFACE_NAME));
+                mDut.getAdvancedCapabilities(WLAN0_IFACE_NAME));
     }
 
     /**
@@ -1686,7 +1909,7 @@
                 android.hardware.wifi.supplicant.V1_2.ISupplicantStaIface
                         .getKeyMgmtCapabilitiesCallback.class));
 
-        assertEquals(WIFI_FEATURE_OWE, mDut.getAdvancedKeyMgmtCapabilities(WLAN0_IFACE_NAME));
+        assertEquals(WIFI_FEATURE_OWE, mDut.getAdvancedCapabilities(WLAN0_IFACE_NAME));
     }
 
     /**
@@ -1706,7 +1929,7 @@
                         .getKeyMgmtCapabilitiesCallback.class));
 
         assertEquals(WIFI_FEATURE_OWE | WIFI_FEATURE_WPA3_SAE,
-                mDut.getAdvancedKeyMgmtCapabilities(WLAN0_IFACE_NAME));
+                mDut.getAdvancedCapabilities(WLAN0_IFACE_NAME));
     }
 
     /**
@@ -1724,7 +1947,46 @@
                 android.hardware.wifi.supplicant.V1_2.ISupplicantStaIface
                         .getKeyMgmtCapabilitiesCallback.class));
 
-        assertEquals(WIFI_FEATURE_DPP, mDut.getAdvancedKeyMgmtCapabilities(WLAN0_IFACE_NAME));
+        assertEquals(WIFI_FEATURE_DPP, mDut.getAdvancedCapabilities(WLAN0_IFACE_NAME));
+    }
+
+    /**
+     * Test Easy Connect (DPP) Enrollee Responder mode supported on supplicant HAL V1_4
+     */
+    @Test
+    public void testGetDppEnrolleeResponderModeSupport() throws Exception {
+        setupMocksForHalV1_4();
+        executeAndValidateInitializationSequenceV1_4();
+
+        doAnswer(new GetKeyMgmtCapabilities_1_3Answer(android.hardware.wifi.supplicant.V1_2
+                .ISupplicantStaNetwork.KeyMgmtMask.DPP))
+                .when(mISupplicantStaIfaceMockV13).getKeyMgmtCapabilities_1_3(any(
+                android.hardware.wifi.supplicant.V1_3.ISupplicantStaIface
+                        .getKeyMgmtCapabilities_1_3Callback.class));
+
+        assertTrue((WIFI_FEATURE_DPP_ENROLLEE_RESPONDER
+                & mDut.getAdvancedCapabilities(WLAN0_IFACE_NAME))
+                == WIFI_FEATURE_DPP_ENROLLEE_RESPONDER);
+    }
+
+    /**
+     * Test Easy Connect (DPP) Enrollee Responder mode is not supported on supplicant HAL
+     * V1_3 or less.
+     */
+    @Test
+    public void testDppEnrolleeResponderModeNotSupportedOnHalV1_3OrLess() throws Exception {
+        setupMocksForHalV1_3();
+        executeAndValidateInitializationSequenceV1_3();
+
+        doAnswer(new GetKeyMgmtCapabilities_1_3Answer(android.hardware.wifi.supplicant.V1_2
+                .ISupplicantStaNetwork.KeyMgmtMask.DPP))
+                .when(mISupplicantStaIfaceMockV13).getKeyMgmtCapabilities_1_3(any(
+                android.hardware.wifi.supplicant.V1_3.ISupplicantStaIface
+                        .getKeyMgmtCapabilities_1_3Callback.class));
+
+        assertFalse((WIFI_FEATURE_DPP_ENROLLEE_RESPONDER
+                & mDut.getAdvancedCapabilities(WLAN0_IFACE_NAME))
+                == WIFI_FEATURE_DPP_ENROLLEE_RESPONDER);
     }
 
     /**
@@ -1742,7 +2004,7 @@
                 android.hardware.wifi.supplicant.V1_3.ISupplicantStaIface
                         .getKeyMgmtCapabilities_1_3Callback.class));
 
-        assertEquals(WIFI_FEATURE_WAPI, mDut.getAdvancedKeyMgmtCapabilities(WLAN0_IFACE_NAME));
+        assertEquals(WIFI_FEATURE_WAPI, mDut.getAdvancedCapabilities(WLAN0_IFACE_NAME));
     }
 
     /**
@@ -1761,7 +2023,7 @@
                         .getKeyMgmtCapabilities_1_3Callback.class));
 
         assertEquals(WIFI_FEATURE_FILS_SHA256,
-                mDut.getAdvancedKeyMgmtCapabilities(WLAN0_IFACE_NAME));
+                mDut.getAdvancedCapabilities(WLAN0_IFACE_NAME));
     }
 
     /**
@@ -1780,7 +2042,7 @@
                         .getKeyMgmtCapabilities_1_3Callback.class));
 
         assertEquals(WIFI_FEATURE_FILS_SHA384,
-                mDut.getAdvancedKeyMgmtCapabilities(WLAN0_IFACE_NAME));
+                mDut.getAdvancedCapabilities(WLAN0_IFACE_NAME));
     }
 
     /**
@@ -1795,6 +2057,11 @@
                 1, 2, "Buckle", "My", "Shoe",
                 3, 4));
         assertFalse(mDut.startDppEnrolleeInitiator(WLAN0_IFACE_NAME, 3, 14));
+        WifiNative.DppBootstrapQrCodeInfo bootstrapInfo =
+                mDut.generateDppBootstrapInfoForResponder(WLAN0_IFACE_NAME, "00:11:22:33:44:55",
+                        "PRODUCT_INFO", DppCurve.PRIME256V1);
+        assertEquals(-1, bootstrapInfo.bootstrapId);
+        assertFalse(mDut.startDppEnrolleeResponder(WLAN0_IFACE_NAME, 6));
     }
 
     /**
@@ -1806,7 +2073,7 @@
         long testStartSeconds = PMK_CACHE_EXPIRATION_IN_SEC / 2;
         WifiConfiguration config = new WifiConfiguration();
         config.networkId = testFrameworkNetworkId;
-        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
+        config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
         PmkCacheStoreData pmkCacheData =
                 new PmkCacheStoreData(PMK_CACHE_EXPIRATION_IN_SEC, new ArrayList<Byte>(),
                         MacAddress.fromBytes(CONNECTED_MAC_ADDRESS_BYTES));
@@ -1841,7 +2108,7 @@
         long testStartSeconds = PMK_CACHE_EXPIRATION_IN_SEC / 2;
         WifiConfiguration config = new WifiConfiguration();
         config.networkId = testFrameworkNetworkId;
-        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
+        config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
         when(mClock.getElapsedSinceBootMillis()).thenReturn(testStartSeconds * 1000L);
 
         setupMocksForHalV1_3();
@@ -1868,7 +2135,7 @@
         long testStartSeconds = PMK_CACHE_EXPIRATION_IN_SEC / 2;
         WifiConfiguration config = new WifiConfiguration();
         config.networkId = testFrameworkNetworkId;
-        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+        config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
         PmkCacheStoreData pmkCacheData =
                 new PmkCacheStoreData(PMK_CACHE_EXPIRATION_IN_SEC, new ArrayList<Byte>(),
                         MacAddress.fromBytes(CONNECTED_MAC_ADDRESS_BYTES));
@@ -1899,7 +2166,7 @@
         long testStartSeconds = PMK_CACHE_EXPIRATION_IN_SEC / 2;
         WifiConfiguration config = new WifiConfiguration();
         config.networkId = testFrameworkNetworkId;
-        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
+        config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
         PmkCacheStoreData pmkCacheData =
                 new PmkCacheStoreData(PMK_CACHE_EXPIRATION_IN_SEC, new ArrayList<Byte>(),
                         MacAddress.fromBytes(CONNECTED_MAC_ADDRESS_BYTES));
@@ -1927,7 +2194,7 @@
         long testStartSeconds = PMK_CACHE_EXPIRATION_IN_SEC / 2;
         WifiConfiguration config = new WifiConfiguration();
         config.networkId = testFrameworkNetworkId;
-        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
+        config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
         PmkCacheStoreData pmkCacheData =
                 new PmkCacheStoreData(PMK_CACHE_EXPIRATION_IN_SEC, new ArrayList<Byte>(),
                         MacAddress.fromBytes(CONNECTED_MAC_ADDRESS_BYTES));
@@ -1940,7 +2207,11 @@
 
         executeAndValidateInitializationSequenceV1_3();
         assertTrue(mDut.connectToNetwork(WLAN0_IFACE_NAME, config));
-
+        mISupplicantStaIfaceCallbackV13.onStateChanged(
+                ISupplicantStaIfaceCallback.State.ASSOCIATING,
+                NativeUtil.macAddressToByteArray(BSSID),
+                SUPPLICANT_NETWORK_ID,
+                NativeUtil.decodeSsid(SUPPLICANT_SSID));
         int statusCode = 7;
         mISupplicantStaIfaceCallbackV13.onAssociationRejected(
                 NativeUtil.macAddressToByteArray(BSSID), statusCode, false);
@@ -1957,7 +2228,7 @@
         long testStartSeconds = PMK_CACHE_EXPIRATION_IN_SEC / 2;
         WifiConfiguration config = new WifiConfiguration();
         config.networkId = testFrameworkNetworkId;
-        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
+        config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
         // Assume we have a PMK cache with a different MAC address.
         final byte[] previouisConnectedMacAddressBytes =
                 {0x00, 0x01, 0x02, 0x03, 0x04, 0x09};
@@ -1994,12 +2265,12 @@
         assertEquals(ScanResult.WIFI_STANDARD_UNKNOWN, cap.wifiStandard);
     }
 
-    private class GetConnCapabilitiesAnswer extends MockAnswerUtil.AnswerWithArguments {
-        private ConnectionCapabilities mConnCapabilities;
+    private class GetConnCapabilitiesAnswerV1_3 extends MockAnswerUtil.AnswerWithArguments {
+        private android.hardware.wifi.supplicant.V1_3.ConnectionCapabilities mConnCapabilities;
 
-        GetConnCapabilitiesAnswer(int wifiTechnology, int channelBandwidth,
+        GetConnCapabilitiesAnswerV1_3(int wifiTechnology, int channelBandwidth,
                 int maxNumberTxSpatialStreams, int maxNumberRxSpatialStreams) {
-            mConnCapabilities = new ConnectionCapabilities();
+            mConnCapabilities = new android.hardware.wifi.supplicant.V1_3.ConnectionCapabilities();
             mConnCapabilities.technology = wifiTechnology;
             mConnCapabilities.channelBandwidth = channelBandwidth;
             mConnCapabilities.maxNumberTxSpatialStreams = maxNumberTxSpatialStreams;
@@ -2012,6 +2283,25 @@
         }
     }
 
+    private class GetConnCapabilitiesAnswerV1_4 extends MockAnswerUtil.AnswerWithArguments {
+        private ConnectionCapabilities mConnCapabilities;
+
+        GetConnCapabilitiesAnswerV1_4(int wifiTechnology, int legacyMode, int channelBandwidth,
+                int maxNumberTxSpatialStreams, int maxNumberRxSpatialStreams) {
+            mConnCapabilities = new ConnectionCapabilities();
+            mConnCapabilities.V1_3.technology = wifiTechnology;
+            mConnCapabilities.legacyMode = legacyMode;
+            mConnCapabilities.V1_3.channelBandwidth = channelBandwidth;
+            mConnCapabilities.V1_3.maxNumberTxSpatialStreams = maxNumberTxSpatialStreams;
+            mConnCapabilities.V1_3.maxNumberRxSpatialStreams = maxNumberRxSpatialStreams;
+        }
+
+        public void answer(android.hardware.wifi.supplicant.V1_4.ISupplicantStaIface
+                .getConnectionCapabilities_1_4Callback cb) {
+            cb.onValues(mStatusSuccessV14, mConnCapabilities);
+        }
+    }
+
     /**
      * Test getConnectionCapabilities if running with HAL V1_3
      */
@@ -2027,13 +2317,43 @@
         int maxNumberTxSpatialStreams = 3;
         int maxNumberRxSpatialStreams = 1;
 
-        doAnswer(new GetConnCapabilitiesAnswer(testWifiTechnologyHal, testChannelBandwidthHal,
+        doAnswer(new GetConnCapabilitiesAnswerV1_3(testWifiTechnologyHal, testChannelBandwidthHal,
                 maxNumberTxSpatialStreams, maxNumberRxSpatialStreams))
                 .when(mISupplicantStaIfaceMockV13).getConnectionCapabilities(any(
                 android.hardware.wifi.supplicant.V1_3.ISupplicantStaIface
                         .getConnectionCapabilitiesCallback.class));
         WifiNative.ConnectionCapabilities cap = mDut.getConnectionCapabilities(WLAN0_IFACE_NAME);
         assertEquals(testWifiStandardWifiInfo, cap.wifiStandard);
+        assertEquals(false, cap.is11bMode);
+        assertEquals(testChannelBandwidth, cap.channelBandwidth);
+        assertEquals(maxNumberTxSpatialStreams, cap.maxNumberTxSpatialStreams);
+        assertEquals(maxNumberRxSpatialStreams, cap.maxNumberRxSpatialStreams);
+    }
+
+    /**
+     * Test getConnectionCapabilities if running with HAL V1_4
+     */
+    @Test
+    public void testGetConnectionCapabilitiesV1_4() throws Exception {
+        setupMocksForHalV1_4();
+
+        executeAndValidateInitializationSequenceV1_4();
+        int testWifiTechnologyHal = WifiTechnology.LEGACY;
+        int testLegacyMode = LegacyMode.B_MODE;
+        int testWifiStandardWifiInfo = ScanResult.WIFI_STANDARD_LEGACY;
+        int testChannelBandwidthHal = WifiChannelWidthInMhz.WIDTH_20;
+        int testChannelBandwidth = ScanResult.CHANNEL_WIDTH_20MHZ;
+        int maxNumberTxSpatialStreams = 1;
+        int maxNumberRxSpatialStreams = 1;
+
+        doAnswer(new GetConnCapabilitiesAnswerV1_4(testWifiTechnologyHal, testLegacyMode,
+                testChannelBandwidthHal, maxNumberTxSpatialStreams, maxNumberRxSpatialStreams))
+                .when(mISupplicantStaIfaceMockV14).getConnectionCapabilities_1_4(any(
+                android.hardware.wifi.supplicant.V1_4.ISupplicantStaIface
+                        .getConnectionCapabilities_1_4Callback.class));
+        WifiNative.ConnectionCapabilities cap = mDut.getConnectionCapabilities(WLAN0_IFACE_NAME);
+        assertEquals(testWifiStandardWifiInfo, cap.wifiStandard);
+        assertEquals(true, cap.is11bMode);
         assertEquals(testChannelBandwidth, cap.channelBandwidth);
         assertEquals(maxNumberTxSpatialStreams, cap.maxNumberTxSpatialStreams);
         assertEquals(maxNumberRxSpatialStreams, cap.maxNumberRxSpatialStreams);
@@ -2066,6 +2386,18 @@
         assertEquals(HS20_URL, wnmDataCaptor.getValue().getUrl());
     }
 
+    private void executeAndValidateHs20TermsAndConditionsCallback() throws Exception {
+        setupMocksForHalV1_4();
+        executeAndValidateInitializationSequenceV1_4();
+        assertNotNull(mISupplicantStaIfaceCallbackV14);
+
+        byte[] bssid = NativeUtil.macAddressToByteArray(BSSID);
+        mISupplicantStaIfaceCallbackV14.onHs20TermsAndConditionsAcceptanceRequestedNotification(
+                bssid, HS20_URL);
+
+        //TODO: Add test logic once framework handling is implemented
+    }
+
     private void executeAndValidateInitializationSequence() throws  Exception {
         executeAndValidateInitializationSequence(false, false, false, false);
     }
@@ -2344,12 +2676,74 @@
                                 .class));
     }
 
+    /**
+     * Calls.initialize(), mocking various call back answers and verifying flow, asserting for the
+     * expected result. Verifies if ISupplicantStaIface manager is initialized or reset.
+     * Each of the arguments will cause a different failure mode when set true.
+     */
+    private void executeAndValidateInitializationSequenceV1_4()
+            throws Exception {
+        // Setup callback mock answers
+        doAnswer(new GetAddInterfaceAnswerV1_4(false))
+                .when(mISupplicantMockV1_1).addInterface(any(ISupplicant.IfaceInfo.class),
+                any(android.hardware.wifi.supplicant.V1_1.ISupplicant
+                        .addInterfaceCallback.class));
+
+        /** Callback registration */
+        doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+            public android.hardware.wifi.supplicant.V1_4.SupplicantStatus answer(
+                    android.hardware.wifi.supplicant.V1_4.ISupplicantStaIfaceCallback cb)
+                    throws RemoteException {
+                mISupplicantStaIfaceCallbackV14 = spy(cb);
+                return mStatusSuccessV14;
+            }
+        }).when(mISupplicantStaIfaceMockV14)
+                .registerCallback_1_4(
+                        any(android.hardware.wifi.supplicant.V1_4.ISupplicantStaIfaceCallback
+                                .class));
+
+        mInOrder = inOrder(mServiceManagerMock, mISupplicantMock, mISupplicantMockV1_1,
+                mISupplicantStaIfaceMockV14, mWifiMonitor);
+        // Initialize SupplicantStaIfaceHal, should call serviceManager.registerForNotifications
+        assertTrue(mDut.initialize());
+        // verify: service manager initialization sequence
+        mInOrder.verify(mServiceManagerMock).linkToDeath(mServiceManagerDeathCaptor.capture(),
+                anyLong());
+        mInOrder.verify(mServiceManagerMock).registerForNotifications(
+                eq(ISupplicant.kInterfaceName), eq(""), mServiceNotificationCaptor.capture());
+        // act: cause the onRegistration(...) callback to execute
+        mServiceNotificationCaptor.getValue().onRegistration(ISupplicant.kInterfaceName, "", true);
+
+        assertTrue(mDut.isInitializationComplete());
+        assertTrue(mDut.setupIface(WLAN0_IFACE_NAME));
+        mInOrder.verify(mISupplicantMock).linkToDeath(mSupplicantDeathCaptor.capture(),
+                anyLong());
+        // verify: addInterface is called
+        mInOrder.verify(mISupplicantMockV1_1)
+                .addInterface(any(ISupplicant.IfaceInfo.class),
+                        any(android.hardware.wifi.supplicant.V1_1.ISupplicant
+                                .addInterfaceCallback.class));
+
+        mInOrder.verify(mISupplicantStaIfaceMockV14)
+                .registerCallback_1_4(
+                        any(android.hardware.wifi.supplicant.V1_4.ISupplicantStaIfaceCallback
+                                .class));
+    }
+
     private SupplicantStatus createSupplicantStatus(int code) {
         SupplicantStatus status = new SupplicantStatus();
         status.code = code;
         return status;
     }
 
+    private android.hardware.wifi.supplicant.V1_4.SupplicantStatus
+            createSupplicantStatusV1_4(int code) {
+        android.hardware.wifi.supplicant.V1_4.SupplicantStatus status =
+                new android.hardware.wifi.supplicant.V1_4.SupplicantStatus();
+        status.code = code;
+        return status;
+    }
+
     /**
      * Create an IfaceInfo with given type and name
      */
@@ -2442,6 +2836,24 @@
         }
     }
 
+    private class GetAddInterfaceAnswerV1_4 extends MockAnswerUtil.AnswerWithArguments {
+        boolean mGetNullInterface;
+
+        GetAddInterfaceAnswerV1_4(boolean getNullInterface) {
+            mGetNullInterface = getNullInterface;
+        }
+
+        public void answer(ISupplicant.IfaceInfo iface,
+                android.hardware.wifi.supplicant.V1_4.ISupplicant
+                        .addInterfaceCallback cb) {
+            if (mGetNullInterface) {
+                cb.onValues(mStatusSuccess, null);
+            } else {
+                cb.onValues(mStatusSuccess, mISupplicantIfaceMock);
+            }
+        }
+    }
+
     /**
      * Setup mocks for connect sequence.
      */
@@ -2504,7 +2916,7 @@
     private WifiConfiguration executeAndValidateConnectSequence(
             final int newFrameworkNetworkId, final boolean haveExistingNetwork) throws Exception {
         return executeAndValidateConnectSequenceWithKeyMgmt(newFrameworkNetworkId,
-                haveExistingNetwork, WifiConfiguration.KeyMgmt.WPA_PSK, null);
+                haveExistingNetwork, WifiConfiguration.SECURITY_TYPE_PSK, null);
     }
 
     /**
@@ -2512,19 +2924,23 @@
      *
      * @param newFrameworkNetworkId Framework Network Id of the new network to connect.
      * @param haveExistingNetwork Removes the existing network.
-     * @param keyMgmt Key management of the new network.
+     * @param securityType The security type.
      * @param wepKey if configurations are for a WEP network else null.
      * @return the WifiConfiguration object of the new network to connect.
      */
     private WifiConfiguration executeAndValidateConnectSequenceWithKeyMgmt(
             final int newFrameworkNetworkId, final boolean haveExistingNetwork,
-            int keyMgmt, String wepKey) throws Exception {
+            int securityType, String wepKey) throws Exception {
         setupMocksForConnectSequence(haveExistingNetwork);
         WifiConfiguration config = new WifiConfiguration();
+        config.setSecurityParams(securityType);
         config.networkId = newFrameworkNetworkId;
-        config.allowedKeyManagement.set(keyMgmt);
         config.wepKeys[0] = wepKey;
         config.wepTxKeyIndex = 0;
+        WifiConfiguration.NetworkSelectionStatus networkSelectionStatus =
+                new WifiConfiguration.NetworkSelectionStatus();
+        networkSelectionStatus.setCandidateSecurityParams(config.getSecurityParams(securityType));
+        config.setNetworkSelectionStatus(networkSelectionStatus);
         assertTrue(mDut.connectToNetwork(WLAN0_IFACE_NAME, config));
         validateConnectSequence(haveExistingNetwork, 1);
         return config;
@@ -2546,8 +2962,10 @@
      * Helper function to execute all the actions to perform roaming to the network.
      *
      * @param sameNetwork Roam to the same network or not.
+     * @param linkedNetwork Roam to linked network or not.
      */
-    private void executeAndValidateRoamSequence(boolean sameNetwork) throws Exception {
+    private void executeAndValidateRoamSequence(boolean sameNetwork, boolean linkedNetwork)
+            throws Exception {
         int connectedNetworkId = ROAM_NETWORK_ID;
         String roamBssid = BSSID;
         int roamNetworkId;
@@ -2562,15 +2980,36 @@
         WifiConfiguration roamingConfig = new WifiConfiguration();
         roamingConfig.networkId = roamNetworkId;
         roamingConfig.getNetworkSelectionStatus().setNetworkSelectionBSSID(roamBssid);
+        SupplicantStaNetworkHal linkedNetworkHandle = mock(SupplicantStaNetworkHal.class);
+        if (linkedNetwork) {
+            when(linkedNetworkHandle.getNetworkId()).thenReturn(roamNetworkId);
+            when(linkedNetworkHandle.saveWifiConfiguration(any())).thenReturn(true);
+            when(linkedNetworkHandle.select()).thenReturn(true);
+            mDut.setStaNetworkMockable(linkedNetworkHandle);
+            final HashMap<String, WifiConfiguration> linkedNetworks = new HashMap<>();
+            linkedNetworks.put(roamingConfig.getProfileKey(), roamingConfig);
+            assertTrue(mDut.updateLinkedNetworks(
+                    WLAN0_IFACE_NAME, connectedNetworkId, linkedNetworks));
+        }
         assertTrue(mDut.roamToNetwork(WLAN0_IFACE_NAME, roamingConfig));
 
-        if (!sameNetwork) {
-            validateConnectSequence(false, 2);
+        if (sameNetwork) {
+            verify(mSupplicantStaNetworkMock).setBssid(eq(roamBssid));
+            verify(mISupplicantStaIfaceMock).reassociate();
+        } else if (linkedNetwork) {
+            verify(mISupplicantStaIfaceMock, never()).removeNetwork(anyInt());
+            verify(mISupplicantStaIfaceMock, times(2))
+                    .addNetwork(any(ISupplicantStaIface.addNetworkCallback.class));
+            verify(mSupplicantStaNetworkMock).saveWifiConfiguration(any(WifiConfiguration.class));
+            verify(mSupplicantStaNetworkMock).select();
+            verify(linkedNetworkHandle).saveWifiConfiguration(any(WifiConfiguration.class));
+            verify(linkedNetworkHandle).select();
             verify(mSupplicantStaNetworkMock, never()).setBssid(anyString());
             verify(mISupplicantStaIfaceMock, never()).reassociate();
         } else {
-            verify(mSupplicantStaNetworkMock).setBssid(eq(roamBssid));
-            verify(mISupplicantStaIfaceMock).reassociate();
+            validateConnectSequence(false, 2);
+            verify(mSupplicantStaNetworkMock, never()).setBssid(anyString());
+            verify(mISupplicantStaIfaceMock, never()).reassociate();
         }
     }
 
@@ -2601,6 +3040,14 @@
         mISupplicantMockV13 = mock(android.hardware.wifi.supplicant.V1_3.ISupplicant.class);
     }
 
+    private void setupMocksForHalV1_4() throws Exception {
+        setupMocksForHalV1_3();
+        when(mServiceManagerMock.getTransport(eq(android.hardware.wifi.supplicant.V1_4.ISupplicant
+                .kInterfaceName), anyString()))
+                .thenReturn(IServiceManager.Transport.HWBINDER);
+        mISupplicantMockV14 = mock(android.hardware.wifi.supplicant.V1_4.ISupplicant.class);
+    }
+
     private void setupMocksForPmkCache() throws Exception {
         /** Callback registration */
         doAnswer(new MockAnswerUtil.AnswerWithArguments() {
@@ -2648,6 +3095,19 @@
         }
     }
 
+    private class GetWpaDriverCapabilities_1_4Answer extends MockAnswerUtil.AnswerWithArguments {
+        private int mWpaDriverCapabilities;
+
+        GetWpaDriverCapabilities_1_4Answer(int wpaDriverCapabilities) {
+            mWpaDriverCapabilities = wpaDriverCapabilities;
+        }
+
+        public void answer(android.hardware.wifi.supplicant.V1_4.ISupplicantStaIface
+                .getWpaDriverCapabilities_1_4Callback cb) {
+            cb.onValues(mStatusSuccessV14, mWpaDriverCapabilities);
+        }
+    }
+
     /**
      * Test To get wpa driver capabilities API on old HAL, should
      * return 0 (not supported)
@@ -2701,6 +3161,27 @@
     }
 
     /**
+     * Test getWpaDriverCapabilities_1_4
+     */
+    @Test
+    public void testGetWpaDriverCapabilities_1_4() throws Exception {
+        setupMocksForHalV1_4();
+
+        executeAndValidateInitializationSequenceV1_4();
+
+        doAnswer(new GetWpaDriverCapabilities_1_4Answer(android.hardware.wifi.supplicant.V1_3
+                .WpaDriverCapabilitiesMask.MBO
+                | android.hardware.wifi.supplicant.V1_3
+                .WpaDriverCapabilitiesMask.OCE))
+                .when(mISupplicantStaIfaceMockV14).getWpaDriverCapabilities_1_4(any(
+                android.hardware.wifi.supplicant.V1_4.ISupplicantStaIface
+                        .getWpaDriverCapabilities_1_4Callback.class));
+
+        assertEquals(WIFI_FEATURE_MBO | WIFI_FEATURE_OCE,
+                mDut.getWpaDriverFeatureSet(WLAN0_IFACE_NAME));
+    }
+
+    /**
      * Test the handling of BSS transition request callback.
      */
     @Test
@@ -2823,8 +3304,11 @@
                 NativeUtil.macAddressToByteArray(BSSID), SUPPLICANT_NETWORK_ID,
                 NativeUtil.decodeSsid(SUPPLICANT_SSID), false);
 
+        WifiSsid wifiSsid = WifiSsid.createFromByteArray(
+                NativeUtil.byteArrayFromArrayList(NativeUtil.decodeSsid(SUPPLICANT_SSID)));
+
         wifiMonitorInOrder.verify(mWifiMonitor).broadcastNetworkConnectionEvent(
-                eq(WLAN0_IFACE_NAME), eq(frameworkNetworkId), eq(false), eq(BSSID));
+                eq(WLAN0_IFACE_NAME), eq(frameworkNetworkId), eq(false), eq(wifiSsid), eq(BSSID));
         wifiMonitorInOrder.verify(mWifiMonitor).broadcastSupplicantStateChangeEvent(
                 eq(WLAN0_IFACE_NAME), eq(frameworkNetworkId),
                 any(WifiSsid.class), eq(BSSID), eq(SupplicantState.COMPLETED));
@@ -2904,11 +3388,79 @@
                 NativeUtil.macAddressToByteArray(BSSID), SUPPLICANT_NETWORK_ID,
                 NativeUtil.decodeSsid(SUPPLICANT_SSID), true);
 
+        WifiSsid wifiSsid = WifiSsid.createFromByteArray(
+                NativeUtil.byteArrayFromArrayList(NativeUtil.decodeSsid(SUPPLICANT_SSID)));
+
         wifiMonitorInOrder.verify(mWifiMonitor).broadcastNetworkConnectionEvent(
-                eq(WLAN0_IFACE_NAME), eq(frameworkNetworkId), eq(true), eq(BSSID));
+                eq(WLAN0_IFACE_NAME), eq(frameworkNetworkId), eq(true), eq(wifiSsid), eq(BSSID));
         wifiMonitorInOrder.verify(mWifiMonitor).broadcastSupplicantStateChangeEvent(
                 eq(WLAN0_IFACE_NAME), eq(frameworkNetworkId),
                 any(WifiSsid.class), eq(BSSID), eq(SupplicantState.COMPLETED));
     }
 
+    @Test
+    public void testDisableNetworkAfterConnected() throws Exception {
+        when(mSupplicantStaNetworkMock.disable()).thenReturn(true);
+
+        executeAndValidateInitializationSequence();
+
+        // Connect to a network.
+        executeAndValidateConnectSequence(4, false);
+
+        // Disable it.
+        assertTrue(mDut.disableCurrentNetwork(WLAN0_IFACE_NAME));
+        verify(mSupplicantStaNetworkMock).disable();
+    }
+
+    /**
+     * Tests the handling of association rejection notification V1_4.
+     */
+    @Test
+    public void testAssociationRejectionCallback_1_4() throws Exception {
+        setupMocksForHalV1_4();
+        executeAndValidateInitializationSequenceV1_4();
+        assertNotNull(mISupplicantStaIfaceCallbackV14);
+        AssociationRejectionData assocRejectData = new AssociationRejectionData();
+        assocRejectData.ssid = NativeUtil.decodeSsid(SUPPLICANT_SSID);
+        assocRejectData.bssid = NativeUtil.macAddressToByteArray(BSSID);
+        assocRejectData.statusCode = 5;
+        assocRejectData.isOceRssiBasedAssocRejectAttrPresent = true;
+        assocRejectData.oceRssiBasedAssocRejectData.retryDelayS = 10;
+        assocRejectData.oceRssiBasedAssocRejectData.deltaRssi = 20;
+        mISupplicantStaIfaceCallbackV14.onAssociationRejected_1_4(assocRejectData);
+
+        ArgumentCaptor<AssocRejectEventInfo> assocRejectEventInfoCaptor =
+                ArgumentCaptor.forClass(AssocRejectEventInfo.class);
+        verify(mWifiMonitor).broadcastAssociationRejectionEvent(
+                eq(WLAN0_IFACE_NAME), assocRejectEventInfoCaptor.capture());
+        AssocRejectEventInfo assocRejectEventInfo =
+                (AssocRejectEventInfo) assocRejectEventInfoCaptor.getValue();
+        assertNotNull(assocRejectEventInfo);
+        assertEquals(SUPPLICANT_SSID, assocRejectEventInfo.ssid);
+        assertEquals(BSSID, assocRejectEventInfo.bssid);
+        assertEquals(assocRejectData.statusCode, assocRejectEventInfo.statusCode);
+        assertFalse(assocRejectEventInfo.timedOut);
+        assertNotNull(assocRejectEventInfo.oceRssiBasedAssocRejectInfo);
+        assertEquals(assocRejectData.oceRssiBasedAssocRejectData.retryDelayS,
+                assocRejectEventInfo.oceRssiBasedAssocRejectInfo.mRetryDelayS);
+        assertEquals(assocRejectData.oceRssiBasedAssocRejectData.deltaRssi,
+                assocRejectEventInfo.oceRssiBasedAssocRejectInfo.mDeltaRssi);
+        assertNull(assocRejectEventInfo.mboAssocDisallowedInfo);
+    }
+
+    /**
+     * Tests the handling of network not found notification.
+     */
+    @Test
+    public void testNetworkNotFoundCallback() throws Exception {
+        setupMocksForHalV1_4();
+        executeAndValidateInitializationSequenceV1_4();
+        assertNotNull(mISupplicantStaIfaceCallbackV14);
+        mISupplicantStaIfaceCallbackV14.onNetworkNotFound(NativeUtil.decodeSsid(SUPPLICANT_SSID));
+
+        verify(mWifiMonitor).broadcastNetworkNotFoundEvent(
+                eq(WLAN0_IFACE_NAME), eq(SUPPLICANT_SSID));
+
+    }
+
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/SupplicantStaNetworkHalTest.java b/service/tests/wifitests/src/com/android/server/wifi/SupplicantStaNetworkHalTest.java
index f7fc9c2..dea8a00 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/SupplicantStaNetworkHalTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/SupplicantStaNetworkHalTest.java
@@ -21,7 +21,9 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyByte;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyString;
@@ -41,11 +43,13 @@
 import android.hardware.wifi.supplicant.V1_0.SupplicantStatusCode;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.WifiManager;
 import android.os.RemoteException;
 import android.text.TextUtils;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.util.NativeUtil;
 import com.android.wifi.resources.R;
 
@@ -55,6 +59,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
+import java.util.BitSet;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Random;
@@ -77,22 +82,33 @@
     private SupplicantStaNetworkHal mSupplicantNetwork;
     private SupplicantStatus mStatusSuccess;
     private SupplicantStatus mStatusFailure;
+    private android.hardware.wifi.supplicant.V1_4.SupplicantStatus mStatusSuccessV14;
+    private android.hardware.wifi.supplicant.V1_4.SupplicantStatus mStatusFailureV14;
     @Mock private ISupplicantStaNetwork mISupplicantStaNetworkMock;
     @Mock
     private android.hardware.wifi.supplicant.V1_2.ISupplicantStaNetwork mISupplicantStaNetworkV12;
     @Mock
     private android.hardware.wifi.supplicant.V1_3.ISupplicantStaNetwork mISupplicantStaNetworkV13;
+    @Mock
+    private android.hardware.wifi.supplicant.V1_4.ISupplicantStaNetwork mISupplicantStaNetworkV14;
     @Mock private Context mContext;
     @Mock private WifiMonitor mWifiMonitor;
+    @Mock private WifiGlobals mWifiGlobals;
+    private long mAdvanceKeyMgmtFeatures = 0;
 
     private SupplicantNetworkVariables mSupplicantVariables;
     private MockResources mResources;
     private ISupplicantStaNetworkCallback mISupplicantStaNetworkCallback;
+    private android.hardware.wifi.supplicant.V1_4.ISupplicantStaNetworkCallback
+            mISupplicantStaNetworkCallbackV14;
+    private static final String TEST_DECORATED_IDENTITY_PREFIX = "androidwifi.dev!";
 
     enum SupplicantStaNetworkVersion {
         V1_0,
+        V1_1,
         V1_2,
         V1_3,
+        V1_4,
     }
 
     /**
@@ -102,8 +118,10 @@
     private class SupplicantStaNetworkHalSpyV1_2 extends SupplicantStaNetworkHal {
         SupplicantStaNetworkHalSpyV1_2(ISupplicantStaNetwork iSupplicantStaNetwork,
                 String ifaceName,
-                Context context, WifiMonitor monitor) {
-            super(iSupplicantStaNetwork, ifaceName, context, monitor);
+                Context context, WifiMonitor monitor, WifiGlobals wifiGlobals,
+                long advanceKeyMgmtFeatures) {
+            super(iSupplicantStaNetwork, ifaceName, context, monitor, wifiGlobals,
+                    advanceKeyMgmtFeatures);
         }
 
         @Override
@@ -120,8 +138,10 @@
     private class SupplicantStaNetworkHalSpyV1_3 extends SupplicantStaNetworkHalSpyV1_2 {
         SupplicantStaNetworkHalSpyV1_3(ISupplicantStaNetwork iSupplicantStaNetwork,
                 String ifaceName,
-                Context context, WifiMonitor monitor) {
-            super(iSupplicantStaNetwork, ifaceName, context, monitor);
+                Context context, WifiMonitor monitor, WifiGlobals wifiGlobals,
+                long advanceKeyMgmtFeatures) {
+            super(iSupplicantStaNetwork, ifaceName, context, monitor, wifiGlobals,
+                    advanceKeyMgmtFeatures);
         }
 
         @Override
@@ -131,16 +151,43 @@
         }
     }
 
+    /**
+     * Spy used to return the V1_4 ISupplicantStaNetwork mock object to simulate the 1.4 HAL running
+     * on the device.
+     */
+    private class SupplicantStaNetworkHalSpyV1_4 extends SupplicantStaNetworkHalSpyV1_3 {
+        SupplicantStaNetworkHalSpyV1_4(ISupplicantStaNetwork iSupplicantStaNetwork,
+                String ifaceName,
+                Context context, WifiMonitor monitor, WifiGlobals wifiGlobals,
+                long advanceKeyMgmtFeatures) {
+            super(iSupplicantStaNetwork, ifaceName, context, monitor, wifiGlobals,
+                    advanceKeyMgmtFeatures);
+        }
+
+        @Override
+        protected android.hardware.wifi.supplicant.V1_4.ISupplicantStaNetwork
+                getSupplicantStaNetworkForV1_4Mockable() {
+            return mISupplicantStaNetworkV14;
+        }
+    }
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mStatusSuccess = createSupplicantStatus(SupplicantStatusCode.SUCCESS);
         mStatusFailure = createSupplicantStatus(SupplicantStatusCode.FAILURE_UNKNOWN);
+        mStatusSuccessV14 = createSupplicantStatusV1_4(
+                android.hardware.wifi.supplicant.V1_4.SupplicantStatusCode.SUCCESS);
+        mStatusFailureV14 = createSupplicantStatusV1_4(
+                android.hardware.wifi.supplicant.V1_4.SupplicantStatusCode.FAILURE_UNKNOWN);
         mSupplicantVariables = new SupplicantNetworkVariables();
         setupISupplicantNetworkMock();
 
         mResources = new MockResources();
         when(mContext.getResources()).thenReturn(mResources);
+        when(mWifiGlobals.isWpa3SaeUpgradeOffloadEnabled()).thenReturn(true);
+
+        mAdvanceKeyMgmtFeatures |= WifiManager.WIFI_FEATURE_WPA3_SUITE_B;
         createSupplicantStaNetwork(SupplicantStaNetworkVersion.V1_0);
     }
 
@@ -194,15 +241,8 @@
     @Test
     public void testPskPassphraseNetworkWifiConfigurationSaveLoad() throws Exception {
         WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
-        config.requirePmf = true;
 
         // Set the new defaults
-        config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.GCMP_256);
-        config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
-        config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.GCMP_256);
-        config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
-        config.allowedGroupManagementCiphers
-                .set(WifiConfiguration.GroupMgmtCipher.BIP_GMAC_256);
         testWifiConfigurationSaveLoad(config);
         verify(mISupplicantStaNetworkMock).setPskPassphrase(anyString());
         verify(mISupplicantStaNetworkMock)
@@ -211,9 +251,13 @@
         verify(mISupplicantStaNetworkMock, never())
                 .getPsk(any(ISupplicantStaNetwork.getPskCallback.class));
         verify(mISupplicantStaNetworkMock)
-                .setPairwiseCipher(ISupplicantStaNetwork.PairwiseCipherMask.CCMP);
+                .setPairwiseCipher(ISupplicantStaNetwork.PairwiseCipherMask.TKIP
+                        | ISupplicantStaNetwork.PairwiseCipherMask.CCMP);
         verify(mISupplicantStaNetworkMock)
-                .setGroupCipher(ISupplicantStaNetwork.GroupCipherMask.CCMP);
+                .setGroupCipher(ISupplicantStaNetwork.GroupCipherMask.WEP40
+                        | ISupplicantStaNetwork.GroupCipherMask.WEP104
+                        | ISupplicantStaNetwork.GroupCipherMask.TKIP
+                        | ISupplicantStaNetwork.GroupCipherMask.CCMP);
     }
 
     /**
@@ -240,6 +284,9 @@
     public void testPskNetworkWifiConfigurationSaveRemovesPskQuotes() throws Exception {
         WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
         config.preSharedKey = "\"quoted_psd\"";
+        // Assume that the default params is used for this test.
+        config.getNetworkSelectionStatus().setCandidateSecurityParams(
+                config.getDefaultSecurityParams());
         assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
         assertEquals(mSupplicantVariables.pskPassphrase,
                 NativeUtil.removeEnclosingQuotes(config.preSharedKey));
@@ -327,7 +374,7 @@
         createSupplicantStaNetwork(SupplicantStaNetworkVersion.V1_2);
 
         WifiConfiguration config = WifiConfigurationTestUtil.createEapSuiteBNetwork();
-        config.allowedSuiteBCiphers.set(WifiConfiguration.SuiteBCipher.ECDHE_RSA);
+        config.enableSuiteBCiphers(false, true);
 
         testWifiConfigurationSaveLoad(config);
         verify(mISupplicantStaNetworkV12, never()).enableSuiteBEapOpenSslCiphers();
@@ -353,7 +400,7 @@
         createSupplicantStaNetwork(SupplicantStaNetworkVersion.V1_2);
 
         WifiConfiguration config = WifiConfigurationTestUtil.createEapSuiteBNetwork();
-        config.allowedSuiteBCiphers.set(WifiConfiguration.SuiteBCipher.ECDHE_ECDSA);
+        config.enableSuiteBCiphers(true, false);
 
         testWifiConfigurationSaveLoad(config);
         verify(mISupplicantStaNetworkV12).enableSuiteBEapOpenSslCiphers();
@@ -383,7 +430,7 @@
         WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork();
         config.enterpriseConfig =
                 WifiConfigurationTestUtil.createTLSWifiEnterpriseConfigWithNonePhase2();
-        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.FILS_SHA256);
+        config.enableFils(true, false);
         config.enterpriseConfig.setFieldValue(WifiEnterpriseConfig.EAP_ERP, "1");
         testWifiConfigurationSaveLoad(config);
         // Check the supplicant variables to ensure that we have added the FILS AKM.
@@ -448,26 +495,13 @@
             }
         }).when(mISupplicantStaNetworkMock).setSsid(any(ArrayList.class));
 
+        // Assume that the default params is used for this test.
+        config.getNetworkSelectionStatus().setCandidateSecurityParams(
+                config.getDefaultSecurityParams());
         assertFalse(mSupplicantNetwork.saveWifiConfiguration(config));
     }
 
     /**
-     * Tests the failure to save invalid key mgmt (unknown bit set in the
-     * {@link WifiConfiguration#allowedKeyManagement} being saved).
-     */
-    @Test
-    public void testInvalidKeyMgmtSaveFailure() throws Exception {
-        WifiConfiguration config = WifiConfigurationTestUtil.createWepHiddenNetwork();
-        config.allowedKeyManagement.set(20);
-        try {
-            assertFalse(mSupplicantNetwork.saveWifiConfiguration(config));
-        } catch (IllegalArgumentException e) {
-            return;
-        }
-        assertTrue(false);
-    }
-
-    /**
      * Tests the failure to save invalid bssid (less than 6 bytes in the
      * {@link WifiConfiguration#BSSID} being saved).
      */
@@ -475,6 +509,9 @@
     public void testInvalidBssidSaveFailure() throws Exception {
         WifiConfiguration config = WifiConfigurationTestUtil.createWepHiddenNetwork();
         config.getNetworkSelectionStatus().setNetworkSelectionBSSID("45:34:23:12");
+        // Assume that the default params is used for this test.
+        config.getNetworkSelectionStatus().setCandidateSecurityParams(
+                config.getDefaultSecurityParams());
         try {
             assertFalse(mSupplicantNetwork.saveWifiConfiguration(config));
         } catch (IllegalArgumentException e) {
@@ -741,6 +778,9 @@
         createSupplicantStaNetwork(SupplicantStaNetworkVersion.V1_0);
 
         WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
+        // Assume that the default params is used for this test.
+        config.getNetworkSelectionStatus().setCandidateSecurityParams(
+                config.getDefaultSecurityParams());
         assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
 
         // Check the supplicant variables to ensure that we have added the FT flags.
@@ -763,6 +803,9 @@
         createSupplicantStaNetwork(SupplicantStaNetworkVersion.V1_0);
 
         WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork();
+        // Assume that the default params is used for this test.
+        config.getNetworkSelectionStatus().setCandidateSecurityParams(
+                config.getDefaultSecurityParams());
         assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
 
         // Check the supplicant variables to ensure that we have added the FT flags.
@@ -784,6 +827,9 @@
         WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
         // Now expose the V1.2 ISupplicantStaNetwork
         createSupplicantStaNetwork(SupplicantStaNetworkVersion.V1_2);
+        // Assume that the default params is used for this test.
+        config.getNetworkSelectionStatus().setCandidateSecurityParams(
+                config.getDefaultSecurityParams());
         assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
 
         // Check the supplicant variables to ensure that we have added the SHA256 flags.
@@ -807,6 +853,9 @@
         WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork();
         // Now expose the V1.2 ISupplicantStaNetwork
         createSupplicantStaNetwork(SupplicantStaNetworkVersion.V1_2);
+        // Assume that the default params is used for this test.
+        config.getNetworkSelectionStatus().setCandidateSecurityParams(
+                config.getDefaultSecurityParams());
         assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
 
         // Check the supplicant variables to ensure that we have added the SHA256 flags.
@@ -828,6 +877,9 @@
     @Test
     public void testAddPskSha256FlagsHal1_1OrLower() throws Exception {
         WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
+        // Assume that the default params is used for this test.
+        config.getNetworkSelectionStatus().setCandidateSecurityParams(
+                config.getDefaultSecurityParams());
         assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
 
         // Check the supplicant variables to ensure that we have NOT added the SHA256 flags.
@@ -843,6 +895,9 @@
     @Test
     public void testAddEapSha256FlagsHal1_1OrLower() throws Exception {
         WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork();
+        // Assume that the default params is used for this test.
+        config.getNetworkSelectionStatus().setCandidateSecurityParams(
+                config.getDefaultSecurityParams());
         assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
 
         // Check the supplicant variables to ensure that we have NOT added the SHA256 flags.
@@ -863,6 +918,9 @@
         config.enterpriseConfig.setClientCertificateAlias("test_alias");
         config.enterpriseConfig.setOcsp(WifiEnterpriseConfig.OCSP_REQUIRE_CERT_STATUS);
 
+        // Assume that the default params is used for this test.
+        config.getNetworkSelectionStatus().setCandidateSecurityParams(
+                config.getDefaultSecurityParams());
         assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
 
         // Check the supplicant variables to ensure that we have NOT change the OCSP status.
@@ -870,6 +928,58 @@
     }
 
     /**
+     * Tests the addition of multiple AKM when the device supports it.
+     */
+    @Test
+    public void testAddPskSaeAkmWhenAutoUpgradeOffloadIsSupported() throws Exception {
+        createSupplicantStaNetwork(SupplicantStaNetworkVersion.V1_2);
+
+        WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
+        config.getNetworkSelectionStatus().setCandidateSecurityParams(
+                config.getDefaultSecurityParams());
+        assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
+
+        // Check the supplicant variables to ensure that we have added the FT flags.
+        assertEquals(ISupplicantStaNetwork.KeyMgmtMask.WPA_PSK,
+                (mSupplicantVariables.keyMgmtMask & ISupplicantStaNetwork.KeyMgmtMask.WPA_PSK));
+        assertEquals(android.hardware.wifi.supplicant.V1_2.ISupplicantStaNetwork.KeyMgmtMask.SAE,
+                (mSupplicantVariables.keyMgmtMask & android.hardware.wifi.supplicant.V1_2
+                .ISupplicantStaNetwork.KeyMgmtMask.SAE));
+
+        WifiConfiguration loadConfig = new WifiConfiguration();
+        Map<String, String> networkExtras = new HashMap<>();
+        assertTrue(mSupplicantNetwork.loadWifiConfiguration(loadConfig, networkExtras));
+        // The additional SAE AMK should be stripped out when reading it back.
+        WifiConfigurationTestUtil.assertConfigurationEqualForSupplicant(config, loadConfig);
+    }
+
+    /**
+     * Tests the addition of multiple AKM when the device does not support it.
+     */
+    @Test
+    public void testAddPskSaeAkmWhenAutoUpgradeOffloadIsNotSupported() throws Exception {
+        when(mWifiGlobals.isWpa3SaeUpgradeOffloadEnabled()).thenReturn(false);
+        createSupplicantStaNetwork(SupplicantStaNetworkVersion.V1_2);
+
+        WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
+        config.getNetworkSelectionStatus().setCandidateSecurityParams(
+                config.getDefaultSecurityParams());
+        assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
+
+        // Check the supplicant variables to ensure that we have added the FT flags.
+        assertEquals(ISupplicantStaNetwork.KeyMgmtMask.WPA_PSK,
+                (mSupplicantVariables.keyMgmtMask & ISupplicantStaNetwork.KeyMgmtMask.WPA_PSK));
+        assertEquals(0,
+                (mSupplicantVariables.keyMgmtMask & android.hardware.wifi.supplicant.V1_2
+                .ISupplicantStaNetwork.KeyMgmtMask.SAE));
+
+        WifiConfiguration loadConfig = new WifiConfiguration();
+        Map<String, String> networkExtras = new HashMap<>();
+        assertTrue(mSupplicantNetwork.loadWifiConfiguration(loadConfig, networkExtras));
+        WifiConfigurationTestUtil.assertConfigurationEqualForSupplicant(config, loadConfig);
+    }
+
+    /**
      * Tests the retrieval of WPS NFC token.
      */
     @Test
@@ -898,6 +1008,26 @@
         when(mISupplicantStaNetworkMock.registerCallback(any(ISupplicantStaNetworkCallback.class)))
                 .thenReturn(mStatusFailure);
         WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
+        // Assume that the default params is used for this test.
+        config.getNetworkSelectionStatus().setCandidateSecurityParams(
+                config.getDefaultSecurityParams());
+        assertFalse(mSupplicantNetwork.saveWifiConfiguration(config));
+    }
+
+    /**
+     * Tests that callback registration failure triggers a failure in saving network config.
+     */
+    @Test
+    public void testSaveFailureDueToCallbackRegV1_4() throws Exception {
+        createSupplicantStaNetwork(SupplicantStaNetworkVersion.V1_4);
+        when(mISupplicantStaNetworkV14.registerCallback_1_4(any(
+                        android.hardware.wifi.supplicant.V1_4
+                        .ISupplicantStaNetworkCallback.class)))
+                .thenReturn(mStatusFailureV14);
+        WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
+        // Assume that the default params is used for this test.
+        config.getNetworkSelectionStatus().setCandidateSecurityParams(
+                config.getDefaultSecurityParams());
         assertFalse(mSupplicantNetwork.saveWifiConfiguration(config));
     }
 
@@ -907,6 +1037,9 @@
     @Test
     public void testNetworkEapGsmAuthCallback() throws Exception {
         WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
+        // Assume that the default params is used for this test.
+        config.getNetworkSelectionStatus().setCandidateSecurityParams(
+                config.getDefaultSecurityParams());
         assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
         assertNotNull(mISupplicantStaNetworkCallback);
 
@@ -939,6 +1072,9 @@
     @Test
     public void testNetworkEapUmtsAuthCallback() throws Exception {
         WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
+        // Assume that the default params is used for this test.
+        config.getNetworkSelectionStatus().setCandidateSecurityParams(
+                config.getDefaultSecurityParams());
         assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
         assertNotNull(mISupplicantStaNetworkCallback);
 
@@ -964,6 +1100,9 @@
     @Test
     public void testNetworkIdentityCallback() throws Exception {
         WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
+        // Assume that the default params is used for this test.
+        config.getNetworkSelectionStatus().setCandidateSecurityParams(
+                config.getDefaultSecurityParams());
         assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
         assertNotNull(mISupplicantStaNetworkCallback);
 
@@ -980,13 +1119,26 @@
             config.allowedPairwiseCiphers.clear(WifiConfiguration.PairwiseCipher.GCMP_256);
             config.allowedGroupCiphers.clear(WifiConfiguration.GroupCipher.GCMP_256);
         }
+        if (mSupplicantNetwork.getSupplicantStaNetworkForV1_3Mockable() == null) {
+            // Clear unsupported settings in HAL v1.0
+            config.allowedPairwiseCiphers.clear(WifiConfiguration.PairwiseCipher.SMS4);
+            config.allowedGroupCiphers.clear(WifiConfiguration.GroupCipher.SMS4);
+        }
+        if (mSupplicantNetwork.getSupplicantStaNetworkForV1_4Mockable() == null) {
+            // Clear unsupported settings in HAL v1.0
+            config.allowedPairwiseCiphers.clear(WifiConfiguration.PairwiseCipher.GCMP_128);
+            config.allowedGroupCiphers.clear(WifiConfiguration.GroupCipher.GCMP_128);
+        }
+        // Assume that the default params is used for this test.
+        config.getNetworkSelectionStatus().setCandidateSecurityParams(
+                config.getDefaultSecurityParams());
         // Save the configuration using the default supplicant network HAL v1.0
         assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
         WifiConfiguration loadConfig = new WifiConfiguration();
         Map<String, String> networkExtras = new HashMap<>();
         assertTrue(mSupplicantNetwork.loadWifiConfiguration(loadConfig, networkExtras));
         WifiConfigurationTestUtil.assertConfigurationEqualForSupplicant(config, loadConfig);
-        assertEquals(config.getKey(),
+        assertEquals(config.getProfileKey(),
                 networkExtras.get(SupplicantStaNetworkHal.ID_STRING_KEY_CONFIG_KEY));
         assertEquals(
                 config.creatorUid,
@@ -1034,6 +1186,9 @@
     public void testFetchEapAnonymousIdentity() {
         WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork();
         config.enterpriseConfig.setAnonymousIdentity(ANONYMOUS_IDENTITY);
+        // Assume that the default params is used for this test.
+        config.getNetworkSelectionStatus().setCandidateSecurityParams(
+                config.getDefaultSecurityParams());
         assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
         assertEquals(ANONYMOUS_IDENTITY, mSupplicantNetwork.fetchEapAnonymousIdentity());
     }
@@ -1049,6 +1204,9 @@
         WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork();
         config.enterpriseConfig =
                 WifiConfigurationTestUtil.createTLSWifiEnterpriseConfigWithNonePhase2();
+        // Assume that the default params is used for this test.
+        config.getNetworkSelectionStatus().setCandidateSecurityParams(
+                config.getDefaultSecurityParams());
         assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
 
         ArrayList<Byte> serializedData = new ArrayList<>();
@@ -1064,6 +1222,9 @@
         WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork();
         config.enterpriseConfig =
                 WifiConfigurationTestUtil.createTLSWifiEnterpriseConfigWithNonePhase2();
+        // Assume that the default params is used for this test.
+        config.getNetworkSelectionStatus().setCandidateSecurityParams(
+                config.getDefaultSecurityParams());
         assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
 
         ArrayList<Byte> serializedData = new ArrayList<>();
@@ -1071,38 +1232,310 @@
         assertNull(mSupplicantVariables.serializedPmkCache);
     }
 
+    /** Verifies that setSaeH2eMode works on HAL 1.4 or newer */
+    @Test
+    public void testEnableSaeH2eOnlyMode() throws Exception {
+        when(mWifiGlobals.isWpa3SaeH2eSupported()).thenReturn(true);
+        // Now expose the V1.4 ISupplicantStaNetwork
+        createSupplicantStaNetwork(SupplicantStaNetworkVersion.V1_4);
+
+        WifiConfiguration config = WifiConfigurationTestUtil.createSaeNetwork();
+        config.enableSaeH2eOnlyMode(true);
+        // Assume that the default params is used for this test.
+        config.getNetworkSelectionStatus().setCandidateSecurityParams(
+                config.getDefaultSecurityParams());
+        assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
+        verify(mISupplicantStaNetworkV14).setSaeH2eMode(
+                eq(android.hardware.wifi.supplicant.V1_4
+                        .ISupplicantStaNetwork.SaeH2eMode.H2E_MANDATORY));
+    }
+
+    /** Verifies that setSaeH2eMode works on HAL 1.4 or newer */
+    @Test
+    public void testDisableSaeH2eOnlyMode() throws Exception {
+        when(mWifiGlobals.isWpa3SaeH2eSupported()).thenReturn(true);
+        // Now expose the V1.4 ISupplicantStaNetwork
+        createSupplicantStaNetwork(SupplicantStaNetworkVersion.V1_4);
+
+        WifiConfiguration config = WifiConfigurationTestUtil.createSaeNetwork();
+        config.enableSaeH2eOnlyMode(false);
+        // Assume that the default params is used for this test.
+        config.getNetworkSelectionStatus().setCandidateSecurityParams(
+                config.getDefaultSecurityParams());
+        assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
+        verify(mISupplicantStaNetworkV14).setSaeH2eMode(
+                eq(android.hardware.wifi.supplicant.V1_4
+                        .ISupplicantStaNetwork.SaeH2eMode.H2E_OPTIONAL));
+    }
+
+    /** Verifies that setSaeH2eMode works on HAL 1.4 or newer */
+    @Test
+    public void testDisableSaeH2eOnlyModeWhenH2eNotSupported() throws Exception {
+        when(mWifiGlobals.isWpa3SaeH2eSupported()).thenReturn(false);
+        // Now expose the V1.4 ISupplicantStaNetwork
+        createSupplicantStaNetwork(SupplicantStaNetworkVersion.V1_4);
+
+        WifiConfiguration config = WifiConfigurationTestUtil.createSaeNetwork();
+        config.enableSaeH2eOnlyMode(false);
+        // Assume that the default params is used for this test.
+        config.getNetworkSelectionStatus().setCandidateSecurityParams(
+                config.getDefaultSecurityParams());
+        assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
+        verify(mISupplicantStaNetworkV14).setSaeH2eMode(
+                eq(android.hardware.wifi.supplicant.V1_4
+                        .ISupplicantStaNetwork.SaeH2eMode.DISABLED));
+    }
+
+    /** Verifies that setSaeH2eMode won't break 1.3 or older HAL. */
+    @Test
+    public void testSaeH2eOnlyModeWithHal1_3OrLower() throws Exception {
+        when(mWifiGlobals.isWpa3SaeH2eSupported()).thenReturn(true);
+        // Now expose the V1.3 ISupplicantStaNetwork
+        createSupplicantStaNetwork(SupplicantStaNetworkVersion.V1_3);
+
+        WifiConfiguration config = WifiConfigurationTestUtil.createSaeNetwork();
+        config.enableSaeH2eOnlyMode(true);
+        // Assume that the default params is used for this test.
+        config.getNetworkSelectionStatus().setCandidateSecurityParams(
+                config.getDefaultSecurityParams());
+        assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
+        verify(mISupplicantStaNetworkV14, never()).setSaeH2eMode(anyByte());
+    }
+
     /**
      * Tests the saving/loading of WifiConfiguration to wpa_supplicant with psk passphrase for
      * HAL v1.2 or higher
      */
     @Test
-    public void testPskPassphraseNetworkWifiConfigurationSaveLoad1_2OrHigher() throws Exception {
-        createSupplicantStaNetwork(SupplicantStaNetworkVersion.V1_2);
-        WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
-        config.requirePmf = true;
+    public void testSaeNetworkWifiConfigurationSaveLoad1_4OrHigher() throws Exception {
+        createSupplicantStaNetwork(SupplicantStaNetworkVersion.V1_4);
+        WifiConfiguration config = WifiConfigurationTestUtil.createSaeNetwork();
 
         // Set the new defaults
-        config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.GCMP_256);
-        config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
-        config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.GCMP_256);
-        config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
         testWifiConfigurationSaveLoad(config);
-        verify(mISupplicantStaNetworkMock).setPskPassphrase(anyString());
-        verify(mISupplicantStaNetworkMock)
-                .getPskPassphrase(any(ISupplicantStaNetwork.getPskPassphraseCallback.class));
+        verify(mISupplicantStaNetworkV12).setSaePassword(anyString());
+        verify(mISupplicantStaNetworkV12, never())
+                .getSaePassword(any(android.hardware.wifi.supplicant.V1_2.ISupplicantStaNetwork
+                        .getSaePasswordCallback.class));
         verify(mISupplicantStaNetworkMock, never()).setPsk(any(byte[].class));
         verify(mISupplicantStaNetworkMock, never())
                 .getPsk(any(ISupplicantStaNetwork.getPskCallback.class));
-        verify(mISupplicantStaNetworkV12)
-                .setPairwiseCipher_1_2(ISupplicantStaNetwork.PairwiseCipherMask.CCMP
+
+        verify(mISupplicantStaNetworkV14)
+                .setPairwiseCipher_1_4(ISupplicantStaNetwork.PairwiseCipherMask.CCMP
+                        | android.hardware.wifi.supplicant.V1_4.ISupplicantStaNetwork
+                        .PairwiseCipherMask.GCMP_128
                         | android.hardware.wifi.supplicant.V1_2.ISupplicantStaNetwork
                         .PairwiseCipherMask.GCMP_256);
-        verify(mISupplicantStaNetworkV12)
-                .setGroupCipher_1_2(ISupplicantStaNetwork.GroupCipherMask.CCMP
+        verify(mISupplicantStaNetworkV14)
+                .setGroupCipher_1_4(ISupplicantStaNetwork.GroupCipherMask.CCMP
+                        | android.hardware.wifi.supplicant.V1_4.ISupplicantStaNetwork
+                        .GroupCipherMask.GCMP_128
                         | android.hardware.wifi.supplicant.V1_2.ISupplicantStaNetwork
                         .GroupCipherMask.GCMP_256);
     }
 
+    private int putAllSupportingPairwiseCiphersAndReturnExpectedHalCiphersValue(
+            WifiConfiguration config,
+            SupplicantStaNetworkVersion version) {
+        int halMaskValue = 0;
+
+        // The default security params is used in the test.
+        BitSet allowedPairwiseCiphers = config.getDefaultSecurityParams()
+                .getAllowedPairwiseCiphers();
+        // These are supported from v1.4
+        if (allowedPairwiseCiphers.get(WifiConfiguration.PairwiseCipher.GCMP_128)
+                && version.ordinal() >= SupplicantStaNetworkVersion.V1_4.ordinal()) {
+            halMaskValue |= android.hardware.wifi.supplicant
+                    .V1_4.ISupplicantStaNetwork
+                    .PairwiseCipherMask.GCMP_128;
+        }
+
+        // These are supported from v1.3
+        if (allowedPairwiseCiphers.get(WifiConfiguration.PairwiseCipher.SMS4)
+                && version.ordinal() >= SupplicantStaNetworkVersion.V1_3.ordinal()) {
+            halMaskValue |= android.hardware.wifi.supplicant
+                    .V1_3.ISupplicantStaNetwork
+                    .PairwiseCipherMask.SMS4;
+        }
+
+        // These are supported from v1.2
+        if (allowedPairwiseCiphers.get(WifiConfiguration.PairwiseCipher.GCMP_256)
+                && version.ordinal() >= SupplicantStaNetworkVersion.V1_2.ordinal()) {
+            halMaskValue |= android.hardware.wifi.supplicant
+                    .V1_2.ISupplicantStaNetwork
+                    .PairwiseCipherMask.GCMP_256;
+        }
+
+        // There are supported from v1.0
+        if (allowedPairwiseCiphers.get(WifiConfiguration.PairwiseCipher.CCMP)) {
+            halMaskValue |= ISupplicantStaNetwork.PairwiseCipherMask.CCMP;
+        }
+
+        return halMaskValue;
+    }
+
+    private int putAllSupportingGroupCiphersAndReturnExpectedHalCiphersValue(
+            WifiConfiguration config,
+            SupplicantStaNetworkVersion version) {
+        int halMaskValue = 0;
+        // The default security params is used in the test.
+        BitSet allowedGroupCiphers = config.getDefaultSecurityParams().getAllowedGroupCiphers();
+
+        // These are supported from v1.4
+        if (allowedGroupCiphers.get(WifiConfiguration.GroupCipher.GCMP_128)
+                && version.ordinal() >= SupplicantStaNetworkVersion.V1_4.ordinal()) {
+            halMaskValue |= android.hardware.wifi.supplicant
+                    .V1_4.ISupplicantStaNetwork
+                    .GroupCipherMask.GCMP_128;
+        }
+
+        // These are supported from v1.2
+        if (allowedGroupCiphers.get(WifiConfiguration.GroupCipher.GCMP_256)
+                && version.ordinal() >= SupplicantStaNetworkVersion.V1_2.ordinal()) {
+            halMaskValue |= android.hardware.wifi.supplicant
+                    .V1_2.ISupplicantStaNetwork
+                    .GroupCipherMask.GCMP_256;
+        }
+
+        // There are supported from v1.0
+        if (allowedGroupCiphers.get(WifiConfiguration.GroupCipher.CCMP)) {
+            halMaskValue |= ISupplicantStaNetwork.GroupCipherMask.CCMP;
+        }
+
+        return halMaskValue;
+    }
+
+    /**
+     * Tests the saving/loading of WifiConfiguration with
+     * unsupporting GCMP-256 ciphers for V1.2 HAL.
+     *
+     * GCMP-256 is supported only if WPA3 SUITE-B is supported.
+     */
+    @Test
+    public void testUnsupportingGcmp256Ciphers1_2OrHigher()
+            throws Exception {
+        mAdvanceKeyMgmtFeatures = 0;
+        createSupplicantStaNetwork(SupplicantStaNetworkVersion.V1_2);
+        WifiConfiguration config = WifiConfigurationTestUtil.createSaeNetwork();
+        int expectedHalPairwiseCiphers =
+                putAllSupportingPairwiseCiphersAndReturnExpectedHalCiphersValue(config,
+                        SupplicantStaNetworkVersion.V1_2);
+        expectedHalPairwiseCiphers &= ~android.hardware.wifi.supplicant.V1_2.ISupplicantStaNetwork
+                .PairwiseCipherMask.GCMP_256;
+        int expectedHalGroupCiphers =
+                putAllSupportingGroupCiphersAndReturnExpectedHalCiphersValue(config,
+                        SupplicantStaNetworkVersion.V1_2);
+        expectedHalGroupCiphers &= ~android.hardware.wifi.supplicant.V1_2.ISupplicantStaNetwork
+                .GroupCipherMask.GCMP_256;
+
+        // Assume that the default params is used for this test.
+        config.getNetworkSelectionStatus().setCandidateSecurityParams(
+                config.getDefaultSecurityParams());
+        assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
+        WifiConfiguration loadConfig = new WifiConfiguration();
+        Map<String, String> networkExtras = new HashMap<>();
+        assertTrue(mSupplicantNetwork.loadWifiConfiguration(loadConfig, networkExtras));
+
+        verify(mISupplicantStaNetworkV12).setPairwiseCipher_1_2(expectedHalPairwiseCiphers);
+        verify(mISupplicantStaNetworkV12).setGroupCipher_1_2(expectedHalGroupCiphers);
+    }
+
+    private void testUnsupportingCiphers(SupplicantStaNetworkVersion version) throws Exception {
+        createSupplicantStaNetwork(version);
+        WifiConfiguration config = WifiConfigurationTestUtil.createSaeNetwork();
+        int expectedHalPairwiseCiphers =
+                putAllSupportingPairwiseCiphersAndReturnExpectedHalCiphersValue(config, version);
+        int expectedHalGroupCiphers =
+                putAllSupportingGroupCiphersAndReturnExpectedHalCiphersValue(config, version);
+
+        // Assume that the default params is used for this test.
+        config.getNetworkSelectionStatus().setCandidateSecurityParams(
+                config.getDefaultSecurityParams());
+        assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
+        WifiConfiguration loadConfig = new WifiConfiguration();
+        Map<String, String> networkExtras = new HashMap<>();
+        assertTrue(mSupplicantNetwork.loadWifiConfiguration(loadConfig, networkExtras));
+
+        switch (version) {
+            case V1_0:
+            // No new cipher added in V1.1
+            case V1_1:
+                verify(mISupplicantStaNetworkMock)
+                        .setPairwiseCipher(expectedHalPairwiseCiphers);
+                verify(mISupplicantStaNetworkMock)
+                        .setGroupCipher(expectedHalGroupCiphers);
+                break;
+            case V1_2:
+                verify(mISupplicantStaNetworkV12)
+                        .setPairwiseCipher_1_2(expectedHalPairwiseCiphers);
+                verify(mISupplicantStaNetworkV12)
+                        .setGroupCipher_1_2(expectedHalGroupCiphers);
+                break;
+            case V1_3:
+                verify(mISupplicantStaNetworkV13)
+                        .setPairwiseCipher_1_3(expectedHalPairwiseCiphers);
+                verify(mISupplicantStaNetworkV13)
+                        .setGroupCipher_1_3(expectedHalGroupCiphers);
+                break;
+            case V1_4:
+                verify(mISupplicantStaNetworkV14)
+                        .setPairwiseCipher_1_4(expectedHalPairwiseCiphers);
+                verify(mISupplicantStaNetworkV14)
+                        .setGroupCipher_1_4(expectedHalGroupCiphers);
+                break;
+        }
+    }
+
+    /**
+     * Tests the saving/loading of WifiConfiguration with unsupporting ciphers for V1.2 HAL.
+     */
+    @Test
+    public void testUnsupportingCiphers1_2() throws Exception {
+        testUnsupportingCiphers(SupplicantStaNetworkVersion.V1_2);
+    }
+
+    /**
+     * Tests the saving/loading of WifiConfiguration with unsupporting ciphers for V1.3 HAL.
+     */
+    @Test
+    public void testUnsupportingCiphers1_3() throws Exception {
+        testUnsupportingCiphers(SupplicantStaNetworkVersion.V1_3);
+    }
+
+    /**
+     * Tests the saving/loading of WifiConfiguration with unsupporting ciphers for V1.4 HAL.
+     */
+    @Test
+    public void testUnsupportingCiphers1_4() throws Exception {
+        testUnsupportingCiphers(SupplicantStaNetworkVersion.V1_4);
+    }
+
+    /**
+     * Tests the appending decorated identity prefix to anonymous identity and saving to
+     * wpa_supplicant.
+     */
+    @Test
+    public void testEapNetworkSetsDecoratedIdentityPrefix() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        createSupplicantStaNetwork(SupplicantStaNetworkVersion.V1_4);
+        WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork();
+        config.enterpriseConfig.setAnonymousIdentity(ANONYMOUS_IDENTITY);
+        config.enterpriseConfig.setDecoratedIdentityPrefix(TEST_DECORATED_IDENTITY_PREFIX);
+        // Assume that the default params is used for this test.
+        config.getNetworkSelectionStatus().setCandidateSecurityParams(
+                config.getDefaultSecurityParams());
+        assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
+        WifiConfiguration loadConfig = new WifiConfiguration();
+        Map<String, String> networkExtras = new HashMap<>();
+        assertTrue(mSupplicantNetwork.loadWifiConfiguration(loadConfig, networkExtras));
+        assertEquals(TEST_DECORATED_IDENTITY_PREFIX
+                + config.enterpriseConfig.getAnonymousIdentity(),
+                loadConfig.enterpriseConfig.getAnonymousIdentity());
+        assertEquals(TEST_DECORATED_IDENTITY_PREFIX + ANONYMOUS_IDENTITY,
+                mSupplicantNetwork.fetchEapAnonymousIdentity());
+    }
+
     /**
      * Sets up the HIDL interface mock with all the setters/getter values.
      * Note: This only sets up the mock to return success on all methods.
@@ -1385,6 +1818,24 @@
                 .getGroupCipher_1_3(any(android.hardware.wifi.supplicant.V1_3.ISupplicantStaNetwork
                         .getGroupCipher_1_3Callback.class));
 
+        /** allowedGroupCiphers v1.4 */
+        doAnswer(new AnswerWithArguments() {
+            public android.hardware.wifi.supplicant.V1_4.SupplicantStatus
+                    answer(int mask) throws RemoteException {
+                mSupplicantVariables.groupCipherMask = mask;
+                return mStatusSuccessV14;
+            }
+        }).when(mISupplicantStaNetworkV14).setGroupCipher_1_4(any(int.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(android.hardware.wifi.supplicant.V1_4.ISupplicantStaNetwork
+                    .getGroupCipher_1_4Callback cb)
+                    throws RemoteException {
+                cb.onValues(mStatusSuccessV14, mSupplicantVariables.groupCipherMask);
+            }
+        }).when(mISupplicantStaNetworkV14)
+                .getGroupCipher_1_4(any(android.hardware.wifi.supplicant.V1_4.ISupplicantStaNetwork
+                        .getGroupCipher_1_4Callback.class));
+
         /** allowedPairwiseCiphers */
         doAnswer(new AnswerWithArguments() {
             public SupplicantStatus answer(int mask) throws RemoteException {
@@ -1433,6 +1884,41 @@
                 .getPairwiseCipher_1_3(any(android.hardware.wifi.supplicant.V1_3
                         .ISupplicantStaNetwork.getPairwiseCipher_1_3Callback.class));
 
+        /** allowedPairwiseCiphers v1.4 */
+        doAnswer(new AnswerWithArguments() {
+            public android.hardware.wifi.supplicant.V1_4.SupplicantStatus
+                    answer(int mask) throws RemoteException {
+                mSupplicantVariables.pairwiseCipherMask = mask;
+                return mStatusSuccessV14;
+            }
+        }).when(mISupplicantStaNetworkV14).setPairwiseCipher_1_4(any(int.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(android.hardware.wifi.supplicant.V1_4.ISupplicantStaNetwork
+                    .getPairwiseCipher_1_4Callback cb)
+                    throws RemoteException {
+                cb.onValues(mStatusSuccessV14, mSupplicantVariables.pairwiseCipherMask);
+            }
+        }).when(mISupplicantStaNetworkV14)
+                .getPairwiseCipher_1_4(any(android.hardware.wifi.supplicant.V1_4
+                        .ISupplicantStaNetwork.getPairwiseCipher_1_4Callback.class));
+
+        /** allowedGroupManagementCiphers v1.2 */
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(int mask) throws RemoteException {
+                mSupplicantVariables.groupManagementCipherMask = mask;
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkV12).setGroupMgmtCipher(any(int.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(android.hardware.wifi.supplicant.V1_2
+                    .ISupplicantStaNetwork.getGroupMgmtCipherCallback cb)
+                    throws RemoteException {
+                cb.onValues(mStatusSuccess, mSupplicantVariables.groupManagementCipherMask);
+            }
+        }).when(mISupplicantStaNetworkV12)
+                .getGroupMgmtCipher(any(android.hardware.wifi.supplicant.V1_2
+                        .ISupplicantStaNetwork.getGroupMgmtCipherCallback.class));
+
         /** metadata: idstr */
         doAnswer(new AnswerWithArguments() {
             public SupplicantStatus answer(String idStr) throws RemoteException {
@@ -1696,6 +2182,20 @@
         }).when(mISupplicantStaNetworkMock)
                 .registerCallback(any(ISupplicantStaNetworkCallback.class));
 
+        /** Callback registration */
+        doAnswer(new AnswerWithArguments() {
+            public android.hardware.wifi.supplicant.V1_4.SupplicantStatus answer(
+                    android.hardware.wifi.supplicant.V1_4
+                    .ISupplicantStaNetworkCallback cb)
+                    throws RemoteException {
+                mISupplicantStaNetworkCallbackV14 = cb;
+                return mStatusSuccessV14;
+            }
+        }).when(mISupplicantStaNetworkV14)
+                .registerCallback_1_4(any(
+                            android.hardware.wifi.supplicant.V1_4
+                            .ISupplicantStaNetworkCallback.class));
+
         /** Suite-B*/
         doAnswer(new AnswerWithArguments() {
             public SupplicantStatus answer(boolean enable) throws RemoteException {
@@ -1758,6 +2258,15 @@
                 return mStatusSuccess;
             }
         }).when(mISupplicantStaNetworkV13).setEapErp(any(boolean.class));
+
+        /** setSaeH2eMode */
+        doAnswer(new AnswerWithArguments() {
+            public android.hardware.wifi.supplicant.V1_4.SupplicantStatus
+                    answer(byte mode) throws RemoteException {
+                mSupplicantVariables.saeH2eMode = mode;
+                return mStatusSuccessV14;
+            }
+        }).when(mISupplicantStaNetworkV14).setSaeH2eMode(any(byte.class));
     }
 
     private SupplicantStatus createSupplicantStatus(int code) {
@@ -1766,6 +2275,14 @@
         return status;
     }
 
+    private android.hardware.wifi.supplicant.V1_4.SupplicantStatus
+            createSupplicantStatusV1_4(int code) {
+        android.hardware.wifi.supplicant.V1_4.SupplicantStatus status =
+                new android.hardware.wifi.supplicant.V1_4.SupplicantStatus();
+        status.code = code;
+        return status;
+    }
+
     /**
      * Need this for tests which wants to manipulate context before creating the instance.
      */
@@ -1773,15 +2290,23 @@
         switch (version) {
             case V1_0:
                 mSupplicantNetwork = new SupplicantStaNetworkHal(
-                        mISupplicantStaNetworkMock, IFACE_NAME, mContext, mWifiMonitor);
+                        mISupplicantStaNetworkMock, IFACE_NAME, mContext, mWifiMonitor,
+                        mWifiGlobals, mAdvanceKeyMgmtFeatures);
                 break;
             case V1_2:
                 mSupplicantNetwork = new SupplicantStaNetworkHalSpyV1_2(
-                        mISupplicantStaNetworkMock, IFACE_NAME, mContext, mWifiMonitor);
+                        mISupplicantStaNetworkMock, IFACE_NAME, mContext, mWifiMonitor,
+                        mWifiGlobals, mAdvanceKeyMgmtFeatures);
                 break;
             case V1_3:
                 mSupplicantNetwork = new SupplicantStaNetworkHalSpyV1_3(
-                        mISupplicantStaNetworkMock, IFACE_NAME, mContext, mWifiMonitor);
+                        mISupplicantStaNetworkMock, IFACE_NAME, mContext, mWifiMonitor,
+                        mWifiGlobals, mAdvanceKeyMgmtFeatures);
+                break;
+            case V1_4:
+                mSupplicantNetwork = new SupplicantStaNetworkHalSpyV1_4(
+                        mISupplicantStaNetworkMock, IFACE_NAME, mContext, mWifiMonitor,
+                        mWifiGlobals, mAdvanceKeyMgmtFeatures);
                 break;
         }
         mSupplicantNetwork.enableVerboseLogging(true);
@@ -1797,6 +2322,7 @@
         public int authAlgMask;
         public int groupCipherMask;
         public int pairwiseCipherMask;
+        public int groupManagementCipherMask;
         public boolean scanSsid;
         public boolean requirePmf;
         public String idStr;
@@ -1824,5 +2350,6 @@
         public ArrayList<Byte> serializedPmkCache;
         public String wapiCertSuite;
         public boolean eapErp;
+        public byte saeH2eMode;
     }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/SupplicantStateTrackerTest.java b/service/tests/wifitests/src/com/android/server/wifi/SupplicantStateTrackerTest.java
index 1b91ea1..d13ebc0 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/SupplicantStateTrackerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/SupplicantStateTrackerTest.java
@@ -15,26 +15,29 @@
  */
 package com.android.server.wifi;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.Mockito.*;
 
-import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.net.wifi.SupplicantState;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiSsid;
 import android.os.BatteryStatsManager;
-import android.os.Handler;
 import android.os.Message;
+import android.os.UserHandle;
 import android.os.test.TestLooper;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.wifi.ClientModeManagerBroadcastQueue.QueuedBroadcast;
+
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -45,31 +48,43 @@
 public class SupplicantStateTrackerTest extends WifiBaseTest {
 
     private static final String TAG = "SupplicantStateTrackerTest";
-    private static final String   sSSID = "\"GoogleGuest\"";
-    private static final WifiSsid sWifiSsid = WifiSsid.createFromAsciiEncoded(sSSID);
-    private static final String   sBSSID = "01:02:03:04:05:06";
+    private static final String SSID = "\"GoogleGuest\"";
+    private static final WifiSsid WIFI_SSID = WifiSsid.createFromAsciiEncoded(SSID);
+    private static final String BSSID = "01:02:03:04:05:06";
+    private static final String TEST_IFACE = "wlan_test";
 
     private @Mock WifiConfigManager mWcm;
     private @Mock Context mContext;
     private @Mock BatteryStatsManager mBatteryStats;
-    private Handler mHandler;
+    private @Mock WifiMonitor mWifiMonitor;
+    private @Mock ClientModeManager mClientModeManager;
+    private @Mock ClientModeManagerBroadcastQueue mBroadcastQueue;
     private SupplicantStateTracker mSupplicantStateTracker;
     private TestLooper mLooper;
 
+    private @Captor ArgumentCaptor<Intent> mIntentCaptor;
+    private @Captor ArgumentCaptor<QueuedBroadcast> mQueuedBroadcastCaptor;
+
     private Message getSupplicantStateChangeMessage(int networkId, WifiSsid wifiSsid,
             String bssid, SupplicantState newSupplicantState) {
         return Message.obtain(null, WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
                 new StateChangeResult(networkId, wifiSsid, bssid, newSupplicantState));
-
     }
 
     @Before
     public void setUp() {
         mLooper = new TestLooper();
-        mHandler = new Handler(mLooper.getLooper());
         MockitoAnnotations.initMocks(this);
         mSupplicantStateTracker = new SupplicantStateTracker(mContext, mWcm, mBatteryStats,
-                mHandler);
+                mLooper.getLooper(), mWifiMonitor, TEST_IFACE, mClientModeManager, mBroadcastQueue);
+
+        verify(mWifiMonitor, atLeastOnce()).registerHandler(eq(TEST_IFACE), anyInt(), any());
+    }
+
+    @After
+    public void tearDown() {
+        mSupplicantStateTracker.stop();
+        verify(mWifiMonitor, atLeastOnce()).deregisterHandler(eq(TEST_IFACE), anyInt(), any());
     }
 
     /**
@@ -78,21 +93,19 @@
      */
     @Test
     public void testSupplicantStateChangeIntent() {
-        BroadcastReceiver wifiBroadcastReceiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                String action = intent.getAction();
-                assertTrue(action.equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION));
-                SupplicantState recvdState =
-                        (SupplicantState) intent.getExtra(WifiManager.EXTRA_NEW_STATE, -1);
-                assertEquals(SupplicantState.SCANNING, recvdState);
-            }
-        };
-        IntentFilter mIntentFilter = new IntentFilter();
-        mIntentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
-        mContext.registerReceiver(wifiBroadcastReceiver, mIntentFilter);
-        mSupplicantStateTracker.sendMessage(getSupplicantStateChangeMessage(0, sWifiSsid,
-                sBSSID, SupplicantState.SCANNING));
+        mSupplicantStateTracker.sendMessage(getSupplicantStateChangeMessage(0, WIFI_SSID,
+                BSSID, SupplicantState.SCANNING));
+        mLooper.dispatchAll();
+
+        verify(mBroadcastQueue).queueOrSendBroadcast(
+                eq(mClientModeManager), mQueuedBroadcastCaptor.capture());
+        mQueuedBroadcastCaptor.getValue().send();
+
+        verify(mContext).sendStickyBroadcastAsUser(mIntentCaptor.capture(), eq(UserHandle.ALL));
+        Intent intent = mIntentCaptor.getValue();
+        assertThat(intent.getAction()).isEqualTo(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
+        assertThat(intent.<SupplicantState>getParcelableExtra(WifiManager.EXTRA_NEW_STATE))
+                .isEqualTo(SupplicantState.SCANNING);
     }
 
     /**
@@ -100,25 +113,21 @@
      */
     @Test
     public void testAuthPassInSupplicantStateChangeIntent() {
-        BroadcastReceiver wifiBroadcastReceiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                String action = intent.getAction();
-                assertTrue(action.equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION));
-                SupplicantState recvdState =
-                        (SupplicantState) intent.getExtra(WifiManager.EXTRA_NEW_STATE, -1);
-                assertEquals(SupplicantState.AUTHENTICATING, recvdState);
-                boolean authStatus =
-                        (boolean) intent.getExtra(WifiManager.EXTRA_SUPPLICANT_ERROR, -1);
-                assertEquals(authStatus, true);
-            }
-        };
-        IntentFilter mIntentFilter = new IntentFilter();
-        mIntentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
-        mContext.registerReceiver(wifiBroadcastReceiver, mIntentFilter);
-        mSupplicantStateTracker.sendMessage(getSupplicantStateChangeMessage(0, sWifiSsid,
-                sBSSID, SupplicantState.AUTHENTICATING));
+        mSupplicantStateTracker.sendMessage(getSupplicantStateChangeMessage(0, WIFI_SSID,
+                BSSID, SupplicantState.AUTHENTICATING));
         mSupplicantStateTracker.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT);
+        mLooper.dispatchAll();
+
+        verify(mBroadcastQueue).queueOrSendBroadcast(
+                eq(mClientModeManager), mQueuedBroadcastCaptor.capture());
+        mQueuedBroadcastCaptor.getValue().send();
+
+        verify(mContext).sendStickyBroadcastAsUser(mIntentCaptor.capture(), eq(UserHandle.ALL));
+        Intent intent = mIntentCaptor.getValue();
+        assertThat(intent.getAction()).isEqualTo(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
+        assertThat(intent.<SupplicantState>getParcelableExtra(WifiManager.EXTRA_NEW_STATE))
+                .isEqualTo(SupplicantState.AUTHENTICATING);
+        assertThat(intent.getIntExtra(WifiManager.EXTRA_SUPPLICANT_ERROR, 0)).isEqualTo(0);
     }
 
     /**
@@ -126,25 +135,22 @@
      */
     @Test
     public void testAuthFailedInSupplicantStateChangeIntent() {
-        BroadcastReceiver wifiBroadcastReceiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                String action = intent.getAction();
-                assertTrue(action.equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION));
-                SupplicantState recvdState =
-                        (SupplicantState) intent.getExtra(WifiManager.EXTRA_NEW_STATE, -1);
-                assertEquals(SupplicantState.AUTHENTICATING, recvdState);
-                boolean authStatus =
-                        (boolean) intent.getExtra(WifiManager.EXTRA_SUPPLICANT_ERROR, -1);
-                assertEquals(authStatus, false);
-            }
-        };
-        IntentFilter mIntentFilter = new IntentFilter();
-        mIntentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
-        mContext.registerReceiver(wifiBroadcastReceiver, mIntentFilter);
         mSupplicantStateTracker.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT);
-        mSupplicantStateTracker.sendMessage(getSupplicantStateChangeMessage(0, sWifiSsid,
-                sBSSID, SupplicantState.AUTHENTICATING));
+        mSupplicantStateTracker.sendMessage(getSupplicantStateChangeMessage(0, WIFI_SSID,
+                BSSID, SupplicantState.AUTHENTICATING));
+        mLooper.dispatchAll();
+
+        verify(mBroadcastQueue).queueOrSendBroadcast(
+                eq(mClientModeManager), mQueuedBroadcastCaptor.capture());
+        mQueuedBroadcastCaptor.getValue().send();
+
+        verify(mContext).sendStickyBroadcastAsUser(mIntentCaptor.capture(), eq(UserHandle.ALL));
+        Intent intent = mIntentCaptor.getValue();
+        assertThat(intent.getAction()).isEqualTo(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
+        assertThat(intent.<SupplicantState>getParcelableExtra(WifiManager.EXTRA_NEW_STATE))
+                .isEqualTo(SupplicantState.AUTHENTICATING);
+        assertThat(intent.getIntExtra(WifiManager.EXTRA_SUPPLICANT_ERROR, 0))
+                .isEqualTo(WifiManager.ERROR_AUTHENTICATING);
     }
 
     /**
@@ -153,28 +159,24 @@
      */
     @Test
     public void testReasonCodeInSupplicantStateChangeIntent() {
-        BroadcastReceiver wifiBroadcastReceiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                String action = intent.getAction();
-                assertTrue(action.equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION));
-                SupplicantState recvdState =
-                        (SupplicantState) intent.getExtra(WifiManager.EXTRA_NEW_STATE, -1);
-                assertEquals(SupplicantState.AUTHENTICATING, recvdState);
-                boolean authStatus =
-                        (boolean) intent.getExtra(WifiManager.EXTRA_SUPPLICANT_ERROR, -1);
-                assertEquals(authStatus, false);
-                int reasonCode = (int)
-                        intent.getExtra(WifiManager.EXTRA_SUPPLICANT_ERROR_REASON, -1);
-                assertEquals(reasonCode, WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD);
-            }
-        };
-        IntentFilter mIntentFilter = new IntentFilter();
-        mIntentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
-        mContext.registerReceiver(wifiBroadcastReceiver, mIntentFilter);
         mSupplicantStateTracker.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT,
                 WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD, -1);
-        mSupplicantStateTracker.sendMessage(getSupplicantStateChangeMessage(0, sWifiSsid,
-                sBSSID, SupplicantState.AUTHENTICATING));
+        mSupplicantStateTracker.sendMessage(getSupplicantStateChangeMessage(0, WIFI_SSID,
+                BSSID, SupplicantState.AUTHENTICATING));
+        mLooper.dispatchAll();
+
+        verify(mBroadcastQueue).queueOrSendBroadcast(
+                eq(mClientModeManager), mQueuedBroadcastCaptor.capture());
+        mQueuedBroadcastCaptor.getValue().send();
+
+        verify(mContext).sendStickyBroadcastAsUser(mIntentCaptor.capture(), eq(UserHandle.ALL));
+        Intent intent = mIntentCaptor.getValue();
+        assertThat(intent.getAction()).isEqualTo(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
+        assertThat(intent.<SupplicantState>getParcelableExtra(WifiManager.EXTRA_NEW_STATE))
+                .isEqualTo(SupplicantState.AUTHENTICATING);
+        assertThat(intent.getIntExtra(WifiManager.EXTRA_SUPPLICANT_ERROR, 0))
+                .isEqualTo(WifiManager.ERROR_AUTHENTICATING);
+        assertThat(intent.getIntExtra(WifiManager.EXTRA_SUPPLICANT_ERROR_REASON, -1))
+                .isEqualTo(WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD);
     }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/ThroughputPredictorTest.java b/service/tests/wifitests/src/com/android/server/wifi/ThroughputPredictorTest.java
index f8b0586..1409a43 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/ThroughputPredictorTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/ThroughputPredictorTest.java
@@ -299,6 +299,15 @@
     }
 
     @Test
+    public void verifyMaxThroughput11BMode() {
+        mConnectionCap.wifiStandard = ScanResult.WIFI_STANDARD_LEGACY;
+        mConnectionCap.is11bMode = true;
+        mConnectionCap.channelBandwidth = ScanResult.CHANNEL_WIDTH_20MHZ;
+        mConnectionCap.maxNumberTxSpatialStreams = 2;
+        assertEquals(11, mThroughputPredictor.predictMaxTxThroughput(mConnectionCap));
+    }
+
+    @Test
     public void verifyMaxThroughputAc40Mhz2ss() {
         mConnectionCap.wifiStandard = ScanResult.WIFI_STANDARD_11AC;
         mConnectionCap.channelBandwidth = ScanResult.CHANNEL_WIDTH_40MHZ;
@@ -356,6 +365,16 @@
     }
 
     @Test
+    public void verifyTxThroughput11BModeLowSnr() {
+        mConnectionCap.wifiStandard = ScanResult.WIFI_STANDARD_LEGACY;
+        mConnectionCap.is11bMode = true;
+        mConnectionCap.channelBandwidth = ScanResult.CHANNEL_WIDTH_20MHZ;
+        mConnectionCap.maxNumberTxSpatialStreams = 1;
+        assertEquals(8, mThroughputPredictor.predictTxThroughput(mConnectionCap,
+                -80, 2437, 80));
+    }
+
+    @Test
     public void verifyTxThroughputAc20Mhz2ssMiddleSnr() {
         mConnectionCap.wifiStandard = ScanResult.WIFI_STANDARD_11AC;
         mConnectionCap.channelBandwidth = ScanResult.CHANNEL_WIDTH_20MHZ;
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WakeupConfigStoreDataTest.java b/service/tests/wifitests/src/com/android/server/wifi/WakeupConfigStoreDataTest.java
index cc7587a..1a95adb 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WakeupConfigStoreDataTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WakeupConfigStoreDataTest.java
@@ -24,6 +24,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.net.wifi.SecurityParams;
+import android.net.wifi.WifiConfiguration;
 import android.util.Xml;
 
 import androidx.test.filters.SmallTest;
@@ -126,15 +128,21 @@
     public void deserializeSerializedData() throws Exception {
         ScanResultMatchInfo network1 = new ScanResultMatchInfo();
         network1.networkSsid = "ssid 1";
-        network1.networkType = 0;
+        network1.securityParamsList.add(
+                SecurityParams.createSecurityParamsBySecurityType(
+                    WifiConfiguration.SECURITY_TYPE_OPEN));
 
         ScanResultMatchInfo network2 = new ScanResultMatchInfo();
         network2.networkSsid = ",.23;4@, .#,%(,";
-        network2.networkType = 1;
+        network2.securityParamsList.add(
+                SecurityParams.createSecurityParamsBySecurityType(
+                    WifiConfiguration.SECURITY_TYPE_WEP));
 
         ScanResultMatchInfo network3 = new ScanResultMatchInfo();
         network3.networkSsid = "";
-        network3.networkType = 2;
+        network3.securityParamsList.add(
+                SecurityParams.createSecurityParamsBySecurityType(
+                    WifiConfiguration.SECURITY_TYPE_PSK));
 
         Set<ScanResultMatchInfo> networks = Sets.newArraySet(network1, network2, network3);
         boolean isActive = true;
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WakeupControllerTest.java b/service/tests/wifitests/src/com/android/server/wifi/WakeupControllerTest.java
index 3ac4adc..f08ba74 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WakeupControllerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WakeupControllerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.wifi;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -26,11 +28,13 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
 
 import android.content.Context;
 import android.database.ContentObserver;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
 import android.net.wifi.WifiNetworkSuggestion;
 import android.net.wifi.WifiScanner;
 import android.os.Handler;
@@ -39,16 +43,21 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.wifi.ActiveModeWarden.PrimaryClientModeManagerChangedCallback;
 import com.android.server.wifi.util.ScanResultUtil;
 import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.ByteArrayOutputStream;
@@ -67,6 +76,8 @@
 
     private static final String SAVED_SSID = "test scan ssid";
     private static final int DFS_CHANNEL_FREQ = 5540;
+    private static final int TEST_PRIORITY_GROUP =
+            WifiNetworkSuggestionsManager.DEFAULT_PRIORITY_GROUP;
 
     @Mock private Context mContext;
     @Mock private WakeupLock mWakeupLock;
@@ -83,24 +94,41 @@
     @Mock private ActiveModeWarden mActiveModeWarden;
     @Mock private WifiNative mWifiNative;
     @Mock private Clock mClock;
+    @Mock private ConcreteClientModeManager mPrimaryClientModeManager;
+    @Mock private WifiGlobals mWifiGlobals;
+
+    @Captor private ArgumentCaptor<PrimaryClientModeManagerChangedCallback> mPrimaryChangedCaptor;
 
     private TestLooper mLooper;
     private WakeupController mWakeupController;
     private WakeupConfigStoreData mWakeupConfigStoreData;
     private WifiScanner.ScanData[] mTestScanDatas;
     private ScanResult mTestScanResult;
+    private MockitoSession mSession;
 
     /** Initialize objects before each test run. */
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        // static mocking
+        mSession = mockitoSession()
+                .mockStatic(WifiInjector.class, withSettings().lenient())
+                .strictness(Strictness.LENIENT)
+                .startMocking();
 
+        when(WifiInjector.getInstance()).thenReturn(mWifiInjector);
         when(mWifiInjector.getWifiScanner()).thenReturn(mWifiScanner);
         when(mWifiInjector.getWifiSettingsStore()).thenReturn(mWifiSettingsStore);
         when(mWifiInjector.getActiveModeWarden()).thenReturn(mActiveModeWarden);
+        when(mActiveModeWarden.getPrimaryClientModeManager()).thenReturn(mPrimaryClientModeManager);
+        when(mPrimaryClientModeManager.getSupportedFeatures()).thenReturn(
+                WifiManager.WIFI_FEATURE_WPA3_SAE | WifiManager.WIFI_FEATURE_OWE);
         when(mWifiInjector.getWifiNative()).thenReturn(mWifiNative);
         when(mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY))
                 .thenReturn(new int[]{DFS_CHANNEL_FREQ});
+        when(mWifiInjector.getWifiGlobals()).thenReturn(mWifiGlobals);
+        when(mWifiGlobals.isWpa3SaeUpgradeEnabled()).thenReturn(true);
+        when(mWifiGlobals.isOweUpgradeEnabled()).thenReturn(true);
 
         when(mWifiSettingsStore.handleWifiToggled(anyBoolean())).thenReturn(true);
         // Saved network needed to start wake.
@@ -123,6 +151,16 @@
                 0 /* bucketsScanned */, scanBand /* bandScanned */, scanResults);
     }
 
+    /**
+     * Called after each test
+     */
+    @After
+    public void cleanup() {
+        if (mSession != null) {
+            mSession.finishMocking();
+        }
+    }
+
     /** Initializes the wakeupcontroller in the given {@code enabled} state. */
     private void initializeWakeupController(boolean enabled) {
         initializeWakeupController(enabled, true /* isRead */);
@@ -144,7 +182,10 @@
                 mWifiWakeMetrics,
                 mWifiInjector,
                 mFrameworkFacade,
-                mClock);
+                mClock, mActiveModeWarden);
+
+        verify(mActiveModeWarden).registerPrimaryClientModeManagerChangedCallback(
+                mPrimaryChangedCaptor.capture());
 
         ArgumentCaptor<WakeupConfigStoreData> captor =
                 ArgumentCaptor.forClass(WakeupConfigStoreData.class);
@@ -330,7 +371,7 @@
         metricsInOrder.verify(mWifiWakeMetrics).recordStartEvent(anyInt());
 
         mWakeupController.stop();
-        mWakeupController.reset();
+        mPrimaryChangedCaptor.getValue().onChange(null, mPrimaryClientModeManager);
         metricsInOrder.verify(mWifiWakeMetrics).recordResetEvent(0 /* numScans */);
 
         mWakeupController.start();
@@ -360,7 +401,7 @@
         ScanResult savedScanResult = createOpenScanResult(ssid1, 2412 /* frequency */);
         ScanResult unsavedScanResult = createOpenScanResult(ssid2, 2412 /* frequency */);
 
-        when(mWifiScanner.getSingleScanResults())
+        when(mWifiConfigManager.getMostRecentScanResultsForConfiguredNetworks(anyInt()))
                 .thenReturn(Arrays.asList(savedScanResult, unsavedScanResult));
 
         // intersection of most recent scan + saved configs
@@ -386,10 +427,12 @@
         // suggestions
         WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork(quotedSsid);
         WifiNetworkSuggestion openNetworkSuggestion =
-                new WifiNetworkSuggestion(openNetwork, null, false, false, true, true);
+                new WifiNetworkSuggestion(openNetwork, null, false, false, true, true,
+                        TEST_PRIORITY_GROUP);
         WifiConfiguration wepNetwork = WifiConfigurationTestUtil.createWepNetwork();
         WifiNetworkSuggestion wepNetworkSuggestion =
-                new WifiNetworkSuggestion(wepNetwork, null, false, false, true, true);
+                new WifiNetworkSuggestion(wepNetwork, null, false, false, true, true,
+                        TEST_PRIORITY_GROUP);
         when(mWifiNetworkSuggestionsManager.getAllApprovedNetworkSuggestions())
                 .thenReturn(new HashSet<>(Arrays.asList(
                         openNetworkSuggestion, wepNetworkSuggestion)));
@@ -398,7 +441,7 @@
         ScanResult savedScanResult = createOpenScanResult(ssid1, 2412 /* frequency */);
         ScanResult unsavedScanResult = createOpenScanResult(ssid2, 2412 /* frequency */);
 
-        when(mWifiScanner.getSingleScanResults())
+        when(mWifiConfigManager.getMostRecentScanResultsForConfiguredNetworks(anyInt()))
                 .thenReturn(Arrays.asList(savedScanResult, unsavedScanResult));
 
         // intersection of most recent scan + saved configs
@@ -431,7 +474,8 @@
 
         WifiConfiguration oweNetwork = WifiConfigurationTestUtil.createOweNetwork(quotedSsid2);
         WifiNetworkSuggestion oweNetworkSuggestion =
-                new WifiNetworkSuggestion(oweNetwork, null, false, false, true, true);
+                new WifiNetworkSuggestion(oweNetwork, null, false, false, true, true,
+                        TEST_PRIORITY_GROUP);
         when(mWifiNetworkSuggestionsManager.getAllApprovedNetworkSuggestions())
                 .thenReturn(new HashSet<>(Arrays.asList(oweNetworkSuggestion)));
 
@@ -440,7 +484,7 @@
         ScanResult suggestionScanResult = createOweScanResult(ssid2, 2412 /* frequency */);
         ScanResult unknownScanResult = createOpenScanResult(ssid3, 2412 /* frequency */);
 
-        when(mWifiScanner.getSingleScanResults())
+        when(mWifiConfigManager.getMostRecentScanResultsForConfiguredNetworks(anyInt()))
                 .thenReturn(Arrays.asList(savedScanResult, suggestionScanResult,
                         unknownScanResult));
 
@@ -457,6 +501,48 @@
     }
 
     /**
+     * Verify that saved networks suggestions ignore captive portal without validated network.
+     */
+    @Test
+    public void getGoodSavedNetworksAndSuggestionsIgnoreInvalidatedCaptivePortal() {
+        String ssid1 = "ssid 1";
+        String quotedSsid1 = ScanResultUtil.createQuotedSSID(ssid1);
+
+        // saved captive portal config without validated network
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork(quotedSsid1);
+        openNetwork.getNetworkSelectionStatus().setHasEverConnected(true);
+        openNetwork.getNetworkSelectionStatus().setHasNeverDetectedCaptivePortal(false);
+        openNetwork.validatedInternetAccess = false;
+        when(mWifiConfigManager.getSavedNetworks(anyInt()))
+                .thenReturn(Arrays.asList(openNetwork));
+
+        initializeWakeupController(true);
+        mWakeupController.start();
+        verify(mWifiInjector, never()).getWifiScanner();
+    }
+
+    /**
+     * Verify that saved networks suggestions include captive portal with validated network.
+     */
+    @Test
+    public void getGoodSavedNetworksAndSuggestionsIncludeValidatedCaptivePortal() {
+        String ssid1 = "ssid 1";
+        String quotedSsid1 = ScanResultUtil.createQuotedSSID(ssid1);
+
+        // saved captive portal config with validated network
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork(quotedSsid1);
+        openNetwork.getNetworkSelectionStatus().setHasEverConnected(true);
+        openNetwork.getNetworkSelectionStatus().setHasNeverDetectedCaptivePortal(false);
+        openNetwork.validatedInternetAccess = true;
+        when(mWifiConfigManager.getSavedNetworks(anyInt()))
+                .thenReturn(Arrays.asList(openNetwork));
+
+        initializeWakeupController(true);
+        mWakeupController.start();
+        verify(mWifiInjector).getWifiScanner();
+    }
+
+    /**
      * Verify that start filters out DFS channels.
      */
     @Test
@@ -479,7 +565,7 @@
         ScanResult scanResultDfs = createOpenScanResult(ssidDfs, DFS_CHANNEL_FREQ);
         ScanResult scanResult24 = createOpenScanResult(ssid24, 2412 /* frequency */);
 
-        when(mWifiScanner.getSingleScanResults())
+        when(mWifiConfigManager.getMostRecentScanResultsForConfiguredNetworks(anyInt()))
                 .thenReturn(Arrays.asList(scanResultDfs, scanResult24));
 
         // should filter out scanResultDfs
@@ -529,7 +615,8 @@
         WifiConfiguration openNetwork = WifiConfigurationTestUtil
                 .createOpenNetwork(ScanResultUtil.createQuotedSSID(SAVED_SSID));
         WifiNetworkSuggestion openNetworkSuggestion =
-                new WifiNetworkSuggestion(openNetwork, null, false, false, true, true);
+                new WifiNetworkSuggestion(openNetwork, null, false, false, true, true,
+                        TEST_PRIORITY_GROUP);
         when(mWifiNetworkSuggestionsManager.getAllApprovedNetworkSuggestions())
                 .thenReturn(new HashSet<>(Collections.singletonList(openNetworkSuggestion)));
 
@@ -729,19 +816,20 @@
         when(mClock.getElapsedSinceBootMillis()).thenReturn(0L,
                 (long) (0.8 * WakeupController.LAST_DISCONNECT_TIMEOUT_MILLIS));
         ScanResultMatchInfo matchInfo = ScanResultMatchInfo.fromScanResult(mTestScanResult);
-        when(mWifiScanner.getSingleScanResults()).thenReturn(Collections.emptyList());
+        when(mWifiConfigManager.getMostRecentScanResultsForConfiguredNetworks(anyInt()))
+                .thenReturn(Collections.emptyList());
         initializeWakeupController(true);
 
         mWakeupController.setLastDisconnectInfo(matchInfo);
         mWakeupController.start();
 
-        verify(mWakeupLock).setLock(eq(Collections.singleton(matchInfo)));
+        verify(mWakeupLock).setLock(Set.of(matchInfo));
 
         mWakeupController.stop();
-        mWakeupController.reset();
+        mPrimaryChangedCaptor.getValue().onChange(null, mPrimaryClientModeManager);
         mWakeupController.start();
 
-        verify(mWakeupLock).setLock(eq(Collections.emptySet()));
+        verify(mWakeupLock).setLock(Set.of());
     }
 
     /**
@@ -754,7 +842,8 @@
         when(mClock.getElapsedSinceBootMillis()).thenReturn(0L,
                 (long) (1.2 * WakeupController.LAST_DISCONNECT_TIMEOUT_MILLIS));
         ScanResultMatchInfo matchInfo = ScanResultMatchInfo.fromScanResult(mTestScanResult);
-        when(mWifiScanner.getSingleScanResults()).thenReturn(Collections.emptyList());
+        when(mWifiConfigManager.getMostRecentScanResultsForConfiguredNetworks(anyInt()))
+                .thenReturn(Collections.emptyList());
         initializeWakeupController(true);
 
         mWakeupController.setLastDisconnectInfo(matchInfo);
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WakeupEvaluatorTest.java b/service/tests/wifitests/src/com/android/server/wifi/WakeupEvaluatorTest.java
index bc28ce1..381ee82 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WakeupEvaluatorTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WakeupEvaluatorTest.java
@@ -16,12 +16,17 @@
 
 package com.android.server.wifi;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
 
 import android.net.wifi.ScanResult;
+import android.net.wifi.WifiManager;
 import android.util.ArraySet;
 
 import androidx.test.filters.SmallTest;
@@ -30,8 +35,13 @@
 
 import com.google.android.collect.Sets;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
 
 import java.util.Collections;
 import java.util.Set;
@@ -53,7 +63,13 @@
     private static final int THRESHOLD_5 = -90;
     private final ScoringParams mScoringParams = new ScoringParams();
 
+    @Mock private WifiInjector mWifiInjector;
+    @Mock private ActiveModeWarden mActiveModeWarden;
+    @Mock private ClientModeManager mPrimaryClientModeManager;
+    @Mock private WifiGlobals mWifiGlobals;
+
     private WakeupEvaluator mWakeupEvaluator;
+    private MockitoSession mSession;
 
     private ScanResult makeScanResult(String ssid, int frequency, int level) {
         ScanResult scanResult = new ScanResult();
@@ -78,6 +94,22 @@
 
     @Before
     public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        // static mocking
+        mSession = mockitoSession()
+                .mockStatic(WifiInjector.class, withSettings().lenient())
+                .strictness(Strictness.LENIENT)
+                .startMocking();
+
+        when(WifiInjector.getInstance()).thenReturn(mWifiInjector);
+        when(mWifiInjector.getActiveModeWarden()).thenReturn(mActiveModeWarden);
+        when(mActiveModeWarden.getPrimaryClientModeManager()).thenReturn(mPrimaryClientModeManager);
+        when(mPrimaryClientModeManager.getSupportedFeatures()).thenReturn(
+                WifiManager.WIFI_FEATURE_WPA3_SAE | WifiManager.WIFI_FEATURE_OWE);
+        when(mWifiInjector.getWifiGlobals()).thenReturn(mWifiGlobals);
+        when(mWifiGlobals.isWpa3SaeUpgradeEnabled()).thenReturn(true);
+        when(mWifiGlobals.isOweUpgradeEnabled()).thenReturn(true);
+
         String params = "rssi2=-120:" + THRESHOLD_24 + ":-2:-1" + ","
                       + "rssi5=-120:" + THRESHOLD_5 + ":-2:-1";
         assertTrue(params, mScoringParams.update(params));
@@ -85,6 +117,16 @@
     }
 
     /**
+     * Called after each test
+     */
+    @After
+    public void cleanup() {
+        if (mSession != null) {
+            mSession.finishMocking();
+        }
+    }
+
+    /**
      * Verify that isBelowThreshold returns true for networks below the filter threshold.
      */
     @Test
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WakeupLockTest.java b/service/tests/wifitests/src/com/android/server/wifi/WakeupLockTest.java
index 7c53f49..52f9783 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WakeupLockTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WakeupLockTest.java
@@ -21,6 +21,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.*;
 
+import android.net.wifi.SecurityParams;
 import android.net.wifi.WifiConfiguration;
 
 import androidx.test.filters.SmallTest;
@@ -63,11 +64,15 @@
 
         mNetwork1 = new ScanResultMatchInfo();
         mNetwork1.networkSsid = SSID_1;
-        mNetwork1.networkType = WifiConfiguration.SECURITY_TYPE_OPEN;
+        mNetwork1.securityParamsList.add(
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_OPEN));
 
         mNetwork2 = new ScanResultMatchInfo();
         mNetwork2.networkSsid = SSID_2;
-        mNetwork2.networkType = WifiConfiguration.SECURITY_TYPE_EAP;
+        mNetwork2.securityParamsList.add(
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_EAP));
 
         mWakeupLock = new WakeupLock(mWifiConfigManager, mWifiWakeMetrics, mClock);
     }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WakeupOnboardingTest.java b/service/tests/wifitests/src/com/android/server/wifi/WakeupOnboardingTest.java
index 7d9cec2..8c4809c 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WakeupOnboardingTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WakeupOnboardingTest.java
@@ -27,11 +27,8 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
-import android.app.NotificationManager;
 import android.content.BroadcastReceiver;
-import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.Handler;
@@ -51,12 +48,11 @@
 /** Unit tests for {@link com.android.server.wifi.WakeupOnboarding} */
 @SmallTest
 public class WakeupOnboardingTest extends WifiBaseTest {
-
-    @Mock private Context mContext;
+    @Mock private WifiContext mContext;
     @Mock private WifiConfigManager mWifiConfigManager;
     @Mock private FrameworkFacade mFrameworkFacade;
     @Mock private WakeupNotificationFactory mWakeupNotificationFactory;
-    @Mock private NotificationManager mNotificationManager;
+    @Mock private WifiNotificationManager mWifiNotificationManager;
 
     private TestLooper mLooper;
     private WakeupOnboarding mWakeupOnboarding;
@@ -74,12 +70,10 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        when(mContext.getSystemService(Context.NOTIFICATION_SERVICE))
-                .thenReturn(mNotificationManager);
-
         mLooper = new TestLooper();
         mWakeupOnboarding = new WakeupOnboarding(mContext, mWifiConfigManager,
-                new Handler(mLooper.getLooper()), mFrameworkFacade, mWakeupNotificationFactory);
+                new Handler(mLooper.getLooper()), mFrameworkFacade, mWakeupNotificationFactory,
+                mWifiNotificationManager);
     }
 
     /**
@@ -90,7 +84,7 @@
         setOnboardedStatus(false);
         mWakeupOnboarding.maybeShowNotification();
 
-        verify(mNotificationManager).notify(eq(WakeupNotificationFactory.ONBOARD_ID), any());
+        verify(mWifiNotificationManager).notify(eq(WakeupNotificationFactory.ONBOARD_ID), any());
     }
 
     /**
@@ -101,7 +95,7 @@
         setOnboardedStatus(true);
         mWakeupOnboarding.maybeShowNotification();
 
-        verify(mNotificationManager, never())
+        verify(mWifiNotificationManager, never())
                 .notify(eq(WakeupNotificationFactory.ONBOARD_ID), any());
     }
 
@@ -114,9 +108,9 @@
         mWakeupOnboarding.maybeShowNotification();
         mWakeupOnboarding.maybeShowNotification();
 
-        InOrder inOrder = Mockito.inOrder(mNotificationManager);
-        inOrder.verify(mNotificationManager)
-                .notify(eq(WakeupNotificationFactory.ONBOARD_ID), any());
+        InOrder inOrder = Mockito.inOrder(mWifiNotificationManager);
+        inOrder.verify(mWifiNotificationManager).notify(eq(WakeupNotificationFactory.ONBOARD_ID),
+                any());
         inOrder.verifyNoMoreInteractions();
     }
 
@@ -136,7 +130,7 @@
 
         broadcastReceiver.onReceive(mContext, new Intent(ACTION_DISMISS_NOTIFICATION));
 
-        verify(mNotificationManager).cancel(WakeupNotificationFactory.ONBOARD_ID);
+        verify(mWifiNotificationManager).cancel(WakeupNotificationFactory.ONBOARD_ID);
         assertTrue(mWakeupOnboarding.isOnboarded());
     }
 
@@ -160,7 +154,7 @@
         verify(mFrameworkFacade).setIntegerSetting(mContext,
                 Settings.Global.WIFI_WAKEUP_ENABLED, 0);
 
-        verify(mNotificationManager).cancel(WakeupNotificationFactory.ONBOARD_ID);
+        verify(mWifiNotificationManager).cancel(WakeupNotificationFactory.ONBOARD_ID);
         assertTrue(mWakeupOnboarding.isOnboarded());
     }
 
@@ -183,7 +177,7 @@
 
         verify(mContext).startActivity(any());
 
-        verify(mNotificationManager).cancel(WakeupNotificationFactory.ONBOARD_ID);
+        verify(mWifiNotificationManager).cancel(WakeupNotificationFactory.ONBOARD_ID);
         assertTrue(mWakeupOnboarding.isOnboarded());
     }
 
@@ -198,7 +192,7 @@
         mWakeupOnboarding.maybeShowNotification();
         mWakeupOnboarding.onStop();
 
-        verify(mNotificationManager).cancel(WakeupNotificationFactory.ONBOARD_ID);
+        verify(mWifiNotificationManager).cancel(WakeupNotificationFactory.ONBOARD_ID);
         assertFalse(mWakeupOnboarding.isOnboarded());
     }
 
@@ -236,10 +230,10 @@
         mWakeupOnboarding.onStop();
         mWakeupOnboarding.maybeShowNotification(0 /* timestamp */);
 
-        InOrder inOrder = Mockito.inOrder(mNotificationManager);
-        inOrder.verify(mNotificationManager)
-                .notify(eq(WakeupNotificationFactory.ONBOARD_ID), any());
-        inOrder.verify(mNotificationManager).cancel(WakeupNotificationFactory.ONBOARD_ID);
+        InOrder inOrder = Mockito.inOrder(mWifiNotificationManager);
+        inOrder.verify(mWifiNotificationManager).notify(eq(WakeupNotificationFactory.ONBOARD_ID),
+                any());
+        inOrder.verify(mWifiNotificationManager).cancel(WakeupNotificationFactory.ONBOARD_ID);
         inOrder.verifyNoMoreInteractions();
     }
 
@@ -257,7 +251,7 @@
         mWakeupOnboarding.onStop();
         mWakeupOnboarding.maybeShowNotification(WakeupOnboarding.REQUIRED_NOTIFICATION_DELAY + 1);
 
-        verify(mNotificationManager, times(2))
+        verify(mWifiNotificationManager, times(2))
                 .notify(eq(WakeupNotificationFactory.ONBOARD_ID), any());
     }
 
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiApConfigStoreTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiApConfigStoreTest.java
index 90e0171..fcddf7a 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiApConfigStoreTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiApConfigStoreTest.java
@@ -21,11 +21,13 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -44,6 +46,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.modules.utils.build.SdkLevel;
 import com.android.wifi.resources.R;
 
 import org.junit.Before;
@@ -76,8 +79,14 @@
     private static final String TEST_STRING_UTF8_WITH_34_BYTES = "Ευπροσηγοροςγινου";
     private static final MacAddress TEST_RANDOMIZED_MAC =
             MacAddress.fromString("d2:11:19:34:a5:20");
+    private static final MacAddress TEST_SAP_BSSID_MAC =
+            MacAddress.fromString("aa:bb:cc:11:22:33");
 
     private final int mBand25G = SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ;
+    private final int mBand256G = SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ
+            | SoftApConfiguration.BAND_6GHZ;
+    private final int mBand25660G = SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ
+            | SoftApConfiguration.BAND_6GHZ | SoftApConfiguration.BAND_60GHZ;
 
     @Mock private Context mContext;
     @Mock private WifiInjector mWifiInjector;
@@ -111,6 +120,8 @@
                              TEST_DEFAULT_AP_SSID);
         mResources.setString(R.string.wifi_localhotspot_configure_ssid_default,
                              TEST_DEFAULT_HOTSPOT_SSID);
+        mResources.setBoolean(R.bool.config_wifiSoftapPassphraseAsciiEncodableCheck, true);
+        setupAllBandsSupported();
         /* Default to device that does not require ap band conversion */
         when(mActiveModeWarden.isStaApConcurrencySupported())
                 .thenReturn(false);
@@ -122,6 +133,18 @@
         mRandom = new Random();
         when(mWifiInjector.getMacAddressUtil()).thenReturn(mMacAddressUtil);
         when(mMacAddressUtil.calculatePersistentMac(any(), any())).thenReturn(TEST_RANDOMIZED_MAC);
+        mResources.setBoolean(R.bool.config_wifi_ap_mac_randomization_supported, true);
+    }
+
+    private void setupAllBandsSupported() {
+        mResources.setBoolean(R.bool.config_wifi24ghzSupport, true);
+        mResources.setBoolean(R.bool.config_wifiSoftap24ghzSupported, true);
+        mResources.setBoolean(R.bool.config_wifi5ghzSupport, true);
+        mResources.setBoolean(R.bool.config_wifiSoftap5ghzSupported, true);
+        mResources.setBoolean(R.bool.config_wifi6ghzSupport, true);
+        mResources.setBoolean(R.bool.config_wifiSoftap6ghzSupported, true);
+        mResources.setBoolean(R.bool.config_wifi60ghzSupport, true);
+        mResources.setBoolean(R.bool.config_wifiSoftap60ghzSupported, true);
     }
 
     /**
@@ -169,6 +192,12 @@
 
     private void verifyDefaultApConfig(SoftApConfiguration config, String expectedSsid,
             boolean isSaeSupport) {
+        verifyDefaultApConfig(config, expectedSsid, isSaeSupport, true, false);
+    }
+
+
+    private void verifyDefaultApConfig(SoftApConfiguration config, String expectedSsid,
+            boolean isSaeSupport, boolean isMacRandomizationSupport, boolean isBridgedApSupport) {
         String[] splitSsid = config.getSsid().split("_");
         assertEquals(2, splitSsid.length);
         assertEquals(expectedSsid, splitSsid[0]);
@@ -182,6 +211,22 @@
             assertEquals(SECURITY_TYPE_WPA2_PSK, config.getSecurityType());
         }
         assertEquals(15, config.getPassphrase().length());
+        if (isMacRandomizationSupport) {
+            assertEquals(config.getMacRandomizationSettingInternal(),
+                    SoftApConfiguration.RANDOMIZATION_PERSISTENT);
+        } else {
+            assertEquals(config.getMacRandomizationSettingInternal(),
+                    SoftApConfiguration.RANDOMIZATION_NONE);
+        }
+        if (isBridgedApSupport) {
+            int[] defaultDualBands = new int[] {
+                    SoftApConfiguration.BAND_2GHZ,
+                    SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ};
+            assertArrayEquals(config.getBands(), defaultDualBands);
+        } else {
+            assertEquals(config.getBand(), SoftApConfiguration.BAND_2GHZ);
+            assertEquals(config.getBands().length, 1);
+        }
     }
 
     private void verifyDefaultApConfig(SoftApConfiguration config, String expectedSsid) {
@@ -191,6 +236,11 @@
 
     private void verifyDefaultLocalOnlyApConfig(SoftApConfiguration config, String expectedSsid,
             int expectedApBand, boolean isSaeSupport) {
+        verifyDefaultLocalOnlyApConfig(config, expectedSsid, expectedApBand, isSaeSupport, true);
+    }
+
+    private void verifyDefaultLocalOnlyApConfig(SoftApConfiguration config, String expectedSsid,
+            int expectedApBand, boolean isSaeSupport, boolean isMacRandomizationSupport) {
         String[] splitSsid = config.getSsid().split("_");
         assertEquals(2, splitSsid.length);
         assertEquals(expectedSsid, splitSsid[0]);
@@ -204,6 +254,13 @@
         }
         assertEquals(15, config.getPassphrase().length());
         assertFalse(config.isAutoShutdownEnabled());
+        if (isMacRandomizationSupport) {
+            assertEquals(config.getMacRandomizationSettingInternal(),
+                    SoftApConfiguration.RANDOMIZATION_PERSISTENT);
+        } else {
+            assertEquals(config.getMacRandomizationSettingInternal(),
+                    SoftApConfiguration.RANDOMIZATION_NONE);
+        }
     }
 
     private void verifyDefaultLocalOnlyApConfig(SoftApConfiguration config, String expectedSsid,
@@ -241,12 +298,14 @@
         WifiApConfigStore store = createWifiApConfigStore();
         mDataStoreSource.fromDeserialized(expectedConfig);
         verifyApConfig(expectedConfig, store.getApConfiguration());
+        assertTrue(store.getApConfiguration().isUserConfigurationInternal());
 
         store.setApConfiguration(null);
         verifyDefaultApConfig(store.getApConfiguration(), TEST_DEFAULT_AP_SSID);
         verifyDefaultApConfig(mDataStoreSource.toSerialize(), TEST_DEFAULT_AP_SSID);
         verify(mWifiConfigManager).saveToStore(true);
         verify(mBackupManagerProxy).notifyDataChanged();
+        assertTrue(store.getApConfiguration().isUserConfigurationInternal());
     }
 
     /**
@@ -258,6 +317,7 @@
         WifiApConfigStore store = createWifiApConfigStore();
 
         verifyDefaultApConfig(store.getApConfiguration(), TEST_DEFAULT_AP_SSID);
+        assertFalse(store.getApConfiguration().isUserConfigurationInternal());
         verify(mWifiConfigManager).saveToStore(true);
 
         /* Update with a valid configuration. */
@@ -273,6 +333,7 @@
         verifyApConfig(expectedConfig, mDataStoreSource.toSerialize());
         verify(mWifiConfigManager, times(2)).saveToStore(true);
         verify(mBackupManagerProxy, times(2)).notifyDataChanged();
+        assertTrue(store.getApConfiguration().isUserConfigurationInternal());
     }
 
     /**
@@ -334,7 +395,7 @@
                 "ConfiguredAP",                 /* SSID */
                 "randomKey",                    /* preshared key */
                 SECURITY_TYPE_WPA2_PSK,         /* security type */
-                SoftApConfiguration.BAND_ANY,   /* AP band */
+                mBand256G,                      /* AP band */
                 0,                              /* AP channel */
                 false                           /* Hidden SSID */);
         store.setApConfiguration(expectedConfig);
@@ -421,7 +482,7 @@
                 "ConfiguredAP",                 /* SSID */
                 "randomKey",                    /* preshared key */
                 SECURITY_TYPE_WPA2_PSK,         /* security type */
-                SoftApConfiguration.BAND_ANY,   /* AP band */
+                mBand256G,                      /* AP band */
                 0,                              /* AP channel */
                 false                           /* Hidden SSID */);
 
@@ -437,9 +498,10 @@
      */
     @Test
     public void getDefaultApConfigurationIsValid() throws Exception {
+        // Default, new feature doesn't supported
         WifiApConfigStore store = createWifiApConfigStore();
         SoftApConfiguration config = store.getApConfiguration();
-        assertTrue(WifiApConfigStore.validateApWifiConfiguration(config, true));
+        assertTrue(WifiApConfigStore.validateApWifiConfiguration(config, true, mContext));
     }
 
     /**
@@ -447,40 +509,42 @@
      * context.
      */
     @Test
-    public void generateLocalOnlyHotspotConfigIsValid() {
-        SoftApConfiguration config = WifiApConfigStore
+    public void generateLocalOnlyHotspotConfigIsValid() throws Exception {
+        WifiApConfigStore store = createWifiApConfigStore();
+        SoftApConfiguration config = store
                 .generateLocalOnlyHotspotConfig(mContext, SoftApConfiguration.BAND_2GHZ, null);
         verifyDefaultLocalOnlyApConfig(config, TEST_DEFAULT_HOTSPOT_SSID,
                 SoftApConfiguration.BAND_2GHZ);
 
         // verify that the config passes the validateApWifiConfiguration check
-        assertTrue(WifiApConfigStore.validateApWifiConfiguration(config, true));
+        assertTrue(WifiApConfigStore.validateApWifiConfiguration(config, true, mContext));
     }
 
     /**
      * Verify a proper local only hotspot config is generated for 5Ghz band.
      */
     @Test
-    public void generateLocalOnlyHotspotConfigIsValid5G() {
-        SoftApConfiguration config = WifiApConfigStore
+    public void generateLocalOnlyHotspotConfigIsValid5G() throws Exception {
+        WifiApConfigStore store = createWifiApConfigStore();
+        SoftApConfiguration config = store
                 .generateLocalOnlyHotspotConfig(mContext, SoftApConfiguration.BAND_5GHZ, null);
         verifyDefaultLocalOnlyApConfig(config, TEST_DEFAULT_HOTSPOT_SSID,
                 SoftApConfiguration.BAND_5GHZ);
 
         // verify that the config passes the validateApWifiConfiguration check
-        assertTrue(WifiApConfigStore.validateApWifiConfiguration(config, true));
+        assertTrue(WifiApConfigStore.validateApWifiConfiguration(config, true, mContext));
     }
 
     @Test
-    public void generateLohsConfig_forwardsCustomMac() {
+    public void generateLohsConfig_forwardsCustomMac() throws Exception {
+        WifiApConfigStore store = createWifiApConfigStore();
         SoftApConfiguration customConfig = new SoftApConfiguration.Builder()
-                .setBssid(MacAddress.fromString("11:22:33:44:55:66"))
+                .setBssid(TEST_SAP_BSSID_MAC)
                 .build();
-        SoftApConfiguration softApConfig = WifiApConfigStore.generateLocalOnlyHotspotConfig(
+        SoftApConfiguration softApConfig = store.generateLocalOnlyHotspotConfig(
                 mContext, SoftApConfiguration.BAND_2GHZ, customConfig);
         assertThat(softApConfig.getBssid().toString()).isNotEmpty();
-        assertThat(softApConfig.getBssid()).isEqualTo(
-                MacAddress.fromString("11:22:33:44:55:66"));
+        assertThat(softApConfig.getBssid()).isEqualTo(TEST_SAP_BSSID_MAC);
     }
 
     @Test
@@ -524,15 +588,28 @@
     public void randomizeBssid_forwardsCustomMac() throws Exception {
         mResources.setBoolean(R.bool.config_wifi_ap_mac_randomization_supported, true);
         Builder baseConfigBuilder = new SoftApConfiguration.Builder();
-        baseConfigBuilder.setBssid(MacAddress.fromString("11:22:33:44:55:66"));
+        baseConfigBuilder.setBssid(TEST_SAP_BSSID_MAC);
 
         WifiApConfigStore store = createWifiApConfigStore();
         SoftApConfiguration config = store.randomizeBssidIfUnset(mContext,
                 baseConfigBuilder.build());
 
         assertThat(config.getBssid().toString()).isNotEmpty();
-        assertThat(config.getBssid()).isEqualTo(
-                MacAddress.fromString("11:22:33:44:55:66"));
+        assertThat(config.getBssid()).isEqualTo(TEST_SAP_BSSID_MAC);
+    }
+
+    @Test
+    public void randomizeBssid__usesFactoryMacWhenRandomizationOffInConfig() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        mResources.setBoolean(R.bool.config_wifi_ap_mac_randomization_supported, true);
+        SoftApConfiguration baseConfig = new SoftApConfiguration.Builder()
+                .setMacRandomizationSetting(SoftApConfiguration.RANDOMIZATION_NONE).build();
+
+        WifiApConfigStore store = createWifiApConfigStore();
+        SoftApConfiguration config = store.randomizeBssidIfUnset(mContext, baseConfig);
+
+        assertThat(config.getBssid()).isNull();
     }
 
     /**
@@ -568,30 +645,37 @@
     public void testSsidVerificationInValidateApWifiConfigurationCheck() {
         Builder configBuilder = new SoftApConfiguration.Builder();
         configBuilder.setSsid(null);
-        assertFalse(WifiApConfigStore.validateApWifiConfiguration(configBuilder.build(), true));
+        assertFalse(WifiApConfigStore.validateApWifiConfiguration(
+                configBuilder.build(), true, mContext));
         // check a string if it's larger than 32 bytes with UTF-8 encode
         // Case 1 : one byte per character (use english words and Arabic numerals)
         configBuilder.setSsid(generateRandomString(WifiApConfigStore.SSID_MAX_LEN + 1));
-        assertFalse(WifiApConfigStore.validateApWifiConfiguration(configBuilder.build(), true));
+        assertFalse(WifiApConfigStore.validateApWifiConfiguration(
+                configBuilder.build(), true, mContext));
         // Case 2 : two bytes per character
         configBuilder.setSsid(TEST_STRING_UTF8_WITH_34_BYTES);
-        assertFalse(WifiApConfigStore.validateApWifiConfiguration(configBuilder.build(), true));
+        assertFalse(WifiApConfigStore.validateApWifiConfiguration(
+                configBuilder.build(), true, mContext));
         // Case 3 : three bytes per character
         configBuilder.setSsid(TEST_STRING_UTF8_WITH_33_BYTES);
-        assertFalse(WifiApConfigStore.validateApWifiConfiguration(configBuilder.build(), true));
+        assertFalse(WifiApConfigStore.validateApWifiConfiguration(
+                configBuilder.build(), true, mContext));
 
         // now check a valid SSID within 32 bytes
         // Case 1 :  one byte per character with random length
         int validLength = WifiApConfigStore.SSID_MAX_LEN - WifiApConfigStore.SSID_MIN_LEN;
         configBuilder.setSsid(generateRandomString(
                 mRandom.nextInt(validLength) + WifiApConfigStore.SSID_MIN_LEN));
-        assertTrue(WifiApConfigStore.validateApWifiConfiguration(configBuilder.build(), true));
+        assertTrue(WifiApConfigStore.validateApWifiConfiguration(
+                configBuilder.build(), true, mContext));
         // Case 2 : two bytes per character
         configBuilder.setSsid(TEST_STRING_UTF8_WITH_32_BYTES);
-        assertTrue(WifiApConfigStore.validateApWifiConfiguration(configBuilder.build(), true));
+        assertTrue(WifiApConfigStore.validateApWifiConfiguration(
+                configBuilder.build(), true, mContext));
         // Case 3 : three bytes per character
         configBuilder.setSsid(TEST_STRING_UTF8_WITH_30_BYTES);
-        assertTrue(WifiApConfigStore.validateApWifiConfiguration(configBuilder.build(), true));
+        assertTrue(WifiApConfigStore.validateApWifiConfiguration(
+                configBuilder.build(), true, mContext));
     }
 
     /**
@@ -606,7 +690,7 @@
         assertTrue(WifiApConfigStore.validateApWifiConfiguration(
                 new SoftApConfiguration.Builder()
                 .setSsid(TEST_DEFAULT_HOTSPOT_SSID)
-                .build(), true));
+                .build(), true, mContext));
     }
 
     /**
@@ -627,10 +711,35 @@
         configBuilder.setPassphrase(
                 generateRandomString(mRandom.nextInt(maxLen - minLen) + minLen),
                 SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
-        assertTrue(WifiApConfigStore.validateApWifiConfiguration(configBuilder.build(), true));
+        assertTrue(WifiApConfigStore.validateApWifiConfiguration(
+                configBuilder.build(), true, mContext));
     }
 
     /**
+     * Verify the WPA2_PSK network checks in validateApWifiConfiguration.
+     *
+     * If the configured network is configured with a preSharedKey, verify that the passwork is set
+     * and it meets ascii encoding when ascii encodable check enabled.
+     */
+    @Test
+    public void testWpa2PskNonAsciiPassphraseConfigInValidateApWifiConfigurationCheck() {
+        Builder configBuilder = new SoftApConfiguration.Builder();
+        configBuilder.setSsid(TEST_DEFAULT_HOTSPOT_SSID);
+        // Builder will auto check auth type and passphrase
+
+        configBuilder.setPassphrase(
+                "測試測試測試測試",
+                SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
+        assertFalse(WifiApConfigStore.validateApWifiConfiguration(
+                configBuilder.build(), true, mContext));
+        // Disable ascii encodable check
+        mResources.setBoolean(R.bool.config_wifiSoftapPassphraseAsciiEncodableCheck, false);
+        assertTrue(WifiApConfigStore.validateApWifiConfiguration(
+                configBuilder.build(), true, mContext));
+    }
+
+
+    /**
      * Verify the default configuration security when SAE support.
      */
     @Test
@@ -645,29 +754,125 @@
      * Verify the LOHS default configuration security when SAE support.
      */
     @Test
-    public void testLohsDefaultConfigurationSecurityTypeIsWpa3SaeTransitionWhenSupport() {
+    public void testLohsDefaultConfigurationSecurityTypeIsWpa3SaeTransitionWhenSupport()
+            throws Exception {
         mResources.setBoolean(R.bool.config_wifi_softap_sae_supported, true);
-        SoftApConfiguration config = WifiApConfigStore
+        WifiApConfigStore store = createWifiApConfigStore();
+        SoftApConfiguration config = store
                 .generateLocalOnlyHotspotConfig(mContext, SoftApConfiguration.BAND_5GHZ, null);
         verifyDefaultLocalOnlyApConfig(config, TEST_DEFAULT_HOTSPOT_SSID,
                 SoftApConfiguration.BAND_5GHZ, true);
 
         // verify that the config passes the validateApWifiConfiguration check
-        assertTrue(WifiApConfigStore.validateApWifiConfiguration(config, true));
+        assertTrue(WifiApConfigStore.validateApWifiConfiguration(config, true, mContext));
 
     }
 
+    /**
+     * Verify the LOHS default configuration when MAC randomization support.
+     */
+    @Test
+    public void testLohsDefaultConfigurationWhenMacRandomizationSupport() throws Exception {
+        mResources.setBoolean(R.bool.config_wifi_softap_sae_supported, true);
+        mResources.setBoolean(R.bool.config_wifi_ap_mac_randomization_supported, true);
+        WifiApConfigStore store = createWifiApConfigStore();
+        SoftApConfiguration config = store
+                .generateLocalOnlyHotspotConfig(mContext, SoftApConfiguration.BAND_5GHZ, null);
+        verifyDefaultLocalOnlyApConfig(config, TEST_DEFAULT_HOTSPOT_SSID,
+                SoftApConfiguration.BAND_5GHZ, true, true);
+
+        // verify that the config passes the validateApWifiConfiguration check
+        assertTrue(WifiApConfigStore.validateApWifiConfiguration(config, true, mContext));
+    }
+
+    /**
+     * Verify the LOHS default configuration when MAC randomization doesn't support.
+     */
+    @Test
+    public void testLohsDefaultConfigurationWhenMacRandomizationDoesntSupport()
+            throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        mResources.setBoolean(R.bool.config_wifi_softap_sae_supported, true);
+        mResources.setBoolean(R.bool.config_wifi_ap_mac_randomization_supported, false);
+        WifiApConfigStore store = createWifiApConfigStore();
+        SoftApConfiguration config = store
+                .generateLocalOnlyHotspotConfig(mContext, SoftApConfiguration.BAND_5GHZ, null);
+        verifyDefaultLocalOnlyApConfig(config, TEST_DEFAULT_HOTSPOT_SSID,
+                SoftApConfiguration.BAND_5GHZ, true, false);
+
+        // verify that the config passes the validateApWifiConfiguration check
+        assertTrue(WifiApConfigStore.validateApWifiConfiguration(config, true, mContext));
+    }
+
+    /**
+     * Verify the LOHS default configuration when MAC randomization support but it was disabled in
+     * tethered configuration.
+     */
+    @Test
+    public void testLohsDefaultConfigurationWhenMacRandomizationDisabledInTetheredCongig()
+            throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        mResources.setBoolean(R.bool.config_wifi_softap_sae_supported, true);
+        mResources.setBoolean(R.bool.config_wifi_ap_mac_randomization_supported, true);
+        WifiApConfigStore storeMacRandomizationSupported = createWifiApConfigStore();
+        SoftApConfiguration disableMacRandomizationConfig = new SoftApConfiguration
+                .Builder(storeMacRandomizationSupported.getApConfiguration())
+                .setMacRandomizationSetting(SoftApConfiguration.RANDOMIZATION_NONE).build();
+        storeMacRandomizationSupported.setApConfiguration(disableMacRandomizationConfig);
+        SoftApConfiguration config = storeMacRandomizationSupported
+                .generateLocalOnlyHotspotConfig(mContext, SoftApConfiguration.BAND_5GHZ, null);
+        verifyDefaultLocalOnlyApConfig(config, TEST_DEFAULT_HOTSPOT_SSID,
+                SoftApConfiguration.BAND_5GHZ, true, false);
+
+        // verify that the config passes the validateApWifiConfiguration check
+        assertTrue(WifiApConfigStore.validateApWifiConfiguration(config, true, mContext));
+    }
+
+
+    /**
+     * Verify the default configuration when MAC randomization support.
+     */
+    @Test
+    public void testDefaultConfigurationWhenMacRandomizationSupport()
+            throws Exception {
+        mResources.setBoolean(R.bool.config_wifi_ap_mac_randomization_supported, true);
+        WifiApConfigStore storeMacRandomizationSupported = createWifiApConfigStore();
+        verifyDefaultApConfig(storeMacRandomizationSupported.getApConfiguration(),
+                TEST_DEFAULT_AP_SSID, false, true, false);
+    }
+
+    /**
+     * Verify the default configuration when bridged AP support.
+     */
+    @Test
+    public void testDefaultConfigurationWhenBridgedSupport()
+            throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        mResources.setBoolean(R.bool.config_wifiBridgedSoftApSupported, true);
+        WifiApConfigStore storeMacRandomizationAndBridgedApSupported = createWifiApConfigStore();
+        verifyDefaultApConfig(storeMacRandomizationAndBridgedApSupported.getApConfiguration(),
+                TEST_DEFAULT_AP_SSID, false, true, true);
+    }
+
     @Test
     public void testResetToDefaultForUnsupportedConfig() throws Exception {
         mResources.setBoolean(R.bool.config_wifiSofapClientForceDisconnectSupported, true);
         mResources.setBoolean(R.bool.config_wifi_softap_sae_supported, true);
-        mResources.setBoolean(R.bool.config_wifi6ghzSupport, true);
         mResources.setBoolean(R.bool.config_wifi5ghzSupport, true);
+        mResources.setBoolean(R.bool.config_wifi6ghzSupport, true);
+        mResources.setBoolean(R.bool.config_wifi60ghzSupport, true);
+        mResources.setBoolean(R.bool.config_wifi_ap_mac_randomization_supported, true);
+        mResources.setBoolean(R.bool.config_wifiBridgedSoftApSupported, true);
 
         // Test all of the features support and all of the reset config are false.
         String testPassphrase = "secretsecret";
+        int[] testDualBands = new int[] {
+                SoftApConfiguration.BAND_2GHZ, SoftApConfiguration.BAND_5GHZ};
         SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder();
         configBuilder.setPassphrase(testPassphrase, SoftApConfiguration.SECURITY_TYPE_WPA3_SAE);
+        if (SdkLevel.isAtLeastS()) {
+            configBuilder.setBands(testDualBands);
+        }
         WifiApConfigStore store = createWifiApConfigStore();
         SoftApConfiguration resetedConfig = store.resetToDefaultForUnsupportedConfig(
                 configBuilder.build());
@@ -675,11 +880,45 @@
 
         verify(mWifiMetrics).noteSoftApConfigReset(configBuilder.build(), resetedConfig);
 
+
+        // Test band is 6Ghz but new device doesn't support 60Ghz
+        mResources.setBoolean(R.bool.config_wifi60ghzSupport, false);
+        configBuilder.setBand(SoftApConfiguration.BAND_60GHZ);
+        resetedConfig = store.resetToDefaultForUnsupportedConfig(configBuilder.build());
+        assertEquals(resetedConfig.getBand(), SoftApConfiguration.BAND_2GHZ);
+
+        // Test band is 6Ghz but new device doesn't support 6Ghz
+        mResources.setBoolean(R.bool.config_wifi6ghzSupport, false);
+        configBuilder.setBand(SoftApConfiguration.BAND_6GHZ);
+        resetedConfig = store.resetToDefaultForUnsupportedConfig(configBuilder.build());
+        assertEquals(resetedConfig.getBand(), SoftApConfiguration.BAND_2GHZ);
+
+        // Test bridged mode reset because the band is not valid.
+        if (SdkLevel.isAtLeastS()) {
+            mResources.setBoolean(R.bool.config_wifi5ghzSupport, false);
+            resetedConfig = store.resetToDefaultForUnsupportedConfig(configBuilder.build());
+            assertEquals(resetedConfig.getBand(), SoftApConfiguration.BAND_2GHZ);
+            assertEquals(resetedConfig.getBands().length, 1);
+
+            // Test bridged mode reset
+            mResources.setBoolean(R.bool.config_wifiBridgedSoftApSupported, false);
+            configBuilder.setBands(testDualBands);
+            resetedConfig = store.resetToDefaultForUnsupportedConfig(configBuilder.build());
+            assertEquals(resetedConfig.getBand(), SoftApConfiguration.BAND_2GHZ);
+            assertEquals(resetedConfig.getBands().length, 1);
+
+            // Test AP MAC randomization not support case.
+            mResources.setBoolean(R.bool.config_wifi_ap_mac_randomization_supported, false);
+            resetedConfig = store.resetToDefaultForUnsupportedConfig(configBuilder.build());
+            assertEquals(resetedConfig.getMacRandomizationSettingInternal(),
+                    SoftApConfiguration.RANDOMIZATION_NONE);
+        }
         // Test SAE not support case.
         mResources.setBoolean(R.bool.config_wifi_softap_sae_supported, false);
         resetedConfig = store.resetToDefaultForUnsupportedConfig(configBuilder.build());
         assertEquals(resetedConfig.getSecurityType(), SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
         // Test force channel
+        mResources.setBoolean(R.bool.config_wifi5ghzSupport, true);
         configBuilder.setChannel(149, SoftApConfiguration.BAND_5GHZ);
         mResources.setBoolean(
                 R.bool.config_wifiSoftapResetChannelConfig, true);
@@ -769,8 +1008,8 @@
     public void testBssidDenyIfCallerWithoutPrivileged() throws Exception {
         WifiApConfigStore store = createWifiApConfigStore();
         SoftApConfiguration config = new SoftApConfiguration.Builder(store.getApConfiguration())
-                .setBssid(MacAddress.fromString("aa:bb:cc:dd:ee:ff")).build();
-        assertFalse(WifiApConfigStore.validateApWifiConfiguration(config, false));
+                .setBssid(TEST_SAP_BSSID_MAC).build();
+        assertFalse(WifiApConfigStore.validateApWifiConfiguration(config, false, mContext));
     }
 
     /**
@@ -780,7 +1019,109 @@
     public void testBssidAllowIfCallerOwnPrivileged() throws Exception {
         WifiApConfigStore store = createWifiApConfigStore();
         SoftApConfiguration config = new SoftApConfiguration.Builder(store.getApConfiguration())
-                .setBssid(MacAddress.fromString("aa:bb:cc:dd:ee:ff")).build();
-        assertTrue(WifiApConfigStore.validateApWifiConfiguration(config, true));
+                .setBssid(TEST_SAP_BSSID_MAC).build();
+        assertTrue(WifiApConfigStore.validateApWifiConfiguration(config, true, mContext));
+    }
+
+    @Test
+    public void testUnSupportedBandConfigurd() throws Exception {
+        WifiApConfigStore store = createWifiApConfigStore();
+        SoftApConfiguration config = new SoftApConfiguration.Builder(store.getApConfiguration())
+                .setBand(mBand25660G).build();
+        // Default is all bands supported.
+        assertTrue(WifiApConfigStore.validateApWifiConfiguration(config, true, mContext));
+
+        mResources.setBoolean(R.bool.config_wifi24ghzSupport, false);
+        assertFalse(WifiApConfigStore.validateApWifiConfiguration(config, true, mContext));
+
+        setupAllBandsSupported();
+        mResources.setBoolean(R.bool.config_wifiSoftap24ghzSupported, false);
+        assertFalse(WifiApConfigStore.validateApWifiConfiguration(config, true, mContext));
+
+        setupAllBandsSupported();
+        mResources.setBoolean(R.bool.config_wifi5ghzSupport, false);
+        assertFalse(WifiApConfigStore.validateApWifiConfiguration(config, true, mContext));
+
+        setupAllBandsSupported();
+        mResources.setBoolean(R.bool.config_wifiSoftap5ghzSupported, false);
+        assertFalse(WifiApConfigStore.validateApWifiConfiguration(config, true, mContext));
+
+        setupAllBandsSupported();
+        mResources.setBoolean(R.bool.config_wifi6ghzSupport, false);
+        assertFalse(WifiApConfigStore.validateApWifiConfiguration(config, true, mContext));
+
+        setupAllBandsSupported();
+        mResources.setBoolean(R.bool.config_wifiSoftap6ghzSupported, false);
+        assertFalse(WifiApConfigStore.validateApWifiConfiguration(config, true, mContext));
+
+        setupAllBandsSupported();
+        mResources.setBoolean(R.bool.config_wifi60ghzSupport, false);
+        assertFalse(WifiApConfigStore.validateApWifiConfiguration(config, true, mContext));
+
+        setupAllBandsSupported();
+        mResources.setBoolean(R.bool.config_wifiSoftap60ghzSupported, false);
+        assertFalse(WifiApConfigStore.validateApWifiConfiguration(config, true, mContext));
+    }
+
+    @Test
+    public void testSanitizePersistentApConfig() throws Exception {
+        WifiApConfigStore store = createWifiApConfigStore();
+        SoftApConfiguration config5Gonly = setupApConfig(
+                "ConfiguredAP",                   /* SSID */
+                "randomKey",                      /* preshared key */
+                SECURITY_TYPE_WPA2_PSK,           /* security type */
+                SoftApConfiguration.BAND_5GHZ,    /* AP band */
+                0,                                /* AP channel */
+                true                              /* Hidden SSID */);
+
+        SoftApConfiguration expectedConfig = new SoftApConfiguration.Builder(config5Gonly)
+                .setBand(SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ)
+                .build();
+        store.setApConfiguration(config5Gonly);
+        verifyApConfig(expectedConfig, store.getApConfiguration());
+
+        if (SdkLevel.isAtLeastS()) {
+            SoftApConfiguration bridgedConfig2GAnd5G = new SoftApConfiguration.Builder(config5Gonly)
+                    .setBands(new int[] {SoftApConfiguration.BAND_2GHZ,
+                            SoftApConfiguration.BAND_5GHZ})
+                    .build();
+
+            SoftApConfiguration expectedBridgedConfig = new SoftApConfiguration
+                    .Builder(bridgedConfig2GAnd5G)
+                    .setBands(new int[] {SoftApConfiguration.BAND_2GHZ,
+                            SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ})
+                    .build();
+        }
+    }
+
+    @Test
+    public void testForceApBaneChannel() throws Exception {
+        int testBand = SoftApConfiguration.BAND_5GHZ; // Not default
+        int testChannal = 149;
+        WifiApConfigStore store = createWifiApConfigStore();
+        verifyDefaultApConfig(store.getApConfiguration(), TEST_DEFAULT_AP_SSID);
+        verify(mWifiConfigManager).saveToStore(true);
+
+        // Test to enable forced AP band
+        store.enableForceSoftApBandOrChannel(testBand, 0);
+        SoftApConfiguration expectedConfig = store.getApConfiguration();
+        assertEquals(expectedConfig.getBand(), testBand);
+        assertEquals(expectedConfig.getChannel(), 0);
+        // Disable forced AP band
+        store.disableForceSoftApBandOrChannel();
+        expectedConfig = store.getApConfiguration();
+        assertEquals(expectedConfig.getBand(), SoftApConfiguration.BAND_2GHZ);
+        assertEquals(expectedConfig.getChannel(), 0);
+
+        // Test to enable forced AP band
+        store.enableForceSoftApBandOrChannel(testBand, testChannal);
+        expectedConfig = store.getApConfiguration();
+        assertEquals(expectedConfig.getBand(), testBand);
+        assertEquals(expectedConfig.getChannel(), testChannal);
+        // Disable forced AP band
+        store.disableForceSoftApBandOrChannel();
+        expectedConfig = store.getApConfiguration();
+        assertEquals(expectedConfig.getBand(), SoftApConfiguration.BAND_2GHZ);
+        assertEquals(expectedConfig.getChannel(), 0);
     }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiBackupRestoreTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiBackupRestoreTest.java
index e640c42..112abca 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiBackupRestoreTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiBackupRestoreTest.java
@@ -112,9 +112,9 @@
                     // Valid Value: 01
                     + "<byte-array name=\"AllowedAuthAlgos\" num=\"1\">11</byte-array>\n"
                     // Valid Value: 0f
-                    + "<byte-array name=\"AllowedGroupCiphers\" num=\"1\">8f</byte-array>\n"
+                    + "<byte-array name=\"AllowedGroupCiphers\" num=\"2\">0f01</byte-array>\n"
                     // Valid Value: 06
-                    + "<byte-array name=\"AllowedPairwiseCiphers\" num=\"1\">26</byte-array>\n"
+                    + "<byte-array name=\"AllowedPairwiseCiphers\" num=\"1\">86</byte-array>\n"
                     + "<boolean name=\"Shared\" value=\"true\" />\n"
                     + "<null name=\"SimSlot\" />\n"
                     + "</WifiConfiguration>\n"
@@ -226,6 +226,51 @@
                     + "</NetworkList>\n"
                     + "</WifiBackupData>\n";
 
+    private static final String WIFI_BACKUP_DATA_V1_3 =
+            "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                    + "<WifiBackupData>\n"
+                    + "<float name=\"Version\" value=\"1.3\" />\n"
+                    + "<NetworkList>\n"
+                    + "<Network>\n"
+                    + "<WifiConfiguration>\n"
+                    + "<string name=\"ConfigKey\">&quot;" + WifiConfigurationTestUtil.TEST_SSID
+                        + "&quot;WPA_PSK</string>\n"
+                    + "<string name=\"SSID\">&quot;" + WifiConfigurationTestUtil.TEST_SSID
+                        + "&quot;</string>\n"
+                    + "<null name=\"PreSharedKey\" />\n"
+                    + "<null name=\"WEPKeys\" />\n"
+                    + "<int name=\"WEPTxKeyIndex\" value=\"0\" />\n"
+                    + "<boolean name=\"HiddenSSID\" value=\"false\" />\n"
+                    + "<boolean name=\"RequirePMF\" value=\"false\" />\n"
+                    + "<byte-array name=\"AllowedKeyMgmt\" num=\"1\">02</byte-array>\n"
+                    + "<byte-array name=\"AllowedProtocols\" num=\"1\">03</byte-array>\n"
+                    + "<byte-array name=\"AllowedAuthAlgos\" num=\"0\"></byte-array>\n"
+                    + "<byte-array name=\"AllowedGroupCiphers\" num=\"1\">0f</byte-array>\n"
+                    + "<byte-array name=\"AllowedPairwiseCiphers\" num=\"1\">06</byte-array>\n"
+                    + "<byte-array name=\"AllowedGroupMgmtCiphers\" num=\"0\"></byte-array>\n"
+                    + "<byte-array name=\"AllowedSuiteBCiphers\" num=\"0\"></byte-array>\n"
+                    + "<boolean name=\"Shared\" value=\"true\" />\n"
+                    + "<boolean name=\"AutoJoinEnabled\" value=\"false\" />\n"
+                    + "<int name=\"DeletionPriority\" value=\"0\" />\n"
+                    + "<int name=\"NumRebootsSinceLastUse\" value=\"0\" />\n"
+                    + "<SecurityParamsList>\n"
+                    + "<SecurityParams>\n"
+                    + "<int name=\"SecurityType\" value=\"2\" />\n"
+                    + "<boolean name=\"SaeIsH2eOnlyMode\" value=\"false\" />\n"
+                    + "<boolean name=\"SaeIsPkOnlyMode\" value=\"false\" />\n"
+                    + "<boolean name=\"IsAddedByAutoUpgrade\" value=\"false\" />\n"
+                    + "</SecurityParams>\n"
+                    + "</SecurityParamsList>\n"
+                    + "<int name=\"MeteredOverride\" value=\"1\" />\n"
+                    + "</WifiConfiguration>\n"
+                    + "<IpConfiguration>\n"
+                    + "<string name=\"IpAssignment\">DHCP</string>\n"
+                    + "<string name=\"ProxySettings\">NONE</string>\n"
+                    + "</IpConfiguration>\n"
+                    + "</Network>\n"
+                    + "</NetworkList>\n"
+                    + "</WifiBackupData>\n";
+
     @Mock WifiPermissionsUtil mWifiPermissionsUtil;
     private WifiBackupRestore mWifiBackupRestore;
     private boolean mCheckDump = true;
@@ -1028,11 +1073,27 @@
                 mWifiBackupRestore.retrieveConfigurationsFromBackupData(backupData);
         WifiConfigurationTestUtil.assertConfigurationsEqualForBackup(
                 configurations, retrievedConfigurations);
+    }
+
+    /**
+     * Verify that restoring of configuration from a 1.3 version backup data.
+     */
+    @Test
+    public void testRestoreFromV1_3BackupData() {
+        List<WifiConfiguration> configurations = new ArrayList<>();
+        configurations.add(createNetworkForConfigurationWithV1_3Data());
+
+        byte[] backupData = WIFI_BACKUP_DATA_V1_3.getBytes();
+        List<WifiConfiguration> retrievedConfigurations =
+                mWifiBackupRestore.retrieveConfigurationsFromBackupData(backupData);
+
+        WifiConfigurationTestUtil.assertConfigurationsEqualForBackup(
+                configurations, retrievedConfigurations);
 
         // Also, assert in the reverse direction to ensure the serialization logic matches.
         // Note: This will stop working when we bump up the version. Then we'll need to copy
         // the below assert to the test for the latest version.
-        assertEquals(WIFI_BACKUP_DATA_V1_2,
+        assertEquals(WIFI_BACKUP_DATA_V1_3,
                 new String(mWifiBackupRestore.retrieveBackupDataFromConfigurations(
                         retrievedConfigurations)));
     }
@@ -1088,6 +1149,17 @@
     }
 
     /**
+     * Creates correct WiFiConfiguration that should be parsed out of
+     * {@link #WIFI_BACKUP_DATA_V1_3} configuration which contains 1.3 version backup.
+     */
+    private static WifiConfiguration createNetworkForConfigurationWithV1_3Data() {
+        final WifiConfiguration config = createNetworkForConfigurationWithV1_2Data();
+        config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
+        config.allowAutojoin = false;
+        return config;
+    }
+
+    /**
      * Helper method to write a list of networks in wpa_supplicant.conf format to the output stream.
      */
     private byte[] createWpaSupplicantConfBackupData(List<WifiConfiguration> configurations) {
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiBlocklistMonitorTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiBlocklistMonitorTest.java
new file mode 100644
index 0000000..9245aa3
--- /dev/null
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiBlocklistMonitorTest.java
@@ -0,0 +1,1244 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import android.content.Context;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
+import android.net.wifi.WifiConfiguration.NetworkSelectionStatus.DisableReasonInfo;
+import android.util.LocalLog;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wifi.resources.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.WifiBlocklistMonitor}.
+ */
+@SmallTest
+public class WifiBlocklistMonitorTest {
+    private static final int TEST_NUM_MAX_FIRMWARE_SUPPORT_BSSIDS = 3;
+    private static final int TEST_NUM_MAX_FIRMWARE_SUPPORT_SSIDS = 3;
+    private static final String TEST_SSID_1 = "TestSSID1";
+    private static final String TEST_SSID_2 = "TestSSID2";
+    private static final String TEST_SSID_3 = "TestSSID3";
+    private static final String TEST_BSSID_1 = "0a:08:5c:67:89:00";
+    private static final String TEST_BSSID_2 = "0a:08:5c:67:89:01";
+    private static final String TEST_BSSID_3 = "0a:08:5c:67:89:02";
+    private static final long TEST_ELAPSED_UPDATE_NETWORK_SELECTION_TIME_MILLIS = 29457631;
+    private static final int TEST_GOOD_RSSI = -50;
+    private static final int TEST_SUFFICIENT_RSSI = -67;
+    private static final int MIN_RSSI_DIFF_TO_UNBLOCK_BSSID = 5;
+    private static final int TEST_FRAMEWORK_BLOCK_REASON =
+            WifiBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_MBO_OCE;
+    private static final int TEST_L2_FAILURE = WifiBlocklistMonitor.REASON_ASSOCIATION_REJECTION;
+    private static final int TEST_DHCP_FAILURE = WifiBlocklistMonitor.REASON_DHCP_FAILURE;
+    private static final long BASE_BLOCKLIST_DURATION = TimeUnit.MINUTES.toMillis(5); // 5 minutes
+    private static final long BASE_CONNECTED_SCORE_BLOCKLIST_DURATION =
+            TimeUnit.SECONDS.toMillis(30);
+    private static final long ABNORMAL_DISCONNECT_TIME_WINDOW_MS = TimeUnit.SECONDS.toMillis(30);
+    private static final long ABNORMAL_DISCONNECT_RESET_TIME_MS = TimeUnit.HOURS.toMillis(3);
+    private static final int FAILURE_STREAK_CAP = 7;
+    private static final Map<Integer, Integer> BLOCK_REASON_TO_DISABLE_THRESHOLD_MAP =
+            Map.ofEntries(
+                    Map.entry(WifiBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA, 1),
+                    Map.entry(WifiBlocklistMonitor.REASON_NETWORK_VALIDATION_FAILURE, 1),
+                    Map.entry(WifiBlocklistMonitor.REASON_WRONG_PASSWORD, 1),
+                    Map.entry(WifiBlocklistMonitor.REASON_EAP_FAILURE, 1),
+                    Map.entry(WifiBlocklistMonitor.REASON_ASSOCIATION_REJECTION, 3),
+                    Map.entry(WifiBlocklistMonitor.REASON_ASSOCIATION_TIMEOUT, 3),
+                    Map.entry(WifiBlocklistMonitor.REASON_AUTHENTICATION_FAILURE, 3),
+                    Map.entry(WifiBlocklistMonitor.REASON_DHCP_FAILURE, 3),
+                    Map.entry(WifiBlocklistMonitor.REASON_ABNORMAL_DISCONNECT, 3),
+                    Map.entry(WifiBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_MBO_OCE, 1),
+                    Map.entry(WifiBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT, 1),
+                    Map.entry(WifiBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE, 1),
+                    Map.entry(WifiBlocklistMonitor.REASON_NONLOCAL_DISCONNECT_CONNECTING, 2)
+            );
+    private static final int NUM_FAILURES_TO_BLOCKLIST =
+            BLOCK_REASON_TO_DISABLE_THRESHOLD_MAP.get(TEST_L2_FAILURE);
+
+    @Mock private Context mContext;
+    @Mock private WifiConnectivityHelper mWifiConnectivityHelper;
+    @Mock private WifiLastResortWatchdog mWifiLastResortWatchdog;
+    @Mock private Clock mClock;
+    @Mock private LocalLog mLocalLog;
+    @Mock private WifiScoreCard mWifiScoreCard;
+    @Mock private ScoringParams mScoringParams;
+    @Mock private WifiMetrics mWifiMetrics;
+    @Mock private WifiScoreCard.PerNetwork mPerNetwork;
+    @Mock private WifiScoreCard.NetworkConnectionStats mRecentStats;
+
+    private MockResources mResources;
+    private WifiBlocklistMonitor mWifiBlocklistMonitor;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+
+        when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
+        when(mWifiConnectivityHelper.getMaxNumBlocklistBssid())
+                .thenReturn(TEST_NUM_MAX_FIRMWARE_SUPPORT_BSSIDS);
+        when(mWifiConnectivityHelper.getMaxNumAllowlistSsid())
+                .thenReturn(TEST_NUM_MAX_FIRMWARE_SUPPORT_SSIDS);
+        when(mScoringParams.getSufficientRssi(anyInt())).thenReturn(TEST_SUFFICIENT_RSSI);
+        mResources = new MockResources();
+        mResources.setInteger(R.integer.config_wifiBssidBlocklistMonitorBaseBlockDurationMs,
+                (int) BASE_BLOCKLIST_DURATION);
+        mResources.setInteger(
+                R.integer.config_wifiBssidBlocklistMonitorConnectedScoreBaseBlockDurationMs,
+                (int) BASE_CONNECTED_SCORE_BLOCKLIST_DURATION);
+        mResources.setInteger(R.integer.config_wifiBssidBlocklistMonitorFailureStreakCap,
+                FAILURE_STREAK_CAP);
+        mResources.setInteger(R.integer.config_wifiBssidBlocklistAbnormalDisconnectTimeWindowMs,
+                (int) ABNORMAL_DISCONNECT_TIME_WINDOW_MS);
+        mResources.setInteger(
+                R.integer.config_wifiBssidBlocklistMonitorApUnableToHandleNewStaThreshold,
+                BLOCK_REASON_TO_DISABLE_THRESHOLD_MAP.get(
+                        WifiBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA));
+        mResources.setInteger(
+                R.integer.config_wifiBssidBlocklistMonitorNetworkValidationFailureThreshold,
+                BLOCK_REASON_TO_DISABLE_THRESHOLD_MAP.get(
+                        WifiBlocklistMonitor.REASON_NETWORK_VALIDATION_FAILURE));
+        mResources.setInteger(R.integer.config_wifiBssidBlocklistMonitorWrongPasswordThreshold,
+                BLOCK_REASON_TO_DISABLE_THRESHOLD_MAP.get(
+                        WifiBlocklistMonitor.REASON_WRONG_PASSWORD));
+        mResources.setInteger(R.integer.config_wifiBssidBlocklistMonitorEapFailureThreshold,
+                BLOCK_REASON_TO_DISABLE_THRESHOLD_MAP.get(
+                        WifiBlocklistMonitor.REASON_EAP_FAILURE));
+        mResources.setInteger(
+                R.integer.config_wifiBssidBlocklistMonitorAssociationRejectionThreshold,
+                BLOCK_REASON_TO_DISABLE_THRESHOLD_MAP.get(
+                        WifiBlocklistMonitor.REASON_ASSOCIATION_REJECTION));
+        mResources.setInteger(
+                R.integer.config_wifiBssidBlocklistMonitorAssociationTimeoutThreshold,
+                BLOCK_REASON_TO_DISABLE_THRESHOLD_MAP.get(
+                        WifiBlocklistMonitor.REASON_ASSOCIATION_TIMEOUT));
+        mResources.setInteger(
+                R.integer.config_wifiBssidBlocklistMonitorAuthenticationFailureThreshold,
+                BLOCK_REASON_TO_DISABLE_THRESHOLD_MAP.get(
+                        WifiBlocklistMonitor.REASON_AUTHENTICATION_FAILURE));
+        mResources.setInteger(R.integer.config_wifiBssidBlocklistMonitorDhcpFailureThreshold,
+                BLOCK_REASON_TO_DISABLE_THRESHOLD_MAP.get(
+                        WifiBlocklistMonitor.REASON_DHCP_FAILURE));
+        mResources.setInteger(
+                R.integer.config_wifiBssidBlocklistMonitorAbnormalDisconnectThreshold,
+                BLOCK_REASON_TO_DISABLE_THRESHOLD_MAP.get(
+                        WifiBlocklistMonitor.REASON_ABNORMAL_DISCONNECT));
+        mResources.setInteger(
+                R.integer.config_wifiBssidBlocklistMonitorNonlocalDisconnectConnectingThreshold,
+                BLOCK_REASON_TO_DISABLE_THRESHOLD_MAP.get(
+                        WifiBlocklistMonitor.REASON_NONLOCAL_DISCONNECT_CONNECTING));
+        mResources.setInteger(
+                R.integer.config_wifiDisableReasonAssociationRejectionThreshold,
+                NetworkSelectionStatus.DISABLE_REASON_INFOS
+                        .get(NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION)
+                        .mDisableThreshold);
+        mResources.setInteger(
+                R.integer.config_wifiDisableReasonAuthenticationFailureThreshold,
+                NetworkSelectionStatus.DISABLE_REASON_INFOS
+                        .get(NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE)
+                        .mDisableThreshold);
+        mResources.setInteger(
+                R.integer.config_wifiDisableReasonDhcpFailureThreshold,
+                NetworkSelectionStatus.DISABLE_REASON_INFOS
+                        .get(NetworkSelectionStatus.DISABLED_DHCP_FAILURE).mDisableThreshold);
+        mResources.setInteger(
+                R.integer.config_wifiDisableReasonNetworkNotFoundThreshold,
+                NetworkSelectionStatus.DISABLE_REASON_INFOS
+                        .get(NetworkSelectionStatus.DISABLED_NETWORK_NOT_FOUND).mDisableThreshold);
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mPerNetwork.getRecentStats()).thenReturn(mRecentStats);
+        when(mWifiScoreCard.lookupNetwork(anyString())).thenReturn(mPerNetwork);
+        mWifiBlocklistMonitor = new WifiBlocklistMonitor(mContext, mWifiConnectivityHelper,
+                mWifiLastResortWatchdog, mClock, mLocalLog, mWifiScoreCard, mScoringParams,
+                mWifiMetrics);
+    }
+
+    private void verifyAddTestBssidToBlocklist() {
+        mWifiBlocklistMonitor.handleBssidConnectionFailure(
+                TEST_BSSID_1, TEST_SSID_1,
+                WifiBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA, TEST_GOOD_RSSI);
+        assertTrue(mWifiBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
+    }
+
+    // Verify adding 2 BSSID for SSID_1 and 1 BSSID for SSID_2 to the blocklist.
+    private void verifyAddMultipleBssidsToBlocklist() {
+        when(mClock.getWallClockMillis()).thenReturn(0L);
+        mWifiBlocklistMonitor.handleBssidConnectionFailure(TEST_BSSID_1,
+                TEST_SSID_1, WifiBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA,
+                TEST_GOOD_RSSI);
+        when(mClock.getWallClockMillis()).thenReturn(1L);
+        mWifiBlocklistMonitor.handleBssidConnectionFailure(TEST_BSSID_2,
+                TEST_SSID_1, WifiBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA,
+                TEST_GOOD_RSSI);
+        mWifiBlocklistMonitor.handleBssidConnectionFailure(TEST_BSSID_3,
+                TEST_SSID_2, WifiBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA,
+                TEST_GOOD_RSSI);
+
+        // Verify that we have 3 BSSIDs in the blocklist.
+        Set<String> bssidList = mWifiBlocklistMonitor.updateAndGetBssidBlocklist();
+        assertEquals(3, bssidList.size());
+        assertTrue(bssidList.contains(TEST_BSSID_1));
+        assertTrue(bssidList.contains(TEST_BSSID_2));
+        assertTrue(bssidList.contains(TEST_BSSID_3));
+    }
+
+    private void handleBssidConnectionFailureMultipleTimes(String bssid, int reason, int times) {
+        handleBssidConnectionFailureMultipleTimes(bssid, TEST_SSID_1, reason, times);
+    }
+
+    private void handleBssidConnectionFailureMultipleTimes(String bssid, String ssid, int reason,
+            int times) {
+        for (int i = 0; i < times; i++) {
+            mWifiBlocklistMonitor.handleBssidConnectionFailure(bssid, ssid, reason,
+                    TEST_GOOD_RSSI);
+        }
+    }
+
+    /**
+     * Verify updateAndGetNumBlockedBssidsForSsid returns the correct number of blocked BSSIDs.
+     */
+    @Test
+    public void testUpdateAndGetNumBlockedBssidsForSsid() {
+        verifyAddMultipleBssidsToBlocklist();
+        assertEquals(2, mWifiBlocklistMonitor.updateAndGetNumBlockedBssidsForSsid(TEST_SSID_1));
+        assertEquals(1, mWifiBlocklistMonitor.updateAndGetNumBlockedBssidsForSsid(TEST_SSID_2));
+    }
+
+    /**
+     * Verify that updateAndGetBssidBlocklist removes expired blocklist entries and clears
+     * all failure counters for those networks.
+     */
+    @Test
+    public void testBssidIsRemovedFromBlocklistAfterTimeout() {
+        verifyAddTestBssidToBlocklist();
+        // Verify TEST_BSSID_1 is not removed from the blocklist until sufficient time have passed.
+        when(mClock.getWallClockMillis()).thenReturn(BASE_BLOCKLIST_DURATION);
+        assertTrue(mWifiBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
+
+        // Verify that TEST_BSSID_1 is removed from the blocklist after the timeout duration.
+        // By default there is no blocklist streak so the timeout duration is simply
+        // BASE_BLOCKLIST_DURATION
+        when(mClock.getWallClockMillis()).thenReturn(BASE_BLOCKLIST_DURATION + 1);
+        assertEquals(0, mWifiBlocklistMonitor.updateAndGetBssidBlocklist().size());
+    }
+
+    /**
+     * Verify that updateAndGetBssidBlocklist(ssid) updates firmware roaming configuration
+     * if a BSSID that belongs to the ssid is removed from blocklist.
+     */
+    @Test
+    public void testBssidRemovalUpdatesFirmwareConfiguration() {
+        verifyAddTestBssidToBlocklist();
+        when(mClock.getWallClockMillis()).thenReturn(BASE_BLOCKLIST_DURATION + 1);
+        assertEquals(0, mWifiBlocklistMonitor
+                .updateAndGetBssidBlocklistForSsids(Set.of(TEST_SSID_1)).size());
+        verify(mWifiConnectivityHelper).setFirmwareRoamingConfiguration(eq(new ArrayList<>()),
+                eq(new ArrayList<>()));
+    }
+
+    /**
+     * Verify that updateAndGetBssidBlocklist(ssid) does not update firmware roaming configuration
+     * if there are no BSSIDs belonging to the ssid removed from blocklist.
+     */
+    @Test
+    public void testBssidRemovalNotUpdateFirmwareConfiguration() {
+        verifyAddTestBssidToBlocklist();
+        when(mClock.getWallClockMillis()).thenReturn(BASE_BLOCKLIST_DURATION + 1);
+        assertEquals(0, mWifiBlocklistMonitor
+                .updateAndGetBssidBlocklistForSsids(Set.of(TEST_SSID_2)).size());
+        verify(mWifiConnectivityHelper, never()).setFirmwareRoamingConfiguration(
+                eq(new ArrayList<>()), eq(new ArrayList<>()));
+    }
+
+    /**
+     * Verify that if REASON_AUTHENTICATION_FAILURE happens on the only BSSID of a SSID, the BSSID
+     * will not get blocked.
+     */
+    @Test
+    public void testIgnoreIfOnlyBssid() {
+        // setup TEST_BSSID_1 to be the only BSSID for its SSID
+        when(mWifiLastResortWatchdog.isBssidOnlyApOfSsid(TEST_BSSID_1)).thenReturn(true);
+        when(mWifiLastResortWatchdog.isBssidOnlyApOfSsid(TEST_BSSID_2)).thenReturn(false);
+
+        // Simulate both BSSIDs failing
+        int failureReason = WifiBlocklistMonitor.REASON_AUTHENTICATION_FAILURE;
+        int threshold = BLOCK_REASON_TO_DISABLE_THRESHOLD_MAP.get(failureReason);
+        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_SSID_1,
+                failureReason, threshold);
+        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_2, TEST_SSID_2,
+                failureReason, threshold);
+
+        // Verify that only TEST_BSSID_2 is added to the blocklist.
+        assertEquals(1, mWifiBlocklistMonitor.updateAndGetBssidBlocklist().size());
+        assertTrue(mWifiBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_2));
+    }
+
+    /**
+     * Verify that for REASON_AP_UNABLE_TO_HANDLE_NEW_STA, the BSSID will get blocked even if it's
+     * the only BSSID for its SSID.
+     */
+    @Test
+    public void testIgnoreIfOnlyBssidNotApplicableForSomeFailures() {
+        // setup TEST_BSSID_1 to be the only BSSID for its SSID
+        when(mWifiLastResortWatchdog.isBssidOnlyApOfSsid(TEST_BSSID_1)).thenReturn(true);
+
+        // Simulate TEST_BSSID_1 failing
+        int failureReason = WifiBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA;
+        int threshold = BLOCK_REASON_TO_DISABLE_THRESHOLD_MAP.get(failureReason);
+        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_SSID_1,
+                failureReason, threshold);
+
+        // Verify verify that TEST_BSSID_1 is in the blocklist
+        assertEquals(1, mWifiBlocklistMonitor.updateAndGetBssidBlocklist().size());
+        assertTrue(mWifiBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
+    }
+
+    /**
+     * Verify that if a failure happens on a disabled WifiConfiguration, the failure will not get
+     * ignored even if it's the only BSSID remaining.
+     */
+    @Test
+    public void testFailuresOnDisabledConfigsGetBlocked() {
+        // setup both TEST_BSSID_1 and TEST_BSSID_2 to be the only BSSID for its SSID
+        when(mWifiLastResortWatchdog.isBssidOnlyApOfSsid(TEST_BSSID_1)).thenReturn(true);
+        when(mWifiLastResortWatchdog.isBssidOnlyApOfSsid(TEST_BSSID_2)).thenReturn(true);
+
+        // setup TEST_SSID_2 to be disabled
+        mWifiBlocklistMonitor.handleWifiConfigurationDisabled(TEST_SSID_2);
+
+        // Simulate both BSSIDs failing
+        int failureReason = WifiBlocklistMonitor.REASON_AUTHENTICATION_FAILURE;
+        int threshold = BLOCK_REASON_TO_DISABLE_THRESHOLD_MAP.get(failureReason);
+        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_SSID_1,
+                failureReason, threshold);
+        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_2, TEST_SSID_2,
+                failureReason, threshold);
+
+        // Verify that only TEST_BSSID_2 is added to the blocklist.
+        assertEquals(1, mWifiBlocklistMonitor.updateAndGetBssidBlocklist().size());
+        assertTrue(mWifiBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_2));
+
+        // Now simulate having a connection success on TEST_BSSID_2 and verify there are
+        // no more blocked BSSIDs, and connection failure should be ignored on TEST_BSSID_2 again.
+        mWifiBlocklistMonitor.handleBssidConnectionSuccess(TEST_BSSID_2, TEST_SSID_2);
+        mWifiBlocklistMonitor.clearBssidBlocklistForSsid(TEST_SSID_2);
+        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_2, TEST_SSID_2,
+                failureReason, threshold);
+        assertEquals(0, mWifiBlocklistMonitor.updateAndGetBssidBlocklist().size());
+    }
+
+    /**
+     * Verify that when adding a AP that had already been failing (therefore has a blocklist
+     * streak), we are setting the blocklist duration using an exponential backoff technique.
+     */
+    @Test
+    public void testBssidIsRemoveFromBlocklistAfterTimoutExponentialBackoff() {
+        verifyAddTestBssidToBlocklist();
+        int multiplier = 2;
+        long duration = 0;
+        for (int i = 1; i <= FAILURE_STREAK_CAP; i++) {
+            when(mWifiScoreCard.getBssidBlocklistStreak(anyString(), anyString(), anyInt()))
+                    .thenReturn(i);
+            when(mClock.getWallClockMillis()).thenReturn(0L);
+            verifyAddTestBssidToBlocklist();
+
+            // calculate the expected blocklist duration then verify that timeout happens
+            // exactly after the duration.
+            duration = multiplier * BASE_BLOCKLIST_DURATION;
+            when(mClock.getWallClockMillis()).thenReturn(duration);
+            assertTrue(mWifiBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
+            when(mClock.getWallClockMillis()).thenReturn(duration + 1);
+            assertEquals(0, mWifiBlocklistMonitor.updateAndGetBssidBlocklist().size());
+
+            multiplier *= 2;
+        }
+
+        // finally verify that the timout is capped by the FAILURE_STREAK_CAP
+        when(mWifiScoreCard.getBssidBlocklistStreak(anyString(), anyString(), anyInt()))
+                .thenReturn(FAILURE_STREAK_CAP + 1);
+        when(mClock.getWallClockMillis()).thenReturn(0L);
+        verifyAddTestBssidToBlocklist();
+        when(mClock.getWallClockMillis()).thenReturn(duration);
+        assertTrue(mWifiBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
+        when(mClock.getWallClockMillis()).thenReturn(duration + 1);
+        assertEquals(0, mWifiBlocklistMonitor.updateAndGetBssidBlocklist().size());
+    }
+
+    /**
+     * Verify that consecutive failures will add a BSSID to blocklist.
+     */
+    @Test
+    public void testRepeatedConnectionFailuresAddToBlocklist() {
+        // First verify that n-1 failrues does not add the BSSID to blocklist
+        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_L2_FAILURE,
+                NUM_FAILURES_TO_BLOCKLIST - 1);
+        assertEquals(0, mWifiBlocklistMonitor.updateAndGetBssidBlocklist().size());
+
+        // Simulate a long time passing to make sure failure counters are not being cleared through
+        // some time based check
+        when(mClock.getWallClockMillis()).thenReturn(10 * BASE_BLOCKLIST_DURATION);
+        assertEquals(0, mWifiBlocklistMonitor.updateAndGetBssidBlocklist().size());
+
+        // Verify that 1 more failure will add the BSSID to blocklist.
+        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_L2_FAILURE, 1);
+        assertTrue(mWifiBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
+    }
+
+    /**
+     * Verify that only abnormal disconnects that happened in a window of time right after
+     * connection gets counted in the WifiBlocklistMonitor.
+     */
+    @Test
+    public void testAbnormalDisconnectRecencyCheck() {
+        // does some setup so that 1 failure is enough to add the BSSID to blocklist.
+        when(mWifiScoreCard.getBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1,
+                WifiBlocklistMonitor.REASON_ABNORMAL_DISCONNECT)).thenReturn(1);
+
+        // simulate an abnormal disconnect coming in after the allowed window of time
+        when(mWifiScoreCard.getBssidConnectionTimestampMs(TEST_SSID_1, TEST_BSSID_1))
+                .thenReturn(0L);
+        when(mClock.getWallClockMillis()).thenReturn(ABNORMAL_DISCONNECT_TIME_WINDOW_MS + 1);
+        assertFalse(mWifiBlocklistMonitor.handleBssidConnectionFailure(TEST_BSSID_1, TEST_SSID_1,
+                WifiBlocklistMonitor.REASON_ABNORMAL_DISCONNECT, TEST_GOOD_RSSI));
+        verify(mWifiScoreCard, never()).incrementBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1,
+                WifiBlocklistMonitor.REASON_ABNORMAL_DISCONNECT);
+
+        // simulate another abnormal disconnect within the time window and verify the BSSID is
+        // added to blocklist.
+        when(mClock.getWallClockMillis()).thenReturn(ABNORMAL_DISCONNECT_TIME_WINDOW_MS);
+        assertTrue(mWifiBlocklistMonitor.handleBssidConnectionFailure(TEST_BSSID_1, TEST_SSID_1,
+                WifiBlocklistMonitor.REASON_ABNORMAL_DISCONNECT, TEST_GOOD_RSSI));
+        verify(mWifiScoreCard).incrementBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1,
+                WifiBlocklistMonitor.REASON_ABNORMAL_DISCONNECT);
+    }
+
+    /**
+     * Verify that when the BSSID blocklist streak is greater or equal to 1, then we block a
+     * BSSID on a single failure regardless of failure type.
+     */
+    @Test
+    public void testBlocklistStreakExpeditesAddingToBlocklist() {
+        when(mWifiScoreCard.getBssidBlocklistStreak(anyString(), anyString(), anyInt()))
+                .thenReturn(1);
+        assertTrue(mWifiBlocklistMonitor.handleBssidConnectionFailure(
+                TEST_BSSID_1, TEST_SSID_1, TEST_L2_FAILURE, TEST_GOOD_RSSI));
+        assertTrue(mWifiBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
+    }
+
+    /**
+     * Verify that onSuccessfulConnection resets L2 related failure counts.
+     */
+    @Test
+    public void testL2FailureCountIsResetAfterSuccessfulConnection() {
+        // First simulate getting a particular L2 failure n-1 times
+        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_L2_FAILURE,
+                NUM_FAILURES_TO_BLOCKLIST - 1);
+
+        // Verify that a connection success event will clear the failure count.
+        mWifiBlocklistMonitor.handleBssidConnectionSuccess(TEST_BSSID_1, TEST_SSID_1);
+        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_L2_FAILURE,
+                NUM_FAILURES_TO_BLOCKLIST - 1);
+
+        // Verify we have not blocklisted anything yet because the failure count was cleared.
+        assertEquals(0, mWifiBlocklistMonitor.updateAndGetBssidBlocklist().size());
+
+        // Verify that TEST_BSSID_1 is added to blocklist after 1 more failure.
+        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_L2_FAILURE, 1);
+        assertTrue(mWifiBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
+    }
+
+    /**
+     * Verify that handleDhcpProvisioningSuccess resets DHCP failure counts.
+     */
+    @Test
+    public void testL3FailureCountIsResetAfterDhcpConfiguration() {
+        // First simulate getting an DHCP failure n-1 times.
+        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_DHCP_FAILURE,
+                NUM_FAILURES_TO_BLOCKLIST - 1);
+
+        // Verify that a dhcp provisioning success event will clear appropirate failure counts.
+        mWifiBlocklistMonitor.handleDhcpProvisioningSuccess(TEST_BSSID_1, TEST_SSID_1);
+        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_DHCP_FAILURE,
+                NUM_FAILURES_TO_BLOCKLIST - 1);
+
+        // Verify we have not blocklisted anything yet because the failure count was cleared.
+        assertEquals(0, mWifiBlocklistMonitor.updateAndGetBssidBlocklist().size());
+
+        // Verify that TEST_BSSID_1 is added to blocklist after 1 more failure.
+        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_DHCP_FAILURE, 1);
+        assertTrue(mWifiBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
+    }
+
+    /**
+     * Verify that handleBssidConnectionSuccess resets appropriate blocklist streak counts, and
+     * notifies WifiScorecard of the successful connection.
+     */
+    @Test
+    public void testNetworkConnectionResetsBlocklistStreak() {
+        when(mClock.getWallClockMillis()).thenReturn(ABNORMAL_DISCONNECT_RESET_TIME_MS + 1);
+        mWifiBlocklistMonitor.handleBssidConnectionSuccess(TEST_BSSID_1, TEST_SSID_1);
+        verify(mWifiScoreCard).resetBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1,
+                WifiBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA);
+        verify(mWifiScoreCard).resetBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1,
+                WifiBlocklistMonitor.REASON_WRONG_PASSWORD);
+        verify(mWifiScoreCard).resetBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1,
+                WifiBlocklistMonitor.REASON_EAP_FAILURE);
+        verify(mWifiScoreCard).resetBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1,
+                WifiBlocklistMonitor.REASON_ASSOCIATION_REJECTION);
+        verify(mWifiScoreCard).resetBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1,
+                WifiBlocklistMonitor.REASON_ASSOCIATION_TIMEOUT);
+        verify(mWifiScoreCard).resetBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1,
+                WifiBlocklistMonitor.REASON_AUTHENTICATION_FAILURE);
+        verify(mWifiScoreCard).setBssidConnectionTimestampMs(TEST_SSID_1, TEST_BSSID_1,
+                ABNORMAL_DISCONNECT_RESET_TIME_MS + 1);
+        verify(mWifiScoreCard).resetBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1,
+                WifiBlocklistMonitor.REASON_ABNORMAL_DISCONNECT);
+        verify(mWifiScoreCard).resetBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1,
+                WifiBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE);
+        verify(mWifiScoreCard).resetBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1,
+                WifiBlocklistMonitor.REASON_NONLOCAL_DISCONNECT_CONNECTING);
+    }
+
+    /**
+     * Verify that the abnormal disconnect streak is not reset if insufficient time has passed.
+     */
+    @Test
+    public void testNetworkConnectionNotResetAbnormalDisconnectStreak() {
+        when(mClock.getWallClockMillis()).thenReturn(ABNORMAL_DISCONNECT_RESET_TIME_MS);
+        mWifiBlocklistMonitor.handleBssidConnectionSuccess(TEST_BSSID_1, TEST_SSID_1);
+        verify(mWifiScoreCard).setBssidConnectionTimestampMs(TEST_SSID_1, TEST_BSSID_1,
+                ABNORMAL_DISCONNECT_RESET_TIME_MS);
+        verify(mWifiScoreCard, never()).resetBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1,
+                WifiBlocklistMonitor.REASON_ABNORMAL_DISCONNECT);
+    }
+
+    /**
+     * Verify that the streak count for REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE is not reset
+     * if insufficient time has passed.
+     */
+    @Test
+    public void testNetworkConnectionNotResetConnectedScoreStreak() {
+        when(mClock.getWallClockMillis()).thenReturn(ABNORMAL_DISCONNECT_RESET_TIME_MS);
+        mWifiBlocklistMonitor.handleBssidConnectionSuccess(TEST_BSSID_1, TEST_SSID_1);
+        verify(mWifiScoreCard).setBssidConnectionTimestampMs(TEST_SSID_1, TEST_BSSID_1,
+                ABNORMAL_DISCONNECT_RESET_TIME_MS);
+        verify(mWifiScoreCard, never()).resetBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1,
+                WifiBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE);
+    }
+
+    /**
+     * Verify that handleDhcpProvisioningSuccess resets appropriate blocklist streak counts.
+     */
+    @Test
+    public void testDhcpProvisioningResetsBlocklistStreak() {
+        mWifiBlocklistMonitor.handleDhcpProvisioningSuccess(TEST_BSSID_1, TEST_SSID_1);
+        verify(mWifiScoreCard).resetBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1,
+                WifiBlocklistMonitor.REASON_DHCP_FAILURE);
+    }
+
+    /**
+     * Verify that handleNetworkValidationSuccess resets appropriate blocklist streak counts
+     * and removes the BSSID from blocklist.
+     */
+    @Test
+    public void testNetworkValidationResetsBlocklistStreak() {
+        verifyAddTestBssidToBlocklist();
+        mWifiBlocklistMonitor.handleNetworkValidationSuccess(TEST_BSSID_1, TEST_SSID_1);
+        verify(mWifiScoreCard).resetBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1,
+                WifiBlocklistMonitor.REASON_NETWORK_VALIDATION_FAILURE);
+        assertEquals(0, mWifiBlocklistMonitor.updateAndGetBssidBlocklist().size());
+    }
+
+    /**
+     * Verify that L3 failure counts are not affected when L2 failure counts are reset.
+     */
+    @Test
+    public void testL3FailureCountIsNotResetByConnectionSuccess() {
+        // First simulate getting an L3 failure n-1 times.
+        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_DHCP_FAILURE,
+                NUM_FAILURES_TO_BLOCKLIST - 1);
+        assertEquals(0, mWifiBlocklistMonitor.updateAndGetBssidBlocklist().size());
+
+        // Verify that the failure counter is not cleared by |handleBssidConnectionSuccess|.
+        mWifiBlocklistMonitor.handleBssidConnectionSuccess(TEST_BSSID_1, TEST_SSID_1);
+        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_DHCP_FAILURE, 1);
+        assertEquals(1, mWifiBlocklistMonitor.updateAndGetBssidBlocklist().size());
+    }
+
+    /**
+     * Verify that the blocklist streak is incremented after adding a BSSID to blocklist.
+     * And then verify the blocklist streak is not reset by a regular timeout.
+     */
+    @Test
+    public void testIncrementingBlocklistStreakCount() {
+        for (Map.Entry<Integer, Integer> entry : BLOCK_REASON_TO_DISABLE_THRESHOLD_MAP.entrySet()) {
+            int reason = entry.getKey();
+            int threshold = entry.getValue();
+            when(mClock.getWallClockMillis()).thenReturn(0L);
+            handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_SSID_1, reason, threshold);
+
+            // verify the BSSID is blocked
+            assertEquals(1, mWifiBlocklistMonitor.updateAndGetNumBlockedBssidsForSsid(
+                    TEST_SSID_1));
+
+            // verify that the blocklist streak is incremented
+            verify(mWifiScoreCard).incrementBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1, reason);
+            // verify WifiMetrics also increments the blocklist count.
+            verify(mWifiMetrics).incrementBssidBlocklistCount(reason);
+
+            // Verify that TEST_BSSID_1 is removed from the blocklist after the timeout duration.
+            when(mClock.getWallClockMillis()).thenReturn(BASE_BLOCKLIST_DURATION + 1);
+            assertEquals(0, mWifiBlocklistMonitor.updateAndGetBssidBlocklist().size());
+
+            // But the blocklist streak count is not cleared
+            verify(mWifiScoreCard, never()).resetBssidBlocklistStreak(TEST_SSID_1, TEST_BSSID_1,
+                    reason);
+        }
+    }
+
+    /**
+     * Verify that when a failure signal is received for a BSSID with different SSID from before,
+     * then the failure counts are reset.
+     */
+    @Test
+    public void testFailureCountIsResetIfSsidChanges() {
+        // First simulate getting a particular L2 failure n-1 times
+        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_L2_FAILURE,
+                NUM_FAILURES_TO_BLOCKLIST - 1);
+
+        // Verify that when the SSID changes, the failure counts are cleared.
+        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_SSID_2, TEST_L2_FAILURE,
+                NUM_FAILURES_TO_BLOCKLIST - 1);
+
+        // Verify we have not blocklisted anything yet because the failure count was cleared.
+        assertEquals(0, mWifiBlocklistMonitor.updateAndGetBssidBlocklist().size());
+
+        // Verify that TEST_BSSID_1 is added to blocklist after 1 more failure.
+        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_SSID_2, TEST_L2_FAILURE, 1);
+        assertTrue(mWifiBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
+    }
+
+    /**
+     * Verify that a BSSID is not added to blocklist as long as
+     * mWifiLastResortWatchdog.shouldIgnoreBssidUpdate is returning true, for failure reasons
+     * that are also being tracked by the watchdog.
+     */
+    @Test
+    public void testWatchdogIsGivenChanceToTrigger() {
+        // Verify that |shouldIgnoreBssidUpdate| can prevent a BSSID from being added to blocklist.
+        when(mWifiLastResortWatchdog.shouldIgnoreBssidUpdate(anyString())).thenReturn(true);
+        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_L2_FAILURE,
+                NUM_FAILURES_TO_BLOCKLIST * 2);
+        assertEquals(0, mWifiBlocklistMonitor.updateAndGetBssidBlocklist().size());
+
+        // Verify that after watchdog is okay with blocking a BSSID, it gets blocked after 1
+        // more failure.
+        when(mWifiLastResortWatchdog.shouldIgnoreBssidUpdate(anyString())).thenReturn(false);
+        handleBssidConnectionFailureMultipleTimes(TEST_BSSID_1, TEST_L2_FAILURE, 1);
+        assertTrue(mWifiBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
+    }
+
+    /**
+     * Verify that non device related errors, and errors that are not monitored by the watchdog
+     * bypasses the watchdog check.
+     */
+    @Test
+    public void testUnrelatedErrorsBypassWatchdogCheck() {
+        when(mWifiLastResortWatchdog.shouldIgnoreBssidUpdate(anyString())).thenReturn(true);
+        verifyAddTestBssidToBlocklist();
+        verify(mWifiLastResortWatchdog, never()).shouldIgnoreBssidUpdate(anyString());
+    }
+
+    /**
+     * Verify that we are correctly filtering by SSID when sending a blocklist down to firmware.
+     */
+    @Test
+    public void testSendBlocklistToFirmwareFilterBySsid() {
+        verifyAddMultipleBssidsToBlocklist();
+
+        // Verify we are sending 2 BSSIDs down to the firmware for SSID_1.
+        ArrayList<String> blocklist1 = new ArrayList<>();
+        blocklist1.add(TEST_BSSID_2);
+        blocklist1.add(TEST_BSSID_1);
+        mWifiBlocklistMonitor.updateFirmwareRoamingConfiguration(Set.of(TEST_SSID_1));
+        verify(mWifiConnectivityHelper).setFirmwareRoamingConfiguration(eq(blocklist1),
+                eq(new ArrayList<>()));
+
+        // Verify we are sending 1 BSSID down to the firmware for SSID_2.
+        ArrayList<String> blocklist2 = new ArrayList<>();
+        blocklist2.add(TEST_BSSID_3);
+        mWifiBlocklistMonitor.updateFirmwareRoamingConfiguration(Set.of(TEST_SSID_2));
+        verify(mWifiConnectivityHelper).setFirmwareRoamingConfiguration(eq(blocklist2),
+                eq(new ArrayList<>()));
+
+        // Verify we are not sending any BSSIDs down to the firmware since there does not
+        // exists any BSSIDs for TEST_SSID_3 in the blocklist.
+        mWifiBlocklistMonitor.updateFirmwareRoamingConfiguration(Set.of(TEST_SSID_3));
+        verify(mWifiConnectivityHelper).setFirmwareRoamingConfiguration(eq(new ArrayList<>()),
+                eq(new ArrayList<>()));
+    }
+
+    /**
+     * Verify that when sending the blocklist down to firmware, the list is sorted by latest
+     * end time first.
+     * Also verify that when there are more blocklisted BSSIDs than the allowed limit by the
+     * firmware, the list sent down is trimmed.
+     */
+    @Test
+    public void testMostRecentBlocklistEntriesAreSentToFirmware() {
+        // Add BSSIDs to blocklist
+        String bssid = "0a:08:5c:67:89:0";
+        for (int i = 0; i < 10; i++) {
+            when(mClock.getWallClockMillis()).thenReturn((long) i);
+            mWifiBlocklistMonitor.handleBssidConnectionFailure(bssid + i,
+                    TEST_SSID_1, WifiBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA,
+                    TEST_GOOD_RSSI);
+
+            // This will build a List of BSSIDs starting from the latest added ones that is at
+            // most size |TEST_NUM_MAX_FIRMWARE_SUPPORT_BSSIDS|.
+            // Then verify that the blocklist is sent down in this sorted order.
+            ArrayList<String> blocklist = new ArrayList<>();
+            for (int j = i; j > i - TEST_NUM_MAX_FIRMWARE_SUPPORT_BSSIDS; j--) {
+                if (j < 0) {
+                    break;
+                }
+                blocklist.add(bssid + j);
+            }
+            mWifiBlocklistMonitor.updateFirmwareRoamingConfiguration(Set.of(TEST_SSID_1));
+            verify(mWifiConnectivityHelper).setFirmwareRoamingConfiguration(eq(blocklist),
+                    eq(new ArrayList<>()));
+        }
+        assertEquals(10, mWifiBlocklistMonitor.updateAndGetBssidBlocklist().size());
+    }
+
+    /**
+     * Verifies that when firmware roaming is disabled, the blocklist does not get plumbed to
+     * hardware, but the blocklist should still accessible by the framework.
+     */
+    @Test
+    public void testFirmwareRoamingDisabled() {
+        when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(false);
+        verifyAddTestBssidToBlocklist();
+
+        mWifiBlocklistMonitor.updateFirmwareRoamingConfiguration(Set.of(TEST_SSID_1));
+        verify(mWifiConnectivityHelper, never()).setFirmwareRoamingConfiguration(any(), any());
+    }
+
+    /**
+     * Verify that clearBssidBlocklist resets internal state.
+     */
+    @Test
+    public void testClearBssidBlocklist() {
+        verifyAddTestBssidToBlocklist();
+        mWifiBlocklistMonitor.clearBssidBlocklist();
+        assertEquals(0, mWifiBlocklistMonitor.updateAndGetBssidBlocklist().size());
+
+    }
+
+    /**
+     * Verify that the BssidBlocklistMonitorLogger is capped.
+     */
+    @Test
+    public void testBssidBlocklistMonitorLoggerSize() {
+        int loggerMaxSize = 60;
+        for (int i = 0; i < loggerMaxSize; i++) {
+            verifyAddTestBssidToBlocklist();
+            mWifiBlocklistMonitor.clearBssidBlocklist();
+            assertEquals(i + 1, mWifiBlocklistMonitor.getBssidBlocklistMonitorLoggerSize());
+        }
+        verifyAddTestBssidToBlocklist();
+        mWifiBlocklistMonitor.clearBssidBlocklist();
+        assertEquals(loggerMaxSize,
+                mWifiBlocklistMonitor.getBssidBlocklistMonitorLoggerSize());
+    }
+
+    /**
+     * Verify that clearBssidBlocklistForSsid removes all BSSIDs for that network from the
+     * blocklist.
+     */
+    @Test
+    public void testClearBssidBlocklistForSsid() {
+        verifyAddMultipleBssidsToBlocklist();
+
+        // Clear the blocklist for SSID 1.
+        mWifiBlocklistMonitor.clearBssidBlocklistForSsid(TEST_SSID_1);
+
+        // Verify that the blocklist is deleted for SSID 1 and the BSSID for SSID 2 still remains.
+        Set<String> bssidList = mWifiBlocklistMonitor.updateAndGetBssidBlocklist();
+        assertEquals(1, bssidList.size());
+        assertTrue(bssidList.contains(TEST_BSSID_3));
+        verify(mWifiScoreCard, never()).resetBssidBlocklistStreakForSsid(TEST_SSID_1);
+    }
+
+    /**
+     * Verify that handleNetworkRemoved removes all BSSIDs for that network from the blocklist
+     * and also reset the blocklist streak count from WifiScoreCard.
+     */
+    @Test
+    public void testHandleNetworkRemovedResetsState() {
+        verifyAddMultipleBssidsToBlocklist();
+
+        // Clear the blocklist for SSID 1.
+        mWifiBlocklistMonitor.handleNetworkRemoved(TEST_SSID_1);
+
+        // Verify that the blocklist is deleted for SSID 1 and the BSSID for SSID 2 still remains.
+        Set<String> bssidList = mWifiBlocklistMonitor.updateAndGetBssidBlocklist();
+        assertEquals(1, bssidList.size());
+        assertTrue(bssidList.contains(TEST_BSSID_3));
+        verify(mWifiScoreCard).resetBssidBlocklistStreakForSsid(TEST_SSID_1);
+    }
+
+    /**
+     * Verify that |blockBssidForDurationMs| adds a BSSID to blocklist for the specified duration.
+     */
+    @Test
+    public void testBlockBssidForDurationMs() {
+        when(mClock.getWallClockMillis()).thenReturn(0L);
+        long testDuration = 5500L;
+        mWifiBlocklistMonitor.blockBssidForDurationMs(TEST_BSSID_1, TEST_SSID_1, testDuration,
+                TEST_FRAMEWORK_BLOCK_REASON, TEST_GOOD_RSSI);
+        assertEquals(1, mWifiBlocklistMonitor.updateAndGetBssidBlocklist().size());
+
+        // Verify that the BSSID is removed from blocklist by clearBssidBlocklistForSsid
+        mWifiBlocklistMonitor.clearBssidBlocklistForSsid(TEST_SSID_1);
+        assertEquals(0, mWifiBlocklistMonitor.updateAndGetBssidBlocklist().size());
+
+        // Add the BSSID to blocklist again.
+        mWifiBlocklistMonitor.blockBssidForDurationMs(TEST_BSSID_1, TEST_SSID_1, testDuration,
+                TEST_FRAMEWORK_BLOCK_REASON, TEST_GOOD_RSSI);
+        assertEquals(1, mWifiBlocklistMonitor.updateAndGetBssidBlocklist().size());
+
+        // Verify that the BSSID is removed from blocklist once the specified duration is over.
+        when(mClock.getWallClockMillis()).thenReturn(testDuration + 1);
+        assertEquals(0, mWifiBlocklistMonitor.updateAndGetBssidBlocklist().size());
+    }
+
+    /**
+     * Verify that invalid inputs are handled and result in no-op.
+     */
+    @Test
+    public void testBlockBssidForDurationMsInvalidInputs() {
+        // test invalid BSSID
+        when(mClock.getWallClockMillis()).thenReturn(0L);
+        long testDuration = 5500L;
+        mWifiBlocklistMonitor.blockBssidForDurationMs(null, TEST_SSID_1, testDuration,
+                TEST_FRAMEWORK_BLOCK_REASON, TEST_GOOD_RSSI);
+        assertEquals(0, mWifiBlocklistMonitor.updateAndGetBssidBlocklist().size());
+
+        // test invalid SSID
+        mWifiBlocklistMonitor.blockBssidForDurationMs(TEST_BSSID_1, null, testDuration,
+                TEST_FRAMEWORK_BLOCK_REASON, TEST_GOOD_RSSI);
+        assertEquals(0, mWifiBlocklistMonitor.updateAndGetBssidBlocklist().size());
+
+        // test invalid duration
+        mWifiBlocklistMonitor.blockBssidForDurationMs(TEST_BSSID_1, TEST_SSID_1, -1,
+                TEST_FRAMEWORK_BLOCK_REASON, TEST_GOOD_RSSI);
+        assertEquals(0, mWifiBlocklistMonitor.updateAndGetBssidBlocklist().size());
+    }
+
+    private void simulateRssiUpdate(String bssid, int rssi) {
+        ScanDetail scanDetail = mock(ScanDetail.class);
+        ScanResult scanResult = mock(ScanResult.class);
+        scanResult.BSSID = bssid;
+        scanResult.level = rssi;
+        when(scanDetail.getScanResult()).thenReturn(scanResult);
+        List<ScanDetail> scanDetails = new ArrayList<>();
+        scanDetails.add(scanDetail);
+        mWifiBlocklistMonitor.tryEnablingBlockedBssids(scanDetails);
+    }
+
+    /**
+     * Verify that if the RSSI is low when the BSSID is blocked, a RSSI improvement will remove
+     * the BSSID from blocklist.
+     */
+    @Test
+    public void testUnblockBssidAfterRssiImproves() {
+        when(mClock.getWallClockMillis()).thenReturn(0L);
+        // verify TEST_BSSID_1 is blocked
+        mWifiBlocklistMonitor.handleBssidConnectionFailure(
+                TEST_BSSID_1, TEST_SSID_1, WifiBlocklistMonitor.REASON_EAP_FAILURE,
+                TEST_SUFFICIENT_RSSI - MIN_RSSI_DIFF_TO_UNBLOCK_BSSID);
+        assertTrue(mWifiBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
+
+        // verify the blocklist is not cleared when the rssi improvement is not large enough.
+        simulateRssiUpdate(TEST_BSSID_1, TEST_SUFFICIENT_RSSI - 1);
+        assertTrue(mWifiBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
+
+        // verify TEST_BSSID_1 is removed from the blocklist after RSSI improves
+        simulateRssiUpdate(TEST_BSSID_1, TEST_SUFFICIENT_RSSI);
+        assertEquals(0, mWifiBlocklistMonitor.updateAndGetBssidBlocklist().size());
+    }
+
+    /**
+     * Verify that if the RSSI is already good when the BSSID is blocked, a RSSI improvement will
+     * not remove the BSSID from blocklist.
+     */
+    @Test
+    public void testBssidNotUnblockedIfRssiAlreadyGood() {
+        when(mClock.getWallClockMillis()).thenReturn(0L);
+        // verify TEST_BSSID_1 is blocked
+        mWifiBlocklistMonitor.handleBssidConnectionFailure(
+                TEST_BSSID_1, TEST_SSID_1, WifiBlocklistMonitor.REASON_EAP_FAILURE,
+                TEST_SUFFICIENT_RSSI);
+        assertTrue(mWifiBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
+
+        // verify TEST_BSSID_1 is not removed from blocklist
+        simulateRssiUpdate(TEST_BSSID_1, TEST_GOOD_RSSI);
+        assertTrue(mWifiBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
+    }
+
+    /**
+     * Verify that the logic to unblock BSSIDs after RSSI improvement does not apply for some
+     * failure reasons.
+     */
+    @Test
+    public void testRssiImprovementNotUnblockBssidForSomeFailureReasons() {
+        when(mClock.getWallClockMillis()).thenReturn(0L);
+        mWifiBlocklistMonitor.handleBssidConnectionFailure(
+                TEST_BSSID_1, TEST_SSID_1, WifiBlocklistMonitor.REASON_WRONG_PASSWORD,
+                TEST_SUFFICIENT_RSSI - MIN_RSSI_DIFF_TO_UNBLOCK_BSSID);
+        assertTrue(mWifiBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
+
+        simulateRssiUpdate(TEST_BSSID_1, TEST_SUFFICIENT_RSSI);
+        assertTrue(mWifiBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
+    }
+
+    /**
+     * Verify the failure reasons for all blocked BSSIDs are retrieved.
+     */
+    @Test
+    public void testGetFailureReasonsForSsid() {
+        // Null input should not crash
+        mWifiBlocklistMonitor.getFailureReasonsForSsid(null).size();
+        assertEquals(0, mWifiBlocklistMonitor.getFailureReasonsForSsid(TEST_SSID_1).size());
+        mWifiBlocklistMonitor.blockBssidForDurationMs(TEST_BSSID_1, TEST_SSID_1, 1000,
+                WifiBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA, TEST_GOOD_RSSI);
+        mWifiBlocklistMonitor.blockBssidForDurationMs(TEST_BSSID_2, TEST_SSID_1, 1000,
+                WifiBlocklistMonitor.REASON_ABNORMAL_DISCONNECT, TEST_GOOD_RSSI);
+
+        assertEquals(2, mWifiBlocklistMonitor.getFailureReasonsForSsid(TEST_SSID_1).size());
+        assertTrue(mWifiBlocklistMonitor.getFailureReasonsForSsid(TEST_SSID_1)
+                .contains(WifiBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA));
+        assertTrue(mWifiBlocklistMonitor.getFailureReasonsForSsid(TEST_SSID_1)
+                .contains(WifiBlocklistMonitor.REASON_ABNORMAL_DISCONNECT));
+    }
+
+    /**
+     * Verifies SSID blocklist consistent with Watchdog trigger.
+     *
+     * Expected behavior: A SSID won't gets blocklisted if there only single SSID
+     * be observed and Watchdog trigger is activated.
+     */
+    @Test
+    public void verifyConsistentWatchdogAndSsidBlocklist() {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        when(mWifiLastResortWatchdog.shouldIgnoreSsidUpdate()).thenReturn(true);
+        // First set it to enabled.
+        assertTrue(mWifiBlocklistMonitor.updateNetworkSelectionStatus(openNetwork,
+                NetworkSelectionStatus.DISABLED_NONE));
+        assertEquals(NetworkSelectionStatus.NETWORK_SELECTION_ENABLED,
+                openNetwork.getNetworkSelectionStatus().getNetworkSelectionStatus());
+
+        int assocRejectReason = NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION;
+        int assocRejectThreshold =
+                mWifiBlocklistMonitor.getNetworkSelectionDisableThreshold(assocRejectReason);
+        for (int i = 1; i <= assocRejectThreshold; i++) {
+            assertFalse(mWifiBlocklistMonitor.updateNetworkSelectionStatus(
+                    openNetwork, assocRejectReason));
+        }
+        assertFalse(openNetwork.getNetworkSelectionStatus().isNetworkTemporaryDisabled());
+    }
+
+    /**
+     * Verifies the update of network status using
+     * {@link WifiConfigManager#updateNetworkSelectionStatus(int, int)}.
+     */
+    @Test
+    public void testNetworkSelectionStatus() {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        // First set it to enabled.
+        verifyUpdateNetworkSelectionStatus(
+                openNetwork, NetworkSelectionStatus.DISABLED_NONE, 0);
+
+        // Now set it to temporarily disabled. The threshold for association rejection is 5, so
+        // disable it 5 times to actually mark it temporarily disabled.
+        int assocRejectReason = NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION;
+        int assocRejectThreshold =
+                mWifiBlocklistMonitor.getNetworkSelectionDisableThreshold(assocRejectReason);
+        for (int i = 1; i <= assocRejectThreshold; i++) {
+            verifyUpdateNetworkSelectionStatus(openNetwork, assocRejectReason, i);
+        }
+        assertEquals(NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION,
+                openNetwork.getNetworkSelectionStatus().getNetworkSelectionDisableReason());
+        // Now set it to permanently disabled.
+        verifyUpdateNetworkSelectionStatus(
+                openNetwork, NetworkSelectionStatus.DISABLED_BY_WIFI_MANAGER, 0);
+        assertEquals(NetworkSelectionStatus.DISABLED_BY_WIFI_MANAGER,
+                openNetwork.getNetworkSelectionStatus().getNetworkSelectionDisableReason());
+        // Now set it back to enabled.
+        verifyUpdateNetworkSelectionStatus(
+                openNetwork, NetworkSelectionStatus.DISABLED_NONE, 0);
+        assertEquals(NetworkSelectionStatus.DISABLED_NONE,
+                openNetwork.getNetworkSelectionStatus().getNetworkSelectionDisableReason());
+    }
+
+    /**
+     * Verifies the update of network status using
+     * {@link WifiBlocklistMonitor#updateNetworkSelectionStatus(int, int)}.
+     */
+    @Test
+    public void testNetworkSelectionStatusTemporarilyDisabledDueToNoInternet() {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        // First set it to enabled.
+        verifyUpdateNetworkSelectionStatus(
+                openNetwork, NetworkSelectionStatus.DISABLED_NONE, 0);
+
+        // Now set it to temporarily disabled. The threshold for no internet is 1, so
+        // disable it once to actually mark it temporarily disabled.
+        verifyUpdateNetworkSelectionStatus(openNetwork,
+                NetworkSelectionStatus.DISABLED_NO_INTERNET_TEMPORARY, 1);
+        // Now set it back to enabled.
+        verifyUpdateNetworkSelectionStatus(
+                openNetwork, NetworkSelectionStatus.DISABLED_NONE, 0);
+    }
+
+    /**
+     * Verifies the update of network status using
+     * {@link WifiConfigManager#updateNetworkSelectionStatus(int, int)} and ensures that
+     * enabling a network clears out all the temporary disable counters.
+     */
+    @Test
+    public void testNetworkSelectionStatusEnableClearsDisableCounters() {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        // First set it to enabled.
+        verifyUpdateNetworkSelectionStatus(
+                openNetwork, NetworkSelectionStatus.DISABLED_NONE, 0);
+
+        // Now set it to temporarily disabled 2 times for 2 different reasons.
+        verifyUpdateNetworkSelectionStatus(
+                openNetwork, NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION, 1);
+        verifyUpdateNetworkSelectionStatus(
+                openNetwork, NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION, 2);
+        verifyUpdateNetworkSelectionStatus(
+                openNetwork, NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE, 1);
+        verifyUpdateNetworkSelectionStatus(
+                openNetwork, NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE, 2);
+
+        // Now set it back to enabled.
+        verifyUpdateNetworkSelectionStatus(
+                openNetwork, NetworkSelectionStatus.DISABLED_NONE, 0);
+
+        // Ensure that the counters have all been reset now.
+        verifyUpdateNetworkSelectionStatus(
+                openNetwork, NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION, 1);
+        verifyUpdateNetworkSelectionStatus(
+                openNetwork, NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE, 1);
+    }
+
+    /**
+     * Verifies the network's selection status update.
+     *
+     * For temporarily disabled reasons, the method ensures that the status has changed only if
+     * disable reason counter has exceeded the threshold.
+     *
+     * For permanently disabled/enabled reasons, the method ensures that the public status has
+     * changed and the network change broadcast has been sent out.
+     */
+    private void verifyUpdateNetworkSelectionStatus(
+            WifiConfiguration config, int reason, int temporaryDisableReasonCounter) {
+        when(mClock.getElapsedSinceBootMillis())
+                .thenReturn(TEST_ELAPSED_UPDATE_NETWORK_SELECTION_TIME_MILLIS);
+        NetworkSelectionStatus currentStatus = config.getNetworkSelectionStatus();
+        int currentDisableReason = currentStatus.getNetworkSelectionDisableReason();
+
+        // First set the status to the provided reason.
+        mWifiBlocklistMonitor.updateNetworkSelectionStatus(config, reason);
+
+        NetworkSelectionStatus retrievedStatus = config.getNetworkSelectionStatus();
+        int retrievedDisableReason = retrievedStatus.getNetworkSelectionDisableReason();
+        long retrievedDisableTime = retrievedStatus.getDisableTime();
+        int retrievedDisableReasonCounter = retrievedStatus.getDisableReasonCounter(reason);
+        int disableReasonThreshold =
+                mWifiBlocklistMonitor.getNetworkSelectionDisableThreshold(reason);
+
+        if (reason == NetworkSelectionStatus.DISABLED_NONE) {
+            assertEquals(reason, retrievedDisableReason);
+            assertTrue(retrievedStatus.isNetworkEnabled());
+            assertEquals(
+                    NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP,
+                    retrievedDisableTime);
+            assertEquals(WifiConfiguration.Status.ENABLED, config.status);
+        } else if (mWifiBlocklistMonitor.getNetworkSelectionDisableTimeoutMillis(reason)
+                != DisableReasonInfo.PERMANENT_DISABLE_TIMEOUT) {
+            // For temporarily disabled networks, we need to ensure that the current status remains
+            // until the threshold is crossed.
+            assertEquals(temporaryDisableReasonCounter, retrievedDisableReasonCounter);
+            if (retrievedDisableReasonCounter < disableReasonThreshold) {
+                assertEquals(currentDisableReason, retrievedDisableReason);
+                assertEquals(
+                        currentStatus.getNetworkSelectionStatus(),
+                        retrievedStatus.getNetworkSelectionStatus());
+            } else {
+                assertEquals(reason, retrievedDisableReason);
+                assertTrue(retrievedStatus.isNetworkTemporaryDisabled());
+                assertEquals(
+                        TEST_ELAPSED_UPDATE_NETWORK_SELECTION_TIME_MILLIS, retrievedDisableTime);
+            }
+        } else if (reason < NetworkSelectionStatus.NETWORK_SELECTION_DISABLED_MAX) {
+            assertEquals(reason, retrievedDisableReason);
+            assertTrue(retrievedStatus.isNetworkPermanentlyDisabled());
+            assertEquals(
+                    NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP,
+                    retrievedDisableTime);
+            assertEquals(WifiConfiguration.Status.DISABLED, config.status);
+        }
+    }
+
+    /**
+     * Verify the disable duration of a network exponentially increases with increasing
+     * CNT_CONSECUTIVE_CONNECTION_FAILURE.
+     */
+    @Test
+    public void testTryEnableNetworkExponentialBackoff() {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+
+        // Verify exponential backoff on the disable duration based on number of BSSIDs in the
+        // BSSID blocklist
+        int disableReason = NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION;
+        verifyDisableNetwork(openNetwork, disableReason);
+
+        // expect exponential backoff 2 times
+        when(mRecentStats.getCount(WifiScoreCard.CNT_CONSECUTIVE_CONNECTION_FAILURE)).thenReturn(
+                WifiBlocklistMonitor.NUM_CONSECUTIVE_FAILURES_PER_NETWORK_EXP_BACKOFF + 2);
+        verifyNetworkIsEnabledAfter(openNetwork,
+                TEST_ELAPSED_UPDATE_NETWORK_SELECTION_TIME_MILLIS
+                        + (mWifiBlocklistMonitor.getNetworkSelectionDisableTimeoutMillis(
+                                disableReason) * 4));
+    }
+
+    /**
+     * Verify the disable duration for a network is capped at
+     * WIFI_CONFIG_MAX_DISABLE_DURATION_MILLIS.
+     */
+    @Test
+    public void testTryEnableNetworkExponentialBackoffCapped() {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        int disableReason = NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION;
+        verifyDisableNetwork(openNetwork, disableReason);
+
+        // verify the exponential backoff is capped at WIFI_CONFIG_MAX_DISABLE_DURATION_MILLIS
+        when(mRecentStats.getCount(WifiScoreCard.CNT_CONSECUTIVE_CONNECTION_FAILURE)).thenReturn(
+                Integer.MAX_VALUE);
+        verifyNetworkIsEnabledAfter(openNetwork,
+                TEST_ELAPSED_UPDATE_NETWORK_SELECTION_TIME_MILLIS
+                        + WifiBlocklistMonitor.WIFI_CONFIG_MAX_DISABLE_DURATION_MILLIS);
+    }
+
+    /**
+     * Verifies that a network is disabled for the base duration even when there are no BSSIDs
+     * blocked.
+     */
+    @Test
+    public void testTryEnableNetworkNoBssidsInBlocklist() {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        int disableReason = NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION;
+
+        verifyDisableNetwork(openNetwork, disableReason);
+        verifyNetworkIsEnabledAfter(openNetwork,
+                mWifiBlocklistMonitor.getNetworkSelectionDisableTimeoutMillis(disableReason)
+                        + TEST_ELAPSED_UPDATE_NETWORK_SELECTION_TIME_MILLIS);
+    }
+
+    private void verifyDisableNetwork(WifiConfiguration config, int reason) {
+        // First set it to enabled.
+        verifyUpdateNetworkSelectionStatus(
+                config, NetworkSelectionStatus.DISABLED_NONE, 0);
+
+        int disableThreshold =
+                mWifiBlocklistMonitor.getNetworkSelectionDisableThreshold(reason);
+        for (int i = 1; i <= disableThreshold; i++) {
+            verifyUpdateNetworkSelectionStatus(config, reason, i);
+        }
+        // verify WifiMetrics increments the blocklist count.
+        verify(mWifiMetrics).incrementWificonfigurationBlocklistCount(reason);
+    }
+
+    private void verifyNetworkIsEnabledAfter(WifiConfiguration config, long timeout) {
+        // try enabling this network 1 second earlier than the expected timeout. This
+        // should fail and the status should remain temporarily disabled.
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(timeout - 1);
+        assertFalse(mWifiBlocklistMonitor.shouldEnableNetwork(config));
+
+        // Now advance time by the timeout for association rejection and ensure that the
+        // network is now enabled.
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(timeout);
+        assertTrue(mWifiBlocklistMonitor.shouldEnableNetwork(config));
+    }
+
+    /**
+     * Verify that the parameters in DISABLE_REASON_INFOS are overlayable.
+     */
+    @Test
+    public void testNetworkSelectionDisableReasonCustomConfigOverride() {
+        int oldThreshold = NetworkSelectionStatus.DISABLE_REASON_INFOS
+                .get(NetworkSelectionStatus.DISABLED_DHCP_FAILURE).mDisableThreshold;
+
+        // Modify the overlay value and create WifiConfigManager again.
+        int newThreshold = oldThreshold + 1;
+        mResources.setInteger(
+                R.integer.config_wifiDisableReasonDhcpFailureThreshold, newThreshold);
+        mWifiBlocklistMonitor = new WifiBlocklistMonitor(mContext, mWifiConnectivityHelper,
+                mWifiLastResortWatchdog, mClock, mLocalLog, mWifiScoreCard, mScoringParams,
+                mWifiMetrics);
+
+        // Verify that the threshold is updated in the copied version
+        assertEquals(newThreshold, mWifiBlocklistMonitor.getNetworkSelectionDisableThreshold(
+                NetworkSelectionStatus.DISABLED_DHCP_FAILURE));
+        // Verify the original DISABLE_REASON_INFOS is unchanged
+        assertEquals(oldThreshold, NetworkSelectionStatus.DISABLE_REASON_INFOS
+                .get(NetworkSelectionStatus.DISABLED_DHCP_FAILURE).mDisableThreshold);
+    }
+
+    /**
+     * Verify the correctness of the copied DISABLE_REASON_INFOS.
+     */
+    @Test
+    public void testNetworkSelectionDisableReasonClone() {
+        for (int i = 1; i < NetworkSelectionStatus.NETWORK_SELECTION_DISABLED_MAX; i++) {
+            assertEquals("Disable threshold for reason=" + i + " should be equal",
+                    NetworkSelectionStatus.DISABLE_REASON_INFOS.get(i).mDisableThreshold,
+                    mWifiBlocklistMonitor.getNetworkSelectionDisableThreshold(i));
+        }
+    }
+}
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiCandidatesTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiCandidatesTest.java
index 6be4af8..c602b6a 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiCandidatesTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiCandidatesTest.java
@@ -16,6 +16,9 @@
 
 package com.android.server.wifi;
 
+import static android.net.wifi.WifiManager.WIFI_FEATURE_OWE;
+import static android.net.wifi.WifiManager.WIFI_FEATURE_WPA3_SAE;
+
 import static com.android.server.wifi.util.NativeUtil.removeEnclosingQuotes;
 
 import static org.junit.Assert.*;
@@ -28,13 +31,16 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.net.module.util.MacAddressUtils;
 import com.android.wifi.resources.R;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
 
 /**
  * Unit tests for {@link com.android.server.wifi.WifiCandidates}.
@@ -47,6 +53,11 @@
     @Mock WifiScoreCard mWifiScoreCard;
     @Mock WifiScoreCard.PerBssid mPerBssid;
     @Mock Context mContext;
+    @Mock WifiInjector mWifiInjector;
+    @Mock WifiGlobals mWifiGlobals;
+    @Mock ActiveModeWarden mActiveModeWarden;
+    @Mock ClientModeManager mClientModeManager;
+    private MockitoSession mSession;
 
     ScanResult mScanResult1;
     ScanResult mScanResult2;
@@ -63,6 +74,19 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        // static mocking
+        mSession = ExtendedMockito.mockitoSession()
+                .mockStatic(WifiInjector.class, withSettings().lenient())
+                .startMocking();
+        when(WifiInjector.getInstance()).thenReturn(mWifiInjector);
+        when(mWifiInjector.getWifiGlobals()).thenReturn(mWifiGlobals);
+        when(mWifiInjector.getActiveModeWarden()).thenReturn(mActiveModeWarden);
+        when(mActiveModeWarden.getPrimaryClientModeManager()).thenReturn(mClientModeManager);
+        when(mClientModeManager.getSupportedFeatures()).thenReturn(
+                WIFI_FEATURE_OWE | WIFI_FEATURE_WPA3_SAE);
+        when(mWifiGlobals.isWpa3SaeUpgradeEnabled()).thenReturn(true);
+        when(mWifiGlobals.isOweUpgradeEnabled()).thenReturn(true);
+
         mWifiCandidates = new WifiCandidates(mWifiScoreCard, mContext);
         mConfig1 = WifiConfigurationTestUtil.createOpenNetwork();
 
@@ -86,6 +110,17 @@
     }
 
     /**
+    * Called after each test
+     */
+    @After
+    public void cleanup() {
+        validateMockitoUsage();
+        if (mSession != null) {
+            mSession.finishMocking();
+        }
+    }
+
+    /**
      * Test for absence of null pointer exceptions
      */
     @Test
@@ -342,11 +377,11 @@
     @Test
     public void testMultiplePasspointCandidatesWithSameFQDN() {
         // Create a Passpoint WifiConfig
-        WifiConfiguration config1 = WifiConfigurationTestUtil.createPasspointNetwork();
+        mScanResult2.SSID = mScanResult1.SSID;
         mScanResult2.BSSID = mScanResult1.BSSID.replace('1', '2');
         // Add candidates with different scanDetail for same passpoint WifiConfig.
-        assertTrue(mWifiCandidates.add(mScanDetail1, config1, 2, 0.0, false, 100));
-        assertTrue(mWifiCandidates.add(mScanDetail2, config1, 2, 0.0, false, 100));
+        assertTrue(mWifiCandidates.add(mScanDetail1, mConfig1, 2, 0.0, false, 100));
+        assertTrue(mWifiCandidates.add(mScanDetail2, mConfig1, 2, 0.0, false, 100));
         // Both should survive and no faults.
         assertEquals(2, mWifiCandidates.size());
         assertEquals(0, mWifiCandidates.getFaultCount());
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiCarrierInfoManagerTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiCarrierInfoManagerTest.java
index 2c1c2ec..e5fb331 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiCarrierInfoManagerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiCarrierInfoManagerTest.java
@@ -16,33 +16,41 @@
 
 package com.android.server.wifi;
 
+import static android.telephony.CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL;
+import static android.telephony.TelephonyManager.DATA_ENABLED_REASON_USER;
+
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 import static com.android.server.wifi.WifiCarrierInfoManager.NOTIFICATION_USER_ALLOWED_CARRIER_INTENT_ACTION;
+import static com.android.server.wifi.WifiCarrierInfoManager.NOTIFICATION_USER_CLICKED_INTENT_ACTION;
 import static com.android.server.wifi.WifiCarrierInfoManager.NOTIFICATION_USER_DISALLOWED_CARRIER_INTENT_ACTION;
 import static com.android.server.wifi.WifiCarrierInfoManager.NOTIFICATION_USER_DISMISSED_INTENT_ACTION;
 
 import static org.junit.Assert.*;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.Mockito.*;
 
 import android.app.AlertDialog;
 import android.app.Notification;
-import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
-import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
 import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.net.wifi.hotspot2.pps.Credential;
 import android.os.Handler;
 import android.os.PersistableBundle;
+import android.os.UserHandle;
 import android.os.test.TestLooper;
 import android.telephony.CarrierConfigManager;
 import android.telephony.ImsiEncryptionInfo;
@@ -57,6 +65,7 @@
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.WifiCarrierInfoManager.SimAuthRequestData;
 import com.android.server.wifi.WifiCarrierInfoManager.SimAuthResponseData;
 import com.android.wifi.resources.R;
@@ -121,32 +130,37 @@
     @Mock WifiConfigStore mWifiConfigStore;
     @Mock WifiInjector mWifiInjector;
     @Mock WifiConfigManager mWifiConfigManager;
-    @Mock ImsiPrivacyProtectionExemptionStoreData mImsiPrivacyProtectionExemptionStoreData;
-    @Mock NotificationManager mNotificationManger;
+    @Mock
+    WifiCarrierInfoStoreManagerData mWifiCarrierInfoStoreManagerData;
+    @Mock WifiNotificationManager mWifiNotificationManager;
     @Mock Notification.Builder mNotificationBuilder;
     @Mock Notification mNotification;
     @Mock AlertDialog.Builder mAlertDialogBuilder;
     @Mock AlertDialog mAlertDialog;
     @Mock WifiCarrierInfoManager.OnUserApproveCarrierListener mListener;
     @Mock WifiMetrics mWifiMetrics;
+    @Mock WifiCarrierInfoManager.OnCarrierOffloadDisabledListener mOnCarrierOffloadDisabledListener;
+    @Mock Clock mClock;
 
     private List<SubscriptionInfo> mSubInfoList;
 
     MockitoSession mMockingSession = null;
     TestLooper mLooper;
+    private WifiCarrierInfoStoreManagerData.DataSource mCarrierInfoDataSource;
     private ImsiPrivacyProtectionExemptionStoreData.DataSource mImsiDataSource;
     private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor =
             ArgumentCaptor.forClass(BroadcastReceiver.class);
+    private ArgumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>
+            mListenerArgumentCaptor = ArgumentCaptor.forClass(
+                    SubscriptionManager.OnSubscriptionsChangedListener.class);
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mLooper = new TestLooper();
-        when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE))
+        when(mContext.getSystemService(CarrierConfigManager.class))
                 .thenReturn(mCarrierConfigManager);
         when(mContext.getResources()).thenReturn(mResources);
-        when(mContext.getSystemService(Context.NOTIFICATION_SERVICE))
-                .thenReturn(mNotificationManger);
         when(mContext.getWifiOverlayApkPkgName()).thenReturn("test.com.android.wifi.resources");
         when(mFrameworkFacade.makeAlertDialogBuilder(any()))
                 .thenReturn(mAlertDialogBuilder);
@@ -166,26 +180,36 @@
         when(mNotificationBuilder.setTicker(any())).thenReturn(mNotificationBuilder);
         when(mNotificationBuilder.setContentTitle(any())).thenReturn(mNotificationBuilder);
         when(mNotificationBuilder.setStyle(any())).thenReturn(mNotificationBuilder);
+        when(mNotificationBuilder.setContentIntent(any())).thenReturn(mNotificationBuilder);
         when(mNotificationBuilder.setDeleteIntent(any())).thenReturn(mNotificationBuilder);
         when(mNotificationBuilder.setShowWhen(anyBoolean())).thenReturn(mNotificationBuilder);
         when(mNotificationBuilder.setLocalOnly(anyBoolean())).thenReturn(mNotificationBuilder);
         when(mNotificationBuilder.setColor(anyInt())).thenReturn(mNotificationBuilder);
         when(mNotificationBuilder.addAction(any())).thenReturn(mNotificationBuilder);
+        when(mNotificationBuilder.setTimeoutAfter(anyLong())).thenReturn(mNotificationBuilder);
         when(mNotificationBuilder.build()).thenReturn(mNotification);
-        when(mWifiInjector.makeImsiProtectionExemptionStoreData(any()))
-                .thenReturn(mImsiPrivacyProtectionExemptionStoreData);
+        when(mWifiInjector.makeWifiCarrierInfoStoreManagerData(any()))
+                .thenReturn(mWifiCarrierInfoStoreManagerData);
         when(mWifiInjector.getWifiConfigManager()).thenReturn(mWifiConfigManager);
+        when(mWifiInjector.getWifiNotificationManager()).thenReturn(mWifiNotificationManager);
         mWifiCarrierInfoManager = new WifiCarrierInfoManager(mTelephonyManager,
                 mSubscriptionManager, mWifiInjector, mFrameworkFacade, mContext, mWifiConfigStore,
-                new Handler(mLooper.getLooper()), mWifiMetrics);
+                new Handler(mLooper.getLooper()), mWifiMetrics, mClock);
+        ArgumentCaptor<WifiCarrierInfoStoreManagerData.DataSource>
+                carrierInfoSourceArgumentCaptor =
+                ArgumentCaptor.forClass(WifiCarrierInfoStoreManagerData.DataSource.class);
         ArgumentCaptor<ImsiPrivacyProtectionExemptionStoreData.DataSource>
                 imsiDataSourceArgumentCaptor =
                 ArgumentCaptor.forClass(ImsiPrivacyProtectionExemptionStoreData.DataSource.class);
         verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(), any(), any(), any());
-        verify(mWifiInjector).makeImsiProtectionExemptionStoreData(imsiDataSourceArgumentCaptor
+        verify(mWifiInjector).makeWifiCarrierInfoStoreManagerData(carrierInfoSourceArgumentCaptor
                 .capture());
+        verify(mWifiInjector).makeImsiPrivacyProtectionExemptionStoreData(
+                imsiDataSourceArgumentCaptor.capture());
+        mCarrierInfoDataSource = carrierInfoSourceArgumentCaptor.getValue();
         mImsiDataSource = imsiDataSourceArgumentCaptor.getValue();
-        assertNotNull(mImsiDataSource);
+        mImsiDataSource.fromDeserialized(new HashMap<>());
+        assertNotNull(mCarrierInfoDataSource);
         mSubInfoList = new ArrayList<>();
         mSubInfoList.add(mDataSubscriptionInfo);
         mSubInfoList.add(mNonDataSubscriptionInfo);
@@ -193,7 +217,10 @@
                 .thenReturn(mDataTelephonyManager);
         when(mTelephonyManager.createForSubscriptionId(eq(NON_DATA_SUBID)))
                 .thenReturn(mNonDataTelephonyManager);
-        when(mTelephonyManager.getSimState(anyInt())).thenReturn(TelephonyManager.SIM_STATE_READY);
+        when(mTelephonyManager.getSimApplicationState(anyInt()))
+                .thenReturn(TelephonyManager.SIM_STATE_LOADED);
+        when(mCarrierConfigManager.getConfigForSubId(anyInt()))
+                .thenReturn(generateTestCarrierConfig(false));
         when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(mSubInfoList);
         mMockingSession = ExtendedMockito.mockitoSession().strictness(Strictness.LENIENT)
                 .mockStatic(SubscriptionManager.class).startMocking();
@@ -216,8 +243,10 @@
         when(mNonDataTelephonyManager.getSimCarrierIdName()).thenReturn(null);
         when(mNonDataTelephonyManager.getSimOperator())
                 .thenReturn(NON_DATA_OPERATOR_NUMERIC);
-        when(mDataTelephonyManager.getSimState()).thenReturn(TelephonyManager.SIM_STATE_READY);
-        when(mNonDataTelephonyManager.getSimState()).thenReturn(TelephonyManager.SIM_STATE_READY);
+        when(mDataTelephonyManager.getSimApplicationState())
+                .thenReturn(TelephonyManager.SIM_STATE_LOADED);
+        when(mNonDataTelephonyManager.getSimApplicationState())
+                .thenReturn(TelephonyManager.SIM_STATE_LOADED);
         when(mSubscriptionManager.getActiveSubscriptionIdList())
                 .thenReturn(new int[]{DATA_SUBID, NON_DATA_SUBID});
 
@@ -245,7 +274,11 @@
                 eq(R.string.wifi_suggestion_action_disallow_imsi_privacy_exemption_confirmation)))
                 .thenReturn("blah");
         mWifiCarrierInfoManager.addImsiExemptionUserApprovalListener(mListener);
-        mImsiDataSource.fromDeserialized(new HashMap<>());
+        verify(mSubscriptionManager).addOnSubscriptionsChangedListener(any(),
+                mListenerArgumentCaptor.capture());
+        mListenerArgumentCaptor.getValue().onSubscriptionsChanged();
+        mLooper.dispatchAll();
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(1000L);
     }
 
     @After
@@ -266,12 +299,13 @@
         ArgumentCaptor<BroadcastReceiver> receiver =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
         verify(mContext).registerReceiver(receiver.capture(), any(IntentFilter.class));
-        receiver.getValue().onReceive(mContext, new Intent("dummyIntent"));
+        receiver.getValue().onReceive(mContext, new Intent("placeholderIntent"));
         verify(mCarrierConfigManager, never()).getConfig();
     }
 
     private PersistableBundle generateTestCarrierConfig(boolean requiresImsiEncryption) {
         PersistableBundle bundle = new PersistableBundle();
+        bundle.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
         if (requiresImsiEncryption) {
             bundle.putInt(CarrierConfigManager.IMSI_KEY_AVAILABILITY_INT,
                     TelephonyManager.KEY_TYPE_WLAN);
@@ -310,6 +344,45 @@
     }
 
     /**
+     * Validate when KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL is change from true to false,
+     * carrier offload will disable for merged network.
+     */
+    @Test
+    public void receivedCarrierConfigChangedAllowMergedNetworkToFalse() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        mWifiCarrierInfoManager.addOnCarrierOffloadDisabledListener(
+                mOnCarrierOffloadDisabledListener);
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
+        String key = CarrierConfigManager.KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL;
+        bundle.putBoolean(key, true);
+        when(mCarrierConfigManager.getConfigForSubId(DATA_SUBID)).thenReturn(bundle);
+        ArgumentCaptor<BroadcastReceiver> receiver =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mContext).registerReceiver(receiver.capture(), any(IntentFilter.class));
+
+        receiver.getValue().onReceive(mContext,
+                new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
+        mLooper.dispatchAll();
+        assertTrue(mWifiCarrierInfoManager.areMergedCarrierWifiNetworksAllowed(DATA_SUBID));
+        verify(mOnCarrierOffloadDisabledListener, never()).onCarrierOffloadDisabled(anyInt(),
+                anyBoolean());
+
+        // When KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL change to false should send merged
+        // carrier offload disable callback.
+        PersistableBundle disallowedBundle = new PersistableBundle();
+        disallowedBundle.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
+        disallowedBundle.putBoolean(key, false);
+        when(mCarrierConfigManager.getConfigForSubId(DATA_SUBID)).thenReturn(disallowedBundle);
+        receiver.getValue().onReceive(mContext,
+                new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
+        mLooper.dispatchAll();
+        assertFalse(mWifiCarrierInfoManager.areMergedCarrierWifiNetworksAllowed(DATA_SUBID));
+        verify(mOnCarrierOffloadDisabledListener).onCarrierOffloadDisabled(eq(DATA_SUBID),
+                eq(true));
+    }
+
+    /**
      * Verify the IMSI encryption is cleared when the configuration in CarrierConfig is removed.
      */
     @Test
@@ -324,6 +397,7 @@
 
         receiver.getValue().onReceive(mContext,
                 new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
+        mLooper.dispatchAll();
 
         assertTrue(mWifiCarrierInfoManager.requiresImsiEncryption(DATA_SUBID));
         assertTrue(mWifiCarrierInfoManager.requiresImsiEncryption(NON_DATA_SUBID));
@@ -334,6 +408,7 @@
                 .thenReturn(generateTestCarrierConfig(false));
         receiver.getValue().onReceive(mContext,
                 new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
+        mLooper.dispatchAll();
 
         assertFalse(mWifiCarrierInfoManager.requiresImsiEncryption(DATA_SUBID));
         assertFalse(mWifiCarrierInfoManager.requiresImsiEncryption(NON_DATA_SUBID));
@@ -360,6 +435,7 @@
         ContentObserver observer = observerCaptor.getValue();
 
         observer.onChange(false);
+        mLooper.dispatchAll();
 
         assertTrue(mWifiCarrierInfoManager.requiresImsiEncryption(DATA_SUBID));
         assertFalse(mWifiCarrierInfoManager.isImsiEncryptionInfoAvailable(DATA_SUBID));
@@ -368,6 +444,7 @@
                 .thenReturn(mock(ImsiEncryptionInfo.class));
 
         observer.onChange(false);
+        mLooper.dispatchAll();
 
         assertTrue(mWifiCarrierInfoManager.requiresImsiEncryption(DATA_SUBID));
         assertTrue(mWifiCarrierInfoManager.isImsiEncryptionInfoAvailable(DATA_SUBID));
@@ -395,6 +472,7 @@
         ContentObserver observer = observerCaptor.getValue();
 
         observer.onChange(false);
+        mLooper.dispatchAll();
 
         assertTrue(mWifiCarrierInfoManager.isImsiEncryptionInfoAvailable(DATA_SUBID));
         assertTrue(mWifiCarrierInfoManager.isImsiEncryptionInfoAvailable(NON_DATA_SUBID));
@@ -405,6 +483,7 @@
                 .getCarrierInfoForImsiEncryption(TelephonyManager.KEY_TYPE_WLAN)).thenReturn(null);
 
         observer.onChange(false);
+        mLooper.dispatchAll();
 
         assertFalse(mWifiCarrierInfoManager.isImsiEncryptionInfoAvailable(DATA_SUBID));
         assertFalse(mWifiCarrierInfoManager.isImsiEncryptionInfoAvailable(NON_DATA_SUBID));
@@ -416,7 +495,6 @@
                 "13214561234567890@wlan.mnc456.mcc321.3gppnetwork.org", "");
 
         when(mDataTelephonyManager.getSubscriberId()).thenReturn("3214561234567890");
-        when(mDataTelephonyManager.getSimState()).thenReturn(TelephonyManager.SIM_STATE_READY);
         when(mDataTelephonyManager.getSimOperator()).thenReturn("321456");
         when(mDataTelephonyManager.getCarrierInfoForImsiEncryption(anyInt())).thenReturn(null);
         WifiConfiguration simConfig =
@@ -432,6 +510,7 @@
         peapSimConfig.carrierId = DATA_CARRIER_ID;
 
         assertEquals(expectedIdentity, mWifiCarrierInfoManager.getSimIdentity(peapSimConfig));
+        verify(mDataTelephonyManager, never()).getCarrierInfoForImsiEncryption(anyInt());
     }
 
     @Test
@@ -440,7 +519,6 @@
                 "03214561234567890@wlan.mnc456.mcc321.3gppnetwork.org", "");
         when(mDataTelephonyManager.getSubscriberId()).thenReturn("3214561234567890");
 
-        when(mDataTelephonyManager.getSimState()).thenReturn(TelephonyManager.SIM_STATE_READY);
         when(mDataTelephonyManager.getSimOperator()).thenReturn("321456");
         when(mDataTelephonyManager.getCarrierInfoForImsiEncryption(anyInt())).thenReturn(null);
         WifiConfiguration akaConfig =
@@ -456,6 +534,7 @@
         peapAkaConfig.carrierId = DATA_CARRIER_ID;
 
         assertEquals(expectedIdentity, mWifiCarrierInfoManager.getSimIdentity(peapAkaConfig));
+        verify(mDataTelephonyManager, never()).getCarrierInfoForImsiEncryption(anyInt());
     }
 
     @Test
@@ -464,7 +543,6 @@
                 "63214561234567890@wlan.mnc456.mcc321.3gppnetwork.org", "");
 
         when(mDataTelephonyManager.getSubscriberId()).thenReturn("3214561234567890");
-        when(mDataTelephonyManager.getSimState()).thenReturn(TelephonyManager.SIM_STATE_READY);
         when(mDataTelephonyManager.getSimOperator()).thenReturn("321456");
         when(mDataTelephonyManager.getCarrierInfoForImsiEncryption(anyInt())).thenReturn(null);
         WifiConfiguration akaPConfig =
@@ -480,6 +558,7 @@
         peapAkaPConfig.carrierId = DATA_CARRIER_ID;
 
         assertEquals(expectedIdentity, mWifiCarrierInfoManager.getSimIdentity(peapAkaPConfig));
+        verify(mDataTelephonyManager, never()).getCarrierInfoForImsiEncryption(anyInt());
     }
 
     /**
@@ -497,6 +576,8 @@
         String encryptedIdentity = "\0" + encryptedImsi;
         final Pair<String, String> expectedIdentity = Pair.create(permanentIdentity,
                 encryptedIdentity);
+        WifiCarrierInfoManager spyTu = spy(mWifiCarrierInfoManager);
+        doReturn(true).when(spyTu).requiresImsiEncryption(DATA_SUBID);
 
         // static mocking
         MockitoSession session = ExtendedMockito.mockitoSession().mockStatic(
@@ -505,7 +586,6 @@
             when(Cipher.getInstance(anyString())).thenReturn(cipher);
             when(cipher.doFinal(any(byte[].class))).thenReturn(permanentIdentity.getBytes());
             when(mDataTelephonyManager.getSubscriberId()).thenReturn(imsi);
-            when(mDataTelephonyManager.getSimState()).thenReturn(TelephonyManager.SIM_STATE_READY);
             when(mDataTelephonyManager.getSimOperator()).thenReturn("321456");
             ImsiEncryptionInfo info = new ImsiEncryptionInfo("321", "456",
                     TelephonyManager.KEY_TYPE_WLAN, null, key, null);
@@ -517,7 +597,7 @@
                             WifiEnterpriseConfig.Phase2.NONE);
             config.carrierId = DATA_CARRIER_ID;
 
-            assertEquals(expectedIdentity, mWifiCarrierInfoManager.getSimIdentity(config));
+            assertEquals(expectedIdentity, spyTu.getSimIdentity(config));
         } finally {
             session.finishMocking();
         }
@@ -540,20 +620,21 @@
             when(Cipher.getInstance(anyString())).thenReturn(cipher);
             when(cipher.doFinal(any(byte[].class))).thenThrow(BadPaddingException.class);
             when(mDataTelephonyManager.getSubscriberId()).thenReturn(imsi);
-            when(mDataTelephonyManager.getSimState()).thenReturn(TelephonyManager.SIM_STATE_READY);
             when(mDataTelephonyManager.getSimOperator()).thenReturn("321456");
             ImsiEncryptionInfo info = new ImsiEncryptionInfo("321", "456",
                     TelephonyManager.KEY_TYPE_WLAN, keyIdentifier, (PublicKey) null, null);
             when(mDataTelephonyManager.getCarrierInfoForImsiEncryption(
                     eq(TelephonyManager.KEY_TYPE_WLAN)))
                     .thenReturn(info);
+            WifiCarrierInfoManager spyTu = spy(mWifiCarrierInfoManager);
+            doReturn(true).when(spyTu).requiresImsiEncryption(DATA_SUBID);
 
             WifiConfiguration config =
                     WifiConfigurationTestUtil.createEapNetwork(WifiEnterpriseConfig.Eap.AKA,
                             WifiEnterpriseConfig.Phase2.NONE);
             config.carrierId = DATA_CARRIER_ID;
 
-            assertNull(mWifiCarrierInfoManager.getSimIdentity(config));
+            assertNull(spyTu.getSimIdentity(config));
         } finally {
             session.finishMocking();
         }
@@ -565,7 +646,6 @@
                 "1321560123456789@wlan.mnc056.mcc321.3gppnetwork.org", "");
 
         when(mDataTelephonyManager.getSubscriberId()).thenReturn("321560123456789");
-        when(mDataTelephonyManager.getSimState()).thenReturn(TelephonyManager.SIM_STATE_READY);
         when(mDataTelephonyManager.getSimOperator()).thenReturn("32156");
         when(mDataTelephonyManager.getCarrierInfoForImsiEncryption(anyInt())).thenReturn(null);
         WifiConfiguration config =
@@ -582,7 +662,6 @@
                 "13214560123456789@wlan.mnc456.mcc321.3gppnetwork.org", "");
 
         when(mDataTelephonyManager.getSubscriberId()).thenReturn("3214560123456789");
-        when(mDataTelephonyManager.getSimState()).thenReturn(TelephonyManager.SIM_STATE_UNKNOWN);
         when(mDataTelephonyManager.getSimOperator()).thenReturn(null);
         when(mDataTelephonyManager.getCarrierInfoForImsiEncryption(anyInt())).thenReturn(null);
         WifiConfiguration config =
@@ -596,7 +675,6 @@
     @Test
     public void getSimIdentityNonTelephonyConfig() {
         when(mDataTelephonyManager.getSubscriberId()).thenReturn("321560123456789");
-        when(mDataTelephonyManager.getSimState()).thenReturn(TelephonyManager.SIM_STATE_READY);
         when(mDataTelephonyManager.getSimOperator()).thenReturn("32156");
 
         assertEquals(null,
@@ -915,7 +993,6 @@
     public void getAnonymousIdentityWithSim() {
         String mccmnc = "123456";
         String expectedIdentity = ANONYMOUS_IDENTITY;
-        when(mDataTelephonyManager.getSimState()).thenReturn(TelephonyManager.SIM_STATE_READY);
         when(mDataTelephonyManager.getSimOperator()).thenReturn(mccmnc);
         WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork(
                 WifiEnterpriseConfig.Eap.AKA, WifiEnterpriseConfig.Phase2.NONE);
@@ -929,7 +1006,8 @@
      */
     @Test
     public void getAnonymousIdentityWithoutSim() {
-        when(mDataTelephonyManager.getSimState()).thenReturn(TelephonyManager.SIM_STATE_ABSENT);
+        when(mDataTelephonyManager.getSimApplicationState())
+                .thenReturn(TelephonyManager.SIM_STATE_NOT_READY);
         WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork(
                 WifiEnterpriseConfig.Eap.AKA, WifiEnterpriseConfig.Phase2.NONE);
 
@@ -947,7 +1025,7 @@
         when(subInfo2.getSubscriptionId()).thenReturn(NON_DATA_SUBID);
         when(mSubscriptionManager.getActiveSubscriptionInfoList())
                 .thenReturn(Arrays.asList(subInfo1, subInfo2));
-        assertTrue(mWifiCarrierInfoManager.isSimPresent(DATA_SUBID));
+        assertTrue(mWifiCarrierInfoManager.isSimReady(DATA_SUBID));
     }
 
     /**
@@ -957,18 +1035,22 @@
     public void isSimPresentWithInvalidOrEmptySubscriptionIdList() {
         when(mSubscriptionManager.getActiveSubscriptionInfoList())
                 .thenReturn(Collections.emptyList());
+        mListenerArgumentCaptor.getValue().onSubscriptionsChanged();
+        mLooper.dispatchAll();
 
-        assertFalse(mWifiCarrierInfoManager.isSimPresent(DATA_SUBID));
+        assertFalse(mWifiCarrierInfoManager.isSimReady(DATA_SUBID));
 
         SubscriptionInfo subInfo = mock(SubscriptionInfo.class);
         when(subInfo.getSubscriptionId()).thenReturn(NON_DATA_SUBID);
         when(mSubscriptionManager.getActiveSubscriptionInfoList())
                 .thenReturn(Arrays.asList(subInfo));
-        assertFalse(mWifiCarrierInfoManager.isSimPresent(DATA_SUBID));
+        mListenerArgumentCaptor.getValue().onSubscriptionsChanged();
+        mLooper.dispatchAll();
+        assertFalse(mWifiCarrierInfoManager.isSimReady(DATA_SUBID));
     }
 
     /**
-     * Verity SIM is consider not present when SIM state is not ready
+     * Verify SIM is considered not present when SIM state is not ready
      */
     @Test
     public void isSimPresentWithValidSubscriptionIdListWithSimStateNotReady() {
@@ -978,9 +1060,30 @@
         when(subInfo2.getSubscriptionId()).thenReturn(NON_DATA_SUBID);
         when(mSubscriptionManager.getActiveSubscriptionInfoList())
                 .thenReturn(Arrays.asList(subInfo1, subInfo2));
-        when(mTelephonyManager.getSimState(anyInt()))
+        when(mDataTelephonyManager.getSimApplicationState())
                 .thenReturn(TelephonyManager.SIM_STATE_NETWORK_LOCKED);
-        assertFalse(mWifiCarrierInfoManager.isSimPresent(DATA_SUBID));
+        assertFalse(mWifiCarrierInfoManager.isSimReady(DATA_SUBID));
+    }
+
+    /**
+     * Verify SIM is considered not present when carrierConfig is not ready.
+     */
+    @Test
+    public void isSimPresentWithValidSubscriptionIdListWithCarrierConfigNotReady() {
+        SubscriptionInfo subInfo1 = mock(SubscriptionInfo.class);
+        when(subInfo1.getSubscriptionId()).thenReturn(DATA_SUBID);
+        SubscriptionInfo subInfo2 = mock(SubscriptionInfo.class);
+        when(subInfo2.getSubscriptionId()).thenReturn(NON_DATA_SUBID);
+        when(mSubscriptionManager.getActiveSubscriptionInfoList())
+                .thenReturn(Arrays.asList(subInfo1, subInfo2));
+        when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(null);
+        ArgumentCaptor<BroadcastReceiver> receiver =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mContext).registerReceiver(receiver.capture(), any(IntentFilter.class));
+        receiver.getValue().onReceive(mContext,
+                new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
+
+        assertFalse(mWifiCarrierInfoManager.isSimReady(DATA_SUBID));
     }
 
     /**
@@ -991,12 +1094,15 @@
         WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork(
                 WifiEnterpriseConfig.Eap.AKA, WifiEnterpriseConfig.Phase2.NONE);
         when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(null);
-        when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[0]);
+        mListenerArgumentCaptor.getValue().onSubscriptionsChanged();
+        mLooper.dispatchAll();
 
         assertEquals(INVALID_SUBID, mWifiCarrierInfoManager.getBestMatchSubscriptionId(config));
 
         when(mSubscriptionManager.getActiveSubscriptionInfoList())
                 .thenReturn(Collections.emptyList());
+        mListenerArgumentCaptor.getValue().onSubscriptionsChanged();
+        mLooper.dispatchAll();
 
         assertEquals(INVALID_SUBID, mWifiCarrierInfoManager.getBestMatchSubscriptionId(config));
     }
@@ -1087,7 +1193,7 @@
         when(mSubscriptionManager.getActiveSubscriptionInfoList())
                 .thenReturn(Collections.emptyList());
 
-        assertNull(mWifiCarrierInfoManager.getMatchingImsi(DEACTIVE_CARRIER_ID));
+        assertNull(mWifiCarrierInfoManager.getMatchingImsiBySubId(INVALID_SUBID));
     }
 
     /**
@@ -1100,7 +1206,7 @@
         doReturn(true).when(spyTu).requiresImsiEncryption(DATA_SUBID);
         doReturn(false).when(spyTu).isImsiEncryptionInfoAvailable(DATA_SUBID);
 
-        assertNull(spyTu.getMatchingImsi(DATA_CARRIER_ID));
+        assertNull(spyTu.getMatchingImsiBySubId(DATA_SUBID));
     }
 
     /**
@@ -1110,7 +1216,7 @@
     @Test
     public void getMatchingImsiCarrierIdWithValidCarrierId() {
         assertEquals(DATA_FULL_IMSI,
-                mWifiCarrierInfoManager.getMatchingImsi(DATA_CARRIER_ID));
+                mWifiCarrierInfoManager.getMatchingImsiBySubId(DATA_SUBID));
     }
 
     /**
@@ -1119,11 +1225,15 @@
     @Test
     public void getMatchingImsiCarrierIdWithEmptyActiveSubscriptionInfoList() {
         when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(null);
+        mListenerArgumentCaptor.getValue().onSubscriptionsChanged();
+        mLooper.dispatchAll();
 
         assertNull(mWifiCarrierInfoManager.getMatchingImsiCarrierId(MATCH_PREFIX_IMSI));
 
         when(mSubscriptionManager.getActiveSubscriptionInfoList())
                 .thenReturn(Collections.emptyList());
+        mListenerArgumentCaptor.getValue().onSubscriptionsChanged();
+        mLooper.dispatchAll();
 
         assertNull(mWifiCarrierInfoManager.getMatchingImsiCarrierId(MATCH_PREFIX_IMSI));
     }
@@ -1386,7 +1496,6 @@
 
         String mccmnc = "123456";
         String expectedIdentity = methodStr + ANONYMOUS_IDENTITY;
-        when(mDataTelephonyManager.getSimState()).thenReturn(TelephonyManager.SIM_STATE_READY);
         when(mDataTelephonyManager.getSimOperator()).thenReturn(mccmnc);
         WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork(
                 method, WifiEnterpriseConfig.Phase2.NONE);
@@ -1482,12 +1591,12 @@
     }
 
     /**
-     * Verify getCarrierNameforSubId returns right value.
+     * Verify getCarrierNameForSubId returns right value.
      */
     @Test
     public void getCarrierNameFromSubId() {
-        assertEquals(CARRIER_NAME, mWifiCarrierInfoManager.getCarrierNameforSubId(DATA_SUBID));
-        assertNull(mWifiCarrierInfoManager.getCarrierNameforSubId(NON_DATA_SUBID));
+        assertEquals(CARRIER_NAME, mWifiCarrierInfoManager.getCarrierNameForSubId(DATA_SUBID));
+        assertNull(mWifiCarrierInfoManager.getCarrierNameForSubId(NON_DATA_SUBID));
     }
 
     @Test
@@ -1535,29 +1644,16 @@
         // Simulate user clicking on allow in the notification.
         sendBroadcastForUserActionOnImsi(NOTIFICATION_USER_ALLOWED_CARRIER_INTENT_ACTION,
                 CARRIER_NAME, DATA_CARRIER_ID);
-        verify(mNotificationManger).cancel(SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE);
+        verify(mWifiNotificationManager).cancel(SystemMessage.NOTE_CARRIER_SUGGESTION_AVAILABLE);
         verify(mWifiMetrics).addUserApprovalCarrierUiReaction(
                 WifiCarrierInfoManager.ACTION_USER_ALLOWED_CARRIER, false);
-        validateUserApprovalDialog(CARRIER_NAME);
-
-        // Simulate user clicking on allow in the dialog.
-        ArgumentCaptor<DialogInterface.OnClickListener> clickListenerCaptor =
-                ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
-        verify(mAlertDialogBuilder, atLeastOnce()).setPositiveButton(
-                any(), clickListenerCaptor.capture());
-        assertNotNull(clickListenerCaptor.getValue());
-        clickListenerCaptor.getValue().onClick(mAlertDialog, 0);
-        mLooper.dispatchAll();
-        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
-        verify(mContext).sendBroadcast(intentCaptor.capture());
-        assertEquals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS, intentCaptor.getValue().getAction());
         verify(mWifiConfigManager).saveToStore(true);
-        assertTrue(mImsiDataSource.hasNewDataToSerialize());
+        assertTrue(mCarrierInfoDataSource.hasNewDataToSerialize());
         assertTrue(mWifiCarrierInfoManager
                 .hasUserApprovedImsiPrivacyExemptionForCarrier(DATA_CARRIER_ID));
         verify(mListener).onUserAllowed(DATA_CARRIER_ID);
         verify(mWifiMetrics).addUserApprovalCarrierUiReaction(
-                WifiCarrierInfoManager.ACTION_USER_ALLOWED_CARRIER, true);
+                WifiCarrierInfoManager.ACTION_USER_ALLOWED_CARRIER, false);
     }
 
     @Test
@@ -1578,11 +1674,11 @@
         // Simulate user clicking on disallow in the notification.
         sendBroadcastForUserActionOnImsi(NOTIFICATION_USER_DISALLOWED_CARRIER_INTENT_ACTION,
                 CARRIER_NAME, DATA_CARRIER_ID);
-        verify(mNotificationManger).cancel(SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE);
+        verify(mWifiNotificationManager).cancel(SystemMessage.NOTE_CARRIER_SUGGESTION_AVAILABLE);
         verify(mAlertDialog, never()).show();
 
         verify(mWifiConfigManager).saveToStore(true);
-        assertTrue(mImsiDataSource.hasNewDataToSerialize());
+        assertTrue(mCarrierInfoDataSource.hasNewDataToSerialize());
         assertFalse(mWifiCarrierInfoManager
                 .hasUserApprovedImsiPrivacyExemptionForCarrier(DATA_CARRIER_ID));
         verify(mListener, never()).onUserAllowed(DATA_CARRIER_ID);
@@ -1607,21 +1703,21 @@
         validateImsiProtectionNotification(CARRIER_NAME);
         //Simulate user dismissal the notification
         sendBroadcastForUserActionOnImsi(NOTIFICATION_USER_DISMISSED_INTENT_ACTION,
-                CARRIER_NAME, DATA_SUBID);
+                CARRIER_NAME, DATA_CARRIER_ID);
         verify(mWifiMetrics).addUserApprovalCarrierUiReaction(
                 WifiCarrierInfoManager.ACTION_USER_DISMISS, false);
-        reset(mNotificationManger);
+        reset(mWifiNotificationManager);
         // No Notification is active, should send notification again.
         mWifiCarrierInfoManager.sendImsiProtectionExemptionNotificationIfRequired(DATA_CARRIER_ID);
         validateImsiProtectionNotification(CARRIER_NAME);
-        reset(mNotificationManger);
+        reset(mWifiNotificationManager);
 
         // As there is notification is active, should not send notification again.
         sendBroadcastForUserActionOnImsi(NOTIFICATION_USER_DISMISSED_INTENT_ACTION,
-                CARRIER_NAME, DATA_SUBID);
-        verifyNoMoreInteractions(mNotificationManger);
+                CARRIER_NAME, DATA_CARRIER_ID);
+        verifyNoMoreInteractions(mWifiNotificationManager);
         verify(mWifiConfigManager, never()).saveToStore(true);
-        assertFalse(mImsiDataSource.hasNewDataToSerialize());
+        assertFalse(mCarrierInfoDataSource.hasNewDataToSerialize());
         assertFalse(mWifiCarrierInfoManager
                 .hasUserApprovedImsiPrivacyExemptionForCarrier(DATA_CARRIER_ID));
         verify(mListener, never()).onUserAllowed(DATA_CARRIER_ID);
@@ -1642,12 +1738,10 @@
 
         mWifiCarrierInfoManager.sendImsiProtectionExemptionNotificationIfRequired(DATA_CARRIER_ID);
         validateImsiProtectionNotification(CARRIER_NAME);
-        // Simulate user clicking on allow in the notification.
-        sendBroadcastForUserActionOnImsi(NOTIFICATION_USER_ALLOWED_CARRIER_INTENT_ACTION,
-                CARRIER_NAME, DATA_SUBID);
-        verify(mNotificationManger).cancel(SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE);
-        verify(mWifiMetrics).addUserApprovalCarrierUiReaction(
-                WifiCarrierInfoManager.ACTION_USER_ALLOWED_CARRIER, false);
+        // Simulate user clicking on the notification.
+        sendBroadcastForUserActionOnImsi(NOTIFICATION_USER_CLICKED_INTENT_ACTION,
+                CARRIER_NAME, DATA_CARRIER_ID);
+        verify(mWifiNotificationManager).cancel(SystemMessage.NOTE_CARRIER_SUGGESTION_AVAILABLE);
         validateUserApprovalDialog(CARRIER_NAME);
 
         // Simulate user clicking on disallow in the dialog.
@@ -1662,7 +1756,7 @@
         verify(mContext).sendBroadcast(intentCaptor.capture());
         assertEquals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS, intentCaptor.getValue().getAction());
         verify(mWifiConfigManager).saveToStore(true);
-        assertTrue(mImsiDataSource.hasNewDataToSerialize());
+        assertTrue(mCarrierInfoDataSource.hasNewDataToSerialize());
         assertFalse(mWifiCarrierInfoManager
                 .hasUserApprovedImsiPrivacyExemptionForCarrier(DATA_CARRIER_ID));
         verify(mListener, never()).onUserAllowed(DATA_CARRIER_ID);
@@ -1685,11 +1779,10 @@
 
         mWifiCarrierInfoManager.sendImsiProtectionExemptionNotificationIfRequired(DATA_CARRIER_ID);
         validateImsiProtectionNotification(CARRIER_NAME);
-        sendBroadcastForUserActionOnImsi(NOTIFICATION_USER_ALLOWED_CARRIER_INTENT_ACTION,
-                CARRIER_NAME, DATA_SUBID);
-        verify(mNotificationManger).cancel(SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE);
-        verify(mWifiMetrics).addUserApprovalCarrierUiReaction(
-                WifiCarrierInfoManager.ACTION_USER_ALLOWED_CARRIER, false);
+        // Simulate user clicking on the notification.
+        sendBroadcastForUserActionOnImsi(NOTIFICATION_USER_CLICKED_INTENT_ACTION,
+                CARRIER_NAME, DATA_CARRIER_ID);
+        verify(mWifiNotificationManager).cancel(SystemMessage.NOTE_CARRIER_SUGGESTION_AVAILABLE);
         validateUserApprovalDialog(CARRIER_NAME);
 
         // Simulate user clicking on dismissal in the dialog.
@@ -1709,7 +1802,7 @@
         validateImsiProtectionNotification(CARRIER_NAME);
 
         verify(mWifiConfigManager, never()).saveToStore(true);
-        assertFalse(mImsiDataSource.hasNewDataToSerialize());
+        assertFalse(mCarrierInfoDataSource.hasNewDataToSerialize());
         assertFalse(mWifiCarrierInfoManager
                 .hasUserApprovedImsiPrivacyExemptionForCarrier(DATA_CARRIER_ID));
         verify(mListener, never()).onUserAllowed(DATA_CARRIER_ID);
@@ -1718,6 +1811,45 @@
     }
 
     @Test
+    public void testSendImsiProtectionExemptionDialogWithUserAllowed() {
+        // Setup carrier without IMSI privacy protection
+        when(mCarrierConfigManager.getConfigForSubId(DATA_SUBID))
+                .thenReturn(generateTestCarrierConfig(false));
+        ArgumentCaptor<BroadcastReceiver> receiver =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mContext).registerReceiver(receiver.capture(), any(IntentFilter.class));
+
+        receiver.getValue().onReceive(mContext,
+                new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
+        assertFalse(mWifiCarrierInfoManager.requiresImsiEncryption(DATA_SUBID));
+
+        mWifiCarrierInfoManager.sendImsiProtectionExemptionNotificationIfRequired(DATA_CARRIER_ID);
+        validateImsiProtectionNotification(CARRIER_NAME);
+        // Simulate user clicking on the notification.
+        sendBroadcastForUserActionOnImsi(NOTIFICATION_USER_CLICKED_INTENT_ACTION,
+                CARRIER_NAME, DATA_CARRIER_ID);
+        verify(mWifiNotificationManager).cancel(SystemMessage.NOTE_CARRIER_SUGGESTION_AVAILABLE);
+        validateUserApprovalDialog(CARRIER_NAME);
+
+        // Simulate user clicking on allow in the dialog.
+        ArgumentCaptor<DialogInterface.OnClickListener> clickListenerCaptor =
+                ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
+        verify(mAlertDialogBuilder, atLeastOnce()).setPositiveButton(
+                any(), clickListenerCaptor.capture());
+        assertNotNull(clickListenerCaptor.getValue());
+        clickListenerCaptor.getValue().onClick(mAlertDialog, 0);
+        mLooper.dispatchAll();
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).sendBroadcast(intentCaptor.capture());
+        assertEquals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS, intentCaptor.getValue().getAction());
+        verify(mWifiConfigManager).saveToStore(true);
+        assertTrue(mCarrierInfoDataSource.hasNewDataToSerialize());
+        verify(mListener).onUserAllowed(DATA_CARRIER_ID);
+        verify(mWifiMetrics).addUserApprovalCarrierUiReaction(
+                WifiCarrierInfoManager.ACTION_USER_ALLOWED_CARRIER, true);
+    }
+
+    @Test
     public void testUserDataStoreIsNotLoadedNotificationWillNotBeSent() {
         // reset data source to unloaded state.
         mImsiDataSource.reset();
@@ -1733,7 +1865,7 @@
         assertFalse(mWifiCarrierInfoManager.requiresImsiEncryption(DATA_SUBID));
 
         mWifiCarrierInfoManager.sendImsiProtectionExemptionNotificationIfRequired(DATA_CARRIER_ID);
-        verifyNoMoreInteractions(mNotificationManger);
+        verifyNoMoreInteractions(mWifiNotificationManager);
 
         // Loaded user data store, notification should be sent
         mImsiDataSource.fromDeserialized(new HashMap<>());
@@ -1741,9 +1873,92 @@
         validateImsiProtectionNotification(CARRIER_NAME);
     }
 
+    @Test
+    public void testCarrierConfigNotAvailableNotificationWillNotBeSent() {
+        // Setup carrier without IMSI privacy protection
+        when(mCarrierConfigManager.getConfigForSubId(DATA_SUBID))
+                .thenReturn(generateTestCarrierConfig(false));
+        ArgumentCaptor<BroadcastReceiver> receiver =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mContext).registerReceiver(receiver.capture(), any(IntentFilter.class));
+
+        receiver.getValue().onReceive(mContext,
+                new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
+        assertFalse(mWifiCarrierInfoManager.requiresImsiEncryption(DATA_SUBID));
+        // Carrier config for Non data carrier is not available, no notification will send.
+        mWifiCarrierInfoManager
+                .sendImsiProtectionExemptionNotificationIfRequired(NON_DATA_CARRIER_ID);
+        verifyNoMoreInteractions(mWifiNotificationManager);
+
+        mWifiCarrierInfoManager.sendImsiProtectionExemptionNotificationIfRequired(DATA_CARRIER_ID);
+        validateImsiProtectionNotification(CARRIER_NAME);
+    }
+
+    @Test
+    public void testImsiProtectionExemptionNotificationNotSentWhenCarrierNameIsInvalid() {
+        when(mCarrierConfigManager.getConfigForSubId(DATA_SUBID))
+                .thenReturn(generateTestCarrierConfig(false));
+        ArgumentCaptor<BroadcastReceiver> receiver =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mContext).registerReceiver(receiver.capture(), any(IntentFilter.class));
+
+        receiver.getValue().onReceive(mContext,
+                new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
+        assertFalse(mWifiCarrierInfoManager.requiresImsiEncryption(DATA_SUBID));
+        when(mDataTelephonyManager.getSimCarrierIdName()).thenReturn(null);
+        mWifiCarrierInfoManager.sendImsiProtectionExemptionNotificationIfRequired(DATA_CARRIER_ID);
+        verify(mWifiNotificationManager, never()).notify(
+                eq(SystemMessage.NOTE_CARRIER_SUGGESTION_AVAILABLE),
+                eq(mNotification));
+
+    }
+
+    @Test
+    public void verifySubIdAndCarrierIdMatching() {
+        assertTrue(mWifiCarrierInfoManager.isSubIdMatchingCarrierId(
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID, DATA_CARRIER_ID));
+        assertFalse(mWifiCarrierInfoManager.isSubIdMatchingCarrierId(
+                DATA_SUBID, TelephonyManager.UNKNOWN_CARRIER_ID));
+
+        assertTrue(mWifiCarrierInfoManager.isSubIdMatchingCarrierId(
+                DATA_SUBID, DATA_CARRIER_ID));
+        assertFalse(mWifiCarrierInfoManager.isSubIdMatchingCarrierId(
+                NON_DATA_SUBID, DATA_CARRIER_ID));
+    }
+
+    @Test
+    public void testSetAndGetUnmergedCarrierNetworkOffload() {
+        assertTrue(mWifiCarrierInfoManager.isCarrierNetworkOffloadEnabled(DATA_SUBID, false));
+        mWifiCarrierInfoManager.setCarrierNetworkOffloadEnabled(DATA_SUBID, false, false);
+        verify(mWifiConfigManager).saveToStore(true);
+        assertFalse(mWifiCarrierInfoManager.isCarrierNetworkOffloadEnabled(DATA_SUBID, false));
+    }
+
+    @Test
+    public void testSetAndGetMergedCarrierNetworkOffload() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        when(mDataTelephonyManager.isDataEnabled()).thenReturn(true);
+        ArgumentCaptor<WifiCarrierInfoManager.UserDataEnabledChangedListener> listenerCaptor =
+                ArgumentCaptor.forClass(
+                        WifiCarrierInfoManager.UserDataEnabledChangedListener.class);
+        // Check default value and verify listen is registered.
+        assertTrue(mWifiCarrierInfoManager.isCarrierNetworkOffloadEnabled(DATA_SUBID, true));
+        verify(mDataTelephonyManager).registerTelephonyCallback(any(), listenerCaptor.capture());
+
+        // Verify result will change with state changes
+        listenerCaptor.getValue().onDataEnabledChanged(false, DATA_ENABLED_REASON_USER);
+        assertFalse(mWifiCarrierInfoManager.isCarrierNetworkOffloadEnabled(DATA_SUBID, true));
+
+        listenerCaptor.getValue().onDataEnabledChanged(true, DATA_ENABLED_REASON_USER);
+        mWifiCarrierInfoManager.setCarrierNetworkOffloadEnabled(DATA_SUBID, true, false);
+        verify(mWifiConfigManager).saveToStore(true);
+        assertFalse(mWifiCarrierInfoManager.isCarrierNetworkOffloadEnabled(DATA_SUBID, true));
+
+    }
+
     private void validateImsiProtectionNotification(String carrierName) {
-        verify(mNotificationManger, atLeastOnce()).notify(
-                eq(SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE),
+        verify(mWifiNotificationManager, atLeastOnce()).notify(
+                eq(SystemMessage.NOTE_CARRIER_SUGGESTION_AVAILABLE),
                 eq(mNotification));
         ArgumentCaptor<CharSequence> contentCaptor =
                 ArgumentCaptor.forClass(CharSequence.class);
@@ -1778,4 +1993,195 @@
         assertNotNull(mBroadcastReceiverCaptor.getValue());
         mBroadcastReceiverCaptor.getValue().onReceive(mContext, intent);
     }
+
+    @Test
+    public void testSendRefreshUserProvisioningOnUnlockedUserSwitching() {
+        PackageManager mockPackageManager = mock(PackageManager.class);
+        when(mContext.getPackageManager()).thenReturn(mockPackageManager);
+        PackageInfo pi = new PackageInfo();
+        pi.packageName = "com.example.app";
+        List<PackageInfo> pis = new ArrayList<>() {{
+                add(pi);
+            }};
+        when(mockPackageManager.getPackagesHoldingPermissions(
+                eq(new String[] {android.Manifest.permission.NETWORK_CARRIER_PROVISIONING}),
+                anyInt())).thenReturn(pis);
+
+        mWifiCarrierInfoManager.onUnlockedUserSwitching(1);
+
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).sendBroadcastAsUser(
+                intentCaptor.capture(),
+                eq(UserHandle.CURRENT),
+                eq(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING));
+        Intent intent = intentCaptor.getValue();
+        assertEquals(intent.getAction(), WifiManager.ACTION_REFRESH_USER_PROVISIONING);
+    }
+
+    /**
+     * Verify that shouldDisableMacRandomization returns true if the SSID in the input config
+     * matches with the SSID list in CarrierConfigManager.
+     */
+    @Test
+    public void testShouldDisableMacRandomization() {
+        // Create 2 WifiConfigurations and mock CarrierConfigManager to include the SSID
+        // of the first one in the MAC randomization disabled list.
+        WifiConfiguration config1 = WifiConfigurationTestUtil.createOpenNetwork();
+        WifiConfiguration config2 = WifiConfigurationTestUtil.createOpenNetwork();
+        config1.carrierId = DATA_CARRIER_ID;
+        config1.subscriptionId = DATA_SUBID;
+        PersistableBundle bundle = new PersistableBundle();
+        PersistableBundle wifiBundle = new PersistableBundle();
+        // Add the first SSID and some garbage SSID to the exception list.
+        wifiBundle.putStringArray(
+                CarrierConfigManager.Wifi.KEY_SUGGESTION_SSID_LIST_WITH_MAC_RANDOMIZATION_DISABLED,
+                new String[]{
+                        WifiInfo.sanitizeSsid(config1.SSID),
+                        WifiInfo.sanitizeSsid(config2.SSID) + "_GARBAGE"});
+        wifiBundle.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
+        bundle.putAll(wifiBundle);
+        when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
+
+        if (SdkLevel.isAtLeastS()) {
+            // Verify MAC randomization is disable for config1, but not disabled for config2
+            assertFalse(mWifiCarrierInfoManager.shouldDisableMacRandomization(config2.SSID,
+                    config2.carrierId, config2.subscriptionId));
+            assertTrue(mWifiCarrierInfoManager.shouldDisableMacRandomization(config1.SSID,
+                    config1.carrierId, config1.subscriptionId));
+
+            // Verify getConfigForSubId is only called once since the CarrierConfig gets cached.
+            verify(mCarrierConfigManager).getConfigForSubId(anyInt());
+        } else {
+            // Verify MAC randomization is not disabled for either configuration.
+            assertFalse(mWifiCarrierInfoManager.shouldDisableMacRandomization(config2.SSID,
+                    config2.carrierId, config2.subscriptionId));
+            assertFalse(mWifiCarrierInfoManager.shouldDisableMacRandomization(config1.SSID,
+                    config1.carrierId, config1.subscriptionId));
+        }
+    }
+
+    /**
+     * Verify that shouldDisableMacRandomization returns false if the carrierId is not set.
+     */
+    @Test
+    public void testOnlyDisableMacRandomizationOnCarrierNetworks() {
+        // Create 2 WifiConfiguration, but only set the carrierId for the first config.
+        WifiConfiguration config1 = WifiConfigurationTestUtil.createOpenNetwork();
+        WifiConfiguration config2 = WifiConfigurationTestUtil.createOpenNetwork();
+        config1.carrierId = DATA_CARRIER_ID;
+        config1.subscriptionId = DATA_SUBID;
+        PersistableBundle bundle = new PersistableBundle();
+        PersistableBundle wifiBundle = new PersistableBundle();
+        // add both the first SSID and second SSID to the exception list.
+        wifiBundle.putStringArray(
+                CarrierConfigManager.Wifi.KEY_SUGGESTION_SSID_LIST_WITH_MAC_RANDOMIZATION_DISABLED,
+                new String[]{
+                        WifiInfo.sanitizeSsid(config1.SSID),
+                        WifiInfo.sanitizeSsid(config2.SSID)});
+        wifiBundle.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
+        bundle.putAll(wifiBundle);
+        when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
+
+        if (SdkLevel.isAtLeastS()) {
+            // Verify MAC randomization is disable for config1, but not disabled for config2
+            assertTrue(mWifiCarrierInfoManager.shouldDisableMacRandomization(config1.SSID,
+                    config1.carrierId, config1.subscriptionId));
+            assertFalse(mWifiCarrierInfoManager.shouldDisableMacRandomization(config2.SSID,
+                    config2.carrierId, config2.subscriptionId));
+            // Verify getConfigForSubId is only called once since the CarrierConfig gets cached.
+            verify(mCarrierConfigManager).getConfigForSubId(anyInt());
+        } else {
+            // Verify MAC randomization is not disabled for either configuration.
+            assertFalse(mWifiCarrierInfoManager.shouldDisableMacRandomization(config1.SSID,
+                    config1.carrierId, config1.subscriptionId));
+            assertFalse(mWifiCarrierInfoManager.shouldDisableMacRandomization(config2.SSID,
+                    config2.carrierId, config2.subscriptionId));
+        }
+    }
+
+    @Test
+    public void testAllowCarrierWifiForCarrier() {
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
+        String key = CarrierConfigManager.KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL;
+        int subId = DATA_SUBID;
+        when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
+
+        if (SdkLevel.isAtLeastS()) {
+            // not allowed: false
+            bundle.putBoolean(key, false);
+            assertFalse(
+                    mWifiCarrierInfoManager.areMergedCarrierWifiNetworksAllowed(subId));
+
+            // allowed: true
+            bundle.putBoolean(key, true);
+            assertTrue(
+                    mWifiCarrierInfoManager.areMergedCarrierWifiNetworksAllowed(subId));
+
+            // no key
+            bundle.clear();
+            assertFalse(
+                    mWifiCarrierInfoManager.areMergedCarrierWifiNetworksAllowed(subId));
+        } else {
+            assertFalse(
+                    mWifiCarrierInfoManager.areMergedCarrierWifiNetworksAllowed(subId));
+        }
+    }
+
+    @Test
+    public void testResetNotification() {
+        mWifiCarrierInfoManager.resetNotification();
+        verify(mWifiNotificationManager).cancel(SystemMessage.NOTE_CARRIER_SUGGESTION_AVAILABLE);
+    }
+
+    @Test
+    public void testClear() {
+        when(mDataTelephonyManager.isDataEnabled()).thenReturn(true);
+        mWifiCarrierInfoManager.setHasUserApprovedImsiPrivacyExemptionForCarrier(
+                true, DATA_CARRIER_ID);
+        assertTrue(mWifiCarrierInfoManager.isCarrierNetworkOffloadEnabled(DATA_SUBID, true));
+        assertTrue(mWifiCarrierInfoManager.isCarrierNetworkOffloadEnabled(NON_DATA_SUBID, false));
+        mWifiCarrierInfoManager.setCarrierNetworkOffloadEnabled(DATA_SUBID, true, false);
+        mWifiCarrierInfoManager.setCarrierNetworkOffloadEnabled(NON_DATA_SUBID, false, false);
+        // Verify values.
+        assertTrue(mWifiCarrierInfoManager
+                .hasUserApprovedImsiPrivacyExemptionForCarrier(DATA_CARRIER_ID));
+        assertFalse(mWifiCarrierInfoManager.isCarrierNetworkOffloadEnabled(DATA_SUBID, true));
+        assertFalse(mWifiCarrierInfoManager.isCarrierNetworkOffloadEnabled(NON_DATA_SUBID, false));
+        // Now clear everything.
+        mWifiCarrierInfoManager.clear();
+
+        verify(mWifiNotificationManager).cancel(SystemMessage.NOTE_CARRIER_SUGGESTION_AVAILABLE);
+        if (SdkLevel.isAtLeastS()) {
+            verify(mDataTelephonyManager).unregisterTelephonyCallback(any());
+        }
+
+        // Verify restore to default value.
+        assertFalse(mWifiCarrierInfoManager
+                .hasUserApprovedImsiPrivacyExemptionForCarrier(DATA_CARRIER_ID));
+        assertTrue(mWifiCarrierInfoManager.isCarrierNetworkOffloadEnabled(DATA_SUBID, true));
+        assertTrue(mWifiCarrierInfoManager.isCarrierNetworkOffloadEnabled(NON_DATA_SUBID, false));
+
+        // Verify active subscription info is not clear
+        assertEquals(DATA_SUBID, mWifiCarrierInfoManager.getMatchingSubId(DATA_CARRIER_ID));
+    }
+
+    @Test
+    public void testOnCarrierOffloadDisabledListener() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        mWifiCarrierInfoManager.addOnCarrierOffloadDisabledListener(
+                mOnCarrierOffloadDisabledListener);
+        mWifiCarrierInfoManager.isCarrierNetworkOffloadEnabled(DATA_SUBID, true);
+        ArgumentCaptor<WifiCarrierInfoManager.UserDataEnabledChangedListener> captor =
+                ArgumentCaptor.forClass(WifiCarrierInfoManager.UserDataEnabledChangedListener
+                        .class);
+        verify(mDataTelephonyManager).registerTelephonyCallback(any(), captor.capture());
+
+        mWifiCarrierInfoManager.setCarrierNetworkOffloadEnabled(DATA_SUBID, true, false);
+        verify(mOnCarrierOffloadDisabledListener).onCarrierOffloadDisabled(DATA_SUBID, true);
+
+        captor.getValue().onDataEnabledChanged(false, DATA_ENABLED_REASON_USER);
+        verify(mOnCarrierOffloadDisabledListener, times(2))
+                .onCarrierOffloadDisabled(DATA_SUBID, true);
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiCarrierInfoStoreManagerDataTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiCarrierInfoStoreManagerDataTest.java
new file mode 100644
index 0000000..d0409be
--- /dev/null
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiCarrierInfoStoreManagerDataTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+public class WifiCarrierInfoStoreManagerDataTest {
+    private static final int TEST_CARRIER_ID = 1911;
+    private static final int TEST_SUB_ID = 3;
+
+    private @Mock WifiCarrierInfoStoreManagerData.DataSource mDataSource;
+    private WifiCarrierInfoStoreManagerData mWifiCarrierInfoStoreManagerData;
+
+    private final Map<Integer, Boolean> mImsiPrivacyProtectionExemptionMap = new HashMap<>();
+    private final Map<Integer, Boolean> mMergedCarrierOffloadMap = new HashMap<>();
+    private final Map<Integer, Boolean> mUnmergedCarrierOffloadMap = new HashMap<>();
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mWifiCarrierInfoStoreManagerData =
+                new WifiCarrierInfoStoreManagerData(mDataSource);
+    }
+
+    /**
+     * Helper function for serializing configuration data to a XML block.
+     */
+    private byte[] serializeData() throws Exception {
+        final XmlSerializer out = new FastXmlSerializer();
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        out.setOutput(outputStream, StandardCharsets.UTF_8.name());
+        mWifiCarrierInfoStoreManagerData.serializeData(out, null);
+        out.flush();
+        return outputStream.toByteArray();
+    }
+
+    /**
+     * Helper function for parsing configuration data from a XML block.
+     */
+    private void deserializeData(byte[] data) throws Exception {
+        final XmlPullParser in = Xml.newPullParser();
+        final ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
+        in.setInput(inputStream, StandardCharsets.UTF_8.name());
+        mWifiCarrierInfoStoreManagerData.deserializeData(in, in.getDepth(),
+                WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION, null);
+    }
+
+    /**
+     * Verify store file Id.
+     */
+    @Test
+    public void verifyStoreFileId() throws Exception {
+        assertEquals(WifiConfigStore.STORE_FILE_SHARED_GENERAL,
+                mWifiCarrierInfoStoreManagerData.getStoreFileId());
+    }
+
+    /**
+     * Verify serialize and deserialize Protection exemption map.
+     */
+    @Test
+    public void testSerializeDeserialize() throws Exception {
+        mImsiPrivacyProtectionExemptionMap.put(TEST_CARRIER_ID, true);
+        mMergedCarrierOffloadMap.put(TEST_SUB_ID, true);
+        mUnmergedCarrierOffloadMap.put(TEST_SUB_ID, false);
+        assertSerializeDeserialize();
+    }
+
+    /**
+     * Verify serialize and deserialize empty protection exemption map.
+     */
+    @Test
+    public void testSerializeDeserializeEmpty() throws Exception {
+        mImsiPrivacyProtectionExemptionMap.clear();
+        mMergedCarrierOffloadMap.clear();
+        mUnmergedCarrierOffloadMap.clear();
+        assertSerializeDeserialize();
+    }
+
+    @Test
+    public void testDeserializeOnNewDeviceOrNewUser() throws Exception {
+        InOrder inOrder = inOrder(mDataSource);
+        mWifiCarrierInfoStoreManagerData.deserializeData(null, 0,
+                WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION, null);
+        inOrder.verify(mDataSource).reset();
+        verifyNoMoreInteractions(mDataSource);
+    }
+
+
+    private void assertSerializeDeserialize() throws Exception {
+        InOrder inOrder = inOrder(mDataSource);
+        // Setup the data to serialize.
+        when(mDataSource.toSerializeMergedCarrierNetworkOffloadMap()).thenReturn(
+                mMergedCarrierOffloadMap);
+        when(mDataSource.toSerializeUnmergedCarrierNetworkOffloadMap())
+                .thenReturn(mUnmergedCarrierOffloadMap);
+
+        // Serialize/deserialize data.
+        deserializeData(serializeData());
+        inOrder.verify(mDataSource).serializeComplete();
+        // Verify the deserialized data.
+        ArgumentCaptor<HashMap> deserializedMap =
+                ArgumentCaptor.forClass(HashMap.class);
+
+        verify(mDataSource)
+                .fromMergedCarrierNetworkOffloadMapDeserialized(deserializedMap.capture());
+        assertEquals(mMergedCarrierOffloadMap,
+                deserializedMap.getValue());
+
+        verify(mDataSource)
+                .fromUnmergedCarrierNetworkOffloadMapDeserialized(deserializedMap.capture());
+        assertEquals(mUnmergedCarrierOffloadMap,
+                deserializedMap.getValue());
+    }
+}
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java
index 9219076..2411834 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java
@@ -16,8 +16,32 @@
 
 package com.android.server.wifi;
 
-import static org.junit.Assert.*;
-import static org.mockito.Mockito.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.validateMockitoUsage;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
 
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -29,6 +53,7 @@
 import android.net.IpConfiguration;
 import android.net.MacAddress;
 import android.net.wifi.ScanResult;
+import android.net.wifi.SecurityParams;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
 import android.net.wifi.WifiEnterpriseConfig;
@@ -54,7 +79,6 @@
 import com.android.server.wifi.proto.nano.WifiMetricsProto.UserActionEvent;
 import com.android.server.wifi.util.LruConnectionTracker;
 import com.android.server.wifi.util.WifiPermissionsUtil;
-import com.android.server.wifi.util.WifiPermissionsWrapper;
 import com.android.wifi.resources.R;
 
 import org.junit.After;
@@ -66,12 +90,14 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.MockitoSession;
 import org.mockito.quality.Strictness;
+import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -79,6 +105,8 @@
 import java.util.Map;
 import java.util.Random;
 import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 /**
  * Unit tests for {@link com.android.server.wifi.WifiConfigManager}.
@@ -90,11 +118,11 @@
     private static final String TEST_BSSID = "0a:08:5c:67:89:00";
     private static final long TEST_WALLCLOCK_CREATION_TIME_MILLIS = 9845637;
     private static final long TEST_WALLCLOCK_UPDATE_TIME_MILLIS = 75455637;
-    private static final long TEST_ELAPSED_UPDATE_NETWORK_SELECTION_TIME_MILLIS = 29457631;
     private static final int TEST_CREATOR_UID = WifiConfigurationTestUtil.TEST_UID;
     private static final int TEST_NO_PERM_UID = 7;
     private static final int TEST_UPDATE_UID = 4;
     private static final int TEST_SYSUI_UID = 56;
+    private static final int TEST_OTHER_USER_UID = 1000000000;
     private static final int TEST_DEFAULT_USER = UserHandle.USER_SYSTEM;
     private static final int TEST_MAX_NUM_ACTIVE_CHANNELS_FOR_PARTIAL_SCAN = 5;
     private static final Integer[] TEST_FREQ_LIST = {2400, 2450, 5150, 5175, 5650};
@@ -102,6 +130,7 @@
     private static final String TEST_UPDATE_NAME = "com.wificonfigmanager.update";
     private static final String TEST_NO_PERM_NAME = "com.wificonfigmanager.noperm";
     private static final String TEST_WIFI_NAME = "android.uid.system";
+    private static final String TEST_OTHER_USER_NAME = "com.test.package";
     private static final String TEST_DEFAULT_GW_MAC_ADDRESS = "0f:67:ad:ef:09:34";
     private static final String TEST_STATIC_PROXY_HOST_1 = "192.168.48.1";
     private static final int    TEST_STATIC_PROXY_PORT_1 = 8000;
@@ -113,11 +142,18 @@
     private static final String TEST_PAC_PROXY_LOCATION_2 = "http://blah";
     private static final int TEST_RSSI = -50;
     private static final int TEST_FREQUENCY_1 = 2412;
-    private static final int MAX_BLOCKED_BSSID_PER_NETWORK = 10;
     private static final MacAddress TEST_RANDOMIZED_MAC =
             MacAddress.fromString("d2:11:19:34:a5:20");
     private static final int DATA_SUBID = 1;
     private static final String SYSUI_PACKAGE_NAME = "com.android.systemui";
+    private static final int ALL_NON_CARRIER_MERGED_WIFI_MIN_DISABLE_DURATION_MINUTES = 30;
+    private static final int ALL_NON_CARRIER_MERGED_WIFI_MAX_DISABLE_DURATION_MINUTES = 480;
+    private static final int TEST_NETWORK_SELECTION_ENABLE_REASON =
+            NetworkSelectionStatus.DISABLED_NONE;
+    private static final int TEST_NETWORK_SELECTION_TEMP_DISABLE_REASON =
+            NetworkSelectionStatus.DISABLED_NO_INTERNET_TEMPORARY;
+    private static final int TEST_NETWORK_SELECTION_PERM_DISABLE_REASON =
+            NetworkSelectionStatus.DISABLED_BY_WIFI_MANAGER;
 
     @Mock private Context mContext;
     @Mock private Clock mClock;
@@ -129,7 +165,6 @@
     @Mock private WifiConfigStore mWifiConfigStore;
     @Mock private PackageManager mPackageManager;
     @Mock private WifiPermissionsUtil mWifiPermissionsUtil;
-    @Mock private WifiPermissionsWrapper mWifiPermissionsWrapper;
     @Mock private WifiInjector mWifiInjector;
     @Mock private WifiLastResortWatchdog mWifiLastResortWatchdog;
     @Mock private NetworkListSharedStoreData mNetworkListSharedStoreData;
@@ -139,11 +174,16 @@
     @Mock private FrameworkFacade mFrameworkFacade;
     @Mock private DeviceConfigFacade mDeviceConfigFacade;
     @Mock private MacAddressUtil mMacAddressUtil;
-    @Mock private BssidBlocklistMonitor mBssidBlocklistMonitor;
+    @Mock private WifiBlocklistMonitor mWifiBlocklistMonitor;
     @Mock private WifiNetworkSuggestionsManager mWifiNetworkSuggestionsManager;
     @Mock private WifiScoreCard mWifiScoreCard;
     @Mock private PerNetwork mPerNetwork;
     @Mock private WifiMetrics mWifiMetrics;
+    @Mock private WifiConfigManager.OnNetworkUpdateListener mListener;
+    @Mock private ActiveModeWarden mActiveModeWarden;
+    @Mock private ClientModeManager mPrimaryClientModeManager;
+    @Mock private WifiGlobals mWifiGlobals;
+    @Mock private BuildProperties mBuildProperties;
     private LruConnectionTracker mLruConnectionTracker;
 
     private MockResources mResources;
@@ -173,12 +213,21 @@
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         mResources = new MockResources();
         mResources.setBoolean(
-                R.bool.config_wifi_only_link_same_credential_configurations, true);
+                R.bool.config_wifi_only_link_same_credential_configurations, false);
+        mResources.setBoolean(
+                R.bool.config_wifiAllowLinkingUnknownDefaultGatewayConfigurations, true);
         mResources.setInteger(
                 R.integer.config_wifi_framework_associated_partial_scan_max_num_active_channels,
                 TEST_MAX_NUM_ACTIVE_CHANNELS_FOR_PARTIAL_SCAN);
         mResources.setBoolean(R.bool.config_wifi_connected_mac_randomization_supported, true);
         mResources.setInteger(R.integer.config_wifiMaxPnoSsidCount, 16);
+        mResources.setInteger(
+                R.integer.config_wifiAllNonCarrierMergedWifiMinDisableDurationMinutes,
+                ALL_NON_CARRIER_MERGED_WIFI_MIN_DISABLE_DURATION_MINUTES);
+        mResources.setInteger(
+                R.integer.config_wifiAllNonCarrierMergedWifiMaxDisableDurationMinutes,
+                ALL_NON_CARRIER_MERGED_WIFI_MAX_DISABLE_DURATION_MINUTES);
+        mResources.setInteger(R.integer.config_wifiMaxNumWifiConfigurations, -1);
         when(mContext.getResources()).thenReturn(mResources);
 
         // Setup UserManager profiles for the default user.
@@ -196,11 +245,32 @@
                     return TEST_NO_PERM_NAME;
                 } else if (uid == Process.WIFI_UID) {
                     return TEST_WIFI_NAME;
+                } else if (uid == TEST_OTHER_USER_UID) {
+                    return TEST_OTHER_USER_NAME;
                 }
                 fail("Unexpected UID: " + uid);
                 return "";
             }
         }).when(mPackageManager).getNameForUid(anyInt());
+        doAnswer(new AnswerWithArguments() {
+            public boolean answer(WifiConfiguration config, int reason) {
+                if (reason == TEST_NETWORK_SELECTION_ENABLE_REASON) {
+                    config.getNetworkSelectionStatus().setNetworkSelectionStatus(
+                            NetworkSelectionStatus.NETWORK_SELECTION_ENABLED);
+                    config.status = WifiConfiguration.Status.ENABLED;
+                } else if (reason == TEST_NETWORK_SELECTION_TEMP_DISABLE_REASON) {
+                    config.getNetworkSelectionStatus().setNetworkSelectionStatus(
+                            NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED);
+                } else if (reason == TEST_NETWORK_SELECTION_PERM_DISABLE_REASON) {
+                    config.getNetworkSelectionStatus().setNetworkSelectionStatus(
+                            NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED);
+                    config.status = WifiConfiguration.Status.DISABLED;
+                } else {
+                    // ignore all other failure reasons for simplicity of mocking.
+                }
+                return true;
+            }
+        }).when(mWifiBlocklistMonitor).updateNetworkSelectionStatus(any(), anyInt());
 
         when(mContext.getSystemService(ActivityManager.class))
                 .thenReturn(mock(ActivityManager.class));
@@ -212,34 +282,46 @@
         setupStoreDataForRead(new ArrayList<>(), new ArrayList<>());
 
         when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(true);
-        when(mWifiPermissionsUtil.isDeviceOwner(anyInt(), any())).thenReturn(false);
+        mockIsDeviceOwner(false);
         when(mWifiPermissionsUtil.isProfileOwner(anyInt(), any())).thenReturn(false);
-        when(mWifiInjector.getWifiNetworkSuggestionsManager())
-                .thenReturn(mWifiNetworkSuggestionsManager);
-        when(mWifiInjector.getBssidBlocklistMonitor()).thenReturn(mBssidBlocklistMonitor);
-        when(mWifiInjector.getWifiLastResortWatchdog()).thenReturn(mWifiLastResortWatchdog);
-        when(mWifiInjector.getWifiLastResortWatchdog().shouldIgnoreSsidUpdate())
-                .thenReturn(false);
-        when(mWifiInjector.getMacAddressUtil()).thenReturn(mMacAddressUtil);
-        when(mWifiInjector.getWifiMetrics()).thenReturn(mWifiMetrics);
         when(mWifiPermissionsUtil.doesUidBelongToCurrentUser(anyInt())).thenReturn(true);
+        when(mWifiLastResortWatchdog.shouldIgnoreSsidUpdate()).thenReturn(false);
         when(mMacAddressUtil.calculatePersistentMac(any(), any())).thenReturn(TEST_RANDOMIZED_MAC);
         when(mWifiScoreCard.lookupNetwork(any())).thenReturn(mPerNetwork);
 
-        mWifiCarrierInfoManager = new WifiCarrierInfoManager(mTelephonyManager,
+        WifiContext wifiContext = mock(WifiContext.class);
+        when(wifiContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mPackageManager.getPackagesHoldingPermissions(any(String[].class), anyInt()))
+                .thenReturn(Collections.emptyList());
+        mWifiCarrierInfoManager = spy(new WifiCarrierInfoManager(mTelephonyManager,
                 mSubscriptionManager, mWifiInjector, mock(FrameworkFacade.class),
-                mock(WifiContext.class), mock(WifiConfigStore.class), mock(Handler.class),
-                mWifiMetrics);
+                wifiContext, mock(WifiConfigStore.class), mock(Handler.class),
+                mWifiMetrics, mClock));
         mLruConnectionTracker = new LruConnectionTracker(100, mContext);
         createWifiConfigManager();
         mWifiConfigManager.addOnNetworkUpdateListener(mWcmListener);
         // static mocking
         mSession = ExtendedMockito.mockitoSession()
+                .mockStatic(WifiInjector.class, withSettings().lenient())
                 .mockStatic(WifiConfigStore.class, withSettings().lenient())
                 .strictness(Strictness.LENIENT)
                 .startMocking();
+        when(WifiInjector.getInstance()).thenReturn(mWifiInjector);
+        when(mWifiInjector.getActiveModeWarden()).thenReturn(mActiveModeWarden);
+        when(mWifiInjector.getWifiGlobals()).thenReturn(mWifiGlobals);
+        when(mActiveModeWarden.getPrimaryClientModeManager()).thenReturn(mPrimaryClientModeManager);
+        when(mPrimaryClientModeManager.getSupportedFeatures()).thenReturn(
+                WifiManager.WIFI_FEATURE_WPA3_SAE | WifiManager.WIFI_FEATURE_OWE);
+        when(mWifiGlobals.isWpa3SaeUpgradeEnabled()).thenReturn(true);
+        when(mWifiGlobals.isOweUpgradeEnabled()).thenReturn(true);
         when(WifiConfigStore.createUserFiles(anyInt(), anyBoolean())).thenReturn(mock(List.class));
         when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mDataTelephonyManager);
+        when(mBuildProperties.isUserBuild()).thenReturn(false);
+    }
+
+    private void mockIsDeviceOwner(boolean result) {
+        when(mWifiPermissionsUtil.isDeviceOwner(anyInt(), any())).thenReturn(result);
+        when(mWifiPermissionsUtil.isDeviceOwner(anyInt())).thenReturn(result);
     }
 
     /**
@@ -469,7 +551,7 @@
         WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
         Map<String, String> randomizedMacAddressMapping = new HashMap<>();
         final String randMac = "12:23:34:45:56:67";
-        randomizedMacAddressMapping.put(openNetwork.getKey(), randMac);
+        randomizedMacAddressMapping.put(openNetwork.getNetworkKey(), randMac);
         assertNotEquals(randMac, openNetwork.getRandomizedMacAddress());
         when(mRandomizedMacStoreData.getMacMapping()).thenReturn(randomizedMacAddressMapping);
 
@@ -573,6 +655,9 @@
         assertAndSetNetworkBSSID(openNetwork, TEST_BSSID);
         // Change the trusted bit.
         openNetwork.trusted = false;
+        // change the oem paid bit.
+        openNetwork.oemPaid = true;
+        openNetwork.oemPrivate = true;
         verifyUpdateNetworkToWifiConfigManagerWithoutIpChange(openNetwork);
 
         // Now verify that the modification has been effective.
@@ -586,9 +671,13 @@
         WifiConfiguration oldConfig = wifiConfigCaptor.getAllValues().get(0);
         assertEquals(openNetwork.networkId, newConfig.networkId);
         assertFalse(newConfig.trusted);
+        assertTrue(newConfig.oemPaid);
+        assertTrue(newConfig.oemPrivate);
         assertEquals(TEST_BSSID, newConfig.BSSID);
         assertEquals(openNetwork.networkId, oldConfig.networkId);
         assertTrue(oldConfig.trusted);
+        assertFalse(oldConfig.oemPaid);
+        assertFalse(oldConfig.oemPrivate);
         assertNull(oldConfig.BSSID);
     }
 
@@ -615,14 +704,14 @@
             public void answer(String ssid) {
                 mBssidStatusMap.entrySet().removeIf(e -> e.getValue().equals(ssid));
             }
-        }).when(mBssidBlocklistMonitor).clearBssidBlocklistForSsid(
+        }).when(mWifiBlocklistMonitor).clearBssidBlocklistForSsid(
                 anyString());
         doAnswer(new AnswerWithArguments() {
             public int answer(String ssid) {
                 return (int) mBssidStatusMap.entrySet().stream()
                         .filter(e -> e.getValue().equals(ssid)).count();
             }
-        }).when(mBssidBlocklistMonitor).updateAndGetNumBlockedBssidsForSsid(
+        }).when(mWifiBlocklistMonitor).updateAndGetNumBlockedBssidsForSsid(
                 anyString());
         // add bssid to the blocklist
         mBssidStatusMap.put(TEST_BSSID, openNetwork.SSID);
@@ -632,6 +721,9 @@
         assertAndSetNetworkBSSID(openNetwork, TEST_BSSID);
         // Change the trusted bit.
         openNetwork.trusted = false;
+        // change the oem paid bit.
+        openNetwork.oemPaid = true;
+        openNetwork.oemPrivate = true;
         verifyUpdateNetworkToWifiConfigManagerWithoutIpChange(openNetwork);
 
         // Now verify that the modification has been effective.
@@ -645,12 +737,16 @@
         WifiConfiguration oldConfig = wifiConfigCaptor.getAllValues().get(0);
         assertEquals(openNetwork.networkId, newConfig.networkId);
         assertFalse(newConfig.trusted);
+        assertTrue(newConfig.oemPaid);
+        assertTrue(newConfig.oemPrivate);
         assertEquals(TEST_BSSID, newConfig.BSSID);
         assertEquals(openNetwork.networkId, oldConfig.networkId);
         assertTrue(oldConfig.trusted);
+        assertFalse(oldConfig.oemPaid);
+        assertFalse(oldConfig.oemPrivate);
         assertNull(oldConfig.BSSID);
 
-        assertEquals(0, mBssidBlocklistMonitor.updateAndGetNumBlockedBssidsForSsid(
+        assertEquals(0, mWifiBlocklistMonitor.updateAndGetNumBlockedBssidsForSsid(
                 openNetwork.SSID));
     }
 
@@ -664,9 +760,9 @@
                 ArgumentCaptor.forClass(WifiConfiguration.class);
         when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(false);
         when(mWifiPermissionsUtil.checkNetworkSetupWizardPermission(anyInt())).thenReturn(false);
-        when(mWifiPermissionsUtil.isDeviceOwner(anyInt(), any())).thenReturn(true);
+        mockIsDeviceOwner(true);
         WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
-        openNetwork.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_PERSISTENT;
+        openNetwork.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_AUTO;
 
         verifyAddNetworkToWifiConfigManager(openNetwork);
         verify(mWcmListener).onNetworkAdded(wifiConfigCaptor.capture());
@@ -694,7 +790,7 @@
                 ArgumentCaptor.forClass(WifiConfiguration.class);
         when(mWifiPermissionsUtil.checkNetworkSetupWizardPermission(anyInt())).thenReturn(false);
         WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
-        openNetwork.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_PERSISTENT;
+        openNetwork.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_AUTO;
         List<WifiConfiguration> networks = new ArrayList<>();
         networks.add(openNetwork);
 
@@ -728,9 +824,9 @@
                 ArgumentCaptor.forClass(WifiConfiguration.class);
         when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(false);
         when(mWifiPermissionsUtil.checkNetworkSetupWizardPermission(anyInt())).thenReturn(true);
-        when(mWifiPermissionsUtil.isDeviceOwner(anyInt(), any())).thenReturn(true);
+        mockIsDeviceOwner(true);
         WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
-        openNetwork.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_PERSISTENT;
+        openNetwork.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_AUTO;
         List<WifiConfiguration> networks = new ArrayList<>();
         networks.add(openNetwork);
 
@@ -760,7 +856,7 @@
                 ArgumentCaptor.forClass(WifiConfiguration.class);
         when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(false);
         when(mWifiPermissionsUtil.checkNetworkSetupWizardPermission(anyInt())).thenReturn(false);
-        when(mWifiPermissionsUtil.isDeviceOwner(anyInt(), any())).thenReturn(false);
+        mockIsDeviceOwner(false);
         WifiConfiguration passpointNetwork = WifiConfigurationTestUtil.createPasspointNetwork();
         // Disable MAC randomization and verify this is added in successfully.
         passpointNetwork.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_NONE;
@@ -782,6 +878,38 @@
                 networks, retrievedNetworks);
     }
 
+    /**
+     * Verify that the mac randomization setting could be modified when added by wifi suggestions.
+     */
+    @Test
+    public void testCanUpdateMacRandomizationSettingForSuggestions() throws Exception {
+        ArgumentCaptor<WifiConfiguration> wifiConfigCaptor =
+                ArgumentCaptor.forClass(WifiConfiguration.class);
+        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(false);
+        when(mWifiPermissionsUtil.checkNetworkSetupWizardPermission(anyInt())).thenReturn(false);
+        mockIsDeviceOwner(false);
+
+        // Create 2 open networks. One is from suggestion and the other is not.
+        WifiConfiguration openNetworkSuggestion = WifiConfigurationTestUtil.createOpenNetwork();
+        openNetworkSuggestion.macRandomizationSetting =
+                WifiConfiguration.RANDOMIZATION_NON_PERSISTENT;
+        openNetworkSuggestion.fromWifiNetworkSuggestion = true;
+
+        WifiConfiguration openNetworkSaved = WifiConfigurationTestUtil.createOpenNetwork();
+        openNetworkSaved.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_NON_PERSISTENT;
+        openNetworkSaved.fromWifiNetworkSuggestion = false;
+
+        // Add both networks into WifiConfigManager, and verify only is network from suggestions is
+        // successfully added.
+        addNetworkToWifiConfigManager(openNetworkSuggestion);
+        addNetworkToWifiConfigManager(openNetworkSaved);
+
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworksWithPasswords();
+        assertEquals(1, retrievedNetworks.size());
+        assertEquals(openNetworkSuggestion.getProfileKey(),
+                retrievedNetworks.get(0).getProfileKey());
+    }
 
     /**
      * Verifies the addition of a single ephemeral network using
@@ -1004,7 +1132,8 @@
         List<WifiConfiguration> retrievedSavedNetworks = mWifiConfigManager.getSavedNetworks(
                 Process.WIFI_UID);
         assertEquals(retrievedSavedNetworks.size(), 1);
-        assertEquals(retrievedSavedNetworks.get(0).getKey(), pskNetwork.getKey());
+        assertEquals(retrievedSavedNetworks.get(0).getProfileKey(),
+                pskNetwork.getProfileKey());
         assertPasswordsMaskedInWifiConfiguration(retrievedSavedNetworks.get(0));
     }
 
@@ -1029,7 +1158,8 @@
         List<WifiConfiguration> retrievedSavedNetworks = mWifiConfigManager.getSavedNetworks(
                 Process.WIFI_UID);
         assertEquals(retrievedSavedNetworks.size(), 1);
-        assertEquals(retrievedSavedNetworks.get(0).getKey(), wepNetwork.getKey());
+        assertEquals(retrievedSavedNetworks.get(0).getProfileKey(),
+                wepNetwork.getProfileKey());
         assertPasswordsMaskedInWifiConfiguration(retrievedSavedNetworks.get(0));
     }
 
@@ -1233,187 +1363,109 @@
 
         int networkId = result.getNetworkId();
         // First set it to enabled.
-        verifyUpdateNetworkSelectionStatus(
-                networkId, NetworkSelectionStatus.DISABLED_NONE, 0);
+        mWifiConfigManager.updateNetworkSelectionStatus(networkId,
+                TEST_NETWORK_SELECTION_ENABLE_REASON);
 
-        // Now set it to temporarily disabled. The threshold for association rejection is 5, so
-        // disable it 5 times to actually mark it temporarily disabled.
-        int assocRejectReason = NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION;
-        int assocRejectThreshold =
-                WifiConfigManager.getNetworkSelectionDisableThreshold(assocRejectReason);
-        for (int i = 1; i <= assocRejectThreshold; i++) {
-            verifyUpdateNetworkSelectionStatus(result.getNetworkId(), assocRejectReason, i);
-        }
+        // Now set it to temporarily disabled.
+        mWifiConfigManager.updateNetworkSelectionStatus(networkId,
+                TEST_NETWORK_SELECTION_TEMP_DISABLE_REASON);
         verify(mWcmListener).onNetworkTemporarilyDisabled(wifiConfigCaptor.capture(),
-                eq(NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION));
+                eq(TEST_NETWORK_SELECTION_TEMP_DISABLE_REASON));
         assertEquals(openNetwork.networkId, wifiConfigCaptor.getValue().networkId);
+        verifyNetworkUpdateBroadcast();
         // Now set it to permanently disabled.
-        verifyUpdateNetworkSelectionStatus(
-                result.getNetworkId(), NetworkSelectionStatus.DISABLED_BY_WIFI_MANAGER, 0);
+        mWifiConfigManager.updateNetworkSelectionStatus(networkId,
+                TEST_NETWORK_SELECTION_PERM_DISABLE_REASON);
         verify(mWcmListener).onNetworkPermanentlyDisabled(
-                wifiConfigCaptor.capture(), eq(NetworkSelectionStatus.DISABLED_BY_WIFI_MANAGER));
+                wifiConfigCaptor.capture(), eq(TEST_NETWORK_SELECTION_PERM_DISABLE_REASON));
         assertEquals(openNetwork.networkId, wifiConfigCaptor.getValue().networkId);
+        verifyNetworkUpdateBroadcast();
         // Now set it back to enabled.
-        verifyUpdateNetworkSelectionStatus(
-                result.getNetworkId(), NetworkSelectionStatus.DISABLED_NONE, 0);
-        verify(mWcmListener, times(2))
-                .onNetworkEnabled(wifiConfigCaptor.capture());
+        mWifiConfigManager.updateNetworkSelectionStatus(networkId,
+                TEST_NETWORK_SELECTION_ENABLE_REASON);
+        verify(mWcmListener, times(2)).onNetworkEnabled(wifiConfigCaptor.capture());
         assertEquals(openNetwork.networkId, wifiConfigCaptor.getValue().networkId);
+        verifyNetworkUpdateBroadcast();
     }
 
     /**
-     * Verifies the update of network status using
-     * {@link WifiConfigManager#updateNetworkSelectionStatus(int, int)}.
-     */
-    @Test
-    public void testNetworkSelectionStatusTemporarilyDisabledDueToNoInternet() {
-        ArgumentCaptor<WifiConfiguration> wifiConfigCaptor =
-                ArgumentCaptor.forClass(WifiConfiguration.class);
-        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
-
-        NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(openNetwork);
-
-        int networkId = result.getNetworkId();
-        // First set it to enabled.
-        verifyUpdateNetworkSelectionStatus(
-                networkId, NetworkSelectionStatus.DISABLED_NONE, 0);
-
-        // Now set it to temporarily disabled. The threshold for no internet is 1, so
-        // disable it once to actually mark it temporarily disabled.
-        verifyUpdateNetworkSelectionStatus(result.getNetworkId(),
-                NetworkSelectionStatus.DISABLED_NO_INTERNET_TEMPORARY, 1);
-        verify(mWcmListener).onNetworkTemporarilyDisabled(
-                wifiConfigCaptor.capture(),
-                eq(NetworkSelectionStatus.DISABLED_NO_INTERNET_TEMPORARY));
-        assertEquals(openNetwork.networkId, wifiConfigCaptor.getValue().networkId);
-        // Now set it back to enabled.
-        verifyUpdateNetworkSelectionStatus(
-                result.getNetworkId(), NetworkSelectionStatus.DISABLED_NONE, 0);
-        verify(mWcmListener, times(2))
-                .onNetworkEnabled(wifiConfigCaptor.capture());
-        assertEquals(openNetwork.networkId, wifiConfigCaptor.getValue().networkId);
-    }
-
-    /**
-     * Verifies the update of network status using
-     * {@link WifiConfigManager#updateNetworkSelectionStatus(int, int)} and ensures that
-     * enabling a network clears out all the temporary disable counters.
-     */
-    @Test
-    public void testNetworkSelectionStatusEnableClearsDisableCounters() {
-        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
-
-        NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(openNetwork);
-
-        // First set it to enabled.
-        verifyUpdateNetworkSelectionStatus(
-                result.getNetworkId(), NetworkSelectionStatus.DISABLED_NONE, 0);
-
-        // Now set it to temporarily disabled 2 times for 2 different reasons.
-        verifyUpdateNetworkSelectionStatus(
-                result.getNetworkId(), NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION, 1);
-        verifyUpdateNetworkSelectionStatus(
-                result.getNetworkId(), NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION, 2);
-        verifyUpdateNetworkSelectionStatus(
-                result.getNetworkId(), NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE, 1);
-        verifyUpdateNetworkSelectionStatus(
-                result.getNetworkId(), NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE, 2);
-
-        // Now set it back to enabled.
-        verifyUpdateNetworkSelectionStatus(
-                result.getNetworkId(), NetworkSelectionStatus.DISABLED_NONE, 0);
-
-        // Ensure that the counters have all been reset now.
-        verifyUpdateNetworkSelectionStatus(
-                result.getNetworkId(), NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION, 1);
-        verifyUpdateNetworkSelectionStatus(
-                result.getNetworkId(), NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE, 1);
-    }
-
-    private void verifyDisableNetwork(NetworkUpdateResult result, int reason) {
-        // First set it to enabled.
-        verifyUpdateNetworkSelectionStatus(
-                result.getNetworkId(), NetworkSelectionStatus.DISABLED_NONE, 0);
-
-        int disableThreshold =
-                WifiConfigManager.getNetworkSelectionDisableThreshold(reason);
-        for (int i = 1; i <= disableThreshold; i++) {
-            verifyUpdateNetworkSelectionStatus(result.getNetworkId(), reason, i);
-        }
-    }
-
-    private void verifyNetworkIsEnabledAfter(int networkId, long timeout) {
-        // try enabling this network 1 second earlier than the expected timeout. This
-        // should fail and the status should remain temporarily disabled.
-        when(mClock.getElapsedSinceBootMillis()).thenReturn(timeout - 1);
-        assertFalse(mWifiConfigManager.tryEnableNetwork(networkId));
-        NetworkSelectionStatus retrievedStatus =
-                mWifiConfigManager.getConfiguredNetwork(networkId).getNetworkSelectionStatus();
-        assertTrue(retrievedStatus.isNetworkTemporaryDisabled());
-
-        // Now advance time by the timeout for association rejection and ensure that the
-        // network is now enabled.
-        when(mClock.getElapsedSinceBootMillis()).thenReturn(timeout);
-        assertTrue(mWifiConfigManager.tryEnableNetwork(networkId));
-        retrievedStatus = mWifiConfigManager.getConfiguredNetwork(networkId)
-                .getNetworkSelectionStatus();
-        assertTrue(retrievedStatus.isNetworkEnabled());
-    }
-
-    /**
-     * Verifies the enabling of temporarily disabled network using
-     * {@link WifiConfigManager#tryEnableNetwork(int)}.
+     * Verify tryEnableNetwork sets NetworkSelectionStatus to NETWORK_SELECTION_ENABLED when
+     * WifiBlocklistMonitor.shouldEnableNetwork returns true.
      */
     @Test
     public void testTryEnableNetwork() {
         WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
         NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(openNetwork);
+        when(mWifiBlocklistMonitor.shouldEnableNetwork(any())).thenReturn(true);
 
-        // Verify exponential backoff on the disable duration based on number of BSSIDs in the
-        // BSSID blocklist
-        long multiplier = 1;
-        int disableReason = NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION;
-        long timeout = 0;
-        for (int i = 1; i < MAX_BLOCKED_BSSID_PER_NETWORK + 1; i++) {
-            verifyDisableNetwork(result, disableReason);
-            int numBssidsInBlocklist = i;
-            when(mBssidBlocklistMonitor.updateAndGetNumBlockedBssidsForSsid(anyString()))
-                    .thenReturn(numBssidsInBlocklist);
-            timeout = WifiConfigManager.getNetworkSelectionDisableTimeoutMillis(disableReason)
-                    * multiplier;
-            multiplier *= 2;
-            verifyNetworkIsEnabledAfter(result.getNetworkId(),
-                    TEST_ELAPSED_UPDATE_NETWORK_SELECTION_TIME_MILLIS + timeout);
-        }
+        // first set the network to temporarily disabled
+        verifyDisableNetwork(result, false);
+        verifyNetworkUpdateBroadcast();
 
-        // Verify one last time that the disable duration is capped at some maximum.
-        verifyDisableNetwork(result, disableReason);
-        when(mBssidBlocklistMonitor.updateAndGetNumBlockedBssidsForSsid(anyString()))
-                .thenReturn(MAX_BLOCKED_BSSID_PER_NETWORK + 1);
-        verifyNetworkIsEnabledAfter(result.getNetworkId(),
-                TEST_ELAPSED_UPDATE_NETWORK_SELECTION_TIME_MILLIS + timeout);
+        assertTrue(mWifiConfigManager.tryEnableNetwork(result.getNetworkId()));
+        assertEquals(NetworkSelectionStatus.NETWORK_SELECTION_ENABLED,
+                mWifiConfigManager.getConfiguredNetworks().get(0)
+                        .getNetworkSelectionStatus().getNetworkSelectionStatus());
+        verifyNetworkUpdateBroadcast();
     }
 
     /**
-     * Verifies that when no BSSIDs for a network is inside the BSSID blocklist then we
-     * re-enable a network.
+     * Verify enableTemporaryDisabledNetworks successfully enable temporarily disabled networks.
      */
     @Test
-    public void testTryEnableNetworkNoBssidsInBlocklist() {
+    public void testEnableTemporaryDisabledNetworks() {
         WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
         NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(openNetwork);
-        int disableReason = NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION;
+        verifyDisableNetwork(result, false);
+        assertEquals(true, mWifiConfigManager.getConfiguredNetworks().get(0)
+                .getNetworkSelectionStatus().isNetworkTemporaryDisabled());
 
-        // Verify that with 0 BSSIDs in blocklist we enable the network immediately
-        verifyDisableNetwork(result, disableReason);
-        when(mBssidBlocklistMonitor.updateAndGetNumBlockedBssidsForSsid(anyString())).thenReturn(0);
-        when(mClock.getElapsedSinceBootMillis())
-                .thenReturn(TEST_ELAPSED_UPDATE_NETWORK_SELECTION_TIME_MILLIS);
-        assertTrue(mWifiConfigManager.tryEnableNetwork(result.getNetworkId()));
-        NetworkSelectionStatus retrievedStatus =
-                mWifiConfigManager.getConfiguredNetwork(result.getNetworkId())
-                        .getNetworkSelectionStatus();
-        assertTrue(retrievedStatus.isNetworkEnabled());
+        mWifiConfigManager.enableTemporaryDisabledNetworks();
+        verify(mWifiBlocklistMonitor).clearBssidBlocklist();
+        assertEquals(true, mWifiConfigManager.getConfiguredNetworks().get(0)
+                .getNetworkSelectionStatus().isNetworkEnabled());
+    }
+
+    /**
+     * Verify that permanently disabled network do not get affected by
+     * enableTemporaryDisabledNetworks
+     */
+    @Test
+    public void testEnableTemporaryDisabledNetworks_PermanentDisableNetworksStillDisabled() {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(openNetwork);
+        verifyDisableNetwork(result, true);
+        assertEquals(true, mWifiConfigManager.getConfiguredNetworks().get(0)
+                .getNetworkSelectionStatus().isNetworkPermanentlyDisabled());
+
+        mWifiConfigManager.enableTemporaryDisabledNetworks();
+        verify(mWifiBlocklistMonitor).clearBssidBlocklist();
+        assertEquals(true, mWifiConfigManager.getConfiguredNetworks().get(0)
+                .getNetworkSelectionStatus().isNetworkPermanentlyDisabled());
+    }
+
+    private void verifyDisableNetwork(NetworkUpdateResult result, boolean permanentlyDisable) {
+        // First set it to enabled.
+        int networkId = result.getNetworkId();
+        assertTrue(mWifiConfigManager.updateNetworkSelectionStatus(networkId,
+                TEST_NETWORK_SELECTION_ENABLE_REASON));
+        assertEquals(NetworkSelectionStatus.NETWORK_SELECTION_ENABLED,
+                mWifiConfigManager.getConfiguredNetworks().get(0)
+                        .getNetworkSelectionStatus().getNetworkSelectionStatus());
+
+        if (permanentlyDisable) {
+            assertTrue(mWifiConfigManager.updateNetworkSelectionStatus(networkId,
+                    TEST_NETWORK_SELECTION_PERM_DISABLE_REASON));
+            assertEquals(NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED,
+                    mWifiConfigManager.getConfiguredNetworks().get(0)
+                            .getNetworkSelectionStatus().getNetworkSelectionStatus());
+        } else {
+            assertTrue(mWifiConfigManager.updateNetworkSelectionStatus(networkId,
+                    TEST_NETWORK_SELECTION_TEMP_DISABLE_REASON));
+            assertEquals(NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED,
+                    mWifiConfigManager.getConfiguredNetworks().get(0)
+                            .getNetworkSelectionStatus().getNetworkSelectionStatus());
+        }
     }
 
     /**
@@ -1536,14 +1588,14 @@
      * {@link WifiConfigManager#updateLastConnectUid(int, int)}.
      */
     @Test
-    public void testUpdateLastConnectUid() throws Exception {
+    public void testConnectNetworkUpdatesLastConnectUid() throws Exception {
         WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
 
         NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(openNetwork);
 
-        assertTrue(
-                mWifiConfigManager.updateLastConnectUid(
-                        result.getNetworkId(), TEST_CREATOR_UID));
+        mWifiConfigManager
+                .updateBeforeConnect(result.getNetworkId(), TEST_CREATOR_UID);
+
         WifiConfiguration retrievedNetwork =
                 mWifiConfigManager.getConfiguredNetwork(result.getNetworkId());
         assertEquals(TEST_CREATOR_UID, retrievedNetwork.lastConnectUid);
@@ -1610,8 +1662,6 @@
                 result.getNetworkId() + 1, TEST_CREATOR_UID, TEST_CREATOR_NAME));
         assertFalse(mWifiConfigManager.updateNetworkSelectionStatus(
                 result.getNetworkId() + 1, NetworkSelectionStatus.DISABLED_BY_WIFI_MANAGER));
-        assertFalse(mWifiConfigManager.updateLastConnectUid(
-                result.getNetworkId() + 1, TEST_CREATOR_UID));
     }
 
     /**
@@ -1626,7 +1676,7 @@
      */
     @Test
     public void testMultipleUpdatesSingleNetwork() {
-        WifiConfiguration network = WifiConfigurationTestUtil.createOpenNetwork();
+        WifiConfiguration network = WifiConfigurationTestUtil.createOpenOweNetwork();
         verifyAddNetworkToWifiConfigManager(network);
 
         // Now add |wepKeys| to the network. We don't need to update the |allowedKeyManagement|
@@ -1641,25 +1691,11 @@
         WifiConfigurationTestUtil.assertConfigurationEqualForConfigManagerAddOrUpdate(
                 network, mWifiConfigManager.getConfiguredNetworkWithPassword(network.networkId));
 
-        // Now empty out 3 of the |wepKeys[]| and ensure that those keys have been reset correctly.
-        for (int i = 1; i < network.wepKeys.length; i++) {
-            wepKeys[i] = "";
-        }
+        // Now shuffle the keys around
+        String[] wepKeysNew = Arrays.copyOf(wepKeys, wepKeys.length);
+        Collections.rotate(Arrays.asList(wepKeysNew), 2);
         wepTxKeyIdx = 0;
-        assertAndSetNetworkWepKeysAndTxIndex(network, wepKeys, wepTxKeyIdx);
-
-        verifyUpdateNetworkToWifiConfigManagerWithoutIpChange(network);
-        WifiConfigurationTestUtil.assertConfigurationEqualForConfigManagerAddOrUpdate(
-                network, mWifiConfigManager.getConfiguredNetworkWithPassword(network.networkId));
-
-        // Now change the config to a PSK network config by resetting the remaining |wepKey[0]|
-        // field and setting the |preSharedKey| and |allowedKeyManagement| fields.
-        wepKeys[0] = "";
-        wepTxKeyIdx = -1;
-        assertAndSetNetworkWepKeysAndTxIndex(network, wepKeys, wepTxKeyIdx);
-        network.allowedKeyManagement.clear();
-        network.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
-        assertAndSetNetworkPreSharedKey(network, WifiConfigurationTestUtil.TEST_PSK);
+        assertAndSetNetworkWepKeysAndTxIndex(network, wepKeysNew, wepTxKeyIdx);
 
         verifyUpdateNetworkToWifiConfigManagerWithoutIpChange(network);
         WifiConfigurationTestUtil.assertConfigurationEqualForConfigManagerAddOrUpdate(
@@ -1702,7 +1738,7 @@
      */
     @Test
     public void testUpdateSingleNetworkWithNullValues() {
-        WifiConfiguration network = WifiConfigurationTestUtil.createEapNetwork();
+        WifiConfiguration network = WifiConfigurationTestUtil.createWpa2Wpa3EnterpriseNetwork();
         verifyAddNetworkToWifiConfigManager(network);
 
         // Save a copy of the original network for comparison.
@@ -1714,7 +1750,6 @@
         network.allowedKeyManagement.clear();
         network.allowedPairwiseCiphers.clear();
         network.allowedGroupCiphers.clear();
-        network.enterpriseConfig = null;
 
         // Update the network.
         NetworkUpdateResult result = updateNetworkToWifiConfigManager(network);
@@ -1735,6 +1770,22 @@
         WifiConfigurationTestUtil.assertConfigurationEqualForConfigManagerAddOrUpdate(
                 originalNetwork,
                 mWifiConfigManager.getConfiguredNetworkWithPassword(originalNetwork.networkId));
+
+        // Save a copy of the original network for comparison.
+        network = new WifiConfiguration(originalNetwork);
+
+        // Now set all the public fields to null including enterprise config and try updating the
+        // network.
+        network.allowedAuthAlgorithms.clear();
+        network.allowedProtocols.clear();
+        network.allowedKeyManagement.clear();
+        network.allowedPairwiseCiphers.clear();
+        network.allowedGroupCiphers.clear();
+        network.enterpriseConfig = null;
+
+        // Update the network.
+        result = updateNetworkToWifiConfigManager(network);
+        assertTrue(result.getNetworkId() == WifiConfiguration.INVALID_NETWORK_ID);
     }
 
     /**
@@ -1787,7 +1838,7 @@
     /**
      * Verifies the matching of networks with different encryption types with the
      * corresponding scan detail using
-     * {@link WifiConfigManager#getConfiguredNetworkForScanDetailAndCache(ScanDetail)}.
+     * {@link WifiConfigManager#getSavedNetworkForScanDetailAndCache(ScanDetail)}.
      * The test also verifies that the provided scan detail was cached,
      */
     @Test
@@ -1812,7 +1863,7 @@
 
     /**
      * Verifies that scan details with wrong SSID/authentication types are not matched using
-     * {@link WifiConfigManager#getConfiguredNetworkForScanDetailAndCache(ScanDetail)}
+     * {@link WifiConfigManager#getSavedNetworkForScanDetailAndCache(ScanDetail)}
      * to the added networks.
      */
     @Test
@@ -1862,19 +1913,19 @@
 
 
         // Try to lookup a saved network using the modified scan details. All of these should fail.
-        assertNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+        assertNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(
                 openNetworkScanDetail));
-        assertNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+        assertNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(
                 wepNetworkScanDetail));
-        assertNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+        assertNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(
                 pskNetworkScanDetail));
-        assertNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+        assertNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(
                 eapNetworkScanDetail));
-        assertNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+        assertNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(
                 saeNetworkScanDetail));
-        assertNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+        assertNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(
                 oweNetworkScanDetail));
-        assertNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+        assertNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(
                 eapSuiteBNetworkScanDetail));
 
         // All the cache's should be empty as well.
@@ -1924,7 +1975,7 @@
         ScanDetail scanDetail = createScanDetailForNetwork(testNetwork);
         ScanResult scanResult = scanDetail.getScanResult();
 
-        mWifiConfigManager.updateScanDetailCacheFromScanDetail(scanDetail);
+        mWifiConfigManager.updateScanDetailCacheFromScanDetailForSavedNetwork(scanDetail);
 
         // Now retrieve the scan detail cache and ensure that the new scan detail is in cache.
         ScanDetailCache retrievedScanDetailCache =
@@ -1957,7 +2008,7 @@
                     createScanDetailForNetwork(
                             openNetwork, String.format("%s%02x", testBssidPrefix, scanDetailNum));
             assertNotNull(
-                    mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(scanDetail));
+                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(scanDetail));
 
             // The size of scan detail cache should keep growing until it hits
             // |SCAN_CACHE_ENTRIES_MAX_SIZE|.
@@ -1970,7 +2021,7 @@
         ScanDetail scanDetail =
                 createScanDetailForNetwork(
                         openNetwork, String.format("%s%02x", testBssidPrefix, scanDetailNum));
-        assertNotNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(scanDetail));
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(scanDetail));
 
         // Retrieve the scan detail cache and ensure that the size was trimmed down to
         // |SCAN_CACHE_ENTRIES_TRIM_SIZE + 1|. The "+1" is to account for the new entry that
@@ -2000,7 +2051,7 @@
 
     /**
      * Verify that the |HasEverConnected| is set when
-     * {@link WifiConfigManager#updateNetworkAfterConnect(int)} is invoked.
+     * {@link WifiConfigManager#updateNetworkAfterConnect(int, boolean, int)} is invoked.
      */
     @Test
     public void testUpdateConfigAfterConnectHasEverConnectedTrue() {
@@ -2034,9 +2085,10 @@
         verifyAddNetworkHasEverConnectedFalse(wepNetwork);
         verifyUpdateNetworkAfterConnectHasEverConnectedTrue(wepNetwork.networkId);
 
-        // Now update the same network with a different wep.
-        assertFalse(wepNetwork.wepKeys[0].equals("newpassword"));
-        wepNetwork.wepKeys[0] = "newpassword";
+        // Now update the same network with a different wep key.
+        assertFalse(wepNetwork.wepKeys[0].equals("\"pswrd\""));
+        wepNetwork.wepKeys = new String[] {"\"pswrd\"", null, null, null};
+        wepNetwork.wepTxKeyIndex = 0;
         verifyUpdateNetworkWithCredentialChangeHasEverConnectedFalse(wepNetwork);
     }
 
@@ -2056,79 +2108,16 @@
     }
 
     /**
-     * Verifies that hasEverConnected is cleared when a network config |allowedKeyManagement| is
-     * updated.
+     * Verifies that hasEverConnected is cleared when the security type of a network config
+     * is updated.
      */
     @Test
-    public void testUpdateAllowedKeyManagementClearsHasEverConnected() {
+    public void testUpdateSecurityTypeClearsHasEverConnected() {
         WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork();
         verifyAddNetworkHasEverConnectedFalse(pskNetwork);
         verifyUpdateNetworkAfterConnectHasEverConnectedTrue(pskNetwork.networkId);
 
-        assertFalse(pskNetwork.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X));
-        pskNetwork.allowedKeyManagement.clear();
-        pskNetwork.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.SAE);
-        pskNetwork.requirePmf = true;
-        verifyUpdateNetworkWithCredentialChangeHasEverConnectedFalse(pskNetwork);
-    }
-
-    /**
-     * Verifies that hasEverConnected is cleared when a network config |allowedProtocol| is
-     * updated.
-     */
-    @Test
-    public void testUpdateProtocolsClearsHasEverConnected() {
-        WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork();
-        verifyAddNetworkHasEverConnectedFalse(pskNetwork);
-        verifyUpdateNetworkAfterConnectHasEverConnectedTrue(pskNetwork.networkId);
-
-        assertFalse(pskNetwork.allowedProtocols.get(WifiConfiguration.Protocol.OSEN));
-        pskNetwork.allowedProtocols.set(WifiConfiguration.Protocol.OSEN);
-        verifyUpdateNetworkWithCredentialChangeHasEverConnectedFalse(pskNetwork);
-    }
-
-    /**
-     * Verifies that hasEverConnected is cleared when a network config |allowedAuthAlgorithms| is
-     * updated.
-     */
-    @Test
-    public void testUpdateAllowedAuthAlgorithmsClearsHasEverConnected() {
-        WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork();
-        verifyAddNetworkHasEverConnectedFalse(pskNetwork);
-        verifyUpdateNetworkAfterConnectHasEverConnectedTrue(pskNetwork.networkId);
-
-        assertFalse(pskNetwork.allowedAuthAlgorithms.get(WifiConfiguration.AuthAlgorithm.LEAP));
-        pskNetwork.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.LEAP);
-        verifyUpdateNetworkWithCredentialChangeHasEverConnectedFalse(pskNetwork);
-    }
-
-    /**
-     * Verifies that hasEverConnected is cleared when a network config |allowedPairwiseCiphers| is
-     * updated.
-     */
-    @Test
-    public void testUpdateAllowedPairwiseCiphersClearsHasEverConnected() {
-        WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork();
-        verifyAddNetworkHasEverConnectedFalse(pskNetwork);
-        verifyUpdateNetworkAfterConnectHasEverConnectedTrue(pskNetwork.networkId);
-
-        assertFalse(pskNetwork.allowedPairwiseCiphers.get(WifiConfiguration.PairwiseCipher.NONE));
-        pskNetwork.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.NONE);
-        verifyUpdateNetworkWithCredentialChangeHasEverConnectedFalse(pskNetwork);
-    }
-
-    /**
-     * Verifies that hasEverConnected is cleared when a network config |allowedGroup| is
-     * updated.
-     */
-    @Test
-    public void testUpdateAllowedGroupCiphersClearsHasEverConnected() {
-        WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork();
-        verifyAddNetworkHasEverConnectedFalse(pskNetwork);
-        verifyUpdateNetworkAfterConnectHasEverConnectedTrue(pskNetwork.networkId);
-
-        assertTrue(pskNetwork.allowedGroupCiphers.get(WifiConfiguration.GroupCipher.WEP104));
-        pskNetwork.allowedGroupCiphers.clear(WifiConfiguration.GroupCipher.WEP104);
+        pskNetwork.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
         verifyUpdateNetworkWithCredentialChangeHasEverConnectedFalse(pskNetwork);
     }
 
@@ -2148,28 +2137,6 @@
     }
 
     /**
-     * Verifies that hasEverConnected is cleared when a network config |requirePMF| is
-     * updated.
-     */
-    @Test
-    public void testUpdateRequirePMFClearsHasEverConnected() {
-        WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork();
-        verifyAddNetworkHasEverConnectedFalse(pskNetwork);
-        verifyUpdateNetworkAfterConnectHasEverConnectedTrue(pskNetwork.networkId);
-
-        assertFalse(pskNetwork.requirePmf);
-        pskNetwork.requirePmf = true;
-
-        NetworkUpdateResult result =
-                verifyUpdateNetworkToWifiConfigManagerWithoutIpChange(pskNetwork);
-        WifiConfiguration retrievedNetwork =
-                mWifiConfigManager.getConfiguredNetwork(result.getNetworkId());
-        assertFalse("Updating network credentials config must clear hasEverConnected.",
-                retrievedNetwork.getNetworkSelectionStatus().hasEverConnected());
-        assertTrue(result.hasCredentialChanged());
-    }
-
-    /**
      * Verifies that hasEverConnected is cleared when a network config |enterpriseConfig| is
      * updated.
      */
@@ -2282,7 +2249,7 @@
      * Verify that the aggressive randomization allowlist works for passpoints. (by checking FQDN)
      */
     @Test
-    public void testShouldUseAggressiveRandomizationPasspoint() {
+    public void testshouldUseEnhancedRandomizationPasspoint() {
         WifiConfiguration c = WifiConfigurationTestUtil.createPasspointNetwork();
         // Adds SSID to the allowlist.
         Set<String> ssidList = new HashSet<>();
@@ -2291,12 +2258,74 @@
                 .thenReturn(ssidList);
 
         // Verify that if for passpoint networks we don't check for the SSID to be in the allowlist
-        assertFalse(mWifiConfigManager.shouldUseAggressiveRandomization(c));
+        assertFalse(mWifiConfigManager.shouldUseEnhancedRandomization(c));
 
         // instead we check for the FQDN
         ssidList.clear();
         ssidList.add(c.FQDN);
-        assertTrue(mWifiConfigManager.shouldUseAggressiveRandomization(c));
+        assertTrue(mWifiConfigManager.shouldUseEnhancedRandomization(c));
+    }
+
+    /**
+     * Verify that macRandomizationSetting == RANDOMIZATION_NON_PERSISTENT enables
+     * enhanced MAC randomization.
+     */
+    @Test
+    public void testShouldUseEnhancedRandomization_randomizationEnhanced() {
+        WifiConfiguration c = WifiConfigurationTestUtil.createPasspointNetwork();
+        c.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_NON_PERSISTENT;
+        assertTrue(mWifiConfigManager.shouldUseEnhancedRandomization(c));
+    }
+
+    /**
+     * Verify that macRandomizationSetting = RANDOMIZATION_PERSISTENT disables enhanced
+     * randomization.
+     */
+    @Test
+    public void testShouldUseEnhancedRandomization_randomizationPersistent() {
+        WifiConfiguration c = WifiConfigurationTestUtil.createPasspointNetwork();
+        c.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_PERSISTENT;
+        assertFalse(mWifiConfigManager.shouldUseEnhancedRandomization(c));
+    }
+
+    /**
+     * Verify that shouldUseEnhancedRandomization returns true for an open network that has been
+     * connected before and never had captive portal detected.
+     */
+    @Test
+    public void testShouldUseEnhancedRandomization_openNetworkNoCaptivePortal() {
+        when(mDeviceConfigFacade.allowEnhancedMacRandomizationOnOpenSsids()).thenReturn(true);
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+
+        // verify enhanced randomization is not enabled because the config has never been connected.
+        assertTrue(config.getNetworkSelectionStatus().hasNeverDetectedCaptivePortal());
+        assertFalse(mWifiConfigManager.shouldUseEnhancedRandomization(config));
+
+        config.getNetworkSelectionStatus().setHasEverConnected(true);
+        assertTrue(mWifiConfigManager.shouldUseEnhancedRandomization(config));
+    }
+
+    /**
+     * Verify that enhanced randomization on open networks could be turned on/off by 2 feature
+     * flags.
+     */
+    @Test
+    public void testShouldUseEnhancedRandomization_openNetworkFeatureFlag() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+        config.getNetworkSelectionStatus().setHasEverConnected(true);
+
+        // Test with both feature flags off, and expected no enhanced randomization.
+        when(mDeviceConfigFacade.allowEnhancedMacRandomizationOnOpenSsids()).thenReturn(false);
+        mResources.setBoolean(R.bool.config_wifiAllowEnhancedMacRandomizationOnOpenSsids, false);
+        assertFalse(mWifiConfigManager.shouldUseEnhancedRandomization(config));
+
+        // Verify either feature flag turned on will enable enhanced randomization.
+        when(mDeviceConfigFacade.allowEnhancedMacRandomizationOnOpenSsids()).thenReturn(true);
+        assertTrue(mWifiConfigManager.shouldUseEnhancedRandomization(config));
+
+        when(mDeviceConfigFacade.allowEnhancedMacRandomizationOnOpenSsids()).thenReturn(false);
+        mResources.setBoolean(R.bool.config_wifiAllowEnhancedMacRandomizationOnOpenSsids, true);
+        assertTrue(mWifiConfigManager.shouldUseEnhancedRandomization(config));
     }
 
     /**
@@ -2309,10 +2338,38 @@
                 eq(WifiConfigManager.ENHANCED_MAC_RANDOMIZATION_FEATURE_FORCE_ENABLE_FLAG),
                 anyInt())).thenReturn(1);
         WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
-        assertTrue(mWifiConfigManager.shouldUseAggressiveRandomization(config));
+        assertTrue(mWifiConfigManager.shouldUseEnhancedRandomization(config));
 
         config.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_NONE;
-        assertFalse(mWifiConfigManager.shouldUseAggressiveRandomization(config));
+        assertFalse(mWifiConfigManager.shouldUseEnhancedRandomization(config));
+    }
+
+    /**
+     * Verify that when enhanced MAC randomization is enabled the MAC address changes after 24 hours
+     * of the first connection this MAC address is used.
+     */
+    @Test
+    public void testEnhancedMacRandomizationEvery24Hours() {
+        setUpWifiConfigurationForEnhancedRandomization();
+        WifiConfiguration config = getFirstInternalWifiConfiguration();
+
+        assertEquals(TEST_WALLCLOCK_CREATION_TIME_MILLIS, config.randomizedMacLastModifiedTimeMs);
+        MacAddress firstMac = config.getRandomizedMacAddress();
+        for (int i = 0; i < 24; i++) {
+            when(mClock.getWallClockMillis()).thenReturn(TEST_WALLCLOCK_CREATION_TIME_MILLIS
+                    + i * 60 * 60 * 1000);
+            mWifiConfigManager.updateNetworkAfterDisconnect(config.networkId);
+            assertEquals("Randomized MAC should be the same after " + i + " hours",
+                    firstMac, mWifiConfigManager.getRandomizedMacAndUpdateIfNeeded(config));
+            config = getFirstInternalWifiConfiguration();
+        }
+
+        // verify that after 24 hours the randomized MAC address changes.
+        long timeAfter24Hours = TEST_WALLCLOCK_CREATION_TIME_MILLIS + 24 * 60 * 60 * 1000;
+        when(mClock.getWallClockMillis()).thenReturn(timeAfter24Hours);
+        assertNotEquals(firstMac, mWifiConfigManager.getRandomizedMacAndUpdateIfNeeded(config));
+        config = getFirstInternalWifiConfiguration();
+        assertEquals(timeAfter24Hours, config.randomizedMacLastModifiedTimeMs);
     }
 
     /**
@@ -2324,25 +2381,25 @@
      */
     @Test
     public void testRandomizedMacUpdateAndRestore() {
-        setUpWifiConfigurationForAggressiveRandomization();
+        setUpWifiConfigurationForEnhancedRandomization();
         // get the aggressive randomized MAC address.
         WifiConfiguration config = getFirstInternalWifiConfiguration();
-        final MacAddress aggressiveMac = config.getRandomizedMacAddress();
-        assertNotEquals(WifiInfo.DEFAULT_MAC_ADDRESS, aggressiveMac.toString());
+        final MacAddress randMac = config.getRandomizedMacAddress();
+        assertNotEquals(WifiInfo.DEFAULT_MAC_ADDRESS, randMac.toString());
         assertEquals(TEST_WALLCLOCK_CREATION_TIME_MILLIS
-                + WifiConfigManager.AGGRESSIVE_MAC_WAIT_AFTER_DISCONNECT_MS,
+                + WifiConfigManager.ENHANCED_MAC_WAIT_AFTER_DISCONNECT_MS,
                 config.randomizedMacExpirationTimeMs);
 
         // verify the new randomized mac should be different from the original mac.
         when(mClock.getWallClockMillis()).thenReturn(TEST_WALLCLOCK_CREATION_TIME_MILLIS
-                + WifiConfigManager.AGGRESSIVE_MAC_WAIT_AFTER_DISCONNECT_MS + 1);
-        MacAddress aggressiveMac2 = mWifiConfigManager.getRandomizedMacAndUpdateIfNeeded(config);
+                + WifiConfigManager.ENHANCED_MAC_WAIT_AFTER_DISCONNECT_MS + 1);
+        MacAddress randMac2 = mWifiConfigManager.getRandomizedMacAndUpdateIfNeeded(config);
 
         // verify internal WifiConfiguration has MacAddress updated correctly by comparing the
         // MAC address from internal WifiConfiguration with the value returned by API.
         config = getFirstInternalWifiConfiguration();
-        assertEquals(aggressiveMac2, config.getRandomizedMacAddress());
-        assertNotEquals(aggressiveMac, aggressiveMac2);
+        assertEquals(randMac2, config.getRandomizedMacAddress());
+        assertNotEquals(randMac, randMac2);
 
         // Now disable aggressive randomization and verify the randomized MAC is changed back to
         // the persistent MAC.
@@ -2356,8 +2413,8 @@
         // MAC address from internal WifiConfiguration with the value returned by API.
         config = getFirstInternalWifiConfiguration();
         assertEquals(persistentMac, config.getRandomizedMacAddress());
-        assertNotEquals(persistentMac, aggressiveMac);
-        assertNotEquals(persistentMac, aggressiveMac2);
+        assertNotEquals(persistentMac, randMac);
+        assertNotEquals(persistentMac, randMac2);
     }
 
     /**
@@ -2366,29 +2423,29 @@
      */
     @Test
     public void testUpdateRandomizedMacExpireTime() {
-        setUpWifiConfigurationForAggressiveRandomization();
+        setUpWifiConfigurationForEnhancedRandomization();
         WifiConfiguration config = getFirstInternalWifiConfiguration();
         when(mClock.getWallClockMillis()).thenReturn(0L);
 
-        // verify that |AGGRESSIVE_MAC_REFRESH_MS_MIN| is honored as the lower bound.
-        long dhcpLeaseTimeInSeconds = (WifiConfigManager.AGGRESSIVE_MAC_REFRESH_MS_MIN / 1000) - 1;
+        // verify that |ENHANCED_MAC_REFRESH_MS_MIN| is honored as the lower bound.
+        long dhcpLeaseTimeInSeconds = (WifiConfigManager.ENHANCED_MAC_REFRESH_MS_MIN / 1000) - 1;
         mWifiConfigManager.updateRandomizedMacExpireTime(config, dhcpLeaseTimeInSeconds);
         config = getFirstInternalWifiConfiguration();
-        assertEquals(WifiConfigManager.AGGRESSIVE_MAC_REFRESH_MS_MIN,
+        assertEquals(WifiConfigManager.ENHANCED_MAC_REFRESH_MS_MIN,
                 config.randomizedMacExpirationTimeMs);
 
-        // verify that |AGGRESSIVE_MAC_REFRESH_MS_MAX| is honored as the upper bound.
-        dhcpLeaseTimeInSeconds = (WifiConfigManager.AGGRESSIVE_MAC_REFRESH_MS_MAX / 1000) + 1;
+        // verify that |ENHANCED_MAC_REFRESH_MS_MAX| is honored as the upper bound.
+        dhcpLeaseTimeInSeconds = (WifiConfigManager.ENHANCED_MAC_REFRESH_MS_MAX / 1000) + 1;
         mWifiConfigManager.updateRandomizedMacExpireTime(config, dhcpLeaseTimeInSeconds);
         config = getFirstInternalWifiConfiguration();
-        assertEquals(WifiConfigManager.AGGRESSIVE_MAC_REFRESH_MS_MAX,
+        assertEquals(WifiConfigManager.ENHANCED_MAC_REFRESH_MS_MAX,
                 config.randomizedMacExpirationTimeMs);
 
         // finally verify setting a valid value between the upper and lower bounds.
-        dhcpLeaseTimeInSeconds = (WifiConfigManager.AGGRESSIVE_MAC_REFRESH_MS_MIN / 1000) + 5;
+        dhcpLeaseTimeInSeconds = (WifiConfigManager.ENHANCED_MAC_REFRESH_MS_MIN / 1000) + 5;
         mWifiConfigManager.updateRandomizedMacExpireTime(config, dhcpLeaseTimeInSeconds);
         config = getFirstInternalWifiConfiguration();
-        assertEquals(WifiConfigManager.AGGRESSIVE_MAC_REFRESH_MS_MIN + 5000,
+        assertEquals(WifiConfigManager.ENHANCED_MAC_REFRESH_MS_MIN + 5000,
                 config.randomizedMacExpirationTimeMs);
     }
 
@@ -2398,17 +2455,17 @@
      */
     @Test
     public void testRandomizedMacExpirationTimeUpdatedAtDisconnect() {
-        setUpWifiConfigurationForAggressiveRandomization();
+        setUpWifiConfigurationForEnhancedRandomization();
         WifiConfiguration config = getFirstInternalWifiConfiguration();
         when(mClock.getWallClockMillis()).thenReturn(0L);
 
         // First set the DHCP expiration time to be longer than the predefined time.
-        long dhcpLeaseTimeInSeconds = (WifiConfigManager.AGGRESSIVE_MAC_WAIT_AFTER_DISCONNECT_MS
+        long dhcpLeaseTimeInSeconds = (WifiConfigManager.ENHANCED_MAC_WAIT_AFTER_DISCONNECT_MS
                 / 1000) + 1;
         mWifiConfigManager.updateRandomizedMacExpireTime(config, dhcpLeaseTimeInSeconds);
         config = getFirstInternalWifiConfiguration();
         long expirationTime = config.randomizedMacExpirationTimeMs;
-        assertEquals(WifiConfigManager.AGGRESSIVE_MAC_WAIT_AFTER_DISCONNECT_MS + 1000,
+        assertEquals(WifiConfigManager.ENHANCED_MAC_WAIT_AFTER_DISCONNECT_MS + 1000,
                 expirationTime);
 
         // Verify that network disconnect does not update the expiration time since the remaining
@@ -2422,7 +2479,7 @@
         when(mClock.getWallClockMillis()).thenReturn(5000L);
         mWifiConfigManager.updateNetworkAfterDisconnect(config.networkId);
         config = getFirstInternalWifiConfiguration();
-        assertEquals(WifiConfigManager.AGGRESSIVE_MAC_WAIT_AFTER_DISCONNECT_MS + 5000,
+        assertEquals(WifiConfigManager.ENHANCED_MAC_WAIT_AFTER_DISCONNECT_MS + 5000,
                 config.randomizedMacExpirationTimeMs);
     }
 
@@ -2432,20 +2489,20 @@
      */
     @Test
     public void testRandomizedMacIsNotUpdatedDueToTimeConstraint() {
-        setUpWifiConfigurationForAggressiveRandomization();
+        setUpWifiConfigurationForEnhancedRandomization();
         // get the persistent randomized MAC address.
         WifiConfiguration config = getFirstInternalWifiConfiguration();
-        final MacAddress aggressiveMac = config.getRandomizedMacAddress();
-        assertNotEquals(WifiInfo.DEFAULT_MAC_ADDRESS, aggressiveMac.toString());
+        final MacAddress randMac = config.getRandomizedMacAddress();
+        assertNotEquals(WifiInfo.DEFAULT_MAC_ADDRESS, randMac.toString());
         assertEquals(TEST_WALLCLOCK_CREATION_TIME_MILLIS
-                + WifiConfigManager.AGGRESSIVE_MAC_WAIT_AFTER_DISCONNECT_MS,
+                + WifiConfigManager.ENHANCED_MAC_WAIT_AFTER_DISCONNECT_MS,
                 config.randomizedMacExpirationTimeMs);
 
         // verify that the randomized MAC is unchanged.
         when(mClock.getWallClockMillis()).thenReturn(TEST_WALLCLOCK_CREATION_TIME_MILLIS
-                + WifiConfigManager.AGGRESSIVE_MAC_WAIT_AFTER_DISCONNECT_MS);
+                + WifiConfigManager.ENHANCED_MAC_WAIT_AFTER_DISCONNECT_MS);
         MacAddress newMac = mWifiConfigManager.getRandomizedMacAndUpdateIfNeeded(config);
-        assertEquals(aggressiveMac, newMac);
+        assertEquals(randMac, newMac);
     }
 
     /**
@@ -2455,16 +2512,16 @@
     @Test
     public void testPerDeviceAggressiveRandomizationSsids() {
         // This will add the SSID to allowlist using DeviceConfig.
-        setUpWifiConfigurationForAggressiveRandomization();
+        setUpWifiConfigurationForEnhancedRandomization();
         WifiConfiguration config = getFirstInternalWifiConfiguration();
-        MacAddress aggressiveMac = config.getRandomizedMacAddress();
+        MacAddress randMac = config.getRandomizedMacAddress();
 
         // add to aggressive randomization blocklist using overlay.
         mResources.setStringArray(R.array.config_wifi_aggressive_randomization_ssid_blocklist,
                 new String[] {config.SSID});
         MacAddress persistentMac = mWifiConfigManager.getRandomizedMacAndUpdateIfNeeded(config);
         // verify that now the persistent randomized MAC is used.
-        assertNotEquals(aggressiveMac, persistentMac);
+        assertNotEquals(randMac, persistentMac);
     }
 
     private WifiConfiguration getFirstInternalWifiConfiguration() {
@@ -2473,7 +2530,7 @@
         return configs.get(0);
     }
 
-    private void setUpWifiConfigurationForAggressiveRandomization() {
+    private void setUpWifiConfigurationForEnhancedRandomization() {
         // sets up a WifiConfiguration for aggressive randomization.
         WifiConfiguration c = WifiConfigurationTestUtil.createOpenNetwork();
         // Adds the WifiConfiguration to aggressive randomization allowlist.
@@ -2501,8 +2558,7 @@
         // Verify macRandomizationSetting is not masked out when feature is supported.
         List<WifiConfiguration> configs = mWifiConfigManager.getSavedNetworks(Process.WIFI_UID);
         assertEquals(1, configs.size());
-        assertEquals(WifiConfiguration.RANDOMIZATION_PERSISTENT,
-                configs.get(0).macRandomizationSetting);
+        assertEquals(WifiConfiguration.RANDOMIZATION_AUTO, configs.get(0).macRandomizationSetting);
     }
 
     /**
@@ -2566,6 +2622,9 @@
         WifiConfiguration network1 = WifiConfigurationTestUtil.createPskNetwork();
         WifiConfiguration network2 = WifiConfigurationTestUtil.createPskNetwork();
         WifiConfiguration network3 = WifiConfigurationTestUtil.createPskNetwork();
+        network1.preSharedKey = "\"preSharedKey1\"";
+        network2.preSharedKey = "\"preSharedKey2\"";
+        network3.preSharedKey = "\"preSharedKey3\"";
         verifyAddNetworkToWifiConfigManager(network1);
         verifyAddNetworkToWifiConfigManager(network2);
         verifyAddNetworkToWifiConfigManager(network3);
@@ -2585,13 +2644,24 @@
 
         // Now save all these scan details corresponding to each of this network and expect
         // all of these networks to be linked with each other.
-        assertNotNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(
                 networkScanDetail1));
-        assertNotNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(
                 networkScanDetail2));
-        assertNotNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(
                 networkScanDetail3));
 
+        // Now update the linked networks for all of the networks
+        mWifiConfigManager.updateNetworkSelectionStatus(network1.networkId,
+                TEST_NETWORK_SELECTION_ENABLE_REASON);
+        mWifiConfigManager.updateNetworkSelectionStatus(network2.networkId,
+                TEST_NETWORK_SELECTION_ENABLE_REASON);
+        mWifiConfigManager.updateNetworkSelectionStatus(network3.networkId,
+                TEST_NETWORK_SELECTION_ENABLE_REASON);
+        mWifiConfigManager.updateLinkedNetworks(network1.networkId);
+        mWifiConfigManager.updateLinkedNetworks(network2.networkId);
+        mWifiConfigManager.updateLinkedNetworks(network3.networkId);
+
         List<WifiConfiguration> retrievedNetworks =
                 mWifiConfigManager.getConfiguredNetworks();
         for (WifiConfiguration network : retrievedNetworks) {
@@ -2600,12 +2670,181 @@
                 if (otherNetwork == network) {
                     continue;
                 }
-                assertNotNull(network.linkedConfigurations.get(otherNetwork.getKey()));
+                assertNotNull(network.linkedConfigurations.get(
+                        otherNetwork.getProfileKey()));
             }
         }
     }
 
     /**
+     * Verifies the linking of networks when they have the same default GW Mac address in
+     * {@link WifiConfigManager#getOrCreateScanDetailCacheForNetwork(WifiConfiguration)} AND the
+     * same pre-shared key.
+     */
+    @Test
+    public void testNetworkLinkOnlyIfCredentialsMatch() {
+        mResources.setBoolean(
+                R.bool.config_wifi_only_link_same_credential_configurations, true);
+        WifiConfiguration network1 = WifiConfigurationTestUtil.createPskNetwork();
+        WifiConfiguration network2 = WifiConfigurationTestUtil.createPskNetwork();
+        WifiConfiguration network3 = WifiConfigurationTestUtil.createPskNetwork();
+        // Set the same pre-shared key for network 1 and 2 but not 3
+        network1.preSharedKey = "\"preSharedKey1\"";
+        network2.preSharedKey = "\"preSharedKey1\"";
+        network3.preSharedKey = "\"preSharedKey2\"";
+        verifyAddNetworkToWifiConfigManager(network1);
+        verifyAddNetworkToWifiConfigManager(network2);
+        verifyAddNetworkToWifiConfigManager(network3);
+
+        // Set the same default GW mac address for all of the networks.
+        assertTrue(mWifiConfigManager.setNetworkDefaultGwMacAddress(
+                network1.networkId, TEST_DEFAULT_GW_MAC_ADDRESS));
+        assertTrue(mWifiConfigManager.setNetworkDefaultGwMacAddress(
+                network2.networkId, TEST_DEFAULT_GW_MAC_ADDRESS));
+        assertTrue(mWifiConfigManager.setNetworkDefaultGwMacAddress(
+                network3.networkId, TEST_DEFAULT_GW_MAC_ADDRESS));
+
+        // Now create fake scan detail corresponding to the networks.
+        ScanDetail networkScanDetail1 = createScanDetailForNetwork(network1);
+        ScanDetail networkScanDetail2 = createScanDetailForNetwork(network2);
+        ScanDetail networkScanDetail3 = createScanDetailForNetwork(network3);
+
+        // Now save all these scan details corresponding to each of this network and expect
+        // all of these networks to be linked with each other.
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(
+                networkScanDetail1));
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(
+                networkScanDetail2));
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(
+                networkScanDetail3));
+
+        // Now update the linked networks for all of the networks
+        mWifiConfigManager.updateNetworkSelectionStatus(network1.networkId,
+                TEST_NETWORK_SELECTION_ENABLE_REASON);
+        mWifiConfigManager.updateNetworkSelectionStatus(network2.networkId,
+                TEST_NETWORK_SELECTION_ENABLE_REASON);
+        mWifiConfigManager.updateNetworkSelectionStatus(network3.networkId,
+                TEST_NETWORK_SELECTION_ENABLE_REASON);
+        mWifiConfigManager.updateLinkedNetworks(network1.networkId);
+        mWifiConfigManager.updateLinkedNetworks(network2.networkId);
+        mWifiConfigManager.updateLinkedNetworks(network3.networkId);
+
+        Map<Integer, WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworks().stream().collect(
+                        Collectors.toMap(config -> config.networkId, Function.identity()));
+        // Networks 1 and 2 should link due to GW match + same pre-shared key
+        WifiConfiguration retrievedNetwork1 = retrievedNetworks.get(network1.networkId);
+        assertEquals(1, retrievedNetwork1.linkedConfigurations.size());
+        assertNotNull(retrievedNetwork1.linkedConfigurations.get(network2.getProfileKey()));
+        WifiConfiguration retrievedNetwork2 = retrievedNetworks.get(network2.networkId);
+        assertEquals(1, retrievedNetwork2.linkedConfigurations.size());
+        assertNotNull(retrievedNetwork2.linkedConfigurations.get(network1.getProfileKey()));
+        // Network 3 should not link due to different pre-shared key
+        WifiConfiguration retrievedNetwork3 = retrievedNetworks.get(network3.networkId);
+        assertNull(retrievedNetwork3.linkedConfigurations);
+    }
+
+    /**
+     * Verifies no linking of networks when the gateways have VRRP MAC addresses that match the VRRP
+     * prefix of 00:00:5E:00:01.
+     */
+    @Test
+    public void testNoNetworkLinkVrrpMacAddress() {
+        WifiConfiguration network1 = WifiConfigurationTestUtil.createPskNetwork();
+        WifiConfiguration network2 = WifiConfigurationTestUtil.createPskNetwork();
+        WifiConfiguration network3 = WifiConfigurationTestUtil.createPskNetwork();
+        network1.preSharedKey = "\"preSharedKey1\"";
+        network2.preSharedKey = "\"preSharedKey2\"";
+        network3.preSharedKey = "\"preSharedKey3\"";
+        verifyAddNetworkToWifiConfigManager(network1);
+        verifyAddNetworkToWifiConfigManager(network2);
+        verifyAddNetworkToWifiConfigManager(network3);
+
+        final String vrrpMacAddress = "00:00:5E:00:01:00";
+        // Set the same VRRP GW mac address for all of the networks.
+        assertTrue(mWifiConfigManager.setNetworkDefaultGwMacAddress(
+                network1.networkId, vrrpMacAddress));
+        assertTrue(mWifiConfigManager.setNetworkDefaultGwMacAddress(
+                network2.networkId, vrrpMacAddress));
+        assertTrue(mWifiConfigManager.setNetworkDefaultGwMacAddress(
+                network3.networkId, vrrpMacAddress));
+
+        // Now create fake scan detail corresponding to the networks.
+        ScanDetail networkScanDetail1 = createScanDetailForNetwork(network1);
+        ScanDetail networkScanDetail2 = createScanDetailForNetwork(network2);
+        ScanDetail networkScanDetail3 = createScanDetailForNetwork(network3);
+
+        // Now save all these scan details corresponding to each of this network and expect
+        // all of these networks to be linked with each other.
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(
+                networkScanDetail1));
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(
+                networkScanDetail2));
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(
+                networkScanDetail3));
+
+        // Now update the linked networks for all of the networks
+        mWifiConfigManager.updateNetworkSelectionStatus(network1.networkId,
+                TEST_NETWORK_SELECTION_ENABLE_REASON);
+        mWifiConfigManager.updateNetworkSelectionStatus(network2.networkId,
+                TEST_NETWORK_SELECTION_ENABLE_REASON);
+        mWifiConfigManager.updateNetworkSelectionStatus(network3.networkId,
+                TEST_NETWORK_SELECTION_ENABLE_REASON);
+        mWifiConfigManager.updateLinkedNetworks(network1.networkId);
+        mWifiConfigManager.updateLinkedNetworks(network2.networkId);
+        mWifiConfigManager.updateLinkedNetworks(network3.networkId);
+
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworks();
+        for (WifiConfiguration network : retrievedNetworks) {
+            assertNull(network.linkedConfigurations);
+        }
+    }
+
+    /**
+     * Verify that setRecentFailureAssociationStatus is setting the recent failure reason and
+     * expiration timestamp properly. Then, verify that cleanupExpiredRecentFailureReasons
+     * properly clears expired recent failure statuses.
+     */
+    @Test
+    public void testSetRecentFailureAssociationStatus() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+        NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(config);
+        int expectedStatusCode = WifiConfiguration.RECENT_FAILURE_POOR_CHANNEL_CONDITIONS;
+        long updateTimeMs = 1234L;
+        mResources.setInteger(R.integer.config_wifiRecentFailureReasonExpirationMinutes, 1);
+        long expectedExpirationTimeMs = updateTimeMs + 60_000;
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(updateTimeMs);
+        mWifiConfigManager.setRecentFailureAssociationStatus(config.networkId, expectedStatusCode);
+
+        // Verify the config changed broadcast is sent
+        verifyNetworkUpdateBroadcast();
+
+        // Verify the recent failure association status is set properly
+        List<WifiConfiguration> configs = mWifiConfigManager.getSavedNetworks(Process.WIFI_UID);
+        assertEquals(1, configs.size());
+        verify(mWifiMetrics).incrementRecentFailureAssociationStatusCount(eq(expectedStatusCode));
+        assertEquals(expectedStatusCode, configs.get(0).getRecentFailureReason());
+        assertEquals(updateTimeMs,
+                configs.get(0).recentFailure.getLastUpdateTimeSinceBootMillis());
+
+        // Verify at t-1 the failure status is not cleared yet.
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(expectedExpirationTimeMs - 1);
+        mWifiConfigManager.cleanupExpiredRecentFailureReasons();
+        assertEquals(expectedStatusCode, mWifiConfigManager.getSavedNetworks(Process.WIFI_UID)
+                .get(0).getRecentFailureReason());
+
+        // Verify at the expiration time the failure status is cleared.
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(expectedExpirationTimeMs);
+        mWifiConfigManager.cleanupExpiredRecentFailureReasons();
+        assertEquals(WifiConfiguration.RECENT_FAILURE_NONE,
+                mWifiConfigManager.getSavedNetworks(Process.WIFI_UID)
+                        .get(0).getRecentFailureReason());
+
+        verifyNetworkUpdateBroadcast();
+    }
+
+    /**
      * Verifies the linking of networks when they have scan results with same first 16 ASCII of
      * bssid in
      * {@link WifiConfigManager#getOrCreateScanDetailCacheForNetwork(WifiConfiguration)}.
@@ -2615,6 +2854,9 @@
         WifiConfiguration network1 = WifiConfigurationTestUtil.createPskNetwork();
         WifiConfiguration network2 = WifiConfigurationTestUtil.createPskNetwork();
         WifiConfiguration network3 = WifiConfigurationTestUtil.createPskNetwork();
+        network1.preSharedKey = "\"preSharedKey1\"";
+        network2.preSharedKey = "\"preSharedKey2\"";
+        network3.preSharedKey = "\"preSharedKey3\"";
         verifyAddNetworkToWifiConfigManager(network1);
         verifyAddNetworkToWifiConfigManager(network2);
         verifyAddNetworkToWifiConfigManager(network3);
@@ -2626,13 +2868,24 @@
 
         // Now save all these scan details corresponding to each of this network and expect
         // all of these networks to be linked with each other.
-        assertNotNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(
                 networkScanDetail1));
-        assertNotNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(
                 networkScanDetail2));
-        assertNotNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(
                 networkScanDetail3));
 
+        // Now update the linked networks for all of the networks
+        mWifiConfigManager.updateNetworkSelectionStatus(network1.networkId,
+                TEST_NETWORK_SELECTION_ENABLE_REASON);
+        mWifiConfigManager.updateNetworkSelectionStatus(network2.networkId,
+                TEST_NETWORK_SELECTION_ENABLE_REASON);
+        mWifiConfigManager.updateNetworkSelectionStatus(network3.networkId,
+                TEST_NETWORK_SELECTION_ENABLE_REASON);
+        mWifiConfigManager.updateLinkedNetworks(network1.networkId);
+        mWifiConfigManager.updateLinkedNetworks(network2.networkId);
+        mWifiConfigManager.updateLinkedNetworks(network3.networkId);
+
         List<WifiConfiguration> retrievedNetworks =
                 mWifiConfigManager.getConfiguredNetworks();
         for (WifiConfiguration network : retrievedNetworks) {
@@ -2641,7 +2894,8 @@
                 if (otherNetwork == network) {
                     continue;
                 }
-                assertNotNull(network.linkedConfigurations.get(otherNetwork.getKey()));
+                assertNotNull(network.linkedConfigurations.get(
+                        otherNetwork.getProfileKey()));
             }
         }
     }
@@ -2662,11 +2916,19 @@
         ScanDetail networkScanDetail1 = createScanDetailForNetwork(network1, "af:89:56:34:56:67");
         ScanDetail networkScanDetail2 = createScanDetailForNetwork(network2, "af:89:56:34:56:68");
 
-        assertNotNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(
                 networkScanDetail1));
-        assertNotNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(
                 networkScanDetail2));
 
+        // Now update the linked networks for all of the networks
+        mWifiConfigManager.updateNetworkSelectionStatus(network1.networkId,
+                TEST_NETWORK_SELECTION_ENABLE_REASON);
+        mWifiConfigManager.updateNetworkSelectionStatus(network2.networkId,
+                TEST_NETWORK_SELECTION_ENABLE_REASON);
+        mWifiConfigManager.updateLinkedNetworks(network1.networkId);
+        mWifiConfigManager.updateLinkedNetworks(network2.networkId);
+
         List<WifiConfiguration> retrievedNetworks =
                 mWifiConfigManager.getConfiguredNetworks();
         for (WifiConfiguration network : retrievedNetworks) {
@@ -2684,6 +2946,8 @@
     public void testNoNetworkLinkUsingBSSIDMatchForNetworksWithHighScanDetailCacheSize() {
         WifiConfiguration network1 = WifiConfigurationTestUtil.createPskNetwork();
         WifiConfiguration network2 = WifiConfigurationTestUtil.createPskNetwork();
+        network1.preSharedKey = "\"preSharedKey1\"";
+        network2.preSharedKey = "\"preSharedKey2\"";
         verifyAddNetworkToWifiConfigManager(network1);
         verifyAddNetworkToWifiConfigManager(network2);
 
@@ -2696,7 +2960,7 @@
                     createScanDetailForNetwork(
                             network1, test_bssid_base + Integer.toString(scan_result_num));
             assertNotNull(
-                    mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(
                             networkScanDetail));
         }
 
@@ -2705,9 +2969,17 @@
         ScanDetail networkScanDetail2 =
                 createScanDetailForNetwork(
                         network2, test_bssid_base + Integer.toString(scan_result_num++));
-        assertNotNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(
                 networkScanDetail2));
 
+        // Now update the linked networks for all of the networks
+        mWifiConfigManager.updateNetworkSelectionStatus(network1.networkId,
+                TEST_NETWORK_SELECTION_ENABLE_REASON);
+        mWifiConfigManager.updateNetworkSelectionStatus(network2.networkId,
+                TEST_NETWORK_SELECTION_ENABLE_REASON);
+        mWifiConfigManager.updateLinkedNetworks(network1.networkId);
+        mWifiConfigManager.updateLinkedNetworks(network2.networkId);
+
         List<WifiConfiguration> retrievedNetworks =
                 mWifiConfigManager.getConfiguredNetworks();
         for (WifiConfiguration network : retrievedNetworks) {
@@ -2724,6 +2996,8 @@
     public void testNetworkLinkUsingBSSIDMatchAndThenUnlinkDueToGwMacAddress() {
         WifiConfiguration network1 = WifiConfigurationTestUtil.createPskNetwork();
         WifiConfiguration network2 = WifiConfigurationTestUtil.createPskNetwork();
+        network1.preSharedKey = "\"preSharedKey1\"";
+        network2.preSharedKey = "\"preSharedKey2\"";
         verifyAddNetworkToWifiConfigManager(network1);
         verifyAddNetworkToWifiConfigManager(network2);
 
@@ -2733,11 +3007,19 @@
 
         // Now save all these scan details corresponding to each of this network and expect
         // all of these networks to be linked with each other.
-        assertNotNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(
                 networkScanDetail1));
-        assertNotNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(
                 networkScanDetail2));
 
+        // Now update the linked networks for all of the networks
+        mWifiConfigManager.updateNetworkSelectionStatus(network1.networkId,
+                TEST_NETWORK_SELECTION_ENABLE_REASON);
+        mWifiConfigManager.updateNetworkSelectionStatus(network2.networkId,
+                TEST_NETWORK_SELECTION_ENABLE_REASON);
+        mWifiConfigManager.updateLinkedNetworks(network1.networkId);
+        mWifiConfigManager.updateLinkedNetworks(network2.networkId);
+
         List<WifiConfiguration> retrievedNetworks =
                 mWifiConfigManager.getConfiguredNetworks();
         for (WifiConfiguration network : retrievedNetworks) {
@@ -2746,7 +3028,8 @@
                 if (otherNetwork == network) {
                     continue;
                 }
-                assertNotNull(network.linkedConfigurations.get(otherNetwork.getKey()));
+                assertNotNull(network.linkedConfigurations.get(
+                        otherNetwork.getProfileKey()));
             }
         }
 
@@ -2755,12 +3038,8 @@
                 network1.networkId, "de:ad:fe:45:23:34"));
         assertTrue(mWifiConfigManager.setNetworkDefaultGwMacAddress(
                 network2.networkId, "ad:de:fe:45:23:34"));
-
-        // Add some fake scan results again to re-evaluate the linking of networks.
-        assertNotNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
-                createScanDetailForNetwork(network1, "af:89:56:34:45:67")));
-        assertNotNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
-                createScanDetailForNetwork(network1, "af:89:56:34:45:68")));
+        mWifiConfigManager.updateLinkedNetworks(network1.networkId);
+        mWifiConfigManager.updateLinkedNetworks(network2.networkId);
 
         retrievedNetworks = mWifiConfigManager.getConfiguredNetworks();
         for (WifiConfiguration network : retrievedNetworks) {
@@ -2817,9 +3096,10 @@
         List<WifiConfiguration> retrievedNetworks =
                 mWifiConfigManager.getConfiguredNetworksWithPasswords();
         for (WifiConfiguration network : retrievedNetworks) {
-            if (network.getKey().equals(sharedNetwork1.getKey())) {
+            if (network.getProfileKey().equals(sharedNetwork1.getProfileKey())) {
                 sharedNetwork1Id = network.networkId;
-            } else if (network.getKey().equals(sharedNetwork2.getKey())) {
+            } else if (network.getProfileKey().equals(
+                    sharedNetwork2.getProfileKey())) {
                 sharedNetwork2Id = network.networkId;
             }
         }
@@ -2845,9 +3125,10 @@
         int updatedSharedNetwork2Id = WifiConfiguration.INVALID_NETWORK_ID;
         retrievedNetworks = mWifiConfigManager.getConfiguredNetworksWithPasswords();
         for (WifiConfiguration network : retrievedNetworks) {
-            if (network.getKey().equals(sharedNetwork1.getKey())) {
+            if (network.getProfileKey().equals(sharedNetwork1.getProfileKey())) {
                 updatedSharedNetwork1Id = network.networkId;
-            } else if (network.getKey().equals(sharedNetwork2.getKey())) {
+            } else if (network.getProfileKey().equals(
+                    sharedNetwork2.getProfileKey())) {
                 updatedSharedNetwork2Id = network.networkId;
             }
         }
@@ -2900,7 +3181,7 @@
         List<WifiConfiguration> retrievedNetworks =
                 mWifiConfigManager.getConfiguredNetworksWithPasswords();
         for (WifiConfiguration network : retrievedNetworks) {
-            if (network.getKey().equals(user1Network.getKey())) {
+            if (network.getProfileKey().equals(user1Network.getProfileKey())) {
                 user1NetworkId = network.networkId;
             }
         }
@@ -2967,7 +3248,7 @@
         List<WifiConfiguration> retrievedNetworks =
                 mWifiConfigManager.getConfiguredNetworksWithPasswords();
         for (WifiConfiguration network : retrievedNetworks) {
-            if (network.getKey().equals(ephemeralNetwork.getKey())) {
+            if (network.getProfileKey().equals(ephemeralNetwork.getProfileKey())) {
                 ephemeralNetworkId = network.networkId;
             }
         }
@@ -3207,6 +3488,8 @@
         // user2 is unlocked and switched to foreground.
         when(mUserManager.isUserUnlockingOrUnlocked(UserHandle.of(user2))).thenReturn(true);
         mWifiConfigManager.handleUserSwitch(user2);
+
+        verify(mWifiCarrierInfoManager).onUnlockedUserSwitching(eq(user2));
         // Ensure that the read was invoked.
         mContextConfigStoreMockOrder.verify(mWifiConfigStore)
                 .switchUserStoresAndRead(any(List.class));
@@ -3317,6 +3600,23 @@
     }
 
     /**
+     * Verify that hasNeverDetectedCaptivePortal is set to false after noteCaptivePortalDetected
+     * gets called.
+     */
+    @Test
+    public void testNoteCaptivePortalDetected() {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(openNetwork);
+
+        assertTrue(mWifiConfigManager.getConfiguredNetworks().get(0).getNetworkSelectionStatus()
+                .hasNeverDetectedCaptivePortal());
+
+        mWifiConfigManager.noteCaptivePortalDetected(openNetwork.networkId);
+        assertFalse(mWifiConfigManager.getConfiguredNetworks().get(0).getNetworkSelectionStatus()
+                .hasNeverDetectedCaptivePortal());
+    }
+
+    /**
      * Verifies the foreground user unlock via {@link WifiConfigManager#handleUserUnlock(int)}
      * results in a store read after bootup.
      */
@@ -3621,7 +3921,8 @@
         assertTrue(mWifiConfigManager.enableNetwork(
                 result.getNetworkId(), true, TEST_CREATOR_UID, TEST_CREATOR_NAME));
         assertEquals(result.getNetworkId(), mWifiConfigManager.getLastSelectedNetwork());
-        assertEquals(openNetwork.getKey(), mWifiConfigManager.getLastSelectedNetworkConfigKey());
+        assertEquals(openNetwork.getProfileKey(),
+                mWifiConfigManager.getLastSelectedNetworkConfigKey());
 
         assertTrue(mWifiConfigManager.removeNetwork(
                 result.getNetworkId(), TEST_CREATOR_UID, TEST_CREATOR_NAME));
@@ -3670,6 +3971,18 @@
         assertTrue(mWifiConfigManager.getConfiguredNetworks().isEmpty());
     }
 
+    private void verifySetUserConnectChoice(int preferredNetId, int notPreferredNetId) {
+        assertTrue(mWifiConfigManager.setNetworkCandidateScanResult(
+                notPreferredNetId, mock(ScanResult.class), 54, mock(SecurityParams.class)));
+        assertTrue(mWifiConfigManager.updateNetworkAfterConnect(preferredNetId, true, TEST_RSSI));
+        WifiConfiguration preferred = mWifiConfigManager.getConfiguredNetwork(preferredNetId);
+        assertNull(preferred.getNetworkSelectionStatus().getConnectChoice());
+        WifiConfiguration notPreferred = mWifiConfigManager.getConfiguredNetwork(notPreferredNetId);
+        assertEquals(preferred.getProfileKey(),
+                notPreferred.getNetworkSelectionStatus().getConnectChoice());
+        assertEquals(TEST_RSSI, notPreferred.getNetworkSelectionStatus().getConnectChoiceRssi());
+    }
+
     /**
      * Verifies that the connect choice is removed from all networks when
      * {@link WifiConfigManager#removeNetwork(int, int)} is invoked.
@@ -3682,16 +3995,14 @@
         verifyAddNetworkToWifiConfigManager(network1);
         verifyAddNetworkToWifiConfigManager(network2);
         verifyAddNetworkToWifiConfigManager(network3);
-
+        mWifiConfigManager.addOnNetworkUpdateListener(mListener);
         // Set connect choice of network 2 over network 1.
-        assertTrue(
-                mWifiConfigManager.setNetworkConnectChoice(
-                        network1.networkId, network2.getKey()));
+        verifySetUserConnectChoice(network2.networkId, network1.networkId);
 
         WifiConfiguration retrievedNetwork =
                 mWifiConfigManager.getConfiguredNetwork(network1.networkId);
         assertEquals(
-                network2.getKey(),
+                network2.getProfileKey(),
                 retrievedNetwork.getNetworkSelectionStatus().getConnectChoice());
 
         // Remove network 3 and ensure that the connect choice on network 1 is not removed.
@@ -3699,7 +4010,7 @@
                 network3.networkId, TEST_CREATOR_UID, TEST_CREATOR_NAME));
         retrievedNetwork = mWifiConfigManager.getConfiguredNetwork(network1.networkId);
         assertEquals(
-                network2.getKey(),
+                network2.getProfileKey(),
                 retrievedNetwork.getNetworkSelectionStatus().getConnectChoice());
 
         // Now remove network 2 and ensure that the connect choice on network 1 is removed..
@@ -3707,12 +4018,13 @@
                 network2.networkId, TEST_CREATOR_UID, TEST_CREATOR_NAME));
         retrievedNetwork = mWifiConfigManager.getConfiguredNetwork(network1.networkId);
         assertNotEquals(
-                network2.getKey(),
+                network2.getProfileKey(),
                 retrievedNetwork.getNetworkSelectionStatus().getConnectChoice());
 
         // This should have triggered 2 buffered writes. 1 for setting the connect choice, 1 for
         // clearing it after network removal.
         mContextConfigStoreMockOrder.verify(mWifiConfigStore, times(2)).write(eq(false));
+        verify(mListener, times(2)).onConnectChoiceRemoved(anyString());
     }
 
     /**
@@ -3726,19 +4038,16 @@
         verifyAddNetworkToWifiConfigManager(network1);
         verifyAddNetworkToWifiConfigManager(network2);
         verifyAddNetworkToWifiConfigManager(network3);
+        mWifiConfigManager.addOnNetworkUpdateListener(mListener);
 
         // Set connect choice of network 2 over network 1 and network 3.
-        assertTrue(
-                mWifiConfigManager.setNetworkConnectChoice(
-                        network1.networkId, network2.getKey()));
-        assertTrue(
-                mWifiConfigManager.setNetworkConnectChoice(
-                        network3.networkId, network2.getKey()));
+        verifySetUserConnectChoice(network2.networkId, network1.networkId);
+        verifySetUserConnectChoice(network2.networkId, network3.networkId);
 
         WifiConfiguration retrievedNetwork =
                 mWifiConfigManager.getConfiguredNetwork(network3.networkId);
         assertEquals(
-                network2.getKey(),
+                network2.getProfileKey(),
                 retrievedNetwork.getNetworkSelectionStatus().getConnectChoice());
 
         // Disable network 3
@@ -3751,8 +4060,10 @@
         // Ensure that the connect choice on network 1 is not removed.
         retrievedNetwork = mWifiConfigManager.getConfiguredNetwork(network1.networkId);
         assertEquals(
-                network2.getKey(),
+                network2.getProfileKey(),
                 retrievedNetwork.getNetworkSelectionStatus().getConnectChoice());
+
+        verify(mListener).onConnectChoiceRemoved(network3.getProfileKey());
     }
 
     /**
@@ -3764,18 +4075,22 @@
         WifiConfiguration savedOpenNetwork = WifiConfigurationTestUtil.createOpenNetwork();
         WifiConfiguration ephemeralNetwork = WifiConfigurationTestUtil.createEphemeralNetwork();
         WifiConfiguration passpointNetwork = WifiConfigurationTestUtil.createPasspointNetwork();
+        WifiConfiguration suggestionNetwork = WifiConfigurationTestUtil.createEphemeralNetwork();
+        suggestionNetwork.fromWifiNetworkSuggestion = true;
 
         verifyAddNetworkToWifiConfigManager(savedOpenNetwork);
         verifyAddEphemeralNetworkToWifiConfigManager(ephemeralNetwork);
         verifyAddPasspointNetworkToWifiConfigManager(passpointNetwork);
+        verifyAddEphemeralNetworkToWifiConfigManager(suggestionNetwork);
 
-        List<WifiConfiguration> expectedConfigsBeforeRemove = new ArrayList<WifiConfiguration>() {{
-                add(savedOpenNetwork);
-                add(ephemeralNetwork);
-                add(passpointNetwork);
-            }};
+        mWifiConfigManager.addOnNetworkUpdateListener(mListener);
+
+        List<WifiConfiguration> expectedConfigsBeforeRemove = new ArrayList<WifiConfiguration>(
+                Arrays.asList(savedOpenNetwork, ephemeralNetwork, passpointNetwork,
+                        suggestionNetwork));
         WifiConfigurationTestUtil.assertConfigurationsEqualForConfigManagerAddOrUpdate(
-                expectedConfigsBeforeRemove, mWifiConfigManager.getConfiguredNetworks());
+                expectedConfigsBeforeRemove,
+                mWifiConfigManager.getConfiguredNetworksWithPasswords());
 
         assertTrue(mWifiConfigManager.removeAllEphemeralOrPasspointConfiguredNetworks());
 
@@ -3787,6 +4102,9 @@
 
         // No more ephemeral or passpoint networks to remove now.
         assertFalse(mWifiConfigManager.removeAllEphemeralOrPasspointConfiguredNetworks());
+
+        // Suggestion and passpoint network will not trigger conncet choice remove.
+        verify(mListener).onConnectChoiceRemoved(ephemeralNetwork.getProfileKey());
     }
 
     /**
@@ -3800,7 +4118,7 @@
         verifyAddPasspointNetworkToWifiConfigManager(passpointNetwork);
 
         assertTrue(mWifiConfigManager.removePasspointConfiguredNetwork(
-                passpointNetwork.getKey()));
+                passpointNetwork.getProfileKey()));
     }
 
     /**
@@ -3811,11 +4129,11 @@
     @Test
     public void testRemoveSuggestionConfiguredNetwork() throws Exception {
         WifiConfiguration suggestedNetwork = WifiConfigurationTestUtil.createEphemeralNetwork();
+        suggestedNetwork.creatorUid = TEST_CREATOR_UID;
         suggestedNetwork.fromWifiNetworkSuggestion = true;
         verifyAddEphemeralNetworkToWifiConfigManager(suggestedNetwork);
 
-        assertTrue(mWifiConfigManager.removeSuggestionConfiguredNetwork(
-                suggestedNetwork.getKey()));
+        assertTrue(mWifiConfigManager.removeSuggestionConfiguredNetwork(suggestedNetwork));
     }
 
     /**
@@ -3900,12 +4218,14 @@
         verifyAddNetworkToWifiConfigManager(network1);
         verifyAddNetworkToWifiConfigManager(network2);
         verifyAddNetworkToWifiConfigManager(network3);
-        mWifiConfigManager.updateNetworkAfterConnect(network3.networkId);
+        mWifiConfigManager.updateNetworkAfterConnect(network3.networkId, false, TEST_RSSI);
 
         // Now set scan results of network2 to set the corresponding
         // {@link NetworkSelectionStatus#mSeenInLastQualifiedNetworkSelection} field.
         assertTrue(mWifiConfigManager.setNetworkCandidateScanResult(network2.networkId,
-                createScanDetailForNetwork(network2).getScanResult(), 54));
+                createScanDetailForNetwork(network2).getScanResult(), 54,
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_PSK)));
 
         // Retrieve the hidden network list & verify the order of the networks returned.
         List<WifiScanner.ScanSettings.HiddenNetwork> hiddenNetworks =
@@ -3928,6 +4248,7 @@
         // successfully.
         WifiConfiguration network1 = new WifiConfiguration();
         network1.SSID = ssid;
+        network1.convertLegacyFieldsToSecurityParamsIfNeeded();
         NetworkUpdateResult result = addNetworkToWifiConfigManager(network1);
         assertTrue(result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID);
         assertTrue(result.isNewNetwork());
@@ -3942,6 +4263,7 @@
         // didn't add a new duplicate network.
         WifiConfiguration network2 = new WifiConfiguration();
         network2.SSID = ssid;
+        network2.convertLegacyFieldsToSecurityParamsIfNeeded();
         result = addNetworkToWifiConfigManager(network2);
         assertTrue(result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID);
         assertFalse(result.isNewNetwork());
@@ -3964,7 +4286,7 @@
         // successfully.
         WifiConfiguration network1 = new WifiConfiguration();
         network1.SSID = ssid;
-        network1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+        network1.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
         network1.preSharedKey = "\"test_blah\"";
         NetworkUpdateResult result = addNetworkToWifiConfigManager(network1);
         assertTrue(result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID);
@@ -3980,7 +4302,7 @@
         // does add a new network.
         WifiConfiguration network2 = new WifiConfiguration();
         network2.SSID = ssid;
-        network2.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+        network2.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN);
         result = addNetworkToWifiConfigManager(network2);
         assertTrue(result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID);
         assertTrue(result.isNewNetwork());
@@ -4412,7 +4734,8 @@
     public void testResetSimNetworks() {
         String expectedIdentity = "13214561234567890@wlan.mnc456.mcc321.3gppnetwork.org";
         when(mDataTelephonyManager.getSubscriberId()).thenReturn("3214561234567890");
-        when(mDataTelephonyManager.getSimState()).thenReturn(TelephonyManager.SIM_STATE_READY);
+        when(mDataTelephonyManager.getSimApplicationState())
+                .thenReturn(TelephonyManager.SIM_STATE_LOADED);
         when(mDataTelephonyManager.getSimOperator()).thenReturn("321456");
         when(mDataTelephonyManager.getCarrierInfoForImsiEncryption(anyInt())).thenReturn(null);
         List<SubscriptionInfo> subList = new ArrayList<>() {{
@@ -4468,7 +4791,8 @@
     @Test
     public void testResetSimNetworks_getSimIdentityNull_shouldResetAllNonPeapSimIdentities() {
         when(mDataTelephonyManager.getSubscriberId()).thenReturn("");
-        when(mDataTelephonyManager.getSimState()).thenReturn(TelephonyManager.SIM_STATE_READY);
+        when(mDataTelephonyManager.getSimApplicationState())
+                .thenReturn(TelephonyManager.SIM_STATE_LOADED);
         when(mDataTelephonyManager.getSimOperator()).thenReturn("");
         when(mDataTelephonyManager.getCarrierInfoForImsiEncryption(anyInt())).thenReturn(null);
         List<SubscriptionInfo> subList = new ArrayList<>() {{
@@ -4527,7 +4851,8 @@
     @Test
     public void testLoadFromStoreResetsSimIdentity() {
         when(mDataTelephonyManager.getSubscriberId()).thenReturn("3214561234567890");
-        when(mDataTelephonyManager.getSimState()).thenReturn(TelephonyManager.SIM_STATE_READY);
+        when(mDataTelephonyManager.getSimApplicationState())
+                .thenReturn(TelephonyManager.SIM_STATE_LOADED);
         when(mDataTelephonyManager.getSimOperator()).thenReturn("321456");
         when(mDataTelephonyManager.getCarrierInfoForImsiEncryption(anyInt())).thenReturn(null);
 
@@ -4567,6 +4892,30 @@
     }
 
     /**
+     * Verifies that SIM configs are reset on {@link WifiConfigManager#loadFromStore()}.
+     */
+    @Test
+    public void testLoadFromStoreIgnoresMalformedNetworks() {
+        WifiConfiguration validNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        WifiConfiguration invalidNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        // SSID larger than 32 chars.
+        invalidNetwork.SSID = "\"sdhdfsjdkdskkdfskldfslksflfdslsflfsalaladlalaala;lalalal\"";
+
+        // Set up the store data.
+        List<WifiConfiguration> sharedNetworks = Arrays.asList(validNetwork, invalidNetwork);
+        setupStoreDataForRead(sharedNetworks, new ArrayList<>());
+
+        // read from store now
+        assertTrue(mWifiConfigManager.loadFromStore());
+
+        // Ensure that the invalid network has been ignored.
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworksWithPasswords();
+        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigManagerAddOrUpdate(
+                Arrays.asList(validNetwork), retrievedNetworks);
+    }
+
+    /**
      * Verifies the deletion of ephemeral network using
      * {@link WifiConfigManager#userTemporarilyDisabledNetwork(String)}.
      */
@@ -4618,15 +4967,14 @@
         verifyAddNetworkToWifiConfigManager(savedNetwork);
         verifyAddPasspointNetworkToWifiConfigManager(passpointNetwork);
 
+        mWifiConfigManager.addOnNetworkUpdateListener(mListener);
         // Set connect choice of passpoint network over saved network.
-        assertTrue(
-                mWifiConfigManager.setNetworkConnectChoice(
-                        savedNetwork.networkId, passpointNetwork.getKey()));
+        verifySetUserConnectChoice(passpointNetwork.networkId, savedNetwork.networkId);
 
         WifiConfiguration retrievedSavedNetwork =
                 mWifiConfigManager.getConfiguredNetwork(savedNetwork.networkId);
         assertEquals(
-                passpointNetwork.getKey(),
+                passpointNetwork.getProfileKey(),
                 retrievedSavedNetwork.getNetworkSelectionStatus().getConnectChoice());
 
         // Disable the passpoint network & ensure the user choice is now removed from saved network.
@@ -4634,10 +4982,11 @@
 
         retrievedSavedNetwork = mWifiConfigManager.getConfiguredNetwork(savedNetwork.networkId);
         assertNull(retrievedSavedNetwork.getNetworkSelectionStatus().getConnectChoice());
+        verify(mListener).onConnectChoiceRemoved(passpointNetwork.getProfileKey());
     }
 
     @Test
-    public void  testUserEnableDisabledNetwork() {
+    public void testUserEnableDisabledNetwork() {
         when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(true);
         WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
         List<WifiConfiguration> networks = new ArrayList<>();
@@ -4727,6 +5076,203 @@
         assertFalse(mWifiConfigManager.isNetworkTemporarilyDisabledByUser(network));
     }
 
+    @Test
+    public void testMaxDisableDurationEnableDisabledNetwork() {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        List<WifiConfiguration> networks = new ArrayList<>();
+        networks.add(openNetwork);
+        verifyAddNetworkToWifiConfigManager(openNetwork);
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworksWithPasswords();
+        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigManagerAddOrUpdate(
+                networks, retrievedNetworks);
+
+        // Disable the network.
+        long maxDisableDuration = ALL_NON_CARRIER_MERGED_WIFI_MAX_DISABLE_DURATION_MINUTES
+                * 60 * 1000;
+        when(mClock.getWallClockMillis()).thenReturn(0L);
+        String network = openNetwork.SSID;
+        mWifiConfigManager.userTemporarilyDisabledNetwork(network, TEST_UPDATE_UID);
+
+        // Verify that the network is disabled.
+        assertTrue(mWifiConfigManager.isNetworkTemporarilyDisabledByUser(network));
+
+        // Before the maxDisableDuration, the network should still be disabled.
+        when(mClock.getWallClockMillis()).thenReturn(maxDisableDuration);
+        assertTrue(mWifiConfigManager.isNetworkTemporarilyDisabledByUser(network));
+
+        // After the maxDisableDuration, the network should be enabled.
+        when(mClock.getWallClockMillis()).thenReturn(maxDisableDuration + 1);
+        assertFalse(mWifiConfigManager.isNetworkTemporarilyDisabledByUser(network));
+    }
+
+    /**
+     * Verify that when startRestrictingAutoJoinToSubscriptionId is called, all
+     * non-carrier-merged networks are disabled for a duration, and non-carrier-merged networks
+     * visible at the time of API call are disabled a longer duration, until they disappear from
+     * scan results for sufficiently long.
+     */
+    @Test
+    public void testStartTemporarilyDisablingAllNonCarrierMergedWifi() {
+        verifyAddNetworkToWifiConfigManager(WifiConfigurationTestUtil.createOpenNetwork());
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworksWithPasswords();
+        WifiConfiguration visibleNetwork = retrievedNetworks.get(0);
+        ScanDetail scanDetail = createScanDetailForNetwork(visibleNetwork, TEST_BSSID,
+                TEST_RSSI, TEST_FREQUENCY_1);
+        mWifiConfigManager.updateScanDetailCacheFromScanDetailForSavedNetwork(scanDetail);
+        WifiConfiguration otherNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+
+        // verify both networks are disabled at the start
+        when(mClock.getWallClockMillis()).thenReturn(0L);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(0L);
+        mWifiConfigManager.startRestrictingAutoJoinToSubscriptionId(5);
+        mWifiConfigManager.updateUserDisabledList(new ArrayList<String>());
+        assertTrue(mWifiConfigManager.isNonCarrierMergedNetworkTemporarilyDisabled(visibleNetwork));
+        assertTrue(mWifiConfigManager.isNonCarrierMergedNetworkTemporarilyDisabled(otherNetwork));
+
+        // the network visible at the start of the API call should still be disabled, but the
+        // other non-carrier-merged network should now be free to connect
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(
+                ALL_NON_CARRIER_MERGED_WIFI_MIN_DISABLE_DURATION_MINUTES * 60 * 1000 + 1L);
+        assertTrue(mWifiConfigManager.isNonCarrierMergedNetworkTemporarilyDisabled(visibleNetwork));
+        assertFalse(mWifiConfigManager.isNonCarrierMergedNetworkTemporarilyDisabled(otherNetwork));
+
+        // all networks should be clear to connect by now.
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(
+                WifiConfigManager.USER_DISCONNECT_NETWORK_BLOCK_EXPIRY_MS + 1);
+        when(mClock.getWallClockMillis()).thenReturn(
+                WifiConfigManager.USER_DISCONNECT_NETWORK_BLOCK_EXPIRY_MS + 1);
+        assertFalse(mWifiConfigManager.isNonCarrierMergedNetworkTemporarilyDisabled(
+                visibleNetwork));
+        assertFalse(mWifiConfigManager.isNonCarrierMergedNetworkTemporarilyDisabled(otherNetwork));
+    }
+
+    /**
+     * Verify when there that non carrier merged networks disabled due to carrier selection gets
+     * re-enabled when cellular data becomes unavailable.
+     */
+    @Test
+    public void testNoCellularDataEnabledNonCarrierMergedWifi() {
+        verifyAddNetworkToWifiConfigManager(WifiConfigurationTestUtil.createOpenNetwork());
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworksWithPasswords();
+        WifiConfiguration visibleNetwork = retrievedNetworks.get(0);
+        ScanDetail scanDetail = createScanDetailForNetwork(visibleNetwork, TEST_BSSID,
+                TEST_RSSI, TEST_FREQUENCY_1);
+        mWifiConfigManager.updateScanDetailCacheFromScanDetailForSavedNetwork(scanDetail);
+
+        // verify the network is disabled after startRestrictingAutoJoinToSubscriptionId is called
+        when(mClock.getWallClockMillis()).thenReturn(0L);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(0L);
+        mWifiConfigManager.startRestrictingAutoJoinToSubscriptionId(5);
+        mWifiConfigManager.updateUserDisabledList(new ArrayList<String>());
+        assertTrue(mWifiConfigManager.isNonCarrierMergedNetworkTemporarilyDisabled(visibleNetwork));
+
+        mWifiConfigManager.onCellularConnectivityChanged(WifiDataStall.CELLULAR_DATA_NOT_AVAILABLE);
+        assertFalse(mWifiConfigManager.isNonCarrierMergedNetworkTemporarilyDisabled(
+                visibleNetwork));
+    }
+
+    /**
+     * Verify that startRestrictingAutoJoinToSubscriptionId disables all passpoint networks
+     * with the same FQDN as the visible passpoint network, even if the SSID is different.
+     */
+    @Test
+    public void testStartTemporarilyDisablingAllNonCarrierMergedWifiPasspointFqdn()
+            throws Exception {
+        int testSubscriptionId = 5;
+        WifiConfiguration passpointNetwork_1 = WifiConfigurationTestUtil.createPasspointNetwork();
+        WifiConfiguration passpointNetwork_2 = WifiConfigurationTestUtil.createPasspointNetwork();
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        // Verify the 2 passpoint network have the same FQDN, but different SSID.
+        assertEquals(passpointNetwork_1.FQDN, passpointNetwork_2.FQDN);
+        assertNotEquals(passpointNetwork_1.SSID, passpointNetwork_2.SSID);
+        NetworkUpdateResult result =
+                verifyAddPasspointNetworkToWifiConfigManager(passpointNetwork_1);
+
+        // Make sure the passpointNetwork_1 is seen in a scan.
+        WifiConfiguration visibleNetwork = mWifiConfigManager.getConfiguredNetworksWithPasswords()
+                .get(0);
+        ScanDetail scanDetail = createScanDetailForNetwork(visibleNetwork, TEST_BSSID,
+                TEST_RSSI, TEST_FREQUENCY_1);
+        mWifiConfigManager.updateScanDetailForNetwork(result.getNetworkId(), scanDetail);
+
+        // verify all networks are disabled at the start
+        when(mClock.getWallClockMillis()).thenReturn(0L);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(0L);
+        mWifiConfigManager.startRestrictingAutoJoinToSubscriptionId(5);
+        assertTrue(mWifiConfigManager.isNonCarrierMergedNetworkTemporarilyDisabled(
+                passpointNetwork_1));
+        assertTrue(mWifiConfigManager.isNonCarrierMergedNetworkTemporarilyDisabled(
+                passpointNetwork_2));
+        assertTrue(mWifiConfigManager.isNonCarrierMergedNetworkTemporarilyDisabled(openNetwork));
+
+        // now the open network should be clear to connect because it is not visible at the start
+        // of the API call. But both passpoint network should still be disabled due to the FQDN
+        // of passpointNetwork_1 being disabled.
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(
+                ALL_NON_CARRIER_MERGED_WIFI_MIN_DISABLE_DURATION_MINUTES * 60 * 1000 + 1L);
+        assertTrue(mWifiConfigManager.isNonCarrierMergedNetworkTemporarilyDisabled(
+                passpointNetwork_1));
+        assertTrue(mWifiConfigManager.isNonCarrierMergedNetworkTemporarilyDisabled(
+                passpointNetwork_2));
+        assertFalse(mWifiConfigManager.isNonCarrierMergedNetworkTemporarilyDisabled(openNetwork));
+    }
+
+    /**
+     * Verify that stopRestrictingAutoJoinToSubscriptionId cancels the effects of
+     * startRestrictingAutoJoinToSubscriptionId.
+     */
+    @Test
+    public void testStopRestrictAutoJoinToSubscriptionId() {
+        verifyAddNetworkToWifiConfigManager(WifiConfigurationTestUtil.createOpenNetwork());
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworksWithPasswords();
+        WifiConfiguration visibleNetwork = retrievedNetworks.get(0);
+        ScanDetail scanDetail = createScanDetailForNetwork(visibleNetwork, TEST_BSSID,
+                TEST_RSSI, TEST_FREQUENCY_1);
+        mWifiConfigManager.updateScanDetailCacheFromScanDetailForSavedNetwork(scanDetail);
+
+        WifiConfiguration otherNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+
+        // verify both networks are disabled at the start
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(0L);
+        mWifiConfigManager.startRestrictingAutoJoinToSubscriptionId(5);
+        mWifiConfigManager.updateUserDisabledList(new ArrayList<String>());
+        assertTrue(mWifiConfigManager.isNonCarrierMergedNetworkTemporarilyDisabled(visibleNetwork));
+        assertTrue(mWifiConfigManager.isNonCarrierMergedNetworkTemporarilyDisabled(otherNetwork));
+
+        mWifiConfigManager.stopRestrictingAutoJoinToSubscriptionId();
+        assertFalse(mWifiConfigManager.isNonCarrierMergedNetworkTemporarilyDisabled(
+                visibleNetwork));
+        assertFalse(mWifiConfigManager.isNonCarrierMergedNetworkTemporarilyDisabled(otherNetwork));
+    }
+
+    /**
+     * Verify that the carrier network with the matching subscription ID is still allowed for
+     * auto connect after startRestrictingAutoJoinToSubscriptionId is called.
+     */
+    @Test
+    public void testStartTemporarilyDisablingAllNonCarrierMergedWifiAllowCarrierNetwork() {
+        int testSubscriptionId = 5;
+        WifiConfiguration network = WifiConfigurationTestUtil.createOpenNetwork();
+        network.carrierMerged = true;
+        network.subscriptionId = testSubscriptionId;
+        verifyAddNetworkToWifiConfigManager(network);
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworksWithPasswords();
+        WifiConfiguration carrierNetwork = retrievedNetworks.get(0);
+        WifiConfiguration otherNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+
+        // verify that the carrier network is not disabled.
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(0L);
+        mWifiConfigManager.startRestrictingAutoJoinToSubscriptionId(testSubscriptionId);
+        assertFalse(mWifiConfigManager
+                .isNonCarrierMergedNetworkTemporarilyDisabled(carrierNetwork));
+        assertTrue(mWifiConfigManager.isNonCarrierMergedNetworkTemporarilyDisabled(otherNetwork));
+    }
+
     private NetworkUpdateResult verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
             boolean withNetworkSettings,
             boolean withProfileOwnerPolicy,
@@ -4755,8 +5301,7 @@
             network = mWifiConfigManager.getConfiguredNetwork(networkId);
         }
         network.setIpConfiguration(ipConfiguration);
-        when(mWifiPermissionsUtil.isDeviceOwner(anyInt(), any()))
-                .thenReturn(withDeviceOwnerPolicy);
+        mockIsDeviceOwner(withDeviceOwnerPolicy);
         when(mWifiPermissionsUtil.isProfileOwner(anyInt(), any()))
                 .thenReturn(withProfileOwnerPolicy);
         when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt()))
@@ -4778,12 +5323,13 @@
                 new WifiConfigManager(
                         mContext, mClock, mUserManager, mWifiCarrierInfoManager,
                         mWifiKeyStore, mWifiConfigStore,
-                        mWifiPermissionsUtil, mWifiPermissionsWrapper, mWifiInjector,
+                        mWifiPermissionsUtil, mMacAddressUtil, mWifiMetrics, mWifiBlocklistMonitor,
+                        mWifiLastResortWatchdog,
                         mNetworkListSharedStoreData, mNetworkListUserStoreData,
                         mRandomizedMacStoreData,
-                        mFrameworkFacade, new Handler(mLooper.getLooper()), mDeviceConfigFacade,
-                        mWifiScoreCard, mLruConnectionTracker);
-        mWifiConfigManager.enableVerboseLogging(1);
+                        mFrameworkFacade, mDeviceConfigFacade,
+                        mWifiScoreCard, mLruConnectionTracker, mBuildProperties);
+        mWifiConfigManager.enableVerboseLogging(true);
     }
 
     /**
@@ -4792,30 +5338,6 @@
      * WifiConfigManager.
      */
     private void setDefaults(WifiConfiguration configuration) {
-        if (configuration.allowedProtocols.isEmpty()) {
-            configuration.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
-            configuration.allowedProtocols.set(WifiConfiguration.Protocol.WPA);
-        }
-        if (configuration.allowedKeyManagement.isEmpty()) {
-            configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
-            configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
-        }
-        if (configuration.allowedPairwiseCiphers.isEmpty()) {
-            configuration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.GCMP_256);
-            configuration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
-            configuration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);
-        }
-        if (configuration.allowedGroupCiphers.isEmpty()) {
-            configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.GCMP_256);
-            configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
-            configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
-            configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40);
-            configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104);
-        }
-        if (configuration.allowedGroupManagementCiphers.isEmpty()) {
-            configuration.allowedGroupManagementCiphers
-                    .set(WifiConfiguration.GroupMgmtCipher.BIP_GMAC_256);
-        }
         if (configuration.getIpAssignment() == IpConfiguration.IpAssignment.UNASSIGNED) {
             configuration.setIpAssignment(IpConfiguration.IpAssignment.DHCP);
         }
@@ -4938,14 +5460,17 @@
      */
     private boolean isNetworkInConfigStoreData(
             WifiConfiguration configuration, List<WifiConfiguration> networkList) {
-        boolean foundNetworkInStoreData = false;
         for (WifiConfiguration retrievedConfig : networkList) {
-            if (retrievedConfig.getKey().equals(configuration.getKey())) {
-                foundNetworkInStoreData = true;
-                break;
+            for (SecurityParams p: retrievedConfig.getSecurityParamsList()) {
+                WifiConfiguration tmpConfig = new WifiConfiguration(retrievedConfig);
+                tmpConfig.setSecurityParams(p);
+                if (tmpConfig.getProfileKey().equals(
+                        configuration.getProfileKey())) {
+                    return true;
+                }
             }
         }
-        return foundNetworkInStoreData;
+        return false;
     }
 
     /**
@@ -5249,7 +5774,7 @@
         verifyNetworkRemoveBroadcast();
         // Verify if the config store write was triggered without this new configuration.
         verifyNetworkNotInConfigStoreData(configuration);
-        verify(mBssidBlocklistMonitor, atLeastOnce()).handleNetworkRemoved(configuration.SSID);
+        verify(mWifiBlocklistMonitor, atLeastOnce()).handleNetworkRemoved(configuration.SSID);
     }
 
     /**
@@ -5290,70 +5815,6 @@
     }
 
     /**
-     * Verifies the network's selection status update.
-     *
-     * For temporarily disabled reasons, the method ensures that the status has changed only if
-     * disable reason counter has exceeded the threshold.
-     *
-     * For permanently disabled/enabled reasons, the method ensures that the public status has
-     * changed and the network change broadcast has been sent out.
-     */
-    private void verifyUpdateNetworkSelectionStatus(
-            int networkId, int reason, int temporaryDisableReasonCounter) {
-        when(mClock.getElapsedSinceBootMillis())
-                .thenReturn(TEST_ELAPSED_UPDATE_NETWORK_SELECTION_TIME_MILLIS);
-
-        // Fetch the current status of the network before we try to update the status.
-        WifiConfiguration retrievedNetwork = mWifiConfigManager.getConfiguredNetwork(networkId);
-        NetworkSelectionStatus currentStatus = retrievedNetwork.getNetworkSelectionStatus();
-        int currentDisableReason = currentStatus.getNetworkSelectionDisableReason();
-
-        // First set the status to the provided reason.
-        assertTrue(mWifiConfigManager.updateNetworkSelectionStatus(networkId, reason));
-
-        // Now fetch the network configuration and verify the new status of the network.
-        retrievedNetwork = mWifiConfigManager.getConfiguredNetwork(networkId);
-
-        NetworkSelectionStatus retrievedStatus = retrievedNetwork.getNetworkSelectionStatus();
-        int retrievedDisableReason = retrievedStatus.getNetworkSelectionDisableReason();
-        long retrievedDisableTime = retrievedStatus.getDisableTime();
-        int retrievedDisableReasonCounter = retrievedStatus.getDisableReasonCounter(reason);
-        int disableReasonThreshold =
-                WifiConfigManager.getNetworkSelectionDisableThreshold(reason);
-
-        if (reason == NetworkSelectionStatus.DISABLED_NONE) {
-            assertEquals(reason, retrievedDisableReason);
-            assertTrue(retrievedStatus.isNetworkEnabled());
-            assertEquals(
-                    NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP,
-                    retrievedDisableTime);
-            verifyUpdateNetworkStatus(retrievedNetwork, WifiConfiguration.Status.ENABLED);
-        } else if (reason < NetworkSelectionStatus.PERMANENTLY_DISABLED_STARTING_INDEX) {
-            // For temporarily disabled networks, we need to ensure that the current status remains
-            // until the threshold is crossed.
-            assertEquals(temporaryDisableReasonCounter, retrievedDisableReasonCounter);
-            if (retrievedDisableReasonCounter < disableReasonThreshold) {
-                assertEquals(currentDisableReason, retrievedDisableReason);
-                assertEquals(
-                        currentStatus.getNetworkSelectionStatus(),
-                        retrievedStatus.getNetworkSelectionStatus());
-            } else {
-                assertEquals(reason, retrievedDisableReason);
-                assertTrue(retrievedStatus.isNetworkTemporaryDisabled());
-                assertEquals(
-                        TEST_ELAPSED_UPDATE_NETWORK_SELECTION_TIME_MILLIS, retrievedDisableTime);
-            }
-        } else if (reason < NetworkSelectionStatus.NETWORK_SELECTION_DISABLED_MAX) {
-            assertEquals(reason, retrievedDisableReason);
-            assertTrue(retrievedStatus.isNetworkPermanentlyDisabled());
-            assertEquals(
-                    NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP,
-                    retrievedDisableTime);
-            verifyUpdateNetworkStatus(retrievedNetwork, WifiConfiguration.Status.DISABLED);
-        }
-    }
-
-    /**
      * Creates a scan detail corresponding to the provided network and given BSSID, level &frequency
      * values.
      */
@@ -5380,7 +5841,7 @@
      * Adds the provided network and then creates a scan detail corresponding to the network. The
      * method then creates a ScanDetail corresponding to the network and ensures that the network
      * is properly matched using
-     * {@link WifiConfigManager#getConfiguredNetworkForScanDetailAndCache(ScanDetail)} and also
+     * {@link WifiConfigManager#getSavedNetworkForScanDetailAndCache(ScanDetail)} and also
      * verifies that the provided scan detail was cached,
      */
     private void verifyAddSingleNetworkAndMatchScanDetailToNetworkAndCache(
@@ -5393,7 +5854,7 @@
         ScanResult scanResult = scanDetail.getScanResult();
 
         WifiConfiguration retrievedNetwork =
-                mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(scanDetail);
+                mWifiConfigManager.getSavedNetworkForScanDetailAndCache(scanDetail);
         // Retrieve the network with password data for comparison.
         retrievedNetwork =
                 mWifiConfigManager.getConfiguredNetworkWithPassword(retrievedNetwork.networkId);
@@ -5437,11 +5898,11 @@
 
     /**
      * Updates an existing network after connection using
-     * {@link WifiConfigManager#updateNetworkAfterConnect(int)} and asserts that the
+     * {@link WifiConfigManager#updateNetworkAfterConnect(int, boolean, int)} and asserts that the
      * |HasEverConnected| flag is set to true.
      */
     private void verifyUpdateNetworkAfterConnectHasEverConnectedTrue(int networkId) {
-        assertTrue(mWifiConfigManager.updateNetworkAfterConnect(networkId));
+        assertTrue(mWifiConfigManager.updateNetworkAfterConnect(networkId, false, TEST_RSSI));
         WifiConfiguration retrievedNetwork = mWifiConfigManager.getConfiguredNetwork(networkId);
         assertTrue("hasEverConnected expected to be true after connection.",
                 retrievedNetwork.getNetworkSelectionStatus().hasEverConnected());
@@ -5474,38 +5935,6 @@
     }
 
     /**
-     * Verifies SSID blocklist consistent with Watchdog trigger.
-     *
-     * Expected behavior: A SSID won't gets blocklisted if there only single SSID
-     * be observed and Watchdog trigger is activated.
-     */
-    @Test
-    public void verifyConsistentWatchdogAndSsidBlocklist() {
-
-        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
-        NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(openNetwork);
-
-        when(mWifiInjector.getWifiLastResortWatchdog().shouldIgnoreSsidUpdate())
-                .thenReturn(true);
-
-        int networkId = result.getNetworkId();
-        // First set it to enabled.
-        verifyUpdateNetworkSelectionStatus(
-                networkId, NetworkSelectionStatus.DISABLED_NONE, 0);
-
-        int assocRejectReason = NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION;
-        int assocRejectThreshold =
-                WifiConfigManager.getNetworkSelectionDisableThreshold(assocRejectReason);
-        for (int i = 1; i <= assocRejectThreshold; i++) {
-            assertFalse(mWifiConfigManager.updateNetworkSelectionStatus(
-                        networkId, assocRejectReason));
-        }
-
-        assertFalse(mWifiConfigManager.getConfiguredNetwork(networkId)
-                    .getNetworkSelectionStatus().isNetworkTemporaryDisabled());
-    }
-
-    /**
      * Verifies that isInFlakyRandomizationSsidHotlist returns true if the network's SSID is in
      * the hotlist and the network is using randomized MAC.
      */
@@ -5546,7 +5975,7 @@
                 TEST_RSSI, TEST_FREQUENCY_1);
         ScanResult scanResult = scanDetail.getScanResult();
 
-        mWifiConfigManager.updateScanDetailCacheFromScanDetail(scanDetail);
+        mWifiConfigManager.updateScanDetailCacheFromScanDetailForSavedNetwork(scanDetail);
         when(mClock.getWallClockMillis()).thenReturn(TEST_WALLCLOCK_CREATION_TIME_MILLIS + 2000);
         assertEquals(mWifiConfigManager.findScanRssi(result.getNetworkId(), 5000), TEST_RSSI);
     }
@@ -5565,7 +5994,7 @@
                 TEST_RSSI, TEST_FREQUENCY_1);
         ScanResult scanResult = scanDetail.getScanResult();
 
-        mWifiConfigManager.updateScanDetailCacheFromScanDetail(scanDetail);
+        mWifiConfigManager.updateScanDetailCacheFromScanDetailForSavedNetwork(scanDetail);
         when(mClock.getWallClockMillis()).thenReturn(TEST_WALLCLOCK_CREATION_TIME_MILLIS + 15000);
         assertEquals(mWifiConfigManager.findScanRssi(result.getNetworkId(), 5000),
                 WifiInfo.INVALID_RSSI);
@@ -5580,7 +6009,7 @@
         verifyAddNetworkToWifiConfigManager(testNetwork1);
         WifiConfiguration testNetwork2 = WifiConfigurationTestUtil.createOpenNetwork();
         verifyAddNetworkToWifiConfigManager(testNetwork2);
-        mWifiConfigManager.updateNetworkAfterConnect(testNetwork2.networkId);
+        mWifiConfigManager.updateNetworkAfterConnect(testNetwork2.networkId, false, TEST_RSSI);
         mWifiConfigManager.saveToStore(true);
         Pair<List<WifiConfiguration>, List<WifiConfiguration>> networkStoreData =
                 captureWriteNetworksListStoreData();
@@ -5594,13 +6023,13 @@
      */
     @Test
     public void testScanComparator() {
-        WifiConfiguration network1 = WifiConfigurationTestUtil.createOpenNetwork();
+        WifiConfiguration network1 = WifiConfigurationTestUtil.createOpenOweNetwork();
         verifyAddNetworkToWifiConfigManager(network1);
-        WifiConfiguration network2 = WifiConfigurationTestUtil.createOpenNetwork();
+        WifiConfiguration network2 = WifiConfigurationTestUtil.createOpenOweNetwork();
         verifyAddNetworkToWifiConfigManager(network2);
-        WifiConfiguration network3 = WifiConfigurationTestUtil.createOpenNetwork();
+        WifiConfiguration network3 = WifiConfigurationTestUtil.createOpenOweNetwork();
         verifyAddNetworkToWifiConfigManager(network3);
-        WifiConfiguration network4 = WifiConfigurationTestUtil.createOpenNetwork();
+        WifiConfiguration network4 = WifiConfigurationTestUtil.createOpenOweNetwork();
         verifyAddNetworkToWifiConfigManager(network4);
 
         // Connect two network in order, network3 --> network2
@@ -5608,7 +6037,9 @@
         verifyUpdateNetworkAfterConnectHasEverConnectedTrue(network2.networkId);
         // Set network4 {@link NetworkSelectionStatus#mSeenInLastQualifiedNetworkSelection} to true.
         assertTrue(mWifiConfigManager.setNetworkCandidateScanResult(network4.networkId,
-                createScanDetailForNetwork(network4).getScanResult(), 54));
+                createScanDetailForNetwork(network4).getScanResult(), 54,
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_OPEN)));
         List<WifiConfiguration> networkList = mWifiConfigManager.getConfiguredNetworks();
         // Expected order should be based on connection order and last qualified selection.
         Collections.sort(networkList, mWifiConfigManager.getScanListComparator());
@@ -5623,4 +6054,1070 @@
         mWifiConfigManager.removeNetwork(network2.networkId, TEST_CREATOR_UID, TEST_CREATOR_NAME);
         assertEquals(Integer.MAX_VALUE, mLruConnectionTracker.getAgeIndexOfNetwork(network2));
     }
+
+    /**
+     * Ensures that setting the user connect choice updates the
+     * NetworkSelectionStatus#mConnectChoice for all other WifiConfigurations in range in the last
+     * round of network selection.
+     *
+     * Expected behavior: WifiConfiguration.NetworkSelectionStatus#mConnectChoice is set to
+     *                    test1's configkey for test2. test3's WifiConfiguration is unchanged.
+     */
+    @Test
+    public void testSetUserConnectChoice() throws Exception {
+        WifiConfiguration selectedWifiConfig = WifiConfigurationTestUtil.createOpenNetwork();
+        selectedWifiConfig.getNetworkSelectionStatus().setCandidate(mock(ScanResult.class));
+        selectedWifiConfig.getNetworkSelectionStatus().setNetworkSelectionStatus(
+                NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED);
+        selectedWifiConfig.getNetworkSelectionStatus().setConnectChoice("bogusKey");
+
+        WifiConfiguration configInLastNetworkSelection =
+                WifiConfigurationTestUtil.createOpenNetwork();
+        configInLastNetworkSelection.getNetworkSelectionStatus()
+                .setSeenInLastQualifiedNetworkSelection(true);
+
+        WifiConfiguration configNotInLastNetworkSelection =
+                WifiConfigurationTestUtil.createOpenNetwork();
+
+        mWifiConfigManager.addOnNetworkUpdateListener(mListener);
+        List<WifiConfiguration> sharedConfigs = Arrays.asList(
+                selectedWifiConfig, configInLastNetworkSelection, configNotInLastNetworkSelection);
+        // Setup xml storage
+        setupStoreDataForRead(sharedConfigs, Arrays.asList());
+        assertTrue(mWifiConfigManager.loadFromStore());
+        verify(mWifiConfigStore).read();
+
+        assertTrue(mWifiConfigManager.updateNetworkAfterConnect(selectedWifiConfig.networkId,
+                true, TEST_RSSI));
+
+        selectedWifiConfig = mWifiConfigManager.getConfiguredNetwork(selectedWifiConfig.networkId);
+        assertEquals(NetworkSelectionStatus.DISABLED_NONE,
+                selectedWifiConfig.getNetworkSelectionStatus().getNetworkSelectionStatus());
+        assertNull(selectedWifiConfig.getNetworkSelectionStatus().getConnectChoice());
+
+        configInLastNetworkSelection = mWifiConfigManager.getConfiguredNetwork(
+                configInLastNetworkSelection.networkId);
+        assertEquals(selectedWifiConfig.getProfileKey(),
+                configInLastNetworkSelection.getNetworkSelectionStatus().getConnectChoice());
+
+        configNotInLastNetworkSelection = mWifiConfigManager.getConfiguredNetwork(
+                configNotInLastNetworkSelection.networkId);
+        assertNull(configNotInLastNetworkSelection.getNetworkSelectionStatus().getConnectChoice());
+
+        ArgumentCaptor<List<WifiConfiguration>> listArgumentCaptor =
+                ArgumentCaptor.forClass(List.class);
+        verify(mListener).onConnectChoiceSet(listArgumentCaptor.capture(),
+                eq(selectedWifiConfig.getProfileKey()), eq(TEST_RSSI));
+        Collection<WifiConfiguration> networks = listArgumentCaptor.getValue();
+        assertEquals(1, networks.size());
+    }
+
+    /**
+     * Ensures that setting the user connect choice updates the all the suggestion passpoint network
+     * by callback.
+     */
+    @Test
+    public void testSetUserConnectChoiceForSuggestionAndPasspointNetwork() throws Exception {
+        WifiConfiguration selectedWifiConfig = WifiConfigurationTestUtil.createOpenNetwork();
+        selectedWifiConfig.getNetworkSelectionStatus().setCandidate(mock(ScanResult.class));
+        selectedWifiConfig.getNetworkSelectionStatus().setNetworkSelectionStatus(
+                NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED);
+        selectedWifiConfig.getNetworkSelectionStatus().setConnectChoice("bogusKey");
+
+        WifiConfiguration configInLastNetworkSelection1 =
+                WifiConfigurationTestUtil.createOpenNetwork();
+        configInLastNetworkSelection1.getNetworkSelectionStatus()
+                .setSeenInLastQualifiedNetworkSelection(true);
+        configInLastNetworkSelection1.fromWifiNetworkSuggestion = true;
+
+        WifiConfiguration configInLastNetworkSelection2 =
+                WifiConfigurationTestUtil.createPasspointNetwork();
+        configInLastNetworkSelection2.getNetworkSelectionStatus()
+                .setSeenInLastQualifiedNetworkSelection(true);
+
+        mWifiConfigManager.addOnNetworkUpdateListener(mListener);
+        List<WifiConfiguration> sharedConfigs = Arrays.asList(
+                selectedWifiConfig, configInLastNetworkSelection1, configInLastNetworkSelection2);
+        // Setup xml storage
+        setupStoreDataForRead(sharedConfigs, Arrays.asList());
+        assertTrue(mWifiConfigManager.loadFromStore());
+        verify(mWifiConfigStore).read();
+
+        assertTrue(mWifiConfigManager.updateNetworkAfterConnect(selectedWifiConfig.networkId,
+                true, TEST_RSSI));
+
+        selectedWifiConfig = mWifiConfigManager.getConfiguredNetwork(selectedWifiConfig.networkId);
+        assertEquals(NetworkSelectionStatus.DISABLED_NONE,
+                selectedWifiConfig.getNetworkSelectionStatus().getNetworkSelectionStatus());
+        assertNull(selectedWifiConfig.getNetworkSelectionStatus().getConnectChoice());
+
+        ArgumentCaptor<List<WifiConfiguration>> listArgumentCaptor =
+                ArgumentCaptor.forClass(List.class);
+        verify(mListener).onConnectChoiceSet(listArgumentCaptor.capture(),
+                eq(selectedWifiConfig.getProfileKey()), eq(TEST_RSSI));
+        Collection<WifiConfiguration> networks = listArgumentCaptor.getValue();
+        assertEquals(2, networks.size());
+    }
+
+    @Test
+    public void testConnectToExistingNetworkSuccess() throws Exception {
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+        config.getNetworkSelectionStatus().setNetworkSelectionStatus(
+                NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED);
+        config.getNetworkSelectionStatus().setConnectChoice("bogusKey");
+
+        List<WifiConfiguration> sharedConfigs = Arrays.asList(config);
+        // Setup xml storage
+        setupStoreDataForRead(sharedConfigs, Arrays.asList());
+        assertTrue(mWifiConfigManager.loadFromStore());
+        verify(mWifiConfigStore).read();
+
+        config = mWifiConfigManager.getConfiguredNetwork(config.networkId);
+        assertFalse(config.getNetworkSelectionStatus().isNetworkEnabled());
+        assertNotEquals(TEST_CREATOR_UID, config.lastConnectUid);
+        assertEquals("bogusKey", config.getNetworkSelectionStatus().getConnectChoice());
+
+        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(TEST_CREATOR_UID))
+                .thenReturn(true);
+
+        mWifiConfigManager.updateBeforeConnect(config.networkId, TEST_CREATOR_UID);
+
+        config = mWifiConfigManager.getConfiguredNetwork(config.networkId);
+        // network became enabled
+        assertTrue(config.getNetworkSelectionStatus().isNetworkEnabled());
+        // lastConnectUid updated
+        assertEquals(TEST_CREATOR_UID, config.lastConnectUid);
+        // connect choice should still be "bogusKey". This should only get cleared after the
+        // connection has actually completed.
+        assertEquals("bogusKey", config.getNetworkSelectionStatus().getConnectChoice());
+    }
+
+    /**
+     * Test that if callingUid doesn't have NETWORK_SETTINGS permission, the connect succeeds,
+     * but user connect choice isn't cleared.
+     */
+    @Test
+    public void testConnectToExistingNetworkSuccessNoNetworkSettingsPermission() throws Exception {
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+        config.getNetworkSelectionStatus().setNetworkSelectionStatus(
+                NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED);
+        config.getNetworkSelectionStatus().setConnectChoice("bogusKey");
+
+        List<WifiConfiguration> sharedConfigs = Arrays.asList(config);
+        // Setup xml storage
+        setupStoreDataForRead(sharedConfigs, Arrays.asList());
+        assertTrue(mWifiConfigManager.loadFromStore());
+        verify(mWifiConfigStore).read();
+
+        config = mWifiConfigManager.getConfiguredNetwork(config.networkId);
+        assertFalse(config.getNetworkSelectionStatus().isNetworkEnabled());
+        assertNotEquals(TEST_CREATOR_UID, config.lastConnectUid);
+        assertEquals("bogusKey", config.getNetworkSelectionStatus().getConnectChoice());
+
+        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(TEST_CREATOR_UID))
+                .thenReturn(false);
+
+        mWifiConfigManager.updateBeforeConnect(config.networkId, TEST_CREATOR_UID);
+
+        config = mWifiConfigManager.getConfiguredNetwork(config.networkId);
+        // network became enabled
+        assertTrue(config.getNetworkSelectionStatus().isNetworkEnabled());
+        // lastConnectUid updated
+        assertEquals(TEST_CREATOR_UID, config.lastConnectUid);
+        // connect choice not cleared
+        assertEquals("bogusKey", config.getNetworkSelectionStatus().getConnectChoice());
+    }
+
+    @Test
+    public void testConnectToExistingNetworkFailure_UidDoesNotBelongToCurrentUser()
+            throws Exception {
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+        config.getNetworkSelectionStatus().setNetworkSelectionStatus(
+                NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED);
+        config.getNetworkSelectionStatus().setConnectChoice("bogusKey");
+
+        List<WifiConfiguration> sharedConfigs = Arrays.asList(config);
+        // Setup xml storage
+        setupStoreDataForRead(sharedConfigs, Arrays.asList());
+        assertTrue(mWifiConfigManager.loadFromStore());
+        verify(mWifiConfigStore).read();
+
+        config = mWifiConfigManager.getConfiguredNetwork(config.networkId);
+        assertFalse(config.getNetworkSelectionStatus().isNetworkEnabled());
+        assertNotEquals(TEST_OTHER_USER_UID, config.lastConnectUid);
+        assertEquals("bogusKey", config.getNetworkSelectionStatus().getConnectChoice());
+
+        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(TEST_OTHER_USER_UID))
+                .thenReturn(false);
+        when(mUserManager.isSameProfileGroup(any(), any())).thenReturn(false);
+
+        mWifiConfigManager.updateBeforeConnect(config.networkId, TEST_OTHER_USER_UID);
+
+        // network still disabled
+        assertFalse(config.getNetworkSelectionStatus().isNetworkEnabled());
+        // lastConnectUid wasn't changed
+        assertNotEquals(TEST_OTHER_USER_UID, config.lastConnectUid);
+        // connect choice wasn't changed
+        assertEquals("bogusKey", config.getNetworkSelectionStatus().getConnectChoice());
+    }
+
+    @Test
+    public void updateBeforeSaveNetworkSuccess() throws Exception {
+        // initialize WifiConfigManager and start off with one network
+        verifyAddNetworkToWifiConfigManager(WifiConfigurationTestUtil.createOpenNetwork());
+
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+
+        NetworkUpdateResult result =
+                mWifiConfigManager.updateBeforeSaveNetwork(config, TEST_CREATOR_UID);
+
+        assertTrue(result.isSuccess());
+
+        config = mWifiConfigManager.getConfiguredNetwork(result.getNetworkId());
+
+        assertNotNull(config);
+        assertTrue(config.getNetworkSelectionStatus().isNetworkEnabled());
+    }
+
+    /** Verifies that configs save fails with an invalid UID. */
+    @Test
+    public void updateBeforeSaveNetworkConfigFailsWithInvalidUid() throws Exception {
+        // initialize WifiConfigManager and start off with one network
+        verifyAddNetworkToWifiConfigManager(WifiConfigurationTestUtil.createOpenNetwork());
+
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+
+        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(TEST_OTHER_USER_UID))
+                .thenReturn(false);
+        when(mWifiPermissionsUtil.doesUidBelongToCurrentUser(TEST_OTHER_USER_UID))
+                .thenReturn(false);
+
+        NetworkUpdateResult result =
+                mWifiConfigManager.updateBeforeSaveNetwork(config, TEST_OTHER_USER_UID);
+
+        assertFalse(result.isSuccess());
+        assertNull(mWifiConfigManager.getConfiguredNetwork(config.networkId));
+    }
+
+    /** Verifies that save fails with a null config. */
+    @Test
+    public void updateBeforeSaveNetworkNullConfigFails() throws Exception {
+        // initialize WifiConfigManager and start off with one network
+        verifyAddNetworkToWifiConfigManager(WifiConfigurationTestUtil.createOpenNetwork());
+
+        NetworkUpdateResult result =
+                mWifiConfigManager.updateBeforeSaveNetwork(null, TEST_OTHER_USER_UID);
+
+        assertFalse(result.isSuccess());
+    }
+
+    /**
+     * Verifies all ephemeral carrier networks are removed
+     */
+    @Test
+    public void testRemoveEphemeralCarrierNetwork() throws Exception {
+        WifiConfiguration savedPskNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        WifiConfiguration ephemeralCarrierNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        ephemeralCarrierNetwork.ephemeral = true;
+        ephemeralCarrierNetwork.subscriptionId = DATA_SUBID;
+        WifiConfiguration ephemeralNonCarrierNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        ephemeralNonCarrierNetwork.ephemeral = true;
+        verifyAddNetworkToWifiConfigManager(savedPskNetwork);
+        verifyAddEphemeralNetworkToWifiConfigManager(ephemeralCarrierNetwork);
+        verifyAddEphemeralNetworkToWifiConfigManager(ephemeralNonCarrierNetwork);
+
+        List<WifiConfiguration> networks = Arrays.asList(savedPskNetwork,
+                ephemeralNonCarrierNetwork);
+        mWifiConfigManager.removeEphemeralCarrierNetworks();
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworksWithPasswords();
+        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigManagerAddOrUpdate(
+                networks, retrievedNetworks);
+    }
+
+    /**
+     * Check persist random Mac Address is generated from stable Mac Random key.
+     */
+    @Test
+    public void testGetPersistRandomMacAddress() {
+        WifiConfiguration network = WifiConfigurationTestUtil.createPskNetwork();
+        mWifiConfigManager.getPersistentMacAddress(network);
+        verify(mMacAddressUtil).calculatePersistentMac(eq(network.getNetworkKey()), any());
+    }
+
+    private void verifyAddUpgradableNetwork(
+            WifiConfiguration baseConfig,
+            WifiConfiguration upgradableConfig) {
+        int baseSecurityType = baseConfig.getDefaultSecurityParams().getSecurityType();
+        int upgradableSecurityType = upgradableConfig.getDefaultSecurityParams().getSecurityType();
+
+        NetworkUpdateResult result = addNetworkToWifiConfigManager(baseConfig);
+        assertTrue(result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID);
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworksWithPasswords();
+        assertEquals(1, retrievedNetworks.size());
+        WifiConfiguration unmergedNetwork = retrievedNetworks.get(0);
+        assertTrue(unmergedNetwork.isSecurityType(baseSecurityType));
+        assertTrue(unmergedNetwork.isSecurityType(upgradableSecurityType));
+        assertTrue(unmergedNetwork.getSecurityParams(upgradableSecurityType)
+                .isAddedByAutoUpgrade());
+
+        result = addNetworkToWifiConfigManager(upgradableConfig);
+        assertTrue(result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID);
+
+        retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworksWithPasswords();
+        // Two networks should be merged into one.
+        assertEquals(1, retrievedNetworks.size());
+        WifiConfiguration mergedNetwork = retrievedNetworks.get(0);
+        assertTrue(mergedNetwork.isSecurityType(baseSecurityType));
+        assertTrue(mergedNetwork.isSecurityType(upgradableSecurityType));
+        assertFalse(mergedNetwork.getSecurityParams(upgradableSecurityType)
+                .isAddedByAutoUpgrade());
+    }
+
+    /**
+     * Verifies the addition of an upgradable network using
+     * {@link WifiConfigManager#addOrUpdateNetwork(WifiConfiguration, int)}
+     */
+    @Test
+    public void testAddUpgradableNetworkForPskSae() {
+        WifiConfiguration baseConfig = new WifiConfiguration();
+        baseConfig.SSID = "\"upgradableNetwork\"";
+        baseConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
+        baseConfig.preSharedKey = "\"Passw0rd\"";
+        WifiConfiguration upgradableConfig = new WifiConfiguration();
+        upgradableConfig.SSID = "\"upgradableNetwork\"";
+        upgradableConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
+        upgradableConfig.preSharedKey = "\"Passw0rd\"";
+
+        verifyAddUpgradableNetwork(baseConfig, upgradableConfig);
+    }
+
+    /**
+     * Verifies the addition of an upgradable network using
+     * {@link WifiConfigManager#addOrUpdateNetwork(WifiConfiguration, int)}
+     */
+    @Test
+    public void testAddUpgradableNetworkForWpa2Wpa3Enterprise() {
+        WifiConfiguration baseConfig = new WifiConfiguration();
+        baseConfig.SSID = "\"upgradableNetwork\"";
+        baseConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
+        baseConfig.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.SIM);
+        WifiConfiguration upgradableConfig = new WifiConfiguration();
+        upgradableConfig.SSID = "\"upgradableNetwork\"";
+        upgradableConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
+        upgradableConfig.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.SIM);
+
+        verifyAddUpgradableNetwork(baseConfig, upgradableConfig);
+    }
+
+    /**
+     * Verifies the addition of an upgradable network using
+     * {@link WifiConfigManager#addOrUpdateNetwork(WifiConfiguration, int)}
+     */
+    @Test
+    public void testAddUpgradableNetworkForOpenOwe() {
+        WifiConfiguration baseConfig = new WifiConfiguration();
+        baseConfig.SSID = "\"upgradableNetwork\"";
+        baseConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN);
+        WifiConfiguration upgradableConfig = new WifiConfiguration();
+        upgradableConfig.SSID = "\"upgradableNetwork\"";
+        upgradableConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OWE);
+
+        verifyAddUpgradableNetwork(baseConfig, upgradableConfig);
+    }
+
+    private void verifyAddDowngradableNetwork(
+            WifiConfiguration baseConfig,
+            WifiConfiguration downgradableConfig) {
+        int baseSecurityType = baseConfig.getDefaultSecurityParams().getSecurityType();
+        int downgradableSecurityType = downgradableConfig.getDefaultSecurityParams()
+                .getSecurityType();
+
+        NetworkUpdateResult result = addNetworkToWifiConfigManager(baseConfig);
+        assertTrue(result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID);
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworksWithPasswords();
+        assertEquals(1, retrievedNetworks.size());
+        WifiConfiguration unmergedNetwork = retrievedNetworks.get(0);
+        assertEquals(baseSecurityType,
+                unmergedNetwork.getDefaultSecurityParams().getSecurityType());
+        assertTrue(unmergedNetwork.isSecurityType(baseSecurityType));
+        assertFalse(unmergedNetwork.isSecurityType(downgradableSecurityType));
+        assertFalse(unmergedNetwork.getSecurityParams(baseSecurityType)
+                .isAddedByAutoUpgrade());
+
+        result = addNetworkToWifiConfigManager(downgradableConfig);
+        assertTrue(result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID);
+
+        retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworksWithPasswords();
+        // Two networks should be merged into one.
+        assertEquals(1, retrievedNetworks.size());
+        WifiConfiguration mergedNetwork = retrievedNetworks.get(0);
+        // default type is changed to downgrading one.
+        assertEquals(downgradableSecurityType,
+                mergedNetwork.getDefaultSecurityParams().getSecurityType());
+        assertTrue(mergedNetwork.isSecurityType(baseSecurityType));
+        assertTrue(mergedNetwork.isSecurityType(downgradableSecurityType));
+        assertFalse(mergedNetwork.getSecurityParams(baseSecurityType)
+                .isAddedByAutoUpgrade());
+    }
+    /**
+     * Verifies the addition of a downgradable network using
+     * {@link WifiConfigManager#addOrUpdateNetwork(WifiConfiguration, int)}
+     */
+    @Test
+    public void testAddDowngradableNetworkSaePsk() {
+        WifiConfiguration baseConfig = new WifiConfiguration();
+        baseConfig.SSID = "\"downgradableNetwork\"";
+        baseConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
+        baseConfig.preSharedKey = "\"Passw0rd\"";
+        WifiConfiguration downgradableConfig = new WifiConfiguration();
+        downgradableConfig.SSID = "\"downgradableNetwork\"";
+        downgradableConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
+        downgradableConfig.preSharedKey = "\"Passw0rd\"";
+
+        verifyAddDowngradableNetwork(
+                baseConfig,
+                downgradableConfig);
+    }
+
+    /**
+     * Verifies the addition of a downgradable network using
+     * {@link WifiConfigManager#addOrUpdateNetwork(WifiConfiguration, int)}
+     */
+    @Test
+    public void testAddDowngradableNetworkWpa2Wpa3Enterprise() {
+        WifiConfiguration baseConfig = new WifiConfiguration();
+        baseConfig.SSID = "\"downgradableNetwork\"";
+        baseConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
+        baseConfig.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.SIM);
+        WifiConfiguration downgradableConfig = new WifiConfiguration();
+        downgradableConfig.SSID = "\"downgradableNetwork\"";
+        downgradableConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
+        downgradableConfig.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.SIM);
+
+        verifyAddDowngradableNetwork(
+                baseConfig,
+                downgradableConfig);
+    }
+
+    /**
+     * Verifies the addition of a downgradable network using
+     * {@link WifiConfigManager#addOrUpdateNetwork(WifiConfiguration, int)}
+     */
+    @Test
+    public void testAddDowngradableNetworkOweOpen() {
+        WifiConfiguration baseConfig = new WifiConfiguration();
+        baseConfig.SSID = "\"downgradableNetwork\"";
+        baseConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OWE);
+        WifiConfiguration downgradableConfig = new WifiConfiguration();
+        downgradableConfig.SSID = "\"downgradableNetwork\"";
+        downgradableConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN);
+
+        verifyAddDowngradableNetwork(
+                baseConfig,
+                downgradableConfig);
+    }
+
+    private void verifyLoadFromStoreMergeUpgradableConfigurations(
+            WifiConfiguration baseConfig,
+            WifiConfiguration upgradableConfig) {
+        int baseSecurityType = baseConfig.getDefaultSecurityParams().getSecurityType();
+        int upgradableSecurityType = upgradableConfig.getDefaultSecurityParams().getSecurityType();
+
+        // Set up the store data.
+        List<WifiConfiguration> sharedNetworks = new ArrayList<WifiConfiguration>() {
+            {
+                add(baseConfig);
+                add(upgradableConfig);
+            }
+        };
+        setupStoreDataForRead(sharedNetworks, new ArrayList<>());
+
+        // read from store now
+        assertTrue(mWifiConfigManager.loadFromStore());
+
+        // assert that the expected identities are reset
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworksWithPasswords();
+        assertEquals(1, retrievedNetworks.size());
+
+        WifiConfiguration mergedNetwork = retrievedNetworks.get(0);
+        assertTrue(mergedNetwork.isSecurityType(baseSecurityType));
+        assertTrue(mergedNetwork.isSecurityType(upgradableSecurityType));
+        assertFalse(mergedNetwork.getSecurityParams(upgradableSecurityType)
+                .isAddedByAutoUpgrade());
+    }
+
+    /**
+     * Verifies that upgradable configuration are merged on
+     * {@link WifiConfigManager#loadFromStore()}.
+     */
+    @Test
+    public void testLoadFromStoreMergeUpgradableConfigurationsPskSae() {
+        WifiConfiguration baseConfig = new WifiConfiguration();
+        baseConfig.SSID = "\"upgradableNetwork\"";
+        baseConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
+        baseConfig.preSharedKey = "\"Passw0rd\"";
+        WifiConfiguration upgradableConfig = new WifiConfiguration();
+        upgradableConfig.SSID = "\"upgradableNetwork\"";
+        upgradableConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
+        upgradableConfig.preSharedKey = "\"Passw0rd\"";
+
+        verifyLoadFromStoreMergeUpgradableConfigurations(
+                baseConfig,
+                upgradableConfig);
+        // reverse ordered networks should still be merged.
+        verifyLoadFromStoreMergeUpgradableConfigurations(
+                upgradableConfig,
+                baseConfig);
+    }
+
+    /**
+     * Verifies that upgradable configuration are merged on
+     * {@link WifiConfigManager#loadFromStore()}.
+     */
+    @Test
+    public void testLoadFromStoreMergeUpgradableConfigurationsOpenOwe() {
+        WifiConfiguration baseConfig = new WifiConfiguration();
+        baseConfig.SSID = "\"upgradableNetwork\"";
+        baseConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN);
+        WifiConfiguration upgradableConfig = new WifiConfiguration();
+        upgradableConfig.SSID = "\"upgradableNetwork\"";
+        upgradableConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OWE);
+
+        verifyLoadFromStoreMergeUpgradableConfigurations(
+                baseConfig,
+                upgradableConfig);
+        // reverse ordered networks should still be merged.
+        verifyLoadFromStoreMergeUpgradableConfigurations(
+                upgradableConfig,
+                baseConfig);
+    }
+
+    /**
+     * Verifies that upgradable configuration are merged on
+     * {@link WifiConfigManager#loadFromStore()}.
+     */
+    @Test
+    public void testLoadFromStoreMergeUpgradableConfigurationsWpa2Wpa3Enterprise() {
+        WifiConfiguration baseConfig = new WifiConfiguration();
+        baseConfig.SSID = "\"upgradableNetwork\"";
+        baseConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
+        baseConfig.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.SIM);
+        WifiConfiguration upgradableConfig = new WifiConfiguration();
+        upgradableConfig.SSID = "\"upgradableNetwork\"";
+        upgradableConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
+        upgradableConfig.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.SIM);
+
+        verifyLoadFromStoreMergeUpgradableConfigurations(
+                baseConfig,
+                upgradableConfig);
+        // reverse ordered networks should still be merged.
+        verifyLoadFromStoreMergeUpgradableConfigurations(
+                upgradableConfig,
+                baseConfig);
+    }
+
+    private void verifyTransitionDisableIndicationForSecurityType(
+            WifiConfiguration testNetwork, int indication) {
+
+        int disabledType = testNetwork.getDefaultSecurityParams().getSecurityType();
+        NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(testNetwork);
+        int networkId = result.getNetworkId();
+
+        WifiConfiguration configBefore = mWifiConfigManager.getConfiguredNetwork(networkId);
+        assertTrue(configBefore.getSecurityParams(disabledType).isEnabled());
+
+        mWifiConfigManager.updateNetworkTransitionDisable(result.getNetworkId(), indication);
+
+        WifiConfiguration configAfter = mWifiConfigManager.getConfiguredNetwork(networkId);
+        assertFalse(configAfter.getSecurityParams(disabledType).isEnabled());
+    }
+
+    /**
+     * Verify Transition Disable Indication for the PSK/SAE configuration.
+     */
+    @Test
+    public void testPskSaeTransitionDisableIndication() {
+        verifyTransitionDisableIndicationForSecurityType(
+                WifiConfigurationTestUtil.createPskNetwork(),
+                WifiMonitor.TDI_USE_WPA3_PERSONAL);
+    }
+
+    /**
+     * Verify Transition Disable Indication for WPA2/WPA3 Enterprise configuration.
+     */
+    @Test
+    public void testWpa2Wpa3EnterpriseValidationTransitionDisableIndication() {
+        verifyTransitionDisableIndicationForSecurityType(
+                WifiConfigurationTestUtil.createEapNetwork(),
+                WifiMonitor.TDI_USE_WPA3_ENTERPRISE);
+    }
+
+    /**
+     * Verify Transition Disable Indication for Open/OWE configuration.
+     */
+    @Test
+    public void testOpenOweValidationTransitionDisableIndication() {
+        verifyTransitionDisableIndicationForSecurityType(
+                WifiConfigurationTestUtil.createOpenNetwork(),
+                WifiMonitor.TDI_USE_ENHANCED_OPEN);
+    }
+
+    /**
+     * Verify Transition Disable Indication for the AP validation enforcement.
+     */
+    @Test
+    public void testSaeApValidationTransitionDisableIndication() {
+        WifiConfiguration testNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(testNetwork);
+        int networkId = result.getNetworkId();
+
+        WifiConfiguration configBefore = mWifiConfigManager.getConfiguredNetwork(networkId);
+        assertFalse(configBefore.getSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE)
+                .isSaePkOnlyMode());
+
+        int indication = WifiMonitor.TDI_USE_SAE_PK;
+        mWifiConfigManager.updateNetworkTransitionDisable(result.getNetworkId(), indication);
+
+        WifiConfiguration configAfter = mWifiConfigManager.getConfiguredNetwork(networkId);
+        assertTrue(configAfter.getSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE)
+                .isSaePkOnlyMode());
+    }
+
+    private void verifyNonApplicableTransitionDisableIndicationForSecurityType(
+            WifiConfiguration testNetwork, int indication) {
+
+        int disabledType = testNetwork.getDefaultSecurityParams().getSecurityType();
+        NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(testNetwork);
+        int networkId = result.getNetworkId();
+
+        WifiConfiguration configBefore = mWifiConfigManager.getConfiguredNetwork(networkId);
+        assertTrue(configBefore.getSecurityParams(disabledType).isEnabled());
+
+        mWifiConfigManager.updateNetworkTransitionDisable(result.getNetworkId(), indication);
+
+        WifiConfiguration configAfter = mWifiConfigManager.getConfiguredNetwork(networkId);
+        assertTrue(configBefore.getSecurityParams(disabledType).isEnabled());
+    }
+
+    /**
+     * Verify Transition Disable Indication does not change anything if
+     * the type is not applicable.
+     */
+    @Test
+    public void testOpenTransitionDisableIndicationNotAffectPskSaeType() {
+        verifyNonApplicableTransitionDisableIndicationForSecurityType(
+                WifiConfigurationTestUtil.createPskNetwork(),
+                WifiMonitor.TDI_USE_SAE_PK
+                        | WifiMonitor.TDI_USE_ENHANCED_OPEN
+                        | WifiMonitor.TDI_USE_WPA3_ENTERPRISE);
+    }
+
+    /**
+     * Verify Transition Disable Indication does not change anything if
+     * the type is not applicable.
+     */
+    @Test
+    public void testNonApplicableTransitionDisableIndicationNotAffectWpa2Wpa3EnterpriseType() {
+        verifyNonApplicableTransitionDisableIndicationForSecurityType(
+                WifiConfigurationTestUtil.createEapNetwork(),
+                WifiMonitor.TDI_USE_SAE_PK
+                        | WifiMonitor.TDI_USE_ENHANCED_OPEN
+                        | WifiMonitor.TDI_USE_WPA3_PERSONAL);
+    }
+
+    /**
+     * Verify Transition Disable Indication does not change anything if
+     * the type is not applicable.
+     */
+    @Test
+    public void testNonApplicableTransitionDisableIndicationNotAffectOpenOweType() {
+        verifyNonApplicableTransitionDisableIndicationForSecurityType(
+                WifiConfigurationTestUtil.createOpenNetwork(),
+                WifiMonitor.TDI_USE_SAE_PK
+                        | WifiMonitor.TDI_USE_WPA3_PERSONAL
+                        | WifiMonitor.TDI_USE_WPA3_ENTERPRISE);
+    }
+
+    /**
+     * Verify Transition Disable Indication does not change anything if
+     * the type is not applicable.
+     */
+    @Test
+    public void testNonApplicableTransitionDisableIndicationNotAffectApValidation() {
+        WifiConfiguration testNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(testNetwork);
+        int networkId = result.getNetworkId();
+
+        WifiConfiguration configBefore = mWifiConfigManager.getConfiguredNetwork(networkId);
+        assertFalse(configBefore.getSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE)
+                .isSaePkOnlyMode());
+
+        int indication = WifiMonitor.TDI_USE_ENHANCED_OPEN
+                | WifiMonitor.TDI_USE_WPA3_PERSONAL
+                | WifiMonitor.TDI_USE_WPA3_ENTERPRISE;
+        mWifiConfigManager.updateNetworkTransitionDisable(result.getNetworkId(), indication);
+
+        WifiConfiguration configAfter = mWifiConfigManager.getConfiguredNetwork(networkId);
+        assertFalse(configAfter.getSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE)
+                .isSaePkOnlyMode());
+    }
+
+    @Test
+    public void testLoadFromSharedAndUserStoreHandleFailureOnUserBuilds() throws Exception {
+        when(mBuildProperties.isUserBuild()).thenReturn(true);
+
+        List<WifiConfiguration> sharedConfigs =
+                Arrays.asList(WifiConfigurationTestUtil.createOpenNetwork());
+        // Setup xml storage
+        setupStoreDataForRead(sharedConfigs, Arrays.asList());
+
+        // Throw an exception when reading.
+        doThrow(new XmlPullParserException("")).when(mWifiConfigStore).read();
+        assertTrue(mWifiConfigManager.loadFromStore());
+        verify(mWifiConfigStore).read();
+
+        // Should have no existing saved networks.
+        assertTrue(mWifiConfigManager.getConfiguredNetworks().isEmpty());
+
+        // Verify that we can add a new network.
+        verifyAddNetworkToWifiConfigManager(WifiConfigurationTestUtil.createOpenNetwork());
+        assertFalse(mWifiConfigManager.getConfiguredNetworks().isEmpty());
+    }
+
+    @Test
+    public void testLoadFromUserStoreHandleFailureOnUserBuilds() throws Exception {
+        when(mBuildProperties.isUserBuild()).thenReturn(true);
+        int user2 = TEST_DEFAULT_USER + 1;
+        setupUserProfiles(user2);
+
+        List<WifiConfiguration> sharedConfigs =
+                Arrays.asList(WifiConfigurationTestUtil.createOpenNetwork());
+        // Setup xml storage
+        setupStoreDataForRead(sharedConfigs, Arrays.asList());
+
+        // Throw an exception when reading.
+        assertTrue(mWifiConfigManager.loadFromStore());
+        verify(mWifiConfigStore).read();
+
+        // Should have 1 shared saved networks.
+        assertEquals(1, mWifiConfigManager.getConfiguredNetworks().size());
+
+        // Now trigger a user switch and throw an excepton when reading user store file.
+        doThrow(new XmlPullParserException("")).when(mWifiConfigStore).switchUserStoresAndRead(
+                any());
+        when(mUserManager.isUserUnlockingOrUnlocked(UserHandle.of(user2))).thenReturn(true);
+        mWifiConfigManager.handleUserSwitch(user2);
+        verify(mWifiConfigStore).switchUserStoresAndRead(any(List.class));
+
+        // Verify that we can add a new network.
+        verifyAddNetworkToWifiConfigManager(WifiConfigurationTestUtil.createOpenNetwork());
+        assertEquals(2, mWifiConfigManager.getConfiguredNetworks().size());
+    }
+
+    /**
+     * Verify that updating an existing config to a incompatible type works well.
+     */
+    @Test
+    public void testUpdateExistingConfigWithIncompatibleSecurityType() {
+        WifiConfiguration testNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        int networkIdBefore = verifyAddNetworkToWifiConfigManager(testNetwork).getNetworkId();
+        WifiConfiguration configBefore = mWifiConfigManager.getConfiguredNetwork(networkIdBefore);
+        assertFalse(configBefore.isSecurityType(WifiConfiguration.SECURITY_TYPE_OPEN));
+        assertTrue(configBefore.isSecurityType(WifiConfiguration.SECURITY_TYPE_PSK));
+
+        // Change the type from PSK to Open.
+        testNetwork.networkId = networkIdBefore;
+        testNetwork.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN);
+        testNetwork.preSharedKey = null;
+        int networkIdAfter = addNetworkToWifiConfigManager(testNetwork).getNetworkId();
+        assertEquals(networkIdBefore, networkIdAfter);
+
+        WifiConfiguration configAfter = mWifiConfigManager.getConfiguredNetwork(networkIdAfter);
+        assertFalse(configAfter.isSecurityType(WifiConfiguration.SECURITY_TYPE_PSK));
+        assertTrue(configAfter.isSecurityType(WifiConfiguration.SECURITY_TYPE_OPEN));
+    }
+
+    @Test
+    public void testRemoveNonCallerConfiguredNetworks() {
+        final int callerUid = TEST_CREATOR_UID;
+        WifiConfiguration callerNetwork0 = WifiConfigurationTestUtil.createPskNetwork();
+        addNetworkToWifiConfigManager(callerNetwork0, callerUid, null);
+        WifiConfiguration callerNetwork1 = WifiConfigurationTestUtil.createPskNetwork();
+        addNetworkToWifiConfigManager(callerNetwork1, callerUid, null);
+        WifiConfiguration nonCallerNetwork0 = WifiConfigurationTestUtil.createPskNetwork();
+        addNetworkToWifiConfigManager(nonCallerNetwork0, TEST_OTHER_USER_UID, null);
+        WifiConfiguration nonCallerNetwork1 = WifiConfigurationTestUtil.createPskNetwork();
+        addNetworkToWifiConfigManager(nonCallerNetwork1, TEST_OTHER_USER_UID, null);
+
+        assertTrue(mWifiConfigManager.removeNonCallerConfiguredNetwork(callerUid));
+        WifiConfigurationTestUtil.assertConfigurationsEqualForBackup(
+                Arrays.asList(callerNetwork0, callerNetwork1),
+                mWifiConfigManager.getConfiguredNetworksWithPasswords());
+    }
+
+    /**
+     * Verify getMostRecentScanResultsForConfiguredNetworks returns most recent scan results.
+     */
+    @Test
+    public void testGetMostRecentScanResultsForConfiguredNetworks() {
+        int testMaxAgeMillis = 2000;
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(openNetwork);
+        when(mClock.getWallClockMillis()).thenReturn(TEST_WALLCLOCK_CREATION_TIME_MILLIS);
+        ScanDetail scanDetail = createScanDetailForNetwork(openNetwork, TEST_BSSID,
+                TEST_RSSI, TEST_FREQUENCY_1);
+        ScanResult scanResult = scanDetail.getScanResult();
+        mWifiConfigManager.updateScanDetailCacheFromScanDetailForSavedNetwork(scanDetail);
+
+        // verify the ScanResult is returned before the testMaxAgeMillis
+        when(mClock.getWallClockMillis()).thenReturn(TEST_WALLCLOCK_CREATION_TIME_MILLIS
+                + testMaxAgeMillis - 1);
+        List<ScanResult> scanResults = mWifiConfigManager
+                .getMostRecentScanResultsForConfiguredNetworks(testMaxAgeMillis);
+        assertEquals(1, scanResults.size());
+        assertEquals(TEST_BSSID, scanResults.get(0).BSSID);
+
+        // verify no ScanResult is returned after the timeout.
+        when(mClock.getWallClockMillis()).thenReturn(TEST_WALLCLOCK_CREATION_TIME_MILLIS
+                + testMaxAgeMillis);
+        assertEquals(0, mWifiConfigManager
+                .getMostRecentScanResultsForConfiguredNetworks(testMaxAgeMillis).size());
+    }
+
+    private void verifyAddUpgradableTypeNetwork(
+            WifiConfiguration baseConfig, WifiConfiguration newConfig,
+            boolean isNewConfigUpgradableType) {
+        NetworkUpdateResult result = addNetworkToWifiConfigManager(baseConfig);
+        int baseConfigId = result.getNetworkId();
+        assertTrue(result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID);
+        assertTrue(result.isNewNetwork());
+
+        result = addNetworkToWifiConfigManager(newConfig);
+        assertEquals(baseConfigId, result.getNetworkId());
+        assertFalse(result.isNewNetwork());
+
+        List<WifiConfiguration> retrievedSavedNetworks = mWifiConfigManager.getSavedNetworks(
+                Process.WIFI_UID);
+        assertEquals(1, retrievedSavedNetworks.size());
+        if (isNewConfigUpgradableType) {
+            assertEquals(baseConfig.getProfileKey(),
+                    retrievedSavedNetworks.get(0).getProfileKey());
+        } else {
+            assertEquals(newConfig.getProfileKey(),
+                    retrievedSavedNetworks.get(0).getProfileKey());
+        }
+    }
+
+    /**
+     * Verifies the new SAE network is merged to existing PSK network correctly.
+     */
+    @Test
+    public void testMergeSaeToPskNetwork() {
+        verifyAddUpgradableTypeNetwork(
+                WifiConfigurationTestUtil.createPskNetwork(TEST_SSID),
+                WifiConfigurationTestUtil.createSaeNetwork(TEST_SSID),
+                true);
+    }
+
+    /**
+     * Verifies the new PSK network is merged to existing SAE network correctly.
+     */
+    @Test
+    public void testMergePskToSaeNetwork() {
+        verifyAddUpgradableTypeNetwork(
+                WifiConfigurationTestUtil.createSaeNetwork(TEST_SSID),
+                WifiConfigurationTestUtil.createPskNetwork(TEST_SSID),
+                false);
+    }
+
+    /**
+     * Verifies the new OWE network is merged to existing Open network correctly.
+     */
+    @Test
+    public void testMergeOweToOpenNetwork() {
+        verifyAddUpgradableTypeNetwork(
+                WifiConfigurationTestUtil.createOpenNetwork(TEST_SSID),
+                WifiConfigurationTestUtil.createOweNetwork(TEST_SSID),
+                true);
+    }
+
+    /**
+     * Verifies the new Open network is merged to existing OWE network correctly.
+     */
+    @Test
+    public void testMergeOpenToOweNetwork() {
+        verifyAddUpgradableTypeNetwork(
+                WifiConfigurationTestUtil.createOweNetwork(TEST_SSID),
+                WifiConfigurationTestUtil.createOpenNetwork(TEST_SSID),
+                false);
+    }
+
+    /**
+     * Verifies the new WPA3 Enterprise network is merged
+     * to existing WPA2 Enterprise network correctly.
+     */
+    @Test
+    public void testMergeWpa3EnterpriseToWpa2EnterprsieNetwork() {
+        verifyAddUpgradableTypeNetwork(
+                WifiConfigurationTestUtil.createEapNetwork(TEST_SSID),
+                WifiConfigurationTestUtil.createWpa3EnterpriseNetwork(TEST_SSID),
+                true);
+    }
+
+    /**
+     * Verifies the new WPA2 Enterprise network is merged
+     * to existing WPA3 Enterprise network correctly.
+     */
+    @Test
+    public void testMergeWpa2EnterpriseToWpa3EnterpriseNetwork() {
+        verifyAddUpgradableTypeNetwork(
+                WifiConfigurationTestUtil.createWpa3EnterpriseNetwork(TEST_SSID),
+                WifiConfigurationTestUtil.createEapNetwork(TEST_SSID),
+                false);
+    }
+
+    /**
+     * Verifies that excess networks are removed in the right order when more than the max amount of
+     * WifiConfigurations are added.
+     */
+    @Test
+    public void testRemoveExcessNetworksOnAdd() {
+        mResources.setInteger(R.integer.config_wifiMaxNumWifiConfigurations, 9);
+        List<WifiConfiguration> configsInDeletionOrder = new ArrayList<>();
+        WifiConfiguration currentConfig = WifiConfigurationTestUtil.createPskNetwork();
+        currentConfig.status = WifiConfiguration.Status.CURRENT;
+        WifiConfiguration lessDeletionPriorityConfig = WifiConfigurationTestUtil.createPskNetwork();
+        lessDeletionPriorityConfig.setDeletionPriority(1);
+        WifiConfiguration newlyAddedConfig = WifiConfigurationTestUtil.createPskNetwork();
+        newlyAddedConfig.lastUpdated = 1;
+        WifiConfiguration recentConfig = WifiConfigurationTestUtil.createPskNetwork();
+        recentConfig.lastConnected = 2;
+        WifiConfiguration notRecentConfig = WifiConfigurationTestUtil.createPskNetwork();
+        notRecentConfig.lastConnected = 1;
+        WifiConfiguration oneRebootSaeConfig = WifiConfigurationTestUtil.createSaeNetwork();
+        oneRebootSaeConfig.numRebootsSinceLastUse = 1;
+        WifiConfiguration oneRebootOpenConfig = WifiConfigurationTestUtil.createOpenNetwork();
+        oneRebootOpenConfig.numRebootsSinceLastUse = 1;
+        oneRebootOpenConfig.numAssociation = 1;
+        WifiConfiguration noAssociationConfig = WifiConfigurationTestUtil.createOpenNetwork();
+        noAssociationConfig.numRebootsSinceLastUse = 1;
+        noAssociationConfig.numAssociation = 0;
+        WifiConfiguration invalidKeyMgmtConfig = WifiConfigurationTestUtil.createEapNetwork();
+        invalidKeyMgmtConfig.allowedKeyManagement.clear(WifiConfiguration.KeyMgmt.IEEE8021X);
+        invalidKeyMgmtConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA2_PSK);
+        noAssociationConfig.numRebootsSinceLastUse = 1;
+        noAssociationConfig.numAssociation = 0;
+
+        configsInDeletionOrder.add(invalidKeyMgmtConfig);
+        configsInDeletionOrder.add(noAssociationConfig);
+        configsInDeletionOrder.add(oneRebootOpenConfig);
+        configsInDeletionOrder.add(oneRebootSaeConfig);
+        configsInDeletionOrder.add(notRecentConfig);
+        configsInDeletionOrder.add(recentConfig);
+        configsInDeletionOrder.add(newlyAddedConfig);
+        configsInDeletionOrder.add(lessDeletionPriorityConfig);
+        configsInDeletionOrder.add(currentConfig);
+
+        // Add carrier configs to flush out the other configs, since they are deleted last.
+        for (WifiConfiguration configToDelete : configsInDeletionOrder) {
+            WifiConfiguration carrierConfig = WifiConfigurationTestUtil.createPskNetwork();
+            carrierConfig.carrierId = 1;
+            verifyAddNetworkToWifiConfigManager(carrierConfig);
+
+            for (WifiConfiguration savedConfig : mWifiConfigManager.getConfiguredNetworks()) {
+                if (savedConfig.networkId == configToDelete.networkId) {
+                    fail("Config was not deleted: " + configToDelete);
+                }
+            }
+        }
+    }
+
+    /**
+     * Verifies that adding a network fails if the amount of saved networks is maxed out and the
+     * next network to be removed is the same as the added network.
+     */
+    @Test
+    public void testAddNetworkFailsIfNetworkMustBeRemoved() {
+        mResources.setInteger(R.integer.config_wifiMaxNumWifiConfigurations, 5);
+        // Max out number of configurations with SAE networks
+        verifyAddNetworkToWifiConfigManager(WifiConfigurationTestUtil.createSaeNetwork());
+        verifyAddNetworkToWifiConfigManager(WifiConfigurationTestUtil.createSaeNetwork());
+        verifyAddNetworkToWifiConfigManager(WifiConfigurationTestUtil.createSaeNetwork());
+        verifyAddNetworkToWifiConfigManager(WifiConfigurationTestUtil.createSaeNetwork());
+        verifyAddNetworkToWifiConfigManager(WifiConfigurationTestUtil.createSaeNetwork());
+
+        // Add an open network, which should be deleted before SAE networks
+        NetworkUpdateResult result = addNetworkToWifiConfigManager(
+                WifiConfigurationTestUtil.createOpenNetwork());
+
+        assertFalse(result.isSuccess());
+    }
+
+    /**
+     * Verifies that numRebootsSinceLastUse is reset after a connection.
+     */
+    @Test
+    public void testNumRebootsSinceLastUseUpdatedAfterConnect() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
+        verifyAddNetworkToWifiConfigManager(config);
+        mWifiConfigManager.incrementNumRebootsSinceLastUse();
+        assertEquals(1,
+                mWifiConfigManager.getConfiguredNetwork(config.networkId).numRebootsSinceLastUse);
+
+        mWifiConfigManager.updateNetworkAfterConnect(config.networkId, false, TEST_RSSI);
+
+        assertEquals(0,
+                mWifiConfigManager.getConfiguredNetwork(config.networkId).numRebootsSinceLastUse);
+    }
+
+    /**
+     * Verifies that numRebootsSinceLastUse is reset after a config update.
+     */
+    @Test
+    public void testNumRebootsSinceLastUseUpdatedAfterConfigUpdate() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
+        verifyAddNetworkToWifiConfigManager(config);
+        mWifiConfigManager.incrementNumRebootsSinceLastUse();
+        assertEquals(1,
+                mWifiConfigManager.getConfiguredNetwork(config.networkId).numRebootsSinceLastUse);
+
+        verifyUpdateNetworkToWifiConfigManager(config);
+
+        assertEquals(0,
+                mWifiConfigManager.getConfiguredNetwork(config.networkId).numRebootsSinceLastUse);
+    }
+
+    /**
+     * Verifies the addition of a network with incomplete security parameters using
+     * {@link WifiConfigManager#addOrUpdateNetwork(WifiConfiguration, int)} does not cause a crash.
+     */
+    @Test
+    public void testAddIncompleteEnterpriseConfig() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.SSID = "\"someNetwork\"";
+        config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
+        // EAP method is kept as Eap.NONE - should not crash, but return invalid ID
+        NetworkUpdateResult result = addNetworkToWifiConfigManager(config);
+        assertTrue(result.getNetworkId() == WifiConfiguration.INVALID_NETWORK_ID);
+
+        // Now add this network correctly
+        config.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.SIM);
+        result = addNetworkToWifiConfigManager(config);
+        assertTrue(result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID);
+
+        // Now try to update
+        config = new WifiConfiguration();
+        config.SSID = "\"someNetwork\"";
+        config.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.SIM);
+        config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
+        config.networkId = result.getNetworkId();
+        NetworkUpdateResult updateResult = addNetworkToWifiConfigManager(config);
+        assertTrue(result.getNetworkId() == updateResult.getNetworkId());
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java
index 671076b..c3d1481 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java
@@ -34,6 +34,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.util.FastPrintWriter;
 import com.android.server.wifi.WifiConfigStore.StoreData;
 import com.android.server.wifi.WifiConfigStore.StoreFile;
 import com.android.server.wifi.util.ArrayUtils;
@@ -53,11 +54,16 @@
 import org.xmlpull.v1.XmlSerializer;
 
 import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Random;
 
@@ -71,6 +77,7 @@
     private static final String TEST_CREATOR_NAME = "CreatorName";
     private static final MacAddress TEST_RANDOMIZED_MAC =
             MacAddress.fromString("da:a1:19:c4:26:fa");
+    private static final int TEST_SUB_ID = 2;
 
     private static final String TEST_DATA_XML_STRING_FORMAT =
             "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
@@ -87,7 +94,7 @@
                     + "<boolean name=\"HiddenSSID\" value=\"false\" />\n"
                     + "<boolean name=\"RequirePMF\" value=\"false\" />\n"
                     + "<byte-array name=\"AllowedKeyMgmt\" num=\"1\">01</byte-array>\n"
-                    + "<byte-array name=\"AllowedProtocols\" num=\"0\"></byte-array>\n"
+                    + "<byte-array name=\"AllowedProtocols\" num=\"1\">03</byte-array>\n"
                     + "<byte-array name=\"AllowedAuthAlgos\" num=\"0\"></byte-array>\n"
                     + "<byte-array name=\"AllowedGroupCiphers\" num=\"0\"></byte-array>\n"
                     + "<byte-array name=\"AllowedPairwiseCiphers\" num=\"0\"></byte-array>\n"
@@ -95,7 +102,26 @@
                     + "<byte-array name=\"AllowedSuiteBCiphers\" num=\"0\"></byte-array>\n"
                     + "<boolean name=\"Shared\" value=\"%s\" />\n"
                     + "<boolean name=\"AutoJoinEnabled\" value=\"true\" />\n"
+                    + "<int name=\"DeletionPriority\" value=\"0\" />\n"
+                    + "<int name=\"NumRebootsSinceLastUse\" value=\"0\" />\n"
+                    + "<SecurityParamsList>\n"
+                    + "<SecurityParams>\n"
+                    + "<int name=\"SecurityType\" value=\"0\" />\n"
+                    + "<boolean name=\"SaeIsH2eOnlyMode\" value=\"false\" />\n"
+                    + "<boolean name=\"SaeIsPkOnlyMode\" value=\"false\" />\n"
+                    + "<boolean name=\"IsAddedByAutoUpgrade\" value=\"false\" />\n"
+                    + "</SecurityParams>\n"
+                    + "<SecurityParams>\n"
+                    + "<int name=\"SecurityType\" value=\"6\" />\n"
+                    + "<boolean name=\"SaeIsH2eOnlyMode\" value=\"false\" />\n"
+                    + "<boolean name=\"SaeIsPkOnlyMode\" value=\"false\" />\n"
+                    + "<boolean name=\"IsAddedByAutoUpgrade\" value=\"true\" />\n"
+                    + "</SecurityParams>\n"
+                    + "</SecurityParamsList>\n"
                     + "<boolean name=\"Trusted\" value=\"true\" />\n"
+                    + "<boolean name=\"OemPaid\" value=\"false\" />\n"
+                    + "<boolean name=\"OemPrivate\" value=\"false\" />\n"
+                    + "<boolean name=\"CarrierMerged\" value=\"false\" />\n"
                     + "<null name=\"BSSID\" />\n"
                     + "<int name=\"Status\" value=\"2\" />\n"
                     + "<null name=\"FQDN\" />\n"
@@ -115,15 +141,18 @@
                     + "<boolean name=\"IsLegacyPasspointConfig\" value=\"false\" />\n"
                     + "<long-array name=\"RoamingConsortiumOIs\" num=\"0\" />\n"
                     + "<string name=\"RandomizedMacAddress\">%s</string>\n"
-                    + "<int name=\"MacRandomizationSetting\" value=\"1\" />\n"
+                    + "<int name=\"MacRandomizationSetting\" value=\"3\" />\n"
                     + "<int name=\"CarrierId\" value=\"-1\" />\n"
                     + "<boolean name=\"IsMostRecentlyConnected\" value=\"false\" />\n"
+                    + "<int name=\"SubscriptionId\" value=\"%d\" />\n"
                     + "</WifiConfiguration>\n"
                     + "<NetworkStatus>\n"
                     + "<string name=\"SelectionStatus\">NETWORK_SELECTION_ENABLED</string>\n"
                     + "<string name=\"DisableReason\">NETWORK_SELECTION_ENABLE</string>\n"
                     + "<null name=\"ConnectChoice\" />\n"
+                    + "<int name=\"ConnectChoiceRssi\" value=\"0\" />\n"
                     + "<boolean name=\"HasEverConnected\" value=\"false\" />\n"
+                    + "<boolean name=\"CaptivePortalNeverDetected\" value=\"true\" />\n"
                     + "</NetworkStatus>\n"
                     + "<IpConfiguration>\n"
                     + "<string name=\"IpAssignment\">DHCP</string>\n"
@@ -178,6 +207,7 @@
     private MockStoreData mSharedStoreData;
     private MockStoreData mUserStoreData;
     private MockitoSession mSession;
+    private @Mock WifiCarrierInfoStoreManagerData.DataSource mDataSource;
 
     /**
      * Test instance of WifiConfigStore.
@@ -537,6 +567,7 @@
                 WifiConfigurationTestUtil.createDHCPIpConfigurationWithNoProxy());
         openNetwork.setRandomizedMacAddress(TEST_RANDOMIZED_MAC);
         List<WifiConfiguration> userConfigs = new ArrayList<>();
+        openNetwork.subscriptionId = TEST_SUB_ID;
         userConfigs.add(openNetwork);
 
         // Setup user store XML bytes.
@@ -544,7 +575,7 @@
                 openNetwork.getKey().replaceAll("\"", "&quot;"),
                 openNetwork.SSID.replaceAll("\"", "&quot;"),
                 openNetwork.shared, openNetwork.creatorUid, openNetwork.creatorName,
-                openNetwork.getRandomizedMacAddress());
+                openNetwork.getRandomizedMacAddress(), openNetwork.subscriptionId);
         byte[] xmlBytes = xmlString.getBytes(StandardCharsets.UTF_8);
         mUserStore.storeRawDataToWrite(xmlBytes);
 
@@ -567,11 +598,12 @@
         // Setup network list store data.
         NetworkListStoreData networkList = new NetworkListUserStoreData(mContext);
         mWifiConfigStore.registerStoreData(networkList);
-        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenOweNetwork();
         openNetwork.creatorName = TEST_CREATOR_NAME;
         openNetwork.setIpConfiguration(
                 WifiConfigurationTestUtil.createDHCPIpConfigurationWithNoProxy());
         openNetwork.setRandomizedMacAddress(TEST_RANDOMIZED_MAC);
+        openNetwork.subscriptionId = TEST_SUB_ID;
         List<WifiConfiguration> userConfigs = new ArrayList<>();
         userConfigs.add(openNetwork);
         networkList.setConfigurations(userConfigs);
@@ -581,7 +613,7 @@
                 openNetwork.getKey().replaceAll("\"", "&quot;"),
                 openNetwork.SSID.replaceAll("\"", "&quot;"),
                 openNetwork.shared, openNetwork.creatorUid, openNetwork.creatorName,
-                openNetwork.getRandomizedMacAddress());
+                openNetwork.getRandomizedMacAddress(), openNetwork.subscriptionId);
 
         mWifiConfigStore.write(true);
         // Verify the user store content.
@@ -620,6 +652,10 @@
         // Scenario 1: StoreData1 in shared store file.
         when(storeData1.getName()).thenReturn(storeData1Name);
         when(storeData2.getName()).thenReturn(storeData2Name);
+        when(storeData1.getSectionsToParse())
+                .thenReturn(new HashSet<>(Collections.singleton(storeData1Name)));
+        when(storeData2.getSectionsToParse())
+                .thenReturn(new HashSet<>(Collections.singleton(storeData2Name)));
         when(storeData1.getStoreFileId())
                 .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL);
         when(storeData2.getStoreFileId())
@@ -628,7 +664,8 @@
         mUserStore.storeRawDataToWrite(null);
 
         mWifiConfigStore.read();
-        verify(storeData1).deserializeData(notNull(), anyInt(), anyInt(), any());
+        verify(storeData1)
+                .deserializeDataForSection(notNull(), anyInt(), anyInt(), any(), anyString());
         verify(storeData1, never()).deserializeData(eq(null), anyInt(), anyInt(), any());
         verify(storeData2).deserializeData(eq(null), anyInt(), anyInt(), any());
         reset(storeData1, storeData2);
@@ -636,6 +673,10 @@
         // Scenario 2: StoreData2 in user store file.
         when(storeData1.getName()).thenReturn(storeData1Name);
         when(storeData2.getName()).thenReturn(storeData2Name);
+        when(storeData1.getSectionsToParse())
+                .thenReturn(new HashSet<>(Collections.singleton(storeData1Name)));
+        when(storeData2.getSectionsToParse())
+                .thenReturn(new HashSet<>(Collections.singleton(storeData2Name)));
         when(storeData1.getStoreFileId())
                 .thenReturn(WifiConfigStore.STORE_FILE_USER_GENERAL);
         when(storeData2.getStoreFileId())
@@ -645,13 +686,18 @@
 
         mWifiConfigStore.read();
         verify(storeData1).deserializeData(eq(null), anyInt(), anyInt(), any());
-        verify(storeData2).deserializeData(notNull(), anyInt(), anyInt(), any());
+        verify(storeData2)
+                .deserializeDataForSection(notNull(), anyInt(), anyInt(), any(), anyString());
         verify(storeData2, never()).deserializeData(eq(null), anyInt(), anyInt(), any());
         reset(storeData1, storeData2);
 
         // Scenario 3: StoreData1 in shared store file & StoreData2 in user store file.
         when(storeData1.getName()).thenReturn(storeData1Name);
         when(storeData2.getName()).thenReturn(storeData2Name);
+        when(storeData1.getSectionsToParse())
+                .thenReturn(new HashSet<>(Collections.singleton(storeData1Name)));
+        when(storeData2.getSectionsToParse())
+                .thenReturn(new HashSet<>(Collections.singleton(storeData2Name)));
         when(storeData1.getStoreFileId())
                 .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL);
         when(storeData2.getStoreFileId())
@@ -660,15 +706,21 @@
         mUserStore.storeRawDataToWrite(fileContentsXmlStringWithOnlyStoreData2.getBytes());
 
         mWifiConfigStore.read();
-        verify(storeData1).deserializeData(notNull(), anyInt(), anyInt(), any());
+        verify(storeData1)
+                .deserializeDataForSection(notNull(), anyInt(), anyInt(), any(), anyString());
         verify(storeData1, never()).deserializeData(eq(null), anyInt(), anyInt(), any());
-        verify(storeData2).deserializeData(notNull(), anyInt(), anyInt(), any());
+        verify(storeData2)
+                .deserializeDataForSection(notNull(), anyInt(), anyInt(), any(), anyString());
         verify(storeData2, never()).deserializeData(eq(null), anyInt(), anyInt(), any());
         reset(storeData1, storeData2);
 
         // Scenario 4: StoreData1 & StoreData2 in shared store file.
         when(storeData1.getName()).thenReturn(storeData1Name);
         when(storeData2.getName()).thenReturn(storeData2Name);
+        when(storeData1.getSectionsToParse())
+                .thenReturn(new HashSet<>(Collections.singleton(storeData1Name)));
+        when(storeData2.getSectionsToParse())
+                .thenReturn(new HashSet<>(Collections.singleton(storeData2Name)));
         when(storeData1.getStoreFileId())
                 .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL);
         when(storeData2.getStoreFileId())
@@ -678,9 +730,11 @@
         mUserStore.storeRawDataToWrite(null);
 
         mWifiConfigStore.read();
-        verify(storeData1).deserializeData(notNull(), anyInt(), anyInt(), any());
+        verify(storeData1)
+                .deserializeDataForSection(notNull(), anyInt(), anyInt(), any(), anyString());
         verify(storeData1, never()).deserializeData(eq(null), anyInt(), anyInt(), any());
-        verify(storeData2).deserializeData(notNull(), anyInt(), anyInt(), any());
+        verify(storeData2)
+                .deserializeDataForSection(notNull(), anyInt(), anyInt(), any(), anyString());
         verify(storeData2, never()).deserializeData(eq(null), anyInt(), anyInt(), any());
         reset(storeData1, storeData2);
     }
@@ -777,10 +831,14 @@
         when(sharedStoreData.getStoreFileId())
                 .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL);
         when(sharedStoreData.getName()).thenReturn(TEST_SHARE_DATA);
+        when(sharedStoreData.getSectionsToParse())
+                .thenReturn(new HashSet<>(Collections.singleton(TEST_SHARE_DATA)));
         StoreData userStoreData = mock(StoreData.class);
         when(userStoreData.getStoreFileId())
                 .thenReturn(WifiConfigStore.STORE_FILE_USER_GENERAL);
         when(userStoreData.getName()).thenReturn(TEST_USER_DATA);
+        when(userStoreData.getSectionsToParse())
+                .thenReturn(new HashSet<>(Collections.singleton(TEST_USER_DATA)));
         mWifiConfigStore.registerStoreData(sharedStoreData);
         mWifiConfigStore.registerStoreData(userStoreData);
 
@@ -798,12 +856,12 @@
         // Read and verify the data content in the store file (metadata stripped out) has been sent
         // to the corresponding store data when integrity check passes.
         mWifiConfigStore.read();
-        verify(sharedStoreData, times(1)).deserializeData(
+        verify(sharedStoreData, times(1)).deserializeDataForSection(
                 any(XmlPullParser.class), anyInt(),
-                eq(WifiConfigStore.INITIAL_CONFIG_STORE_DATA_VERSION), any());
-        verify(userStoreData, times(1)).deserializeData(
+                eq(WifiConfigStore.INITIAL_CONFIG_STORE_DATA_VERSION), any(), eq(TEST_SHARE_DATA));
+        verify(userStoreData, times(1)).deserializeDataForSection(
                 any(XmlPullParser.class), anyInt(),
-                eq(WifiConfigStore.INITIAL_CONFIG_STORE_DATA_VERSION), any());
+                eq(WifiConfigStore.INITIAL_CONFIG_STORE_DATA_VERSION), any(), eq(TEST_USER_DATA));
     }
 
     /**
@@ -822,11 +880,15 @@
         when(sharedStoreData.getStoreFileId())
                 .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL);
         when(sharedStoreData.getName()).thenReturn(TEST_SHARE_DATA);
+        when(sharedStoreData.getSectionsToParse())
+                .thenReturn(new HashSet<>(Collections.singleton(TEST_SHARE_DATA)));
         when(sharedStoreData.hasNewDataToSerialize()).thenReturn(true);
         StoreData userStoreData = mock(StoreData.class);
         when(userStoreData.getStoreFileId())
                 .thenReturn(WifiConfigStore.STORE_FILE_USER_GENERAL);
         when(userStoreData.getName()).thenReturn(TEST_USER_DATA);
+        when(userStoreData.getSectionsToParse())
+                .thenReturn(new HashSet<>(Collections.singleton(TEST_USER_DATA)));
         when(userStoreData.hasNewDataToSerialize()).thenReturn(true);
         mWifiConfigStore.registerStoreData(sharedStoreData);
         mWifiConfigStore.registerStoreData(userStoreData);
@@ -850,11 +912,13 @@
         // to the corresponding store data.
         mWifiConfigStore.read();
         verify(sharedStoreData, times(1))
-                .deserializeData(any(XmlPullParser.class), anyInt(),
-                        eq(WifiConfigStore.INTEGRITY_CONFIG_STORE_DATA_VERSION), any());
+                .deserializeDataForSection(any(XmlPullParser.class), anyInt(),
+                        eq(WifiConfigStore.INTEGRITY_CONFIG_STORE_DATA_VERSION), any(),
+                        eq(TEST_SHARE_DATA));
         verify(userStoreData, times(1))
-                .deserializeData(any(XmlPullParser.class), anyInt(),
-                        eq(WifiConfigStore.INTEGRITY_CONFIG_STORE_DATA_VERSION), any());
+                .deserializeDataForSection(any(XmlPullParser.class), anyInt(),
+                        eq(WifiConfigStore.INTEGRITY_CONFIG_STORE_DATA_VERSION), any(),
+                        eq(TEST_USER_DATA));
     }
 
     /**
@@ -885,10 +949,14 @@
         when(sharedStoreData.getStoreFileId())
                 .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL);
         when(sharedStoreData.getName()).thenReturn(TEST_SHARE_DATA);
+        when(sharedStoreData.getSectionsToParse())
+                .thenReturn(new HashSet<>(Collections.singleton(TEST_SHARE_DATA)));
         StoreData userStoreData = mock(StoreData.class);
         when(userStoreData.getStoreFileId())
                 .thenReturn(WifiConfigStore.STORE_FILE_USER_GENERAL);
         when(userStoreData.getName()).thenReturn(TEST_USER_DATA);
+        when(userStoreData.getSectionsToParse())
+                .thenReturn(new HashSet<>(Collections.singleton(TEST_USER_DATA)));
         mWifiConfigStore.registerStoreData(sharedStoreData);
         mWifiConfigStore.registerStoreData(userStoreData);
 
@@ -969,11 +1037,13 @@
 
         // Verify that we correctly deserialized the data and sent it to the corresponding sources.
         verify(sharedStoreData, times(1))
-                .deserializeData(any(XmlPullParser.class), anyInt(),
-                        eq(WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION), any());
+                .deserializeDataForSection(any(XmlPullParser.class), anyInt(),
+                        eq(WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION), any(),
+                        eq(TEST_SHARE_DATA));
         verify(userStoreData, times(1))
-                .deserializeData(any(XmlPullParser.class), anyInt(),
-                        eq(WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION), any());
+                .deserializeDataForSection(any(XmlPullParser.class), anyInt(),
+                        eq(WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION), any(),
+                        eq(TEST_USER_DATA));
 
         // Verify we did not read from the real store files.
         verify(sharedStoreFile1, never()).readRawData();
@@ -1084,4 +1154,18 @@
             mHasAnyNewData = hasAnyNewData;
         }
     }
+
+    /**
+     * Verify dump will not crash when no UserStores set.
+     */
+    @Test
+    public void testDump() {
+        final FileDescriptor fd = new FileDescriptor();
+        final FileOutputStream fout = new FileOutputStream(fd);
+        final PrintWriter pw = new FastPrintWriter(fout);
+        mWifiConfigStore.dump(fd, pw, new String[0]);
+
+        mWifiConfigStore.setUserStores(mUserStores);
+        mWifiConfigStore.dump(fd, pw, new String[0]);
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiConfigurationTestUtil.java b/service/tests/wifitests/src/com/android/server/wifi/WifiConfigurationTestUtil.java
index 866586b..67bf1e8 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiConfigurationTestUtil.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiConfigurationTestUtil.java
@@ -24,12 +24,15 @@
 import android.net.ProxyInfo;
 import android.net.StaticIpConfiguration;
 import android.net.Uri;
+import android.net.wifi.SecurityParams;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
 import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiSsid;
 import android.text.TextUtils;
 
+import com.android.modules.utils.build.SdkLevel;
+
 import java.net.InetAddress;
 import java.security.cert.X509Certificate;
 import java.util.Arrays;
@@ -43,15 +46,16 @@
      * These values are used to describe AP's security setting. One AP can support multiple of them,
      * only if there is no conflict.
      */
-    public static final int SECURITY_NONE = 0;
-    public static final int SECURITY_WEP =  1 << 0;
-    public static final int SECURITY_PSK =  1 << 1;
-    public static final int SECURITY_EAP =  1 << 2;
-    public static final int SECURITY_SAE =  1 << 3;
-    public static final int SECURITY_OWE =  1 << 4;
-    public static final int SECURITY_EAP_SUITE_B =  1 << 5;
-    public static final int SECURITY_WAPI_PSK =     1 << 6;
-    public static final int SECURITY_WAPI_CERT =    1 << 7;
+    public static final int SECURITY_NONE = 1 << 0;
+    public static final int SECURITY_WEP =  1 << 1;
+    public static final int SECURITY_PSK =  1 << 2;
+    public static final int SECURITY_EAP =  1 << 3;
+    public static final int SECURITY_SAE =  1 << 4;
+    public static final int SECURITY_OWE =  1 << 5;
+    public static final int SECURITY_EAP_SUITE_B =  1 << 6;
+    public static final int SECURITY_WAPI_PSK =     1 << 7;
+    public static final int SECURITY_WAPI_CERT =    1 << 8;
+    public static final int SECURITY_WPA3_ENTERPRISE = 1 << 9;
 
     /**
      * These values are used to describe ip configuration parameters for a network.
@@ -70,7 +74,7 @@
     public static final String TEST_SSID = "WifiConfigurationTestSSID";
     public static final String TEST_PSK = "\"WifiConfigurationTestUtilPsk\"";
     public static final String[] TEST_WEP_KEYS =
-            {"\"WifiConfigurationTestUtilWep1\"", "\"WifiConfigurationTestUtilWep2\"",
+            {"\"WifiTestWep12\"", "\"WifiTestWep34\"",
                     "45342312ab", "45342312ab45342312ab34ac12"};
     public static final String TEST_EAP_PASSWORD = "WifiConfigurationTestUtilEapPassword";
     public static final int TEST_WEP_TX_KEY_INDEX = 1;
@@ -90,6 +94,7 @@
     public static final String TEST_CA_CERT_SUITE_B_ALIAS = "SuiteBCaCertAlias";
     public static final String TEST_CA_CERT_PATH = "caPath";
     public static final String TEST_DOM_SUBJECT_MATCH = "domSubjectMatch";
+    public static final String TEST_IDENTITY = "user@example.com";
 
     private static final int MAX_SSID_LENGTH = 32;
     /**
@@ -108,7 +113,7 @@
      * @param providerFriendlyName the configuration's provider's friendly name (Hotspot 2.0 only)
      * @return the constructed {@link android.net.wifi.WifiConfiguration}
      */
-    public static WifiConfiguration generateWifiConfig(int networkId, int uid, String ssid,
+    private static WifiConfiguration generateWifiConfig(int networkId, int uid, String ssid,
             boolean shared, boolean enabled, String fqdn, String providerFriendlyName) {
         final WifiConfiguration config = new WifiConfiguration();
         config.SSID = ssid;
@@ -145,46 +150,56 @@
         WifiConfiguration config = generateWifiConfig(networkId, uid, ssid, shared, enabled, fqdn,
                 providerFriendlyName);
 
-        if ((security == SECURITY_NONE) || ((security & SECURITY_WEP) != 0)) {
-            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
-        } else {
-            if ((security & SECURITY_PSK) != 0) {
-                config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
-            }
+        if ((security & SECURITY_NONE) != 0) {
+            config.addSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN);
+        }
+        if ((security & SECURITY_WEP) != 0) {
+            config.addSecurityParams(WifiConfiguration.SECURITY_TYPE_WEP);
+        }
+        if ((security & SECURITY_PSK) != 0) {
+            config.addSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
+        }
 
-            if ((security & SECURITY_SAE) != 0) {
-                config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.SAE);
-                config.requirePmf = true;
-            }
+        if ((security & SECURITY_SAE) != 0) {
+            config.addSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
+        }
 
-            if ((security & SECURITY_OWE) != 0) {
-                config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.OWE);
-                config.requirePmf = true;
-            }
+        if ((security & SECURITY_OWE) != 0) {
+            config.addSecurityParams(WifiConfiguration.SECURITY_TYPE_OWE);
+        }
 
-            if ((security & SECURITY_EAP) != 0) {
-                config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
-                config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
-                config.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TTLS);
-                config.enterpriseConfig.setCaPath(TEST_CA_CERT_PATH);
-                config.enterpriseConfig.setDomainSuffixMatch(TEST_DOM_SUBJECT_MATCH);
-            }
+        if ((security & SECURITY_EAP) != 0) {
+            config.addSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
+            config.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TTLS);
+            config.enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.MSCHAPV2);
+            config.enterpriseConfig.setIdentity(TEST_IDENTITY);
+            config.enterpriseConfig.setPassword(TEST_EAP_PASSWORD);
+            config.enterpriseConfig.setCaPath(TEST_CA_CERT_PATH);
+            config.enterpriseConfig.setDomainSuffixMatch(TEST_DOM_SUBJECT_MATCH);
+        }
 
-            if ((security & SECURITY_EAP_SUITE_B) != 0) {
-                config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
-                config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
-                config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.SUITE_B_192);
-                config.requirePmf = true;
-            }
+        if ((security & SECURITY_WPA3_ENTERPRISE) != 0) {
+            config.addSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
+            config.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.PEAP);
+            config.enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.MSCHAPV2);
+            config.enterpriseConfig.setIdentity(TEST_IDENTITY);
+            config.enterpriseConfig.setPassword(TEST_EAP_PASSWORD);
+            config.enterpriseConfig.setCaPath(TEST_CA_CERT_PATH);
+            config.enterpriseConfig.setDomainSuffixMatch(TEST_DOM_SUBJECT_MATCH);
+        }
 
-            if ((security & SECURITY_WAPI_PSK) != 0) {
-                config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WAPI_PSK);
-            }
+        if ((security & SECURITY_EAP_SUITE_B) != 0) {
+            config.addSecurityParams(
+                    WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT);
+        }
 
-            if ((security & SECURITY_WAPI_CERT) != 0) {
-                config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WAPI_CERT);
-            }
+        if ((security & SECURITY_WAPI_PSK) != 0) {
+            config.addSecurityParams(WifiConfiguration.SECURITY_TYPE_WAPI_PSK);
+        }
 
+        if ((security & SECURITY_WAPI_CERT) != 0) {
+            config.addSecurityParams(WifiConfiguration.SECURITY_TYPE_WAPI_CERT);
+            config.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.WAPI_CERT);
         }
         return config;
     }
@@ -280,8 +295,26 @@
     }
 
     public static WifiConfiguration createOpenNetwork(String ssid) {
-        return generateWifiConfig(TEST_NETWORK_ID, TEST_UID, ssid, true, true, null,
+        WifiConfiguration config = generateWifiConfig(
+                TEST_NETWORK_ID, TEST_UID, ssid, true, true, null,
                 null, SECURITY_NONE);
+        return config;
+    }
+
+    public static WifiConfiguration createOpenOweNetwork() {
+        return createOpenOweNetwork(createNewSSID());
+    }
+
+    public static WifiConfiguration createOpenOweNetwork(String ssid) {
+        WifiConfiguration config = generateWifiConfig(
+                TEST_NETWORK_ID, TEST_UID, ssid, true, true, null,
+                null, SECURITY_NONE);
+        // the upgradable type is always added.
+        SecurityParams params = SecurityParams
+                .createSecurityParamsBySecurityType(WifiConfiguration.SECURITY_TYPE_OWE);
+        params.setIsAddedByAutoUpgrade(true);
+        config.addSecurityParams(params);
+        return config;
     }
 
     public static WifiConfiguration createEphemeralNetwork() {
@@ -312,6 +345,23 @@
         return configuration;
     }
 
+    public static WifiConfiguration createPskSaeNetwork() {
+        return createPskSaeNetwork(createNewSSID());
+    }
+
+    public static WifiConfiguration createPskSaeNetwork(String ssid) {
+        WifiConfiguration configuration =
+                generateWifiConfig(TEST_NETWORK_ID, TEST_UID, ssid, true, true, null,
+                        null, SECURITY_PSK);
+        configuration.preSharedKey = TEST_PSK;
+        // the upgradable type is always added.
+        SecurityParams params = SecurityParams
+                .createSecurityParamsBySecurityType(WifiConfiguration.SECURITY_TYPE_SAE);
+        params.setIsAddedByAutoUpgrade(true);
+        configuration.addSecurityParams(params);
+        return configuration;
+    }
+
     public static WifiConfiguration createSaeNetwork(String ssid) {
         WifiConfiguration configuration =
                 generateWifiConfig(TEST_NETWORK_ID, TEST_UID, ssid, true, true, null,
@@ -355,26 +405,69 @@
 
 
     public static WifiConfiguration createEapNetwork() {
-        WifiConfiguration configuration =
-                generateWifiConfig(TEST_NETWORK_ID, TEST_UID, createNewSSID(), true, true,
-                        null, null, SECURITY_EAP);
-        return configuration;
+        return createEapNetwork(createNewSSID());
     }
 
     public static WifiConfiguration createEapNetwork(String ssid) {
-        WifiConfiguration configuration =
-                generateWifiConfig(TEST_NETWORK_ID, TEST_UID, ssid, true, true,
-                        null, null, SECURITY_EAP);
-        return configuration;
+        return createEapNetwork(ssid,
+                WifiEnterpriseConfig.Eap.NONE,
+                WifiEnterpriseConfig.Phase2.NONE);
     }
 
 
     public static WifiConfiguration createEapNetwork(int eapMethod, int phase2Method) {
+        return createEapNetwork(createNewSSID(), eapMethod, phase2Method);
+    }
+
+    public static WifiConfiguration createEapNetwork(String ssid, int eapMethod, int phase2Method) {
         WifiConfiguration configuration =
-                generateWifiConfig(TEST_NETWORK_ID, TEST_UID, createNewSSID(), true, true,
+                generateWifiConfig(TEST_NETWORK_ID, TEST_UID, ssid, true, true,
                         null, null, SECURITY_EAP);
-        configuration.enterpriseConfig.setEapMethod(eapMethod);
-        configuration.enterpriseConfig.setPhase2Method(phase2Method);
+        if (eapMethod != WifiEnterpriseConfig.Eap.NONE) {
+            configuration.enterpriseConfig.setEapMethod(eapMethod);
+            configuration.enterpriseConfig.setPhase2Method(phase2Method);
+        }
+        return configuration;
+    }
+
+    public static WifiConfiguration createWpa2Wpa3EnterpriseNetwork() {
+        return createWpa2Wpa3EnterpriseNetwork(createNewSSID());
+    }
+
+    public static WifiConfiguration createWpa2Wpa3EnterpriseNetwork(
+            int eapMethod, int phase2Method) {
+        return createWpa2Wpa3EnterpriseNetwork(createNewSSID(), eapMethod, phase2Method);
+    }
+
+    public static WifiConfiguration createWpa2Wpa3EnterpriseNetwork(String ssid) {
+        return createWpa2Wpa3EnterpriseNetwork(
+                ssid,
+                WifiEnterpriseConfig.Eap.NONE,
+                WifiEnterpriseConfig.Phase2.NONE);
+    }
+
+    public static WifiConfiguration createWpa2Wpa3EnterpriseNetwork(
+            String ssid, int eapMethod, int phase2Method) {
+        WifiConfiguration configuration =
+                generateWifiConfig(TEST_NETWORK_ID, TEST_UID, ssid, true, true,
+                        null, null, SECURITY_EAP);
+        if (eapMethod != WifiEnterpriseConfig.Eap.NONE) {
+            configuration.enterpriseConfig.setEapMethod(eapMethod);
+            configuration.enterpriseConfig.setPhase2Method(phase2Method);
+        }
+        // the upgradable type is always added.
+        SecurityParams params = SecurityParams
+                .createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
+        params.setIsAddedByAutoUpgrade(true);
+        configuration.addSecurityParams(params);
+        return configuration;
+    }
+
+    public static WifiConfiguration createWpa3EnterpriseNetwork(String ssid) {
+        WifiConfiguration configuration =
+                generateWifiConfig(TEST_NETWORK_ID, TEST_UID, ssid, true, true,
+                        null, null, SECURITY_WPA3_ENTERPRISE);
         return configuration;
     }
 
@@ -387,7 +480,6 @@
                 generateWifiConfig(TEST_NETWORK_ID, TEST_UID, createNewSSID(), true, true,
                         null, null, SECURITY_EAP_SUITE_B);
 
-        configuration.requirePmf = true;
         configuration.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
         configuration.enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.NONE);
 
@@ -513,6 +605,8 @@
         config.setCaCertificateAliases(new String[] {TEST_CA_CERT_ALIAS + "PEAP"});
         config.setCaCertificates(new X509Certificate[] {FakeKeys.CA_CERT0, FakeKeys.CA_CERT1});
         config.setDomainSuffixMatch(TEST_DOM_SUBJECT_MATCH);
+        config.setIdentity(TEST_IDENTITY);
+        config.setPassword(TEST_EAP_PASSWORD);
         return config;
     }
 
@@ -538,27 +632,35 @@
      * Gets scan result capabilities for a particular network configuration.
      */
     public static String getScanResultCapsForNetwork(WifiConfiguration configuration) {
-        String caps;
-        if (configuration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
-            caps = "[RSN-PSK-CCMP]";
-        } else if (configuration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP)
-                || configuration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X)) {
-            caps = "[RSN-EAP-CCMP]";
-        } else if (configuration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)
+        String caps = "";
+        if (configuration.isSecurityType(WifiConfiguration.SECURITY_TYPE_PSK)) {
+            caps += "[RSN-PSK-CCMP]";
+        }
+        if (configuration.isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE)) {
+            caps += "[RSN-EAP/SHA256-CCMP][MFPC][MFPR]";
+        }
+        if (configuration.isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP)) {
+            caps += "[RSN-EAP/SHA1-CCMP]";
+        }
+        if (configuration.isSecurityType(WifiConfiguration.SECURITY_TYPE_WEP)
                 && WifiConfigurationUtil.hasAnyValidWepKey(configuration.wepKeys)) {
-            caps = "[WEP]";
-        } else if (configuration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
-            caps = "[RSN-SAE-CCMP]";
-        } else if (configuration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
-            caps = "[RSN-OWE-CCMP]";
-        } else if (configuration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SUITE_B_192)) {
-            caps = "[RSN-SUITE-B-192-CCMP]";
-        } else if (configuration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WAPI_PSK)) {
-            caps = "[WAPI-WAPI-PSK-SMS4]";
-        } else if (configuration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WAPI_CERT)) {
-            caps = "[WAPI-WAPI-CERT-SMS4]";
-        } else {
-            caps = "[]";
+            caps += "[WEP]";
+        }
+        if (configuration.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE)) {
+            caps += "[RSN-SAE-CCMP]";
+        }
+        if (configuration.isSecurityType(WifiConfiguration.SECURITY_TYPE_OWE)) {
+            caps += "[RSN-OWE-CCMP]";
+        }
+        if (configuration.isSecurityType(
+                WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT)) {
+            caps += "[RSN-SUITE_B_192-CCMP][MFPR]";
+        }
+        if (configuration.isSecurityType(WifiConfiguration.SECURITY_TYPE_WAPI_PSK)) {
+            caps += "[WAPI-WAPI-PSK-SMS4]";
+        }
+        if (configuration.isSecurityType(WifiConfiguration.SECURITY_TYPE_WAPI_CERT)) {
+            caps += "[WAPI-WAPI-CERT-SMS4]";
         }
         return caps;
     }
@@ -599,7 +701,7 @@
      * and config store.
      */
     private static void assertCommonConfigurationElementsEqual(
-            WifiConfiguration expected, WifiConfiguration actual) {
+            WifiConfiguration expected, WifiConfiguration actual, boolean isSupplicantBackup) {
         assertNotNull(expected);
         assertNotNull(actual);
         assertEquals(expected.SSID, actual.SSID);
@@ -610,16 +712,19 @@
         assertEquals(expected.hiddenSSID, actual.hiddenSSID);
         assertEquals(expected.requirePmf, actual.requirePmf);
         assertEquals(expected.allowedKeyManagement, actual.allowedKeyManagement);
-        assertEquals(expected.allowedProtocols, actual.allowedProtocols);
         assertEquals(expected.allowedAuthAlgorithms, actual.allowedAuthAlgorithms);
-        assertEquals(expected.allowedGroupCiphers, actual.allowedGroupCiphers);
-        assertEquals(expected.allowedPairwiseCiphers, actual.allowedPairwiseCiphers);
-        assertEquals(expected.shared, actual.shared);
+        // Supplicant backup does not include the following fields.
+        if (!isSupplicantBackup) {
+            assertEquals(expected.allowedProtocols, actual.allowedProtocols);
+            assertEquals(expected.allowedGroupCiphers, actual.allowedGroupCiphers);
+            assertEquals(expected.allowedPairwiseCiphers, actual.allowedPairwiseCiphers);
+            assertEquals(expected.shared, actual.shared);
+        }
         assertEquals(expected.getIpConfiguration(), actual.getIpConfiguration());
     }
 
 
-   /**
+    /**
      * Asserts that the 2 WifiConfigurations are equal. This only compares the elements saved
      * for softAp used.
      */
@@ -640,7 +745,7 @@
      */
     public static void assertConfigurationEqualForBackup(
             WifiConfiguration expected, WifiConfiguration actual) {
-        assertCommonConfigurationElementsEqual(expected, actual);
+        assertCommonConfigurationElementsEqual(expected, actual, true);
         assertEquals(expected.meteredOverride, actual.meteredOverride);
     }
 
@@ -650,7 +755,7 @@
      */
     public static void assertConfigurationEqualForConfigStore(
             WifiConfiguration expected, WifiConfiguration actual) {
-        assertCommonConfigurationElementsEqual(expected, actual);
+        assertCommonConfigurationElementsEqual(expected, actual, false);
         assertEquals(expected.status, actual.status);
         assertEquals(expected.FQDN, actual.FQDN);
         assertEquals(expected.providerFriendlyName, actual.providerFriendlyName);
@@ -662,6 +767,10 @@
         assertEquals(expected.meteredHint, actual.meteredHint);
         assertEquals(expected.meteredOverride, actual.meteredOverride);
         assertEquals(expected.useExternalScores, actual.useExternalScores);
+        assertEquals(expected.trusted, actual.trusted);
+        assertEquals(expected.oemPaid, actual.oemPaid);
+        assertEquals(expected.oemPrivate, actual.oemPrivate);
+        assertEquals(expected.carrierMerged, actual.carrierMerged);
         assertEquals(0, actual.numAssociation);
         assertEquals(expected.creatorUid, actual.creatorUid);
         assertEquals(expected.creatorName, actual.creatorName);
@@ -683,7 +792,7 @@
      */
     public static void assertConfigurationEqualForConfigManagerAddOrUpdate(
             WifiConfiguration expected, WifiConfiguration actual) {
-        assertCommonConfigurationElementsEqual(expected, actual);
+        assertCommonConfigurationElementsEqual(expected, actual, false);
         assertEquals(expected.FQDN, actual.FQDN);
         assertEquals(expected.providerFriendlyName, actual.providerFriendlyName);
         assertEquals(expected.noInternetAccessExpected, actual.noInternetAccessExpected);
@@ -693,6 +802,7 @@
         assertEquals(expected.ephemeral, actual.ephemeral);
         assertEquals(expected.osu, actual.osu);
         assertEquals(expected.trusted, actual.trusted);
+        assertEquals(expected.oemPaid, actual.oemPaid);
         assertEquals(expected.fromWifiNetworkSuggestion, actual.fromWifiNetworkSuggestion);
         assertEquals(expected.fromWifiNetworkSpecifier, actual.fromWifiNetworkSpecifier);
         assertEquals(expected.creatorUid, actual.creatorUid);
@@ -722,10 +832,7 @@
         assertEquals(expected.hiddenSSID, actual.hiddenSSID);
         assertEquals(expected.requirePmf, actual.requirePmf);
         assertEquals(expected.allowedKeyManagement, actual.allowedKeyManagement);
-        assertEquals(expected.allowedProtocols, actual.allowedProtocols);
-        assertEquals(expected.allowedAuthAlgorithms, actual.allowedAuthAlgorithms);
-        assertEquals(expected.allowedGroupCiphers, actual.allowedGroupCiphers);
-        assertEquals(expected.allowedPairwiseCiphers, actual.allowedPairwiseCiphers);
+        /* security params are static to the security type, no need to check them anymore */
         assertWifiEnterpriseConfigEqualForConfigStore(
                 expected.enterpriseConfig, actual.enterpriseConfig);
     }
@@ -737,7 +844,7 @@
      */
     public static void assertConfigurationEqual(
             WifiConfiguration expected, WifiConfiguration actual) {
-        assertCommonConfigurationElementsEqual(expected, actual);
+        assertCommonConfigurationElementsEqual(expected, actual, false);
         assertEquals(expected.networkId, actual.networkId);
         assertEquals(expected.ephemeral, actual.ephemeral);
         assertEquals(expected.fromWifiNetworkSuggestion, actual.fromWifiNetworkSuggestion);
@@ -746,6 +853,26 @@
     }
 
     /**
+     * Assert that the 2 lists of WifiConfigurations are equal.
+     */
+    public static void assertConfigurationsEqual(
+            List<WifiConfiguration> expected, List<WifiConfiguration> actual) {
+        assertEquals(expected.size(), actual.size());
+        for (WifiConfiguration expectedConfiguration : expected) {
+            String expectedConfigKey = expectedConfiguration.getProfileKey();
+            boolean didCompare = false;
+            for (WifiConfiguration actualConfiguration : actual) {
+                String actualConfigKey = actualConfiguration.getProfileKey();
+                if (actualConfigKey.equals(expectedConfigKey)) {
+                    assertConfigurationEqual(expectedConfiguration, actualConfiguration);
+                    didCompare = true;
+                }
+            }
+            assertTrue(didCompare);
+        }
+    }
+
+    /**
      * Assert that the 2 NetworkSelectionStatus's are equal. This compares all the elements saved
      * for config store.
      */
@@ -805,6 +932,9 @@
                 actual.getFieldValue(WifiEnterpriseConfig.PLMN_KEY));
         assertEquals(expected.getEapMethod(), actual.getEapMethod());
         assertEquals(expected.getPhase2Method(), actual.getPhase2Method());
+        if (SdkLevel.isAtLeastS()) {
+            assertEquals(expected.getClientKeyPairAlias(), actual.getClientKeyPairAlias());
+        }
     }
 
     /**
@@ -815,10 +945,10 @@
             List<WifiConfiguration> expected, List<WifiConfiguration> actual) {
         assertEquals(expected.size(), actual.size());
         for (WifiConfiguration expectedConfiguration : expected) {
-            String expectedConfigKey = expectedConfiguration.getKey();
+            String expectedConfigKey = expectedConfiguration.getProfileKey();
             boolean didCompare = false;
             for (WifiConfiguration actualConfiguration : actual) {
-                String actualConfigKey = actualConfiguration.getKey();
+                String actualConfigKey = actualConfiguration.getProfileKey();
                 if (actualConfigKey.equals(expectedConfigKey)) {
                     assertConfigurationEqualForBackup(
                             expectedConfiguration, actualConfiguration);
@@ -838,10 +968,10 @@
             List<WifiConfiguration> expected, List<WifiConfiguration> actual) {
         assertEquals(expected.size(), actual.size());
         for (WifiConfiguration expectedConfiguration : expected) {
-            String expectedConfigKey = expectedConfiguration.getKey();
+            String expectedConfigKey = expectedConfiguration.getProfileKey();
             boolean didCompare = false;
             for (WifiConfiguration actualConfiguration : actual) {
-                String actualConfigKey = actualConfiguration.getKey();
+                String actualConfigKey = actualConfiguration.getProfileKey();
                 if (actualConfigKey.equals(expectedConfigKey)) {
                     assertConfigurationEqualForConfigManagerAddOrUpdate(
                             expectedConfiguration, actualConfiguration);
@@ -860,10 +990,10 @@
             List<WifiConfiguration> expected, List<WifiConfiguration> actual) {
         assertEquals(expected.size(), actual.size());
         for (WifiConfiguration expectedConfiguration : expected) {
-            String expectedConfigKey = expectedConfiguration.getKey();
+            String expectedConfigKey = expectedConfiguration.getProfileKey();
             boolean didCompare = false;
             for (WifiConfiguration actualConfiguration : actual) {
-                String actualConfigKey = actualConfiguration.getKey();
+                String actualConfigKey = actualConfiguration.getProfileKey();
                 if (actualConfigKey.equals(expectedConfigKey)) {
                     assertConfigurationEqualForConfigStore(
                             expectedConfiguration, actualConfiguration);
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtilTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtilTest.java
index 0dd5b12..75cdfb5 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtilTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtilTest.java
@@ -16,16 +16,25 @@
 
 package com.android.server.wifi;
 
+import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
 import static android.net.wifi.WifiEnterpriseConfig.OCSP_NONE;
 import static android.net.wifi.WifiEnterpriseConfig.OCSP_REQUIRE_CERT_STATUS;
 
-import static org.junit.Assert.*;
+import static com.android.server.wifi.WifiConfigurationUtil.addSecurityTypeToNetworkId;
+import static com.android.server.wifi.WifiConfigurationUtil.convertWifiInfoSecurityTypeToWifiConfiguration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import android.content.pm.UserInfo;
 import android.net.IpConfiguration;
 import android.net.MacAddress;
+import android.net.wifi.ScanResult;
+import android.net.wifi.SecurityParams;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiNetworkSpecifier;
 import android.net.wifi.WifiScanner;
@@ -34,6 +43,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.modules.utils.build.SdkLevel;
+
 import org.junit.Test;
 
 import java.security.cert.X509Certificate;
@@ -52,8 +63,8 @@
     static final int OTHER_USER_ID = 11;
     static final int TEST_UID = 10000;
     static final String TEST_PACKAGE = "com.test";
-    static final String TEST_SSID = "test_ssid";
-    static final String TEST_SSID_1 = "test_ssid_1";
+    static final String TEST_SSID = "\"test_ssid\"";
+    static final String TEST_SSID_1 = "\"test_ssid_1\"";
     static final String TEST_BSSID = "aa:aa:11:22:cc:dd";
     static final String TEST_BSSID_1 = "11:22:11:22:cc:dd";
     static final List<UserInfo> PROFILES = Arrays.asList(
@@ -183,7 +194,7 @@
      * values.
      */
     @Test
-    public void testValidatePositiveCases_Ascii() {
+    public void testValidatePositiveCases_AsciiSsidString() {
         assertTrue(WifiConfigurationUtil.validate(
                 WifiConfigurationTestUtil.createOpenNetwork(),
                 WifiConfigurationUtil.VALIDATE_FOR_ADD));
@@ -212,7 +223,7 @@
      * values.
      */
     @Test
-    public void testValidatePositiveCases_Hex() {
+    public void testValidatePositiveCases_HexSsidString() {
         WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
         config.SSID = "abcd1234555a";
         config.preSharedKey = "abcd123455151234556788990034556667332345667322344556676743233445";
@@ -220,6 +231,18 @@
     }
 
     /**
+     * Verify that the validate method validates WifiConfiguration with "any" in the BSSID field.
+     */
+    @Test
+    public void testValidatePositiveCases_AnyBssidString() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
+        config.BSSID = "any";
+        assertTrue(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+        assertTrue(WifiConfigurationUtil.validate(
+                config, WifiConfigurationUtil.VALIDATE_FOR_UPDATE));
+    }
+
+    /**
      * Verify that the validate method validates WifiConfiguration with masked psk string only for
      * an update.
      */
@@ -411,6 +434,53 @@
     }
 
     /**
+     * Verify that the validate method validates WifiConfiguration with masked wep key only for
+     * an update.
+     */
+    @Test
+    public void testValidatePositiveCases_MaskedWepKeysString() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createWepNetwork();
+        assertTrue(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+
+        config.wepKeys = new String[]{ WifiConfigurationUtil.PASSWORD_MASK,
+                WifiConfigurationUtil.PASSWORD_MASK,
+                WifiConfigurationUtil.PASSWORD_MASK,
+                WifiConfigurationUtil.PASSWORD_MASK};
+        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+        assertTrue(WifiConfigurationUtil.validate(
+                config, WifiConfigurationUtil.VALIDATE_FOR_UPDATE));
+    }
+
+    /**
+     * Verify that the validate method fails to validate WifiConfiguration with bad wep length.
+     */
+    @Test
+    public void testValidateNegativeCases_BadWepKeysLength() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createWepNetwork();
+        assertTrue(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+
+        config.wepKeys = new String[] {"\"abcd\""};
+        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+        config.wepKeys = new String[] {"456"};
+        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+        // Error scenario in b/169638868.
+        config.wepKeys = new String[] {""};
+        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+    }
+
+    /**
+     * Verify that the validate method fails to validate WifiConfiguration with bad wep tx key idx.
+     */
+    @Test
+    public void testValidateNegativeCases_BadWepTxKeysIndex() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createWepNetwork();
+        assertTrue(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+        // Should be < wepKeys.length
+        config.wepTxKeyIndex = config.wepKeys.length;
+        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+    }
+
+    /**
      * Verify that the validate method fails to validate WifiConfiguration with bad key mgmt values.
      */
     @Test
@@ -447,6 +517,19 @@
     }
 
     /**
+     * Verify that the validate method fails to validate WifiConfiguration with bad key mgmt values.
+     */
+    @Test
+    public void testValidateNegativeCases_BadSuiteBKeyMgmt() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createEapSuiteBNetwork();
+        assertTrue(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+
+        config.allowedKeyManagement.clear(WifiConfiguration.KeyMgmt.IEEE8021X);
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA2_PSK);
+        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+    }
+
+    /**
      * Verify that the validate method fails to validate WifiConfiguration with bad ipconfiguration
      * values.
      */
@@ -483,8 +566,7 @@
         WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
         assertTrue(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
 
-        config.allowedKeyManagement.clear();
-        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.OSEN);
+        config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OSEN);
         assertTrue(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
         // Verify we reset the KeyMgmt
         assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK));
@@ -525,7 +607,7 @@
         WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
         assertTrue(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
 
-        config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.GTK_NOT_USED + 3);
+        config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.GTK_NOT_USED + 4);
         assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
     }
 
@@ -538,46 +620,7 @@
         WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
         assertTrue(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
 
-        config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP + 3);
-        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
-    }
-
-    /**
-     * Verify that the validate method fails to validate WifiConfiguration with malformed sae
-     * string.
-     */
-    @Test
-    public void testValidateNegativeCases_SaeMissingPmf() {
-        WifiConfiguration config = WifiConfigurationTestUtil.createSaeNetwork();
-        assertTrue(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
-
-        config.requirePmf = false;
-        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
-    }
-
-    /**
-     * Verify that the validate method fails to validate WifiConfiguration with malformed owe
-     * string.
-     */
-    @Test
-    public void testValidateNegativeCases_OweMissingPmf() {
-        WifiConfiguration config = WifiConfigurationTestUtil.createOweNetwork();
-        assertTrue(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
-
-        config.requirePmf = false;
-        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
-    }
-
-    /**
-     * Verify that the validate method fails to validate WifiConfiguration with malformed suiteb
-     * string.
-     */
-    @Test
-    public void testValidateNegativeCases_SuitebMissingPmf() {
-        WifiConfiguration config = WifiConfigurationTestUtil.createEapSuiteBNetwork();
-        assertTrue(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
-
-        config.requirePmf = false;
+        config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP + 4);
         assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
     }
 
@@ -590,6 +633,7 @@
         WifiNetworkSpecifier specifier = new WifiNetworkSpecifier(
                 new PatternMatcher(TEST_SSID, PatternMatcher.PATTERN_LITERAL),
                 Pair.create(WifiManager.ALL_ZEROS_MAC_ADDRESS, WifiManager.ALL_ZEROS_MAC_ADDRESS),
+                ScanResult.UNSPECIFIED,
                 WifiConfigurationTestUtil.createOpenNetwork());
         assertTrue(WifiConfigurationUtil.validateNetworkSpecifier(specifier));
     }
@@ -603,6 +647,7 @@
         WifiNetworkSpecifier specifier = new WifiNetworkSpecifier(
                 new PatternMatcher(".*", PatternMatcher.PATTERN_SIMPLE_GLOB),
                 Pair.create(MacAddress.fromString(TEST_BSSID), MacAddress.BROADCAST_ADDRESS),
+                ScanResult.UNSPECIFIED,
                 WifiConfigurationTestUtil.createOpenNetwork());
         assertTrue(WifiConfigurationUtil.validateNetworkSpecifier(specifier));
     }
@@ -616,11 +661,28 @@
         WifiNetworkSpecifier specifier = new WifiNetworkSpecifier(
                 new PatternMatcher(TEST_SSID, PatternMatcher.PATTERN_LITERAL),
                 Pair.create(MacAddress.fromString(TEST_BSSID), MacAddress.BROADCAST_ADDRESS),
+                ScanResult.UNSPECIFIED,
                 WifiConfigurationTestUtil.createOpenNetwork());
         assertTrue(WifiConfigurationUtil.validateNetworkSpecifier(specifier));
     }
 
     /**
+     * Verify that the validate method validates a WifiNetworkSpecifier that specifies ssid, bssid,
+     * and band. Note that such requests will currently still be rejected by WifiNetworkFactory, but
+     * requesting specific bands may be supported in future releases.
+     */
+    @Test
+    public void testValidateNetworkSpecifierPositiveCases_SsidPatternAndBssidPatternAndBand() {
+        WifiNetworkSpecifier specifier = new WifiNetworkSpecifier(
+                new PatternMatcher(TEST_SSID, PatternMatcher.PATTERN_LITERAL),
+                Pair.create(MacAddress.fromString(TEST_BSSID), MacAddress.BROADCAST_ADDRESS),
+                ScanResult.WIFI_BAND_5_GHZ,
+                WifiConfigurationTestUtil.createOpenNetwork());
+        assertTrue(WifiConfigurationUtil.validateNetworkSpecifier(specifier));
+    }
+
+
+    /**
      * Verify that the validate method fails to validate WifiNetworkSpecifier with no
      * ssid/bssid info.
      */
@@ -629,6 +691,7 @@
         WifiNetworkSpecifier specifier = new WifiNetworkSpecifier(
                 new PatternMatcher(".*", PatternMatcher.PATTERN_SIMPLE_GLOB),
                 Pair.create(WifiManager.ALL_ZEROS_MAC_ADDRESS, WifiManager.ALL_ZEROS_MAC_ADDRESS),
+                ScanResult.UNSPECIFIED,
                 WifiConfigurationTestUtil.createOpenNetwork());
         assertFalse(WifiConfigurationUtil.validateNetworkSpecifier(specifier));
     }
@@ -642,6 +705,7 @@
         WifiNetworkSpecifier specifier = new WifiNetworkSpecifier(
                 new PatternMatcher("", PatternMatcher.PATTERN_LITERAL),
                 Pair.create(WifiManager.ALL_ZEROS_MAC_ADDRESS, WifiManager.ALL_ZEROS_MAC_ADDRESS),
+                ScanResult.UNSPECIFIED,
                 WifiConfigurationTestUtil.createOpenNetwork());
         assertFalse(WifiConfigurationUtil.validateNetworkSpecifier(specifier));
     }
@@ -655,6 +719,7 @@
         WifiNetworkSpecifier specifier = new WifiNetworkSpecifier(
                 new PatternMatcher(TEST_SSID, PatternMatcher.PATTERN_LITERAL),
                 Pair.create(MacAddress.BROADCAST_ADDRESS, MacAddress.BROADCAST_ADDRESS),
+                ScanResult.UNSPECIFIED,
                 WifiConfigurationTestUtil.createOpenNetwork());
         assertFalse(WifiConfigurationUtil.validateNetworkSpecifier(specifier));
     }
@@ -668,6 +733,7 @@
         WifiNetworkSpecifier specifier = new WifiNetworkSpecifier(
                 new PatternMatcher(TEST_SSID, PatternMatcher.PATTERN_LITERAL),
                 Pair.create(MacAddress.fromString(TEST_BSSID), WifiManager.ALL_ZEROS_MAC_ADDRESS),
+                ScanResult.UNSPECIFIED,
                 WifiConfigurationTestUtil.createOpenNetwork());
         assertFalse(WifiConfigurationUtil.validateNetworkSpecifier(specifier));
     }
@@ -681,11 +747,25 @@
         WifiNetworkSpecifier specifier = new WifiNetworkSpecifier(
                 new PatternMatcher(TEST_SSID, PatternMatcher.PATTERN_PREFIX),
                 Pair.create(WifiManager.ALL_ZEROS_MAC_ADDRESS, WifiManager.ALL_ZEROS_MAC_ADDRESS),
+                ScanResult.UNSPECIFIED,
                 WifiConfigurationTestUtil.createOpenHiddenNetwork());
         assertFalse(WifiConfigurationUtil.validateNetworkSpecifier(specifier));
     }
 
     /**
+     * Verify that the validate method fails to validate WifiNetworkSpecifier with an invalid band.
+     */
+    @Test
+    public void testValidateNetworkSpecifierNegativeCases_InvalidBand() {
+        WifiNetworkSpecifier specifier = new WifiNetworkSpecifier(
+                new PatternMatcher(TEST_SSID, PatternMatcher.PATTERN_LITERAL),
+                Pair.create(MacAddress.fromString(TEST_BSSID), MacAddress.BROADCAST_ADDRESS),
+                42,  // invalid
+                WifiConfigurationTestUtil.createOpenNetwork());
+        assertFalse(WifiConfigurationUtil.validateNetworkSpecifier(specifier));
+    }
+
+    /**
      * Verify the instance of {@link android.net.wifi.WifiScanner.PnoSettings.PnoNetwork} created
      * for an open network using {@link WifiConfigurationUtil#createPnoNetwork(
      * WifiConfiguration)}.
@@ -825,6 +905,40 @@
     }
 
     /**
+     * Verify that WifiConfigurationUtil.isSameNetwork returns true when two WifiConfiguration
+     * objects have the same candidate security params.
+     */
+    @Test
+    public void testIsSameNetworkReturnsTrueOnSameNetworkWithSameCandidateSecurityParams() {
+        WifiConfiguration network = WifiConfigurationTestUtil.createPskNetwork(TEST_SSID);
+        network.getNetworkSelectionStatus().setCandidateSecurityParams(
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_PSK));
+        WifiConfiguration network1 = WifiConfigurationTestUtil.createPskNetwork(TEST_SSID);
+        network1.getNetworkSelectionStatus().setCandidateSecurityParams(
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_PSK));
+        assertTrue(WifiConfigurationUtil.isSameNetwork(network, network1));
+    }
+
+    /**
+     * Verify that WifiConfigurationUtil.isSameNetwork returns false when two WifiConfiguration
+     * objects have the different candidate security params.
+     */
+    @Test
+    public void testIsSameNetworkReturnsTrueOnSameNetworkWithDifferentCandidateSecurityParams() {
+        WifiConfiguration network = WifiConfigurationTestUtil.createPskNetwork(TEST_SSID);
+        network.getNetworkSelectionStatus().setCandidateSecurityParams(
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_PSK));
+        WifiConfiguration network1 = WifiConfigurationTestUtil.createPskNetwork(TEST_SSID);
+        network1.getNetworkSelectionStatus().setCandidateSecurityParams(
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_SAE));
+        assertFalse(WifiConfigurationUtil.isSameNetwork(network, network1));
+    }
+
+    /**
      * Verify the instance of {@link android.net.wifi.WifiScanner.PnoSettings.PnoNetwork} created
      * for a EAP network using {@link WifiConfigurationUtil#createPnoNetwork(WifiConfiguration)
      * }.
@@ -956,6 +1070,7 @@
         public String password;
         public X509Certificate[] caCerts;
         public WifiEnterpriseConfig enterpriseConfig;
+        public String wapiCertSuite;
 
         EnterpriseConfig(int eapMethod) {
             enterpriseConfig = new WifiEnterpriseConfig();
@@ -982,6 +1097,12 @@
             caCerts = certs;
             return this;
         }
+
+        public EnterpriseConfig setWapiCertSuite(String certSuite) {
+            enterpriseConfig.setWapiCertSuite(certSuite);
+            wapiCertSuite = certSuite;
+            return this;
+        }
     }
 
     /**
@@ -1061,4 +1182,178 @@
         assertTrue(WifiConfigurationUtil.hasEnterpriseConfigChanged(eapConfig1.enterpriseConfig,
                 eapConfig2.enterpriseConfig));
     }
+
+    /**
+     * Verify that new WifiEnterpriseConfig is detected.
+     */
+    @Test
+    public void testEnterpriseConfigWapiCertChanged() {
+        EnterpriseConfig eapConfig1 = new EnterpriseConfig(WifiEnterpriseConfig.Eap.WAPI_CERT)
+                .setWapiCertSuite("WapiCertSuite1");
+        EnterpriseConfig eapConfig2 = new EnterpriseConfig(WifiEnterpriseConfig.Eap.WAPI_CERT)
+                .setWapiCertSuite("WapiCertSuite2");
+
+        assertTrue(WifiConfigurationUtil.hasEnterpriseConfigChanged(
+                eapConfig1.enterpriseConfig, eapConfig2.enterpriseConfig));
+    }
+
+    /**
+     * Verify that a WAPI config is not considered an OPEN config.
+     */
+    @Test
+    public void testWapiConfigNotOpenConfig() {
+        WifiConfiguration wapiPskConfig = new WifiConfiguration();
+        wapiPskConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_WAPI_CERT);
+        assertFalse(WifiConfigurationUtil.isConfigForOpenNetwork(wapiPskConfig));
+
+        WifiConfiguration wapiCertConfig = new WifiConfiguration();
+        wapiCertConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_WAPI_CERT);
+        assertFalse(WifiConfigurationUtil.isConfigForOpenNetwork(wapiCertConfig));
+    }
+
+
+    /**
+     * Verify that the validate method fails to validate WifiConfiguration with malformed
+     * enterprise configuration
+     */
+    @Test
+    public void testValidateNegativeCases_MalformedEnterpriseConfig() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.SSID = "\"someNetwork\"";
+        config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
+        // EAP method is kept as Eap.NONE - should not crash, but return invalid ID
+        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+
+        config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
+        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+
+        config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT);
+        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+    }
+
+    /**
+     * Verify that the validate method fails to validate WifiConfiguration with enterprise
+     * configuration that is missing the identity and/or password.
+     */
+    @Test
+    public void testValidateNegativeCases_NoIdentityOrPasswordEnterpriseConfig() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork();
+        config.enterpriseConfig.setIdentity(null);
+        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+        assertFalse(WifiConfigurationUtil.validate(config,
+                WifiConfigurationUtil.VALIDATE_FOR_UPDATE));
+
+        config = WifiConfigurationTestUtil.createEapNetwork();
+        config.enterpriseConfig.setPassword(null);
+        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+        assertTrue(WifiConfigurationUtil.validate(config,
+                WifiConfigurationUtil.VALIDATE_FOR_UPDATE));
+
+        config = WifiConfigurationTestUtil.createWpa3EnterpriseNetwork(TEST_SSID);
+        config.enterpriseConfig.setIdentity(null);
+        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+        assertFalse(WifiConfigurationUtil.validate(config,
+                WifiConfigurationUtil.VALIDATE_FOR_UPDATE));
+
+        config = WifiConfigurationTestUtil.createWpa3EnterpriseNetwork(TEST_SSID);
+        config.enterpriseConfig.setPassword(null);
+        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+        assertTrue(WifiConfigurationUtil.validate(config,
+                WifiConfigurationUtil.VALIDATE_FOR_UPDATE));
+    }
+
+    /**
+     * Verify the behavior of convertWifiInfoSecurityTypeToWifiConfiguration
+     */
+    @Test
+    public void testConvertWifiInfoSecurityTypeToWifiConfiguration() {
+        assertEquals(WifiConfiguration.SECURITY_TYPE_OPEN,
+                convertWifiInfoSecurityTypeToWifiConfiguration(WifiInfo.SECURITY_TYPE_OPEN));
+        assertEquals(WifiConfiguration.SECURITY_TYPE_WEP,
+                convertWifiInfoSecurityTypeToWifiConfiguration(WifiInfo.SECURITY_TYPE_WEP));
+        assertEquals(WifiConfiguration.SECURITY_TYPE_PSK,
+                convertWifiInfoSecurityTypeToWifiConfiguration(WifiInfo.SECURITY_TYPE_PSK));
+        assertEquals(WifiConfiguration.SECURITY_TYPE_EAP,
+                convertWifiInfoSecurityTypeToWifiConfiguration(WifiInfo.SECURITY_TYPE_EAP));
+        assertEquals(WifiConfiguration.SECURITY_TYPE_SAE,
+                convertWifiInfoSecurityTypeToWifiConfiguration(WifiInfo.SECURITY_TYPE_SAE));
+        assertEquals(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT,
+                convertWifiInfoSecurityTypeToWifiConfiguration(
+                        WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT));
+        assertEquals(WifiConfiguration.SECURITY_TYPE_OWE,
+                convertWifiInfoSecurityTypeToWifiConfiguration(WifiInfo.SECURITY_TYPE_OWE));
+        assertEquals(WifiConfiguration.SECURITY_TYPE_WAPI_PSK,
+                convertWifiInfoSecurityTypeToWifiConfiguration(WifiInfo.SECURITY_TYPE_WAPI_PSK));
+        assertEquals(WifiConfiguration.SECURITY_TYPE_WAPI_CERT,
+                convertWifiInfoSecurityTypeToWifiConfiguration(WifiInfo.SECURITY_TYPE_WAPI_CERT));
+        assertEquals(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE,
+                convertWifiInfoSecurityTypeToWifiConfiguration(
+                        WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE));
+        assertEquals(WifiConfiguration.SECURITY_TYPE_OSEN,
+                convertWifiInfoSecurityTypeToWifiConfiguration(WifiInfo.SECURITY_TYPE_OSEN));
+        assertEquals(WifiConfiguration.SECURITY_TYPE_PASSPOINT_R1_R2,
+                convertWifiInfoSecurityTypeToWifiConfiguration(
+                        WifiInfo.SECURITY_TYPE_PASSPOINT_R1_R2));
+        assertEquals(WifiConfiguration.SECURITY_TYPE_PASSPOINT_R3,
+                convertWifiInfoSecurityTypeToWifiConfiguration(
+                        WifiInfo.SECURITY_TYPE_PASSPOINT_R3));
+        assertEquals(-1, convertWifiInfoSecurityTypeToWifiConfiguration(13));
+        assertEquals(-1,
+                convertWifiInfoSecurityTypeToWifiConfiguration(WifiInfo.SECURITY_TYPE_UNKNOWN));
+    }
+
+    /**
+     * Verify that adding and removing the security type for network ID behaves correctly
+     */
+    @Test
+    public void testAddAndRemoveSecurityTypeForNetworkId() {
+        List<Integer> securityList = Arrays.asList(
+                WifiConfiguration.SECURITY_TYPE_OPEN,
+                WifiConfiguration.SECURITY_TYPE_WEP,
+                WifiConfiguration.SECURITY_TYPE_PSK,
+                WifiConfiguration.SECURITY_TYPE_EAP,
+                WifiConfiguration.SECURITY_TYPE_SAE,
+                WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT,
+                WifiConfiguration.SECURITY_TYPE_OWE,
+                WifiConfiguration.SECURITY_TYPE_WAPI_PSK,
+                WifiConfiguration.SECURITY_TYPE_WAPI_CERT,
+                WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE,
+                WifiConfiguration.SECURITY_TYPE_OSEN,
+                WifiConfiguration.SECURITY_TYPE_PASSPOINT_R1_R2,
+                WifiConfiguration.SECURITY_TYPE_PASSPOINT_R3
+        );
+
+        final int netId = 1;
+        if (!SdkLevel.isAtLeastS()) {
+            // INVALID_NET_ID should remain the same from either adding or removing
+            assertEquals(INVALID_NETWORK_ID, WifiConfigurationUtil.addSecurityTypeToNetworkId(
+                    INVALID_NETWORK_ID, WifiConfiguration.SECURITY_TYPE_OPEN));
+            assertEquals(INVALID_NETWORK_ID, WifiConfigurationUtil.removeSecurityTypeFromNetworkId(
+                    INVALID_NETWORK_ID));
+            // Add and then remove should result in the original netId
+            for (@WifiConfiguration.SecurityType int securityType : securityList) {
+                assertEquals(netId, WifiConfigurationUtil.removeSecurityTypeFromNetworkId(
+                        WifiConfigurationUtil.addSecurityTypeToNetworkId(
+                                netId, securityType)));
+            }
+            // Multiple removes should result in the same netId as a single remove
+            for (@WifiConfiguration.SecurityType int securityType : securityList) {
+                assertEquals(WifiConfigurationUtil.removeSecurityTypeFromNetworkId(netId),
+                        WifiConfigurationUtil.removeSecurityTypeFromNetworkId(
+                                WifiConfigurationUtil.removeSecurityTypeFromNetworkId(
+                                        WifiConfigurationUtil.addSecurityTypeToNetworkId(
+                                                netId, securityType))));
+            }
+            // A unique net id should be created for each security type added
+            assertEquals(securityList.size(), securityList.stream()
+                    .map(security -> addSecurityTypeToNetworkId(netId, security))
+                    .distinct()
+                    .count());
+        } else {
+            // Add should do nothing for SDK level S and above.
+            for (@WifiConfiguration.SecurityType int securityType : securityList) {
+                assertEquals(netId, addSecurityTypeToNetworkId(netId, securityType));
+            }
+        }
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiConnectivityHelperTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiConnectivityHelperTest.java
index 7361540..bbe9cf7 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiConnectivityHelperTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiConnectivityHelperTest.java
@@ -21,8 +21,6 @@
 import static org.junit.Assert.*;
 import static org.mockito.Mockito.*;
 
-import android.app.test.MockAnswerUtil.AnswerWithArguments;
-
 import androidx.test.filters.SmallTest;
 
 import org.junit.After;
@@ -46,7 +44,7 @@
         MockitoAnnotations.initMocks(this);
         setupWifiNative();
 
-        mWifiConnectivityHelper = new WifiConnectivityHelper(mWifiNative);
+        mWifiConnectivityHelper = new WifiConnectivityHelper(mWifiInjector);
     }
 
     /** Cleans up test. */
@@ -56,51 +54,52 @@
     }
 
     private WifiConnectivityHelper mWifiConnectivityHelper;
-    @Mock private WifiNative mWifiNative;
+    @Mock private WifiInjector mWifiInjector;
+    @Mock private ActiveModeWarden mActiveModeWarden;
+    @Mock private ClientModeManager mClientModeManager;
     @Captor ArgumentCaptor<WifiNative.RoamingConfig> mRoamingConfigCaptor;
     private int mFeatureSetValue;
     private static final String TAG = "WifiConnectivityHelperTest";
-    private static final int MAX_BSSID_BLACKLIST_SIZE = 16;
-    private static final int MAX_SSID_WHITELIST_SIZE = 8;
+    private static final int MAX_BSSID_BLOCKLIST_SIZE = 16;
+    private static final int MAX_SSID_ALLOWLIST_SIZE = 8;
 
     private void setupWifiNative() {
+        when(mWifiInjector.getActiveModeWarden()).thenReturn(mActiveModeWarden);
+        when(mActiveModeWarden.getPrimaryClientModeManager()).thenReturn(mClientModeManager);
+
         // Return firmware roaming feature as supported by default.
-        when(mWifiNative.getSupportedFeatureSet(any()))
-                .thenReturn((long) WIFI_FEATURE_CONTROL_ROAMING);
+        when(mClientModeManager.getSupportedFeatures()).thenReturn(WIFI_FEATURE_CONTROL_ROAMING);
 
-        doAnswer(new AnswerWithArguments() {
-            public boolean answer(String ifaceName, WifiNative.RoamingCapabilities roamCap)
-                    throws Exception {
-                roamCap.maxBlocklistSize = MAX_BSSID_BLACKLIST_SIZE;
-                roamCap.maxAllowlistSize = MAX_SSID_WHITELIST_SIZE;
-                return true;
-            }}).when(mWifiNative).getRoamingCapabilities(any(), anyObject());
+        WifiNative.RoamingCapabilities roamCap = new WifiNative.RoamingCapabilities();
+        roamCap.maxBlocklistSize = MAX_BSSID_BLOCKLIST_SIZE;
+        roamCap.maxAllowlistSize = MAX_SSID_ALLOWLIST_SIZE;
+        when(mClientModeManager.getRoamingCapabilities()).thenReturn(roamCap);
 
-        when(mWifiNative.configureRoaming(any(), anyObject())).thenReturn(true);
+        when(mClientModeManager.configureRoaming(isNotNull())).thenReturn(true);
     }
 
-    private ArrayList<String> buildBssidBlacklist(int size) {
-        ArrayList<String> bssidBlacklist = new ArrayList<String>();
+    private ArrayList<String> buildBssidBlocklist(int size) {
+        ArrayList<String> bssidBlocklist = new ArrayList<String>();
 
         for (int i = 0; i < size; i++) {
             StringBuilder bssid = new StringBuilder("11:22:33:44:55:66");
             bssid.setCharAt(16, (char) ('0' + i));
-            bssidBlacklist.add(bssid.toString());
+            bssidBlocklist.add(bssid.toString());
         }
 
-        return bssidBlacklist;
+        return bssidBlocklist;
     }
 
-    private ArrayList<String> buildSsidWhitelist(int size) {
-        ArrayList<String> ssidWhitelist = new ArrayList<String>();
+    private ArrayList<String> buildSsidAllowlist(int size) {
+        ArrayList<String> ssidAllowlist = new ArrayList<String>();
 
         for (int i = 0; i < size; i++) {
             StringBuilder ssid = new StringBuilder("\"Test_Ap_0\"");
             ssid.setCharAt(9, (char) ('0' + i));
-            ssidWhitelist.add(ssid.toString());
+            ssidAllowlist.add(ssid.toString());
         }
 
-        return ssidWhitelist;
+        return ssidAllowlist;
     }
 
     /**
@@ -120,8 +119,7 @@
      */
     @Test
     public void returnFirmwareRoamingNotSupported() {
-        when(mWifiNative.getSupportedFeatureSet(any()))
-                .thenReturn((long) ~WIFI_FEATURE_CONTROL_ROAMING);
+        when(mClientModeManager.getSupportedFeatures()).thenReturn(~WIFI_FEATURE_CONTROL_ROAMING);
         assertTrue(mWifiConnectivityHelper.getFirmwareRoamingInfo());
         assertFalse(mWifiConnectivityHelper.isFirmwareRoamingSupported());
     }
@@ -134,134 +132,126 @@
     public void verifyFirmwareRoamingCapabilityWithSuccessfulNativeCall() {
         assertTrue(mWifiConnectivityHelper.getFirmwareRoamingInfo());
         assertTrue(mWifiConnectivityHelper.isFirmwareRoamingSupported());
-        assertEquals(MAX_BSSID_BLACKLIST_SIZE, mWifiConnectivityHelper.getMaxNumBlacklistBssid());
-        assertEquals(MAX_SSID_WHITELIST_SIZE, mWifiConnectivityHelper.getMaxNumWhitelistSsid());
+        assertEquals(MAX_BSSID_BLOCKLIST_SIZE, mWifiConnectivityHelper.getMaxNumBlocklistBssid());
+        assertEquals(MAX_SSID_ALLOWLIST_SIZE, mWifiConnectivityHelper.getMaxNumAllowlistSsid());
     }
 
     /**
      * Verify that firmware roaming is set to not supported if WifiNative returned firmware roaming
-     * is supported but failed to return roaming capabilities. Firmware roaming capabilty values
+     * is supported but failed to return roaming capabilities. Firmware roaming capability values
      * should be reset to INVALID_LIST_SIZE.
      */
     @Test
     public void verifyFirmwareRoamingCapabilityWithFailureNativeCall() {
-        doAnswer(new AnswerWithArguments() {
-            public boolean answer(String ifaceName, WifiNative.RoamingCapabilities roamCap)
-                    throws Exception {
-                return false;
-            }}).when(mWifiNative).getRoamingCapabilities(any(), anyObject());
+        when(mClientModeManager.getRoamingCapabilities()).thenReturn(null);
         assertFalse(mWifiConnectivityHelper.getFirmwareRoamingInfo());
         assertFalse(mWifiConnectivityHelper.isFirmwareRoamingSupported());
         assertEquals(WifiConnectivityHelper.INVALID_LIST_SIZE,
-                mWifiConnectivityHelper.getMaxNumBlacklistBssid());
+                mWifiConnectivityHelper.getMaxNumBlocklistBssid());
         assertEquals(WifiConnectivityHelper.INVALID_LIST_SIZE,
-                mWifiConnectivityHelper.getMaxNumWhitelistSsid());
+                mWifiConnectivityHelper.getMaxNumAllowlistSsid());
     }
 
     /**
      * Verify that firmware roaming is set to not supported if WifiNative returned firmware roaming
-     * is supported but returned invalid max BSSID balcklist size. Firmware roaming capabilty values
-     * should be reset to INVALID_LIST_SIZE.
+     * is supported but returned invalid max BSSID blocklist size. Firmware roaming capability
+     * values should be reset to INVALID_LIST_SIZE.
      */
     @Test
-    public void verifyFirmwareRoamingCapabilityWithInvalidMaxBssidBlacklistSize() {
-        doAnswer(new AnswerWithArguments() {
-            public boolean answer(String ifaceName, WifiNative.RoamingCapabilities roamCap)
-                    throws Exception {
-                roamCap.maxBlocklistSize = -5;
-                roamCap.maxAllowlistSize = MAX_SSID_WHITELIST_SIZE;
-                return true;
-            }}).when(mWifiNative).getRoamingCapabilities(any(), anyObject());
+    public void verifyFirmwareRoamingCapabilityWithInvalidMaxBssidBlocklistSize() {
+        WifiNative.RoamingCapabilities roamCap = new WifiNative.RoamingCapabilities();
+        roamCap.maxBlocklistSize = -5;
+        roamCap.maxAllowlistSize = MAX_SSID_ALLOWLIST_SIZE;
+        when(mClientModeManager.getRoamingCapabilities()).thenReturn(roamCap);
+
         assertFalse(mWifiConnectivityHelper.getFirmwareRoamingInfo());
         assertFalse(mWifiConnectivityHelper.isFirmwareRoamingSupported());
         assertEquals(WifiConnectivityHelper.INVALID_LIST_SIZE,
-                mWifiConnectivityHelper.getMaxNumBlacklistBssid());
+                mWifiConnectivityHelper.getMaxNumBlocklistBssid());
         assertEquals(WifiConnectivityHelper.INVALID_LIST_SIZE,
-                mWifiConnectivityHelper.getMaxNumWhitelistSsid());
+                mWifiConnectivityHelper.getMaxNumAllowlistSsid());
     }
 
     /**
      * Verify that firmware roaming is set to not supported if WifiNative returned firmware roaming
-     * is supported but returned invalid max SSID whitelist size. Firmware roaming capabilty values
+     * is supported but returned invalid max SSID allowlist size. Firmware roaming capability values
      * should be reset to INVALID_LIST_SIZE.
      */
     @Test
-    public void verifyFirmwareRoamingCapabilityWithInvalidMaxSsidWhitelistSize() {
-        doAnswer(new AnswerWithArguments() {
-            public boolean answer(String ifaceName, WifiNative.RoamingCapabilities roamCap)
-                    throws Exception {
-                roamCap.maxBlocklistSize = MAX_BSSID_BLACKLIST_SIZE;
-                roamCap.maxAllowlistSize = -2;
-                return true;
-            }}).when(mWifiNative).getRoamingCapabilities(any(), anyObject());
+    public void verifyFirmwareRoamingCapabilityWithInvalidMaxSsidAllowlistSize() {
+        WifiNative.RoamingCapabilities roamCap = new WifiNative.RoamingCapabilities();
+        roamCap.maxBlocklistSize = MAX_BSSID_BLOCKLIST_SIZE;
+        roamCap.maxAllowlistSize = -2;
+        when(mClientModeManager.getRoamingCapabilities()).thenReturn(roamCap);
+
         assertFalse(mWifiConnectivityHelper.getFirmwareRoamingInfo());
         assertFalse(mWifiConnectivityHelper.isFirmwareRoamingSupported());
         assertEquals(WifiConnectivityHelper.INVALID_LIST_SIZE,
-                mWifiConnectivityHelper.getMaxNumBlacklistBssid());
+                mWifiConnectivityHelper.getMaxNumBlocklistBssid());
         assertEquals(WifiConnectivityHelper.INVALID_LIST_SIZE,
-                mWifiConnectivityHelper.getMaxNumWhitelistSsid());
+                mWifiConnectivityHelper.getMaxNumAllowlistSsid());
     }
 
     /**
-     * Verify that correct size BSSID blacklist and SSID whitelist are accepted.
+     * Verify that correct size BSSID blocklist and SSID allowlist are accepted.
      */
     @Test
     public void verifySetFirmwareRoamingConfigurationWithGoodInput() {
         assertTrue(mWifiConnectivityHelper.getFirmwareRoamingInfo());
-        ArrayList<String> blacklist = buildBssidBlacklist(MAX_BSSID_BLACKLIST_SIZE);
-        ArrayList<String> whitelist = buildSsidWhitelist(MAX_SSID_WHITELIST_SIZE);
-        assertTrue(mWifiConnectivityHelper.setFirmwareRoamingConfiguration(blacklist, whitelist));
+        ArrayList<String> blocklist = buildBssidBlocklist(MAX_BSSID_BLOCKLIST_SIZE);
+        ArrayList<String> allowlist = buildSsidAllowlist(MAX_SSID_ALLOWLIST_SIZE);
+        assertTrue(mWifiConnectivityHelper.setFirmwareRoamingConfiguration(blocklist, allowlist));
 
-        blacklist = buildBssidBlacklist(MAX_BSSID_BLACKLIST_SIZE - 2);
-        whitelist = buildSsidWhitelist(MAX_SSID_WHITELIST_SIZE - 3);
-        assertTrue(mWifiConnectivityHelper.setFirmwareRoamingConfiguration(blacklist, whitelist));
+        blocklist = buildBssidBlocklist(MAX_BSSID_BLOCKLIST_SIZE - 2);
+        allowlist = buildSsidAllowlist(MAX_SSID_ALLOWLIST_SIZE - 3);
+        assertTrue(mWifiConnectivityHelper.setFirmwareRoamingConfiguration(blocklist, allowlist));
     }
 
     /**
-     * Verify that null BSSID blacklist or SSID whitelist is rejected.
+     * Verify that null BSSID blocklist or SSID allowlist is rejected.
      */
     @Test
     public void verifySetFirmwareRoamingConfigurationWithNullInput() {
         assertTrue(mWifiConnectivityHelper.getFirmwareRoamingInfo());
-        ArrayList<String> blacklist = buildBssidBlacklist(MAX_BSSID_BLACKLIST_SIZE);
-        ArrayList<String> whitelist = buildSsidWhitelist(MAX_SSID_WHITELIST_SIZE);
-        assertFalse(mWifiConnectivityHelper.setFirmwareRoamingConfiguration(null, whitelist));
-        assertFalse(mWifiConnectivityHelper.setFirmwareRoamingConfiguration(blacklist, null));
+        ArrayList<String> blocklist = buildBssidBlocklist(MAX_BSSID_BLOCKLIST_SIZE);
+        ArrayList<String> allowlist = buildSsidAllowlist(MAX_SSID_ALLOWLIST_SIZE);
+        assertFalse(mWifiConnectivityHelper.setFirmwareRoamingConfiguration(null, allowlist));
+        assertFalse(mWifiConnectivityHelper.setFirmwareRoamingConfiguration(blocklist, null));
     }
 
     /**
-     * Verify that incorrect size BSSID blacklist is rejected.
+     * Verify that incorrect size BSSID blocklist is rejected.
      */
     @Test
-    public void verifySetFirmwareRoamingConfigurationWithIncorrectBlacklist() {
+    public void verifySetFirmwareRoamingConfigurationWithIncorrectBlocklist() {
         assertTrue(mWifiConnectivityHelper.getFirmwareRoamingInfo());
-        ArrayList<String> blacklist = buildBssidBlacklist(MAX_BSSID_BLACKLIST_SIZE + 1);
-        ArrayList<String> whitelist = buildSsidWhitelist(MAX_SSID_WHITELIST_SIZE);
-        assertFalse(mWifiConnectivityHelper.setFirmwareRoamingConfiguration(blacklist, whitelist));
+        ArrayList<String> blocklist = buildBssidBlocklist(MAX_BSSID_BLOCKLIST_SIZE + 1);
+        ArrayList<String> allowlist = buildSsidAllowlist(MAX_SSID_ALLOWLIST_SIZE);
+        assertFalse(mWifiConnectivityHelper.setFirmwareRoamingConfiguration(blocklist, allowlist));
     }
 
     /**
-     * Verify that incorrect size SSID whitelist is rejected.
+     * Verify that incorrect size SSID allowlist is rejected.
      */
     @Test
-    public void verifySetFirmwareRoamingConfigurationWithIncorrectWhitelist() {
+    public void verifySetFirmwareRoamingConfigurationWithIncorrectAllowlist() {
         assertTrue(mWifiConnectivityHelper.getFirmwareRoamingInfo());
-        ArrayList<String> blacklist = buildBssidBlacklist(MAX_BSSID_BLACKLIST_SIZE);
-        ArrayList<String> whitelist = buildSsidWhitelist(MAX_SSID_WHITELIST_SIZE + 1);
-        assertFalse(mWifiConnectivityHelper.setFirmwareRoamingConfiguration(blacklist, whitelist));
+        ArrayList<String> blocklist = buildBssidBlocklist(MAX_BSSID_BLOCKLIST_SIZE);
+        ArrayList<String> allowlist = buildSsidAllowlist(MAX_SSID_ALLOWLIST_SIZE + 1);
+        assertFalse(mWifiConnectivityHelper.setFirmwareRoamingConfiguration(blocklist, allowlist));
     }
 
     /**
-     * Verify that empty BSSID blacklist and SSID whitelist are sent to WifiNative
+     * Verify that empty BSSID blocklist and SSID allowlist are sent to WifiNative
      * to reset the firmware roaming configuration.
      */
     @Test
-    public void verifySetFirmwareRoamingConfigurationWithEmptyBlacklistAndWhitelist() {
+    public void verifySetFirmwareRoamingConfigurationWithEmptyBlocklistAndAllowlist() {
         assertTrue(mWifiConnectivityHelper.getFirmwareRoamingInfo());
-        ArrayList<String> blacklist = buildBssidBlacklist(0);
-        ArrayList<String> whitelist = buildSsidWhitelist(0);
-        assertTrue(mWifiConnectivityHelper.setFirmwareRoamingConfiguration(blacklist, whitelist));
-        verify(mWifiNative).configureRoaming(any(), mRoamingConfigCaptor.capture());
+        ArrayList<String> blocklist = buildBssidBlocklist(0);
+        ArrayList<String> allowlist = buildSsidAllowlist(0);
+        assertTrue(mWifiConnectivityHelper.setFirmwareRoamingConfiguration(blocklist, allowlist));
+        verify(mClientModeManager).configureRoaming(mRoamingConfigCaptor.capture());
         assertEquals(0, mRoamingConfigCaptor.getValue().blocklistBssids.size());
         assertEquals(0, mRoamingConfigCaptor.getValue().allowlistSsids.size());
     }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
index 38d2e82..d2acbae 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
@@ -16,19 +16,51 @@
 
 package com.android.server.wifi;
 
+import static android.content.Intent.ACTION_SCREEN_OFF;
+import static android.content.Intent.ACTION_SCREEN_ON;
+
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_PRIMARY;
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SECONDARY_LONG_LIVED;
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SECONDARY_TRANSIENT;
 import static com.android.server.wifi.ClientModeImpl.WIFI_WORK_SOURCE;
 import static com.android.server.wifi.WifiConfigurationTestUtil.generateWifiConfig;
 
-import static org.junit.Assert.*;
-import static org.mockito.Mockito.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyList;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.anyObject;
+import static org.mockito.Mockito.anySet;
+import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.validateMockitoUsage;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
 
+import android.app.AlarmManager;
 import android.app.test.MockAnswerUtil.AnswerWithArguments;
 import android.app.test.TestAlarmManager;
-import android.content.Context;
+import android.content.BroadcastReceiver;
+import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.net.MacAddress;
-import android.net.NetworkScoreManager;
 import android.net.wifi.ScanResult;
 import android.net.wifi.ScanResult.InformationElement;
 import android.net.wifi.SupplicantState;
@@ -45,6 +77,9 @@
 import android.net.wifi.WifiSsid;
 import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.os.Handler;
+import android.os.IPowerManager;
+import android.os.IThermalService;
+import android.os.PowerManager;
 import android.os.Process;
 import android.os.SystemClock;
 import android.os.WorkSource;
@@ -53,6 +88,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.modules.utils.build.SdkLevel;
+import com.android.server.wifi.ActiveModeWarden.ExternalClientModeManagerRequestListener;
 import com.android.server.wifi.hotspot2.PasspointManager;
 import com.android.server.wifi.util.LruConnectionTracker;
 import com.android.server.wifi.util.ScanResultUtil;
@@ -99,36 +136,49 @@
         mAlarmManager = new TestAlarmManager();
         mContext = mockContext();
         mLocalLog = new LocalLog(512);
-        mClientModeImpl = mockClientModeImpl();
+        setupMockForClientModeManager(mPrimaryClientModeManager);
         mWifiConfigManager = mockWifiConfigManager();
         mWifiInfo = getWifiInfo();
         mScanData = mockScanData();
         mWifiScanner = mockWifiScanner();
         mWifiConnectivityHelper = mockWifiConnectivityHelper();
         mWifiNS = mockWifiNetworkSelector();
-        when(mWifiInjector.getWifiScanner()).thenReturn(mWifiScanner);
+        when(mContext.getSystemService(WifiScanner.class)).thenReturn(mWifiScanner);
         when(mWifiNetworkSuggestionsManager.retrieveHiddenNetworkList())
                 .thenReturn(new ArrayList<>());
         when(mWifiNetworkSuggestionsManager.getAllApprovedNetworkSuggestions())
                 .thenReturn(new HashSet<>());
-        when(mWifiInjector.getBssidBlocklistMonitor()).thenReturn(mBssidBlocklistMonitor);
-        when(mWifiInjector.getWifiChannelUtilizationScan()).thenReturn(mWifiChannelUtilization);
-        when(mWifiInjector.getWifiScoreCard()).thenReturn(mWifiScoreCard);
-        when(mWifiInjector.getWifiNetworkSuggestionsManager())
-                .thenReturn(mWifiNetworkSuggestionsManager);
-        when(mWifiInjector.getPasspointManager()).thenReturn(mPasspointManager);
         when(mPasspointManager.getProviderConfigs(anyInt(), anyBoolean()))
                 .thenReturn(new ArrayList<>());
+        mPowerManagerService = mock(IPowerManager.class);
+        PowerManager powerManager =
+                new PowerManager(mContext, mPowerManagerService, mock(IThermalService.class),
+                        new Handler());
+        when(mContext.getSystemService(PowerManager.class)).thenReturn(powerManager);
+        when(powerManager.isInteractive()).thenReturn(false);
+        when(mPrimaryClientModeManager.getRole()).thenReturn(ActiveModeManager.ROLE_CLIENT_PRIMARY);
+        when(mPrimaryClientModeManager.syncRequestConnectionInfo()).thenReturn(mWifiInfo);
+        when(mActiveModeWarden.getPrimaryClientModeManager()).thenReturn(mPrimaryClientModeManager);
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ExternalClientModeManagerRequestListener listener,
+                    WorkSource requestorWs, String ssid, String bssid) {
+                listener.onAnswer(mPrimaryClientModeManager);
+            }
+        }).when(mActiveModeWarden).requestSecondaryTransientClientModeManager(
+                any(), eq(ActiveModeWarden.INTERNAL_REQUESTOR_WS), any(), any());
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ExternalClientModeManagerRequestListener listener,
+                    WorkSource requestorWs, String ssid, String bssid) {
+                listener.onAnswer(mPrimaryClientModeManager);
+            }
+        }).when(mActiveModeWarden).requestSecondaryLongLivedClientModeManager(
+                any(), any(), any(), any());
+
         mWifiConnectivityManager = createConnectivityManager();
-        verify(mWifiConfigManager).addOnNetworkUpdateListener(
-                mNetworkUpdateListenerCaptor.capture());
-        verify(mWifiNetworkSuggestionsManager).addOnSuggestionUpdateListener(
-                mSuggestionUpdateListenerCaptor.capture());
         mWifiConnectivityManager.setTrustedConnectionAllowed(true);
-        mWifiConnectivityManager.setWifiEnabled(true);
+        mWifiConnectivityManager.enableVerboseLogging(true);
+        setWifiEnabled(true);
         when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime());
-        mMinPacketRateActiveTraffic = mResources.getInteger(
-                R.integer.config_wifiFrameworkMinPacketPerSecondActiveTraffic);
         when(mWifiLastResortWatchdog.shouldIgnoreBssidUpdate(anyString())).thenReturn(false);
         mLruConnectionTracker = new LruConnectionTracker(100, mContext);
         Comparator<WifiConfiguration> comparator =
@@ -164,6 +214,11 @@
                 MOVING_PNO_SCAN_INTERVAL_MILLIS);
         resources.setInteger(R.integer.config_wifiStationaryPnoScanIntervalMillis,
                 STATIONARY_PNO_SCAN_INTERVAL_MILLIS);
+        resources.setInteger(R.integer.config_wifiPnoScanLowRssiNetworkRetryStartDelaySec,
+                LOW_RSSI_NETWORK_RETRY_START_DELAY_SEC);
+        resources.setInteger(R.integer.config_wifiPnoScanLowRssiNetworkRetryMaxDelaySec,
+                LOW_RSSI_NETWORK_RETRY_MAX_DELAY_SEC);
+        resources.setBoolean(R.bool.config_wifiEnable6ghzPscScanning, true);
     }
 
     /**
@@ -176,12 +231,11 @@
         validateMockitoUsage();
     }
 
-    private Context mContext;
+    private WifiContext mContext;
     private TestAlarmManager mAlarmManager;
     private TestLooper mLooper = new TestLooper();
     private WifiConnectivityManager mWifiConnectivityManager;
     private WifiNetworkSelector mWifiNS;
-    private ClientModeImpl mClientModeImpl;
     private WifiScanner mWifiScanner;
     private WifiConnectivityHelper mWifiConnectivityHelper;
     private ScanData mScanData;
@@ -189,15 +243,12 @@
     private WifiInfo mWifiInfo;
     private LocalLog mLocalLog;
     private LruConnectionTracker mLruConnectionTracker;
-    @Mock private WifiInjector mWifiInjector;
-    @Mock private NetworkScoreManager mNetworkScoreManager;
     @Mock private Clock mClock;
     @Mock private WifiLastResortWatchdog mWifiLastResortWatchdog;
     @Mock private OpenNetworkNotifier mOpenNetworkNotifier;
     @Mock private WifiMetrics mWifiMetrics;
-    @Mock private WifiNetworkScoreCache mScoreCache;
     @Mock private WifiNetworkSuggestionsManager mWifiNetworkSuggestionsManager;
-    @Mock private BssidBlocklistMonitor mBssidBlocklistMonitor;
+    @Mock private WifiBlocklistMonitor mWifiBlocklistMonitor;
     @Mock private WifiChannelUtilization mWifiChannelUtilization;
     @Mock private ScoringParams mScoringParams;
     @Mock private WifiScoreCard mWifiScoreCard;
@@ -207,27 +258,35 @@
     @Mock private PasspointConfiguration mPasspointConfiguration;
     @Mock private WifiConfiguration mSuggestionConfig;
     @Mock private WifiNetworkSuggestion mWifiNetworkSuggestion;
+    @Mock private IPowerManager mPowerManagerService;
+    @Mock private DeviceConfigFacade mDeviceConfigFacade;
+    @Mock private ActiveModeWarden mActiveModeWarden;
+    @Mock private ConcreteClientModeManager mPrimaryClientModeManager;
+    @Mock private ConcreteClientModeManager mSecondaryClientModeManager;
+    @Mock private WifiGlobals mWifiGlobals;
     @Mock WifiCandidates.Candidate mCandidate1;
     @Mock WifiCandidates.Candidate mCandidate2;
+    private WifiConfiguration mCandidateWifiConfig1;
+    private WifiConfiguration mCandidateWifiConfig2;
     private List<WifiCandidates.Candidate> mCandidateList;
-    @Captor ArgumentCaptor<ScanResult> mCandidateScanResultCaptor;
-    @Captor ArgumentCaptor<ArrayList<String>> mBssidBlacklistCaptor;
-    @Captor ArgumentCaptor<ArrayList<String>> mSsidWhitelistCaptor;
+    @Captor ArgumentCaptor<String> mCandidateBssidCaptor;
     @Captor ArgumentCaptor<WifiConfigManager.OnNetworkUpdateListener>
             mNetworkUpdateListenerCaptor;
     @Captor ArgumentCaptor<WifiNetworkSuggestionsManager.OnSuggestionUpdateListener>
             mSuggestionUpdateListenerCaptor;
+    @Captor ArgumentCaptor<ActiveModeWarden.ModeChangeCallback> mModeChangeCallbackCaptor;
+    @Captor ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor;
     private MockResources mResources;
-    private int mMinPacketRateActiveTraffic;
 
     private static final int CANDIDATE_NETWORK_ID = 0;
+    private static final int CANDIDATE_NETWORK_ID_2 = 2;
     private static final String CANDIDATE_SSID = "\"AnSsid\"";
     private static final String CANDIDATE_BSSID = "6c:f3:7f:ae:8c:f3";
     private static final String CANDIDATE_BSSID_2 = "6c:f3:7f:ae:8d:f3";
     private static final String INVALID_SCAN_RESULT_BSSID = "6c:f3:7f:ae:8c:f4";
     private static final int TEST_FREQUENCY = 2420;
     private static final long CURRENT_SYSTEM_TIME_MS = 1000;
-    private static final int MAX_BSSID_BLACKLIST_SIZE = 16;
+    private static final int MAX_BSSID_BLOCKLIST_SIZE = 16;
     private static final int[] VALID_CONNECTED_SINGLE_SCAN_SCHEDULE_SEC = {10, 30, 50};
     private static final int[] VALID_CONNECTED_SINGLE_SAVED_NETWORK_SCHEDULE_SEC = {15, 35, 55};
     private static final int[] VALID_DISCONNECTED_SINGLE_SCAN_SCHEDULE_SEC = {25, 40, 60};
@@ -247,15 +306,19 @@
     private static final String TEST_SSID = "SSID";
     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 String TEST_CONNECTED_BSSID = "6c:f3:7f:ae:8c:f1";
     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;
+    private static final int POWER_SAVE_SCAN_INTERVAL_MULTIPLIER = 2;
+    private static final int LOW_RSSI_NETWORK_RETRY_START_DELAY_SEC = 20;
+    private static final int LOW_RSSI_NETWORK_RETRY_MAX_DELAY_SEC = 80;
 
-    Context mockContext() {
-        Context context = mock(Context.class);
+    WifiContext mockContext() {
+        WifiContext context = mock(WifiContext.class);
 
         when(context.getResources()).thenReturn(mResources);
-        when(context.getSystemService(Context.ALARM_SERVICE)).thenReturn(
+        when(context.getSystemService(AlarmManager.class)).thenReturn(
                 mAlarmManager.getAlarmManager());
         when(context.getPackageManager()).thenReturn(mock(PackageManager.class));
 
@@ -265,7 +328,7 @@
     ScanData mockScanData() {
         ScanData scanData = mock(ScanData.class);
 
-        when(scanData.getBandScanned()).thenReturn(WifiScanner.WIFI_BAND_ALL);
+        when(scanData.getScannedBandsInternal()).thenReturn(WifiScanner.WIFI_BAND_ALL);
 
         return scanData;
     }
@@ -329,35 +392,39 @@
         WifiConnectivityHelper connectivityHelper = mock(WifiConnectivityHelper.class);
 
         when(connectivityHelper.isFirmwareRoamingSupported()).thenReturn(false);
-        when(connectivityHelper.getMaxNumBlacklistBssid()).thenReturn(MAX_BSSID_BLACKLIST_SIZE);
+        when(connectivityHelper.getMaxNumBlocklistBssid()).thenReturn(MAX_BSSID_BLOCKLIST_SIZE);
 
         return connectivityHelper;
     }
 
-    ClientModeImpl mockClientModeImpl() {
-        ClientModeImpl stateMachine = mock(ClientModeImpl.class);
-
-        when(stateMachine.isConnected()).thenReturn(false);
-        when(stateMachine.isDisconnected()).thenReturn(true);
-        when(stateMachine.isSupplicantTransientState()).thenReturn(false);
-
-        return stateMachine;
+    private void setupMockForClientModeManager(ConcreteClientModeManager cmm) {
+        when(cmm.getRole()).thenReturn(ActiveModeManager.ROLE_CLIENT_PRIMARY);
+        when(cmm.isConnected()).thenReturn(false);
+        when(cmm.isDisconnected()).thenReturn(true);
+        when(cmm.isSupplicantTransientState()).thenReturn(false);
     }
 
     WifiNetworkSelector mockWifiNetworkSelector() {
         WifiNetworkSelector ns = mock(WifiNetworkSelector.class);
 
         WifiConfiguration candidate = generateWifiConfig(
-                0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
+                0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null,
+                WifiConfigurationTestUtil.SECURITY_NONE);
         candidate.BSSID = ClientModeImpl.SUPPLICANT_BSSID_ANY;
         ScanResult candidateScanResult = new ScanResult();
         candidateScanResult.SSID = CANDIDATE_SSID;
         candidateScanResult.BSSID = CANDIDATE_BSSID;
         candidate.getNetworkSelectionStatus().setCandidate(candidateScanResult);
+        mCandidateWifiConfig1 = candidate;
+        mCandidateWifiConfig2 = new WifiConfiguration(candidate);
+        mCandidateWifiConfig2.networkId = CANDIDATE_NETWORK_ID_2;
 
         when(mWifiConfigManager.getConfiguredNetwork(CANDIDATE_NETWORK_ID)).thenReturn(candidate);
         MacAddress macAddress = MacAddress.fromString(CANDIDATE_BSSID);
-        WifiCandidates.Key key = new WifiCandidates.Key(mock(ScanResultMatchInfo.class),
+        ScanResultMatchInfo matchInfo = mock(ScanResultMatchInfo.class);
+        // Assume that this test use the default security params.
+        when(matchInfo.getDefaultSecurityParams()).thenReturn(candidate.getDefaultSecurityParams());
+        WifiCandidates.Key key = new WifiCandidates.Key(matchInfo,
                 macAddress, 0);
         when(mCandidate1.getKey()).thenReturn(key);
         when(mCandidate1.getScanRssi()).thenReturn(-40);
@@ -402,42 +469,609 @@
     }
 
     WifiConnectivityManager createConnectivityManager() {
-        return new WifiConnectivityManager(mContext,
-                mScoringParams,
-                mClientModeImpl, mWifiInjector, mWifiConfigManager, mWifiNetworkSuggestionsManager,
-                mWifiInfo, mWifiNS, mWifiConnectivityHelper,
+        WifiConnectivityManager wCm = new WifiConnectivityManager(mContext, mScoringParams,
+                mWifiConfigManager, mWifiNetworkSuggestionsManager,
+                mWifiNS, mWifiConnectivityHelper,
                 mWifiLastResortWatchdog, mOpenNetworkNotifier,
                 mWifiMetrics, new Handler(mLooper.getLooper()), mClock,
-                mLocalLog, mWifiScoreCard);
+                mLocalLog, mWifiScoreCard, mWifiBlocklistMonitor, mWifiChannelUtilization,
+                mPasspointManager, mDeviceConfigFacade, mActiveModeWarden, mWifiGlobals);
+        verify(mActiveModeWarden, atLeastOnce()).registerModeChangeCallback(
+                mModeChangeCallbackCaptor.capture());
+        verify(mContext, atLeastOnce()).registerReceiver(
+                mBroadcastReceiverCaptor.capture(), any(), any(), any());
+        verify(mWifiConfigManager, atLeastOnce()).addOnNetworkUpdateListener(
+                mNetworkUpdateListenerCaptor.capture());
+        verify(mWifiNetworkSuggestionsManager, atLeastOnce()).addOnSuggestionUpdateListener(
+                mSuggestionUpdateListenerCaptor.capture());
+        return wCm;
     }
 
     void setWifiStateConnected() {
+        setWifiStateConnected(TEST_CONNECTED_NETWORK_ID, TEST_CONNECTED_BSSID);
+    }
+
+    void setWifiStateConnected(int networkId, String bssid) {
         // Prep for setting WiFi to connected state
         WifiConfiguration connectedWifiConfiguration = new WifiConfiguration();
-        connectedWifiConfiguration.networkId = TEST_CONNECTED_NETWORK_ID;
-        when(mClientModeImpl.getCurrentWifiConfiguration()).thenReturn(connectedWifiConfiguration);
+        connectedWifiConfiguration.networkId = networkId;
+        when(mPrimaryClientModeManager.getConnectedWifiConfiguration())
+                .thenReturn(connectedWifiConfiguration);
+        when(mPrimaryClientModeManager.getConnectedBssid())
+                .thenReturn(bssid);
 
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_CONNECTED);
     }
 
     /**
+     * Don't connect to the candidate network if we're already connected to that network on the
+     * primary ClientModeManager.
+     */
+    @Test
+    public void alreadyConnectedOnPrimaryCmm_dontConnectAgain() {
+        when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
+        // Set screen to on
+        setScreenState(true);
+
+        WifiConfiguration config = new WifiConfiguration();
+        config.networkId = CANDIDATE_NETWORK_ID;
+        when(mPrimaryClientModeManager.getConnectingWifiConfiguration()).thenReturn(config);
+
+        // Set WiFi to disconnected state
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
+                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+
+        verify(mActiveModeWarden, never()).requestSecondaryTransientClientModeManager(
+                any(), any(), any(), any());
+        verify(mPrimaryClientModeManager, never()).startConnectToNetwork(
+                anyInt(), anyInt(), any());
+    }
+
+    /** Connect using the primary ClientModeManager if it's not connected to anything */
+    @Test
+    public void disconnectedOnPrimaryCmm_connectUsingPrimaryCmm() {
+        when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
+        // Set screen to on
+        setScreenState(true);
+
+        when(mPrimaryClientModeManager.getConnectedWifiConfiguration()).thenReturn(null);
+        when(mPrimaryClientModeManager.getConnectingWifiConfiguration()).thenReturn(null);
+
+        // Set WiFi to disconnected state
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
+                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+
+        verify(mPrimaryClientModeManager).startConnectToNetwork(
+                CANDIDATE_NETWORK_ID, Process.WIFI_UID, "any");
+        verify(mActiveModeWarden).stopAllClientModeManagersInRole(ROLE_CLIENT_SECONDARY_TRANSIENT);
+        verify(mActiveModeWarden, never()).requestSecondaryTransientClientModeManager(
+                any(), any(), any(), any());
+    }
+
+    /** Don't crash if allocated a null ClientModeManager. */
+    @Test
+    public void requestSecondaryTransientCmm_gotNullCmm() {
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ExternalClientModeManagerRequestListener listener,
+                    WorkSource requestorWs, String ssid, String bssid) {
+                listener.onAnswer(null);
+            }
+        }).when(mActiveModeWarden).requestSecondaryTransientClientModeManager(
+                any(), eq(ActiveModeWarden.INTERNAL_REQUESTOR_WS), any(), any());
+
+        // primary CMM already connected
+        WifiConfiguration config2 = new WifiConfiguration();
+        config2.networkId = CANDIDATE_NETWORK_ID_2;
+        when(mPrimaryClientModeManager.getConnectedWifiConfiguration())
+                .thenReturn(config2);
+
+        // Set screen to on
+        setScreenState(true);
+
+        // Set WiFi to disconnected state
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
+                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+
+        verify(mActiveModeWarden).requestSecondaryTransientClientModeManager(
+                any(),
+                eq(ActiveModeWarden.INTERNAL_REQUESTOR_WS),
+                eq(CANDIDATE_SSID),
+                eq(CANDIDATE_BSSID));
+        verify(mPrimaryClientModeManager, never()).startConnectToNetwork(
+                anyInt(), anyInt(), any());
+    }
+
+    /**
+     * Don't attempt to connect again if the allocated ClientModeManager is already connected to
+     * the desired network.
+     */
+    @Test
+    public void requestSecondaryTransientCmm_gotAlreadyConnectedCmm() {
+        when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
+
+        WifiConfiguration config = new WifiConfiguration();
+        config.networkId = CANDIDATE_NETWORK_ID;
+        ClientModeManager alreadyConnectedCmm = mock(ClientModeManager.class);
+        when(alreadyConnectedCmm.getConnectingWifiConfiguration()).thenReturn(config);
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ExternalClientModeManagerRequestListener listener,
+                    WorkSource requestorWs, String ssid, String bssid) {
+                listener.onAnswer(alreadyConnectedCmm);
+            }
+        }).when(mActiveModeWarden).requestSecondaryTransientClientModeManager(
+                any(), eq(ActiveModeWarden.INTERNAL_REQUESTOR_WS), any(), any());
+
+        // primary CMM already connected
+        WifiConfiguration config2 = new WifiConfiguration();
+        config2.networkId = CANDIDATE_NETWORK_ID_2;
+        when(mPrimaryClientModeManager.getConnectedWifiConfiguration())
+                .thenReturn(config2);
+
+        // Set screen to on
+        setScreenState(true);
+
+        // Set WiFi to disconnected state
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
+                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+
+        verify(mActiveModeWarden).requestSecondaryTransientClientModeManager(
+                any(),
+                eq(ActiveModeWarden.INTERNAL_REQUESTOR_WS),
+                eq(CANDIDATE_SSID),
+                eq(null));
+
+        // already connected, don't connect again
+        verify(alreadyConnectedCmm, never()).startConnectToNetwork(
+                anyInt(), anyInt(), any());
+    }
+
+    /**
+     * Verify MBB full flow.
+     */
+    @Test
+    public void connectWhenConnected_UsingMbb() {
+        when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
+
+        ClientModeManager mbbCmm = mock(ClientModeManager.class);
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ExternalClientModeManagerRequestListener listener,
+                    WorkSource requestorWs, String ssid, String bssid) {
+                listener.onAnswer(mbbCmm);
+            }
+        }).when(mActiveModeWarden).requestSecondaryTransientClientModeManager(
+                any(), eq(ActiveModeWarden.INTERNAL_REQUESTOR_WS), any(), any());
+
+        // primary CMM already connected
+        when(mPrimaryClientModeManager.getConnectedWifiConfiguration())
+                .thenReturn(mCandidateWifiConfig2);
+
+        // Set screen to on
+        setScreenState(true);
+
+        // Set WiFi to connected state
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
+                WifiConnectivityManager.WIFI_STATE_CONNECTED);
+
+        // Request secondary STA and connect using it.
+        verify(mActiveModeWarden).requestSecondaryTransientClientModeManager(
+                any(),
+                eq(ActiveModeWarden.INTERNAL_REQUESTOR_WS),
+                eq(CANDIDATE_SSID),
+                eq(null));
+        verify(mbbCmm).startConnectToNetwork(eq(CANDIDATE_NETWORK_ID), anyInt(), any());
+    }
+
+    /**
+     * Fallback to single STA behavior when both networks have MAC randomization disabled.
+     */
+    @Test
+    public void connectWhenConnected_UsingBbmIfBothNetworksHaveMacRandomizationDisabled() {
+        when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
+
+        ClientModeManager mbbCmm = mock(ClientModeManager.class);
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ExternalClientModeManagerRequestListener listener,
+                    WorkSource requestorWs, String ssid, String bssid) {
+                listener.onAnswer(mbbCmm);
+            }
+        }).when(mActiveModeWarden).requestSecondaryTransientClientModeManager(
+                any(), eq(ActiveModeWarden.INTERNAL_REQUESTOR_WS), any(), any());
+
+        // Turn off MAC randomization on both networks.
+        mCandidateWifiConfig1.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_NONE;
+        mCandidateWifiConfig2.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_NONE;
+
+        // primary CMM already connected
+        when(mPrimaryClientModeManager.getConnectedWifiConfiguration())
+                .thenReturn(mCandidateWifiConfig2);
+
+        // Set screen to on
+        setScreenState(true);
+
+        // Set WiFi to connected state
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
+                WifiConnectivityManager.WIFI_STATE_CONNECTED);
+
+        // Don't request secondary STA, fallback to primary STA.
+        verify(mActiveModeWarden, never()).requestSecondaryTransientClientModeManager(
+                any(), any(), any(), any());
+        verify(mbbCmm, never()).startConnectToNetwork(anyInt(), anyInt(), any());
+        verify(mPrimaryClientModeManager).startConnectToNetwork(
+                eq(CANDIDATE_NETWORK_ID), anyInt(), any());
+    }
+
+    /**
+     * Setup all the mocks for the positive case, individual negative test cases below override
+     * specific params.
+     */
+    private void setupMocksForSecondaryLongLivedTests() {
+        when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
+        when(mCandidate1.isOemPaid()).thenReturn(true);
+        when(mCandidate1.isOemPrivate()).thenReturn(true);
+        mCandidateWifiConfig1.oemPaid = true;
+        mCandidateWifiConfig1.oemPrivate = true;
+        when(mWifiNS.selectNetwork(argThat(
+                candidates -> (candidates != null && candidates.size() == 1
+                        && (candidates.get(0).isOemPaid() || candidates.get(0).isOemPrivate()))
+        ))).thenReturn(mCandidateWifiConfig1);
+        when(mActiveModeWarden.isStaStaConcurrencySupportedForRestrictedConnections())
+                .thenReturn(true);
+        when(mActiveModeWarden.canRequestMoreClientModeManagersInRole(
+                any(), eq(ROLE_CLIENT_SECONDARY_LONG_LIVED))).thenReturn(true);
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ExternalClientModeManagerRequestListener listener,
+                    WorkSource requestorWs, String ssid, String bssid) {
+                listener.onAnswer(mSecondaryClientModeManager);
+            }
+        }).when(mActiveModeWarden).requestSecondaryLongLivedClientModeManager(
+                any(), any(), any(), any());
+        when(mSecondaryClientModeManager.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_LONG_LIVED);
+    }
+
+    @Test
+    public void secondaryLongLived_noOemPaidOrOemPrivateConnectionAllowed() {
+        setupMocksForSecondaryLongLivedTests();
+
+        // Set screen to on
+        setScreenState(true);
+
+        // OEM paid/OEM private connection disallowed.
+        mWifiConnectivityManager.setOemPaidConnectionAllowed(false, null);
+        mWifiConnectivityManager.setOemPrivateConnectionAllowed(false, null);
+
+        // Set WiFi to disconnected state
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
+                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+
+        verify(mPrimaryClientModeManager).startConnectToNetwork(
+                CANDIDATE_NETWORK_ID, Process.WIFI_UID, "any");
+        verify(mActiveModeWarden, never()).requestSecondaryLongLivedClientModeManager(
+                any(), any(), any(), any());
+    }
+
+    @Test
+    public void secondaryLongLived_oemPaidConnectionAllowedWithOemPrivateCandidate() {
+        setupMocksForSecondaryLongLivedTests();
+
+        // Set screen to on
+        setScreenState(true);
+
+        // OEM paid connection allowed.
+        mWifiConnectivityManager.setOemPaidConnectionAllowed(true, new WorkSource());
+
+        // Mark the candidate oem private only
+        when(mCandidate1.isOemPaid()).thenReturn(false);
+        when(mCandidate1.isOemPrivate()).thenReturn(true);
+        mCandidateWifiConfig1.oemPaid = false;
+        mCandidateWifiConfig1.oemPrivate = true;
+
+        // Set WiFi to disconnected state
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
+                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+
+        verify(mPrimaryClientModeManager).startConnectToNetwork(
+                CANDIDATE_NETWORK_ID, Process.WIFI_UID, "any");
+        verify(mActiveModeWarden, never()).requestSecondaryLongLivedClientModeManager(
+                any(), any(), any(), any());
+    }
+
+    @Test
+    public void secondaryLongLived_oemPrivateConnectionAllowedWithOemPaidCandidate() {
+        setupMocksForSecondaryLongLivedTests();
+
+        // Set screen to on
+        setScreenState(true);
+
+        // OEM private connection allowed.
+        mWifiConnectivityManager.setOemPrivateConnectionAllowed(true, new WorkSource());
+
+        // Mark the candidate oem paid only
+        when(mCandidate1.isOemPaid()).thenReturn(true);
+        when(mCandidate1.isOemPrivate()).thenReturn(false);
+        mCandidateWifiConfig1.oemPaid = true;
+        mCandidateWifiConfig1.oemPrivate = false;
+
+        // Set WiFi to disconnected state
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
+                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+
+        verify(mPrimaryClientModeManager).startConnectToNetwork(
+                CANDIDATE_NETWORK_ID, Process.WIFI_UID, "any");
+        verify(mActiveModeWarden, never()).requestSecondaryLongLivedClientModeManager(
+                any(), any(), any(), any());
+    }
+
+    @Test
+    public void secondaryLongLived_noSecondaryStaSupport() {
+        setupMocksForSecondaryLongLivedTests();
+
+        // Set screen to on
+        setScreenState(true);
+
+        // OEM paid connection allowed.
+        mWifiConnectivityManager.setOemPaidConnectionAllowed(true, new WorkSource());
+
+        // STA + STA is not supported.
+        when(mActiveModeWarden.isStaStaConcurrencySupportedForRestrictedConnections())
+                .thenReturn(false);
+
+        // Set WiFi to disconnected state
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
+                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+
+        verify(mPrimaryClientModeManager).startConnectToNetwork(
+                CANDIDATE_NETWORK_ID, Process.WIFI_UID, "any");
+        verify(mActiveModeWarden, never()).requestSecondaryLongLivedClientModeManager(
+                any(), any(), any(), any());
+    }
+
+    @Test
+    public void secondaryLongLived_noSecondaryCandidateSelected() {
+        setupMocksForSecondaryLongLivedTests();
+
+        // Set screen to on
+        setScreenState(true);
+
+        // OEM paid connection allowed.
+        mWifiConnectivityManager.setOemPaidConnectionAllowed(true, new WorkSource());
+
+        // Network selection does not select a secondary candidate.
+        when(mWifiNS.selectNetwork(argThat(
+                candidates -> (candidates != null && candidates.size() == 1
+                        && (candidates.get(0).isOemPaid() || candidates.get(0).isOemPrivate()))
+        ))).thenReturn(null) // first for secondary returns null.
+                .thenReturn(mCandidateWifiConfig1); // second for primary returns something.
+
+        // Set WiFi to disconnected state
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
+                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+
+        verify(mPrimaryClientModeManager).startConnectToNetwork(
+                CANDIDATE_NETWORK_ID, Process.WIFI_UID, "any");
+        verify(mActiveModeWarden, never()).requestSecondaryLongLivedClientModeManager(
+                any(), any(), any(), any());
+    }
+
+    @Test
+    public void secondaryLongLived_noSecondaryStaAvailable() {
+        setupMocksForSecondaryLongLivedTests();
+
+        // Set screen to on
+        setScreenState(true);
+
+        // OEM paid connection allowed.
+        mWifiConnectivityManager.setOemPaidConnectionAllowed(true, new WorkSource());
+
+        // STA + STA is supported, but not available.
+        when(mActiveModeWarden.canRequestMoreClientModeManagersInRole(
+                any(), eq(ROLE_CLIENT_SECONDARY_LONG_LIVED))).thenReturn(false);
+
+        // Set WiFi to disconnected state
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
+                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+
+        verify(mPrimaryClientModeManager).startConnectToNetwork(
+                CANDIDATE_NETWORK_ID, Process.WIFI_UID, "any");
+        verify(mActiveModeWarden, never()).requestSecondaryLongLivedClientModeManager(
+                any(), any(), any(), any());
+    }
+
+    @Test
+    public void secondaryLongLived_secondaryStaRequestReturnsNull() {
+        setupMocksForSecondaryLongLivedTests();
+
+        // Set screen to on
+        setScreenState(true);
+
+        // OEM paid connection allowed.
+        mWifiConnectivityManager.setOemPaidConnectionAllowed(true, new WorkSource());
+
+        // STA + STA is supported, but secondary STA request returns null
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ExternalClientModeManagerRequestListener listener,
+                    WorkSource requestorWs, String ssid, String bssid) {
+                listener.onAnswer(null);
+            }
+        }).when(mActiveModeWarden).requestSecondaryLongLivedClientModeManager(
+                any(), any(), any(), any());
+
+        // Set WiFi to disconnected state
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
+                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+
+        // No connection triggered (even on primary since wifi is off).
+        verify(mPrimaryClientModeManager, never()).startConnectToNetwork(
+                CANDIDATE_NETWORK_ID, Process.WIFI_UID, "any");
+        verify(mActiveModeWarden).requestSecondaryLongLivedClientModeManager(
+                any(), any(), any(), any());
+    }
+
+    @Test
+    public void secondaryLongLived_secondaryStaRequestReturnsPrimary() {
+        setupMocksForSecondaryLongLivedTests();
+
+        // Set screen to on
+        setScreenState(true);
+
+        // OEM paid connection allowed.
+        mWifiConnectivityManager.setOemPaidConnectionAllowed(true, new WorkSource());
+
+        // STA + STA is supported, but secondary STA request returns null
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ExternalClientModeManagerRequestListener listener,
+                    WorkSource requestorWs, String ssid, String bssid) {
+                listener.onAnswer(mPrimaryClientModeManager);
+            }
+        }).when(mActiveModeWarden).requestSecondaryLongLivedClientModeManager(
+                any(), any(), any(), any());
+
+        // Set WiFi to disconnected state
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
+                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+
+        // connection triggered on primary
+        verify(mPrimaryClientModeManager).startConnectToNetwork(
+                CANDIDATE_NETWORK_ID, Process.WIFI_UID, "any");
+        verify(mActiveModeWarden).requestSecondaryLongLivedClientModeManager(
+                any(), any(), any(), any());
+    }
+
+    @Test
+    public void secondaryLongLived_secondaryStaRequestSucceedsWithOemPaidConnectionAllowed() {
+        setupMocksForSecondaryLongLivedTests();
+
+        // Set screen to on
+        setScreenState(true);
+
+        // OEM paid connection allowed.
+        mWifiConnectivityManager.setOemPaidConnectionAllowed(true, new WorkSource());
+
+        // Mark the candidate oem paid only
+        when(mCandidate1.isOemPaid()).thenReturn(true);
+        when(mCandidate1.isOemPrivate()).thenReturn(false);
+        mCandidateWifiConfig1.oemPaid = true;
+        mCandidateWifiConfig1.oemPrivate = false;
+
+        // Set WiFi to disconnected state
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
+                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+
+        // connection triggered on secondary
+        verify(mPrimaryClientModeManager, never()).startConnectToNetwork(
+                CANDIDATE_NETWORK_ID, Process.WIFI_UID, "any");
+        verify(mSecondaryClientModeManager).startConnectToNetwork(
+                CANDIDATE_NETWORK_ID, Process.WIFI_UID, "any");
+        verify(mActiveModeWarden).requestSecondaryLongLivedClientModeManager(
+                any(), any(), any(), any());
+    }
+
+    @Test
+    public void secondaryLongLived_secondaryStaRequestSucceedsWithOemPrivateConnectionAllowed() {
+        setupMocksForSecondaryLongLivedTests();
+
+        // Set screen to on
+        setScreenState(true);
+
+        // OEM paid connection allowed.
+        mWifiConnectivityManager.setOemPrivateConnectionAllowed(true, new WorkSource());
+
+        // Mark the candidate oem private only
+        when(mCandidate1.isOemPaid()).thenReturn(false);
+        when(mCandidate1.isOemPrivate()).thenReturn(true);
+        mCandidateWifiConfig1.oemPaid = false;
+        mCandidateWifiConfig1.oemPrivate = true;
+
+        // Set WiFi to disconnected state
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
+                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+
+        // connection triggered on secondary
+        verify(mPrimaryClientModeManager, never()).startConnectToNetwork(
+                CANDIDATE_NETWORK_ID, Process.WIFI_UID, "any");
+        verify(mSecondaryClientModeManager).startConnectToNetwork(
+                CANDIDATE_NETWORK_ID, Process.WIFI_UID, "any");
+        verify(mActiveModeWarden).requestSecondaryLongLivedClientModeManager(
+                any(), any(), any(), any());
+    }
+
+    @Test
+    public void secondaryLongLived_secondaryStaRequestSucceedsAlongWithPrimary() {
+        setupMocksForSecondaryLongLivedTests();
+
+        // 2 candidates - 1 oem paid, other regular.
+        // Mark the first candidate oem private only
+        when(mCandidate1.isOemPaid()).thenReturn(false);
+        when(mCandidate1.isOemPrivate()).thenReturn(true);
+        mCandidateWifiConfig1.oemPaid = false;
+        mCandidateWifiConfig1.oemPrivate = true;
+
+        // Add the second regular candidate.
+        mCandidateList.add(mCandidate2);
+
+        // Set screen to on
+        setScreenState(true);
+
+        // OEM paid connection allowed.
+        mWifiConnectivityManager.setOemPrivateConnectionAllowed(true, new WorkSource());
+
+        // Network selection setup for primary.
+        when(mWifiNS.selectNetwork(argThat(
+                candidates -> (candidates != null && candidates.size() == 1
+                        // not oem paid or oem private.
+                        && !(candidates.get(0).isOemPaid() || candidates.get(0).isOemPrivate()))
+        ))).thenReturn(mCandidateWifiConfig2);
+
+        // Set WiFi to disconnected state
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
+                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+
+        // connection triggered on primary & secondary
+        verify(mPrimaryClientModeManager).startConnectToNetwork(
+                CANDIDATE_NETWORK_ID_2, Process.WIFI_UID, "any");
+        verify(mSecondaryClientModeManager).startConnectToNetwork(
+                CANDIDATE_NETWORK_ID, Process.WIFI_UID, "any");
+        verify(mActiveModeWarden).requestSecondaryLongLivedClientModeManager(
+                any(), any(), any(), any());
+    }
+
+    /**
      *  Wifi enters disconnected state while screen is on.
      *
      * Expected behavior: WifiConnectivityManager calls
-     * ClientModeImpl.startConnectToNetwork() with the
+     * ClientModeManager.startConnectToNetwork() with the
      * expected candidate network ID and BSSID.
      */
     @Test
     public void enterWifiDisconnectedStateWhenScreenOn() {
         // Set screen to on
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(true);
 
         // Set WiFi to disconnected state
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
 
-        verify(mClientModeImpl).startConnectToNetwork(
+        verify(mPrimaryClientModeManager).startConnectToNetwork(
                 CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
     }
 
@@ -445,20 +1079,21 @@
      *  Wifi enters connected state while screen is on.
      *
      * Expected behavior: WifiConnectivityManager calls
-     * ClientModeImpl.startConnectToNetwork() with the
+     * ClientModeManager.startConnectToNetwork() with the
      * expected candidate network ID and BSSID.
      */
     @Test
     public void enterWifiConnectedStateWhenScreenOn() {
         // Set screen to on
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(true);
 
         // Set WiFi to connected state
         setWifiStateConnected();
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_CONNECTED);
 
-        verify(mClientModeImpl).startConnectToNetwork(
+        verify(mPrimaryClientModeManager).startConnectToNetwork(
                 CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
     }
 
@@ -466,19 +1101,20 @@
      *  Screen turned on while WiFi in disconnected state.
      *
      * Expected behavior: WifiConnectivityManager calls
-     * ClientModeImpl.startConnectToNetwork() with the
+     * ClientModeManager.startConnectToNetwork() with the
      * expected candidate network ID and BSSID.
      */
     @Test
     public void turnScreenOnWhenWifiInDisconnectedState() {
         // Set WiFi to disconnected state
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
 
         // Set screen to on
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(true);
 
-        verify(mClientModeImpl, atLeastOnce()).startConnectToNetwork(
+        verify(mPrimaryClientModeManager, atLeastOnce()).startConnectToNetwork(
                 CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
     }
 
@@ -486,7 +1122,7 @@
      *  Screen turned on while WiFi in connected state.
      *
      * Expected behavior: WifiConnectivityManager calls
-     * ClientModeImpl.startConnectToNetwork() with the
+     * ClientModeManager.startConnectToNetwork() with the
      * expected candidate network ID and BSSID.
      */
     @Test
@@ -495,9 +1131,9 @@
         setWifiStateConnected();
 
         // Set screen to on
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(true);
 
-        verify(mClientModeImpl, atLeastOnce()).startConnectToNetwork(
+        verify(mPrimaryClientModeManager, atLeastOnce()).startConnectToNetwork(
                 CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
     }
 
@@ -506,7 +1142,7 @@
      *  auto roaming is disabled.
      *
      * Expected behavior: WifiConnectivityManager doesn't invoke
-     * ClientModeImpl.startConnectToNetwork() because roaming
+     * ClientModeManager.startConnectToNetwork() because roaming
      * is turned off.
      */
     @Test
@@ -521,16 +1157,16 @@
         setWifiStateConnected();
 
         // Set screen to on
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(true);
 
-        verify(mClientModeImpl, times(0)).startConnectToNetwork(
+        verify(mPrimaryClientModeManager, times(0)).startConnectToNetwork(
                 CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
     }
 
     /**
      * Multiple back to back connection attempts within the rate interval should be rate limited.
      *
-     * Expected behavior: WifiConnectivityManager calls ClientModeImpl.startConnectToNetwork()
+     * Expected behavior: WifiConnectivityManager calls ClientModeManager.startConnectToNetwork()
      * with the expected candidate network ID and BSSID for only the expected number of times within
      * the given interval.
      */
@@ -541,7 +1177,7 @@
         int numAttempts = 0;
         int connectionAttemptIntervals = timeInterval / maxAttemptRate;
 
-        mWifiConnectivityManager.handleScreenStateChanged(false);
+        setScreenState(false);
 
         // First attempt the max rate number of connections within the rate interval.
         long currentTimeStamp = 0;
@@ -550,6 +1186,7 @@
             when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
             // Set WiFi to disconnected state to trigger PNO scan
             mWifiConnectivityManager.handleConnectionStateChanged(
+                    mPrimaryClientModeManager,
                     WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
             numAttempts++;
         }
@@ -558,10 +1195,11 @@
         when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
         // Set WiFi to disconnected state to trigger PNO scan
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
 
         // Verify that we attempt to connect upto the rate.
-        verify(mClientModeImpl, times(numAttempts)).startConnectToNetwork(
+        verify(mPrimaryClientModeManager, times(numAttempts)).startConnectToNetwork(
                 CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
     }
 
@@ -569,7 +1207,7 @@
      * Multiple back to back connection attempts outside the rate interval should not be rate
      * limited.
      *
-     * Expected behavior: WifiConnectivityManager calls ClientModeImpl.startConnectToNetwork()
+     * Expected behavior: WifiConnectivityManager calls ClientModeManager.startConnectToNetwork()
      * with the expected candidate network ID and BSSID for only the expected number of times within
      * the given interval.
      */
@@ -580,7 +1218,7 @@
         int numAttempts = 0;
         int connectionAttemptIntervals = timeInterval / maxAttemptRate;
 
-        mWifiConnectivityManager.handleScreenStateChanged(false);
+        setScreenState(false);
 
         // First attempt the max rate number of connections within the rate interval.
         long currentTimeStamp = 0;
@@ -589,6 +1227,7 @@
             when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
             // Set WiFi to disconnected state to trigger PNO scan
             mWifiConnectivityManager.handleConnectionStateChanged(
+                    mPrimaryClientModeManager,
                     WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
             numAttempts++;
         }
@@ -598,11 +1237,12 @@
                 currentTimeStamp + connectionAttemptIntervals * 2);
         // Set WiFi to disconnected state to trigger PNO scan
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
         numAttempts++;
 
         // Verify that all the connection attempts went through
-        verify(mClientModeImpl, times(numAttempts)).startConnectToNetwork(
+        verify(mPrimaryClientModeManager, times(numAttempts)).startConnectToNetwork(
                 CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
     }
 
@@ -610,7 +1250,7 @@
      * Multiple back to back connection attempts after a force connectivity scan should not be rate
      * limited.
      *
-     * Expected behavior: WifiConnectivityManager calls ClientModeImpl.startConnectToNetwork()
+     * Expected behavior: WifiConnectivityManager calls ClientModeManager.startConnectToNetwork()
      * with the expected candidate network ID and BSSID for only the expected number of times within
      * the given interval.
      */
@@ -621,7 +1261,7 @@
         int numAttempts = 0;
         int connectionAttemptIntervals = timeInterval / maxAttemptRate;
 
-        mWifiConnectivityManager.handleScreenStateChanged(false);
+        setScreenState(false);
 
         // First attempt the max rate number of connections within the rate interval.
         long currentTimeStamp = 0;
@@ -630,6 +1270,7 @@
             when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
             // Set WiFi to disconnected state to trigger PNO scan
             mWifiConnectivityManager.handleConnectionStateChanged(
+                    mPrimaryClientModeManager,
                     WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
             numAttempts++;
         }
@@ -641,19 +1282,20 @@
             when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
             // Set WiFi to disconnected state to trigger PNO scan
             mWifiConnectivityManager.handleConnectionStateChanged(
+                    mPrimaryClientModeManager,
                     WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
             numAttempts++;
         }
 
         // Verify that all the connection attempts went through
-        verify(mClientModeImpl, times(numAttempts)).startConnectToNetwork(
+        verify(mPrimaryClientModeManager, 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()
+     * Expected behavior: WifiConnectivityManager calls ClientModeManager.startConnectToNetwork()
      * with the expected candidate network ID and BSSID for only the expected number of times within
      * the given interval.
      */
@@ -664,7 +1306,7 @@
         int numAttempts = 0;
         int connectionAttemptIntervals = timeInterval / maxAttemptRate;
 
-        mWifiConnectivityManager.handleScreenStateChanged(false);
+        setScreenState(false);
 
         // First attempt the max rate number of connections within the rate interval.
         long currentTimeStamp = 0;
@@ -673,11 +1315,11 @@
             when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
             // Set WiFi to disconnected state to trigger PNO scan
             mWifiConnectivityManager.handleConnectionStateChanged(
+                    mPrimaryClientModeManager,
                     WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
             numAttempts++;
         }
 
-        mWifiConnectivityManager.setUserConnectChoice(CANDIDATE_NETWORK_ID);
         mWifiConnectivityManager.prepareForForcedConnection(CANDIDATE_NETWORK_ID);
 
         for (int attempt = 0; attempt < maxAttemptRate; attempt++) {
@@ -685,12 +1327,59 @@
             when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
             // Set WiFi to disconnected state to trigger PNO scan
             mWifiConnectivityManager.handleConnectionStateChanged(
+                    mPrimaryClientModeManager,
                     WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
             numAttempts++;
         }
 
         // Verify that all the connection attempts went through
-        verify(mClientModeImpl, times(numAttempts)).startConnectToNetwork(
+        verify(mPrimaryClientModeManager, times(numAttempts)).startConnectToNetwork(
+                CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
+    }
+
+    /**
+     * Multiple back to back connection attempts after a wifi toggle should not be rate limited.
+     *
+     * Expected behavior: WifiConnectivityManager calls ClientModeManager.startConnectToNetwork()
+     * with the expected candidate network ID and BSSID for only the expected number of times within
+     * the given interval.
+     */
+    @Test
+    public void connectionAttemptNotRateLimitedWhenScreenOffAfterWifiToggle() {
+        int maxAttemptRate = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_RATE;
+        int timeInterval = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS;
+        int numAttempts = 0;
+        int connectionAttemptIntervals = timeInterval / maxAttemptRate;
+
+        setScreenState(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(
+                    mPrimaryClientModeManager,
+                    WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+            numAttempts++;
+        }
+
+        setWifiEnabled(false);
+        setWifiEnabled(true);
+
+        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(
+                    mPrimaryClientModeManager,
+                    WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+            numAttempts++;
+        }
+
+        // Verify that all the connection attempts went through
+        verify(mPrimaryClientModeManager, times(numAttempts)).startConnectToNetwork(
                 CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
     }
 
@@ -699,30 +1388,40 @@
      *
      * Expected behavior: WifiConnectivityManager doubles the low RSSI
      * network retry delay value after QNS skips the PNO scan results
-     * because of their low RSSI values.
+     * because of their low RSSI values and reaches max after three scans
      */
     @Test
     public void pnoRetryForLowRssiNetwork() {
         when(mWifiNS.selectNetwork(any())).thenReturn(null);
 
         // Set screen to off
-        mWifiConnectivityManager.handleScreenStateChanged(false);
-
-        // Get the current retry delay value
-        int lowRssiNetworkRetryDelayStartValue = mWifiConnectivityManager
-                .getLowRssiNetworkRetryDelay();
+        setScreenState(false);
 
         // Set WiFi to disconnected state to trigger PNO scan
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
 
+        verify(mWifiMetrics).noteFirstNetworkSelectionAfterBoot(false);
+
         // Get the retry delay value after QNS didn't select a
-        // network candicate from the PNO scan results.
-        int lowRssiNetworkRetryDelayAfterPnoValue = mWifiConnectivityManager
+        // network candidate from the PNO scan results.
+        int lowRssiNetworkRetryDelayAfterOnePnoMs = mWifiConnectivityManager
                 .getLowRssiNetworkRetryDelay();
 
-        assertEquals(lowRssiNetworkRetryDelayStartValue * 2,
-                lowRssiNetworkRetryDelayAfterPnoValue);
+        assertEquals(LOW_RSSI_NETWORK_RETRY_START_DELAY_SEC * 2000,
+                lowRssiNetworkRetryDelayAfterOnePnoMs);
+
+        // Set WiFi to disconnected state to trigger two more PNO scans
+        for (int i = 0; i < 2; i++) {
+            mWifiConnectivityManager.handleConnectionStateChanged(
+                    mPrimaryClientModeManager,
+                    WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+        }
+        int lowRssiNetworkRetryDelayAfterThreePnoMs = mWifiConnectivityManager
+                .getLowRssiNetworkRetryDelay();
+        assertEquals(LOW_RSSI_NETWORK_RETRY_MAX_DELAY_SEC * 1000,
+                lowRssiNetworkRetryDelayAfterThreePnoMs);
     }
 
     /**
@@ -734,10 +1433,11 @@
     @Test
     public void watchdogBitePnoBadIncrementsMetrics() {
         // Set screen to off
-        mWifiConnectivityManager.handleScreenStateChanged(false);
+        setScreenState(false);
 
         // Set WiFi to disconnected state to trigger PNO scan
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
 
         // Now fire the watchdog alarm and verify the metrics were incremented.
@@ -760,10 +1460,11 @@
         when(mWifiNS.selectNetwork(any())).thenReturn(null);
 
         // Set screen to off
-        mWifiConnectivityManager.handleScreenStateChanged(false);
+        setScreenState(false);
 
         // Set WiFi to disconnected state to trigger PNO scan
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
 
         // Now fire the watchdog alarm and verify the metrics were incremented.
@@ -789,34 +1490,37 @@
 
         // Set WiFi to disconnected state to trigger scan
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
         mLooper.dispatchAll();
 
         // Verify there is no connection due to currently having no cached candidates.
-        verify(mClientModeImpl, never()).startConnectToNetwork(
+        verify(mPrimaryClientModeManager, never()).startConnectToNetwork(
                 CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
 
         // Move time forward but do not cross HIGH_MVMT_SCAN_DELAY_MS yet.
         when(mClock.getElapsedSinceBootMillis()).thenReturn(HIGH_MVMT_SCAN_DELAY_MS - 1L);
         // Set WiFi to disconnected state to trigger scan
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
         mLooper.dispatchAll();
 
         // Verify we still don't connect because not enough time have passed since the candidates
         // were cached.
-        verify(mClientModeImpl, never()).startConnectToNetwork(
+        verify(mPrimaryClientModeManager, never()).startConnectToNetwork(
                 CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
 
         // Move time past HIGH_MVMT_SCAN_DELAY_MS.
         when(mClock.getElapsedSinceBootMillis()).thenReturn((long) HIGH_MVMT_SCAN_DELAY_MS);
         // Set WiFi to disconnected state to trigger scan
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
         mLooper.dispatchAll();
 
         // Verify a candidate if found this time.
-        verify(mClientModeImpl).startConnectToNetwork(
+        verify(mPrimaryClientModeManager).startConnectToNetwork(
                 CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
         verify(mWifiMetrics, times(2)).incrementNumHighMovementConnectionSkipped();
         verify(mWifiMetrics).incrementNumHighMovementConnectionStarted();
@@ -837,10 +1541,11 @@
 
         // Set WiFi to disconnected state to trigger scan
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
         mLooper.dispatchAll();
         // Verify there is no connection due to currently having no cached candidates.
-        verify(mClientModeImpl, never()).startConnectToNetwork(
+        verify(mPrimaryClientModeManager, never()).startConnectToNetwork(
                 CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
 
         // Move time forward and verify that a delayed partial scan is scheduled.
@@ -869,7 +1574,7 @@
         // Setup WifiNetworkSelector to return 2 valid candidates from scan results
         MacAddress macAddress = MacAddress.fromString(CANDIDATE_BSSID_2);
         WifiCandidates.Key key = new WifiCandidates.Key(mock(ScanResultMatchInfo.class),
-                macAddress, 0);
+                macAddress, 0, WifiConfiguration.SECURITY_TYPE_OPEN);
         WifiCandidates.Candidate otherCandidate = mock(WifiCandidates.Candidate.class);
         when(otherCandidate.getKey()).thenReturn(key);
         List<WifiCandidates.Candidate> candidateList = new ArrayList<>();
@@ -880,33 +1585,38 @@
 
         // Set WiFi to disconnected state to trigger scan
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
         mLooper.dispatchAll();
         // Verify a connection starting
         verify(mWifiNS).selectNetwork((List<WifiCandidates.Candidate>)
                 argThat(new WifiCandidatesListSizeMatcher(2)));
-        verify(mClientModeImpl).startConnectToNetwork(anyInt(), anyInt(), any());
+        verify(mPrimaryClientModeManager).startConnectToNetwork(anyInt(), anyInt(), any());
 
         // Simulate the connection failing
         mWifiConnectivityManager.handleConnectionAttemptEnded(
+                mPrimaryClientModeManager,
                 WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_REJECTION, CANDIDATE_BSSID,
                 CANDIDATE_SSID);
         // Verify the failed BSSID is added to blocklist
-        verify(mBssidBlocklistMonitor).blockBssidForDurationMs(eq(CANDIDATE_BSSID),
+        verify(mWifiBlocklistMonitor).blockBssidForDurationMs(eq(CANDIDATE_BSSID),
                 eq(CANDIDATE_SSID), anyLong(), anyInt(), anyInt());
         // Verify another connection starting
         verify(mWifiNS).selectNetwork((List<WifiCandidates.Candidate>)
                 argThat(new WifiCandidatesListSizeMatcher(1)));
-        verify(mClientModeImpl, times(2)).startConnectToNetwork(anyInt(), anyInt(), any());
+        verify(mPrimaryClientModeManager, times(2)).startConnectToNetwork(
+                anyInt(), anyInt(), any());
 
         // Simulate the second connection also failing
         mWifiConnectivityManager.handleConnectionAttemptEnded(
+                mPrimaryClientModeManager,
                 WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_REJECTION, CANDIDATE_BSSID_2,
                 CANDIDATE_SSID);
         // Verify there are no more connections
         verify(mWifiNS).selectNetwork((List<WifiCandidates.Candidate>)
                 argThat(new WifiCandidatesListSizeMatcher(0)));
-        verify(mClientModeImpl, times(2)).startConnectToNetwork(anyInt(), anyInt(), any());
+        verify(mPrimaryClientModeManager, times(2)).startConnectToNetwork(
+                anyInt(), anyInt(), any());
     }
 
     private class WifiCandidatesListSizeMatcher implements
@@ -930,7 +1640,7 @@
         when(mClock.getElapsedSinceBootMillis()).thenReturn(0L);
         MacAddress macAddress = MacAddress.fromString(CANDIDATE_BSSID_2);
         WifiCandidates.Key key = new WifiCandidates.Key(mock(ScanResultMatchInfo.class),
-                macAddress, 0);
+                macAddress, 0, WifiConfiguration.SECURITY_TYPE_OPEN);
         WifiCandidates.Candidate otherCandidate = mock(WifiCandidates.Candidate.class);
         when(otherCandidate.getKey()).thenReturn(key);
         List<WifiCandidates.Candidate> candidateList = new ArrayList<>();
@@ -941,20 +1651,71 @@
 
         // Set WiFi to disconnected state to trigger scan
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
         mLooper.dispatchAll();
         // Verify a connection starting
         verify(mWifiNS).selectNetwork((List<WifiCandidates.Candidate>)
                 argThat(new WifiCandidatesListSizeMatcher(2)));
-        verify(mClientModeImpl).startConnectToNetwork(anyInt(), anyInt(), any());
+        verify(mPrimaryClientModeManager).startConnectToNetwork(anyInt(), anyInt(), any());
 
         // Simulate the connection failing after the cache timeout period.
         when(mClock.getElapsedSinceBootMillis()).thenReturn(TEMP_BSSID_BLOCK_DURATION_MS + 1L);
         mWifiConnectivityManager.handleConnectionAttemptEnded(
+                mPrimaryClientModeManager,
                 WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_REJECTION, CANDIDATE_BSSID,
                 CANDIDATE_SSID);
         // verify there are no additional connections.
-        verify(mClientModeImpl).startConnectToNetwork(anyInt(), anyInt(), any());
+        verify(mPrimaryClientModeManager).startConnectToNetwork(anyInt(), anyInt(), any());
+    }
+
+    /**
+     * Verify that the cached candidates that become disabled are not selected for connection.
+     */
+    @Test
+    public void testRetryConnectionIgnoresDisabledNetworks() {
+        // Setup WifiNetworkSelector to return 2 valid candidates from scan results
+        int testOtherNetworkNetworkId = 123;
+        MacAddress macAddress = MacAddress.fromString(CANDIDATE_BSSID_2);
+        WifiCandidates.Key key = new WifiCandidates.Key(mock(ScanResultMatchInfo.class),
+                macAddress, testOtherNetworkNetworkId, WifiConfiguration.SECURITY_TYPE_OPEN);
+        WifiCandidates.Candidate otherCandidate = mock(WifiCandidates.Candidate.class);
+        when(otherCandidate.getKey()).thenReturn(key);
+        List<WifiCandidates.Candidate> candidateList = new ArrayList<>();
+        candidateList.add(mCandidate1);
+        candidateList.add(otherCandidate);
+        when(mWifiNS.getCandidatesFromScan(any(), any(), any(), anyBoolean(), anyBoolean(),
+                anyBoolean())).thenReturn(candidateList);
+
+        // Set WiFi to disconnected state to trigger scan
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
+                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+        mLooper.dispatchAll();
+        // Verify a connection starting
+        verify(mWifiNS).selectNetwork((List<WifiCandidates.Candidate>)
+                argThat(new WifiCandidatesListSizeMatcher(2)));
+        verify(mPrimaryClientModeManager).startConnectToNetwork(anyInt(), anyInt(), any());
+
+        // make sure the configuration for otherCandidate is disabled, and verify there is no
+        // connection attempt after the disconnect happens.
+        when(otherCandidate.getNetworkConfigId()).thenReturn(testOtherNetworkNetworkId);
+        WifiConfiguration candidateOtherConfig = WifiConfigurationTestUtil.createOpenNetwork();
+        candidateOtherConfig.getNetworkSelectionStatus().setNetworkSelectionStatus(
+                WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED);
+        when(mWifiConfigManager.getConfiguredNetwork(testOtherNetworkNetworkId))
+                .thenReturn(candidateOtherConfig);
+
+        // Simulate the connection failing
+        mWifiConnectivityManager.handleConnectionAttemptEnded(
+                mPrimaryClientModeManager,
+                WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_REJECTION, CANDIDATE_BSSID,
+                CANDIDATE_SSID);
+
+        // Verify no more connections since there are 0 valid candidates remaining.
+        verify(mWifiNS).selectNetwork((List<WifiCandidates.Candidate>)
+                argThat(new WifiCandidatesListSizeMatcher(0)));
+        verify(mPrimaryClientModeManager).startConnectToNetwork(anyInt(), anyInt(), any());
     }
 
     /**
@@ -973,11 +1734,12 @@
 
         // Set WiFi to disconnected state to trigger scan
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
         mLooper.dispatchAll();
 
         // Verify there is no connection due to currently having no cached candidates.
-        verify(mClientModeImpl, never()).startConnectToNetwork(
+        verify(mPrimaryClientModeManager, never()).startConnectToNetwork(
                 CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
 
         // Move time past HIGH_MVMT_SCAN_DELAY_MS.
@@ -989,11 +1751,12 @@
 
         // Set WiFi to disconnected state to trigger scan
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
         mLooper.dispatchAll();
 
         // Verify connect is not started.
-        verify(mClientModeImpl, never()).startConnectToNetwork(
+        verify(mPrimaryClientModeManager, never()).startConnectToNetwork(
                 CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
         verify(mWifiMetrics, times(2)).incrementNumHighMovementConnectionSkipped();
     }
@@ -1004,7 +1767,7 @@
      * Expected behavior: ONA handles scan results
      */
     @Test
-    public void wifiDisconnected_noConnectionCandidate_openNetworkNotifierScanResultsHandled() {
+    public void wifiDisconnected_noCandidateInSelect_openNetworkNotifierScanResultsHandled() {
         // no connection candidate selected
         when(mWifiNS.selectNetwork(any())).thenReturn(null);
 
@@ -1020,6 +1783,36 @@
 
         // Set WiFi to disconnected state to trigger PNO scan
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
+                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+
+        verify(mOpenNetworkNotifier).handleScanResults(expectedOpenNetworks);
+    }
+
+    /**
+     * {@link OpenNetworkNotifier} handles scan results on network selection.
+     *
+     * Expected behavior: ONA handles scan results
+     */
+    @Test
+    public void wifiDisconnected_noCandidatesInScan_openNetworkNotifierScanResultsHandled() {
+        // no connection candidates from scan.
+        when(mWifiNS.getCandidatesFromScan(any(), any(), any(), anyBoolean(), anyBoolean(),
+                anyBoolean())).thenReturn(null);
+
+        List<ScanDetail> expectedOpenNetworks = new ArrayList<>();
+        expectedOpenNetworks.add(
+                new ScanDetail(
+                        new ScanResult(WifiSsid.createFromAsciiEncoded(CANDIDATE_SSID),
+                                CANDIDATE_SSID, CANDIDATE_BSSID, 1245, 0, "some caps", -78, 2450,
+                                1025, 22, 33, 20, 0, 0, true), null));
+
+        when(mWifiNS.getFilteredScanDetailsForOpenUnsavedNetworks())
+                .thenReturn(expectedOpenNetworks);
+
+        // Set WiFi to disconnected state to trigger PNO scan
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
 
         verify(mOpenNetworkNotifier).handleScanResults(expectedOpenNetworks);
@@ -1035,6 +1828,7 @@
         // Set WiFi to connected state
         mWifiInfo.setSSID(WifiSsid.createFromAsciiEncoded(CANDIDATE_SSID));
         mWifiConnectivityManager.handleConnectionAttemptEnded(
+                mPrimaryClientModeManager,
                 WifiMetrics.ConnectionEvent.FAILURE_NONE, CANDIDATE_BSSID, CANDIDATE_SSID);
         verify(mOpenNetworkNotifier).handleWifiConnected(CANDIDATE_SSID);
     }
@@ -1049,6 +1843,7 @@
     public void wifiDisconnected_openNetworkNotifierDoesNotClearPendingNotification() {
         // Set WiFi to disconnected state
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
 
         verify(mOpenNetworkNotifier, never()).clearPendingNotification(anyBoolean());
@@ -1064,6 +1859,7 @@
     @Test
     public void wifiConnectionEndsWithFailure_openNetworkNotifierHandlesConnectionFailure() {
         mWifiConnectivityManager.handleConnectionAttemptEnded(
+                mPrimaryClientModeManager,
                 WifiMetrics.ConnectionEvent.FAILURE_CONNECT_NETWORK_FAILED, CANDIDATE_BSSID,
                 CANDIDATE_SSID);
 
@@ -1080,6 +1876,7 @@
     @Test
     public void wifiConnectionEndsWithSuccess_openNetworkNotifierDoesNotHandleConnectionFailure() {
         mWifiConnectivityManager.handleConnectionAttemptEnded(
+                mPrimaryClientModeManager,
                 WifiMetrics.ConnectionEvent.FAILURE_NONE, CANDIDATE_BSSID, CANDIDATE_SSID);
 
         verify(mOpenNetworkNotifier, never()).handleConnectionFailure();
@@ -1092,7 +1889,7 @@
      * */
     @Test
     public void openNetworkNotifierClearsPendingNotificationOnWifiDisabled() {
-        mWifiConnectivityManager.setWifiEnabled(false);
+        setWifiEnabled(false);
 
         verify(mOpenNetworkNotifier).clearPendingNotification(true /* resetRepeatDelay */);
     }
@@ -1102,16 +1899,57 @@
      */
     @Test
     public void openNetworkNotifierTracksScreenStateChanges() {
-        mWifiConnectivityManager.handleScreenStateChanged(false);
-
+        // Screen state change at bootup.
         verify(mOpenNetworkNotifier).handleScreenStateChanged(false);
 
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(false);
+
+        verify(mOpenNetworkNotifier, times(2)).handleScreenStateChanged(false);
+
+        setScreenState(true);
 
         verify(mOpenNetworkNotifier).handleScreenStateChanged(true);
     }
 
     /**
+     * Verify that the initial fast scan schedules the scan timer just like regular scans.
+     */
+    @Test
+    public void testInitialFastScanSchedulesMoreScans() {
+        // Enable the fast initial scan feature
+        mResources.setBoolean(R.bool.config_wifiEnablePartialInitialScan, true);
+        // return 2 available frequencies
+        when(mWifiScoreCard.lookupNetwork(anyString())).thenReturn(mPerNetwork);
+        when(mPerNetwork.getFrequencies(anyLong())).thenReturn(new ArrayList<>(
+                Arrays.asList(TEST_FREQUENCY_1, TEST_FREQUENCY_2)));
+
+        long currentTimeStamp = CURRENT_SYSTEM_TIME_MS;
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
+        mWifiConnectivityManager.setTrustedConnectionAllowed(true);
+
+        // set screen off and wifi disconnected
+        setScreenState(false);
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
+                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+
+        // Set screen to ON to start a fast initial scan
+        setScreenState(true);
+
+        // Verify the initial scan state is awaiting for response
+        assertEquals(WifiConnectivityManager.INITIAL_SCAN_STATE_AWAITING_RESPONSE,
+                mWifiConnectivityManager.getInitialScanState());
+        verify(mWifiMetrics).incrementInitialPartialScanCount();
+
+        // Also verify the scan timer is set properly.
+        long firstIntervalMs = mAlarmManager
+                .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
+                - currentTimeStamp;
+        int expected = (int) (VALID_DISCONNECTED_SINGLE_SCAN_SCHEDULE_SEC[0] * 1000);
+        assertEquals(expected, firstIntervalMs);
+    }
+
+    /**
      * Verify that if configuration for single scan schedule is empty, default
      * schedule is being used.
      */
@@ -1149,16 +1987,33 @@
         checkWorkingWithDefaultSchedule();
     }
 
+    /**
+     * Verify that when power save mode in on, the periodic scan interval is increased.
+     */
+    @Test
+    public void checkPeriodicScanIntervalWhenDisconnectAndPowerSaveModeOn() throws Exception {
+        mResources.setIntArray(
+                R.array.config_wifiDisconnectedScanIntervalScheduleSec,
+                INVALID_SCHEDULE_ZERO_VALUES_SEC);
+        when(mDeviceConfigFacade.isWifiBatterySaverEnabled()).thenReturn(true);
+        when(mPowerManagerService.isPowerSaveMode()).thenReturn(true);
+        checkWorkingWithDefaultScheduleWithMultiplier(POWER_SAVE_SCAN_INTERVAL_MULTIPLIER);
+    }
+
     private void checkWorkingWithDefaultSchedule() {
+        checkWorkingWithDefaultScheduleWithMultiplier(1);
+    }
+
+    private void checkWorkingWithDefaultScheduleWithMultiplier(float multiplier) {
         long currentTimeStamp = CURRENT_SYSTEM_TIME_MS;
         when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
 
         mWifiConnectivityManager = createConnectivityManager();
         mWifiConnectivityManager.setTrustedConnectionAllowed(true);
-        mWifiConnectivityManager.setWifiEnabled(true);
+        setWifiEnabled(true);
 
         // Set screen to ON
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(true);
 
         // Wait for max periodic scan interval so that any impact triggered
         // by screen state change can settle
@@ -1167,13 +2022,15 @@
 
         // Set WiFi to disconnected state to trigger periodic scan
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
 
         // Get the first periodic scan interval
         long firstIntervalMs = mAlarmManager
                 .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
                 - currentTimeStamp;
-        assertEquals(DEFAULT_SINGLE_SCAN_SCHEDULE_SEC[0] * 1000, firstIntervalMs);
+        int expected = (int) (DEFAULT_SINGLE_SCAN_SCHEDULE_SEC[0] * 1000 * multiplier);
+        assertEquals(expected, firstIntervalMs);
 
         currentTimeStamp += firstIntervalMs;
         when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
@@ -1188,7 +2045,8 @@
                 - currentTimeStamp;
 
         // Verify the intervals are exponential back off
-        assertEquals(DEFAULT_SINGLE_SCAN_SCHEDULE_SEC[1] * 1000, secondIntervalMs);
+        expected = (int) (DEFAULT_SINGLE_SCAN_SCHEDULE_SEC[1] * 1000 * multiplier);
+        assertEquals(expected, secondIntervalMs);
 
         currentTimeStamp += secondIntervalMs;
         when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
@@ -1205,8 +2063,9 @@
             when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
         }
 
-        assertEquals(DEFAULT_SINGLE_SCAN_SCHEDULE_SEC[DEFAULT_SINGLE_SCAN_SCHEDULE_SEC.length - 1]
-                * 1000, intervalMs);
+        expected = (int) (DEFAULT_SINGLE_SCAN_SCHEDULE_SEC[DEFAULT_SINGLE_SCAN_SCHEDULE_SEC.length
+                - 1] * 1000 * multiplier);
+        assertEquals(expected, intervalMs);
     }
 
     /**
@@ -1222,7 +2081,7 @@
         when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
 
         // Set screen to ON
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(true);
 
         // Wait for max periodic scan interval so that any impact triggered
         // by screen state change can settle
@@ -1231,6 +2090,7 @@
 
         // Set WiFi to disconnected state to trigger periodic scan
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
 
         // Get the first periodic scan interval
@@ -1286,7 +2146,7 @@
         when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
 
         // Set screen to ON
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(true);
 
         // Wait for max scanning interval so that any impact triggered
         // by screen state change can settle
@@ -1346,7 +2206,7 @@
         when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
 
         // Set screen to ON
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(true);
 
         // Wait for max scanning interval so that any impact triggered
         // by screen state change can settle
@@ -1370,6 +2230,7 @@
 
         // Set WiFi to connected state to trigger periodic scan
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_CONNECTED);
 
         // Get the first periodic scan interval
@@ -1391,7 +2252,7 @@
         when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
 
         // Set screen to ON
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(true);
 
         // Wait for max scanning interval so that any impact triggered
         // by screen state change can settle
@@ -1434,7 +2295,7 @@
         when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
 
         // Set screen to ON
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(true);
 
         // Wait for max scanning interval so that any impact triggered
         // by screen state change can settle
@@ -1477,7 +2338,7 @@
         when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
 
         // Set screen to ON
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(true);
 
         // Wait for max scanning interval so that any impact triggered
         // by screen state change can settle
@@ -1527,7 +2388,7 @@
         when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
 
         // Set screen to ON
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(true);
 
         // Wait for max scanning interval so that any impact triggered
         // by screen state change can settle
@@ -1543,7 +2404,7 @@
         config.networkId = TEST_CONNECTED_NETWORK_ID;
         String networkKey = "NETWORK_KEY";
         when(mWifiConfigManager.getConfiguredNetwork(networkKey)).thenReturn(config);
-        when(mSuggestionConfig.getKey()).thenReturn(networkKey);
+        when(mSuggestionConfig.getProfileKey()).thenReturn(networkKey);
         when(mWifiNetworkSuggestion.getWifiConfiguration()).thenReturn(mSuggestionConfig);
         Set<WifiNetworkSuggestion> suggestionNetworks = new HashSet<WifiNetworkSuggestion>();
         suggestionNetworks.add(mWifiNetworkSuggestion);
@@ -1578,7 +2439,7 @@
         when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
 
         // Set screen to ON
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(true);
 
         // Wait for max scanning interval so that any impact triggered
         // by screen state change can settle
@@ -1594,7 +2455,7 @@
         config.networkId = TEST_CONNECTED_NETWORK_ID;
         String networkKey = "NETWORK_KEY";
         when(mWifiConfigManager.getConfiguredNetwork(networkKey)).thenReturn(config);
-        when(mSuggestionConfig.getKey()).thenReturn(networkKey);
+        when(mSuggestionConfig.getProfileKey()).thenReturn(networkKey);
         when(mWifiNetworkSuggestion.getWifiConfiguration()).thenReturn(mSuggestionConfig);
         Set<WifiNetworkSuggestion> suggestionNetworks = new HashSet<WifiNetworkSuggestion>();
         suggestionNetworks.add(mWifiNetworkSuggestion);
@@ -1634,7 +2495,7 @@
         when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
 
         // Set screen to ON
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(true);
 
         // Wait for max scanning interval so that any impact triggered
         // by screen state change can settle
@@ -1658,7 +2519,7 @@
         // Set WiFi to connected state.
         setWifiStateConnected();
         // Simulate remove network, disconnect not finished.
-        when(mClientModeImpl.getCurrentWifiConfiguration()).thenReturn(null);
+        when(mPrimaryClientModeManager.getConnectedWifiConfiguration()).thenReturn(null);
         mNetworkUpdateListenerCaptor.getValue().onNetworkRemoved(null);
 
         // Get the first periodic scan interval
@@ -1681,7 +2542,7 @@
         when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
 
         // Set screen to ON
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(true);
 
         // Wait for max scanning interval in schedule so that any impact triggered
         // by screen state change can settle
@@ -1691,6 +2552,7 @@
 
         // Set WiFi to disconnected state which triggers a scan immediately
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
         verify(mWifiScanner, times(1)).startScan(
                 anyObject(), anyObject(), anyObject(), anyObject());
@@ -1728,7 +2590,7 @@
         when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
 
         // Set screen to ON
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(true);
 
         // Wait for maximum scanning interval in schedule so that any impact triggered
         // by screen state change can settle
@@ -1749,6 +2611,7 @@
 
         // Set WiFi to disconnected state to trigger its periodic scan
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
 
         // Verify the very first scan for DISCONNECTED state is fired immediately
@@ -1778,7 +2641,7 @@
         when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
 
         // Set screen to ON
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(true);
 
         // Wait for maximum interval in scanning schedule so that any impact triggered
         // by screen state change can settle
@@ -1833,7 +2696,7 @@
         configuration.networkId = TEST_CONNECTED_NETWORK_ID;
         when(mWifiConfigManager.getConfiguredNetwork(TEST_CONNECTED_NETWORK_ID))
                 .thenReturn(configuration);
-        when(mClientModeImpl.getCurrentWifiConfiguration())
+        when(mPrimaryClientModeManager.getConnectedWifiConfiguration())
                 .thenReturn(configuration);
         when(mWifiScoreCard.lookupNetwork(configuration.SSID)).thenReturn(mPerNetwork);
         when(mPerNetwork.getFrequencies(anyLong())).thenReturn(new ArrayList<>());
@@ -1847,10 +2710,11 @@
 
         when(mClock.getElapsedSinceBootMillis()).thenReturn(0L);
         // Set screen to ON
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(true);
 
         // Set WiFi to connected state to trigger periodic scan
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_CONNECTED);
 
         verify(mWifiScanner).startScan(anyObject(), anyObject(), anyObject(), anyObject());
@@ -1861,9 +2725,10 @@
         when(mWifiNS.hasSufficientLinkQuality(eq(mWifiInfo))).thenReturn(true);
         when(mWifiNS.hasInternetOrExpectNoInternet(eq(mWifiInfo))).thenReturn(true);
         when(mClock.getElapsedSinceBootMillis()).thenReturn(600_000L + 1L);
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(true);
         // Set WiFi to connected state to trigger periodic scan
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_CONNECTED);
         verify(mWifiScanner, times(2)).startScan(anyObject(), anyObject(), anyObject(),
                 anyObject());
@@ -1874,9 +2739,10 @@
         when(mWifiNS.hasSufficientLinkQuality(eq(mWifiInfo))).thenReturn(true);
         when(mWifiNS.hasInternetOrExpectNoInternet(eq(mWifiInfo))).thenReturn(false);
         when(mClock.getElapsedSinceBootMillis()).thenReturn(0L);
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(true);
         // Set WiFi to connected state to trigger periodic scan
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_CONNECTED);
         verify(mWifiScanner, times(2)).startScan(anyObject(), anyObject(), anyObject(),
                 anyObject());
@@ -1906,7 +2772,7 @@
         when(mWifiConfigManager.getConfiguredNetwork(TEST_CONNECTED_NETWORK_ID))
                 .thenReturn(configuration);
         List<Integer> channelList = linkScoreCardFreqsToNetwork(configuration).get(0);
-        when(mClientModeImpl.getCurrentWifiConfiguration())
+        when(mPrimaryClientModeManager.getConnectedWifiConfiguration())
                 .thenReturn(configuration);
 
         when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(false);
@@ -1916,16 +2782,23 @@
                     WorkSource workSource) throws Exception {
                 assertEquals(settings.band, WifiScanner.WIFI_BAND_UNSPECIFIED);
                 assertEquals(settings.channels.length, channelList.size());
+                if (SdkLevel.isAtLeastS()) {
+                    assertEquals("Should never force enable RNR for partial scans",
+                            WifiScanner.WIFI_RNR_NOT_NEEDED, settings.getRnrSetting());
+                    assertFalse("PSC should be disabled for partial scans",
+                            settings.is6GhzPscOnlyEnabled());
+                }
                 for (int chanIdx = 0; chanIdx < settings.channels.length; chanIdx++) {
                     assertTrue(channelList.contains(settings.channels[chanIdx].frequency));
                 }
             }}).when(mWifiScanner).startScan(anyObject(), anyObject(), anyObject(), anyObject());
 
         // Set screen to ON
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(true);
 
         // Set WiFi to connected state to trigger periodic scan
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_CONNECTED);
 
         verify(mWifiScanner).startScan(anyObject(), anyObject(), anyObject(), anyObject());
@@ -1940,7 +2813,7 @@
      * Expected behavior: WifiConnectivityManager does partial scan.
      */
     @Test
-    public void checkPartialScanRequestedWithHighRssiNoActiveStreamWithoutFwRoaming() {
+    public void checkPartialSCanRequestedWithHighRssiNoActiveStreamWithoutFwRoaming() {
         when(mWifiNS.isNetworkSufficient(eq(mWifiInfo))).thenReturn(false);
         when(mWifiNS.hasActiveStream(eq(mWifiInfo))).thenReturn(false);
         when(mWifiNS.hasSufficientLinkQuality(eq(mWifiInfo))).thenReturn(true);
@@ -1956,7 +2829,7 @@
                 .thenReturn(configuration);
         List<Integer> channelList = linkScoreCardFreqsToNetwork(configuration).get(0);
 
-        when(mClientModeImpl.getCurrentWifiConfiguration())
+        when(mPrimaryClientModeManager.getConnectedWifiConfiguration())
                 .thenReturn(configuration);
         when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(false);
 
@@ -1971,10 +2844,11 @@
             }}).when(mWifiScanner).startScan(anyObject(), anyObject(), anyObject(), anyObject());
 
         // Set screen to ON
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(true);
 
         // Set WiFi to connected state to trigger periodic scan
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_CONNECTED);
 
         verify(mWifiScanner).startScan(anyObject(), anyObject(), anyObject(), anyObject());
@@ -2002,21 +2876,30 @@
                 .thenReturn(configuration);
         List<Integer> channelList = linkScoreCardFreqsToNetwork(configuration).get(0);
 
-        when(mClientModeImpl.getCurrentWifiConfiguration())
+        when(mPrimaryClientModeManager.getConnectedWifiConfiguration())
                 .thenReturn(new WifiConfiguration());
 
         doAnswer(new AnswerWithArguments() {
             public void answer(ScanSettings settings, Executor executor, ScanListener listener,
                     WorkSource workSource) throws Exception {
-                assertEquals(settings.band, WifiScanner.WIFI_BAND_ALL);
                 assertNull(settings.channels);
+                if (SdkLevel.isAtLeastS()) {
+                    assertEquals(WifiScanner.WIFI_BAND_24_5_WITH_DFS_6_GHZ, settings.band);
+                    assertEquals("RNR should be enabled for full scans",
+                            WifiScanner.WIFI_RNR_ENABLED, settings.getRnrSetting());
+                    assertTrue("PSC should be enabled for full scans",
+                            settings.is6GhzPscOnlyEnabled());
+                } else {
+                    assertEquals(WifiScanner.WIFI_BAND_ALL, settings.band);
+                }
             }}).when(mWifiScanner).startScan(anyObject(), anyObject(), anyObject(), anyObject());
 
         // Set screen to ON
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(true);
 
         // Set WiFi to connected state to trigger periodic scan
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_CONNECTED);
 
         verify(mWifiScanner).startScan(anyObject(), anyObject(), anyObject(), anyObject());
@@ -2032,7 +2915,7 @@
     @Test
     public void checkMaximumScanRetry() {
         // Set screen to ON
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(true);
 
         doAnswer(new AnswerWithArguments() {
             public void answer(ScanSettings settings, Executor executor, ScanListener listener,
@@ -2042,6 +2925,7 @@
 
         // Set WiFi to disconnected state to trigger the single scan based periodic scan
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
 
         // Fire the alarm timer 2x timers
@@ -2118,7 +3002,7 @@
      * act on them.
      *
      * Expected behavior: WifiConnectivityManager calls
-     * ClientModeImpl.startConnectToNetwork() with the
+     * ClientModeManager.startConnectToNetwork() with the
      * expected candidate network ID and BSSID.
      */
     @Test
@@ -2131,7 +3015,7 @@
 
         // Verify that WCM receives the scan results and initiates a connection
         // to the network.
-        verify(mClientModeImpl).startConnectToNetwork(
+        verify(mPrimaryClientModeManager).startConnectToNetwork(
                 CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
     }
 
@@ -2140,7 +3024,7 @@
      *  results.
      *
      * Expected behavior: WifiConnectivityManager doesn't invoke
-     * ClientModeImpl.startConnectToNetwork() when full band scan
+     * ClientModeManager.startConnectToNetwork() when full band scan
      * results are not available.
      */
     @Test
@@ -2149,25 +3033,25 @@
         setWifiStateConnected();
 
         // Set up as partial scan results.
-        when(mScanData.getBandScanned()).thenReturn(WifiScanner.WIFI_BAND_5_GHZ);
+        when(mScanData.getScannedBandsInternal()).thenReturn(WifiScanner.WIFI_BAND_5_GHZ);
 
         // Force a connectivity scan which enables WifiConnectivityManager
         // to wait for full band scan results.
         mWifiConnectivityManager.forceConnectivityScan(WIFI_WORK_SOURCE);
 
         // No roaming because no full band scan results.
-        verify(mClientModeImpl, times(0)).startConnectToNetwork(
+        verify(mPrimaryClientModeManager, times(0)).startConnectToNetwork(
                 CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
 
         // Set up as full band scan results.
-        when(mScanData.getBandScanned()).thenReturn(WifiScanner.WIFI_BAND_ALL);
+        when(mScanData.getScannedBandsInternal()).thenReturn(WifiScanner.WIFI_BAND_ALL);
 
         // Force a connectivity scan which enables WifiConnectivityManager
         // to wait for full band scan results.
         mWifiConnectivityManager.forceConnectivityScan(WIFI_WORK_SOURCE);
 
         // Roaming attempt because full band scan results are available.
-        verify(mClientModeImpl).startConnectToNetwork(
+        verify(mPrimaryClientModeManager).startConnectToNetwork(
                 CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
     }
 
@@ -2203,43 +3087,72 @@
     }
 
     /**
-     *  Verify that a blacklisted BSSID becomes available only after
-     *  BSSID_BLACKLIST_EXPIRE_TIME_MS.
+     * Verify that after receiving scan results, we attempt to clear expired recent failure reasons.
      */
     @Test
-    public void verifyBlacklistRefreshedAfterScanResults() {
-        when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
-
-        InOrder inOrder = inOrder(mBssidBlocklistMonitor);
-        // Force a connectivity scan
-        inOrder.verify(mBssidBlocklistMonitor, never())
-                .updateAndGetBssidBlocklistForSsid(anyString());
+    public void verifyClearExpiredRecentFailureStatusAfterScan() {
+        // mWifiScanner is mocked to directly return scan results when a scan is triggered.
         mWifiConnectivityManager.forceConnectivityScan(WIFI_WORK_SOURCE);
-
-        inOrder.verify(mBssidBlocklistMonitor).tryEnablingBlockedBssids(any());
-        inOrder.verify(mBssidBlocklistMonitor).updateAndGetBssidBlocklistForSsid(anyString());
+        verify(mWifiConfigManager).cleanupExpiredRecentFailureReasons();
     }
 
     /**
-     *  Verify that BSSID blacklist gets cleared when exiting Wifi client mode.
+     *  Verify that a blocklisted BSSID becomes available only after
+     *  BSSID_BLOCKLIST_EXPIRE_TIME_MS.
      */
     @Test
-    public void clearBssidBlocklistWhenExitingWifiClientMode() {
+    public void verifyBlocklistRefreshedAfterScanResults() {
         when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
 
-        // Verify the BSSID blacklist is cleared at start up.
-        verify(mBssidBlocklistMonitor).clearBssidBlocklist();
-        // Exit Wifi client mode.
-        mWifiConnectivityManager.setWifiEnabled(false);
+        InOrder inOrder = inOrder(mWifiBlocklistMonitor);
+        // Force a connectivity scan
+        inOrder.verify(mWifiBlocklistMonitor, never())
+                .updateAndGetBssidBlocklistForSsids(anySet());
+        mWifiConnectivityManager.forceConnectivityScan(WIFI_WORK_SOURCE);
 
-        // Verify the BSSID blacklist is cleared again.
-        verify(mBssidBlocklistMonitor, times(2)).clearBssidBlocklist();
+        inOrder.verify(mWifiBlocklistMonitor).tryEnablingBlockedBssids(any());
+        inOrder.verify(mWifiBlocklistMonitor).updateAndGetBssidBlocklistForSsids(anySet());
+    }
+
+    /**
+     *  Verify blocklists and ephemeral networks are cleared from WifiConfigManager when exiting
+     *  Wifi client mode. And if requires, ANQP cache is also flushed.
+     */
+    @Test
+    public void clearEnableTemporarilyDisabledNetworksWhenExitingWifiClientMode() {
+        when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
+        when(mWifiGlobals.flushAnqpCacheOnWifiToggleOffEvent()).thenReturn(true);
+        // Exit Wifi client mode.
+        setWifiEnabled(false);
+
+        // Verify the blocklists is cleared again.
+        verify(mWifiConfigManager).enableTemporaryDisabledNetworks();
+        verify(mWifiConfigManager).stopRestrictingAutoJoinToSubscriptionId();
+        verify(mWifiConfigManager).removeAllEphemeralOrPasspointConfiguredNetworks();
+        verify(mWifiConfigManager).clearUserTemporarilyDisabledList();
+
+        // Verify ANQP cache is flushed.
+        verify(mPasspointManager).clearAnqpRequestsAndFlushCache();
         // Verify WifiNetworkSelector is informed of the disable.
         verify(mWifiNS).resetOnDisable();
     }
 
     /**
-     *  Verify that BSSID blacklist gets cleared when preparing for a forced connection
+     * Verifies that the ANQP cache is not flushed when the configuration does not permit it.
+     */
+    @Test
+    public void testAnqpFlushCacheSkippedIfNotConfigured() {
+        when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
+        when(mWifiGlobals.flushAnqpCacheOnWifiToggleOffEvent()).thenReturn(false);
+        // Exit Wifi client mode.
+        setWifiEnabled(false);
+
+        // Verify ANQP cache is not flushed.
+        verify(mPasspointManager, never()).clearAnqpRequestsAndFlushCache();
+    }
+
+    /**
+     *  Verify that BSSID blocklist gets cleared when preparing for a forced connection
      *  initiated by user/app.
      */
     @Test
@@ -2247,10 +3160,11 @@
         when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
         // Prepare for a forced connection attempt.
         WifiConfiguration currentNetwork = generateWifiConfig(
-                0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
+                0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null,
+                WifiConfigurationTestUtil.SECURITY_NONE);
         when(mWifiConfigManager.getConfiguredNetwork(anyInt())).thenReturn(currentNetwork);
         mWifiConnectivityManager.prepareForForcedConnection(1);
-        verify(mBssidBlocklistMonitor).clearBssidBlocklistForSsid(CANDIDATE_SSID);
+        verify(mWifiBlocklistMonitor).clearBssidBlocklistForSsid(CANDIDATE_SSID);
     }
 
     /**
@@ -2264,7 +3178,7 @@
     @Test
     public void verifyGetFirmwareRoamingInfoIsCalledWhenEnableWiFiAndWcmOn() {
         // WifiConnectivityManager is on by default
-        mWifiConnectivityManager.setWifiEnabled(true);
+        setWifiEnabled(true);
         verify(mWifiConnectivityHelper).getFirmwareRoamingInfo();
     }
 
@@ -2280,7 +3194,7 @@
     public void verifyGetFirmwareRoamingInfoIsNotCalledWhenEnableWiFiAndWcmOff() {
         reset(mWifiConnectivityHelper);
         mWifiConnectivityManager.setAutoJoinEnabledExternal(false);
-        mWifiConnectivityManager.setWifiEnabled(true);
+        setWifiEnabled(true);
         verify(mWifiConnectivityHelper, times(0)).getFirmwareRoamingInfo();
     }
 
@@ -2289,7 +3203,7 @@
      * Connect to a network which doesn't have a config specified BSSID.
      *
      * Expected behavior: WifiConnectivityManager calls
-     * ClientModeImpl.startConnectToNetwork() with the
+     * ClientModeManager.startConnectToNetwork() with the
      * expected candidate network ID, and the BSSID value should be
      * 'any' since firmware controls the roaming.
      */
@@ -2299,13 +3213,14 @@
         when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
 
         // Set screen to on
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(true);
 
         // Set WiFi to disconnected state
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
 
-        verify(mClientModeImpl).startConnectToNetwork(
+        verify(mPrimaryClientModeManager).startConnectToNetwork(
                 CANDIDATE_NETWORK_ID, Process.WIFI_UID, ClientModeImpl.SUPPLICANT_BSSID_ANY);
     }
 
@@ -2314,7 +3229,7 @@
      * Connect to a network which has a config specified BSSID.
      *
      * Expected behavior: WifiConnectivityManager calls
-     * ClientModeImpl.startConnectToNetwork() with the
+     * ClientModeManager.startConnectToNetwork() with the
      * expected candidate network ID, and the BSSID value should be
      * the config specified one.
      */
@@ -2325,7 +3240,8 @@
 
         // Set up the candidate configuration such that it has a BSSID specified.
         WifiConfiguration candidate = generateWifiConfig(
-                0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
+                0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null,
+                WifiConfigurationTestUtil.SECURITY_NONE);
         candidate.BSSID = CANDIDATE_BSSID; // config specified
         ScanResult candidateScanResult = new ScanResult();
         candidateScanResult.SSID = CANDIDATE_SSID;
@@ -2334,14 +3250,17 @@
         when(mWifiNS.selectNetwork(any())).thenReturn(candidate);
 
         // Set screen to on
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(true);
 
         // Set WiFi to disconnected state
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
 
-        verify(mClientModeImpl).startConnectToNetwork(
+        verify(mPrimaryClientModeManager).startConnectToNetwork(
                 CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
+
+        verify(mWifiMetrics).noteFirstNetworkSelectionAfterBoot(true);
     }
 
     /*
@@ -2349,19 +3268,20 @@
      * Connect to a network which doesn't have a config specified BSSID.
      *
      * Expected behavior: WifiConnectivityManager calls
-     * ClientModeImpl.startConnectToNetwork() with the expected candidate network ID,
+     * ClientModeManager.startConnectToNetwork() with the expected candidate network ID,
      * and the BSSID value should be the candidate scan result specified.
      */
     @Test
     public void useScanResultBssidToConnectWhenFirmwareRoamingOffAndConfigHasNoBssidSpecified() {
         // Set screen to on
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(true);
 
         // Set WiFi to disconnected state
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
 
-        verify(mClientModeImpl).startConnectToNetwork(
+        verify(mPrimaryClientModeManager).startConnectToNetwork(
                 CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
     }
 
@@ -2370,14 +3290,15 @@
      * Connect to a network which has a config specified BSSID.
      *
      * Expected behavior: WifiConnectivityManager calls
-     * ClientModeImpl.startConnectToNetwork() with the expected candidate network ID,
+     * ClientModeManager.startConnectToNetwork() with the expected candidate network ID,
      * and the BSSID value should be the config specified one.
      */
     @Test
     public void useConfigSpecifiedBssidToConnectionWhenFirmwareRoamingOff() {
         // Set up the candidate configuration such that it has a BSSID specified.
         WifiConfiguration candidate = generateWifiConfig(
-                0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
+                0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null,
+                WifiConfigurationTestUtil.SECURITY_NONE);
         candidate.BSSID = CANDIDATE_BSSID; // config specified
         ScanResult candidateScanResult = new ScanResult();
         candidateScanResult.SSID = CANDIDATE_SSID;
@@ -2386,13 +3307,14 @@
         when(mWifiNS.selectNetwork(any())).thenReturn(candidate);
 
         // Set screen to on
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(true);
 
         // Set WiFi to disconnected state
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
 
-        verify(mClientModeImpl).startConnectToNetwork(
+        verify(mPrimaryClientModeManager).startConnectToNetwork(
                 CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
     }
 
@@ -2401,25 +3323,21 @@
      * WiFi in connected state, framework triggers roaming.
      *
      * Expected behavior: WifiConnectivityManager invokes
-     * ClientModeImpl.startRoamToNetwork().
+     * ClientModeManager.startRoamToNetwork().
      */
     @Test
     public void frameworkInitiatedRoaming() {
-        // Mock the currently connected network which has the same networkID and
-        // SSID as the one to be selected.
-        WifiConfiguration currentNetwork = generateWifiConfig(
-                0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
-        when(mWifiConfigManager.getConfiguredNetwork(anyInt())).thenReturn(currentNetwork);
-
         // Set WiFi to connected state
-        setWifiStateConnected();
+        setWifiStateConnected(CANDIDATE_NETWORK_ID, CANDIDATE_BSSID_2);
 
         // Set screen to on
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(true);
 
-        verify(mClientModeImpl).startRoamToNetwork(eq(CANDIDATE_NETWORK_ID),
-                mCandidateScanResultCaptor.capture());
-        assertEquals(mCandidateScanResultCaptor.getValue().BSSID, CANDIDATE_BSSID);
+        verify(mPrimaryClientModeManager).startRoamToNetwork(eq(CANDIDATE_NETWORK_ID),
+                mCandidateBssidCaptor.capture());
+        assertEquals(mCandidateBssidCaptor.getValue(), CANDIDATE_BSSID);
+        verify(mPrimaryClientModeManager, never()).startConnectToNetwork(
+                anyInt(), anyInt(), anyObject());
     }
 
     /**
@@ -2428,26 +3346,22 @@
      * as it's handed off to the firmware.
      *
      * Expected behavior: WifiConnectivityManager doesn't invoke
-     * ClientModeImpl.startRoamToNetwork().
+     * ClientModeManager.startRoamToNetwork().
      */
     @Test
     public void noFrameworkRoamingIfConnectedAndFirmwareRoamingSupported() {
-        // Mock the currently connected network which has the same networkID and
-        // SSID as the one to be selected.
-        WifiConfiguration currentNetwork = generateWifiConfig(
-                0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
-        when(mWifiConfigManager.getConfiguredNetwork(anyInt())).thenReturn(currentNetwork);
+        // Set WiFi to connected state
+        setWifiStateConnected(CANDIDATE_NETWORK_ID, CANDIDATE_BSSID_2);
 
         // Firmware controls roaming
         when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
 
-        // Set WiFi to connected state
-        setWifiStateConnected();
-
         // Set screen to on
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(true);
 
-        verify(mClientModeImpl, times(0)).startRoamToNetwork(anyInt(), anyObject());
+        verify(mPrimaryClientModeManager, never()).startRoamToNetwork(anyInt(), anyObject());
+        verify(mPrimaryClientModeManager, never()).startConnectToNetwork(
+                anyInt(), anyInt(), anyObject());
     }
 
     /*
@@ -2456,13 +3370,14 @@
      * match it.
      *
      * Expected behavior: WifiConnectivityManager doesn't invoke
-     * ClientModeImpl.startConnectToNetwork().
+     * ClientModeManager.startConnectToNetwork().
      */
     @Test
     public void dropConnectAttemptIfConfigSpecifiedBssidDifferentFromScanResultBssid() {
         // Set up the candidate configuration such that it has a BSSID specified.
         WifiConfiguration candidate = generateWifiConfig(
-                0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
+                0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null,
+                WifiConfigurationTestUtil.SECURITY_NONE);
         candidate.BSSID = CANDIDATE_BSSID; // config specified
         ScanResult candidateScanResult = new ScanResult();
         candidateScanResult.SSID = CANDIDATE_SSID;
@@ -2472,13 +3387,14 @@
         when(mWifiNS.selectNetwork(any())).thenReturn(candidate);
 
         // Set screen to on
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(true);
 
         // Set WiFi to disconnected state
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
 
-        verify(mClientModeImpl, times(0)).startConnectToNetwork(
+        verify(mPrimaryClientModeManager, times(0)).startConnectToNetwork(
                 CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
     }
 
@@ -2488,19 +3404,21 @@
      * match it.
      *
      * Expected behavior: WifiConnectivityManager doesn't invoke
-     * ClientModeImpl.startRoamToNetwork().
+     * ClientModeManager.startRoamToNetwork().
      */
     @Test
     public void dropRoamingAttemptIfConfigSpecifiedBssidDifferentFromScanResultBssid() {
         // Mock the currently connected network which has the same networkID and
         // SSID as the one to be selected.
         WifiConfiguration currentNetwork = generateWifiConfig(
-                0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
+                TEST_CONNECTED_NETWORK_ID, 0, CANDIDATE_SSID, false, true, null, null,
+                WifiConfigurationTestUtil.SECURITY_NONE);
         when(mWifiConfigManager.getConfiguredNetwork(anyInt())).thenReturn(currentNetwork);
 
         // Set up the candidate configuration such that it has a BSSID specified.
         WifiConfiguration candidate = generateWifiConfig(
-                0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
+                TEST_CONNECTED_NETWORK_ID, 0, CANDIDATE_SSID, false, true, null, null,
+                WifiConfigurationTestUtil.SECURITY_NONE);
         candidate.BSSID = CANDIDATE_BSSID; // config specified
         ScanResult candidateScanResult = new ScanResult();
         candidateScanResult.SSID = CANDIDATE_SSID;
@@ -2513,9 +3431,9 @@
         setWifiStateConnected();
 
         // Set screen to on
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setScreenState(true);
 
-        verify(mClientModeImpl, times(0)).startRoamToNetwork(anyInt(), anyObject());
+        verify(mPrimaryClientModeManager, times(0)).startRoamToNetwork(anyInt(), anyObject());
     }
 
     /**
@@ -2584,8 +3502,8 @@
      * on a DBS supported device.
      *
      * Expected behavior: WifiConnectivityManager invokes
-     * {@link WifiNetworkSelector#selectNetwork(List, HashSet, WifiInfo, boolean, boolean, boolean)}
-     * after filtering out the scan results obtained via DBS scan.
+     * {@link WifiNetworkSelector#getCandidatesFromScan(List, Set, List, boolean, boolean, boolean)}
+     * boolean, boolean, boolean)} after filtering out the scan results obtained via DBS scan.
      */
     @Test
     public void filterScanResultsWithOneRadioChainInfoForNetworkSelectionIfConfigDisabled() {
@@ -2601,19 +3519,23 @@
         final List<ScanDetail> capturedScanDetails = new ArrayList<>();
         doAnswer(new AnswerWithArguments() {
             public List<WifiCandidates.Candidate> answer(
-                    List<ScanDetail> scanDetails, Set<String> bssidBlacklist, WifiInfo wifiInfo,
-                    boolean connected, boolean disconnected, boolean untrustedNetworkAllowed)
+                    List<ScanDetail> scanDetails, Set<String> bssidBlocklist,
+                    List<WifiNetworkSelector.ClientModeManagerState> cmmStates,
+                    boolean untrustedNetworkAllowed,
+                    boolean oemPaidNetworkAllowed, boolean oemPrivateNetworkAllowed)
                     throws Exception {
                 capturedScanDetails.addAll(scanDetails);
                 return null;
             }}).when(mWifiNS).getCandidatesFromScan(
-                    any(), any(), any(), anyBoolean(), anyBoolean(), anyBoolean());
+                    any(), any(), any(), anyBoolean(), eq(true), eq(false));
 
         mWifiConnectivityManager.setTrustedConnectionAllowed(true);
+        mWifiConnectivityManager.setOemPaidConnectionAllowed(true, new WorkSource());
         // Set WiFi to disconnected state with screen on which triggers a scan immediately.
-        mWifiConnectivityManager.setWifiEnabled(true);
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setWifiEnabled(true);
+        setScreenState(true);
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
 
         // We should have filtered out the 3rd scan result.
@@ -2639,7 +3561,7 @@
      * on a DBS supported device.
      *
      * Expected behavior: WifiConnectivityManager invokes
-     * {@link WifiNetworkSelector#selectNetwork(List, HashSet, WifiInfo, boolean, boolean, boolean)}
+     * {@link WifiNetworkSelector#selectNetwork(List)}
      * after filtering out the scan results obtained via DBS scan.
      */
     @Test
@@ -2656,19 +3578,23 @@
         final List<ScanDetail> capturedScanDetails = new ArrayList<>();
         doAnswer(new AnswerWithArguments() {
             public List<WifiCandidates.Candidate> answer(
-                    List<ScanDetail> scanDetails, Set<String> bssidBlacklist, WifiInfo wifiInfo,
-                    boolean connected, boolean disconnected, boolean untrustedNetworkAllowed)
+                    List<ScanDetail> scanDetails, Set<String> bssidBlocklist,
+                    List<WifiNetworkSelector.ClientModeManagerState> cmmStates,
+                    boolean untrustedNetworkAllowed,
+                    boolean oemPaidNetworkAllowed, boolean oemPrivateNetworkAllowed)
                     throws Exception {
                 capturedScanDetails.addAll(scanDetails);
                 return null;
             }}).when(mWifiNS).getCandidatesFromScan(
-                any(), any(), any(), anyBoolean(), anyBoolean(), anyBoolean());
+                any(), any(), any(), anyBoolean(), eq(false), eq(true));
 
         mWifiConnectivityManager.setTrustedConnectionAllowed(true);
+        mWifiConnectivityManager.setOemPrivateConnectionAllowed(true, new WorkSource());
         // Set WiFi to disconnected state with screen on which triggers a scan immediately.
-        mWifiConnectivityManager.setWifiEnabled(true);
-        mWifiConnectivityManager.handleScreenStateChanged(true);
+        setWifiEnabled(true);
+        setScreenState(true);
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
 
         // We should not filter any of the scan results.
@@ -2701,8 +3627,9 @@
         mWifiConnectivityManager = createConnectivityManager();
 
         // set wifi on & disconnected to trigger pno scans when auto-join is enabled.
-        mWifiConnectivityManager.setWifiEnabled(true);
+        setWifiEnabled(true);
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
 
         // Disable externally.
@@ -2739,8 +3666,9 @@
         mWifiConnectivityManager = createConnectivityManager();
 
         // set wifi on & disconnected to trigger pno scans when auto-join is enabled.
-        mWifiConnectivityManager.setWifiEnabled(true);
+        setWifiEnabled(true);
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
 
         // Enable trusted connection. This should trigger a pno scan for auto-join.
@@ -2766,15 +3694,68 @@
     }
 
     /**
+     * Verify that the increased PNO interval is used when power save is on.
+     */
+    @Test
+    public void testPnoIntervalPowerSaveEnabled() throws Exception {
+        when(mDeviceConfigFacade.isWifiBatterySaverEnabled()).thenReturn(true);
+        when(mPowerManagerService.isPowerSaveMode()).thenReturn(true);
+        verifyPnoScanWithInterval(
+                MOVING_PNO_SCAN_INTERVAL_MILLIS * POWER_SAVE_SCAN_INTERVAL_MULTIPLIER);
+    }
+
+    /**
+     * Verify that the normal PNO interval is used when power save is off.
+     */
+    @Test
+    public void testPnoIntervalPowerSaveDisabled() throws Exception {
+        when(mDeviceConfigFacade.isWifiBatterySaverEnabled()).thenReturn(true);
+        when(mPowerManagerService.isPowerSaveMode()).thenReturn(false);
+        verifyPnoScanWithInterval(MOVING_PNO_SCAN_INTERVAL_MILLIS);
+    }
+
+    /**
+     * Verify that the normal PNO interval is used when the power save feature is disabled.
+     */
+    @Test
+    public void testPnoIntervalPowerSaveEnabled_FeatureDisabled() throws Exception {
+        when(mDeviceConfigFacade.isWifiBatterySaverEnabled()).thenReturn(false);
+        when(mPowerManagerService.isPowerSaveMode()).thenReturn(true);
+        verifyPnoScanWithInterval(MOVING_PNO_SCAN_INTERVAL_MILLIS);
+    }
+
+
+    /**
+     * Verify PNO scan is started with the given scan interval.
+     */
+    private void verifyPnoScanWithInterval(int interval) throws Exception {
+        setWifiEnabled(true);
+        // starts a PNO scan
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
+                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+        mWifiConnectivityManager.setTrustedConnectionAllowed(true);
+
+        ArgumentCaptor<ScanSettings> scanSettingsCaptor = ArgumentCaptor.forClass(
+                ScanSettings.class);
+        InOrder inOrder = inOrder(mWifiScanner);
+
+        inOrder.verify(mWifiScanner).startDisconnectedPnoScan(
+                scanSettingsCaptor.capture(), any(), any(), any());
+        assertEquals(interval, scanSettingsCaptor.getValue().periodInMs);
+    }
+
+    /**
      * Change device mobility state in the middle of a PNO scan. PNO scan should stop, then restart
      * with the updated scan period.
      */
     @Test
     public void changeDeviceMobilityStateDuringScan() {
-        mWifiConnectivityManager.setWifiEnabled(true);
+        setWifiEnabled(true);
 
         // starts a PNO scan
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
         mWifiConnectivityManager.setTrustedConnectionAllowed(true);
 
@@ -2805,10 +3786,11 @@
      */
     @Test
     public void changeDeviceMobilityStateDuringScanWithSameScanPeriod() {
-        mWifiConnectivityManager.setWifiEnabled(true);
+        setWifiEnabled(true);
 
         // starts a PNO scan
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
         mWifiConnectivityManager.setTrustedConnectionAllowed(true);
 
@@ -2833,7 +3815,7 @@
     @Test
     public void setDeviceMobilityStateBeforePnoScan() {
         // ensure no PNO scan running
-        mWifiConnectivityManager.setWifiEnabled(true);
+        setWifiEnabled(true);
         setWifiStateConnected();
 
         // initial connectivity state uses moving PNO scan interval, now set it to stationary
@@ -2846,6 +3828,7 @@
 
         // starts a PNO scan
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
         mWifiConnectivityManager.setTrustedConnectionAllowed(true);
 
@@ -2868,7 +3851,7 @@
         InOrder inOrder = inOrder(mWifiMetrics);
 
         mWifiConnectivityManager = createConnectivityManager();
-        mWifiConnectivityManager.setWifiEnabled(true);
+        setWifiEnabled(true);
 
         // change mobility state while no PNO scans running
         mWifiConnectivityManager.setDeviceMobilityState(
@@ -2878,6 +3861,7 @@
 
         // starts a PNO scan
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
         mWifiConnectivityManager.setTrustedConnectionAllowed(true);
         inOrder.verify(mWifiMetrics).logPnoScanStart();
@@ -2917,7 +3901,7 @@
     @Test
     public void verifyWifiChannelUtilizationRefreshedAfterScanResults() {
         WifiLinkLayerStats llstats = new WifiLinkLayerStats();
-        when(mClientModeImpl.getWifiLinkLayerStats()).thenReturn(llstats);
+        when(mPrimaryClientModeManager.getWifiLinkLayerStats()).thenReturn(llstats);
 
         // Force a connectivity scan
         mWifiConnectivityManager.forceConnectivityScan(WIFI_WORK_SOURCE);
@@ -2933,10 +3917,10 @@
     public void verifyWifiChannelUtilizationInitAfterWifiToggle() {
         verify(mWifiChannelUtilization, times(1)).init(null);
         WifiLinkLayerStats llstats = new WifiLinkLayerStats();
-        when(mClientModeImpl.getWifiLinkLayerStats()).thenReturn(llstats);
+        when(mPrimaryClientModeManager.getWifiLinkLayerStats()).thenReturn(llstats);
 
-        mWifiConnectivityManager.setWifiEnabled(false);
-        mWifiConnectivityManager.setWifiEnabled(true);
+        setWifiEnabled(false);
+        setWifiEnabled(true);
         verify(mWifiChannelUtilization, times(1)).init(llstats);
     }
 
@@ -2946,7 +3930,7 @@
     @Test
     public void verifyWifiChannelUtilizationSetMobilityState() {
         WifiLinkLayerStats llstats = new WifiLinkLayerStats();
-        when(mClientModeImpl.getWifiLinkLayerStats()).thenReturn(llstats);
+        when(mPrimaryClientModeManager.getWifiLinkLayerStats()).thenReturn(llstats);
 
         mWifiConnectivityManager.setDeviceMobilityState(
                 WifiManager.DEVICE_MOBILITY_STATE_HIGH_MVMT);
@@ -2959,17 +3943,6 @@
     }
 
     /**
-     *  Verify that WifiNetworkSelector sets bluetoothConnected correctly
-     */
-    @Test
-    public void verifyWifiNetworkSelectorSetBluetoothConnected() {
-        mWifiConnectivityManager.setBluetoothConnected(true);
-        verify(mWifiNS).setBluetoothConnected(true);
-        mWifiConnectivityManager.setBluetoothConnected(false);
-        verify(mWifiNS).setBluetoothConnected(false);
-    }
-
-    /**
      *  Verify that WifiChannelUtilization is updated
      */
     @Test
@@ -2979,15 +3952,15 @@
         mWifiConnectivityManager.forceConnectivityScan(WIFI_WORK_SOURCE);
         verify(mWifiScanner).startScan(any(), any(), any(), any());
 
-        // Auto-join disabled
+        // Auto-join disabled, no new scans
         mWifiConnectivityManager.setAutoJoinEnabledExternal(false);
         mWifiConnectivityManager.forceConnectivityScan(WIFI_WORK_SOURCE);
-        verify(mWifiScanner, times(2)).startScan(any(), any(), any(), any());
+        verify(mWifiScanner, times(1)).startScan(any(), any(), any(), any());
 
         // Wifi disabled, no new scans
-        mWifiConnectivityManager.setWifiEnabled(false);
+        setWifiEnabled(false);
         mWifiConnectivityManager.forceConnectivityScan(WIFI_WORK_SOURCE);
-        verify(mWifiScanner, times(2)).startScan(any(), any(), any(), any());
+        verify(mWifiScanner, times(1)).startScan(any(), any(), any(), any());
     }
 
     /**
@@ -3219,10 +4192,11 @@
 
     @Test
     public void restartPnoScanForNetworkChanges() {
-        mWifiConnectivityManager.setWifiEnabled(true);
+        setWifiEnabled(true);
 
         // starts a PNO scan
         mWifiConnectivityManager.handleConnectionStateChanged(
+                mPrimaryClientModeManager,
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
         mWifiConnectivityManager.setTrustedConnectionAllowed(true);
 
@@ -3243,4 +4217,100 @@
         inOrder.verify(mWifiScanner).stopPnoScan(any());
         inOrder.verify(mWifiScanner).startDisconnectedPnoScan(any(), any(), any(), any());
     }
+
+    @Test
+    public void includeSecondaryStaWhenPresentInGetCandidatesFromScan() {
+        // Set screen to on
+        setScreenState(true);
+
+        ConcreteClientModeManager primaryCmm = mock(ConcreteClientModeManager.class);
+        WifiInfo wifiInfo1 = mock(WifiInfo.class);
+        when(primaryCmm.getInterfaceName()).thenReturn("wlan0");
+        when(primaryCmm.getRole()).thenReturn(ROLE_CLIENT_PRIMARY);
+        when(primaryCmm.isConnected()).thenReturn(false);
+        when(primaryCmm.isDisconnected()).thenReturn(true);
+        when(primaryCmm.syncRequestConnectionInfo()).thenReturn(wifiInfo1);
+
+        ConcreteClientModeManager secondaryCmm = mock(ConcreteClientModeManager.class);
+        WifiInfo wifiInfo2 = mock(WifiInfo.class);
+        when(secondaryCmm.getInterfaceName()).thenReturn("wlan1");
+        when(secondaryCmm.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_LONG_LIVED);
+        when(secondaryCmm.isConnected()).thenReturn(false);
+        when(secondaryCmm.isDisconnected()).thenReturn(true);
+        when(secondaryCmm.syncRequestConnectionInfo()).thenReturn(wifiInfo2);
+
+        when(mActiveModeWarden.getInternetConnectivityClientModeManagers())
+                .thenReturn(Arrays.asList(primaryCmm, secondaryCmm));
+
+        // Set WiFi to disconnected state to trigger scan
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                primaryCmm,
+                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+
+        List<WifiNetworkSelector.ClientModeManagerState> expectedCmmStates =
+                Arrays.asList(new WifiNetworkSelector.ClientModeManagerState(
+                                "wlan0", false, true, wifiInfo1),
+                        new WifiNetworkSelector.ClientModeManagerState(
+                                "wlan1", false, true, wifiInfo2));
+        verify(mWifiNS).getCandidatesFromScan(any(), any(),
+                eq(expectedCmmStates), anyBoolean(), anyBoolean(), anyBoolean());
+    }
+
+    @Test
+    public void includeSecondaryStaWhenNotPresentButAvailableInGetCandidatesFromScan() {
+        // Set screen to on
+        setScreenState(true);
+        // set OEM paid connection allowed.
+        WorkSource oemPaidWs = new WorkSource();
+        mWifiConnectivityManager.setOemPaidConnectionAllowed(true, oemPaidWs);
+
+        ConcreteClientModeManager primaryCmm = mock(ConcreteClientModeManager.class);
+        WifiInfo wifiInfo1 = mock(WifiInfo.class);
+        when(primaryCmm.getInterfaceName()).thenReturn("wlan0");
+        when(primaryCmm.getRole()).thenReturn(ROLE_CLIENT_PRIMARY);
+        when(primaryCmm.isConnected()).thenReturn(false);
+        when(primaryCmm.isDisconnected()).thenReturn(true);
+        when(primaryCmm.syncRequestConnectionInfo()).thenReturn(wifiInfo1);
+
+        when(mActiveModeWarden.getInternetConnectivityClientModeManagers())
+                .thenReturn(Arrays.asList(primaryCmm));
+        // Second STA creation is allowed.
+        when(mActiveModeWarden.canRequestMoreClientModeManagersInRole(
+                eq(oemPaidWs), eq(ROLE_CLIENT_SECONDARY_LONG_LIVED))).thenReturn(true);
+
+        // Set WiFi to disconnected state to trigger scan
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                primaryCmm,
+                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+
+        List<WifiNetworkSelector.ClientModeManagerState> expectedCmmStates =
+                Arrays.asList(new WifiNetworkSelector.ClientModeManagerState(
+                        "wlan0", false, true, wifiInfo1),
+                new WifiNetworkSelector.ClientModeManagerState(
+                        "unknown", false, true, new WifiInfo()));
+        verify(mWifiNS).getCandidatesFromScan(any(), any(),
+                eq(expectedCmmStates), anyBoolean(), anyBoolean(), anyBoolean());
+    }
+
+    private void setWifiEnabled(boolean enable) {
+        ActiveModeWarden.ModeChangeCallback modeChangeCallback =
+                mModeChangeCallbackCaptor.getValue();
+        assertNotNull(modeChangeCallback);
+        if (enable) {
+            when(mActiveModeWarden.getInternetConnectivityClientModeManagers())
+                    .thenReturn(Arrays.asList(mPrimaryClientModeManager));
+            modeChangeCallback.onActiveModeManagerAdded(mPrimaryClientModeManager);
+        } else {
+            when(mActiveModeWarden.getInternetConnectivityClientModeManagers())
+                    .thenReturn(Arrays.asList());
+            modeChangeCallback.onActiveModeManagerRemoved(mPrimaryClientModeManager);
+        }
+    }
+
+    private void setScreenState(boolean screenOn) {
+        BroadcastReceiver broadcastReceiver = mBroadcastReceiverCaptor.getValue();
+        assertNotNull(broadcastReceiver);
+        Intent intent = new Intent(screenOn  ? ACTION_SCREEN_ON : ACTION_SCREEN_OFF);
+        broadcastReceiver.onReceive(mContext, intent);
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiCountryCodeTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiCountryCodeTest.java
index 6fb0354..fda21db 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiCountryCodeTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiCountryCodeTest.java
@@ -16,19 +16,16 @@
 
 package com.android.server.wifi;
 
-import static org.junit.Assert.*;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyString;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_PRIMARY;
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SECONDARY_LONG_LIVED;
+import static com.android.server.wifi.WifiSettingsConfigStore.WIFI_DEFAULT_COUNTRY_CODE;
 
-import android.content.BroadcastReceiver;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.doAnswer;
+
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
 import android.content.Context;
-import android.content.Intent;
-import android.os.Handler;
 import android.telephony.TelephonyManager;
 
 import androidx.test.filters.SmallTest;
@@ -38,6 +35,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -52,18 +50,29 @@
 public class WifiCountryCodeTest extends WifiBaseTest {
 
     private static final String TAG = "WifiCountryCodeTest";
+    private static final String TEST_COUNTRY_CODE = "JP";
     private String mDefaultCountryCode = "US";
     private String mTelephonyCountryCode = "JP";
     private boolean mRevertCountryCodeOnCellularLoss = true;
     @Mock Context mContext;
     MockResources mResources = new MockResources();
     @Mock TelephonyManager mTelephonyManager;
-    @Mock Handler mHandler;
+    @Mock ActiveModeWarden mActiveModeWarden;
+    @Mock ConcreteClientModeManager mClientModeManager;
+    @Mock ClientModeImplMonitor mClientModeImplMonitor;
     @Mock WifiNative mWifiNative;
-    private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor =
-            ArgumentCaptor.forClass(BroadcastReceiver.class);
+    @Mock WifiSettingsConfigStore mSettingsConfigStore;
     private WifiCountryCode mWifiCountryCode;
 
+    @Captor
+    private ArgumentCaptor<ActiveModeWarden.ModeChangeCallback> mModeChangeCallbackCaptor;
+    @Captor
+    private ArgumentCaptor<ClientModeImplListener> mClientModeImplListenerCaptor;
+    @Captor
+    private ArgumentCaptor<WifiCountryCode.ChangeListener> mChangeListenerCaptor;
+    @Captor
+    private ArgumentCaptor<String> mSetCountryCodeCaptor;
+
     /**
      * Setup test.
      */
@@ -71,10 +80,27 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        when(mWifiNative.setCountryCode(any(), anyString())).thenReturn(true);
+        when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_PRIMARY);
+        when(mClientModeManager.setCountryCode(anyString())).thenReturn(true);
         when(mContext.getSystemService(Context.TELEPHONY_SERVICE))
                 .thenReturn(mTelephonyManager);
 
+        doAnswer((invocation) -> {
+            mChangeListenerCaptor.getValue()
+                    .onDriverCountryCodeChanged(mSetCountryCodeCaptor.getValue());
+            return true;
+        }).when(mClientModeManager).setCountryCode(
+                    mSetCountryCodeCaptor.capture());
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(WifiSettingsConfigStore.Key<String> key, Object countryCode) {
+                when(mSettingsConfigStore.get(WIFI_DEFAULT_COUNTRY_CODE))
+                        .thenReturn((String) countryCode);
+            }
+        }).when(mSettingsConfigStore).put(eq(WIFI_DEFAULT_COUNTRY_CODE), any(String.class));
+
+        when(mSettingsConfigStore.get(WIFI_DEFAULT_COUNTRY_CODE)).thenReturn(mDefaultCountryCode);
+
         createWifiCountryCode();
     }
 
@@ -84,18 +110,16 @@
         when(mContext.getResources()).thenReturn(mResources);
         mWifiCountryCode = new WifiCountryCode(
                 mContext,
-                mHandler,
+                mActiveModeWarden,
+                mClientModeImplMonitor,
                 mWifiNative,
-                mDefaultCountryCode);
-        verify(mContext, atLeastOnce()).registerReceiver(
-                mBroadcastReceiverCaptor.capture(), any(), any(), any());
-    }
-
-    private void sendCountryCodeChangedBroadcast(String countryCode) {
-        Intent intent = new Intent(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED);
-        intent.putExtra(TelephonyManager.EXTRA_NETWORK_COUNTRY, countryCode);
-        assertNotNull(mBroadcastReceiverCaptor.getValue());
-        mBroadcastReceiverCaptor.getValue().onReceive(mContext, intent);
+                mSettingsConfigStore);
+        verify(mActiveModeWarden, atLeastOnce()).registerModeChangeCallback(
+                    mModeChangeCallbackCaptor.capture());
+        verify(mClientModeImplMonitor, atLeastOnce()).registerListener(
+                mClientModeImplListenerCaptor.capture());
+        verify(mWifiNative, atLeastOnce()).registerCountryCodeEventListener(
+                mChangeListenerCaptor.capture());
     }
 
     /**
@@ -105,10 +129,10 @@
     @Test
     public void useDefaultCountryCode() throws Exception {
         // Supplicant started.
-        mWifiCountryCode.setReadyForChange(true);
+        mModeChangeCallbackCaptor.getValue().onActiveModeManagerAdded(mClientModeManager);
         // Wifi get L2 connected.
-        mWifiCountryCode.setReadyForChange(false);
-        verify(mWifiNative).setCountryCode(any(), anyString());
+        mClientModeImplListenerCaptor.getValue().onConnectionStart(mClientModeManager);
+        verify(mClientModeManager).setCountryCode(anyString());
         assertEquals(mDefaultCountryCode, mWifiCountryCode.getCountryCodeSentToDriver());
     }
 
@@ -120,10 +144,10 @@
     public void useTelephonyCountryCodeOnBootup() throws Exception {
         when(mTelephonyManager.getNetworkCountryIso()).thenReturn(mTelephonyCountryCode);
         // Supplicant started.
-        mWifiCountryCode.setReadyForChange(true);
+        mModeChangeCallbackCaptor.getValue().onActiveModeManagerAdded(mClientModeManager);
         // Wifi get L2 connected.
-        mWifiCountryCode.setReadyForChange(false);
-        verify(mWifiNative).setCountryCode(any(), anyString());
+        mClientModeImplListenerCaptor.getValue().onConnectionStart(mClientModeManager);
+        verify(mClientModeManager).setCountryCode(anyString());
         assertEquals(mTelephonyCountryCode, mWifiCountryCode.getCountryCodeSentToDriver());
     }
 
@@ -133,13 +157,13 @@
      */
     @Test
     public void useTelephonyCountryCodeOnChange() throws Exception {
-        sendCountryCodeChangedBroadcast(mTelephonyCountryCode);
+        mWifiCountryCode.setTelephonyCountryCodeAndUpdate(mTelephonyCountryCode);
         assertEquals(null, mWifiCountryCode.getCountryCodeSentToDriver());
         // Supplicant started.
-        mWifiCountryCode.setReadyForChange(true);
+        mModeChangeCallbackCaptor.getValue().onActiveModeManagerAdded(mClientModeManager);
         // Wifi get L2 connected.
-        mWifiCountryCode.setReadyForChange(false);
-        verify(mWifiNative).setCountryCode(any(), anyString());
+        mClientModeImplListenerCaptor.getValue().onConnectionStart(mClientModeManager);
+        verify(mClientModeManager).setCountryCode(anyString());
         assertEquals(mTelephonyCountryCode, mWifiCountryCode.getCountryCodeSentToDriver());
     }
 
@@ -149,17 +173,60 @@
      */
     @Test
     public void telephonyCountryCodeChangeAfterSupplicantStarts() throws Exception {
-        // Supplicant starts.
-        mWifiCountryCode.setReadyForChange(true);
+        // Start in scan only mode.
+        mModeChangeCallbackCaptor.getValue().onActiveModeManagerAdded(mClientModeManager);
         assertEquals(mDefaultCountryCode, mWifiCountryCode.getCountryCodeSentToDriver());
+
+        // Supplicant starts.
+        when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_PRIMARY);
+        mModeChangeCallbackCaptor.getValue().onActiveModeManagerRoleChanged(mClientModeManager);
+        assertEquals(mDefaultCountryCode, mWifiCountryCode.getCountryCodeSentToDriver());
+
         // Telephony country code arrives.
-        sendCountryCodeChangedBroadcast(mTelephonyCountryCode);
+        mWifiCountryCode.setTelephonyCountryCodeAndUpdate(mTelephonyCountryCode);
         // Wifi get L2 connected.
-        mWifiCountryCode.setReadyForChange(false);
-        verify(mWifiNative, times(2)).setCountryCode(any(), anyString());
+        mClientModeImplListenerCaptor.getValue().onConnectionStart(mClientModeManager);
+
+        verify(mClientModeManager, times(3)).setCountryCode(anyString());
         assertEquals(mTelephonyCountryCode, mWifiCountryCode.getCountryCodeSentToDriver());
     }
 
+
+    /**
+     * Test if we receive country code from Telephony after supplicant stop.
+     * @throws Exception
+     */
+    @Test
+    public void telephonyCountryCodeChangeAfterSupplicantStop() throws Exception {
+        // Start in scan only mode.
+        mModeChangeCallbackCaptor.getValue().onActiveModeManagerAdded(mClientModeManager);
+        assertEquals(mDefaultCountryCode, mWifiCountryCode.getCountryCodeSentToDriver());
+
+        // Supplicant starts.
+        when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_PRIMARY);
+        mModeChangeCallbackCaptor.getValue().onActiveModeManagerRoleChanged(mClientModeManager);
+        assertEquals(mDefaultCountryCode, mWifiCountryCode.getCountryCodeSentToDriver());
+
+        // Telephony country code arrives.
+        mWifiCountryCode.setTelephonyCountryCodeAndUpdate(mTelephonyCountryCode);
+        // Wifi get L2 connected.
+        mClientModeImplListenerCaptor.getValue().onConnectionStart(mClientModeManager);
+
+        verify(mClientModeManager, times(3)).setCountryCode(anyString());
+        assertEquals(mTelephonyCountryCode, mWifiCountryCode.getCountryCodeSentToDriver());
+
+        // Remove mode manager.
+        mModeChangeCallbackCaptor.getValue().onActiveModeManagerRemoved(mClientModeManager);
+
+        // Send Telephony country code again - should be ignored.
+        mWifiCountryCode.setTelephonyCountryCodeAndUpdate(mTelephonyCountryCode);
+        verify(mClientModeManager, times(3)).setCountryCode(anyString());
+        assertEquals(mTelephonyCountryCode, mWifiCountryCode.getCountryCodeSentToDriver());
+
+        // Now try removing the mode manager again - should not crash.
+        mModeChangeCallbackCaptor.getValue().onActiveModeManagerRemoved(mClientModeManager);
+    }
+
     /**
      * Test if we receive country code from Telephony after we get L2 connected.
      * @throws Exception
@@ -167,16 +234,58 @@
     @Test
     public void telephonyCountryCodeChangeAfterL2Connected() throws Exception {
         // Supplicant starts.
-        mWifiCountryCode.setReadyForChange(true);
+        mModeChangeCallbackCaptor.getValue().onActiveModeManagerAdded(mClientModeManager);
         // Wifi get L2 connected.
-        mWifiCountryCode.setReadyForChange(false);
+        mClientModeImplListenerCaptor.getValue().onConnectionStart(mClientModeManager);
         // Telephony country code arrives.
-        sendCountryCodeChangedBroadcast(mTelephonyCountryCode);
+        mWifiCountryCode.setTelephonyCountryCodeAndUpdate(mTelephonyCountryCode);
         // Telephony coutry code won't be applied at this time.
         assertEquals(mDefaultCountryCode, mWifiCountryCode.getCountryCodeSentToDriver());
-        mWifiCountryCode.setReadyForChange(true);
+
+        mClientModeImplListenerCaptor.getValue().onConnectionEnd(mClientModeManager);
         // Telephony coutry is applied after supplicant is ready.
-        verify(mWifiNative, times(2)).setCountryCode(any(), anyString());
+        verify(mClientModeManager, times(2)).setCountryCode(anyString());
+        assertEquals(mTelephonyCountryCode, mWifiCountryCode.getCountryCodeSentToDriver());
+    }
+
+    /**
+     * Test if we receive country code from Telephony after we get L2 connected on 2 STA interfaces.
+     * @throws Exception
+     */
+    @Test
+    public void telephonyCountryCodeChangeAfterL2ConnectedOnTwoClientModeManager()
+            throws Exception {
+        // Primary CMM
+        mModeChangeCallbackCaptor.getValue().onActiveModeManagerAdded(mClientModeManager);
+        // Wifi get L2 connected on the primary.
+        mClientModeImplListenerCaptor.getValue().onConnectionStart(mClientModeManager);
+        // Telephony country code arrives.
+        mWifiCountryCode.setTelephonyCountryCodeAndUpdate(mTelephonyCountryCode);
+        // Telephony country code won't be applied at this time.
+        assertEquals(mDefaultCountryCode, mWifiCountryCode.getCountryCodeSentToDriver());
+
+        // Create secondary CMM
+        ConcreteClientModeManager secondaryClientModeManager =
+                mock(ConcreteClientModeManager.class);
+        when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_LONG_LIVED);
+        mModeChangeCallbackCaptor.getValue().onActiveModeManagerAdded(secondaryClientModeManager);
+        // Wifi get L2 connected on the secondary.
+        mClientModeImplListenerCaptor.getValue().onConnectionStart(secondaryClientModeManager);
+
+        // Telephony country code still not applied.
+        assertEquals(mDefaultCountryCode, mWifiCountryCode.getCountryCodeSentToDriver());
+
+        // Disconnection on primary
+        mClientModeImplListenerCaptor.getValue().onConnectionEnd(mClientModeManager);
+
+        // Telephony country code still not applied.
+        assertEquals(mDefaultCountryCode, mWifiCountryCode.getCountryCodeSentToDriver());
+
+        // Disconnection on secondary
+        mClientModeImplListenerCaptor.getValue().onConnectionEnd(secondaryClientModeManager);
+
+        // Telephony coutry is applied after both of them are disconnected.
+        verify(mClientModeManager, times(2)).setCountryCode(anyString());
         assertEquals(mTelephonyCountryCode, mWifiCountryCode.getCountryCodeSentToDriver());
     }
 
@@ -190,10 +299,10 @@
     @Test
     public void resetCountryCodeWhenOutOfService() throws Exception {
         assertEquals(mDefaultCountryCode, mWifiCountryCode.getCountryCode());
-        sendCountryCodeChangedBroadcast(mTelephonyCountryCode);
+        mWifiCountryCode.setTelephonyCountryCodeAndUpdate(mTelephonyCountryCode);
         assertEquals(mTelephonyCountryCode, mWifiCountryCode.getCountryCode());
         // Out of service.
-        sendCountryCodeChangedBroadcast("");
+        mWifiCountryCode.setTelephonyCountryCodeAndUpdate("");
         assertEquals(mDefaultCountryCode, mWifiCountryCode.getCountryCode());
     }
 
@@ -212,10 +321,10 @@
         createWifiCountryCode();
 
         assertEquals(mDefaultCountryCode, mWifiCountryCode.getCountryCode());
-        sendCountryCodeChangedBroadcast(mTelephonyCountryCode);
+        mWifiCountryCode.setTelephonyCountryCodeAndUpdate(mTelephonyCountryCode);
         assertEquals(mTelephonyCountryCode, mWifiCountryCode.getCountryCode());
         // Out of service.
-        sendCountryCodeChangedBroadcast("");
+        mWifiCountryCode.setTelephonyCountryCodeAndUpdate("");
         assertEquals(mTelephonyCountryCode, mWifiCountryCode.getCountryCode());
     }
 
@@ -237,12 +346,12 @@
         Locale.setDefault(new Locale("tr"));
 
         // Trigger a country code change using the OEM country code.
-        mWifiCountryCode.setReadyForChange(true);
-        verify(mWifiNative).setCountryCode(any(), eq(oemCountryCodeUpper));
+        mModeChangeCallbackCaptor.getValue().onActiveModeManagerAdded(mClientModeManager);
+        verify(mClientModeManager).setCountryCode(oemCountryCodeUpper);
 
         // Now trigger a country code change using the telephony country code.
-        sendCountryCodeChangedBroadcast(telephonyCountryCodeLower);
-        verify(mWifiNative).setCountryCode(any(), eq(telephonyCountryCodeUpper));
+        mWifiCountryCode.setTelephonyCountryCodeAndUpdate(telephonyCountryCodeLower);
+        verify(mClientModeManager).setCountryCode(telephonyCountryCodeUpper);
     }
     /**
      * Verifies that dump() does not fail
@@ -260,10 +369,39 @@
 
         assertTrue(dumpCountryCodeStr.contains("mDriverCountryCode"));
         assertTrue(dumpCountryCodeStr.contains("mTelephonyCountryCode"));
-        assertTrue(dumpCountryCodeStr.contains("mDefaultCountryCode"));
+        assertTrue(dumpCountryCodeStr.contains("DefaultCountryCode(system property)"));
+        assertTrue(dumpCountryCodeStr.contains("DefaultCountryCode(config store)"));
         assertTrue(dumpCountryCodeStr.contains("mTelephonyCountryTimestamp"));
         assertTrue(dumpCountryCodeStr.contains("mDriverCountryTimestamp"));
         assertTrue(dumpCountryCodeStr.contains("mReadyTimestamp"));
         assertTrue(dumpCountryCodeStr.contains("mReady"));
     }
+
+    /**
+     * Test set Default country code
+     * @throws Exception
+     */
+    @Test
+    public void setDefaultCountryCode() throws Exception {
+        // Supplicant started.
+        mModeChangeCallbackCaptor.getValue().onActiveModeManagerAdded(mClientModeManager);
+        mWifiCountryCode.setDefaultCountryCode(TEST_COUNTRY_CODE);
+        verify(mClientModeManager).setCountryCode(eq(TEST_COUNTRY_CODE));
+        assertEquals(TEST_COUNTRY_CODE, mWifiCountryCode.getCountryCodeSentToDriver());
+        verify(mSettingsConfigStore).put(eq(WIFI_DEFAULT_COUNTRY_CODE), eq(TEST_COUNTRY_CODE));
+        assertEquals(TEST_COUNTRY_CODE, mSettingsConfigStore.get(WIFI_DEFAULT_COUNTRY_CODE));
+    }
+
+    /**
+     * Test is valid country code
+     * @throws Exception
+     */
+    @Test
+    public void testValidCountryCode() throws Exception {
+        assertEquals(WifiCountryCode.isValid(null), false);
+        assertEquals(WifiCountryCode.isValid("JPUS"), false);
+        assertEquals(WifiCountryCode.isValid("JP"), true);
+        assertEquals(WifiCountryCode.isValid("00"), true);
+        assertEquals(WifiCountryCode.isValid("0U"), true);
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiDataStallTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiDataStallTest.java
index de1e5cb..16b9d2b 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiDataStallTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiDataStallTest.java
@@ -16,13 +16,18 @@
 
 package com.android.server.wifi;
 
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_PRIMARY;
+import static com.android.server.wifi.WifiMetricsTest.TEST_IFACE_NAME;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -37,8 +42,7 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.server.wifi.proto.WifiStatsLog;
+import com.android.server.wifi.ActiveModeWarden.PrimaryClientModeManagerChangedCallback;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.WifiIsUnusableEvent;
 import com.android.wifi.resources.R;
 
@@ -48,7 +52,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.MockitoSession;
-import org.mockito.quality.Strictness;
 
 /**
  * Unit tests for {@link com.android.server.wifi.WifiDataStall}.
@@ -73,7 +76,13 @@
     @Mock Handler mHandler;
     @Mock ThroughputPredictor mThroughputPredictor;
     @Mock WifiNative.ConnectionCapabilities mCapabilities;
+    @Mock ActiveModeWarden mActiveModeWarden;
+    @Mock ClientModeImplMonitor mClientModeImplMonitor;
+    @Mock ClientModeManager mClientModeManager;
 
+    private ActiveModeWarden.ModeChangeCallback mModeChangeCallback;
+    private PrimaryClientModeManagerChangedCallback mPrimaryModeChangeCallback;
+    private ClientModeImplListener mClientModeImplListener;
     private final WifiLinkLayerStats mOldLlStats = new WifiLinkLayerStats();
     private final WifiLinkLayerStats mNewLlStats = new WifiLinkLayerStats();
     private MockitoSession mSession;
@@ -88,6 +97,7 @@
         TestLooper looper = new TestLooper();
         when(mContext.getResources()).thenReturn(mMockResources);
         when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephonyManager);
+        when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager);
 
         mMockResources.setInteger(
                 R.integer.config_wifiPollRssiIntervalMilliseconds,
@@ -135,7 +145,7 @@
 
         mWifiDataStall = new WifiDataStall(mFrameworkFacade, mWifiMetrics, mContext,
                 mDeviceConfigFacade, mWifiChannelUtilization, mClock, mHandler,
-                mThroughputPredictor);
+                mThroughputPredictor, mActiveModeWarden, mClientModeImplMonitor);
         mOldLlStats.txmpdu_be = 1000;
         mOldLlStats.retries_be = 1000;
         mOldLlStats.lostmpdu_be = 3000;
@@ -145,7 +155,7 @@
         mNewLlStats.txmpdu_be = 2 * mOldLlStats.txmpdu_be;
         mNewLlStats.retries_be = 10 * mOldLlStats.retries_be;
         mNewLlStats.lostmpdu_be = mOldLlStats.lostmpdu_be;
-        mNewLlStats.rxmpdu_be = mOldLlStats.rxmpdu_be + 100;
+        mNewLlStats.rxmpdu_be = mOldLlStats.rxmpdu_be + 130;
         mNewLlStats.timeStampInMs = mOldLlStats.timeStampInMs
                 + WifiDataStall.MAX_MS_DELTA_FOR_DATA_STALL - 1;
         when(mWifiChannelUtilization.getUtilizationRatio(anyInt())).thenReturn(10);
@@ -153,8 +163,25 @@
                 .thenReturn(50);
         when(mThroughputPredictor.predictRxThroughput(any(), anyInt(), anyInt(), anyInt()))
                 .thenReturn(150);
-        mWifiDataStall.init();
-        mWifiDataStall.setConnectionCapabilities(mCapabilities);
+        when(mActiveModeWarden.getPrimaryClientModeManager()).thenReturn(mClientModeManager);
+
+        ArgumentCaptor<ActiveModeWarden.ModeChangeCallback> modeChangeCallbackArgumentCaptor =
+                ArgumentCaptor.forClass(ActiveModeWarden.ModeChangeCallback.class);
+        verify(mActiveModeWarden).registerModeChangeCallback(
+                modeChangeCallbackArgumentCaptor.capture());
+        mModeChangeCallback = modeChangeCallbackArgumentCaptor.getValue();
+        ArgumentCaptor<PrimaryClientModeManagerChangedCallback>
+                primaryModeChangeCallbackArgumentCaptor =
+                ArgumentCaptor.forClass(PrimaryClientModeManagerChangedCallback.class);
+        verify(mActiveModeWarden).registerPrimaryClientModeManagerChangedCallback(
+                primaryModeChangeCallbackArgumentCaptor.capture());
+        mPrimaryModeChangeCallback = primaryModeChangeCallbackArgumentCaptor.getValue();
+        ArgumentCaptor<ClientModeImplListener> clientModeImplListenerArgumentCaptor =
+                ArgumentCaptor.forClass(ClientModeImplListener.class);
+        verify(mClientModeImplMonitor).registerListener(
+                clientModeImplListenerArgumentCaptor.capture());
+        mClientModeImplListener = clientModeImplListenerArgumentCaptor.getValue();
+        mPrimaryModeChangeCallback.onChange(null, mock(ConcreteClientModeManager.class));
         setUpWifiBytes(1, 1);
     }
 
@@ -189,66 +216,148 @@
         return dataConnectionStateListener;
     }
 
+    private void setWifiEnabled(boolean enabled) {
+        if (enabled) {
+            when(mActiveModeWarden.getPrimaryClientModeManagerNullable())
+                    .thenReturn(mock(ConcreteClientModeManager.class));
+            mModeChangeCallback.onActiveModeManagerAdded(mock(ConcreteClientModeManager.class));
+        } else {
+            when(mActiveModeWarden.getPrimaryClientModeManagerNullable()).thenReturn(null);
+            mModeChangeCallback.onActiveModeManagerRemoved(mock(ConcreteClientModeManager.class));
+        }
+    }
+
     /**
      * Test cellular data connection is on and then off
      */
     @Test
     public void testCellularDataConnectionOnOff() throws Exception {
-        mWifiDataStall.disablePhoneStateListener();
-        mWifiDataStall.enablePhoneStateListener();
+        setWifiEnabled(false);
+        setWifiEnabled(true);
+
         PhoneStateListener phoneStateListener = mockPhoneStateListener();
         phoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
                 TelephonyManager.NETWORK_TYPE_LTE);
         assertEquals(true, mWifiDataStall.isCellularDataAvailable());
+        verify(mClientModeManager).onCellularConnectivityChanged(
+                WifiDataStall.CELLULAR_DATA_AVAILABLE);
         phoneStateListener.onDataConnectionStateChanged(
                 TelephonyManager.DATA_DISCONNECTED, TelephonyManager.NETWORK_TYPE_LTE);
         assertEquals(false, mWifiDataStall.isCellularDataAvailable());
-        mWifiDataStall.disablePhoneStateListener();
+        verify(mClientModeManager).onCellularConnectivityChanged(
+                WifiDataStall.CELLULAR_DATA_NOT_AVAILABLE);
+        setWifiEnabled(false);
         verify(mTelephonyManager, times(1)).listen(phoneStateListener,
                 PhoneStateListener.LISTEN_NONE);
-        mWifiDataStall.disablePhoneStateListener();
+        setWifiEnabled(false);
         verify(mTelephonyManager, times(1)).listen(phoneStateListener,
                 PhoneStateListener.LISTEN_NONE);
     }
 
     /**
+     * Verify that resetPhoneStateListener re-registers the phoneStateListener so that it is
+     * listening to changes to the default data subription.
+     */
+    @Test
+    public void testCellularDataListenerReset() throws Exception {
+        setWifiEnabled(false);
+        setWifiEnabled(true);
+
+        PhoneStateListener phoneStateListener = mockPhoneStateListener();
+        // Verify 0 unregister and 1 register call to TelephonyManager
+        verify(mTelephonyManager, never()).listen(phoneStateListener,
+                PhoneStateListener.LISTEN_NONE);
+        verify(mTelephonyManager, times(1)).listen(phoneStateListener,
+                PhoneStateListener.LISTEN_DATA_CONNECTION_STATE);
+
+        // Verify that resetPhoneStateListener will unregister the listener and the register it.
+        mWifiDataStall.resetPhoneStateListener();
+        verify(mTelephonyManager, times(1)).listen(phoneStateListener,
+                PhoneStateListener.LISTEN_NONE);
+        verify(mTelephonyManager, times(2)).listen(phoneStateListener,
+                PhoneStateListener.LISTEN_DATA_CONNECTION_STATE);
+        verify(mClientModeManager).onCellularConnectivityChanged(
+                WifiDataStall.CELLULAR_DATA_UNKNOWN);
+
+        // Verify the phone state listener still works
+        phoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+        assertEquals(true, mWifiDataStall.isCellularDataAvailable());
+        verify(mClientModeManager).onCellularConnectivityChanged(
+                WifiDataStall.CELLULAR_DATA_AVAILABLE);
+    }
+
+    /**
      * Verify throughput when Rx link speed is unavailable.
      * Also verify the logging of channel utilization and throughput.
      */
     @Test
     public void verifyThroughputNoRxLinkSpeed() throws Exception {
-        mWifiDataStall.checkDataStallAndThroughputSufficiency(null, mNewLlStats, mWifiInfo);
+        mWifiDataStall.checkDataStallAndThroughputSufficiency(TEST_IFACE_NAME,
+                mCapabilities, null, mNewLlStats, mWifiInfo);
         verify(mWifiMetrics).incrementChannelUtilizationCount(10, 5850);
         verify(mWifiMetrics).incrementThroughputKbpsCount(50_000, 150_000, 5850);
         assertEquals(50_000, mWifiDataStall.getTxThroughputKbps());
         assertEquals(150_000, mWifiDataStall.getRxThroughputKbps());
         when(mWifiInfo.getRxLinkSpeedMbps()).thenReturn(-1);
-        mWifiDataStall.checkDataStallAndThroughputSufficiency(mOldLlStats, mNewLlStats, mWifiInfo);
+        mWifiDataStall.checkDataStallAndThroughputSufficiency(TEST_IFACE_NAME,
+                mCapabilities, mOldLlStats, mNewLlStats, mWifiInfo);
         assertEquals(960, mWifiDataStall.getTxThroughputKbps());
         assertEquals(-1, mWifiDataStall.getRxThroughputKbps());
         verify(mWifiMetrics).incrementThroughputKbpsCount(960, -1, 5850);
     }
 
+    private void verifyDataStallTxFailureInternal() {
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(10L);
+        assertEquals(WifiIsUnusableEvent.TYPE_UNKNOWN, mWifiDataStall
+                .checkDataStallAndThroughputSufficiency(TEST_IFACE_NAME,
+                        mCapabilities, mOldLlStats, mNewLlStats, mWifiInfo));
+        verify(mWifiMetrics).incrementThroughputKbpsCount(960, 9609, 5850);
+        verifyUpdateWifiIsUnusableLinkLayerStats();
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(
+                10L + DeviceConfigFacade.DEFAULT_DATA_STALL_DURATION_MS);
+        setUpWifiBytes(TEST_WIFI_BYTES, TEST_WIFI_BYTES);
+        assertEquals(WifiIsUnusableEvent.TYPE_DATA_STALL_BAD_TX, mWifiDataStall
+                .checkDataStallAndThroughputSufficiency(TEST_IFACE_NAME,
+                        mCapabilities, mOldLlStats, mNewLlStats, mWifiInfo));
+        assertEquals(false, mWifiDataStall.isThroughputSufficient());
+        assertEquals(960, mWifiDataStall.getTxThroughputKbps());
+        assertEquals(9609, mWifiDataStall.getRxThroughputKbps());
+        verify(mWifiMetrics).logWifiIsUnusableEvent(TEST_IFACE_NAME,
+                WifiIsUnusableEvent.TYPE_DATA_STALL_BAD_TX);
+    }
+
     /**
      * Verify there is a Tx data stall from high Tx PER and low Tx throughput
      */
     @Test
     public void verifyDataStallTxFailure() throws Exception {
-        when(mClock.getElapsedSinceBootMillis()).thenReturn(10L);
+        verifyDataStallTxFailureInternal();
+    }
 
-        assertEquals(WifiIsUnusableEvent.TYPE_UNKNOWN, mWifiDataStall
-                .checkDataStallAndThroughputSufficiency(mOldLlStats, mNewLlStats, mWifiInfo));
-        verify(mWifiMetrics).incrementThroughputKbpsCount(960, 9609, 5850);
-        verifyUpdateWifiIsUnusableLinkLayerStats();
-        when(mClock.getElapsedSinceBootMillis()).thenReturn(
-                10L + DeviceConfigFacade.DEFAULT_DATA_STALL_DURATION_MS);
-        setUpWifiBytes(TEST_WIFI_BYTES, TEST_WIFI_BYTES);
-        assertEquals(WifiIsUnusableEvent.TYPE_DATA_STALL_BAD_TX, mWifiDataStall
-                .checkDataStallAndThroughputSufficiency(mOldLlStats, mNewLlStats, mWifiInfo));
-        assertEquals(false, mWifiDataStall.isThroughputSufficient());
-        assertEquals(960, mWifiDataStall.getTxThroughputKbps());
-        assertEquals(9609, mWifiDataStall.getRxThroughputKbps());
-        verify(mWifiMetrics).logWifiIsUnusableEvent(WifiIsUnusableEvent.TYPE_DATA_STALL_BAD_TX);
+    @Test
+    public void verifyDataStallTxFailureAfterConnectionEnd() throws Exception {
+        verifyDataStallTxFailureInternal();
+        clearInvocations(mWifiMetrics);
+
+        ConcreteClientModeManager cmm = mock(ConcreteClientModeManager.class);
+        when(cmm.getRole()).thenReturn(ROLE_CLIENT_PRIMARY);
+        mClientModeImplListener.onConnectionEnd(cmm);
+
+        verifyDataStallTxFailureInternal();
+    }
+
+    @Test
+    public void verifyDataStallTxFailureAfterWifiToggle() throws Exception {
+        verifyDataStallTxFailureInternal();
+        clearInvocations(mWifiMetrics);
+
+        // Wifi off
+        mPrimaryModeChangeCallback.onChange(mock(ConcreteClientModeManager.class), null);
+        // Wifi on.
+        mPrimaryModeChangeCallback.onChange(null, mock(ConcreteClientModeManager.class));
+
+        verifyDataStallTxFailureInternal();
     }
 
     /**
@@ -261,17 +370,19 @@
         mNewLlStats.retries_be = mOldLlStats.retries_be;
 
         assertEquals(WifiIsUnusableEvent.TYPE_UNKNOWN, mWifiDataStall
-                .checkDataStallAndThroughputSufficiency(mOldLlStats, mNewLlStats, mWifiInfo));
+                .checkDataStallAndThroughputSufficiency(TEST_IFACE_NAME,
+                        mCapabilities, mOldLlStats, mNewLlStats, mWifiInfo));
         verifyUpdateWifiIsUnusableLinkLayerStats();
         when(mClock.getElapsedSinceBootMillis()).thenReturn(
                 10L + DeviceConfigFacade.DEFAULT_DATA_STALL_DURATION_MS);
         setUpWifiBytes(TEST_WIFI_BYTES, TEST_WIFI_BYTES);
         assertEquals(WifiIsUnusableEvent.TYPE_UNKNOWN, mWifiDataStall
-                .checkDataStallAndThroughputSufficiency(mOldLlStats, mNewLlStats, mWifiInfo));
+                .checkDataStallAndThroughputSufficiency(TEST_IFACE_NAME,
+                        mCapabilities, mOldLlStats, mNewLlStats, mWifiInfo));
         assertEquals(true, mWifiDataStall.isThroughputSufficient());
         assertEquals(833132, mWifiDataStall.getTxThroughputKbps());
         assertEquals(9609, mWifiDataStall.getRxThroughputKbps());
-        verify(mWifiMetrics, never()).logWifiIsUnusableEvent(
+        verify(mWifiMetrics, never()).logWifiIsUnusableEvent(TEST_IFACE_NAME,
                 WifiIsUnusableEvent.TYPE_DATA_STALL_BAD_TX);
     }
 
@@ -283,7 +394,8 @@
         when(mClock.getElapsedSinceBootMillis()).thenReturn(10L);
 
         assertEquals(WifiIsUnusableEvent.TYPE_UNKNOWN, mWifiDataStall
-                .checkDataStallAndThroughputSufficiency(mOldLlStats, mNewLlStats, mWifiInfo));
+                .checkDataStallAndThroughputSufficiency(TEST_IFACE_NAME,
+                        mCapabilities, mOldLlStats, mNewLlStats, mWifiInfo));
         assertEquals(true, mWifiDataStall.isThroughputSufficient());
         verifyUpdateWifiIsUnusableLinkLayerStats();
 
@@ -292,9 +404,10 @@
         mNewLlStats.retries_be = 2 * mOldLlStats.retries_be;
         setUpWifiBytes(TEST_WIFI_BYTES, TEST_WIFI_BYTES);
         assertEquals(WifiIsUnusableEvent.TYPE_UNKNOWN, mWifiDataStall
-                .checkDataStallAndThroughputSufficiency(mOldLlStats, mNewLlStats, mWifiInfo));
+                .checkDataStallAndThroughputSufficiency(TEST_IFACE_NAME,
+                        mCapabilities, mOldLlStats, mNewLlStats, mWifiInfo));
         assertEquals(true, mWifiDataStall.isThroughputSufficient());
-        verify(mWifiMetrics, never()).logWifiIsUnusableEvent(
+        verify(mWifiMetrics, never()).logWifiIsUnusableEvent(TEST_IFACE_NAME,
                 WifiIsUnusableEvent.TYPE_DATA_STALL_BAD_TX);
     }
 
@@ -307,17 +420,19 @@
         mNewLlStats.retries_be = 2 * mOldLlStats.retries_be;
         when(mClock.getElapsedSinceBootMillis()).thenReturn(10L);
         assertEquals(WifiIsUnusableEvent.TYPE_UNKNOWN, mWifiDataStall
-                .checkDataStallAndThroughputSufficiency(mOldLlStats, mNewLlStats, mWifiInfo));
+                .checkDataStallAndThroughputSufficiency(TEST_IFACE_NAME,
+                        mCapabilities, mOldLlStats, mNewLlStats, mWifiInfo));
         verifyUpdateWifiIsUnusableLinkLayerStats();
         when(mClock.getElapsedSinceBootMillis()).thenReturn(
                 10L + DeviceConfigFacade.DEFAULT_DATA_STALL_DURATION_MS);
         setUpWifiBytes(TEST_WIFI_BYTES, TEST_WIFI_BYTES);
         assertEquals(WifiIsUnusableEvent.TYPE_DATA_STALL_TX_WITHOUT_RX, mWifiDataStall
-                .checkDataStallAndThroughputSufficiency(mOldLlStats, mNewLlStats, mWifiInfo));
+                .checkDataStallAndThroughputSufficiency(TEST_IFACE_NAME,
+                        mCapabilities, mOldLlStats, mNewLlStats, mWifiInfo));
         assertEquals(false, mWifiDataStall.isThroughputSufficient());
         assertEquals(4804, mWifiDataStall.getTxThroughputKbps());
         assertEquals(960, mWifiDataStall.getRxThroughputKbps());
-        verify(mWifiMetrics).logWifiIsUnusableEvent(
+        verify(mWifiMetrics).logWifiIsUnusableEvent(TEST_IFACE_NAME,
                 WifiIsUnusableEvent.TYPE_DATA_STALL_TX_WITHOUT_RX);
     }
 
@@ -334,17 +449,19 @@
         when(mClock.getElapsedSinceBootMillis()).thenReturn(10L);
 
         assertEquals(WifiIsUnusableEvent.TYPE_UNKNOWN, mWifiDataStall
-                .checkDataStallAndThroughputSufficiency(mOldLlStats, mNewLlStats, mWifiInfo));
+                .checkDataStallAndThroughputSufficiency(TEST_IFACE_NAME,
+                        mCapabilities, mOldLlStats, mNewLlStats, mWifiInfo));
         verifyUpdateWifiIsUnusableLinkLayerStats();
         when(mClock.getElapsedSinceBootMillis()).thenReturn(
                 10L + DeviceConfigFacade.DEFAULT_DATA_STALL_DURATION_MS);
         setUpWifiBytes(TEST_WIFI_BYTES, TEST_WIFI_BYTES);
         assertEquals(WifiIsUnusableEvent.TYPE_UNKNOWN, mWifiDataStall
-                .checkDataStallAndThroughputSufficiency(mOldLlStats, mNewLlStats, mWifiInfo));
+                .checkDataStallAndThroughputSufficiency(TEST_IFACE_NAME,
+                        mCapabilities, mOldLlStats, mNewLlStats, mWifiInfo));
         assertEquals(true, mWifiDataStall.isThroughputSufficient());
         assertEquals(9128, mWifiDataStall.getTxThroughputKbps());
         assertEquals(-1, mWifiDataStall.getRxThroughputKbps());
-        verify(mWifiMetrics, never()).logWifiIsUnusableEvent(anyInt());
+        verify(mWifiMetrics, never()).logWifiIsUnusableEvent(any(), anyInt());
     }
 
     /**
@@ -357,7 +474,8 @@
         when(mClock.getElapsedSinceBootMillis()).thenReturn(10L);
 
         assertEquals(WifiIsUnusableEvent.TYPE_UNKNOWN, mWifiDataStall
-                .checkDataStallAndThroughputSufficiency(mOldLlStats, mNewLlStats, mWifiInfo));
+                .checkDataStallAndThroughputSufficiency(TEST_IFACE_NAME,
+                        mCapabilities, mOldLlStats, mNewLlStats, mWifiInfo));
         verifyUpdateWifiIsUnusableLinkLayerStats();
         assertEquals(960, mWifiDataStall.getTxThroughputKbps());
         assertEquals(960, mWifiDataStall.getRxThroughputKbps());
@@ -367,11 +485,13 @@
                 10L + DeviceConfigFacade.DEFAULT_DATA_STALL_DURATION_MS);
         setUpWifiBytes(TEST_WIFI_BYTES, TEST_WIFI_BYTES);
         assertEquals(WifiIsUnusableEvent.TYPE_DATA_STALL_BOTH, mWifiDataStall
-                .checkDataStallAndThroughputSufficiency(mOldLlStats, mNewLlStats, mWifiInfo));
+                .checkDataStallAndThroughputSufficiency(TEST_IFACE_NAME,
+                        mCapabilities, mOldLlStats, mNewLlStats, mWifiInfo));
         assertEquals(false, mWifiDataStall.isThroughputSufficient());
         assertEquals(960, mWifiDataStall.getTxThroughputKbps());
         assertEquals(960, mWifiDataStall.getRxThroughputKbps());
-        verify(mWifiMetrics).logWifiIsUnusableEvent(WifiIsUnusableEvent.TYPE_DATA_STALL_BOTH);
+        verify(mWifiMetrics).logWifiIsUnusableEvent(TEST_IFACE_NAME,
+                WifiIsUnusableEvent.TYPE_DATA_STALL_BOTH);
 
         // 3rd poll with low tx/rx traffic and throughput
         when(mWifiInfo.getLinkSpeed()).thenReturn(1);
@@ -385,7 +505,8 @@
         setUpWifiBytes(TEST_WIFI_BYTES, TEST_WIFI_BYTES);
 
         assertEquals(WifiIsUnusableEvent.TYPE_UNKNOWN, mWifiDataStall
-                .checkDataStallAndThroughputSufficiency(mOldLlStats, mNewLlStats, mWifiInfo));
+                .checkDataStallAndThroughputSufficiency(TEST_IFACE_NAME,
+                        mCapabilities, mOldLlStats, mNewLlStats, mWifiInfo));
         assertEquals(false, mWifiDataStall.isThroughputSufficient());
         assertEquals(960, mWifiDataStall.getTxThroughputKbps());
         assertEquals(960, mWifiDataStall.getRxThroughputKbps());
@@ -398,7 +519,8 @@
                 10L + 2 * DeviceConfigFacade.DEFAULT_DATA_STALL_DURATION_MS);
         setUpWifiBytes(TEST_WIFI_BYTES, TEST_WIFI_BYTES);
         assertEquals(WifiIsUnusableEvent.TYPE_UNKNOWN, mWifiDataStall
-                .checkDataStallAndThroughputSufficiency(mOldLlStats, mNewLlStats, mWifiInfo));
+                .checkDataStallAndThroughputSufficiency(TEST_IFACE_NAME,
+                        mCapabilities, mOldLlStats, mNewLlStats, mWifiInfo));
         assertEquals(true, mWifiDataStall.isThroughputSufficient());
         assertEquals(8943, mWifiDataStall.getTxThroughputKbps());
         assertEquals(9414, mWifiDataStall.getRxThroughputKbps());
@@ -414,15 +536,17 @@
         when(mDeviceConfigFacade.getDataStallDurationMs()).thenReturn(
                 DeviceConfigFacade.DEFAULT_DATA_STALL_DURATION_MS + 1);
         assertEquals(WifiIsUnusableEvent.TYPE_UNKNOWN, mWifiDataStall
-                .checkDataStallAndThroughputSufficiency(mOldLlStats, mNewLlStats, mWifiInfo));
+                .checkDataStallAndThroughputSufficiency(TEST_IFACE_NAME,
+                        mCapabilities, mOldLlStats, mNewLlStats, mWifiInfo));
         verifyUpdateWifiIsUnusableLinkLayerStats();
         when(mClock.getElapsedSinceBootMillis()).thenReturn(
                 10L + DeviceConfigFacade.DEFAULT_DATA_STALL_DURATION_MS);
         setUpWifiBytes(TEST_WIFI_BYTES, TEST_WIFI_BYTES);
         assertEquals(WifiIsUnusableEvent.TYPE_UNKNOWN, mWifiDataStall
-                .checkDataStallAndThroughputSufficiency(mOldLlStats, mNewLlStats, mWifiInfo));
+                .checkDataStallAndThroughputSufficiency(TEST_IFACE_NAME,
+                        mCapabilities, mOldLlStats, mNewLlStats, mWifiInfo));
         assertEquals(false, mWifiDataStall.isThroughputSufficient());
-        verify(mWifiMetrics, never()).logWifiIsUnusableEvent(
+        verify(mWifiMetrics, never()).logWifiIsUnusableEvent(TEST_IFACE_NAME,
                 WifiIsUnusableEvent.TYPE_DATA_STALL_BAD_TX);
     }
 
@@ -437,15 +561,17 @@
                 DeviceConfigFacade.DEFAULT_DATA_STALL_TX_PER_THR + 1);
         when(mDeviceConfigFacade.getDataStallTxTputThrKbps()).thenReturn(800);
         assertEquals(WifiIsUnusableEvent.TYPE_UNKNOWN, mWifiDataStall
-                .checkDataStallAndThroughputSufficiency(mOldLlStats, mNewLlStats, mWifiInfo));
+                .checkDataStallAndThroughputSufficiency(TEST_IFACE_NAME,
+                        mCapabilities, mOldLlStats, mNewLlStats, mWifiInfo));
         verifyUpdateWifiIsUnusableLinkLayerStats();
         when(mClock.getElapsedSinceBootMillis()).thenReturn(
                 10L + DeviceConfigFacade.DEFAULT_DATA_STALL_DURATION_MS);
         setUpWifiBytes(TEST_WIFI_BYTES, TEST_WIFI_BYTES);
         assertEquals(WifiIsUnusableEvent.TYPE_UNKNOWN, mWifiDataStall
-                .checkDataStallAndThroughputSufficiency(mOldLlStats, mNewLlStats, mWifiInfo));
+                .checkDataStallAndThroughputSufficiency(TEST_IFACE_NAME,
+                        mCapabilities, mOldLlStats, mNewLlStats, mWifiInfo));
         assertEquals(false, mWifiDataStall.isThroughputSufficient());
-        verify(mWifiMetrics, never()).logWifiIsUnusableEvent(
+        verify(mWifiMetrics, never()).logWifiIsUnusableEvent(TEST_IFACE_NAME,
                 WifiIsUnusableEvent.TYPE_DATA_STALL_BAD_TX);
     }
 
@@ -455,10 +581,11 @@
     @Test
     public void verifyNoDataStallWhenNoFail() throws Exception {
         assertEquals(WifiIsUnusableEvent.TYPE_UNKNOWN, mWifiDataStall
-                .checkDataStallAndThroughputSufficiency(mOldLlStats, mNewLlStats, mWifiInfo));
+                .checkDataStallAndThroughputSufficiency(TEST_IFACE_NAME,
+                        mCapabilities, mOldLlStats, mNewLlStats, mWifiInfo));
         verify(mWifiMetrics, never()).resetWifiIsUnusableLinkLayerStats();
         verifyUpdateWifiIsUnusableLinkLayerStats();
-        verify(mWifiMetrics, never()).logWifiIsUnusableEvent(anyInt());
+        verify(mWifiMetrics, never()).logWifiIsUnusableEvent(any(), anyInt());
     }
 
     /**
@@ -471,9 +598,10 @@
         mNewLlStats.timeStampInMs = mOldLlStats.timeStampInMs
                 + WifiDataStall.MAX_MS_DELTA_FOR_DATA_STALL + 1;
         assertEquals(WifiIsUnusableEvent.TYPE_UNKNOWN, mWifiDataStall
-                .checkDataStallAndThroughputSufficiency(mOldLlStats, mNewLlStats, mWifiInfo));
+                .checkDataStallAndThroughputSufficiency(TEST_IFACE_NAME,
+                        mCapabilities, mOldLlStats, mNewLlStats, mWifiInfo));
         verifyUpdateWifiIsUnusableLinkLayerStats();
-        verify(mWifiMetrics, never()).logWifiIsUnusableEvent(anyInt());
+        verify(mWifiMetrics, never()).logWifiIsUnusableEvent(any(), anyInt());
     }
 
     /**
@@ -483,11 +611,12 @@
     public void verifyReset() throws Exception {
         mNewLlStats.lostmpdu_be = mOldLlStats.lostmpdu_be - 1;
         assertEquals(WifiIsUnusableEvent.TYPE_UNKNOWN, mWifiDataStall
-                .checkDataStallAndThroughputSufficiency(mOldLlStats, mNewLlStats, mWifiInfo));
+                .checkDataStallAndThroughputSufficiency(TEST_IFACE_NAME,
+                        mCapabilities, mOldLlStats, mNewLlStats, mWifiInfo));
         verify(mWifiMetrics).resetWifiIsUnusableLinkLayerStats();
         verify(mWifiMetrics, never()).updateWifiIsUnusableLinkLayerStats(
                 anyLong(), anyLong(), anyLong(), anyLong(), anyLong());
-        verify(mWifiMetrics, never()).logWifiIsUnusableEvent(anyInt());
+        verify(mWifiMetrics, never()).logWifiIsUnusableEvent(any(), anyInt());
     }
 
     /**
@@ -495,7 +624,7 @@
      */
     @Test
     public void testIncrementConnectionDuration() throws Exception {
-        mWifiDataStall.enablePhoneStateListener();
+        setWifiEnabled(true);
         PhoneStateListener phoneStateListener = mockPhoneStateListener();
         phoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
                 TelephonyManager.NETWORK_TYPE_LTE);
@@ -503,14 +632,14 @@
         mNewLlStats.timeStampInMs = mOldLlStats.timeStampInMs + 1000;
         // Expect 1st throughput sufficiency check to return true
         // because it hits mLastTxBytes == 0 || mLastRxBytes == 0
-        mWifiDataStall.checkDataStallAndThroughputSufficiency(
-                mOldLlStats, mNewLlStats, mWifiInfo);
+        mWifiDataStall.checkDataStallAndThroughputSufficiency(TEST_IFACE_NAME,
+                mCapabilities, mOldLlStats, mNewLlStats, mWifiInfo);
         verify(mWifiMetrics, times(1)).incrementConnectionDuration(
                 1000, true, true);
 
         // Expect 2nd throughput sufficiency check to return false
-        mWifiDataStall.checkDataStallAndThroughputSufficiency(
-                mOldLlStats, mNewLlStats, mWifiInfo);
+        mWifiDataStall.checkDataStallAndThroughputSufficiency(TEST_IFACE_NAME,
+                mCapabilities, mOldLlStats, mNewLlStats, mWifiInfo);
         verify(mWifiMetrics, times(1)).incrementConnectionDuration(
                 1000, false, true);
 
@@ -519,57 +648,18 @@
         phoneStateListener.onDataConnectionStateChanged(
                 TelephonyManager.DATA_DISCONNECTED, TelephonyManager.NETWORK_TYPE_LTE);
         assertEquals(false, mWifiDataStall.isCellularDataAvailable());
-        mWifiDataStall.checkDataStallAndThroughputSufficiency(
-                mOldLlStats, mNewLlStats, mWifiInfo);
+        mWifiDataStall.checkDataStallAndThroughputSufficiency(TEST_IFACE_NAME,
+                mCapabilities, mOldLlStats, mNewLlStats, mWifiInfo);
         verify(mWifiMetrics, times(1)).incrementConnectionDuration(
                 2000, false, false);
 
         // Expect this update to be ignored by connection duration counters due to its
         // too large poll interval
         mNewLlStats.timeStampInMs = mOldLlStats.timeStampInMs + 10000;
-        mWifiDataStall.checkDataStallAndThroughputSufficiency(
-                mOldLlStats, mNewLlStats, mWifiInfo);
+        mWifiDataStall.checkDataStallAndThroughputSufficiency(TEST_IFACE_NAME,
+                mCapabilities, mOldLlStats, mNewLlStats, mWifiInfo);
         verify(mWifiMetrics, never()).incrementConnectionDuration(
                 10000, false, false);
-        mWifiDataStall.disablePhoneStateListener();
-    }
-
-    /**
-     * Check statsd logging
-     */
-    @Test
-    public void testWifiStatsLogWrite() throws Exception {
-        mWifiDataStall.enableVerboseLogging(true);
-        mWifiDataStall.enablePhoneStateListener();
-        PhoneStateListener phoneStateListener = mockPhoneStateListener();
-        phoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
-                TelephonyManager.NETWORK_TYPE_LTE);
-        // static mocking for WifiStatsLog
-        mSession = ExtendedMockito.mockitoSession()
-                .strictness(Strictness.LENIENT)
-                .mockStatic(WifiStatsLog.class)
-                .startMocking();
-        mNewLlStats.timeStampInMs = mOldLlStats.timeStampInMs + 3000;
-        when(mWifiInfo.getFrequency()).thenReturn(5850);
-        mWifiDataStall.checkDataStallAndThroughputSufficiency(mOldLlStats, mNewLlStats, mWifiInfo);
-        ExtendedMockito.verify(() -> WifiStatsLog.write(
-                WifiStatsLog.WIFI_HEALTH_STAT_REPORTED, 3000, true, true,
-                WifiStatsLog.WIFI_HEALTH_STAT_REPORTED__BAND__BAND_5G_HIGH));
-
-        mNewLlStats.timeStampInMs = mOldLlStats.timeStampInMs + 2000;
-        when(mWifiInfo.getFrequency()).thenReturn(6850);
-        mWifiDataStall.checkDataStallAndThroughputSufficiency(mOldLlStats, mNewLlStats, mWifiInfo);
-        ExtendedMockito.verify(() -> WifiStatsLog.write(
-                WifiStatsLog.WIFI_HEALTH_STAT_REPORTED, 2000, true, true,
-                WifiStatsLog.WIFI_HEALTH_STAT_REPORTED__BAND__BAND_6G_MIDDLE));
-
-        mNewLlStats.timeStampInMs = mOldLlStats.timeStampInMs + 1000;
-        when(mWifiInfo.getFrequency()).thenReturn(1850);
-        mWifiDataStall.checkDataStallAndThroughputSufficiency(mOldLlStats, mNewLlStats, mWifiInfo);
-        ExtendedMockito.verify(() -> WifiStatsLog.write(
-                WifiStatsLog.WIFI_HEALTH_STAT_REPORTED, 1000, true, true,
-                WifiStatsLog.WIFI_HEALTH_STAT_REPORTED__BAND__UNKNOWN));
-        mSession.finishMocking();
-        mWifiDataStall.disablePhoneStateListener();
+        setWifiEnabled(false);
     }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiDiagnosticsTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiDiagnosticsTest.java
index e72f808..7cff275 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiDiagnosticsTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiDiagnosticsTest.java
@@ -38,6 +38,7 @@
 import android.app.test.MockAnswerUtil.AnswerWithArguments;
 import android.content.Context;
 import android.os.BugreportManager;
+import android.os.test.TestLooper;
 
 import androidx.test.filters.SmallTest;
 
@@ -54,6 +55,9 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 import java.util.regex.Pattern;
 
 /**
@@ -74,9 +78,13 @@
     @Mock Clock mClock;
     @Mock BugreportManager mBugreportManager;
     @Mock WifiScoreCard mWifiScoreCard;
+    @Mock ActiveModeWarden mActiveModeWarden;
+    @Mock ClientModeManager mClientModeManager;
+    @Mock ClientModeManager mClientModeManager2;
     private long mBootTimeMs = 0L;
     MockResources mResources;
     WifiDiagnostics mWifiDiagnostics;
+    TestLooper mTestLooper;
 
     private static final String FAKE_RING_BUFFER_NAME = "fake-ring-buffer";
     private static final String STA_IF_NAME = "wlan0";
@@ -108,6 +116,7 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        mTestLooper = new TestLooper();
 
         mFakeRbs = new WifiNative.RingBufferStatus();
         mFakeRbs.name = FAKE_RING_BUFFER_NAME;
@@ -123,6 +132,10 @@
         when(mExternalProcess.getErrorStream()).thenReturn(new ByteArrayInputStream(new byte[0]));
         when(mJavaRuntime.exec(anyString())).thenReturn(mExternalProcess);
 
+        List<ClientModeManager> clientModeManagerList = List.of(mClientModeManager,
+                mClientModeManager2);
+        when(mClientModeManager.getInterfaceName()).thenReturn(STA_IF_NAME);
+
         mResources = new MockResources();
         mResources.setInteger(R.integer.config_wifi_logger_ring_buffer_default_size_limit_kb,
                 SMALL_RING_BUFFER_SIZE_KB);
@@ -138,6 +151,9 @@
         when(mWifiInjector.getWifiMetrics()).thenReturn(mWifiMetrics);
         when(mWifiInjector.getDeviceConfigFacade()).thenReturn(mDeviceConfigFacade);
         when(mWifiInjector.getWifiScoreCard()).thenReturn(mWifiScoreCard);
+        when(mWifiInjector.getActiveModeWarden()).thenReturn(mActiveModeWarden);
+        when(mActiveModeWarden.getPrimaryClientModeManager()).thenReturn(mClientModeManager);
+        when(mActiveModeWarden.getClientModeManagers()).thenReturn(clientModeManagerList);
         when(mDeviceConfigFacade.getBugReportMinWindowMs()).thenReturn(BUG_REPORT_MIN_WINDOW_MS);
         // needed to for the loop in WifiDiagnostics.readLogcatStreamLinesWithTimeout().
         doAnswer(new AnswerWithArguments() {
@@ -147,8 +163,9 @@
             }
         }).when(mClock).getElapsedSinceBootMillis();
         mWifiDiagnostics = new WifiDiagnostics(
-                mContext, mWifiInjector, mWifiNative, mBuildProperties, mLastMileLogger, mClock);
-        mWifiNative.enableVerboseLogging(0);
+                mContext, mWifiInjector, mWifiNative, mBuildProperties, mLastMileLogger, mClock,
+                mTestLooper.getLooper());
+        mWifiNative.enableVerboseLogging(false);
     }
 
     /** Verifies that startLogging() registers a logging event handler. */
@@ -280,7 +297,8 @@
 
         final byte[] data = new byte[SMALL_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE];
         mWifiDiagnostics.onRingBufferData(mFakeRbs, data);
-        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
+        mWifiDiagnostics.triggerBugReportDataCapture(WifiDiagnostics.REPORT_REASON_NONE);
+        mTestLooper.dispatchAll();
 
         byte[][] ringBufferData = getLoggerRingBufferData();
         assertEquals(1, ringBufferData.length);
@@ -301,7 +319,8 @@
         final byte[] data2 = {1, 2, 3};
         mWifiDiagnostics.onRingBufferData(mFakeRbs, data1);
         mWifiDiagnostics.onRingBufferData(mFakeRbs, data2);
-        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
+        mWifiDiagnostics.triggerBugReportDataCapture(WifiDiagnostics.REPORT_REASON_NONE);
+        mTestLooper.dispatchAll();
 
         byte[][] ringBufferData = getLoggerRingBufferData();
         assertEquals(1, ringBufferData.length);
@@ -325,9 +344,10 @@
         final boolean verbosityToggle = false;
         mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
         mWifiDiagnostics.startPktFateMonitoring(STA_IF_NAME);
-        mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_FAILED);
-        verify(mWifiNative).getTxPktFates(any(), anyObject());
-        verify(mWifiNative).getRxPktFates(any(), anyObject());
+        mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_FAILED,
+                mClientModeManager);
+        verify(mClientModeManager).getTxPktFates();
+        verify(mClientModeManager).getRxPktFates();
     }
 
     /**
@@ -338,9 +358,10 @@
         final boolean verbosityToggle = true;
         mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
         mWifiDiagnostics.startPktFateMonitoring(STA_IF_NAME);
-        mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_FAILED);
-        verify(mWifiNative).getTxPktFates(any(), anyObject());
-        verify(mWifiNative).getRxPktFates(any(), anyObject());
+        mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_FAILED,
+                mClientModeManager);
+        verify(mClientModeManager).getTxPktFates();
+        verify(mClientModeManager).getRxPktFates();
     }
 
     @Test
@@ -348,8 +369,10 @@
         final boolean verbosityToggle = false;
         mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
         mWifiDiagnostics.startLogging(STA_IF_NAME);
-        mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_STARTED);
-        verify(mLastMileLogger).reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_STARTED);
+        mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_STARTED,
+                mClientModeManager);
+        verify(mLastMileLogger).reportConnectionEvent(
+                STA_IF_NAME, WifiDiagnostics.CONNECTION_EVENT_STARTED);
     }
 
     @Test
@@ -357,8 +380,10 @@
         final boolean verbosityToggle = false;
         mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
         mWifiDiagnostics.startLogging(STA_IF_NAME);
-        mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_SUCCEEDED);
-        verify(mLastMileLogger).reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_SUCCEEDED);
+        mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_SUCCEEDED,
+                mClientModeManager);
+        verify(mLastMileLogger).reportConnectionEvent(
+                STA_IF_NAME, WifiDiagnostics.CONNECTION_EVENT_SUCCEEDED);
     }
 
     @Test
@@ -366,8 +391,10 @@
         final boolean verbosityToggle = false;
         mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
         mWifiDiagnostics.startLogging(STA_IF_NAME);
-        mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_FAILED);
-        verify(mLastMileLogger).reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_FAILED);
+        mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_FAILED,
+                mClientModeManager);
+        verify(mLastMileLogger).reportConnectionEvent(
+                STA_IF_NAME, WifiDiagnostics.CONNECTION_EVENT_FAILED);
     }
 
     /**
@@ -378,8 +405,10 @@
         final boolean verbosityToggle = true;
         mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
         mWifiDiagnostics.startLogging(STA_IF_NAME);
-        mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_TIMEOUT);
-        verify(mLastMileLogger).reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_TIMEOUT);
+        mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_TIMEOUT,
+                mClientModeManager);
+        verify(mLastMileLogger).reportConnectionEvent(
+                STA_IF_NAME, WifiDiagnostics.CONNECTION_EVENT_TIMEOUT);
     }
 
     /**
@@ -388,12 +417,13 @@
     @Test
     public void loggerFetchesTxFatesEvenIfFetchingRxFatesFails() {
         final boolean verbosityToggle = true;
-        when(mWifiNative.getRxPktFates(any(), anyObject())).thenReturn(false);
+        when(mClientModeManager.getRxPktFates()).thenReturn(new ArrayList<>());
         mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
         mWifiDiagnostics.startPktFateMonitoring(STA_IF_NAME);
-        mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_FAILED);
-        verify(mWifiNative).getTxPktFates(any(), anyObject());
-        verify(mWifiNative).getRxPktFates(any(), anyObject());
+        mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_FAILED,
+                mClientModeManager);
+        verify(mClientModeManager).getTxPktFates();
+        verify(mClientModeManager).getRxPktFates();
     }
 
     /**
@@ -402,15 +432,18 @@
     @Test
     public void loggerFetchesRxFatesEvenIfFetchingTxFatesFails() {
         final boolean verbosityToggle = true;
-        when(mWifiNative.getTxPktFates(any(), anyObject())).thenReturn(false);
+        when(mClientModeManager.getTxPktFates()).thenReturn(new ArrayList<>());
         mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
         mWifiDiagnostics.startPktFateMonitoring(STA_IF_NAME);
-        mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_FAILED);
-        verify(mWifiNative).getTxPktFates(any(), anyObject());
-        verify(mWifiNative).getRxPktFates(any(), anyObject());
+        mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_FAILED,
+                mClientModeManager);
+        verify(mClientModeManager).getTxPktFates();
+        verify(mClientModeManager).getRxPktFates();
+        verify(mClientModeManager2, never()).getTxPktFates();
+        verify(mClientModeManager2, never()).getRxPktFates();
     }
 
-    /** Verifies that dump() fetches the latest fates. */
+    /** Verifies that dump() fetches the latest fates from both ClientModeManager. */
     @Test
     public void dumpFetchesFates() {
         final boolean verbosityToggle = false;
@@ -419,8 +452,10 @@
         mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
         mWifiDiagnostics.startPktFateMonitoring(STA_IF_NAME);
         mWifiDiagnostics.dump(new FileDescriptor(), pw, new String[]{"bogus", "args"});
-        verify(mWifiNative).getTxPktFates(any(), anyObject());
-        verify(mWifiNative).getRxPktFates(any(), anyObject());
+        verify(mClientModeManager).getTxPktFates();
+        verify(mClientModeManager).getRxPktFates();
+        verify(mClientModeManager2).getTxPktFates();
+        verify(mClientModeManager2).getRxPktFates();
     }
 
     /**
@@ -449,9 +484,10 @@
         final boolean verbosityToggle = true;
         mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
         mWifiDiagnostics.startPktFateMonitoring(STA_IF_NAME);
-        mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_FAILED);
-        verify(mWifiNative).getTxPktFates(any(), anyObject());
-        verify(mWifiNative).getRxPktFates(any(), anyObject());
+        mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_FAILED,
+                mClientModeManager);
+        verify(mClientModeManager).getTxPktFates();
+        verify(mClientModeManager).getRxPktFates();
 
         StringWriter sw = new StringWriter();
         PrintWriter pw = new PrintWriter(sw);
@@ -468,34 +504,27 @@
         mWifiDiagnostics.enableVerboseLogging(verbose);
         mWifiDiagnostics.startPktFateMonitoring(STA_IF_NAME);
         mWifiDiagnostics.startLogging(STA_IF_NAME);
-        mWifiNative.enableVerboseLogging(verbose ? 1 : 0);
-        when(mWifiNative.getTxPktFates(any(), anyObject())).then(new AnswerWithArguments() {
-            public boolean answer(String ifaceName, WifiNative.TxFateReport[] fates) {
-                fates[0] = new WifiNative.TxFateReport(
+        mWifiNative.enableVerboseLogging(verbose);
+        when(mClientModeManager.getTxPktFates()).thenReturn(Arrays.asList(
+                new WifiNative.TxFateReport(
                         WifiLoggerHal.TX_PKT_FATE_ACKED, 2, WifiLoggerHal.FRAME_TYPE_ETHERNET_II,
                         new byte[0]
-                );
-                fates[1] = new WifiNative.TxFateReport(
+                ),
+                new WifiNative.TxFateReport(
                         WifiLoggerHal.TX_PKT_FATE_ACKED, 0, WifiLoggerHal.FRAME_TYPE_ETHERNET_II,
                         new byte[0]
-                );
-                return true;
-            }
-        });
-        when(mWifiNative.getRxPktFates(any(), anyObject())).then(new AnswerWithArguments() {
-            public boolean answer(String ifaceName, WifiNative.RxFateReport[] fates) {
-                fates[0] = new WifiNative.RxFateReport(
+                )));
+        when(mClientModeManager.getRxPktFates()).thenReturn(Arrays.asList(
+                new WifiNative.RxFateReport(
                         WifiLoggerHal.RX_PKT_FATE_SUCCESS, 3, WifiLoggerHal.FRAME_TYPE_ETHERNET_II,
                         new byte[0]
-                );
-                fates[1] = new WifiNative.RxFateReport(
+                ),
+                new WifiNative.RxFateReport(
                         WifiLoggerHal.RX_PKT_FATE_SUCCESS, 1, WifiLoggerHal.FRAME_TYPE_ETHERNET_II,
                         new byte[0]
-                );
-                return true;
-            }
-        });
-        mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_FAILED);
+                )));
+        mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_FAILED,
+                mClientModeManager);
 
         StringWriter sw = new StringWriter();
         PrintWriter pw = new PrintWriter(sw);
@@ -603,27 +632,20 @@
         final boolean verbosityToggle = true;
         mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
         mWifiDiagnostics.startPktFateMonitoring(STA_IF_NAME);
-        when(mWifiNative.getTxPktFates(any(), anyObject())).then(new AnswerWithArguments() {
-            public boolean answer(String ifaceName, WifiNative.TxFateReport[] fates) {
-                fates[0] = new WifiNative.TxFateReport(
+        when(mClientModeManager.getTxPktFates()).thenReturn(Arrays.asList(
+                new WifiNative.TxFateReport(
                         WifiLoggerHal.TX_PKT_FATE_ACKED, 0, WifiLoggerHal.FRAME_TYPE_ETHERNET_II,
                         new byte[0]
-                );
-                return true;
-            }
-        });
-        when(mWifiNative.getRxPktFates(any(), anyObject())).then(new AnswerWithArguments() {
-            public boolean answer(String ifaceName, WifiNative.RxFateReport[] fates) {
-                fates[0] = new WifiNative.RxFateReport(
+                )));
+        when(mClientModeManager.getRxPktFates()).thenReturn(Arrays.asList(
+                new WifiNative.RxFateReport(
                         WifiLoggerHal.RX_PKT_FATE_SUCCESS, 1, WifiLoggerHal.FRAME_TYPE_ETHERNET_II,
                         new byte[0]
-                );
-                return true;
-            }
-        });
-        mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_FAILED);
-        verify(mWifiNative).getTxPktFates(any(), anyObject());
-        verify(mWifiNative).getRxPktFates(any(), anyObject());
+                )));
+        mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_FAILED,
+                mClientModeManager);
+        verify(mClientModeManager).getTxPktFates();
+        verify(mClientModeManager).getRxPktFates();
 
         final boolean newVerbosityToggle = false;
         mWifiDiagnostics.enableVerboseLogging(newVerbosityToggle);
@@ -647,7 +669,8 @@
         mWifiDiagnostics.startLogging(STA_IF_NAME);
         mWifiDiagnostics.onRingBufferData(
                 mFakeRbs, new byte[SMALL_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE + 1]);
-        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
+        mWifiDiagnostics.triggerBugReportDataCapture(WifiDiagnostics.REPORT_REASON_NONE);
+        mTestLooper.dispatchAll();
         assertEquals(0, getLoggerRingBufferData().length);
     }
 
@@ -663,7 +686,8 @@
         mWifiDiagnostics.startLogging(STA_IF_NAME);
         mWifiDiagnostics.onRingBufferData(
                 mFakeRbs, new byte[SMALL_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE + 1]);
-        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
+        mWifiDiagnostics.triggerBugReportDataCapture(WifiDiagnostics.REPORT_REASON_NONE);
+        mTestLooper.dispatchAll();
         assertEquals(0, getLoggerRingBufferData().length);
     }
 
@@ -679,7 +703,8 @@
         mWifiDiagnostics.startLogging(STA_IF_NAME);
         mWifiDiagnostics.onRingBufferData(
                 mFakeRbs, new byte[SMALL_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE + 1]);
-        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
+        mWifiDiagnostics.triggerBugReportDataCapture(WifiDiagnostics.REPORT_REASON_NONE);
+        mTestLooper.dispatchAll();
         assertEquals(0, getLoggerRingBufferData().length);
     }
 
@@ -692,7 +717,8 @@
         mWifiDiagnostics.startLogging(STA_IF_NAME);
         mWifiDiagnostics.onRingBufferData(
                 mFakeRbs, new byte[LARGE_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE]);
-        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
+        mWifiDiagnostics.triggerBugReportDataCapture(WifiDiagnostics.REPORT_REASON_NONE);
+        mTestLooper.dispatchAll();
         assertEquals(1, getLoggerRingBufferData().length);
     }
 
@@ -705,7 +731,8 @@
         mWifiDiagnostics.startLogging(STA_IF_NAME);
         mWifiDiagnostics.onRingBufferData(
                 mFakeRbs, new byte[LARGE_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE]);
-        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
+        mWifiDiagnostics.triggerBugReportDataCapture(WifiDiagnostics.REPORT_REASON_NONE);
+        mTestLooper.dispatchAll();
         assertEquals(1, getLoggerRingBufferData().length);
     }
 
@@ -722,13 +749,15 @@
         // Existing data is nuked (too large).
         mWifiDiagnostics.enableVerboseLogging(false /* verbose disabled */);
         mWifiDiagnostics.startLogging(STA_IF_NAME);
-        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
+        mWifiDiagnostics.triggerBugReportDataCapture(WifiDiagnostics.REPORT_REASON_NONE);
+        mTestLooper.dispatchAll();
         assertEquals(0, getLoggerRingBufferData().length);
 
         // New data must obey limit as well.
         mWifiDiagnostics.onRingBufferData(
                 mFakeRbs, new byte[SMALL_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE + 1]);
-        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
+        mWifiDiagnostics.triggerBugReportDataCapture(WifiDiagnostics.REPORT_REASON_NONE);
+        mTestLooper.dispatchAll();
         assertEquals(0, getLoggerRingBufferData().length);
     }
 
@@ -739,20 +768,22 @@
     @Test
     public void onWifiAlertCapturesBugreportAndLogsMetrics() throws Exception {
         mWifiDiagnostics.onWifiAlert(ALERT_REASON_CODE, ALERT_DATA);
+        mTestLooper.dispatchAll();
 
         assertEquals(1, mWifiDiagnostics.getAlertReports().size());
         WifiDiagnostics.BugReport alertReport = mWifiDiagnostics.getAlertReports().get(0);
         assertEquals(ALERT_REASON_CODE, alertReport.errorCode);
         assertArrayEquals(ALERT_DATA, alertReport.alertData);
 
-        verify(mWifiMetrics).logFirmwareAlert(ALERT_REASON_CODE);
+        verify(mWifiMetrics).logFirmwareAlert(anyString(), eq(ALERT_REASON_CODE));
         verify(mWifiScoreCard).noteFirmwareAlert(ALERT_REASON_CODE);
     }
 
     /** Verifies that we skip the firmware and driver dumps if verbose is not enabled. */
     @Test
     public void captureBugReportSkipsFirmwareAndDriverDumpsByDefault() {
-        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
+        mWifiDiagnostics.triggerBugReportDataCapture(WifiDiagnostics.REPORT_REASON_NONE);
+        mTestLooper.dispatchAll();
         verify(mWifiNative, never()).getFwMemoryDump();
         verify(mWifiNative, never()).getDriverStateDump();
     }
@@ -762,7 +793,8 @@
     public void captureBugReportTakesFirmwareAndDriverDumpsInVerboseMode() {
         mWifiDiagnostics.enableVerboseLogging(true /* verbose enabled */);
         mWifiDiagnostics.startLogging(STA_IF_NAME);
-        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
+        mWifiDiagnostics.triggerBugReportDataCapture(WifiDiagnostics.REPORT_REASON_NONE);
+        mTestLooper.dispatchAll();
         verify(mWifiNative).getFwMemoryDump();
         verify(mWifiNative).getDriverStateDump();
     }
@@ -774,7 +806,8 @@
 
         mWifiDiagnostics.enableVerboseLogging(true /* verbose enabled */);
         mWifiDiagnostics.startLogging(STA_IF_NAME);
-        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
+        mWifiDiagnostics.triggerBugReportDataCapture(WifiDiagnostics.REPORT_REASON_NONE);
+        mTestLooper.dispatchAll();
         verify(mWifiNative).getDriverStateDump();
 
         StringWriter sw = new StringWriter();
@@ -788,7 +821,8 @@
     public void dumpOmitsDriverStateDumpIfUnavailable() {
         mWifiDiagnostics.enableVerboseLogging(true /* verbose enabled */);
         mWifiDiagnostics.startLogging(STA_IF_NAME);
-        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
+        mWifiDiagnostics.triggerBugReportDataCapture(WifiDiagnostics.REPORT_REASON_NONE);
+        mTestLooper.dispatchAll();
         verify(mWifiNative).getDriverStateDump();
 
         StringWriter sw = new StringWriter();
@@ -804,7 +838,8 @@
 
         mWifiDiagnostics.enableVerboseLogging(true /* verbose enabled */);
         mWifiDiagnostics.startLogging(STA_IF_NAME);
-        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
+        mWifiDiagnostics.triggerBugReportDataCapture(WifiDiagnostics.REPORT_REASON_NONE);
+        mTestLooper.dispatchAll();
         verify(mWifiNative).getDriverStateDump();
 
         mWifiDiagnostics.enableVerboseLogging(false /* verbose disabled */);
@@ -823,7 +858,8 @@
 
         mWifiDiagnostics.enableVerboseLogging(true /* verbose enabled */);
         mWifiDiagnostics.startLogging(STA_IF_NAME);
-        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
+        mWifiDiagnostics.triggerBugReportDataCapture(WifiDiagnostics.REPORT_REASON_NONE);
+        mTestLooper.dispatchAll();
         verify(mWifiNative).getFwMemoryDump();
 
         StringWriter sw = new StringWriter();
@@ -837,7 +873,8 @@
     public void dumpOmitsFirmwareMemoryDumpIfUnavailable() {
         mWifiDiagnostics.enableVerboseLogging(true /* verbose enabled */);
         mWifiDiagnostics.startLogging(STA_IF_NAME);
-        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
+        mWifiDiagnostics.triggerBugReportDataCapture(WifiDiagnostics.REPORT_REASON_NONE);
+        mTestLooper.dispatchAll();
         verify(mWifiNative).getFwMemoryDump();
 
         StringWriter sw = new StringWriter();
@@ -853,7 +890,8 @@
 
         mWifiDiagnostics.enableVerboseLogging(true /* verbose enabled */);
         mWifiDiagnostics.startLogging(STA_IF_NAME);
-        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
+        mWifiDiagnostics.triggerBugReportDataCapture(WifiDiagnostics.REPORT_REASON_NONE);
+        mTestLooper.dispatchAll();
         verify(mWifiNative).getFwMemoryDump();
 
         mWifiDiagnostics.enableVerboseLogging(false /* verbose disabled */);
@@ -913,7 +951,8 @@
         when(mBuildProperties.isUserBuild()).thenReturn(false);
         mResources.setBoolean(R.bool.config_wifi_diagnostics_bugreport_enabled, false);
         mWifiDiagnostics = new WifiDiagnostics(
-                mContext, mWifiInjector, mWifiNative, mBuildProperties, mLastMileLogger, mClock);
+                mContext, mWifiInjector, mWifiNative, mBuildProperties, mLastMileLogger, mClock,
+                mTestLooper.getLooper());
 
         mWifiDiagnostics.takeBugReport("", "");
         verify(mBugreportManager, never()).requestBugreport(any(), any(), any());
@@ -921,30 +960,33 @@
 
     /** Verifies that we flush HAL ringbuffer when capture bugreport. */
     @Test
-    public void captureBugReportFlushRingBufferData() {
+    public void triggerBugReportFlushRingBufferDataCapture() {
         when(mBuildProperties.isUserBuild()).thenReturn(false);
         when(mWifiNative.flushRingBufferData()).thenReturn(true);
-        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
+        mWifiDiagnostics.triggerBugReportDataCapture(WifiDiagnostics.REPORT_REASON_NONE);
+        mTestLooper.dispatchAll();
         verify(mWifiNative).flushRingBufferData();
     }
 
     /** Verifies that we flush HAL ringbuffer when detecting fatal firmware alert. */
     @Test
-    public void captureAlertFlushRingBufferData() {
+    public void triggerAlertFlushRingBufferDataCapture() {
         when(mBuildProperties.isUserBuild()).thenReturn(false);
         when(mWifiNative.flushRingBufferData()).thenReturn(true);
         /** captureAlertData with mock fatal firmware alert*/
-        mWifiDiagnostics.captureAlertData(FATAL_FW_ALERT_LIST[0], ALERT_DATA);
+        mWifiDiagnostics.onWifiAlert(FATAL_FW_ALERT_LIST[0], ALERT_DATA);
+        mTestLooper.dispatchAll();
         verify(mWifiNative).flushRingBufferData();
     }
 
     /** Verifies that we don't flush HAL ringbuffer when detecting non fatal firmware alert. */
     @Test
-    public void captureNonAlertFlushRingBufferData() {
+    public void triggerNonAlertFlushRingBufferDataCapture() {
         when(mBuildProperties.isUserBuild()).thenReturn(false);
         when(mWifiNative.flushRingBufferData()).thenReturn(true);
         /** captureAlertData with mock non fatal firmware alert*/
-        mWifiDiagnostics.captureAlertData(NON_FATAL_FW_ALERT, ALERT_DATA);
+        mWifiDiagnostics.onWifiAlert(NON_FATAL_FW_ALERT, ALERT_DATA);
+        mTestLooper.dispatchAll();
         verify(mWifiNative, never()).flushRingBufferData();
     }
 
@@ -960,7 +1002,8 @@
 
         final byte[] data = new byte[SMALL_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE];
         mWifiDiagnostics.onRingBufferData(mFakeRbs, data);
-        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
+        mWifiDiagnostics.triggerBugReportDataCapture(WifiDiagnostics.REPORT_REASON_NONE);
+        mTestLooper.dispatchAll();
 
         byte[][] ringBufferData = getLoggerRingBufferData();
         assertEquals(1, ringBufferData.length);
@@ -981,7 +1024,8 @@
 
         final byte[] data = new byte[SMALL_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE];
         mWifiDiagnostics.onRingBufferData(mFakeRbs, data);
-        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
+        mWifiDiagnostics.triggerBugReportDataCapture(WifiDiagnostics.REPORT_REASON_NONE);
+        mTestLooper.dispatchAll();
 
         byte[][] ringBufferData = getLoggerRingBufferData();
         assertEquals(1, ringBufferData.length);
@@ -1003,7 +1047,8 @@
         mWifiDiagnostics.startLogging(AP_IF_NAME);
 
         mWifiDiagnostics.onRingBufferData(mFakeRbs, data);
-        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
+        mWifiDiagnostics.triggerBugReportDataCapture(WifiDiagnostics.REPORT_REASON_NONE);
+        mTestLooper.dispatchAll();
 
         byte[][] ringBufferData0 = getLoggerRingBufferData();
         assertEquals(1, ringBufferData0.length);
@@ -1012,7 +1057,8 @@
         mWifiDiagnostics.stopLogging(STA_IF_NAME);
 
         mWifiDiagnostics.onRingBufferData(mFakeRbs, data);
-        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
+        mWifiDiagnostics.triggerBugReportDataCapture(WifiDiagnostics.REPORT_REASON_NONE);
+        mTestLooper.dispatchAll();
 
         byte[][] ringBufferData1 = getLoggerRingBufferData();
         assertEquals(1, ringBufferData1.length);
@@ -1034,7 +1080,8 @@
         mWifiDiagnostics.startLogging(AP_IF_NAME);
 
         mWifiDiagnostics.onRingBufferData(mFakeRbs, data);
-        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
+        mWifiDiagnostics.triggerBugReportDataCapture(WifiDiagnostics.REPORT_REASON_NONE);
+        mTestLooper.dispatchAll();
 
         byte[][] ringBufferData0 = getLoggerRingBufferData();
         assertEquals(1, ringBufferData0.length);
@@ -1043,7 +1090,8 @@
         mWifiDiagnostics.stopLogging(AP_IF_NAME);
 
         mWifiDiagnostics.onRingBufferData(mFakeRbs, data);
-        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
+        mWifiDiagnostics.triggerBugReportDataCapture(WifiDiagnostics.REPORT_REASON_NONE);
+        mTestLooper.dispatchAll();
 
         byte[][] ringBufferData1 = getLoggerRingBufferData();
         assertEquals(1, ringBufferData1.length);
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiGlobalsTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiGlobalsTest.java
new file mode 100644
index 0000000..e3e9274
--- /dev/null
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiGlobalsTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wifi.resources.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+
+/** Unit tests for {@link WifiGlobals} */
+@SmallTest
+public class WifiGlobalsTest extends WifiBaseTest {
+
+    private WifiGlobals mWifiGlobals;
+    private MockResources mResources;
+
+    @Mock private Context mContext;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mResources = new MockResources();
+        mResources.setInteger(R.integer.config_wifiPollRssiIntervalMilliseconds, 3000);
+        mResources.setInteger(R.integer.config_wifiClientModeImplNumLogRecs, 200);
+        when(mContext.getResources()).thenReturn(mResources);
+
+        mWifiGlobals = new WifiGlobals(mContext);
+    }
+
+    /** Test that the interval for poll RSSI is read from config overlay correctly. */
+    @Test
+    public void testPollRssiIntervalIsSetCorrectly() throws Exception {
+        assertEquals(3000, mWifiGlobals.getPollRssiIntervalMillis());
+        mResources.setInteger(R.integer.config_wifiPollRssiIntervalMilliseconds, 6000);
+        assertEquals(6000, mWifiGlobals.getPollRssiIntervalMillis());
+        mResources.setInteger(R.integer.config_wifiPollRssiIntervalMilliseconds, 7000);
+        assertEquals(6000, mWifiGlobals.getPollRssiIntervalMillis());
+    }
+
+    /** Verify that Bluetooth active is set correctly with BT state/connection state changes */
+    @Test
+    public void verifyBluetoothStateAndConnectionStateChanges() {
+        mWifiGlobals.setBluetoothEnabled(true);
+        assertThat(mWifiGlobals.isBluetoothConnected()).isFalse();
+
+        mWifiGlobals.setBluetoothConnected(true);
+        assertThat(mWifiGlobals.isBluetoothConnected()).isTrue();
+
+        mWifiGlobals.setBluetoothEnabled(false);
+        assertThat(mWifiGlobals.isBluetoothConnected()).isFalse();
+
+        mWifiGlobals.setBluetoothEnabled(true);
+        assertThat(mWifiGlobals.isBluetoothConnected()).isFalse();
+
+        mWifiGlobals.setBluetoothConnected(true);
+        assertThat(mWifiGlobals.isBluetoothConnected()).isTrue();
+
+        mWifiGlobals.setBluetoothConnected(false);
+        assertThat(mWifiGlobals.isBluetoothConnected()).isFalse();
+
+        mWifiGlobals.setBluetoothConnected(true);
+        assertThat(mWifiGlobals.isBluetoothConnected()).isTrue();
+    }
+
+    /** Verify SAE Hash-to-Element overlay. */
+    @Test
+    public void testSaeH2eSupportOverlay() {
+        mResources.setBoolean(R.bool.config_wifiSaeH2eSupported, false);
+        mWifiGlobals = new WifiGlobals(mContext);
+        assertFalse(mWifiGlobals.isWpa3SaeH2eSupported());
+
+        mResources.setBoolean(R.bool.config_wifiSaeH2eSupported, true);
+        mWifiGlobals = new WifiGlobals(mContext);
+        assertTrue(mWifiGlobals.isWpa3SaeH2eSupported());
+    }
+
+    /** Verify P2P device name customization. */
+    @Test
+    public void testP2pDeviceNameCustomization() {
+        final String customPrefix = "Custom-";
+        final int customPostfixDigit = 5;
+        mResources.setString(R.string.config_wifiP2pDeviceNamePrefix, customPrefix);
+        mResources.setInteger(R.integer.config_wifiP2pDeviceNamePostfixNumDigits,
+                customPostfixDigit);
+        mWifiGlobals = new WifiGlobals(mContext);
+        assertEquals(customPrefix, mWifiGlobals.getWifiP2pDeviceNamePrefix());
+        assertEquals(customPostfixDigit, mWifiGlobals.getWifiP2pDeviceNamePostfixNumDigits());
+    }
+
+    /** Test that the number of log records is read from config overlay correctly. */
+    @Test
+    public void testNumLogRecsNormalIsSetCorrectly() throws Exception {
+        assertEquals(200, mWifiGlobals.getClientModeImplNumLogRecs());
+    }
+}
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiHealthMonitorTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiHealthMonitorTest.java
index c679954..8fe1c07 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiHealthMonitorTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiHealthMonitorTest.java
@@ -31,6 +31,7 @@
 import android.content.pm.ModuleInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.res.Resources;
 import android.net.MacAddress;
 import android.net.wifi.ScanResult.InformationElement;
 import android.net.wifi.WifiConfiguration;
@@ -46,6 +47,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.server.wifi.ActiveModeWarden.ModeChangeCallback;
 import com.android.server.wifi.WifiConfigManager.OnNetworkUpdateListener;
 import com.android.server.wifi.WifiHealthMonitor.ScanStats;
 import com.android.server.wifi.WifiHealthMonitor.WifiSoftwareBuildInfo;
@@ -54,10 +56,11 @@
 import com.android.server.wifi.proto.WifiScoreCardProto.SystemInfoStats;
 import com.android.server.wifi.proto.WifiStatsLog;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.HealthMonitorMetrics;
-
+import com.android.wifi.resources.R;
 
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.MockitoSession;
@@ -78,6 +81,7 @@
     static final WifiSsid TEST_SSID_2 = WifiSsid.createFromAsciiEncoded("Poe's Place");
     static final MacAddress TEST_BSSID_1 = MacAddress.fromString("aa:bb:cc:dd:ee:ff");
     private static final long CURRENT_ELAPSED_TIME_MS = 1000;
+    private static final String WIFI_IFACE_NAME = "wlanTest";
 
     private WifiScoreCard mWifiScoreCard;
     private WifiHealthMonitor mWifiHealthMonitor;
@@ -101,6 +105,12 @@
     PackageInfo mPackageInfo;
     @Mock
     ModuleInfo mModuleInfo;
+    @Mock
+    FrameworkFacade mFrameworkFacade;
+    @Mock
+    Resources mResources;
+    @Mock
+    ActiveModeWarden mActiveModeWarden;
 
     private final ArrayList<String> mKeys = new ArrayList<>();
     private final ArrayList<WifiScoreCard.BlobListener> mBlobListeners = new ArrayList<>();
@@ -121,6 +131,7 @@
     private ScanData mScanData;
     private ScanListener mScanListener;
     private OnNetworkUpdateListener mOnNetworkUpdateListener;
+    private ModeChangeCallback mModeChangeCallback;
 
     private void millisecondsPass(long ms) {
         mMilliSecondsSinceBoot += ms;
@@ -139,7 +150,7 @@
         mBlobs.clear();
         mConfiguredNetworks = new ArrayList<>();
         mMilliSecondsSinceBoot = 0;
-        mWifiInfo = new ExtendedWifiInfo(mock(Context.class));
+        mWifiInfo = new ExtendedWifiInfo(mock(WifiGlobals.class), WIFI_IFACE_NAME);
         mWifiInfo.setBSSID(TEST_BSSID_1.toString());
         mWifiInfo.setSSID(TEST_SSID_1);
         // Add 1st configuration
@@ -164,7 +175,8 @@
 
         mWifiConfigManager = mockConfigManager();
 
-        mWifiScoreCard = new WifiScoreCard(mClock, "some seed", mDeviceConfigFacade);
+        mWifiScoreCard = new WifiScoreCard(mClock, "some seed", mDeviceConfigFacade,
+                mFrameworkFacade, mContext);
         mAlarmManager = new TestAlarmManager();
         when(mContext.getSystemService(Context.ALARM_SERVICE))
                 .thenReturn(mAlarmManager.getAlarmManager());
@@ -178,6 +190,10 @@
                 DeviceConfigFacade.DEFAULT_CONNECTION_FAILURE_HIGH_THR_PERCENT);
         when(mDeviceConfigFacade.getConnectionFailureCountMin()).thenReturn(
                 DeviceConfigFacade.DEFAULT_CONNECTION_FAILURE_COUNT_MIN);
+        when(mDeviceConfigFacade.getConnectionFailureDisconnectionHighThrPercent()).thenReturn(
+                DeviceConfigFacade.DEFAULT_CONNECTION_FAILURE_DISCONNECTION_HIGH_THR_PERCENT);
+        when(mDeviceConfigFacade.getConnectionFailureDisconnectionCountMin()).thenReturn(
+                DeviceConfigFacade.DEFAULT_CONNECTION_FAILURE_DISCONNECTION_COUNT_MIN);
         when(mDeviceConfigFacade.getAssocRejectionHighThrPercent()).thenReturn(
                 DeviceConfigFacade.DEFAULT_ASSOC_REJECTION_HIGH_THR_PERCENT);
         when(mDeviceConfigFacade.getAssocRejectionCountMin()).thenReturn(
@@ -216,9 +232,18 @@
                 DeviceConfigFacade.DEFAULT_NONSTATIONARY_SCAN_RSSI_VALID_TIME_MS);
         when(mDeviceConfigFacade.getStationaryScanRssiValidTimeMs()).thenReturn(
                 DeviceConfigFacade.DEFAULT_STATIONARY_SCAN_RSSI_VALID_TIME_MS);
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getIntArray(R.array.config_wifiRssiLevelThresholds))
+                .thenReturn(new int[]{-88, -77, -66, -55});
         mWifiHealthMonitor = new WifiHealthMonitor(mContext, mWifiInjector, mClock,
                 mWifiConfigManager, mWifiScoreCard, new Handler(mLooper.getLooper()), mWifiNative,
-                "some seed", mDeviceConfigFacade);
+                "some seed", mDeviceConfigFacade, mActiveModeWarden);
+
+        ArgumentCaptor<ModeChangeCallback> modeChangeCallbackArgumentCaptor =
+                ArgumentCaptor.forClass(ModeChangeCallback.class);
+        verify(mActiveModeWarden).registerModeChangeCallback(
+                modeChangeCallbackArgumentCaptor.capture());
+        mModeChangeCallback = modeChangeCallbackArgumentCaptor.getValue();
     }
 
     private WifiConfigManager mockConfigManager() {
@@ -285,7 +310,7 @@
 
         ScanData[] scanDatas = new ScanData[1];
         scanDatas[0] = mock(ScanData.class);
-        when(scanDatas[0].getBandScanned()).thenReturn(wifiBand);
+        when(scanDatas[0].getScannedBandsInternal()).thenReturn(wifiBand);
         doAnswer(new AnswerWithArguments() {
             public void answer(ScanSettings settings, ScanListener listener) throws Exception {
                 if (mScanData != null && mScanData.getResults() != null) {
@@ -306,14 +331,15 @@
         mWifiScoreCard.noteConnectionAttempt(mWifiInfo, -53, mWifiInfo.getSSID());
         millisecondsPass(5000);
         mWifiInfo.setRssi(-55);
+        mWifiScoreCard.noteIpConfiguration(mWifiInfo);
         mWifiScoreCard.noteValidationSuccess(mWifiInfo);
         millisecondsPass(1000);
         mWifiScoreCard.noteSignalPoll(mWifiInfo);
         millisecondsPass(2000);
         int disconnectionReason = 0;
-        mWifiScoreCard.noteNonlocalDisconnect(disconnectionReason);
+        mWifiScoreCard.noteNonlocalDisconnect(WIFI_IFACE_NAME, disconnectionReason);
         millisecondsPass(10);
-        mWifiScoreCard.resetConnectionState();
+        mWifiScoreCard.resetAllConnectionStates();
     }
 
     private void makeRecentStatsWithSufficientConnectionAttempt() {
@@ -322,11 +348,23 @@
         }
     }
 
+    private void setWifiEnabled(boolean enabled) {
+        if (enabled) {
+            when(mActiveModeWarden.getPrimaryClientModeManagerNullable())
+                    .thenReturn(mock(ConcreteClientModeManager.class));
+            mModeChangeCallback.onActiveModeManagerAdded(mock(ConcreteClientModeManager.class));
+        } else {
+            when(mActiveModeWarden.getPrimaryClientModeManagerNullable()).thenReturn(null);
+            mModeChangeCallback.onActiveModeManagerRemoved(mock(ConcreteClientModeManager.class));
+        }
+    }
+
     private byte[] makeSerializedExample() {
-        // Install a dummy memoryStore
+        // Install a placeholder memoryStore
         // trigger extractCurrentSoftwareBuildInfo() call to update currSoftwareBuildInfo
         mWifiHealthMonitor.installMemoryStoreSetUpDetectionAlarm(mMemoryStore);
-        mWifiHealthMonitor.setWifiEnabled(true);
+        setWifiEnabled(true);
+
         assertEquals(MODULE_VERSION, mWifiHealthMonitor.getWifiStackVersion());
         millisecondsPass(5000);
         mWifiScanner.startScan(mScanSettings, mScanListener);
@@ -375,7 +413,7 @@
         mAlarmManager.dispatch(WifiHealthMonitor.POST_BOOT_DETECTION_TIMER_TAG);
         mLooper.dispatchAll();
         // Now it should detect SW change, disable WiFi to trigger write
-        mWifiHealthMonitor.setWifiEnabled(false);
+        setWifiEnabled(false);
 
         // Check current and previous FW version of WifiSystemInfoStats
         WifiSystemInfoStats wifiSystemInfoStats = mWifiHealthMonitor.getWifiSystemInfoStats();
@@ -400,10 +438,10 @@
      */
     @Test
     public void testSerializationDeserialization() throws Exception  {
-        // Install a dummy memoryStore
+        // Install a placeholder memoryStore
         // trigger extractCurrentSoftwareBuildInfo() call to update currSoftwareBuildInfo
         mWifiHealthMonitor.installMemoryStoreSetUpDetectionAlarm(mMemoryStore);
-        mWifiHealthMonitor.setWifiEnabled(true);
+        setWifiEnabled(true);
         millisecondsPass(5000);
         mWifiScanner.startScan(mScanSettings, mScanListener);
         mAlarmManager.dispatch(WifiHealthMonitor.POST_BOOT_DETECTION_TIMER_TAG);
@@ -522,6 +560,7 @@
         assertEquals(0, healthMetrics.failureStatsIncrease.cntAssocTimeout);
         assertEquals(0, healthMetrics.failureStatsIncrease.cntAuthFailure);
         assertEquals(0, healthMetrics.failureStatsIncrease.cntConnectionFailure);
+        assertEquals(0, healthMetrics.failureStatsIncrease.cntDisconnectionNonlocalConnecting);
         assertEquals(0, healthMetrics.failureStatsIncrease.cntDisconnectionNonlocal);
         assertEquals(0, healthMetrics.failureStatsIncrease.cntShortConnectionNonlocal);
         assertEquals(0, healthMetrics.failureStatsHigh.cntAssocRejection);
@@ -601,7 +640,8 @@
         String firmwareVersion = "HW 1.2";
         makeSwBuildChangeExample(firmwareVersion);
         // Disable WiFi before post-boot-detection
-        mWifiHealthMonitor.setWifiEnabled(false);
+        setWifiEnabled(false);
+
         mAlarmManager.dispatch(WifiHealthMonitor.POST_BOOT_DETECTION_TIMER_TAG);
         mLooper.dispatchAll();
         // Skip SW build change detection
@@ -612,7 +652,7 @@
                 perNetwork.getStatsPrevBuild().getCount(WifiScoreCard.CNT_CONNECTION_ATTEMPT));
 
         // Day 3
-        mWifiHealthMonitor.setWifiEnabled(true);
+        setWifiEnabled(true);
         mAlarmManager.dispatch(WifiHealthMonitor.POST_BOOT_DETECTION_TIMER_TAG);
         mLooper.dispatchAll();
         // Finally detect SW build change
@@ -666,7 +706,7 @@
     @Test
     public void testFullBandScan() throws Exception {
         millisecondsPass(5000);
-        mWifiHealthMonitor.setWifiEnabled(true);
+        setWifiEnabled(true);
         mWifiScanner.startScan(mScanSettings, mScanListener);
         ScanStats scanStats = mWifiHealthMonitor.getWifiSystemInfoStats().getCurrScanStats();
         assertEquals(1_500_000_005_000L, scanStats.getLastScanTimeMs());
@@ -682,7 +722,7 @@
         mWifiScanner = mockWifiScanner(WifiScanner.WIFI_BAND_24_GHZ);
         when(mWifiInjector.getWifiScanner()).thenReturn(mWifiScanner);
         millisecondsPass(5000);
-        mWifiHealthMonitor.setWifiEnabled(true);
+        setWifiEnabled(true);
         mWifiScanner.startScan(mScanSettings, mScanListener);
         ScanStats scanStats = mWifiHealthMonitor.getWifiSystemInfoStats().getCurrScanStats();
         assertEquals(TS_NONE, scanStats.getLastScanTimeMs());
@@ -741,7 +781,7 @@
         mWifiScanner = mockWifiScanner(WifiScanner.WIFI_BAND_ALL);
         when(mWifiInjector.getWifiScanner()).thenReturn(mWifiScanner);
         millisecondsPass(5000);
-        mWifiHealthMonitor.setWifiEnabled(true);
+        setWifiEnabled(true);
         mWifiScanner.startScan(mScanSettings, mScanListener);
 
         mAlarmManager.dispatch(WifiHealthMonitor.POST_BOOT_DETECTION_TIMER_TAG);
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiKeyStoreTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiKeyStoreTest.java
index f6cae66..279a358 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiKeyStoreTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiKeyStoreTest.java
@@ -16,9 +16,12 @@
 
 package com.android.server.wifi;
 
+import static com.android.server.wifi.WifiConfigurationTestUtil.TEST_UID;
+
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.AdditionalMatchers.aryEq;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
@@ -28,11 +31,15 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import android.content.Context;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiEnterpriseConfig;
+import android.os.UserHandle;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.modules.utils.build.SdkLevel;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -48,14 +55,21 @@
 @SmallTest
 public class WifiKeyStoreTest extends WifiBaseTest {
     @Mock private WifiEnterpriseConfig mWifiEnterpriseConfig;
+    @Mock private WifiEnterpriseConfig mExistingWifiEnterpriseConfig;
     @Mock private KeyStore mKeyStore;
+    @Mock private Context mContext;
+    @Mock private FrameworkFacade mFrameworkFacade;
 
     private WifiKeyStore mWifiKeyStore;
     private static final String TEST_KEY_ID = "blah";
     private static final String USER_CERT_ALIAS = "aabbccddee";
     private static final String USER_CA_CERT_ALIAS = "aacccddd";
+    private static final String USER_CA_CERT_ALIAS2 = "bbbccccaaa";
     private static final String [] USER_CA_CERT_ALIASES = {"aacccddd", "bbbccccaaa"};
     private static final String TEST_PACKAGE_NAME = "TestApp";
+    private static final String KEYCHAIN_ALIAS = "kc-alias";
+    private static final String KEYCHAIN_KEY_GRANT = "kc-grant";
+    public static final UserHandle TEST_USER_HANDLE = UserHandle.getUserHandleForUid(TEST_UID);
 
     /**
      * Setup the mocks and an instance of WifiConfigManager before each test.
@@ -63,7 +77,7 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mWifiKeyStore = new WifiKeyStore(mKeyStore);
+        mWifiKeyStore = new WifiKeyStore(mContext, mKeyStore, mFrameworkFacade);
 
         when(mWifiEnterpriseConfig.getClientCertificateAlias()).thenReturn(USER_CERT_ALIAS);
         when(mWifiEnterpriseConfig.getCaCertificateAlias()).thenReturn(USER_CA_CERT_ALIAS);
@@ -328,6 +342,33 @@
     }
 
     /**
+     * Test configuring WPA3-Enterprise in 192-bit mode for RSA 3072 fails when a client certificate
+     * key length is less than 3072 bits
+     */
+    @Test
+    public void testConfigurationFailureSuiteB2048Rsa() throws Exception {
+        when(mWifiEnterpriseConfig.getCaCertificateAliases())
+                .thenReturn(new String[]{USER_CA_CERT_ALIAS});
+        when(mWifiEnterpriseConfig.getClientPrivateKey())
+                .thenReturn(FakeKeys.CLIENT_BAD_SUITE_B_RSA2048_KEY);
+        when(mWifiEnterpriseConfig.getClientCertificate()).thenReturn(
+                FakeKeys.CLIENT_SUITE_B_RSA3072_CERT);
+        when(mWifiEnterpriseConfig.getCaCertificate()).thenReturn(FakeKeys.CA_SUITE_B_RSA3072_CERT);
+        when(mWifiEnterpriseConfig.getClientCertificateChain())
+                .thenReturn(new X509Certificate[]{FakeKeys.CLIENT_BAD_SUITE_B_RSA2048_CERT});
+        when(mWifiEnterpriseConfig.getCaCertificates())
+                .thenReturn(new X509Certificate[]{FakeKeys.CA_SUITE_B_RSA3072_CERT});
+        when(mKeyStore.getCertificate(eq(USER_CERT_ALIAS))).thenReturn(
+                FakeKeys.CLIENT_BAD_SUITE_B_RSA2048_CERT);
+        when(mKeyStore.getCertificate(eq(USER_CA_CERT_ALIASES[0]))).thenReturn(
+                FakeKeys.CA_SUITE_B_RSA3072_CERT);
+        WifiConfiguration savedNetwork = WifiConfigurationTestUtil.createEapSuiteBNetwork(
+                WifiConfiguration.SuiteBCipher.ECDHE_RSA);
+        savedNetwork.enterpriseConfig = mWifiEnterpriseConfig;
+        assertFalse(mWifiKeyStore.updateNetworkKeys(savedNetwork, null));
+    }
+
+    /**
      * Test configuring WPA3-Enterprise in 192-bit mode for RSA 3072 fails when one CA in the list
      * is RSA but not with the required security
      */
@@ -389,4 +430,242 @@
         savedNetwork.enterpriseConfig = mWifiEnterpriseConfig;
         assertFalse(mWifiKeyStore.updateNetworkKeys(savedNetwork, null));
     }
+
+    /**
+     * Test configuring WPA3-Enterprise in 192-bit mode for ECDSA fails when a client
+     * certificate key bit length is less than 384 bits.
+     */
+    @Test
+    public void testConfigureFailureSuiteBEcdsa256() throws Exception {
+        when(mWifiEnterpriseConfig.getCaCertificateAliases())
+                .thenReturn(new String[]{USER_CA_CERT_ALIAS});
+        when(mWifiEnterpriseConfig.getClientPrivateKey())
+                .thenReturn(FakeKeys.CLIENT_BAD_SUITE_B_ECC_256_KEY);
+        when(mWifiEnterpriseConfig.getClientCertificate()).thenReturn(
+                FakeKeys.CLIENT_BAD_SUITE_B_ECDSA_256_CERT);
+        when(mWifiEnterpriseConfig.getCaCertificate()).thenReturn(FakeKeys.CA_SUITE_B_ECDSA_CERT);
+        when(mWifiEnterpriseConfig.getClientCertificateChain())
+                .thenReturn(new X509Certificate[]{FakeKeys.CLIENT_BAD_SUITE_B_ECDSA_256_CERT});
+        when(mWifiEnterpriseConfig.getCaCertificates())
+                .thenReturn(new X509Certificate[]{FakeKeys.CA_SUITE_B_ECDSA_CERT});
+        when(mKeyStore.getCertificate(eq(USER_CERT_ALIAS))).thenReturn(
+                FakeKeys.CLIENT_BAD_SUITE_B_ECDSA_256_CERT);
+        when(mKeyStore.getCertificate(eq(USER_CA_CERT_ALIASES[0]))).thenReturn(
+                FakeKeys.CA_SUITE_B_ECDSA_CERT);
+        WifiConfiguration savedNetwork = WifiConfigurationTestUtil.createEapSuiteBNetwork(
+                WifiConfiguration.SuiteBCipher.ECDHE_ECDSA);
+        savedNetwork.enterpriseConfig = mWifiEnterpriseConfig;
+        assertFalse(mWifiKeyStore.updateNetworkKeys(savedNetwork, null));
+    }
+    /**
+     * Test to confirm that old CA alias was removed only if the certificate was installed
+     * by the app.
+     */
+    @Test
+    public void testConfirmCaCertAliasRemoved() throws Exception {
+        when(mWifiEnterpriseConfig.getCaCertificateAliases())
+                .thenReturn(new String[]{USER_CA_CERT_ALIAS});
+        when(mWifiEnterpriseConfig.getClientPrivateKey())
+                .thenReturn(FakeKeys.CLIENT_SUITE_B_RSA3072_KEY);
+        when(mWifiEnterpriseConfig.getClientCertificate()).thenReturn(
+                FakeKeys.CLIENT_SUITE_B_RSA3072_CERT);
+        when(mWifiEnterpriseConfig.getCaCertificate()).thenReturn(FakeKeys.CA_SUITE_B_RSA3072_CERT);
+        when(mWifiEnterpriseConfig.getClientCertificateChain())
+                .thenReturn(new X509Certificate[]{FakeKeys.CLIENT_SUITE_B_RSA3072_CERT});
+        when(mWifiEnterpriseConfig.getCaCertificates())
+                .thenReturn(new X509Certificate[]{FakeKeys.CA_SUITE_B_RSA3072_CERT});
+        when(mWifiEnterpriseConfig.isAppInstalledCaCert()).thenReturn(true);
+        when(mKeyStore.getCertificate(eq(USER_CERT_ALIAS))).thenReturn(
+                FakeKeys.CLIENT_SUITE_B_RSA3072_CERT);
+        when(mKeyStore.getCertificate(eq(USER_CA_CERT_ALIASES[0]))).thenReturn(
+                FakeKeys.CA_SUITE_B_RSA3072_CERT);
+        WifiConfiguration savedNetwork = WifiConfigurationTestUtil.createEapSuiteBNetwork(
+                WifiConfiguration.SuiteBCipher.ECDHE_RSA);
+        savedNetwork.enterpriseConfig = mWifiEnterpriseConfig;
+
+        mExistingWifiEnterpriseConfig = mWifiEnterpriseConfig;
+        when(mExistingWifiEnterpriseConfig.getCaCertificateAliases())
+                .thenReturn(new String[]{USER_CA_CERT_ALIAS2});
+        WifiConfiguration existingNetwork = savedNetwork;
+        when(mKeyStore.getCertificate(eq(USER_CA_CERT_ALIASES[1]))).thenReturn(
+                FakeKeys.CA_SUITE_B_RSA3072_CERT);
+        existingNetwork.enterpriseConfig = mExistingWifiEnterpriseConfig;
+
+        assertTrue(mWifiKeyStore.updateNetworkKeys(savedNetwork, existingNetwork));
+        verify(mKeyStore).deleteEntry(eq(USER_CA_CERT_ALIAS2));
+    }
+
+    /**
+     * Test to confirm that old CA alias was not removed when the certificate was not installed
+     * by the app.
+     */
+    @Test
+    public void testConfirmCaCertAliasNotRemoved() throws Exception {
+        when(mWifiEnterpriseConfig.getCaCertificateAliases())
+                .thenReturn(new String[]{USER_CA_CERT_ALIAS});
+        when(mWifiEnterpriseConfig.getClientPrivateKey())
+                .thenReturn(FakeKeys.CLIENT_SUITE_B_RSA3072_KEY);
+        when(mWifiEnterpriseConfig.getClientCertificate()).thenReturn(
+                FakeKeys.CLIENT_SUITE_B_RSA3072_CERT);
+        when(mWifiEnterpriseConfig.getCaCertificate()).thenReturn(FakeKeys.CA_SUITE_B_RSA3072_CERT);
+        when(mWifiEnterpriseConfig.getClientCertificateChain())
+                .thenReturn(new X509Certificate[]{FakeKeys.CLIENT_SUITE_B_RSA3072_CERT});
+        when(mWifiEnterpriseConfig.getCaCertificates())
+                .thenReturn(new X509Certificate[]{FakeKeys.CA_SUITE_B_RSA3072_CERT});
+        when(mWifiEnterpriseConfig.isAppInstalledCaCert()).thenReturn(false);
+        when(mKeyStore.getCertificate(eq(USER_CERT_ALIAS))).thenReturn(
+                FakeKeys.CLIENT_SUITE_B_RSA3072_CERT);
+        when(mKeyStore.getCertificate(eq(USER_CA_CERT_ALIASES[0]))).thenReturn(
+                FakeKeys.CA_SUITE_B_RSA3072_CERT);
+        WifiConfiguration savedNetwork = WifiConfigurationTestUtil.createEapSuiteBNetwork(
+                WifiConfiguration.SuiteBCipher.ECDHE_RSA);
+        savedNetwork.enterpriseConfig = mWifiEnterpriseConfig;
+
+        mExistingWifiEnterpriseConfig = mWifiEnterpriseConfig;
+        when(mExistingWifiEnterpriseConfig.getCaCertificateAliases())
+                .thenReturn(new String[]{USER_CA_CERT_ALIAS2});
+        WifiConfiguration existingNetwork = savedNetwork;
+        when(mKeyStore.getCertificate(eq(USER_CA_CERT_ALIASES[1]))).thenReturn(
+                FakeKeys.CA_SUITE_B_RSA3072_CERT);
+        existingNetwork.enterpriseConfig = mExistingWifiEnterpriseConfig;
+
+        assertTrue(mWifiKeyStore.updateNetworkKeys(savedNetwork, existingNetwork));
+        verify(mKeyStore, never()).deleteEntry(eq(USER_CA_CERT_ALIAS2));
+    }
+
+    /**
+     * Test to confirm that old client certificate alias was removed only if the certificate was
+     * installed by the app.
+     */
+    @Test
+    public void testConfirmClientCertAliasRemoved() throws Exception {
+        when(mWifiEnterpriseConfig.getCaCertificateAliases())
+                .thenReturn(new String[]{USER_CA_CERT_ALIAS});
+        when(mWifiEnterpriseConfig.getClientPrivateKey())
+                .thenReturn(FakeKeys.CLIENT_SUITE_B_RSA3072_KEY);
+        when(mWifiEnterpriseConfig.getClientCertificate()).thenReturn(
+                FakeKeys.CLIENT_SUITE_B_RSA3072_CERT);
+        when(mWifiEnterpriseConfig.getCaCertificate()).thenReturn(FakeKeys.CA_SUITE_B_RSA3072_CERT);
+        when(mWifiEnterpriseConfig.getClientCertificateChain())
+                .thenReturn(new X509Certificate[]{FakeKeys.CLIENT_SUITE_B_RSA3072_CERT});
+        when(mWifiEnterpriseConfig.getCaCertificates())
+                .thenReturn(new X509Certificate[]{FakeKeys.CA_SUITE_B_RSA3072_CERT});
+        when(mWifiEnterpriseConfig.isAppInstalledDeviceKeyAndCert()).thenReturn(true);
+        when(mKeyStore.getCertificate(eq(USER_CERT_ALIAS))).thenReturn(
+                FakeKeys.CLIENT_SUITE_B_RSA3072_CERT);
+        when(mKeyStore.getCertificate(eq(USER_CA_CERT_ALIASES[0]))).thenReturn(
+                FakeKeys.CA_SUITE_B_RSA3072_CERT);
+        WifiConfiguration savedNetwork = WifiConfigurationTestUtil.createEapSuiteBNetwork(
+                WifiConfiguration.SuiteBCipher.ECDHE_RSA);
+        savedNetwork.enterpriseConfig = mWifiEnterpriseConfig;
+
+        mExistingWifiEnterpriseConfig = mWifiEnterpriseConfig;
+        when(mExistingWifiEnterpriseConfig.getCaCertificateAliases())
+                .thenReturn(new String[]{USER_CA_CERT_ALIAS2});
+        WifiConfiguration existingNetwork = WifiConfigurationTestUtil.createEapSuiteBNetwork(
+                WifiConfiguration.SuiteBCipher.ECDHE_RSA);
+        when(mKeyStore.getCertificate(eq(USER_CA_CERT_ALIASES[1]))).thenReturn(
+                FakeKeys.CA_SUITE_B_RSA3072_CERT);
+        existingNetwork.enterpriseConfig = mExistingWifiEnterpriseConfig;
+
+        assertTrue(mWifiKeyStore.updateNetworkKeys(savedNetwork, existingNetwork));
+        verify(mKeyStore).deleteEntry(eq(existingNetwork.getKeyIdForCredentials(existingNetwork)));
+    }
+
+    /**
+     * Test to confirm that old client certificate alias was not removed if the certificate was not
+     * installed by the app.
+     */
+    @Test
+    public void testConfirmClientCertAliasNotRemoved() throws Exception {
+        when(mWifiEnterpriseConfig.getCaCertificateAliases())
+                .thenReturn(new String[]{USER_CA_CERT_ALIAS});
+        when(mWifiEnterpriseConfig.getClientPrivateKey())
+                .thenReturn(FakeKeys.CLIENT_SUITE_B_RSA3072_KEY);
+        when(mWifiEnterpriseConfig.getClientCertificate()).thenReturn(
+                FakeKeys.CLIENT_SUITE_B_RSA3072_CERT);
+        when(mWifiEnterpriseConfig.getCaCertificate()).thenReturn(FakeKeys.CA_SUITE_B_RSA3072_CERT);
+        when(mWifiEnterpriseConfig.getClientCertificateChain())
+                .thenReturn(new X509Certificate[]{FakeKeys.CLIENT_SUITE_B_RSA3072_CERT});
+        when(mWifiEnterpriseConfig.getCaCertificates())
+                .thenReturn(new X509Certificate[]{FakeKeys.CA_SUITE_B_RSA3072_CERT});
+        when(mWifiEnterpriseConfig.isAppInstalledDeviceKeyAndCert()).thenReturn(false);
+        when(mKeyStore.getCertificate(eq(USER_CERT_ALIAS))).thenReturn(
+                FakeKeys.CLIENT_SUITE_B_RSA3072_CERT);
+        when(mKeyStore.getCertificate(eq(USER_CA_CERT_ALIASES[0]))).thenReturn(
+                FakeKeys.CA_SUITE_B_RSA3072_CERT);
+        WifiConfiguration savedNetwork = WifiConfigurationTestUtil.createEapSuiteBNetwork(
+                WifiConfiguration.SuiteBCipher.ECDHE_RSA);
+        savedNetwork.enterpriseConfig = mWifiEnterpriseConfig;
+
+        mExistingWifiEnterpriseConfig = mWifiEnterpriseConfig;
+        when(mExistingWifiEnterpriseConfig.getCaCertificateAliases())
+                .thenReturn(new String[]{USER_CA_CERT_ALIAS2});
+        WifiConfiguration existingNetwork = WifiConfigurationTestUtil.createEapSuiteBNetwork(
+                WifiConfiguration.SuiteBCipher.ECDHE_RSA);
+        when(mKeyStore.getCertificate(eq(USER_CA_CERT_ALIASES[1]))).thenReturn(
+                FakeKeys.CA_SUITE_B_RSA3072_CERT);
+        existingNetwork.enterpriseConfig = mExistingWifiEnterpriseConfig;
+
+        assertTrue(mWifiKeyStore.updateNetworkKeys(savedNetwork, existingNetwork));
+        verify(mKeyStore, never()).deleteEntry(eq(USER_CERT_ALIAS));
+    }
+
+    @Test
+    public void testUpdateKeysKeyChainAliasNotGranted() {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        final WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork();
+        when(mWifiEnterpriseConfig.getClientKeyPairAliasInternal()).thenReturn(KEYCHAIN_ALIAS);
+        when(mFrameworkFacade.getWifiKeyGrantAsUser(
+                any(Context.class), any(UserHandle.class), any(String.class))).thenReturn(null);
+        config.enterpriseConfig = mWifiEnterpriseConfig;
+
+        assertFalse(mWifiKeyStore.updateNetworkKeys(config, null));
+    }
+
+    @Test
+    public void testUpdateKeysKeyChainAliasGranted() {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        final WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork();
+        when(mWifiEnterpriseConfig.getClientKeyPairAliasInternal()).thenReturn(KEYCHAIN_ALIAS);
+        when(mFrameworkFacade.getWifiKeyGrantAsUser(
+                any(Context.class), eq(TEST_USER_HANDLE), eq(KEYCHAIN_ALIAS)))
+                .thenReturn(KEYCHAIN_KEY_GRANT);
+        config.enterpriseConfig = mWifiEnterpriseConfig;
+
+        assertTrue(mWifiKeyStore.updateNetworkKeys(config, null));
+        verify(mWifiEnterpriseConfig).setClientCertificateAlias(eq(KEYCHAIN_KEY_GRANT));
+    }
+
+    @Test
+    public void testValidateKeyChainAliasNotGranted() {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        when(mFrameworkFacade.hasWifiKeyGrantAsUser(
+                any(Context.class), any(UserHandle.class), any(String.class))).thenReturn(false);
+
+        assertFalse(mWifiKeyStore.validateKeyChainAlias(KEYCHAIN_ALIAS, TEST_UID));
+    }
+
+    @Test
+    public void testValidateKeyChainAliasEmpty() {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        when(mFrameworkFacade.hasWifiKeyGrantAsUser(
+                any(Context.class), any(UserHandle.class), any(String.class))).thenReturn(true);
+
+        assertFalse(mWifiKeyStore.validateKeyChainAlias("", TEST_UID));
+    }
+
+    @Test
+    public void testValidateKeyChainAliasGranted() {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        when(mFrameworkFacade.hasWifiKeyGrantAsUser(
+                any(Context.class), eq(TEST_USER_HANDLE), eq(KEYCHAIN_ALIAS))).thenReturn(true);
+
+        assertTrue(mWifiKeyStore.validateKeyChainAlias(KEYCHAIN_ALIAS, TEST_UID));
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiLastResortWatchdogTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiLastResortWatchdogTest.java
index b463f90..ec699ab 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiLastResortWatchdogTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiLastResortWatchdogTest.java
@@ -35,6 +35,8 @@
 
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 
 import java.util.ArrayList;
@@ -49,11 +51,16 @@
     @Mock WifiInjector mWifiInjector;
     @Mock WifiMetrics mWifiMetrics;
     @Mock SelfRecovery mSelfRecovery;
-    @Mock ClientModeImpl mClientModeImpl;
+    @Mock WifiDiagnostics mWifiDiagnostics;
     @Mock Clock mClock;
     @Mock WifiInfo mWifiInfo;
     @Mock Context mContext;
     @Mock DeviceConfigFacade mDeviceConfigFacade;
+    @Mock WifiMonitor mWifiMonitor;
+    @Mock ActiveModeWarden mActiveModeWarden;
+    @Mock ClientModeManager mClientModeManager;
+
+    @Captor ArgumentCaptor<Handler> mHandlerCaptor;
 
     private WifiLastResortWatchdog mLastResortWatchdog;
     private MockResources mResources;
@@ -61,20 +68,27 @@
     private String[] mBssids = {"aa:bb:cc:dd:ee:ff", "00:11:22:33:44:55", "a0:b0:c0:d0:e0:f0",
             "01:23:45:67:89:ab"};
     private int[] mFrequencies = {2437, 5180, 5180, 2437};
-    private String[] mCaps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]",
-            "[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+    private String[] mCaps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]",
+            "[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
     private int[] mLevels = {-60, -86, -50, -62};
     private boolean[] mIsEphemeral = {false, false, false, false};
     private boolean[] mHasEverConnected = {false, false, false, false};
     private TestLooper mLooper;
     private static final String TEST_NETWORK_SSID = "\"test_ssid\"";
     private static final int DEFAULT_ABNORMAL_CONNECTION_DURATION_MS = 30000;
+    private static final int TEST_NETWORK_ID = 47;
+    private static final int TEST_NETWORK_ID_2 = 54;
+    private static final WifiSsid TEST_WIFI_SSID =
+            WifiSsid.createFromByteArray(new byte[]{'a', 'b', 'c'});
 
     @Before
     public void setUp() throws Exception {
         initMocks(this);
         mLooper = new TestLooper();
         when(mWifiInjector.getSelfRecovery()).thenReturn(mSelfRecovery);
+        when(mWifiInjector.getActiveModeWarden()).thenReturn(mActiveModeWarden);
+        when(mActiveModeWarden.getPrimaryClientModeManager()).thenReturn(mClientModeManager);
+        when(mClientModeManager.syncRequestConnectionInfo()).thenReturn(mWifiInfo);
         when(mDeviceConfigFacade.isAbnormalConnectionBugreportEnabled()).thenReturn(true);
         when(mDeviceConfigFacade.getAbnormalConnectionDurationMs()).thenReturn(
                         DEFAULT_ABNORMAL_CONNECTION_DURATION_MS);
@@ -82,15 +96,18 @@
         mResources.setBoolean(R.bool.config_wifi_watchdog_enabled, true);
         when(mContext.getResources()).thenReturn(mResources);
         createWifiLastResortWatchdog();
-        when(mClientModeImpl.getWifiInfo()).thenReturn(mWifiInfo);
         when(mWifiInfo.getSSID()).thenReturn(TEST_NETWORK_SSID);
+
+        mLastResortWatchdog.registerForWifiMonitorEvents("wlan0");
+        verify(mWifiMonitor, atLeastOnce())
+                .registerHandler(eq("wlan0"), anyInt(), mHandlerCaptor.capture());
     }
 
     private void createWifiLastResortWatchdog() {
         WifiThreadRunner wifiThreadRunner = new WifiThreadRunner(new Handler(mLooper.getLooper()));
         mLastResortWatchdog = new WifiLastResortWatchdog(mWifiInjector, mContext, mClock,
-                mWifiMetrics, mClientModeImpl, mLooper.getLooper(), mDeviceConfigFacade,
-                wifiThreadRunner);
+                mWifiMetrics, mWifiDiagnostics, mLooper.getLooper(), mDeviceConfigFacade,
+                wifiThreadRunner, mWifiMonitor);
         mLastResortWatchdog.setBugReportProbability(1);
     }
 
@@ -317,21 +334,21 @@
         int net = 0;
         for (int i = 0; i < associationRejections; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
             assertEquals(i + 1, mLastResortWatchdog.getRecentAvailableNetworks()
                     .get(mBssids[net]).associationRejection);
         }
         net = 1;
         for (int i = 0; i < authenticationFailures; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION, false);
             assertEquals(i + 1, mLastResortWatchdog.getRecentAvailableNetworks()
                     .get(mBssids[net]).authenticationFailure);
         }
         net = 2;
         for (int i = 0; i < dhcpFailures; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_DHCP);
+                    WifiLastResortWatchdog.FAILURE_CODE_DHCP, false);
             assertEquals(i + 1, mLastResortWatchdog.getRecentAvailableNetworks()
                     .get(mBssids[net]).dhcpFailure);
         }
@@ -369,21 +386,21 @@
         int net = 0;
         for (int i = 0; i < associationRejections; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
             assertEquals(mLastResortWatchdog.getRecentAvailableNetworks()
                     .get(mBssids[net]).associationRejection, i + 1);
         }
         net = 1;
         for (int i = 0; i < authenticationFailures; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION, false);
             assertEquals(mLastResortWatchdog.getRecentAvailableNetworks()
                     .get(mBssids[net]).authenticationFailure, i + 1);
         }
         net = 2;
         for (int i = 0; i < dhcpFailures; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_DHCP);
+                    WifiLastResortWatchdog.FAILURE_CODE_DHCP, false);
             assertEquals(mLastResortWatchdog.getRecentAvailableNetworks()
                     .get(mBssids[net]).dhcpFailure, i + 1);
         }
@@ -419,17 +436,17 @@
         int net = 0;
         for (int i = 0; i < associationRejections; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(badSsid, mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
         }
         net = 1;
         for (int i = 0; i < authenticationFailures; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(badSsid, mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION, false);
         }
         net = 2;
         for (int i = 0; i < dhcpFailures; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(badSsid, mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_DHCP);
+                    WifiLastResortWatchdog.FAILURE_CODE_DHCP, false);
         }
 
         // Ensure all networks still have zero failure count
@@ -463,17 +480,17 @@
         int net = 0;
         for (int i = 0; i < associationRejections; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], badBssid,
-                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
         }
         net = 1;
         for (int i = 0; i < authenticationFailures; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], badBssid,
-                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION, false);
         }
         net = 2;
         for (int i = 0; i < dhcpFailures; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], badBssid,
-                    WifiLastResortWatchdog.FAILURE_CODE_DHCP);
+                    WifiLastResortWatchdog.FAILURE_CODE_DHCP, false);
         }
 
         // Ensure all networks still have zero failure count
@@ -509,19 +526,19 @@
         for (int i = 0; i < associationRejections; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
                     ssids[0], WifiLastResortWatchdog.BSSID_ANY,
-                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
         }
         net = 1;
         for (int i = 0; i < authenticationFailures; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
                     ssids[0], WifiLastResortWatchdog.BSSID_ANY,
-                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION, false);
         }
         net = 2;
         for (int i = 0; i < dhcpFailures; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
                     ssids[0], WifiLastResortWatchdog.BSSID_ANY,
-                    WifiLastResortWatchdog.FAILURE_CODE_DHCP);
+                    WifiLastResortWatchdog.FAILURE_CODE_DHCP, false);
         }
         assertFailureCountEquals(mBssids[0], associationRejections, authenticationFailures,
                 dhcpFailures);
@@ -558,19 +575,19 @@
         for (int i = 0; i < associationRejections; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
                     badSsid, WifiLastResortWatchdog.BSSID_ANY,
-                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
         }
         net = 1;
         for (int i = 0; i < authenticationFailures; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
                     badSsid, WifiLastResortWatchdog.BSSID_ANY,
-                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION, false);
         }
         net = 2;
         for (int i = 0; i < dhcpFailures; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
                     badSsid, WifiLastResortWatchdog.BSSID_ANY,
-                    WifiLastResortWatchdog.FAILURE_CODE_DHCP);
+                    WifiLastResortWatchdog.FAILURE_CODE_DHCP, false);
         }
         // Check that all network failure counts are still zero
         for (int i = 0; i < mSsids.length; i++) {
@@ -603,17 +620,17 @@
         int net = 0;
         for (int i = 0; i < associationRejections; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
         }
         net = 1;
         for (int i = 0; i < authenticationFailures; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION, false);
         }
         net = 2;
         for (int i = 0; i < dhcpFailures; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_DHCP);
+                    WifiLastResortWatchdog.FAILURE_CODE_DHCP, false);
         }
         assertEquals(true, mLastResortWatchdog.isOverFailureThreshold(mBssids[0]));
         assertEquals(true, mLastResortWatchdog.isOverFailureThreshold(mBssids[1]));
@@ -646,17 +663,17 @@
         int net = 0;
         for (int i = 0; i < associationRejections; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
         }
         net = 1;
         for (int i = 0; i < authenticationFailures; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION, false);
         }
         net = 2;
         for (int i = 0; i < dhcpFailures; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_DHCP);
+                    WifiLastResortWatchdog.FAILURE_CODE_DHCP, false);
         }
         assertEquals(false, mLastResortWatchdog.isOverFailureThreshold(mBssids[0]));
         assertEquals(false, mLastResortWatchdog.isOverFailureThreshold(mBssids[1]));
@@ -691,21 +708,21 @@
         int net = 0;
         for (int i = 0; i < associationRejections; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
             assertEquals(i + 1, mLastResortWatchdog.getRecentAvailableNetworks()
                     .get(mBssids[net]).associationRejection);
         }
         net = 1;
         for (int i = 0; i < authenticationFailures; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION, false);
             assertEquals(i + 1, mLastResortWatchdog.getRecentAvailableNetworks()
                     .get(mBssids[net]).authenticationFailure);
         }
         net = 2;
         for (int i = 0; i < dhcpFailures; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_DHCP);
+                    WifiLastResortWatchdog.FAILURE_CODE_DHCP, false);
             assertEquals(i + 1, mLastResortWatchdog.getRecentAvailableNetworks()
                     .get(mBssids[net]).dhcpFailure);
         }
@@ -755,21 +772,21 @@
         int net = 0;
         for (int i = 0; i < associationRejections; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
             assertEquals(i + 1, mLastResortWatchdog.getRecentAvailableNetworks()
                     .get(mBssids[net]).associationRejection);
         }
         net = 1;
         for (int i = 0; i < authenticationFailures; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION, false);
             assertEquals(i + 1, mLastResortWatchdog.getRecentAvailableNetworks()
                     .get(mBssids[net]).authenticationFailure);
         }
         net = 2;
         for (int i = 0; i < dhcpFailures; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_DHCP);
+                    WifiLastResortWatchdog.FAILURE_CODE_DHCP, false);
             assertEquals(i + 1, mLastResortWatchdog.getRecentAvailableNetworks()
                     .get(mBssids[net]).dhcpFailure);
         }
@@ -828,17 +845,17 @@
         int net = 0;
         for (int i = 0; i < associationRejections; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(null, mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
         }
         net = 1;
         for (int i = 0; i < authenticationFailures; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], null,
-                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION, false);
         }
         net = 2;
         for (int i = 0; i < dhcpFailures; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(null, null,
-                    WifiLastResortWatchdog.FAILURE_CODE_DHCP);
+                    WifiLastResortWatchdog.FAILURE_CODE_DHCP, false);
         }
 
         // Ensure new networks have zero'ed failure counts
@@ -865,9 +882,10 @@
                 "ff:ee:dd:cc:bb:aa", "66:77:88:99:aa:bb", "cc:dd:ee:ff:00:01", "02:03:04:05:06:07",
                 "08:09:aa:bb:cc:dd"};
         int[] frequencies = {2437, 5180, 5180, 2437, 2437, 5180, 5180, 2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]",
-                "[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]",
-                "[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]",
+                "[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]",
+                "[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]",
+                "[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {-60, -86, -50, -62, -60, -86, -50, -62};
         boolean[] isEphemeral = {false, false, false, false, false, false, false, false};
         boolean[] hasEverConnected = {false, false, false, false, false, false, false,
@@ -887,23 +905,23 @@
         //Increment failure count for the first test network ssid & bssid
         for (int i = 0; i < firstNetFails; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                    ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION, false);
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_DHCP);
+                    ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_DHCP, false);
         }
         //Increment failure count for the first test network ssid & BSSID_ANY
         for (int i = 0; i < secondNetFails; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
                     ssids[1], WifiLastResortWatchdog.BSSID_ANY,
-                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
                     ssids[1], WifiLastResortWatchdog.BSSID_ANY,
-                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION, false);
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
                     ssids[1], WifiLastResortWatchdog.BSSID_ANY,
-                    WifiLastResortWatchdog.FAILURE_CODE_DHCP);
+                    WifiLastResortWatchdog.FAILURE_CODE_DHCP, false);
         }
         assertFailureCountEquals(bssids[0], firstNetFails, firstNetFails, firstNetFails);
         assertFailureCountEquals(bssids[1], secondNetFails, secondNetFails, secondNetFails);
@@ -945,21 +963,21 @@
         int net = 0;
         for (int i = 0; i < associationRejections; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
             assertEquals(i + 1, mLastResortWatchdog.getRecentAvailableNetworks()
                     .get(mBssids[net]).associationRejection);
         }
         net = 1;
         for (int i = 0; i < authenticationFailures; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION, false);
             assertEquals(i + 1, mLastResortWatchdog.getRecentAvailableNetworks()
                     .get(mBssids[net]).authenticationFailure);
         }
         net = 2;
         for (int i = 0; i < dhcpFailures; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_DHCP);
+                    WifiLastResortWatchdog.FAILURE_CODE_DHCP, false);
             assertEquals(i + 1, mLastResortWatchdog.getRecentAvailableNetworks()
                     .get(mBssids[net]).dhcpFailure);
         }
@@ -989,17 +1007,17 @@
         int net = 0;
         for (int i = 0; i < associationRejections; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
         }
         net = 1;
         for (int i = 0; i < authenticationFailures; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION, false);
         }
         net = 2;
         for (int i = 0; i < dhcpFailures; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_DHCP);
+                    WifiLastResortWatchdog.FAILURE_CODE_DHCP, false);
         }
 
         // Check that we have Failures
@@ -1041,19 +1059,20 @@
         int net = 0;
         for (int i = 0; i < associationRejections; i++) {
             watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
             assertEquals(false, watchdogTriggered);
         }
         net = 1;
         for (int i = 0; i < authenticationFailures; i++) {
             watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION,
+                    false);
             assertEquals(false, watchdogTriggered);
         }
         net = 2;
         for (int i = 0; i < dhcpFailures; i++) {
             watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_DHCP);
+                mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_DHCP, false);
             assertEquals(false, watchdogTriggered);
         }
 
@@ -1065,7 +1084,7 @@
         // Add one more failure to one of the already over threshold networks, assert that it
         // does not trigger
         watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    mSsids[0], mBssids[0], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    mSsids[0], mBssids[0], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
         assertEquals(false, watchdogTriggered);
     }
 
@@ -1095,19 +1114,21 @@
         int net = 0;
         for (int i = 0; i < associationRejections; i++) {
             watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION,
+                    false);
             assertEquals(false, watchdogTriggered);
         }
         net = 1;
         for (int i = 0; i < authenticationFailures; i++) {
             watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION,
+                    false);
             assertEquals(false, watchdogTriggered);
         }
         net = 2;
         for (int i = 0; i < dhcpFailures; i++) {
             watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_DHCP);
+                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_DHCP, false);
             assertEquals(false, watchdogTriggered);
         }
 
@@ -1115,19 +1136,22 @@
         net = 3;
         for (int i = 0; i < WifiLastResortWatchdog.FAILURE_THRESHOLD - 1; i++) {
             watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION,
+                    false);
             assertEquals(false, watchdogTriggered);
         }
         // Increment failure count once more, check that watchdog triggered this time
         watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION,
+                false);
         mLastResortWatchdog.updateAvailableNetworks(candidates);
         assertEquals(true, watchdogTriggered);
 
         // Increment failure count 5 more times, watchdog should not trigger
         for (int i = 0; i < 5; i++) {
             watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                        mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                        mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION,
+                    false);
             assertEquals(false, watchdogTriggered);
         }
     }
@@ -1158,19 +1182,21 @@
         int net = 0;
         for (int i = 0; i < associationRejections; i++) {
             watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION,
+                    false);
             assertEquals(false, watchdogTriggered);
         }
         net = 1;
         for (int i = 0; i < authenticationFailures; i++) {
             watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION,
+                    false);
             assertEquals(false, watchdogTriggered);
         }
         net = 2;
         for (int i = 0; i < dhcpFailures; i++) {
             watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_DHCP);
+                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_DHCP, false);
             assertEquals(false, watchdogTriggered);
         }
 
@@ -1178,19 +1204,22 @@
         net = 3;
         for (int i = 0; i < WifiLastResortWatchdog.FAILURE_THRESHOLD - 1; i++) {
             watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION,
+                    false);
             assertEquals(false, watchdogTriggered);
         }
         // Increment failure count once more, check that watchdog triggered this time
         watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION,
+                false);
         mLastResortWatchdog.updateAvailableNetworks(candidates);
         assertEquals(true, watchdogTriggered);
 
         // Increment failure count 5 more times, watchdog should not trigger
         for (int i = 0; i < 5; i++) {
             watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                        mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                        mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION,
+                    false);
             assertEquals(false, watchdogTriggered);
         }
     }
@@ -1219,25 +1248,28 @@
         int net = 0;
         for (int i = 0; i < associationRejections; i++) {
             watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION,
+                    false);
             assertEquals(false, watchdogTriggered);
         }
         net = 1;
         for (int i = 0; i < authenticationFailures; i++) {
             watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION,
+                    false);
             assertEquals(false, watchdogTriggered);
         }
         net = 2;
         for (int i = 0; i < dhcpFailures; i++) {
             watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_DHCP);
+                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_DHCP, false);
             assertEquals(false, watchdogTriggered);
         }
         net = 3;
         for (int i = 0; i < WifiLastResortWatchdog.FAILURE_THRESHOLD + 1; i++) {
             watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION,
+                    false);
             assertEquals(false, watchdogTriggered);
         }
     }
@@ -1269,25 +1301,28 @@
         int net = 0;
         for (int i = 0; i < associationRejections; i++) {
             watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION,
+                    false);
             assertEquals(false, watchdogTriggered);
         }
         net = 1;
         for (int i = 0; i < authenticationFailures; i++) {
             watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION,
+                    false);
             assertEquals(false, watchdogTriggered);
         }
         net = 2;
         for (int i = 0; i < dhcpFailures; i++) {
             watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_DHCP);
+                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_DHCP, false);
             assertEquals(false, watchdogTriggered);
         }
         net = 3;
         for (int i = 0; i < WifiLastResortWatchdog.FAILURE_THRESHOLD + 1; i++) {
             watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    mSsids[net], mBssids[net], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION,
+                    false);
             assertEquals(false, watchdogTriggered);
         }
     }
@@ -1299,21 +1334,22 @@
             for (int j = 0; j < ssids.length - 1; j++) {
                 watchdogTriggered = mLastResortWatchdog
                         .noteConnectionFailureAndTriggerIfNeeded(ssids[j], bssids[j],
-                        WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                        WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
                 assertEquals(false, watchdogTriggered);
             }
         }
         for (int i = 0; i < WifiLastResortWatchdog.FAILURE_THRESHOLD - 1; i++) {
             watchdogTriggered = mLastResortWatchdog
                     .noteConnectionFailureAndTriggerIfNeeded(ssids[ssids.length - 1],
-                    bssids[ssids.length - 1], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    bssids[ssids.length - 1], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION,
+                            false);
             assertEquals(false, watchdogTriggered);
         }
 
         // Increment failure count once more, check that watchdog triggered this time
         watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
                     ssids[ssids.length - 1], bssids[ssids.length - 1],
-                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
         assertEquals(true, watchdogTriggered);
         verify(mSelfRecovery).trigger(eq(SelfRecovery.REASON_LAST_RESORT_WATCHDOG));
         reset(mSelfRecovery);
@@ -1342,7 +1378,8 @@
         // Increment failure count 5 more times, watchdog should not trigger
         for (int i = 0; i < 5; i++) {
             boolean watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                        mSsids[3], mBssids[3], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                        mSsids[3], mBssids[3], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION,
+                    false);
             assertEquals(false, watchdogTriggered);
         }
     }
@@ -1371,7 +1408,8 @@
         // Increment failure count 5 more times, ensure trigger is deactivated
         for (int i = 0; i < 5; i++) {
             watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                        mSsids[3], mBssids[3], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                        mSsids[3], mBssids[3], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION,
+                    false);
             mLastResortWatchdog.updateAvailableNetworks(candidates);
             assertEquals(false, watchdogTriggered);
         }
@@ -1383,25 +1421,26 @@
         // Fail 3/4 networks until they're over threshold
         for (int i = 0; i < WifiLastResortWatchdog.FAILURE_THRESHOLD + 1; i++) {
             watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    mSsids[0], mBssids[0], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    mSsids[0], mBssids[0], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
             assertEquals(false, watchdogTriggered);
             watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    mSsids[1], mBssids[1], WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                    mSsids[1], mBssids[1], WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION,
+                    false);
             assertEquals(false, watchdogTriggered);
             watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    mSsids[2], mBssids[2], WifiLastResortWatchdog.FAILURE_CODE_DHCP);
+                    mSsids[2], mBssids[2], WifiLastResortWatchdog.FAILURE_CODE_DHCP, false);
             assertEquals(false, watchdogTriggered);
         }
 
         // Bring the remaining unfailed network upto 1 less than the failure threshold
         for (int i = 0; i < WifiLastResortWatchdog.FAILURE_THRESHOLD - 1; i++) {
             watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    mSsids[3], mBssids[3], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    mSsids[3], mBssids[3], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
             assertEquals(false, watchdogTriggered);
         }
         // Increment failure count once more, check that watchdog triggered this time
         watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    mSsids[3], mBssids[3], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    mSsids[3], mBssids[3], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
         assertEquals(true, watchdogTriggered);
     }
 
@@ -1440,7 +1479,8 @@
         // Increment failure count 5 more times, ensure trigger is deactivated
         for (int i = 0; i < 5; i++) {
             watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                        mSsids[2], mBssids[2], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                        mSsids[2], mBssids[2], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION,
+                    false);
             mLastResortWatchdog.updateAvailableNetworks(candidates);
             assertEquals(false, watchdogTriggered);
         }
@@ -1476,8 +1516,9 @@
         String[] bssids = {"aa:bb:cc:dd:ee:ff", "00:11:22:33:44:55", "a0:b0:c0:d0:e0:f0",
                 "01:23:45:67:89:ab", "00:01:02:03:04:05"};
         int[] frequencies = {2437, 5180, 5180, 2437, 2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]",
-                "[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]",
+                "[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]",
+                "[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {-60, -86, -50, -62, -60};
         boolean[] isEphemeral = {false, false, false, false, false};
         boolean[] hasEverConnected = {true, false, false, false, false};
@@ -1499,17 +1540,17 @@
         //Increment failure counts
         for (int i = 0; i < WifiLastResortWatchdog.FAILURE_THRESHOLD; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    ssids[1], bssids[1], WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                    ssids[1], bssids[1], WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION, false);
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    ssids[2], bssids[2], WifiLastResortWatchdog.FAILURE_CODE_DHCP);
+                    ssids[2], bssids[2], WifiLastResortWatchdog.FAILURE_CODE_DHCP, false);
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    ssids[3], bssids[3], WifiLastResortWatchdog.FAILURE_CODE_DHCP);
+                    ssids[3], bssids[3], WifiLastResortWatchdog.FAILURE_CODE_DHCP, false);
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    ssids[4], bssids[4], WifiLastResortWatchdog.FAILURE_CODE_DHCP);
+                    ssids[4], bssids[4], WifiLastResortWatchdog.FAILURE_CODE_DHCP, false);
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    ssids[4], bssids[4], WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                    ssids[4], bssids[4], WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION, false);
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
         }
 
         // Verify relevant WifiMetrics calls were made once with appropriate arguments
@@ -1538,7 +1579,7 @@
 
         // Verify takeBugReport is called
         mLooper.dispatchAll();
-        verify(mClientModeImpl, times(1)).takeBugReport(anyString(), anyString());
+        verify(mWifiDiagnostics, times(1)).takeBugReport(anyString(), anyString());
 
         // Simulate wifi disconnecting
         mLastResortWatchdog.connectedStateTransition(false);
@@ -1548,7 +1589,7 @@
         verify(mWifiMetrics, times(1)).setWatchdogSuccessTimeDurationMs(eq(expectedDuration));
         // Verify takeBugReport not called again
         mLooper.dispatchAll();
-        verify(mClientModeImpl, times(1)).takeBugReport(anyString(), anyString());
+        verify(mWifiDiagnostics, times(1)).takeBugReport(anyString(), anyString());
 
         // Remove the fifth network from candidates
         candidates = createFilteredQnsCandidates(Arrays.copyOfRange(mSsids, 0, 4),
@@ -1571,13 +1612,13 @@
         //Increment failure counts
         for (int i = 0; i < WifiLastResortWatchdog.FAILURE_THRESHOLD; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    ssids[1], bssids[1], WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                    ssids[1], bssids[1], WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION, false);
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    ssids[2], bssids[2], WifiLastResortWatchdog.FAILURE_CODE_DHCP);
+                    ssids[2], bssids[2], WifiLastResortWatchdog.FAILURE_CODE_DHCP, false);
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    ssids[3], bssids[3], WifiLastResortWatchdog.FAILURE_CODE_DHCP);
+                    ssids[3], bssids[3], WifiLastResortWatchdog.FAILURE_CODE_DHCP, false);
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
         }
 
         // increment the timer to enable Watchdog
@@ -1632,7 +1673,8 @@
         boolean watchdogTriggered = false;
         for (int i = 0; i < WifiLastResortWatchdog.FAILURE_THRESHOLD; i++) {
             watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    mSsids[0], mBssids[0], WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                    mSsids[0], mBssids[0], WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION,
+                    false);
         }
         assertEquals(true, watchdogTriggered);
     }
@@ -1664,7 +1706,8 @@
         boolean watchdogTriggered = false;
         for (int i = 0; i < WifiLastResortWatchdog.FAILURE_THRESHOLD; i++) {
             watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    mSsids[0], mBssids[0], WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                    mSsids[0], mBssids[0], WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION,
+                    false);
         }
         assertEquals(false, watchdogTriggered);
 
@@ -1681,7 +1724,7 @@
         mLastResortWatchdog.updateAvailableNetworks(candidates);
 
         watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                mSsids[0], mBssids[0], WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                mSsids[0], mBssids[0], WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION, false);
         assertEquals(true, watchdogTriggered);
     }
 
@@ -1712,7 +1755,8 @@
         boolean watchdogTriggered = false;
         for (int i = 0; i < WifiLastResortWatchdog.FAILURE_THRESHOLD; i++) {
             watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    mSsids[0], mBssids[0], WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                    mSsids[0], mBssids[0], WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION,
+                    false);
         }
         assertEquals(true, watchdogTriggered);
 
@@ -1730,7 +1774,8 @@
 
         for (int i = 0; i < WifiLastResortWatchdog.FAILURE_THRESHOLD; i++) {
             watchdogTriggered = mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    mSsids[0], mBssids[0], WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                    mSsids[0], mBssids[0], WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION,
+                    false);
         }
         assertEquals(false, watchdogTriggered);
     }
@@ -1784,7 +1829,7 @@
         String[] ssids = {"\"test1\""};
         String[] bssids = {"04:03:02:01:00:0f"};
         int[] frequencies = {2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {-60};
         boolean[] isEphemeral = {false};
         boolean[] hasEverConnected = {true};
@@ -1800,7 +1845,7 @@
         //Increment failure counts
         for (int i = 0; i < WifiLastResortWatchdog.FAILURE_THRESHOLD; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
         }
 
         // Verify relevant WifiMetrics calls were made once with appropriate arguments
@@ -1811,7 +1856,7 @@
 
         // Fail 1 more time and verify this time it's counted
         mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
         verify(mWifiMetrics, times(1)).incrementWatchdogTotalConnectionFailureCountAfterTrigger();
     }
 
@@ -1829,7 +1874,7 @@
         String[] ssids = {"\"test1\""};
         String[] bssids = {"04:03:02:01:00:0f"};
         int[] frequencies = {2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {-60};
         boolean[] isEphemeral = {false};
         boolean[] hasEverConnected = {true};
@@ -1845,7 +1890,7 @@
         //Increment failure counts
         for (int i = 0; i < WifiLastResortWatchdog.FAILURE_THRESHOLD; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
         }
 
         // Verify watchdog has triggered a restart
@@ -1853,13 +1898,13 @@
 
         // Fail 1 more time and verify this time it's counted
         mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
 
         // Simulate wifi connecting after triggering
         mLastResortWatchdog.connectedStateTransition(true);
         // Verify takeBugReport is not called again
         mLooper.dispatchAll();
-        verify(mClientModeImpl, never()).takeBugReport(anyString(), anyString());
+        verify(mWifiDiagnostics, never()).takeBugReport(anyString(), anyString());
         verify(mWifiMetrics, never()).incrementNumLastResortWatchdogSuccesses();
 
         // Simulate wifi disconnecting
@@ -1875,7 +1920,7 @@
         }
         for (int i = 0; i < WifiLastResortWatchdog.FAILURE_THRESHOLD; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
         }
 
         // Verify watchdog has triggered a restart
@@ -1884,7 +1929,7 @@
         mLastResortWatchdog.connectedStateTransition(true);
         // Verify takeBugReport is not called again
         mLooper.dispatchAll();
-        verify(mClientModeImpl, times(1)).takeBugReport(anyString(), anyString());
+        verify(mWifiDiagnostics, times(1)).takeBugReport(anyString(), anyString());
         verify(mWifiMetrics, times(1)).incrementNumLastResortWatchdogSuccesses();
     }
 
@@ -1902,7 +1947,7 @@
         String[] ssids = {"\"test1\""};
         String[] bssids = {"04:03:02:01:00:0f"};
         int[] frequencies = {2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {-60};
         boolean[] isEphemeral = {false};
         boolean[] hasEverConnected = {true};
@@ -1918,7 +1963,7 @@
         //Increment failure counts
         for (int i = 0; i < WifiLastResortWatchdog.FAILURE_THRESHOLD; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
         }
 
         // Verify watchdog has triggered a restart
@@ -1931,7 +1976,7 @@
         mLastResortWatchdog.connectedStateTransition(true);
         // Verify takeBugReport is not called again
         mLooper.dispatchAll();
-        verify(mClientModeImpl, never()).takeBugReport(anyString(), anyString());
+        verify(mWifiDiagnostics, never()).takeBugReport(anyString(), anyString());
         verify(mWifiMetrics, never()).incrementNumLastResortWatchdogSuccesses();
     }
 
@@ -1960,29 +2005,29 @@
         int net = 0;
         for (int i = 0; i < associationRejections; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
         }
         for (int i = 0; i < authenticationFailures; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION, false);
         }
         net = 1;
         for (int i = 0; i < associationRejections; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
         }
         for (int i = 0; i < dhcpFailures; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_DHCP);
+                    WifiLastResortWatchdog.FAILURE_CODE_DHCP, false);
         }
         net = 2;
         for (int i = 0; i < authenticationFailures; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION, false);
         }
         for (int i = 0; i < dhcpFailures; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(mSsids[net], mBssids[net],
-                    WifiLastResortWatchdog.FAILURE_CODE_DHCP);
+                    WifiLastResortWatchdog.FAILURE_CODE_DHCP, false);
         }
 
         assertEquals(true, mLastResortWatchdog.isOverFailureThreshold(mBssids[0]));
@@ -2006,7 +2051,7 @@
         String[] ssids = {"\"test1\""};
         String[] bssids = {"04:03:02:01:00:0f"};
         int[] frequencies = {2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {-60};
         boolean[] isEphemeral = {false};
         boolean[] hasEverConnected = {true};
@@ -2025,11 +2070,11 @@
         //Increment failure counts
         for (int i = 0; i < authenticationFailures; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                    ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION, false);
         }
         for (int i = 0; i < dhcpFailures; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_DHCP);
+                    ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_DHCP, false);
         }
 
         // Verify relevant WifiMetrics calls were made once with appropriate arguments
@@ -2058,7 +2103,7 @@
         String[] ssids = {"\"test1\""};
         String[] bssids = {"04:03:02:01:00:0f"};
         int[] frequencies = {2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {-60};
         boolean[] isEphemeral = {false};
         boolean[] hasEverConnected = {true};
@@ -2101,7 +2146,7 @@
         // Increment failure counts
         for (int i = 0; i < WifiLastResortWatchdog.FAILURE_THRESHOLD; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
         }
 
         // Watchdog should not be triggerred since time based logic.
@@ -2121,7 +2166,7 @@
         String[] ssids = {"\"test1\""};
         String[] bssids = {"04:03:02:01:00:0f"};
         int[] frequencies = {2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {-60};
         boolean[] isEphemeral = {false};
         boolean[] hasEverConnected = {true};
@@ -2137,7 +2182,7 @@
         //Increment failure counts
         for (int i = 0; i < WifiLastResortWatchdog.FAILURE_THRESHOLD; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
         }
 
         // Verify watchdog has triggered a restart
@@ -2147,7 +2192,7 @@
         mLastResortWatchdog.connectedStateTransition(true);
         // Verify takeBugReport is not called because connected on different SSID
         mLooper.dispatchAll();
-        verify(mClientModeImpl, never()).takeBugReport(anyString(), anyString());
+        verify(mWifiDiagnostics, never()).takeBugReport(anyString(), anyString());
         verify(mWifiMetrics, never()).incrementNumLastResortWatchdogSuccesses();
 
         // Simulate wifi disconnecting
@@ -2163,7 +2208,7 @@
         }
         for (int i = 0; i < WifiLastResortWatchdog.FAILURE_THRESHOLD; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
         }
 
         // Verify watchdog has triggered a restart
@@ -2172,7 +2217,7 @@
         mLastResortWatchdog.connectedStateTransition(true);
         // Verify takeBugReport is called because connected back on same SSID
         mLooper.dispatchAll();
-        verify(mClientModeImpl, times(1)).takeBugReport(anyString(), anyString());
+        verify(mWifiDiagnostics, times(1)).takeBugReport(anyString(), anyString());
         verify(mWifiMetrics, times(1)).incrementNumLastResortWatchdogSuccesses();
     }
 
@@ -2186,34 +2231,65 @@
         // first verifies that bugreports are not taken when connection takes less than
         // DEFAULT_ABNORMAL_CONNECTION_DURATION_MS
         when(mClock.getElapsedSinceBootMillis()).thenReturn(1L);
-        mLastResortWatchdog.noteStartConnectTime();
+        mLastResortWatchdog.noteStartConnectTime(TEST_NETWORK_ID);
         mLooper.dispatchAll();
         when(mClock.getElapsedSinceBootMillis()).thenReturn(
                 (long) DEFAULT_ABNORMAL_CONNECTION_DURATION_MS);
-        Handler handler = mLastResortWatchdog.getHandler();
-        handler.sendMessage(
-                handler.obtainMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, null));
+        Handler handler = mHandlerCaptor.getValue();
+        handler.sendMessage(handler.obtainMessage(
+                WifiMonitor.NETWORK_CONNECTION_EVENT,
+                new NetworkConnectionEventInfo(TEST_NETWORK_ID, TEST_WIFI_SSID, null, false)));
         mLooper.dispatchAll();
-        verify(mClientModeImpl, never()).takeBugReport(anyString(), anyString());
+        verify(mWifiDiagnostics, never()).takeBugReport(anyString(), anyString());
 
         // Now verify that bugreport is taken
-        mLastResortWatchdog.noteStartConnectTime();
+        mLastResortWatchdog.noteStartConnectTime(TEST_NETWORK_ID);
         mLooper.dispatchAll();
         when(mClock.getElapsedSinceBootMillis()).thenReturn(
                 2L * DEFAULT_ABNORMAL_CONNECTION_DURATION_MS + 1);
-        handler.sendMessage(
-                handler.obtainMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, null));
+        handler.sendMessage(handler.obtainMessage(
+                WifiMonitor.NETWORK_CONNECTION_EVENT,
+                new NetworkConnectionEventInfo(TEST_NETWORK_ID, TEST_WIFI_SSID, null, false)));
         mLooper.dispatchAll();
-        verify(mClientModeImpl).takeBugReport(anyString(), anyString());
+        verify(mWifiDiagnostics).takeBugReport(anyString(), anyString());
 
         // Verify additional connections (without more TYPE_CMD_START_CONNECT) don't trigger more
         // bugreports.
         when(mClock.getElapsedSinceBootMillis()).thenReturn(
                 4L * DEFAULT_ABNORMAL_CONNECTION_DURATION_MS);
-        handler.sendMessage(
-                handler.obtainMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, null));
+        handler.sendMessage(handler.obtainMessage(
+                WifiMonitor.NETWORK_CONNECTION_EVENT,
+                new NetworkConnectionEventInfo(TEST_NETWORK_ID, TEST_WIFI_SSID, null, false)));
         mLooper.dispatchAll();
-        verify(mClientModeImpl).takeBugReport(anyString(), anyString());
+        verify(mWifiDiagnostics).takeBugReport(anyString(), anyString());
+    }
+
+    /** Verifies abnormal connection time works on a per-network basis. */
+    @Test
+    public void testAbnormalConnectionTime_perNetwork() throws Exception {
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(
+                (long) DEFAULT_ABNORMAL_CONNECTION_DURATION_MS);
+        Handler handler = mHandlerCaptor.getValue();
+
+        // start connection for network1
+        mLastResortWatchdog.noteStartConnectTime(TEST_NETWORK_ID);
+        mLooper.dispatchAll();
+
+        // network2 connected after a long time, shouldn't trigger BR
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(
+                2L * DEFAULT_ABNORMAL_CONNECTION_DURATION_MS + 1);
+        handler.sendMessage(handler.obtainMessage(
+                WifiMonitor.NETWORK_CONNECTION_EVENT,
+                new NetworkConnectionEventInfo(TEST_NETWORK_ID_2, TEST_WIFI_SSID, null, false)));
+        mLooper.dispatchAll();
+        verify(mWifiDiagnostics, never()).takeBugReport(anyString(), anyString());
+
+        // network1 connected after a long time, should trigger BR
+        handler.sendMessage(handler.obtainMessage(
+                WifiMonitor.NETWORK_CONNECTION_EVENT,
+                new NetworkConnectionEventInfo(TEST_NETWORK_ID, TEST_WIFI_SSID, null, false)));
+        mLooper.dispatchAll();
+        verify(mWifiDiagnostics).takeBugReport(anyString(), anyString());
     }
 
     /**
@@ -2228,14 +2304,15 @@
 
         // verifies that bugreport is taken for connections that take longer than |testDurationMs|.
         when(mClock.getElapsedSinceBootMillis()).thenReturn(1L);
-        mLastResortWatchdog.noteStartConnectTime();
+        mLastResortWatchdog.noteStartConnectTime(TEST_NETWORK_ID);
         mLooper.dispatchAll();
         when(mClock.getElapsedSinceBootMillis()).thenReturn((long) testDurationMs + 2);
-        Handler handler = mLastResortWatchdog.getHandler();
-        handler.sendMessage(
-                handler.obtainMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, null));
+        Handler handler = mHandlerCaptor.getValue();
+        handler.sendMessage(handler.obtainMessage(
+                WifiMonitor.NETWORK_CONNECTION_EVENT,
+                new NetworkConnectionEventInfo(TEST_NETWORK_ID, TEST_WIFI_SSID, null, false)));
         mLooper.dispatchAll();
-        verify(mClientModeImpl).takeBugReport(anyString(), anyString());
+        verify(mWifiDiagnostics).takeBugReport(anyString(), anyString());
     }
 
     /**
@@ -2249,15 +2326,16 @@
 
         // verifies that bugreports are not taken.
         when(mClock.getElapsedSinceBootMillis()).thenReturn(1L);
-        mLastResortWatchdog.noteStartConnectTime();
+        mLastResortWatchdog.noteStartConnectTime(TEST_NETWORK_ID);
         mLooper.dispatchAll();
         when(mClock.getElapsedSinceBootMillis()).thenReturn(
                 (long) DEFAULT_ABNORMAL_CONNECTION_DURATION_MS + 2);
-        Handler handler = mLastResortWatchdog.getHandler();
-        handler.sendMessage(
-                handler.obtainMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, null));
+        Handler handler = mHandlerCaptor.getValue();
+        handler.sendMessage(handler.obtainMessage(
+                WifiMonitor.NETWORK_CONNECTION_EVENT,
+                new NetworkConnectionEventInfo(TEST_NETWORK_ID, TEST_WIFI_SSID, null, false)));
         mLooper.dispatchAll();
-        verify(mClientModeImpl, never()).takeBugReport(anyString(), anyString());
+        verify(mWifiDiagnostics, never()).takeBugReport(anyString(), anyString());
     }
 
     /**
@@ -2271,7 +2349,8 @@
         String[] ssids = {"\"test1\"", "\"test1\"", "\"test1\""};
         String[] bssids =  {"aa:bb:cc:dd:ee:ff", "00:11:22:33:44:55", "a0:b0:c0:d0:e0:f0"};
         int[] frequencies = {2437, 5180, 5180};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]",
+                "[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {-60, -86, -50};
         boolean[] isEphemeral = {false, false, false};
         boolean[] hasEverConnected = {true, true, true};
@@ -2292,11 +2371,11 @@
         //Increment failure counts
         for (int i = 0; i < associationRejections; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(ssids[1], bssids[1],
-                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
         }
         for (int i = 0; i < authenticationFailures; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(ssids[2], bssids[2],
-                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION, false);
         }
 
         // Verify watchdog has triggered a restart
@@ -2312,7 +2391,7 @@
         // Verify takeBugReport is not called
         mLooper.dispatchAll();
         verify(mWifiMetrics, never()).incrementNumLastResortWatchdogSuccesses();
-        verify(mClientModeImpl, never()).takeBugReport(anyString(), anyString());
+        verify(mWifiDiagnostics, never()).takeBugReport(anyString(), anyString());
     }
 
     /**
@@ -2323,7 +2402,7 @@
         String[] ssids = {"\"test1\""};
         String[] bssids = {"04:03:02:01:00:0f"};
         int[] frequencies = {2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {-60};
         boolean[] isEphemeral = {false};
         boolean[] hasEverConnected = {true};
@@ -2344,13 +2423,13 @@
         // Increment failure counts
         for (int i = 0; i < WifiLastResortWatchdog.FAILURE_THRESHOLD; i++) {
             mLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(
-                    ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    ssids[0], bssids[0], WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION, false);
         }
 
         // Verify watchdog never trigger recovery
         verify(mWifiMetrics, never()).incrementNumLastResortWatchdogTriggers();
         // Verify watchdog still trigger bugreport
         mLooper.dispatchAll();
-        verify(mClientModeImpl, times(1)).takeBugReport(anyString(), anyString());
+        verify(mWifiDiagnostics, times(1)).takeBugReport(anyString(), anyString());
     }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiLinkLayerStatsTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiLinkLayerStatsTest.java
index b18d80b..3b2a0b1 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiLinkLayerStatsTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiLinkLayerStatsTest.java
@@ -20,8 +20,6 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 
-import android.content.Context;
-
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
@@ -35,6 +33,8 @@
 @SmallTest
 public class WifiLinkLayerStatsTest extends WifiBaseTest {
 
+    private static final String WIFI_IFACE_NAME = "wlanTest";
+
     ExtendedWifiInfo mWifiInfo;
     WifiLinkLayerStats mWifiLinkLayerStats;
     Random mRandom = new Random();
@@ -44,7 +44,7 @@
      */
     @Before
     public void setUp() throws Exception {
-        mWifiInfo = new ExtendedWifiInfo(mock(Context.class));
+        mWifiInfo = new ExtendedWifiInfo(mock(WifiGlobals.class), WIFI_IFACE_NAME);
         mWifiLinkLayerStats = new WifiLinkLayerStats();
     }
 
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiLockManagerTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiLockManagerTest.java
index 40c47f4..cac37ea 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiLockManagerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiLockManagerTest.java
@@ -16,25 +16,37 @@
 
 package com.android.server.wifi;
 
+import static android.content.Intent.ACTION_SCREEN_OFF;
+import static android.content.Intent.ACTION_SCREEN_ON;
+
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_PRIMARY;
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SECONDARY_TRANSIENT;
+
 import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.*;
 import static org.mockito.Mockito.*;
 
 import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
 import android.net.wifi.WifiManager;
 import android.os.BatteryStatsManager;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.PowerManager;
 import android.os.WorkSource;
 import android.os.test.TestLooper;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.wifi.ActiveModeWarden.PrimaryClientModeManagerChangedCallback;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -53,7 +65,6 @@
     private static final int DEFAULT_TEST_UID_4 = 55;
     private static final int WIFI_LOCK_MODE_INVALID = -1;
     private static final String TEST_WIFI_LOCK_TAG = "TestTag";
-    private static final String INTERFACE_NAME = "IfaceName";
 
     private ActivityManager.OnUidImportanceListener mUidImportanceListener;
 
@@ -65,13 +76,17 @@
     WorkSource mWorkSource;
     WorkSource mChainedWorkSource;
     @Mock Context mContext;
-    @Mock ClientModeImpl mClientModeImpl;
+    @Mock ConcreteClientModeManager mClientModeManager;
+    @Mock ConcreteClientModeManager mClientModeManager2;
     @Mock FrameworkFacade mFrameworkFacade;
     @Mock ActivityManager mActivityManager;
     @Mock WifiMetrics mWifiMetrics;
-    @Mock WifiNative mWifiNative;
+    @Mock ActiveModeWarden mActiveModeWarden;
+    @Mock PowerManager mPowerManager;
     TestLooper mLooper;
     Handler mHandler;
+    @Captor ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor;
+    @Captor ArgumentCaptor<PrimaryClientModeManagerChangedCallback> mPrimaryChangedCallbackCaptor;
 
     /**
      * Method to setup a WifiLockManager for the tests.
@@ -89,17 +104,26 @@
         mLooper = new TestLooper();
         mHandler = new Handler(mLooper.getLooper());
         when(mContext.getSystemService(Context.ACTIVITY_SERVICE)).thenReturn(mActivityManager);
-        when(mWifiNative.getClientInterfaceName()).thenReturn(INTERFACE_NAME);
+        when(mContext.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
+
+        when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_PRIMARY);
+        when(mActiveModeWarden.getPrimaryClientModeManager()).thenReturn(mClientModeManager);
+
+        when(mClientModeManager2.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_TRANSIENT);
 
         mWifiLockManager = new WifiLockManager(mContext, mBatteryStats,
-                mClientModeImpl, mFrameworkFacade, mHandler, mWifiNative, mClock, mWifiMetrics);
+                mActiveModeWarden, mFrameworkFacade, mHandler, mClock, mWifiMetrics);
+        verify(mContext, atLeastOnce()).registerReceiver(
+                mBroadcastReceiverCaptor.capture(), any(), any(), any());
+        verify(mActiveModeWarden).registerPrimaryClientModeManagerChangedCallback(
+                mPrimaryChangedCallbackCaptor.capture());
     }
 
     private void acquireWifiLockSuccessful(int lockMode, String tag, IBinder binder, WorkSource ws)
             throws Exception {
         ArgumentCaptor<IBinder.DeathRecipient> deathRecipient =
                 ArgumentCaptor.forClass(IBinder.DeathRecipient.class);
-        mWifiLockManager.updateWifiClientConnected(true);
+        mWifiLockManager.updateWifiClientConnected(mClientModeManager, true);
         assertTrue(mWifiLockManager.acquireWifiLock(lockMode, tag, binder, ws));
         assertThat(mWifiLockManager.getStrongestLockMode(),
                 not(WifiManager.WIFI_MODE_NO_LOCKS_HELD));
@@ -372,7 +396,7 @@
                 mBinder, mWorkSource);
         assertEquals(WifiManager.WIFI_MODE_FULL_HIGH_PERF,
                 mWifiLockManager.getStrongestLockMode());
-        verify(mClientModeImpl).setPowerSave(false);
+        verify(mClientModeManager).setPowerSave(false);
     }
 
     /**
@@ -381,19 +405,19 @@
      */
     @Test
     public void testHiPerfLockReleaseCauseEnablePS() throws Exception {
-        InOrder inOrder = inOrder(mClientModeImpl);
-        when(mClientModeImpl.setPowerSave(anyBoolean())).thenReturn(true);
+        InOrder inOrder = inOrder(mClientModeManager);
+        when(mClientModeManager.setPowerSave(anyBoolean())).thenReturn(true);
 
         acquireWifiLockSuccessful(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "",
                 mBinder, mWorkSource);
         assertEquals(WifiManager.WIFI_MODE_FULL_HIGH_PERF,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl).setPowerSave(false);
+        inOrder.verify(mClientModeManager).setPowerSave(false);
 
         releaseWifiLockSuccessful(mBinder);
         assertEquals(WifiManager.WIFI_MODE_NO_LOCKS_HELD,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl).setPowerSave(true);
+        inOrder.verify(mClientModeManager).setPowerSave(true);
         verify(mWifiMetrics).addWifiLockActiveSession(eq(WifiManager.WIFI_MODE_FULL_HIGH_PERF),
                 anyLong());
     }
@@ -404,22 +428,22 @@
      */
     @Test
     public void testHiPerfLockAcquireReleaseTwice() throws Exception {
-        InOrder inOrder = inOrder(mClientModeImpl);
-        when(mClientModeImpl.setPowerSave(anyBoolean())).thenReturn(true);
+        InOrder inOrder = inOrder(mClientModeManager);
+        when(mClientModeManager.setPowerSave(anyBoolean())).thenReturn(true);
 
         // Acquire the first lock
         acquireWifiLockSuccessful(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "",
                 mBinder, mWorkSource);
         assertEquals(WifiManager.WIFI_MODE_FULL_HIGH_PERF,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl).setPowerSave(false);
+        inOrder.verify(mClientModeManager).setPowerSave(false);
 
         // Now acquire another lock
         acquireWifiLockSuccessful(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "",
                 mBinder2, mWorkSource);
         assertEquals(WifiManager.WIFI_MODE_FULL_HIGH_PERF,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl, never()).setPowerSave(anyBoolean());
+        inOrder.verify(mClientModeManager, never()).setPowerSave(anyBoolean());
 
         // Release the first lock
         releaseWifiLockSuccessful(mBinder);
@@ -428,7 +452,7 @@
 
         assertEquals(WifiManager.WIFI_MODE_FULL_HIGH_PERF,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl, never()).setPowerSave(anyBoolean());
+        inOrder.verify(mClientModeManager, never()).setPowerSave(anyBoolean());
 
         // Release the second lock
         releaseWifiLockSuccessful(mBinder2);
@@ -436,7 +460,7 @@
                 anyLong());
         assertEquals(WifiManager.WIFI_MODE_NO_LOCKS_HELD,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl).setPowerSave(true);
+        inOrder.verify(mClientModeManager).setPowerSave(true);
         verify(mWifiMetrics).addWifiLockActiveSession(eq(WifiManager.WIFI_MODE_FULL_HIGH_PERF),
                 anyLong());
     }
@@ -451,34 +475,34 @@
         IBinder scanOnlyLockBinder = mock(IBinder.class);
         WorkSource scanOnlyLockWS = new WorkSource(DEFAULT_TEST_UID_2);
 
-        InOrder inOrder = inOrder(mClientModeImpl);
-        when(mClientModeImpl.setPowerSave(anyBoolean())).thenReturn(true);
+        InOrder inOrder = inOrder(mClientModeManager);
+        when(mClientModeManager.setPowerSave(anyBoolean())).thenReturn(true);
 
         // Acquire the first lock as FULL
         assertTrue(mWifiLockManager.acquireWifiLock(WifiManager.WIFI_MODE_FULL, "",
                 fullLockBinder, fullLockWS));
         assertEquals(WifiManager.WIFI_MODE_NO_LOCKS_HELD,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl, never()).setPowerSave(anyBoolean());
+        inOrder.verify(mClientModeManager, never()).setPowerSave(anyBoolean());
 
         // Now acquire another lock with SCAN-ONLY
         assertTrue(mWifiLockManager.acquireWifiLock(WifiManager.WIFI_MODE_SCAN_ONLY, "",
                 scanOnlyLockBinder, scanOnlyLockWS));
         assertEquals(WifiManager.WIFI_MODE_NO_LOCKS_HELD,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl, never()).setPowerSave(anyBoolean());
+        inOrder.verify(mClientModeManager, never()).setPowerSave(anyBoolean());
 
         // Release the FULL lock
         releaseWifiLockSuccessful_noBatteryStats(fullLockBinder);
         assertEquals(WifiManager.WIFI_MODE_NO_LOCKS_HELD,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl, never()).setPowerSave(anyBoolean());
+        inOrder.verify(mClientModeManager, never()).setPowerSave(anyBoolean());
 
         // Release the SCAN-ONLY lock
         releaseWifiLockSuccessful_noBatteryStats(scanOnlyLockBinder);
         assertEquals(WifiManager.WIFI_MODE_NO_LOCKS_HELD,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl, never()).setPowerSave(anyBoolean());
+        inOrder.verify(mClientModeManager, never()).setPowerSave(anyBoolean());
     }
 
     /**
@@ -491,21 +515,21 @@
         IBinder fullLockBinder = mock(IBinder.class);
         WorkSource fullLockWS = new WorkSource(DEFAULT_TEST_UID_1);
 
-        InOrder inOrder = inOrder(mClientModeImpl);
-        when(mClientModeImpl.setPowerSave(anyBoolean())).thenReturn(false);
+        InOrder inOrder = inOrder(mClientModeManager);
+        when(mClientModeManager.setPowerSave(anyBoolean())).thenReturn(false);
         acquireWifiLockSuccessful(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "",
                 mBinder, mWorkSource);
         assertEquals(WifiManager.WIFI_MODE_FULL_HIGH_PERF,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl).setPowerSave(false);
+        inOrder.verify(mClientModeManager).setPowerSave(false);
 
         // Now attempting adding some other lock, WifiLockManager should retry setPowerSave()
-        when(mClientModeImpl.setPowerSave(anyBoolean())).thenReturn(true);
+        when(mClientModeManager.setPowerSave(anyBoolean())).thenReturn(true);
         assertTrue(mWifiLockManager.acquireWifiLock(WifiManager.WIFI_MODE_FULL, "",
                 fullLockBinder, fullLockWS));
         assertEquals(WifiManager.WIFI_MODE_FULL_HIGH_PERF,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl).setPowerSave(false);
+        inOrder.verify(mClientModeManager).setPowerSave(false);
     }
 
     /**
@@ -518,31 +542,31 @@
         IBinder fullLockBinder = mock(IBinder.class);
         WorkSource fullLockWS = new WorkSource(DEFAULT_TEST_UID_1);
 
-        InOrder inOrder = inOrder(mClientModeImpl);
-        when(mClientModeImpl.setPowerSave(false)).thenReturn(true);
-        when(mClientModeImpl.setPowerSave(true)).thenReturn(false);
+        InOrder inOrder = inOrder(mClientModeManager);
+        when(mClientModeManager.setPowerSave(false)).thenReturn(true);
+        when(mClientModeManager.setPowerSave(true)).thenReturn(false);
 
         acquireWifiLockSuccessful(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "",
                 mBinder, mWorkSource);
         assertEquals(WifiManager.WIFI_MODE_FULL_HIGH_PERF,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl).setPowerSave(false);
+        inOrder.verify(mClientModeManager).setPowerSave(false);
 
         releaseWifiLockSuccessful(mBinder);
         verify(mWifiMetrics).addWifiLockAcqSession(eq(WifiManager.WIFI_MODE_FULL_HIGH_PERF),
                 anyLong());
         assertEquals(WifiManager.WIFI_MODE_NO_LOCKS_HELD,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl).setPowerSave(true);
+        inOrder.verify(mClientModeManager).setPowerSave(true);
         verify(mWifiMetrics, never()).addWifiLockActiveSession(anyInt(), anyLong());
 
         // Now attempting adding some other lock, WifiLockManager should retry setPowerSave()
-        when(mClientModeImpl.setPowerSave(true)).thenReturn(true);
+        when(mClientModeManager.setPowerSave(true)).thenReturn(true);
         assertTrue(mWifiLockManager.acquireWifiLock(WifiManager.WIFI_MODE_FULL, "",
                 fullLockBinder, fullLockWS));
         assertEquals(WifiManager.WIFI_MODE_NO_LOCKS_HELD,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl).setPowerSave(true);
+        inOrder.verify(mClientModeManager).setPowerSave(true);
         verify(mWifiMetrics).addWifiLockActiveSession(eq(WifiManager.WIFI_MODE_FULL_HIGH_PERF),
                 anyLong());
     }
@@ -553,9 +577,9 @@
      */
     @Test
     public void testForceHiPerf() throws Exception {
-        when(mClientModeImpl.setPowerSave(anyBoolean())).thenReturn(true);
-        InOrder inOrder = inOrder(mClientModeImpl);
-        mWifiLockManager.updateWifiClientConnected(true);
+        when(mClientModeManager.setPowerSave(anyBoolean())).thenReturn(true);
+        InOrder inOrder = inOrder(mClientModeManager);
+        mWifiLockManager.updateWifiClientConnected(mClientModeManager, true);
         assertTrue(mWifiLockManager.acquireWifiLock(WifiManager.WIFI_MODE_SCAN_ONLY, "",
                 mBinder, mWorkSource));
         assertEquals(WifiManager.WIFI_MODE_NO_LOCKS_HELD,
@@ -563,11 +587,11 @@
         assertTrue(mWifiLockManager.forceHiPerfMode(true));
         assertEquals(WifiManager.WIFI_MODE_FULL_HIGH_PERF,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl).setPowerSave(false);
+        inOrder.verify(mClientModeManager).setPowerSave(false);
         assertTrue(mWifiLockManager.forceHiPerfMode(false));
         assertEquals(WifiManager.WIFI_MODE_NO_LOCKS_HELD,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl).setPowerSave(true);
+        inOrder.verify(mClientModeManager).setPowerSave(true);
         verify(mWifiMetrics).addWifiLockActiveSession(eq(WifiManager.WIFI_MODE_FULL_HIGH_PERF),
                 anyLong());
     }
@@ -577,31 +601,31 @@
      */
     @Test
     public void testForceHiPerfAcqRelHiPerf() throws Exception {
-        when(mClientModeImpl.setPowerSave(anyBoolean())).thenReturn(true);
-        InOrder inOrder = inOrder(mClientModeImpl);
+        when(mClientModeManager.setPowerSave(anyBoolean())).thenReturn(true);
+        InOrder inOrder = inOrder(mClientModeManager);
 
         acquireWifiLockSuccessful(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "",
                 mBinder, mWorkSource);
         assertEquals(WifiManager.WIFI_MODE_FULL_HIGH_PERF,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl).setPowerSave(false);
+        inOrder.verify(mClientModeManager).setPowerSave(false);
 
         assertTrue(mWifiLockManager.forceHiPerfMode(true));
         assertEquals(WifiManager.WIFI_MODE_FULL_HIGH_PERF,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl, never()).setPowerSave(anyBoolean());
+        inOrder.verify(mClientModeManager, never()).setPowerSave(anyBoolean());
 
         releaseWifiLockSuccessful(mBinder);
         verify(mWifiMetrics).addWifiLockAcqSession(eq(WifiManager.WIFI_MODE_FULL_HIGH_PERF),
                 anyLong());
         assertEquals(WifiManager.WIFI_MODE_FULL_HIGH_PERF,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl, never()).setPowerSave(anyBoolean());
+        inOrder.verify(mClientModeManager, never()).setPowerSave(anyBoolean());
 
         assertTrue(mWifiLockManager.forceHiPerfMode(false));
         assertEquals(WifiManager.WIFI_MODE_NO_LOCKS_HELD,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl).setPowerSave(true);
+        inOrder.verify(mClientModeManager).setPowerSave(true);
         verify(mWifiMetrics).addWifiLockActiveSession(eq(WifiManager.WIFI_MODE_FULL_HIGH_PERF),
                 anyLong());
     }
@@ -611,19 +635,19 @@
      */
     @Test
     public void testForceHiPerfTwice() throws Exception {
-        when(mClientModeImpl.setPowerSave(anyBoolean())).thenReturn(true);
-        mWifiLockManager.updateWifiClientConnected(true);
-        InOrder inOrder = inOrder(mClientModeImpl);
+        when(mClientModeManager.setPowerSave(anyBoolean())).thenReturn(true);
+        mWifiLockManager.updateWifiClientConnected(mClientModeManager, true);
+        InOrder inOrder = inOrder(mClientModeManager);
 
         assertTrue(mWifiLockManager.forceHiPerfMode(true));
         assertEquals(WifiManager.WIFI_MODE_FULL_HIGH_PERF,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl).setPowerSave(false);
+        inOrder.verify(mClientModeManager).setPowerSave(false);
 
         assertTrue(mWifiLockManager.forceHiPerfMode(true));
         assertEquals(WifiManager.WIFI_MODE_FULL_HIGH_PERF,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl, never()).setPowerSave(anyBoolean());
+        inOrder.verify(mClientModeManager, never()).setPowerSave(anyBoolean());
     }
 
     /**
@@ -631,9 +655,9 @@
      */
     @Test
     public void testForceHiPerfFailure() throws Exception {
-        when(mClientModeImpl.setPowerSave(anyBoolean())).thenReturn(false);
-        mWifiLockManager.updateWifiClientConnected(true);
-        InOrder inOrder = inOrder(mClientModeImpl);
+        when(mClientModeManager.setPowerSave(anyBoolean())).thenReturn(false);
+        mWifiLockManager.updateWifiClientConnected(mClientModeManager, true);
+        InOrder inOrder = inOrder(mClientModeManager);
 
         assertTrue(mWifiLockManager.acquireWifiLock(WifiManager.WIFI_MODE_SCAN_ONLY, "",
                 mBinder, mWorkSource));
@@ -643,7 +667,7 @@
         assertFalse(mWifiLockManager.forceHiPerfMode(true));
         assertEquals(WifiManager.WIFI_MODE_NO_LOCKS_HELD,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl).setPowerSave(false);
+        inOrder.verify(mClientModeManager).setPowerSave(false);
     }
 
     /**
@@ -653,7 +677,7 @@
     @Test
     public void testForegroundAppAcquireLowLatencyScreenOn() throws Exception {
         // Set screen on, and app foreground
-        mWifiLockManager.handleScreenStateChanged(true);
+        setScreenState(true);
         when(mFrameworkFacade.isAppForeground(any(), anyInt())).thenReturn(true);
 
         acquireWifiLockSuccessful(WifiManager.WIFI_MODE_FULL_LOW_LATENCY, "",
@@ -671,7 +695,7 @@
     @Test
     public void testForegroundAppAcquireLowLatencyScreenOff() throws Exception {
         // Set screen off, and app is foreground
-        mWifiLockManager.handleScreenStateChanged(false);
+        setScreenState(false);
         when(mFrameworkFacade.isAppForeground(any(), anyInt())).thenReturn(true);
 
         assertTrue(mWifiLockManager.acquireWifiLock(WifiManager.WIFI_MODE_FULL_LOW_LATENCY,
@@ -687,7 +711,7 @@
     @Test
     public void testBackgroundAppAcquireLowLatencyScreenOn() throws Exception {
         // Set screen on, and app is background
-        mWifiLockManager.handleScreenStateChanged(true);
+        setScreenState(true);
         when(mFrameworkFacade.isAppForeground(any(), anyInt())).thenReturn(false);
 
         assertTrue(mWifiLockManager.acquireWifiLock(WifiManager.WIFI_MODE_FULL_LOW_LATENCY,
@@ -703,17 +727,17 @@
     @Test
     public void testLatencyLockAcquireCauseLlEnableNew() throws Exception {
         // Set screen on, and app foreground
-        mWifiLockManager.handleScreenStateChanged(true);
+        setScreenState(true);
         when(mFrameworkFacade.isAppForeground(any(), anyInt())).thenReturn(true);
-        when(mClientModeImpl.setLowLatencyMode(anyBoolean())).thenReturn(true);
-        when(mWifiNative.getSupportedFeatureSet(INTERFACE_NAME))
-                .thenReturn((long) WifiManager.WIFI_FEATURE_LOW_LATENCY);
+        when(mClientModeManager.setLowLatencyMode(anyBoolean())).thenReturn(true);
+        when(mClientModeManager.getSupportedFeatures())
+                .thenReturn(WifiManager.WIFI_FEATURE_LOW_LATENCY);
 
         acquireWifiLockSuccessful(WifiManager.WIFI_MODE_FULL_LOW_LATENCY, "",
                 mBinder, mWorkSource);
 
-        verify(mClientModeImpl).setLowLatencyMode(true);
-        verify(mClientModeImpl).setPowerSave(false);
+        verify(mClientModeManager).setLowLatencyMode(true);
+        verify(mClientModeManager).setPowerSave(false);
     }
 
     /**
@@ -723,16 +747,16 @@
     @Test
     public void testLatencyLockAcquireCauseLL_enableLegacy() throws Exception {
         // Set screen on, and app foreground
-        mWifiLockManager.handleScreenStateChanged(true);
+        setScreenState(true);
         when(mFrameworkFacade.isAppForeground(any(), anyInt())).thenReturn(true);
-        when(mWifiNative.getSupportedFeatureSet(INTERFACE_NAME))
+        when(mClientModeManager.getSupportedFeatures())
                 .thenReturn((long) WifiManager.WIFI_FEATURE_TX_POWER_LIMIT);
 
         acquireWifiLockSuccessful(WifiManager.WIFI_MODE_FULL_LOW_LATENCY, "",
                 mBinder, mWorkSource);
 
-        verify(mClientModeImpl).setPowerSave(false);
-        verify(mClientModeImpl, never()).setLowLatencyMode(anyBoolean());
+        verify(mClientModeManager).setPowerSave(false);
+        verify(mClientModeManager, never()).setLowLatencyMode(anyBoolean());
     }
 
     /**
@@ -742,27 +766,27 @@
     @Test
     public void testLatencyLockReleaseCauseLlDisable() throws Exception {
         // Set screen on, and app foreground
-        mWifiLockManager.handleScreenStateChanged(true);
+        setScreenState(true);
         when(mFrameworkFacade.isAppForeground(any(), anyInt())).thenReturn(true);
-        when(mWifiNative.getSupportedFeatureSet(INTERFACE_NAME))
+        when(mClientModeManager.getSupportedFeatures())
                 .thenReturn((long) WifiManager.WIFI_FEATURE_LOW_LATENCY);
 
         // Make sure setLowLatencyMode() is successful
-        when(mClientModeImpl.setLowLatencyMode(anyBoolean())).thenReturn(true);
-        when(mClientModeImpl.setPowerSave(anyBoolean())).thenReturn(true);
+        when(mClientModeManager.setLowLatencyMode(anyBoolean())).thenReturn(true);
+        when(mClientModeManager.setPowerSave(anyBoolean())).thenReturn(true);
 
-        InOrder inOrder = inOrder(mClientModeImpl);
+        InOrder inOrder = inOrder(mClientModeManager);
 
         acquireWifiLockSuccessful(WifiManager.WIFI_MODE_FULL_LOW_LATENCY, "",
                 mBinder, mWorkSource);
-        inOrder.verify(mClientModeImpl).setLowLatencyMode(true);
-        inOrder.verify(mClientModeImpl).setPowerSave(false);
+        inOrder.verify(mClientModeManager).setLowLatencyMode(true);
+        inOrder.verify(mClientModeManager).setPowerSave(false);
 
         releaseLowLatencyWifiLockSuccessful(mBinder);
         assertEquals(WifiManager.WIFI_MODE_NO_LOCKS_HELD,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl).setLowLatencyMode(false);
-        inOrder.verify(mClientModeImpl).setPowerSave(true);
+        inOrder.verify(mClientModeManager).setLowLatencyMode(false);
+        inOrder.verify(mClientModeManager).setPowerSave(true);
         verify(mWifiMetrics).addWifiLockActiveSession(eq(WifiManager.WIFI_MODE_FULL_LOW_LATENCY),
                 anyLong());
     }
@@ -773,27 +797,27 @@
      */
     @Test
     public void testLatencyLockReleaseFailure() throws Exception {
-        InOrder inOrder = inOrder(mClientModeImpl);
+        InOrder inOrder = inOrder(mClientModeManager);
 
         // Set screen on, and app is foreground
-        mWifiLockManager.handleScreenStateChanged(true);
+        setScreenState(true);
         when(mFrameworkFacade.isAppForeground(any(), anyInt())).thenReturn(true);
-        when(mWifiNative.getSupportedFeatureSet(INTERFACE_NAME))
+        when(mClientModeManager.getSupportedFeatures())
                 .thenReturn((long) WifiManager.WIFI_FEATURE_LOW_LATENCY);
 
-        // Fail the call to ClientModeImpl
-        when(mClientModeImpl.setLowLatencyMode(true)).thenReturn(false);
+        // Fail the call to ClientModeManager
+        when(mClientModeManager.setLowLatencyMode(true)).thenReturn(false);
 
         acquireWifiLockSuccessful(WifiManager.WIFI_MODE_FULL_LOW_LATENCY, "",
                 mBinder, mWorkSource);
         assertEquals(WifiManager.WIFI_MODE_FULL_LOW_LATENCY,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl).setLowLatencyMode(true);
+        inOrder.verify(mClientModeManager).setLowLatencyMode(true);
 
         releaseLowLatencyWifiLockSuccessful(mBinder);
         assertEquals(WifiManager.WIFI_MODE_NO_LOCKS_HELD,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl, never()).setLowLatencyMode(anyBoolean());
+        inOrder.verify(mClientModeManager, never()).setLowLatencyMode(anyBoolean());
     }
 
     /**
@@ -802,26 +826,26 @@
      */
     @Test
     public void testLatencyfail2DisablePowerSave() throws Exception {
-        InOrder inOrder = inOrder(mClientModeImpl);
+        InOrder inOrder = inOrder(mClientModeManager);
 
         // Set screen on, and app is foreground
-        mWifiLockManager.handleScreenStateChanged(true);
+        setScreenState(true);
         when(mFrameworkFacade.isAppForeground(any(), anyInt())).thenReturn(true);
-        when(mWifiNative.getSupportedFeatureSet(INTERFACE_NAME))
+        when(mClientModeManager.getSupportedFeatures())
                 .thenReturn((long) WifiManager.WIFI_FEATURE_LOW_LATENCY);
 
         // Succeed to setLowLatencyMode()
-        when(mClientModeImpl.setLowLatencyMode(anyBoolean())).thenReturn(true);
+        when(mClientModeManager.setLowLatencyMode(anyBoolean())).thenReturn(true);
         // Fail to setPowerSave()
-        when(mClientModeImpl.setPowerSave(false)).thenReturn(false);
+        when(mClientModeManager.setPowerSave(false)).thenReturn(false);
 
         acquireWifiLockSuccessful(WifiManager.WIFI_MODE_FULL_LOW_LATENCY, "",
                 mBinder, mWorkSource);
         assertEquals(WifiManager.WIFI_MODE_FULL_LOW_LATENCY,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl).setLowLatencyMode(true);
-        inOrder.verify(mClientModeImpl).setPowerSave(false);
-        inOrder.verify(mClientModeImpl).setLowLatencyMode(false);
+        inOrder.verify(mClientModeManager).setLowLatencyMode(true);
+        inOrder.verify(mClientModeManager).setPowerSave(false);
+        inOrder.verify(mClientModeManager).setLowLatencyMode(false);
     }
 
     /**
@@ -831,29 +855,29 @@
     @Test
     public void testLatencyLockGoScreenOff() throws Exception {
         // Set screen on, app foreground
-        mWifiLockManager.handleScreenStateChanged(true);
+        setScreenState(true);
         when(mFrameworkFacade.isAppForeground(any(), anyInt())).thenReturn(true);
-        when(mWifiNative.getSupportedFeatureSet(INTERFACE_NAME))
+        when(mClientModeManager.getSupportedFeatures())
                 .thenReturn((long) WifiManager.WIFI_FEATURE_LOW_LATENCY);
 
         // Make sure setLowLatencyMode() is successful
-        when(mClientModeImpl.setLowLatencyMode(anyBoolean())).thenReturn(true);
-        when(mClientModeImpl.setPowerSave(anyBoolean())).thenReturn(true);
+        when(mClientModeManager.setLowLatencyMode(anyBoolean())).thenReturn(true);
+        when(mClientModeManager.setPowerSave(anyBoolean())).thenReturn(true);
 
-        InOrder inOrder = inOrder(mClientModeImpl);
+        InOrder inOrder = inOrder(mClientModeManager);
 
         acquireWifiLockSuccessful(WifiManager.WIFI_MODE_FULL_LOW_LATENCY, "",
                 mBinder, mWorkSource);
         assertEquals(WifiManager.WIFI_MODE_FULL_LOW_LATENCY,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl).setLowLatencyMode(true);
-        inOrder.verify(mClientModeImpl).setPowerSave(false);
+        inOrder.verify(mClientModeManager).setLowLatencyMode(true);
+        inOrder.verify(mClientModeManager).setPowerSave(false);
 
-        mWifiLockManager.handleScreenStateChanged(false);
+        setScreenState(false);
         assertEquals(WifiManager.WIFI_MODE_NO_LOCKS_HELD,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl).setLowLatencyMode(false);
-        inOrder.verify(mClientModeImpl).setPowerSave(true);
+        inOrder.verify(mClientModeManager).setLowLatencyMode(false);
+        inOrder.verify(mClientModeManager).setPowerSave(true);
         verify(mWifiMetrics).addWifiLockActiveSession(eq(WifiManager.WIFI_MODE_FULL_LOW_LATENCY),
                 anyLong());
     }
@@ -865,16 +889,16 @@
     @Test
     public void testLatencyLockGoBackground() throws Exception {
         // Initially, set screen on, app foreground
-        mWifiLockManager.handleScreenStateChanged(true);
+        setScreenState(true);
         when(mFrameworkFacade.isAppForeground(any(), anyInt())).thenReturn(true);
-        when(mWifiNative.getSupportedFeatureSet(INTERFACE_NAME))
+        when(mClientModeManager.getSupportedFeatures())
                 .thenReturn((long) WifiManager.WIFI_FEATURE_LOW_LATENCY);
 
         // Make sure setLowLatencyMode() is successful
-        when(mClientModeImpl.setLowLatencyMode(anyBoolean())).thenReturn(true);
-        when(mClientModeImpl.setPowerSave(anyBoolean())).thenReturn(true);
+        when(mClientModeManager.setLowLatencyMode(anyBoolean())).thenReturn(true);
+        when(mClientModeManager.setPowerSave(anyBoolean())).thenReturn(true);
 
-        InOrder inOrder = inOrder(mClientModeImpl);
+        InOrder inOrder = inOrder(mClientModeManager);
 
         acquireWifiLockSuccessful(WifiManager.WIFI_MODE_FULL_LOW_LATENCY, "",
                 mBinder, mWorkSource);
@@ -882,8 +906,8 @@
 
         assertEquals(WifiManager.WIFI_MODE_FULL_LOW_LATENCY,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl).setLowLatencyMode(true);
-        inOrder.verify(mClientModeImpl).setPowerSave(false);
+        inOrder.verify(mClientModeManager).setLowLatencyMode(true);
+        inOrder.verify(mClientModeManager).setPowerSave(false);
 
         /* App going to background */
         mUidImportanceListener.onUidImportance(DEFAULT_TEST_UID_1,
@@ -891,8 +915,8 @@
         mLooper.dispatchAll();
         assertEquals(WifiManager.WIFI_MODE_NO_LOCKS_HELD,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl).setLowLatencyMode(false);
-        inOrder.verify(mClientModeImpl).setPowerSave(true);
+        inOrder.verify(mClientModeManager).setLowLatencyMode(false);
+        inOrder.verify(mClientModeManager).setPowerSave(true);
         verify(mWifiMetrics).addWifiLockActiveSession(eq(WifiManager.WIFI_MODE_FULL_LOW_LATENCY),
                 anyLong());
     }
@@ -904,16 +928,16 @@
     @Test
     public void testLatencyLockGoForeground() throws Exception {
         // Initially, set screen on, and app background
-        mWifiLockManager.handleScreenStateChanged(true);
+        setScreenState(true);
         when(mFrameworkFacade.isAppForeground(any(), anyInt())).thenReturn(false);
-        when(mWifiNative.getSupportedFeatureSet(INTERFACE_NAME))
+        when(mClientModeManager.getSupportedFeatures())
                 .thenReturn((long) WifiManager.WIFI_FEATURE_LOW_LATENCY);
         // Make sure setLowLatencyMode() is successful
-        when(mClientModeImpl.setLowLatencyMode(anyBoolean())).thenReturn(true);
+        when(mClientModeManager.setLowLatencyMode(anyBoolean())).thenReturn(true);
 
-        mWifiLockManager.updateWifiClientConnected(true);
+        mWifiLockManager.updateWifiClientConnected(mClientModeManager, true);
 
-        InOrder inOrder = inOrder(mClientModeImpl);
+        InOrder inOrder = inOrder(mClientModeManager);
 
         assertTrue(mWifiLockManager.acquireWifiLock(WifiManager.WIFI_MODE_FULL_LOW_LATENCY,
                 "", mBinder, mWorkSource));
@@ -921,8 +945,8 @@
 
         assertEquals(WifiManager.WIFI_MODE_NO_LOCKS_HELD,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl, never()).setLowLatencyMode(anyBoolean());
-        inOrder.verify(mClientModeImpl, never()).setPowerSave(anyBoolean());
+        inOrder.verify(mClientModeManager, never()).setLowLatencyMode(anyBoolean());
+        inOrder.verify(mClientModeManager, never()).setPowerSave(anyBoolean());
 
         /* App going to foreground */
         mUidImportanceListener.onUidImportance(DEFAULT_TEST_UID_1,
@@ -931,8 +955,8 @@
 
         assertEquals(WifiManager.WIFI_MODE_FULL_LOW_LATENCY,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl).setLowLatencyMode(true);
-        inOrder.verify(mClientModeImpl).setPowerSave(false);
+        inOrder.verify(mClientModeManager).setLowLatencyMode(true);
+        inOrder.verify(mClientModeManager).setPowerSave(false);
     }
 
     /**
@@ -943,17 +967,17 @@
     @Test
     public void testLatencyHiPerfLocks() throws Exception {
         // Initially, set screen on, and app background
-        mWifiLockManager.handleScreenStateChanged(true);
+        setScreenState(true);
         when(mFrameworkFacade.isAppForeground(any(), anyInt())).thenReturn(false);
-        when(mWifiNative.getSupportedFeatureSet(INTERFACE_NAME))
+        when(mClientModeManager.getSupportedFeatures())
                 .thenReturn((long) WifiManager.WIFI_FEATURE_LOW_LATENCY);
-        mWifiLockManager.updateWifiClientConnected(true);
+        mWifiLockManager.updateWifiClientConnected(mClientModeManager, true);
 
         // Make sure setLowLatencyMode()/setPowerSave() is successful
-        when(mClientModeImpl.setLowLatencyMode(anyBoolean())).thenReturn(true);
-        when(mClientModeImpl.setPowerSave(anyBoolean())).thenReturn(true);
+        when(mClientModeManager.setLowLatencyMode(anyBoolean())).thenReturn(true);
+        when(mClientModeManager.setPowerSave(anyBoolean())).thenReturn(true);
 
-        InOrder inOrder = inOrder(mClientModeImpl);
+        InOrder inOrder = inOrder(mClientModeManager);
 
         assertTrue(mWifiLockManager.acquireWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF,
                 "", mBinder, mWorkSource));
@@ -963,8 +987,8 @@
 
         assertEquals(WifiManager.WIFI_MODE_FULL_HIGH_PERF,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl, never()).setLowLatencyMode(anyBoolean());
-        inOrder.verify(mClientModeImpl).setPowerSave(false);
+        inOrder.verify(mClientModeManager, never()).setLowLatencyMode(anyBoolean());
+        inOrder.verify(mClientModeManager).setPowerSave(false);
 
         /* App going to foreground */
         mUidImportanceListener.onUidImportance(DEFAULT_TEST_UID_1,
@@ -974,11 +998,11 @@
         assertEquals(WifiManager.WIFI_MODE_FULL_LOW_LATENCY,
                 mWifiLockManager.getStrongestLockMode());
 
-        inOrder.verify(mClientModeImpl).setPowerSave(true);
+        inOrder.verify(mClientModeManager).setPowerSave(true);
         verify(mWifiMetrics).addWifiLockActiveSession(eq(WifiManager.WIFI_MODE_FULL_HIGH_PERF),
                 anyLong());
-        inOrder.verify(mClientModeImpl).setLowLatencyMode(true);
-        inOrder.verify(mClientModeImpl).setPowerSave(false);
+        inOrder.verify(mClientModeManager).setLowLatencyMode(true);
+        inOrder.verify(mClientModeManager).setPowerSave(false);
     }
 
     /**
@@ -987,12 +1011,12 @@
      */
     @Test
     public void testForceLowLatency() throws Exception {
-        when(mClientModeImpl.setLowLatencyMode(anyBoolean())).thenReturn(true);
-        when(mClientModeImpl.setPowerSave(anyBoolean())).thenReturn(true);
-        when(mWifiNative.getSupportedFeatureSet(INTERFACE_NAME))
+        when(mClientModeManager.setLowLatencyMode(anyBoolean())).thenReturn(true);
+        when(mClientModeManager.setPowerSave(anyBoolean())).thenReturn(true);
+        when(mClientModeManager.getSupportedFeatures())
                 .thenReturn((long) WifiManager.WIFI_FEATURE_LOW_LATENCY);
 
-        InOrder inOrder = inOrder(mClientModeImpl);
+        InOrder inOrder = inOrder(mClientModeManager);
 
         acquireWifiLockSuccessful(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "",
                 mBinder, mWorkSource);
@@ -1001,13 +1025,13 @@
         assertTrue(mWifiLockManager.forceLowLatencyMode(true));
         assertEquals(WifiManager.WIFI_MODE_FULL_LOW_LATENCY,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl).setLowLatencyMode(true);
-        inOrder.verify(mClientModeImpl).setPowerSave(false);
+        inOrder.verify(mClientModeManager).setLowLatencyMode(true);
+        inOrder.verify(mClientModeManager).setPowerSave(false);
         assertTrue(mWifiLockManager.forceLowLatencyMode(false));
         assertEquals(WifiManager.WIFI_MODE_FULL_HIGH_PERF,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl).setLowLatencyMode(false);
-        inOrder.verify(mClientModeImpl).setPowerSave(true);
+        inOrder.verify(mClientModeManager).setLowLatencyMode(false);
+        inOrder.verify(mClientModeManager).setPowerSave(true);
         verify(mWifiMetrics).addWifiLockActiveSession(eq(WifiManager.WIFI_MODE_FULL_LOW_LATENCY),
                 anyLong());
     }
@@ -1017,34 +1041,34 @@
      */
     @Test
     public void testForceLowLatencyScreenOff() throws Exception {
-        when(mClientModeImpl.setLowLatencyMode(anyBoolean())).thenReturn(true);
-        when(mClientModeImpl.setPowerSave(anyBoolean())).thenReturn(true);
-        mWifiLockManager.handleScreenStateChanged(false);
-        when(mWifiNative.getSupportedFeatureSet(INTERFACE_NAME))
+        when(mClientModeManager.setLowLatencyMode(anyBoolean())).thenReturn(true);
+        when(mClientModeManager.setPowerSave(anyBoolean())).thenReturn(true);
+        setScreenState(false);
+        when(mClientModeManager.getSupportedFeatures())
                 .thenReturn((long) WifiManager.WIFI_FEATURE_LOW_LATENCY);
-        mWifiLockManager.updateWifiClientConnected(true);
+        mWifiLockManager.updateWifiClientConnected(mClientModeManager, true);
 
-        InOrder inOrder = inOrder(mClientModeImpl);
+        InOrder inOrder = inOrder(mClientModeManager);
 
         assertEquals(WifiManager.WIFI_MODE_NO_LOCKS_HELD,
                 mWifiLockManager.getStrongestLockMode());
         assertTrue(mWifiLockManager.forceLowLatencyMode(true));
         assertEquals(WifiManager.WIFI_MODE_FULL_LOW_LATENCY,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl).setLowLatencyMode(true);
-        inOrder.verify(mClientModeImpl).setPowerSave(false);
+        inOrder.verify(mClientModeManager).setLowLatencyMode(true);
+        inOrder.verify(mClientModeManager).setPowerSave(false);
 
-        mWifiLockManager.handleScreenStateChanged(true);
+        setScreenState(true);
         assertEquals(WifiManager.WIFI_MODE_FULL_LOW_LATENCY,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl, never()).setLowLatencyMode(anyBoolean());
-        inOrder.verify(mClientModeImpl, never()).setPowerSave(anyBoolean());
+        inOrder.verify(mClientModeManager, never()).setLowLatencyMode(anyBoolean());
+        inOrder.verify(mClientModeManager, never()).setPowerSave(anyBoolean());
 
-        mWifiLockManager.handleScreenStateChanged(false);
+        setScreenState(false);
         assertEquals(WifiManager.WIFI_MODE_FULL_LOW_LATENCY,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl, never()).setLowLatencyMode(anyBoolean());
-        inOrder.verify(mClientModeImpl, never()).setPowerSave(anyBoolean());
+        inOrder.verify(mClientModeManager, never()).setLowLatencyMode(anyBoolean());
+        inOrder.verify(mClientModeManager, never()).setPowerSave(anyBoolean());
     }
 
     /**
@@ -1052,39 +1076,39 @@
      */
     @Test
     public void testForceLowLatencyAcqRelLowLatency() throws Exception {
-        when(mClientModeImpl.setLowLatencyMode(anyBoolean())).thenReturn(true);
-        when(mClientModeImpl.setPowerSave(anyBoolean())).thenReturn(true);
-        mWifiLockManager.handleScreenStateChanged(true);
+        when(mClientModeManager.setLowLatencyMode(anyBoolean())).thenReturn(true);
+        when(mClientModeManager.setPowerSave(anyBoolean())).thenReturn(true);
+        setScreenState(true);
         when(mFrameworkFacade.isAppForeground(any(), anyInt())).thenReturn(true);
-        when(mWifiNative.getSupportedFeatureSet(INTERFACE_NAME))
+        when(mClientModeManager.getSupportedFeatures())
                 .thenReturn((long) WifiManager.WIFI_FEATURE_LOW_LATENCY);
 
-        InOrder inOrder = inOrder(mClientModeImpl);
+        InOrder inOrder = inOrder(mClientModeManager);
 
         acquireWifiLockSuccessful(WifiManager.WIFI_MODE_FULL_LOW_LATENCY, "",
                 mBinder, mWorkSource);
         assertEquals(WifiManager.WIFI_MODE_FULL_LOW_LATENCY,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl).setLowLatencyMode(true);
-        inOrder.verify(mClientModeImpl).setPowerSave(false);
+        inOrder.verify(mClientModeManager).setLowLatencyMode(true);
+        inOrder.verify(mClientModeManager).setPowerSave(false);
 
         assertTrue(mWifiLockManager.forceLowLatencyMode(true));
         assertEquals(WifiManager.WIFI_MODE_FULL_LOW_LATENCY,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl, never()).setLowLatencyMode(anyBoolean());
-        inOrder.verify(mClientModeImpl, never()).setPowerSave(anyBoolean());
+        inOrder.verify(mClientModeManager, never()).setLowLatencyMode(anyBoolean());
+        inOrder.verify(mClientModeManager, never()).setPowerSave(anyBoolean());
 
         releaseLowLatencyWifiLockSuccessful(mBinder);
         assertEquals(WifiManager.WIFI_MODE_FULL_LOW_LATENCY,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl, never()).setLowLatencyMode(anyBoolean());
-        inOrder.verify(mClientModeImpl, never()).setPowerSave(anyBoolean());
+        inOrder.verify(mClientModeManager, never()).setLowLatencyMode(anyBoolean());
+        inOrder.verify(mClientModeManager, never()).setPowerSave(anyBoolean());
 
         assertTrue(mWifiLockManager.forceLowLatencyMode(false));
         assertEquals(WifiManager.WIFI_MODE_NO_LOCKS_HELD,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl).setLowLatencyMode(false);
-        inOrder.verify(mClientModeImpl).setPowerSave(true);
+        inOrder.verify(mClientModeManager).setLowLatencyMode(false);
+        inOrder.verify(mClientModeManager).setPowerSave(true);
         verify(mWifiMetrics).addWifiLockActiveSession(eq(WifiManager.WIFI_MODE_FULL_LOW_LATENCY),
                 anyLong());
     }
@@ -1094,25 +1118,25 @@
      */
     @Test
     public void testForceLowLatencyTwice() throws Exception {
-        when(mClientModeImpl.setLowLatencyMode(anyBoolean())).thenReturn(true);
-        when(mClientModeImpl.setPowerSave(anyBoolean())).thenReturn(true);
-        when(mWifiNative.getSupportedFeatureSet(INTERFACE_NAME))
+        when(mClientModeManager.setLowLatencyMode(anyBoolean())).thenReturn(true);
+        when(mClientModeManager.setPowerSave(anyBoolean())).thenReturn(true);
+        when(mClientModeManager.getSupportedFeatures())
                 .thenReturn((long) WifiManager.WIFI_FEATURE_LOW_LATENCY);
-        mWifiLockManager.updateWifiClientConnected(true);
+        mWifiLockManager.updateWifiClientConnected(mClientModeManager, true);
 
-        InOrder inOrder = inOrder(mClientModeImpl);
+        InOrder inOrder = inOrder(mClientModeManager);
 
         assertTrue(mWifiLockManager.forceLowLatencyMode(true));
         assertEquals(WifiManager.WIFI_MODE_FULL_LOW_LATENCY,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl).setLowLatencyMode(true);
-        inOrder.verify(mClientModeImpl).setPowerSave(false);
+        inOrder.verify(mClientModeManager).setLowLatencyMode(true);
+        inOrder.verify(mClientModeManager).setPowerSave(false);
 
         assertTrue(mWifiLockManager.forceLowLatencyMode(true));
         assertEquals(WifiManager.WIFI_MODE_FULL_LOW_LATENCY,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl, never()).setLowLatencyMode(anyBoolean());
-        inOrder.verify(mClientModeImpl, never()).setPowerSave(anyBoolean());
+        inOrder.verify(mClientModeManager, never()).setLowLatencyMode(anyBoolean());
+        inOrder.verify(mClientModeManager, never()).setPowerSave(anyBoolean());
     }
 
     /**
@@ -1120,27 +1144,27 @@
      */
     @Test
     public void testForceHiPerfLowLatency() throws Exception {
-        when(mClientModeImpl.setLowLatencyMode(anyBoolean())).thenReturn(true);
-        when(mClientModeImpl.setPowerSave(anyBoolean())).thenReturn(true);
-        when(mWifiNative.getSupportedFeatureSet(INTERFACE_NAME))
+        when(mClientModeManager.setLowLatencyMode(anyBoolean())).thenReturn(true);
+        when(mClientModeManager.setPowerSave(anyBoolean())).thenReturn(true);
+        when(mClientModeManager.getSupportedFeatures())
                 .thenReturn((long) WifiManager.WIFI_FEATURE_LOW_LATENCY);
-        mWifiLockManager.updateWifiClientConnected(true);
-        InOrder inOrder = inOrder(mClientModeImpl);
+        mWifiLockManager.updateWifiClientConnected(mClientModeManager, true);
+        InOrder inOrder = inOrder(mClientModeManager);
 
         assertTrue(mWifiLockManager.forceHiPerfMode(true));
         assertEquals(WifiManager.WIFI_MODE_FULL_HIGH_PERF,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl).setPowerSave(false);
+        inOrder.verify(mClientModeManager).setPowerSave(false);
 
         assertTrue(mWifiLockManager.forceLowLatencyMode(true));
         assertEquals(WifiManager.WIFI_MODE_FULL_LOW_LATENCY,
                 mWifiLockManager.getStrongestLockMode());
 
-        inOrder.verify(mClientModeImpl).setPowerSave(true);
+        inOrder.verify(mClientModeManager).setPowerSave(true);
         verify(mWifiMetrics).addWifiLockActiveSession(eq(WifiManager.WIFI_MODE_FULL_HIGH_PERF),
                 anyLong());
-        inOrder.verify(mClientModeImpl).setLowLatencyMode(true);
-        inOrder.verify(mClientModeImpl).setPowerSave(false);
+        inOrder.verify(mClientModeManager).setLowLatencyMode(true);
+        inOrder.verify(mClientModeManager).setPowerSave(false);
     }
 
     /**
@@ -1148,28 +1172,28 @@
      */
     @Test
     public void testForceLowLatencyHiPerf() throws Exception {
-        when(mClientModeImpl.setLowLatencyMode(anyBoolean())).thenReturn(true);
-        when(mClientModeImpl.setPowerSave(anyBoolean())).thenReturn(true);
-        when(mWifiNative.getSupportedFeatureSet(INTERFACE_NAME))
+        when(mClientModeManager.setLowLatencyMode(anyBoolean())).thenReturn(true);
+        when(mClientModeManager.setPowerSave(anyBoolean())).thenReturn(true);
+        when(mClientModeManager.getSupportedFeatures())
                 .thenReturn((long) WifiManager.WIFI_FEATURE_LOW_LATENCY);
-        mWifiLockManager.updateWifiClientConnected(true);
+        mWifiLockManager.updateWifiClientConnected(mClientModeManager, true);
 
-        InOrder inOrder = inOrder(mClientModeImpl);
+        InOrder inOrder = inOrder(mClientModeManager);
 
         assertTrue(mWifiLockManager.forceLowLatencyMode(true));
         assertEquals(WifiManager.WIFI_MODE_FULL_LOW_LATENCY,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl).setLowLatencyMode(true);
-        inOrder.verify(mClientModeImpl).setPowerSave(false);
+        inOrder.verify(mClientModeManager).setLowLatencyMode(true);
+        inOrder.verify(mClientModeManager).setPowerSave(false);
 
         assertTrue(mWifiLockManager.forceHiPerfMode(true));
         assertEquals(WifiManager.WIFI_MODE_FULL_HIGH_PERF,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl).setLowLatencyMode(false);
-        inOrder.verify(mClientModeImpl).setPowerSave(true);
+        inOrder.verify(mClientModeManager).setLowLatencyMode(false);
+        inOrder.verify(mClientModeManager).setPowerSave(true);
         verify(mWifiMetrics).addWifiLockActiveSession(eq(WifiManager.WIFI_MODE_FULL_LOW_LATENCY),
                 anyLong());
-        inOrder.verify(mClientModeImpl).setPowerSave(false);
+        inOrder.verify(mClientModeManager).setPowerSave(false);
     }
 
     /**
@@ -1177,11 +1201,11 @@
      */
     @Test
     public void testForceLowLatencyFailure() throws Exception {
-        when(mClientModeImpl.setLowLatencyMode(anyBoolean())).thenReturn(false);
-        when(mWifiNative.getSupportedFeatureSet(INTERFACE_NAME))
+        when(mClientModeManager.setLowLatencyMode(anyBoolean())).thenReturn(false);
+        when(mClientModeManager.getSupportedFeatures())
                 .thenReturn((long) WifiManager.WIFI_FEATURE_LOW_LATENCY);
 
-        InOrder inOrder = inOrder(mClientModeImpl);
+        InOrder inOrder = inOrder(mClientModeManager);
 
         acquireWifiLockSuccessful(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "",
                 mBinder, mWorkSource);
@@ -1191,9 +1215,9 @@
         assertFalse(mWifiLockManager.forceLowLatencyMode(true));
         assertEquals(WifiManager.WIFI_MODE_FULL_HIGH_PERF,
                 mWifiLockManager.getStrongestLockMode());
-        inOrder.verify(mClientModeImpl).setLowLatencyMode(true);
+        inOrder.verify(mClientModeManager).setLowLatencyMode(true);
         // Since setLowLatencyMode() failed, no call to setPowerSave()
-        inOrder.verify(mClientModeImpl, never()).setPowerSave(anyBoolean());
+        inOrder.verify(mClientModeManager, never()).setPowerSave(anyBoolean());
     }
 
     /**
@@ -1216,7 +1240,7 @@
     public void testAcquireLockWhileDisconnectedConnect() throws Exception {
         assertTrue(mWifiLockManager.acquireWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "",
                 mBinder, mWorkSource));
-        mWifiLockManager.updateWifiClientConnected(true);
+        mWifiLockManager.updateWifiClientConnected(mClientModeManager, true);
 
         assertEquals(WifiManager.WIFI_MODE_FULL_HIGH_PERF, mWifiLockManager.getStrongestLockMode());
         verify(mBatteryStats).reportFullWifiLockAcquiredFromSource(eq(mWorkSource));
@@ -1229,17 +1253,68 @@
     @Test
     public void testAcquireLockWhileConnectedDisconnect() throws Exception {
         // Set screen on, and app foreground
-        mWifiLockManager.handleScreenStateChanged(true);
+        setScreenState(true);
         when(mFrameworkFacade.isAppForeground(any(), anyInt())).thenReturn(true);
 
         acquireWifiLockSuccessful(WifiManager.WIFI_MODE_FULL_LOW_LATENCY, "", mBinder, mWorkSource);
-        mWifiLockManager.updateWifiClientConnected(false);
+        mWifiLockManager.updateWifiClientConnected(mClientModeManager, false);
 
         assertEquals(WifiManager.WIFI_MODE_NO_LOCKS_HELD, mWifiLockManager.getStrongestLockMode());
         verify(mBatteryStats).reportFullWifiLockReleasedFromSource(mWorkSource);
     }
 
     /**
+     * Test acquiring locks while device is connected, then disconnecting from the AP
+     * Expected: Upon disconnection, lock becomes ineffective
+     */
+    @Test
+    public void testSwitchPrimaryClientModeManagerWhileConnected() throws Exception {
+        // Set screen on, and app foreground
+        setScreenState(true);
+        when(mFrameworkFacade.isAppForeground(any(), anyInt())).thenReturn(true);
+
+        // all operations succeed
+        when(mClientModeManager.setLowLatencyMode(anyBoolean())).thenReturn(true);
+        when(mClientModeManager.setPowerSave(anyBoolean())).thenReturn(true);
+        when(mClientModeManager.getSupportedFeatures())
+                .thenReturn(WifiManager.WIFI_FEATURE_LOW_LATENCY);
+        when(mClientModeManager2.setLowLatencyMode(anyBoolean())).thenReturn(true);
+        when(mClientModeManager2.setPowerSave(anyBoolean())).thenReturn(true);
+        when(mClientModeManager2.getSupportedFeatures())
+                .thenReturn(WifiManager.WIFI_FEATURE_LOW_LATENCY);
+
+        // CMM1 is created
+        mPrimaryChangedCallbackCaptor.getValue().onChange(null, mClientModeManager);
+        // acquire low latency lock
+        acquireWifiLockSuccessful(WifiManager.WIFI_MODE_FULL_LOW_LATENCY, "", mBinder, mWorkSource);
+        // verify CMM1 enabled low latency mode
+        verify(mClientModeManager).setLowLatencyMode(true);
+
+        // CMM2 is connected
+        when(mClientModeManager2.isConnected()).thenReturn(true);
+        mWifiLockManager.updateWifiClientConnected(mClientModeManager2, true);
+        // verify CMM1 didn't disable low latency mode
+        verify(mClientModeManager, never()).setLowLatencyMode(false);
+        // verify CMM2 didn't enable low latency mode
+        verify(mClientModeManager2, never()).setLowLatencyMode(anyBoolean());
+
+        // CMM1 becomes secondary transient
+        when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_TRANSIENT);
+        mPrimaryChangedCallbackCaptor.getValue().onChange(mClientModeManager, null);
+
+        // CMM1 low latency disabled
+        verify(mClientModeManager).setLowLatencyMode(false);
+
+        // CMM2 becomes primary
+        when(mClientModeManager2.getRole()).thenReturn(ROLE_CLIENT_PRIMARY);
+        when(mActiveModeWarden.getPrimaryClientModeManager()).thenReturn(mClientModeManager2);
+        mPrimaryChangedCallbackCaptor.getValue().onChange(null, mClientModeManager2);
+
+        // CMM2 low latency enabled
+        verify(mClientModeManager2).setLowLatencyMode(true);
+    }
+
+    /**
      * Test that reporting of metrics for hi-perf lock acquistion is correct for both acquisition
      * time and active time.
      */
@@ -1251,7 +1326,7 @@
         long releaseTime      = 4000;
 
         // Make sure setPowerSave() is successful
-        when(mClientModeImpl.setPowerSave(anyBoolean())).thenReturn(true);
+        when(mClientModeManager.setPowerSave(anyBoolean())).thenReturn(true);
 
         InOrder inOrder = inOrder(mWifiMetrics);
 
@@ -1262,11 +1337,11 @@
 
         // Activate the lock
         when(mClock.getElapsedSinceBootMillis()).thenReturn(activationTime);
-        mWifiLockManager.updateWifiClientConnected(true);
+        mWifiLockManager.updateWifiClientConnected(mClientModeManager, true);
 
         // Deactivate the lock
         when(mClock.getElapsedSinceBootMillis()).thenReturn(deactivationTime);
-        mWifiLockManager.updateWifiClientConnected(false);
+        mWifiLockManager.updateWifiClientConnected(mClientModeManager, false);
 
         inOrder.verify(mWifiMetrics).addWifiLockActiveSession(
                 eq(WifiManager.WIFI_MODE_FULL_HIGH_PERF),
@@ -1293,13 +1368,13 @@
         long releaseTime      = 4000;
 
         // Make sure setLowLatencyMode()/setPowerSave() is successful
-        when(mClientModeImpl.setLowLatencyMode(anyBoolean())).thenReturn(true);
-        when(mClientModeImpl.setPowerSave(anyBoolean())).thenReturn(true);
+        when(mClientModeManager.setLowLatencyMode(anyBoolean())).thenReturn(true);
+        when(mClientModeManager.setPowerSave(anyBoolean())).thenReturn(true);
 
         // Set condition for activation of low-latency (except connection to AP)
-        mWifiLockManager.handleScreenStateChanged(true);
+        setScreenState(true);
         when(mFrameworkFacade.isAppForeground(any(), anyInt())).thenReturn(true);
-        when(mWifiNative.getSupportedFeatureSet(INTERFACE_NAME))
+        when(mClientModeManager.getSupportedFeatures())
                 .thenReturn((long) WifiManager.WIFI_FEATURE_LOW_LATENCY);
 
         InOrder inOrder = inOrder(mWifiMetrics);
@@ -1311,11 +1386,11 @@
 
         // Activate the lock
         when(mClock.getElapsedSinceBootMillis()).thenReturn(activationTime);
-        mWifiLockManager.updateWifiClientConnected(true);
+        mWifiLockManager.updateWifiClientConnected(mClientModeManager, true);
 
         // Deactivate the lock
         when(mClock.getElapsedSinceBootMillis()).thenReturn(deactivationTime);
-        mWifiLockManager.updateWifiClientConnected(false);
+        mWifiLockManager.updateWifiClientConnected(mClientModeManager, false);
 
         inOrder.verify(mWifiMetrics).addWifiLockActiveSession(
                 eq(WifiManager.WIFI_MODE_FULL_LOW_LATENCY),
@@ -1391,4 +1466,11 @@
         assertEquals(WifiManager.WIFI_MODE_NO_LOCKS_HELD,
                 mWifiLockManager.getStrongestLockMode());
     }
+
+    private void setScreenState(boolean screenOn) {
+        BroadcastReceiver broadcastReceiver = mBroadcastReceiverCaptor.getValue();
+        assertNotNull(broadcastReceiver);
+        Intent intent = new Intent(screenOn  ? ACTION_SCREEN_ON : ACTION_SCREEN_OFF);
+        broadcastReceiver.onReceive(mContext, intent);
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
index 5439279..abb08fa 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
@@ -15,6 +15,8 @@
  */
 package com.android.server.wifi;
 
+import static android.content.Intent.ACTION_SCREEN_OFF;
+import static android.content.Intent.ACTION_SCREEN_ON;
 import static android.net.wifi.WifiManager.DEVICE_MOBILITY_STATE_HIGH_MVMT;
 import static android.net.wifi.WifiManager.DEVICE_MOBILITY_STATE_LOW_MVMT;
 import static android.net.wifi.WifiManager.DEVICE_MOBILITY_STATE_STATIONARY;
@@ -45,19 +47,27 @@
 import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import static java.lang.StrictMath.toIntExact;
+
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
 import android.net.wifi.EAPConstants;
 import android.net.wifi.IOnWifiUsabilityStatsListener;
 import android.net.wifi.ScanResult;
 import android.net.wifi.SoftApCapability;
 import android.net.wifi.SoftApConfiguration;
+import android.net.wifi.SoftApInfo;
 import android.net.wifi.SupplicantState;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
@@ -71,6 +81,8 @@
 import android.net.wifi.nl80211.WifiNl80211Manager;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Message;
+import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.test.TestLooper;
 import android.telephony.TelephonyManager;
@@ -82,6 +94,9 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.server.wifi.WifiLinkLayerStats.PeerInfo;
+import com.android.server.wifi.WifiLinkLayerStats.RadioStat;
+import com.android.server.wifi.WifiLinkLayerStats.RateStat;
 import com.android.server.wifi.aware.WifiAwareMetrics;
 import com.android.server.wifi.hotspot2.NetworkDetail;
 import com.android.server.wifi.hotspot2.PasspointManager;
@@ -104,6 +119,8 @@
 import com.android.server.wifi.proto.nano.WifiMetricsProto.PasspointProfileTypeCount;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.PasspointProvisionStats;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.PnoScanMetrics;
+import com.android.server.wifi.proto.nano.WifiMetricsProto.RadioStats;
+import com.android.server.wifi.proto.nano.WifiMetricsProto.RateStats;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.SoftApConnectedClientsEvent;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.StaEvent;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.WifiIsUnusableEvent;
@@ -111,13 +128,14 @@
 import com.android.server.wifi.proto.nano.WifiMetricsProto.WifiUsabilityStats;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.WifiUsabilityStatsEntry;
 import com.android.server.wifi.rtt.RttMetrics;
-import com.android.server.wifi.util.ExternalCallbackTracker;
 import com.android.server.wifi.util.InformationElementUtil;
 import com.android.wifi.resources.R;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.MockitoSession;
@@ -127,6 +145,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.BitSet;
@@ -149,8 +168,9 @@
     WifiMetricsProto.WifiLog mDecodedProto;
     TestLooper mTestLooper;
     Random mRandom = new Random();
-    private static final int TEST_WIFI_USABILITY_STATS_LISTENER_IDENTIFIER = 2;
     private static final int TEST_NETWORK_ID = 42;
+    public static final String TEST_IFACE_NAME = "wlan0";
+    public static final String TEST_IFACE_NAME2 = "wlan1";
     private MockitoSession mSession;
     @Mock Context mContext;
     MockResources mResources;
@@ -158,21 +178,28 @@
     @Mock Clock mClock;
     @Mock ScoringParams mScoringParams;
     @Mock WifiConfigManager mWcm;
-    @Mock BssidBlocklistMonitor mBssidBlocklistMonitor;
+    @Mock WifiBlocklistMonitor mWifiBlocklistMonitor;
     @Mock PasspointManager mPpm;
     @Mock WifiNetworkSelector mWns;
     @Mock WifiPowerMetrics mWifiPowerMetrics;
     @Mock WifiDataStall mWifiDataStall;
+    @Mock WifiChannelUtilization mWifiChannelUtilization;
+    @Mock WifiSettingsStore mWifiSettingsStore;
     @Mock WifiHealthMonitor mWifiHealthMonitor;
     @Mock IBinder mAppBinder;
     @Mock IOnWifiUsabilityStatsListener mOnWifiUsabilityStatsListener;
-    @Mock ExternalCallbackTracker<IOnWifiUsabilityStatsListener> mListenerTracker;
     @Mock WifiP2pMetrics mWifiP2pMetrics;
     @Mock DppMetrics mDppMetrics;
     @Mock WifiScoreCard mWifiScoreCard;
     @Mock WifiScoreCard.PerNetwork mPerNetwork;
     @Mock WifiScoreCard.NetworkConnectionStats mNetworkConnectionStats;
-    @Mock WifiConfiguration mWifiConfig;
+    @Mock PowerManager mPowerManager;
+    @Mock WifiMonitor mWifiMonitor;
+    @Mock ActiveModeWarden mActiveModeWarden;
+
+    @Captor ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor;
+    @Captor ArgumentCaptor<ActiveModeWarden.ModeChangeCallback> mModeChangeCallbackArgumentCaptor;
+    @Captor ArgumentCaptor<Handler> mHandlerCaptor;
 
     @Before
     public void setUp() throws Exception {
@@ -182,19 +209,50 @@
         mTestLooper = new TestLooper();
         mResources = new MockResources();
         when(mContext.getResources()).thenReturn(mResources);
+        when(mContext.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
+        when(mPowerManager.isInteractive()).thenReturn(true);
         mWifiMetrics = new WifiMetrics(mContext, mFacade, mClock, mTestLooper.getLooper(),
                 new WifiAwareMetrics(mClock), new RttMetrics(mClock), mWifiPowerMetrics,
-                mWifiP2pMetrics, mDppMetrics);
+                mWifiP2pMetrics, mDppMetrics, mWifiMonitor);
         mWifiMetrics.setWifiConfigManager(mWcm);
-        mWifiMetrics.setBssidBlocklistMonitor(mBssidBlocklistMonitor);
+        mWifiMetrics.setWifiBlocklistMonitor(mWifiBlocklistMonitor);
         mWifiMetrics.setPasspointManager(mPpm);
         mWifiMetrics.setScoringParams(mScoringParams);
         mWifiMetrics.setWifiNetworkSelector(mWns);
         mWifiMetrics.setWifiDataStall(mWifiDataStall);
+        mWifiMetrics.setWifiChannelUtilization(mWifiChannelUtilization);
+        mWifiMetrics.setWifiSettingsStore(mWifiSettingsStore);
         mWifiMetrics.setWifiHealthMonitor(mWifiHealthMonitor);
         mWifiMetrics.setWifiScoreCard(mWifiScoreCard);
+        when(mOnWifiUsabilityStatsListener.asBinder()).thenReturn(mAppBinder);
         when(mWifiScoreCard.lookupNetwork(anyString())).thenReturn(mPerNetwork);
         when(mPerNetwork.getRecentStats()).thenReturn(mNetworkConnectionStats);
+        verify(mContext, atLeastOnce()).registerReceiver(
+                mBroadcastReceiverCaptor.capture(), any(), any(), any());
+
+        mWifiMetrics.registerForWifiMonitorEvents("wlan0");
+        verify(mWifiMonitor, atLeastOnce())
+                .registerHandler(eq("wlan0"), anyInt(), mHandlerCaptor.capture());
+
+        mWifiMetrics.setActiveModeWarden(mActiveModeWarden);
+        verify(mActiveModeWarden).registerModeChangeCallback(
+                mModeChangeCallbackArgumentCaptor.capture());
+        ActiveModeWarden.ModeChangeCallback modeChangeCallback =
+                        mModeChangeCallbackArgumentCaptor.getValue();
+        ConcreteClientModeManager concreteClientModeManager = mock(ConcreteClientModeManager.class);
+        when(concreteClientModeManager.getInterfaceName()).thenReturn(TEST_IFACE_NAME);
+        when(concreteClientModeManager.getRole()).thenReturn(ActiveModeManager.ROLE_CLIENT_PRIMARY);
+        modeChangeCallback.onActiveModeManagerAdded(concreteClientModeManager);
+
+        mSession = ExtendedMockito.mockitoSession()
+                .strictness(Strictness.LENIENT)
+                .mockStatic(WifiStatsLog.class)
+                .startMocking();
+    }
+
+    @After
+    public void tearDown() {
+        mSession.finishMocking();
     }
 
     /**
@@ -204,22 +262,22 @@
     @Test
     public void startAndEndConnectionEventSucceeds() throws Exception {
         //Start and end Connection event
-        mWifiMetrics.startConnectionEvent(null, "RED",
-                WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
-        mWifiMetrics.endConnectionEvent(
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, null,
+                "RED", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
                 WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE,
                 WifiMetricsProto.ConnectionEvent.HLF_DHCP,
-                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
+                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, 0);
         //end Connection event without starting one
-        mWifiMetrics.endConnectionEvent(
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
                 WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE,
                 WifiMetricsProto.ConnectionEvent.HLF_DHCP,
-                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
+                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, 0);
         //start two ConnectionEvents in a row
-        mWifiMetrics.startConnectionEvent(null, "BLUE",
-                WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
-        mWifiMetrics.startConnectionEvent(null, "GREEN",
-                WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, null,
+                "BLUE", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, null,
+                "GREEN", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
     }
 
     private static final long TEST_RECORD_DURATION_SEC = 12 * 60 * 60;
@@ -228,7 +286,7 @@
      * Simulate how dumpsys gets the proto from mWifiMetrics, filter the proto bytes out and
      * deserialize them into mDecodedProto
      */
-    public void dumpProtoAndDeserialize() throws Exception {
+    private void dumpProtoAndDeserialize() throws Exception {
         ByteArrayOutputStream stream = new ByteArrayOutputStream();
         PrintWriter writer = new PrintWriter(stream);
 
@@ -247,7 +305,7 @@
         mDecodedProto = WifiMetricsProto.WifiLog.parseFrom(protoBytes);
     }
 
-    /**
+    /*, LOCAL_GEN, DEAUTH_REASON*
      * Gets the 'clean dump' proto bytes from mWifiMetrics & deserializes it into
      * mDecodedProto
      */
@@ -280,16 +338,16 @@
     /** Verifies that dump() includes correct alert count when there is one alert. */
     @Test
     public void stateDumpAlertCountIsCorrectWithOneAlert() throws Exception {
-        mWifiMetrics.logFirmwareAlert(1);
+        mWifiMetrics.logFirmwareAlert(TEST_IFACE_NAME, 1);
         assertStringContains(getStateDump(), "mWifiLogProto.alertReasonCounts=(1,1)");
     }
 
     /** Verifies that dump() includes correct alert count when there are multiple alerts. */
     @Test
     public void stateDumpAlertCountIsCorrectWithMultipleAlerts() throws Exception {
-        mWifiMetrics.logFirmwareAlert(1);
-        mWifiMetrics.logFirmwareAlert(1);
-        mWifiMetrics.logFirmwareAlert(16);
+        mWifiMetrics.logFirmwareAlert(TEST_IFACE_NAME, 1);
+        mWifiMetrics.logFirmwareAlert(TEST_IFACE_NAME, 1);
+        mWifiMetrics.logFirmwareAlert(TEST_IFACE_NAME, 16);
         assertStringContains(getStateDump(), "mWifiLogProto.alertReasonCounts=(1,2),(16,1)");
     }
 
@@ -355,13 +413,13 @@
     private static final int NUM_LEGACY_PERSONAL_NETWORK_SCAN_RESULTS = 4;
     private static final int NUM_ENHANCED_OPEN_NETWORK_SCAN_RESULTS = 1;
     private static final int NUM_WPA3_PERSONAL_NETWORK_SCAN_RESULTS = 2;
-    private static final int NUM_WPA3_ENTERPRISE_NETWORK_SCAN_RESULTS = 1;
+    private static final int NUM_WPA3_ENTERPRISE_NETWORK_SCAN_RESULTS = 3;
     private static final int NUM_WAPI_PERSONAL_NETWORK_SCAN_RESULTS = 1;
     private static final int NUM_WAPI_ENTERPRISE_NETWORK_SCAN_RESULTS = 2;
     private static final int NUM_HIDDEN_NETWORK_SCAN_RESULTS = 1;
     private static final int NUM_HOTSPOT2_R1_NETWORK_SCAN_RESULTS = 1;
     private static final int NUM_HOTSPOT2_R2_NETWORK_SCAN_RESULTS = 2;
-    private static final int NUM_HOTSPOT2_R3_NETWORK_SCAN_RESULTS = 1;
+    private static final int NUM_HOTSPOT2_R3_NETWORK_SCAN_RESULTS = 2;
     private static final int NUM_LEGACY_ENTERPRISE_NETWORK_SCAN_RESULTS =
             NUM_HOTSPOT2_R1_NETWORK_SCAN_RESULTS + NUM_HOTSPOT2_R2_NETWORK_SCAN_RESULTS
             + NUM_HOTSPOT2_R3_NETWORK_SCAN_RESULTS;
@@ -442,6 +500,7 @@
     private static final int NUM_ENABLE_NETWORK_CALLS = 6;
     private static final long NUM_IP_RENEWAL_FAILURE = 7;
     private static final int NUM_NETWORK_ABNORMAL_ASSOC_REJECTION = 2;
+    private static final int NUM_NETWORK_ABNORMAL_CONNECTION_FAILURE_DISCONNECTION = 5;
     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;
@@ -463,9 +522,16 @@
     private static final String OPEN_NET_NOTIFIER_TAG = OpenNetworkNotifier.TAG;
 
     private static final int NUM_SOFT_AP_EVENT_ENTRIES = 3;
+    private static final int NUM_SOFT_AP_EVENT_ENTRIES_FOR_BRIDGED_AP = 4;
     private static final int NUM_SOFT_AP_ASSOCIATED_STATIONS = 3;
-    private static final int SOFT_AP_CHANNEL_FREQUENCY = 2437;
-    private static final int SOFT_AP_CHANNEL_BANDWIDTH = SoftApConnectedClientsEvent.BANDWIDTH_20;
+    private static final int SOFT_AP_CHANNEL_FREQUENCY_2G = 2437;
+    private static final int SOFT_AP_CHANNEL_BANDWIDTH_2G =
+            SoftApConnectedClientsEvent.BANDWIDTH_20;
+    private static final int SOFT_AP_GENERATION_2G = ScanResult.WIFI_STANDARD_11N;
+    private static final int SOFT_AP_CHANNEL_FREQUENCY_5G = 5180;
+    private static final int SOFT_AP_CHANNEL_BANDWIDTH_5G =
+            SoftApConnectedClientsEvent.BANDWIDTH_80;
+    private static final int SOFT_AP_GENERATION_5G = ScanResult.WIFI_STANDARD_11AC;
     private static final int SOFT_AP_MAX_CLIENT_SETTING = 10;
     private static final int SOFT_AP_MAX_CLIENT_CAPABILITY = 16;
     private static final long SOFT_AP_SHUTDOWN_TIMEOUT_SETTING = 10_000;
@@ -483,20 +549,23 @@
     private static final long NUM_FILS_SUPPORTED_NETWORKS_SCAN_RESULTS = 2;
     private static final long NUM_11AX_NETWORKS_SCAN_RESULTS = 3;
     private static final long NUM_6G_NETWORKS_SCAN_RESULTS = 2;
+    private static final long NUM_6G_PSC_NETWORKS_SCAN_RESULTS = 1;
     private static final long NUM_BSSID_FILTERED_DUE_TO_MBO_ASSOC_DISALLOW_IND = 3;
     private static final long NUM_CONNECT_TO_MBO_SUPPORTED_NETWORKS = 4;
     private static final long NUM_CONNECT_TO_OCE_SUPPORTED_NETWORKS = 3;
+    private static final long NUM_STEERING_REQUEST = 3;
     private static final long NUM_FORCE_SCAN_DUE_TO_STEERING_REQUEST = 2;
     private static final long NUM_MBO_CELLULAR_SWITCH_REQUEST = 3;
     private static final long NUM_STEERING_REQUEST_INCLUDING_MBO_ASSOC_RETRY_DELAY = 3;
     private static final long NUM_CONNECT_REQUEST_WITH_FILS_AKM = 4;
     private static final long NUM_L2_CONNECTION_THROUGH_FILS_AUTHENTICATION = 3;
 
-    public static final int FEATURE_MBO = 1 << 0;
-    public static final int FEATURE_MBO_CELL_DATA_AWARE = 1 << 1;
-    public static final int FEATURE_OCE = 1 << 2;
-    public static final int FEATURE_11AX = 1 << 3;
-    public static final int FEATURE_6G = 1 << 4;
+    private static final int FEATURE_MBO = 1 << 0;
+    private static final int FEATURE_MBO_CELL_DATA_AWARE = 1 << 1;
+    private static final int FEATURE_OCE = 1 << 2;
+    private static final int FEATURE_11AX = 1 << 3;
+    private static final int FEATURE_6G = 1 << 4;
+    private static final int FEATURE_6G_PSC = 1 << 5;
 
     private ScanDetail buildMockScanDetail(boolean hidden, NetworkDetail.HSRelease hSRelease,
             String capabilities, int supportedFeatures) {
@@ -524,6 +593,9 @@
         if ((supportedFeatures & FEATURE_6G) != 0) {
             when(mockScanResult.is6GHz()).thenReturn(true);
         }
+        if ((supportedFeatures & FEATURE_6G_PSC) != 0) {
+            when(mockScanResult.is6GhzPsc()).thenReturn(true);
+        }
         return mockScanDetail;
     }
 
@@ -539,7 +611,7 @@
         when(mWns.isSignalTooWeak(eq(scanResult))).thenReturn(isWeakRssi);
         scanResult.capabilities = isOpen ? "" : "PSK";
         if (isSaved) {
-            when(mWcm.getConfiguredNetworkForScanDetail(eq(mockScanDetail)))
+            when(mWcm.getSavedNetworkForScanDetail(eq(mockScanDetail)))
                     .thenReturn(mock(WifiConfiguration.class));
         }
         if (isProvider) {
@@ -583,22 +655,31 @@
                 FEATURE_MBO | FEATURE_MBO_CELL_DATA_AWARE));
         mockScanDetails.add(buildMockScanDetail(false, null, "[WPA2-OWE-CCMP]",
                 FEATURE_MBO | FEATURE_MBO_CELL_DATA_AWARE | FEATURE_OCE));
-        mockScanDetails.add(buildMockScanDetail(false, null, "[WPA2-EAP-SUITE-B-192]",
-                FEATURE_11AX | FEATURE_6G));
+        mockScanDetails.add(buildMockScanDetail(false, null, "[RSN-SUITE_B_192][MFPR]",
+                FEATURE_11AX | FEATURE_6G | FEATURE_6G_PSC));
+        // WPA3 Enterprise transition network
+        mockScanDetails.add(buildMockScanDetail(false, null,
+                "[RSN-EAP/SHA1+EAP/SHA256-CCMP][MFPC]", 0));
+        // WPA3 Enterprise only network
+        mockScanDetails.add(buildMockScanDetail(false, null,
+                "[RSN-EAP/SHA256-CCMP][MFPR][MFPC]", 0));
         mockScanDetails.add(buildMockScanDetail(false, null, "[WAPI-WAPI-PSK-SMS4-SMS4]", 0));
         mockScanDetails.add(buildMockScanDetail(false, null, "[WAPI-WAPI-CERT-SMS4-SMS4]", 0));
         mockScanDetails.add(buildMockScanDetail(false, null, "[WAPI-WAPI-CERT-SMS4-SMS4]", 0));
         // Number of scans of R2 networks must be equal to NUM_HOTSPOT2_R2_NETWORK_SCAN_RESULTS
         mockScanDetails.add(buildMockScanDetail(false, NetworkDetail.HSRelease.R2,
-                "[WPA-EAP-CCMP+EAP-FILS-SHA256-CCMP]", FEATURE_MBO | FEATURE_OCE));
+                "[WPA-EAP/SHA1-CCMP+EAP-FILS-SHA256-CCMP]", FEATURE_MBO | FEATURE_OCE));
         mockScanDetails.add(buildMockScanDetail(false, NetworkDetail.HSRelease.R2,
-                "[WPA2-EAP+FT/EAP-CCMP+EAP-FILS-SHA256-CCMP]", 0));
+                "[WPA2-EAP/SHA1+FT/EAP-CCMP+EAP-FILS-SHA256-CCMP]", 0));
         // Number of scans of R1 networks must be equal to NUM_HOTSPOT2_R1_NETWORK_SCAN_RESULTS
         mockScanDetails.add(buildMockScanDetail(false, NetworkDetail.HSRelease.R1,
-                "[WPA-EAP-CCMP]", 0));
+                "[WPA-EAP/SHA1-CCMP]", 0));
         // Number of scans of R3 networks must be equal to NUM_HOTSPOT2_R3_NETWORK_SCAN_RESULTS
         mockScanDetails.add(buildMockScanDetail(false, NetworkDetail.HSRelease.R3,
-                "[WPA-EAP-CCMP]", 0));
+                "[WPA-EAP/SHA1-CCMP]", 0));
+        // WPA2 Enterprise network with MFPR and MFPC
+        mockScanDetails.add(buildMockScanDetail(false, NetworkDetail.HSRelease.R3,
+                "[WPA-EAP/SHA1-CCMP][MFPR][MFPC]", 0));
         return mockScanDetails;
     }
 
@@ -774,13 +855,13 @@
         }
 
         // Test alert-reason clamping.
-        mWifiMetrics.logFirmwareAlert(WifiLoggerHal.WIFI_ALERT_REASON_MIN - 1);
-        mWifiMetrics.logFirmwareAlert(WifiLoggerHal.WIFI_ALERT_REASON_MAX + 1);
+        mWifiMetrics.logFirmwareAlert(TEST_IFACE_NAME, WifiLoggerHal.WIFI_ALERT_REASON_MIN - 1);
+        mWifiMetrics.logFirmwareAlert(TEST_IFACE_NAME, WifiLoggerHal.WIFI_ALERT_REASON_MAX + 1);
         // Simple cases for alert reason.
-        mWifiMetrics.logFirmwareAlert(1);
-        mWifiMetrics.logFirmwareAlert(1);
-        mWifiMetrics.logFirmwareAlert(1);
-        mWifiMetrics.logFirmwareAlert(2);
+        mWifiMetrics.logFirmwareAlert(TEST_IFACE_NAME, 1);
+        mWifiMetrics.logFirmwareAlert(TEST_IFACE_NAME, 1);
+        mWifiMetrics.logFirmwareAlert(TEST_IFACE_NAME, 1);
+        mWifiMetrics.logFirmwareAlert(TEST_IFACE_NAME, 2);
         List<ScanDetail> mockScanDetails = buildMockScanDetailList();
         for (int i = 0; i < NUM_SCANS; i++) {
             mWifiMetrics.countScanResults(mockScanDetails);
@@ -800,25 +881,28 @@
         }
         for (int score = 0; score < NUM_WIFI_SCORES_TO_INCREMENT; score++) {
             for (int offset = 0; offset <= score; offset++) {
-                mWifiMetrics.incrementWifiScoreCount(WIFI_SCORE_RANGE_MIN + score);
+                mWifiMetrics.incrementWifiScoreCount(TEST_IFACE_NAME, WIFI_SCORE_RANGE_MIN + score);
             }
         }
         for (int i = 1; i < NUM_OUT_OF_BOUND_ENTRIES; i++) {
-            mWifiMetrics.incrementWifiScoreCount(WIFI_SCORE_RANGE_MIN - i);
+            mWifiMetrics.incrementWifiScoreCount(TEST_IFACE_NAME, WIFI_SCORE_RANGE_MIN - i);
         }
         for (int i = 1; i < NUM_OUT_OF_BOUND_ENTRIES; i++) {
-            mWifiMetrics.incrementWifiScoreCount(WIFI_SCORE_RANGE_MAX + i);
+            mWifiMetrics.incrementWifiScoreCount(TEST_IFACE_NAME, WIFI_SCORE_RANGE_MAX + i);
         }
         for (int score = 0; score < NUM_WIFI_SCORES_TO_INCREMENT; score++) {
             for (int offset = 0; offset <= score; offset++) {
-                mWifiMetrics.incrementWifiUsabilityScoreCount(1, WIFI_SCORE_RANGE_MIN + score, 15);
+                mWifiMetrics.incrementWifiUsabilityScoreCount(
+                        TEST_IFACE_NAME, 1, WIFI_SCORE_RANGE_MIN + score, 15);
             }
         }
         for (int i = 1; i < NUM_OUT_OF_BOUND_ENTRIES; i++) {
-            mWifiMetrics.incrementWifiUsabilityScoreCount(1, WIFI_SCORE_RANGE_MIN - i, 15);
+            mWifiMetrics.incrementWifiUsabilityScoreCount(
+                    TEST_IFACE_NAME, 1, WIFI_SCORE_RANGE_MIN - i, 15);
         }
         for (int i = 1; i < NUM_OUT_OF_BOUND_ENTRIES; i++) {
-            mWifiMetrics.incrementWifiUsabilityScoreCount(1, WIFI_SCORE_RANGE_MAX + i, 15);
+            mWifiMetrics.incrementWifiUsabilityScoreCount(
+                    TEST_IFACE_NAME, 1, WIFI_SCORE_RANGE_MAX + i, 15);
         }
 
         // increment soft ap start return codes
@@ -982,6 +1066,9 @@
         for (int i = 0; i < NUM_BSSID_FILTERED_DUE_TO_MBO_ASSOC_DISALLOW_IND; i++) {
             mWifiMetrics.incrementNetworkSelectionFilteredBssidCountDueToMboAssocDisallowInd();
         }
+        for (int i = 0; i < NUM_STEERING_REQUEST; i++) {
+            mWifiMetrics.incrementSteeringRequestCount();
+        }
         for (int i = 0; i < NUM_FORCE_SCAN_DUE_TO_STEERING_REQUEST; i++) {
             mWifiMetrics.incrementForceScanCountDueToSteeringRequest();
         }
@@ -1012,6 +1099,8 @@
         metrics.failureStatsDecrease = new HealthMonitorFailureStats();
         metrics.failureStatsHigh = new HealthMonitorFailureStats();
         metrics.failureStatsIncrease.cntAssocRejection = NUM_NETWORK_ABNORMAL_ASSOC_REJECTION;
+        metrics.failureStatsDecrease.cntDisconnectionNonlocalConnecting =
+                NUM_NETWORK_ABNORMAL_CONNECTION_FAILURE_DISCONNECTION;
         metrics.numNetworkSufficientRecentStatsOnly = NUM_NETWORK_SUFFICIENT_RECENT_STATS_ONLY;
         metrics.numNetworkSufficientRecentPrevStats = NUM_NETWORK_SUFFICIENT_RECENT_PREV_STATS;
         when(mWifiHealthMonitor.buildProto()).thenReturn(metrics);
@@ -1019,19 +1108,35 @@
     }
 
     private void addSoftApEventsToMetrics() {
-        // Total number of events recorded is NUM_SOFT_AP_EVENT_ENTRIES in both modes
+        SoftApInfo testSoftApInfo_2G = new SoftApInfo();
+        testSoftApInfo_2G.setFrequency(SOFT_AP_CHANNEL_FREQUENCY_2G);
+        testSoftApInfo_2G.setBandwidth(SOFT_AP_CHANNEL_BANDWIDTH_2G);
+        testSoftApInfo_2G.setWifiStandard(SOFT_AP_GENERATION_2G);
+        SoftApInfo testSoftApInfo_5G = new SoftApInfo();
+        testSoftApInfo_5G.setFrequency(SOFT_AP_CHANNEL_FREQUENCY_5G);
+        testSoftApInfo_5G.setBandwidth(SOFT_AP_CHANNEL_BANDWIDTH_5G);
+        testSoftApInfo_5G.setWifiStandard(SOFT_AP_GENERATION_5G);
 
+        // Total number of events recorded is NUM_SOFT_AP_EVENT_ENTRIES in both modes
         mWifiMetrics.addSoftApUpChangedEvent(true, WifiManager.IFACE_IP_MODE_TETHERED,
-                SOFT_AP_SHUTDOWN_TIMEOUT_DEFAULT_SETTING);
+                SOFT_AP_SHUTDOWN_TIMEOUT_DEFAULT_SETTING, false);
         mWifiMetrics.addSoftApNumAssociatedStationsChangedEvent(NUM_SOFT_AP_ASSOCIATED_STATIONS,
-                WifiManager.IFACE_IP_MODE_TETHERED);
+                NUM_SOFT_AP_ASSOCIATED_STATIONS, WifiManager.IFACE_IP_MODE_TETHERED,
+                testSoftApInfo_2G);
+
+        // Should be dropped.
         mWifiMetrics.addSoftApNumAssociatedStationsChangedEvent(NUM_SOFT_AP_ASSOCIATED_STATIONS,
-                WifiManager.IFACE_IP_MODE_UNSPECIFIED);  // Should be dropped.
+                NUM_SOFT_AP_ASSOCIATED_STATIONS, WifiManager.IFACE_IP_MODE_UNSPECIFIED,
+                testSoftApInfo_2G);
+
         mWifiMetrics.addSoftApUpChangedEvent(false, WifiManager.IFACE_IP_MODE_TETHERED,
-                SOFT_AP_SHUTDOWN_TIMEOUT_DEFAULT_SETTING);
+                SOFT_AP_SHUTDOWN_TIMEOUT_DEFAULT_SETTING, false);
+
+
+
         // Channel switch info should be added to the last Soft AP UP event in the list
-        mWifiMetrics.addSoftApChannelSwitchedEvent(SOFT_AP_CHANNEL_FREQUENCY,
-                SOFT_AP_CHANNEL_BANDWIDTH, WifiManager.IFACE_IP_MODE_TETHERED);
+        mWifiMetrics.addSoftApChannelSwitchedEvent(new ArrayList<>() {{ add(testSoftApInfo_2G); }},
+                WifiManager.IFACE_IP_MODE_TETHERED, false);
         SoftApConfiguration testSoftApConfig = new SoftApConfiguration.Builder()
                 .setSsid("Test_Metric_SSID")
                 .setMaxNumberOfClients(SOFT_AP_MAX_CLIENT_SETTING)
@@ -1039,32 +1144,54 @@
                 .setClientControlByUserEnabled(SOFT_AP_CLIENT_CONTROL_ENABLE)
                 .build();
         mWifiMetrics.updateSoftApConfiguration(testSoftApConfig,
-                WifiManager.IFACE_IP_MODE_TETHERED);
+                WifiManager.IFACE_IP_MODE_TETHERED, false);
         SoftApCapability testSoftApCapability = new SoftApCapability(0);
         testSoftApCapability.setMaxSupportedClients(SOFT_AP_MAX_CLIENT_CAPABILITY);
         mWifiMetrics.updateSoftApCapability(testSoftApCapability,
-                WifiManager.IFACE_IP_MODE_TETHERED);
+                WifiManager.IFACE_IP_MODE_TETHERED, false);
 
         mWifiMetrics.addSoftApUpChangedEvent(true, WifiManager.IFACE_IP_MODE_LOCAL_ONLY,
-                SOFT_AP_SHUTDOWN_TIMEOUT_DEFAULT_SETTING);
+                SOFT_AP_SHUTDOWN_TIMEOUT_DEFAULT_SETTING, false);
         mWifiMetrics.addSoftApNumAssociatedStationsChangedEvent(NUM_SOFT_AP_ASSOCIATED_STATIONS,
-                WifiManager.IFACE_IP_MODE_LOCAL_ONLY);
+                NUM_SOFT_AP_ASSOCIATED_STATIONS, WifiManager.IFACE_IP_MODE_LOCAL_ONLY,
+                testSoftApInfo_2G);
+
         // Should be dropped.
         mWifiMetrics.addSoftApUpChangedEvent(false, WifiManager.IFACE_IP_MODE_CONFIGURATION_ERROR,
-                SOFT_AP_SHUTDOWN_TIMEOUT_DEFAULT_SETTING);
+                SOFT_AP_SHUTDOWN_TIMEOUT_DEFAULT_SETTING, false);
         mWifiMetrics.addSoftApUpChangedEvent(false, WifiManager.IFACE_IP_MODE_LOCAL_ONLY,
-                SOFT_AP_SHUTDOWN_TIMEOUT_DEFAULT_SETTING);
+                SOFT_AP_SHUTDOWN_TIMEOUT_DEFAULT_SETTING, false);
+
+        // Bridged mode test, total NUM_SOFT_AP_EVENT_ENTRIES_FOR_BRIDGED_AP events for bridged mode
+        mWifiMetrics.addSoftApUpChangedEvent(true, WifiManager.IFACE_IP_MODE_TETHERED,
+                SOFT_AP_SHUTDOWN_TIMEOUT_DEFAULT_SETTING, true);
+        mWifiMetrics.addSoftApChannelSwitchedEvent(new ArrayList<>() {{
+                    add(testSoftApInfo_2G);
+                    add(testSoftApInfo_5G);
+                }},
+                WifiManager.IFACE_IP_MODE_TETHERED, true);
+
+        mWifiMetrics.updateSoftApConfiguration(testSoftApConfig,
+                WifiManager.IFACE_IP_MODE_TETHERED, true);
+        mWifiMetrics.updateSoftApCapability(testSoftApCapability,
+                WifiManager.IFACE_IP_MODE_TETHERED, true);
+
+        mWifiMetrics.addSoftApInstanceDownEventInDualMode(WifiManager.IFACE_IP_MODE_TETHERED,
+                testSoftApInfo_5G);
+        mWifiMetrics.addSoftApUpChangedEvent(false, WifiManager.IFACE_IP_MODE_TETHERED,
+                SOFT_AP_SHUTDOWN_TIMEOUT_DEFAULT_SETTING, true);
     }
 
     private void verifySoftApEventsStoredInProto() {
-        assertEquals(NUM_SOFT_AP_EVENT_ENTRIES,
+        // Tethered mode includes single AP and dual AP test.
+        assertEquals(NUM_SOFT_AP_EVENT_ENTRIES + NUM_SOFT_AP_EVENT_ENTRIES_FOR_BRIDGED_AP,
                 mDecodedProto.softApConnectedClientsEventsTethered.length);
         assertEquals(SoftApConnectedClientsEvent.SOFT_AP_UP,
                 mDecodedProto.softApConnectedClientsEventsTethered[0].eventType);
         assertEquals(0, mDecodedProto.softApConnectedClientsEventsTethered[0].numConnectedClients);
-        assertEquals(SOFT_AP_CHANNEL_FREQUENCY,
+        assertEquals(SOFT_AP_CHANNEL_FREQUENCY_2G,
                 mDecodedProto.softApConnectedClientsEventsTethered[0].channelFrequency);
-        assertEquals(SOFT_AP_CHANNEL_BANDWIDTH,
+        assertEquals(SOFT_AP_CHANNEL_BANDWIDTH_2G,
                 mDecodedProto.softApConnectedClientsEventsTethered[0].channelBandwidth);
         assertEquals(SOFT_AP_MAX_CLIENT_SETTING,
                 mDecodedProto.softApConnectedClientsEventsTethered[0]
@@ -1089,6 +1216,56 @@
                 mDecodedProto.softApConnectedClientsEventsTethered[2].eventType);
         assertEquals(0, mDecodedProto.softApConnectedClientsEventsTethered[2].numConnectedClients);
 
+        // Verify the bridged AP metrics
+        assertEquals(SoftApConnectedClientsEvent.DUAL_AP_BOTH_INSTANCES_UP,
+                mDecodedProto.softApConnectedClientsEventsTethered[3].eventType);
+        assertEquals(0, mDecodedProto.softApConnectedClientsEventsTethered[3].numConnectedClients);
+        assertEquals(SOFT_AP_CHANNEL_FREQUENCY_2G,
+                mDecodedProto.softApConnectedClientsEventsTethered[3].channelFrequency);
+        assertEquals(SOFT_AP_CHANNEL_BANDWIDTH_2G,
+                mDecodedProto.softApConnectedClientsEventsTethered[3].channelBandwidth);
+        assertEquals(SOFT_AP_MAX_CLIENT_SETTING,
+                mDecodedProto.softApConnectedClientsEventsTethered[3]
+                .maxNumClientsSettingInSoftapConfiguration);
+        assertEquals(SOFT_AP_MAX_CLIENT_CAPABILITY,
+                mDecodedProto.softApConnectedClientsEventsTethered[3]
+                .maxNumClientsSettingInSoftapCapability);
+        assertEquals(SOFT_AP_SHUTDOWN_TIMEOUT_SETTING,
+                mDecodedProto.softApConnectedClientsEventsTethered[3]
+                .shutdownTimeoutSettingInSoftapConfiguration);
+        assertEquals(SOFT_AP_SHUTDOWN_TIMEOUT_DEFAULT_SETTING,
+                mDecodedProto.softApConnectedClientsEventsTethered[3]
+                .defaultShutdownTimeoutSetting);
+        assertEquals(SOFT_AP_CLIENT_CONTROL_ENABLE,
+                mDecodedProto.softApConnectedClientsEventsTethered[3].clientControlIsEnabled);
+        assertEquals(SoftApConnectedClientsEvent.DUAL_AP_BOTH_INSTANCES_UP,
+                mDecodedProto.softApConnectedClientsEventsTethered[4].eventType);
+        assertEquals(0, mDecodedProto.softApConnectedClientsEventsTethered[4].numConnectedClients);
+        assertEquals(SOFT_AP_CHANNEL_FREQUENCY_5G,
+                mDecodedProto.softApConnectedClientsEventsTethered[4].channelFrequency);
+        assertEquals(SOFT_AP_CHANNEL_BANDWIDTH_5G,
+                mDecodedProto.softApConnectedClientsEventsTethered[4].channelBandwidth);
+        assertEquals(SOFT_AP_MAX_CLIENT_SETTING,
+                mDecodedProto.softApConnectedClientsEventsTethered[4]
+                .maxNumClientsSettingInSoftapConfiguration);
+        assertEquals(SOFT_AP_MAX_CLIENT_CAPABILITY,
+                mDecodedProto.softApConnectedClientsEventsTethered[4]
+                .maxNumClientsSettingInSoftapCapability);
+        assertEquals(SOFT_AP_SHUTDOWN_TIMEOUT_SETTING,
+                mDecodedProto.softApConnectedClientsEventsTethered[4]
+                .shutdownTimeoutSettingInSoftapConfiguration);
+        assertEquals(SOFT_AP_SHUTDOWN_TIMEOUT_DEFAULT_SETTING,
+                mDecodedProto.softApConnectedClientsEventsTethered[4]
+                .defaultShutdownTimeoutSetting);
+        assertEquals(SOFT_AP_CLIENT_CONTROL_ENABLE,
+                mDecodedProto.softApConnectedClientsEventsTethered[4].clientControlIsEnabled);
+        assertEquals(SoftApConnectedClientsEvent.DUAL_AP_ONE_INSTANCE_DOWN,
+                mDecodedProto.softApConnectedClientsEventsTethered[5].eventType);
+        assertEquals(0, mDecodedProto.softApConnectedClientsEventsTethered[5].numConnectedClients);
+        assertEquals(SoftApConnectedClientsEvent.SOFT_AP_DOWN,
+                mDecodedProto.softApConnectedClientsEventsTethered[6].eventType);
+        assertEquals(0, mDecodedProto.softApConnectedClientsEventsTethered[6].numConnectedClients);
+
         assertEquals(SoftApConnectedClientsEvent.SOFT_AP_UP,
                 mDecodedProto.softApConnectedClientsEventsLocalOnly[0].eventType);
         assertEquals(0, mDecodedProto.softApConnectedClientsEventsLocalOnly[0].numConnectedClients);
@@ -1104,7 +1281,7 @@
     /**
      * Assert that values in deserializedWifiMetrics match those set in 'setAndIncrementMetrics'
      */
-    public void assertDeserializedMetricsCorrect() throws Exception {
+    private void assertDeserializedMetricsCorrect() throws Exception {
         assertEquals("mDecodedProto.numSavedNetworks == NUM_SAVED_NETWORKS",
                 NUM_SAVED_NETWORKS, mDecodedProto.numSavedNetworks);
         assertEquals("mDecodedProto.numSavedNetworksWithMacRandomization == NUM_SAVED_NETWORKS-1",
@@ -1245,6 +1422,8 @@
                 mDecodedProto.num11AxNetworkScanResults);
         assertEquals(NUM_6G_NETWORKS_SCAN_RESULTS * NUM_SCANS,
                 mDecodedProto.num6GNetworkScanResults);
+        assertEquals(NUM_6G_PSC_NETWORKS_SCAN_RESULTS * NUM_SCANS,
+                mDecodedProto.num6GPscNetworkScanResults);
         assertEquals(NUM_SCANS,
                 mDecodedProto.numScans);
         assertEquals(NUM_CONNECTIVITY_ONESHOT_SCAN_EVENT,
@@ -1368,7 +1547,7 @@
         }
 
         assertEquals(SIZE_OPEN_NETWORK_RECOMMENDER_BLOCKLIST,
-                mDecodedProto.openNetworkRecommenderBlacklistSize);
+                mDecodedProto.openNetworkRecommenderBlocklistSize);
         assertEquals(IS_WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
                 mDecodedProto.isWifiNetworksAvailableNotificationOn);
         assertEquals(NUM_OPEN_NETWORK_RECOMMENDATION_UPDATES,
@@ -1400,6 +1579,9 @@
         assertEquals(NUM_IP_RENEWAL_FAILURE, mDecodedProto.numIpRenewalFailure);
         assertEquals(NUM_NETWORK_ABNORMAL_ASSOC_REJECTION,
                 mDecodedProto.healthMonitorMetrics.failureStatsIncrease.cntAssocRejection);
+        assertEquals(NUM_NETWORK_ABNORMAL_CONNECTION_FAILURE_DISCONNECTION,
+                mDecodedProto.healthMonitorMetrics.failureStatsDecrease
+                        .cntDisconnectionNonlocalConnecting);
         assertEquals(0,
                 mDecodedProto.healthMonitorMetrics.failureStatsIncrease.cntAssocTimeout);
         assertEquals(NUM_NETWORK_SUFFICIENT_RECENT_STATS_ONLY,
@@ -1408,6 +1590,8 @@
                 mDecodedProto.healthMonitorMetrics.numNetworkSufficientRecentPrevStats);
         assertEquals(NUM_BSSID_FILTERED_DUE_TO_MBO_ASSOC_DISALLOW_IND,
                 mDecodedProto.numBssidFilteredDueToMboAssocDisallowInd);
+        assertEquals(NUM_STEERING_REQUEST,
+                mDecodedProto.numSteeringRequest);
         assertEquals(NUM_FORCE_SCAN_DUE_TO_STEERING_REQUEST,
                 mDecodedProto.numForceScanDueToSteeringRequest);
         assertEquals(NUM_MBO_CELLULAR_SWITCH_REQUEST,
@@ -1507,20 +1691,26 @@
         int upper = WifiMetrics.LOW_WIFI_SCORE + 7;
         int mid = WifiMetrics.LOW_WIFI_SCORE;
         int lower = WifiMetrics.LOW_WIFI_SCORE - 8;
-        mWifiMetrics.setWifiState(WifiMetricsProto.WifiLog.WIFI_ASSOCIATED);
-        for (int score = upper; score >= mid; score--) mWifiMetrics.incrementWifiScoreCount(score);
-        mWifiMetrics.incrementWifiScoreCount(mid + 1);
-        mWifiMetrics.incrementWifiScoreCount(lower); // First breach
-        for (int score = lower; score <= mid; score++) mWifiMetrics.incrementWifiScoreCount(score);
-        mWifiMetrics.incrementWifiScoreCount(mid - 1);
-        mWifiMetrics.incrementWifiScoreCount(upper); // Second breach
+        mWifiMetrics.setWifiState(TEST_IFACE_NAME, WifiMetricsProto.WifiLog.WIFI_ASSOCIATED);
+        for (int score = upper; score >= mid; score--) {
+            mWifiMetrics.incrementWifiScoreCount(TEST_IFACE_NAME, score);
+        }
+        mWifiMetrics.incrementWifiScoreCount(TEST_IFACE_NAME, mid + 1);
+        mWifiMetrics.incrementWifiScoreCount(TEST_IFACE_NAME, lower); // First breach
+        for (int score = lower; score <= mid; score++) {
+            mWifiMetrics.incrementWifiScoreCount(TEST_IFACE_NAME, score);
+        }
+        mWifiMetrics.incrementWifiScoreCount(TEST_IFACE_NAME, mid - 1);
+        mWifiMetrics.incrementWifiScoreCount(TEST_IFACE_NAME, upper); // Second breach
 
         dumpProtoAndDeserialize();
 
         assertEquals(2, mDecodedProto.staEventList.length);
         assertEquals(StaEvent.TYPE_SCORE_BREACH, mDecodedProto.staEventList[0].type);
+        assertEquals(TEST_IFACE_NAME, mDecodedProto.staEventList[0].interfaceName);
         assertEquals(lower, mDecodedProto.staEventList[0].lastScore);
         assertEquals(StaEvent.TYPE_SCORE_BREACH, mDecodedProto.staEventList[1].type);
+        assertEquals(TEST_IFACE_NAME, mDecodedProto.staEventList[1].interfaceName);
         assertEquals(upper, mDecodedProto.staEventList[1].lastScore);
     }
 
@@ -1532,17 +1722,19 @@
         int upper = WifiMetrics.LOW_WIFI_USABILITY_SCORE + 7;
         int mid = WifiMetrics.LOW_WIFI_USABILITY_SCORE;
         int lower = WifiMetrics.LOW_WIFI_USABILITY_SCORE - 8;
-        mWifiMetrics.setWifiState(WifiMetricsProto.WifiLog.WIFI_ASSOCIATED);
+        mWifiMetrics.setWifiState(TEST_IFACE_NAME, WifiMetricsProto.WifiLog.WIFI_ASSOCIATED);
         for (int score = upper; score >= mid; score--) {
-            mWifiMetrics.incrementWifiUsabilityScoreCount(1, score, 15);
+            mWifiMetrics.incrementWifiUsabilityScoreCount(TEST_IFACE_NAME, 1, score, 15);
         }
-        mWifiMetrics.incrementWifiUsabilityScoreCount(1, mid + 1, 15);
-        mWifiMetrics.incrementWifiUsabilityScoreCount(1, lower, 15); // First breach
+        mWifiMetrics.incrementWifiUsabilityScoreCount(TEST_IFACE_NAME, 1, mid + 1, 15);
+        // First breach
+        mWifiMetrics.incrementWifiUsabilityScoreCount(TEST_IFACE_NAME, 1, lower, 15);
         for (int score = lower; score <= mid; score++) {
-            mWifiMetrics.incrementWifiUsabilityScoreCount(1, score, 15);
+            mWifiMetrics.incrementWifiUsabilityScoreCount(TEST_IFACE_NAME, 1, score, 15);
         }
-        mWifiMetrics.incrementWifiUsabilityScoreCount(1, mid - 1, 15);
-        mWifiMetrics.incrementWifiUsabilityScoreCount(1, upper, 15); // Second breach
+        mWifiMetrics.incrementWifiUsabilityScoreCount(TEST_IFACE_NAME, 1, mid - 1, 15);
+        // Second breach
+        mWifiMetrics.incrementWifiUsabilityScoreCount(TEST_IFACE_NAME, 1, upper, 15);
 
         dumpProtoAndDeserialize();
 
@@ -1553,16 +1745,36 @@
         assertEquals(upper, mDecodedProto.staEventList[1].lastWifiUsabilityScore);
     }
 
-    private static final String SSID = "red";
-    private static final int CONFIG_DTIM = 3;
-    private static final int NETWORK_DETAIL_WIFIMODE = 5;
-    private static final int NETWORK_DETAIL_DTIM = 7;
-    private static final int SCAN_RESULT_LEVEL = -30;
     /**
      * Test that WifiMetrics is correctly getting data from ScanDetail and WifiConfiguration
      */
     @Test
     public void testScanDetailAndWifiConfigurationUsage() throws Exception {
+        setupNetworkAndVerify();
+    }
+
+    /**
+     * Test that WifiMetrics is correctly getting data from ScanDetail and WifiConfiguration for
+     * Passpoint use cases.
+     */
+    @Test
+    public void testScanDetailAndWifiConfigurationUsageForPasspoint() throws Exception {
+        setupNetworkAndVerify(true, false);
+        setupNetworkAndVerify(true, true);
+    }
+
+    private static final String SSID = "red";
+    private static final int CONFIG_DTIM = 3;
+    private static final int NETWORK_DETAIL_WIFIMODE = 5;
+    private static final int NETWORK_DETAIL_DTIM = 7;
+    private static final int SCAN_RESULT_LEVEL = -30;
+
+    private void setupNetworkAndVerify() throws Exception {
+        setupNetworkAndVerify(false, false);
+    }
+
+    private void setupNetworkAndVerify(boolean isPasspoint, boolean isPasspointHomeProvider)
+            throws Exception {
         //Setup mock configs and scan details
         NetworkDetail networkDetail = mock(NetworkDetail.class);
         when(networkDetail.getWifiMode()).thenReturn(NETWORK_DETAIL_WIFIMODE);
@@ -1570,11 +1782,11 @@
         when(networkDetail.getDtimInterval()).thenReturn(NETWORK_DETAIL_DTIM);
         ScanResult scanResult = mock(ScanResult.class);
         scanResult.level = SCAN_RESULT_LEVEL;
-        scanResult.capabilities = "EAP";
+        scanResult.capabilities = "EAP/SHA1";
         WifiConfiguration config = mock(WifiConfiguration.class);
         config.SSID = "\"" + SSID + "\"";
         config.dtimInterval = CONFIG_DTIM;
-        config.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_PERSISTENT;
+        config.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_AUTO;
         config.allowedKeyManagement = new BitSet();
         config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
         config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
@@ -1596,25 +1808,30 @@
         mWifiMetrics.setNominatorForNetwork(TEST_NETWORK_ID,
                 WifiMetricsProto.ConnectionEvent.NOMINATOR_MANUAL);
 
+        when(config.isPasspoint()).thenReturn(isPasspoint);
+        config.isHomeProviderNetwork = isPasspointHomeProvider;
+
         //Create a connection event using only the config
-        mWifiMetrics.startConnectionEvent(config, "Red",
-                WifiMetricsProto.ConnectionEvent.ROAM_NONE);
-        mWifiMetrics.endConnectionEvent(
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, config,
+                "Red", WifiMetricsProto.ConnectionEvent.ROAM_NONE);
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
                 WifiMetrics.ConnectionEvent.FAILURE_NONE,
                 WifiMetricsProto.ConnectionEvent.HLF_NONE,
-                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
+                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, 0);
 
         //Change configuration to open without randomization
         config.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_NONE;
         scanResult.capabilities = "";
+
         //Create a connection event using the config and a scan detail
-        mWifiMetrics.startConnectionEvent(config, "Green",
-                WifiMetricsProto.ConnectionEvent.ROAM_NONE);
-        mWifiMetrics.setConnectionScanDetail(scanDetail);
-        mWifiMetrics.endConnectionEvent(
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, config,
+                "Green", WifiMetricsProto.ConnectionEvent.ROAM_NONE);
+        mWifiMetrics.setConnectionScanDetail(TEST_IFACE_NAME, scanDetail);
+        mWifiMetrics.logBugReport();
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
                 WifiMetrics.ConnectionEvent.FAILURE_NONE,
                 WifiMetricsProto.ConnectionEvent.HLF_NONE,
-                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
+                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, 0);
 
         //Dump proto from mWifiMetrics and deserialize it to mDecodedProto
         dumpProtoAndDeserialize();
@@ -1643,12 +1860,17 @@
         assertEquals(SCAN_RESULT_LEVEL, mDecodedProto.connectionEvent[1].signalStrength);
         assertEquals(NETWORK_DETAIL_WIFIMODE,
                 mDecodedProto.connectionEvent[1].routerFingerprint.routerTechnology);
+        assertFalse(mDecodedProto.connectionEvent[0].automaticBugReportTaken);
+        assertTrue(mDecodedProto.connectionEvent[1].automaticBugReportTaken);
         assertTrue(mDecodedProto.connectionEvent[0].useRandomizedMac);
         assertFalse(mDecodedProto.connectionEvent[1].useRandomizedMac);
         assertEquals(WifiMetricsProto.ConnectionEvent.NOMINATOR_MANUAL,
                 mDecodedProto.connectionEvent[0].connectionNominator);
         assertEquals(1, mDecodedProto.numConnectToNetworkSupportingMbo);
         assertEquals(1, mDecodedProto.numConnectToNetworkSupportingOce);
+        assertEquals(isPasspoint, mDecodedProto.connectionEvent[0].routerFingerprint.passpoint);
+        assertEquals(isPasspointHomeProvider,
+                mDecodedProto.connectionEvent[0].routerFingerprint.isPasspointHomeProvider);
     }
 
     /**
@@ -1666,7 +1888,7 @@
         WifiConfiguration config = mock(WifiConfiguration.class);
         config.SSID = "\"" + SSID + "\"";
         config.dtimInterval = CONFIG_DTIM;
-        config.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_PERSISTENT;
+        config.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_AUTO;
         config.allowedKeyManagement = new BitSet();
         WifiConfiguration.NetworkSelectionStatus networkSelectionStat =
                 mock(WifiConfiguration.NetworkSelectionStatus.class);
@@ -1685,12 +1907,12 @@
                 new String[]{WifiMetrics.PROTO_DUMP_ARG});
 
         // Create a connection event using only the config
-        mWifiMetrics.startConnectionEvent(config, "Red",
-                WifiMetricsProto.ConnectionEvent.ROAM_NONE);
-        mWifiMetrics.endConnectionEvent(
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, config,
+                "Red", WifiMetricsProto.ConnectionEvent.ROAM_NONE);
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
                 WifiMetrics.ConnectionEvent.FAILURE_NONE,
                 WifiMetricsProto.ConnectionEvent.HLF_NONE,
-                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
+                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, 0);
 
         dumpProtoAndDeserialize();
 
@@ -1703,12 +1925,12 @@
      */
     @Test
     public void testMetricsAssociationTimedOut() throws Exception {
-        mWifiMetrics.startConnectionEvent(null, "RED",
-                WifiMetricsProto.ConnectionEvent.ROAM_NONE);
-        mWifiMetrics.endConnectionEvent(
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, null,
+                "RED", WifiMetricsProto.ConnectionEvent.ROAM_NONE);
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
                 WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_TIMED_OUT,
                 WifiMetricsProto.ConnectionEvent.HLF_NONE,
-                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
+                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, 0);
 
         //Dump proto and deserialize
         //This should clear all the metrics in mWifiMetrics,
@@ -1731,14 +1953,14 @@
         config.allowedKeyManagement = new BitSet();
         when(config.getNetworkSelectionStatus()).thenReturn(
                 mock(WifiConfiguration.NetworkSelectionStatus.class));
-        when(mBssidBlocklistMonitor.updateAndGetNumBlockedBssidsForSsid(eq(config.SSID)))
+        when(mWifiBlocklistMonitor.updateAndGetNumBlockedBssidsForSsid(eq(config.SSID)))
                 .thenReturn(3);
-        mWifiMetrics.startConnectionEvent(config, "RED",
-                WifiMetricsProto.ConnectionEvent.ROAM_NONE);
-        mWifiMetrics.endConnectionEvent(
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, config,
+                "RED", WifiMetricsProto.ConnectionEvent.ROAM_NONE);
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
                 WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_TIMED_OUT,
                 WifiMetricsProto.ConnectionEvent.HLF_NONE,
-                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
+                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, 0);
         dumpProtoAndDeserialize();
 
         assertEquals(1, mDecodedProto.connectionEvent.length);
@@ -1756,12 +1978,12 @@
         when(config.getNetworkSelectionStatus()).thenReturn(
                 mock(WifiConfiguration.NetworkSelectionStatus.class));
         when(config.isOpenNetwork()).thenReturn(true);
-        mWifiMetrics.startConnectionEvent(config, "RED",
-                WifiMetricsProto.ConnectionEvent.ROAM_NONE);
-        mWifiMetrics.endConnectionEvent(
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, config,
+                "RED", WifiMetricsProto.ConnectionEvent.ROAM_NONE);
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
                 WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_TIMED_OUT,
                 WifiMetricsProto.ConnectionEvent.HLF_NONE,
-                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
+                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, 0);
         dumpProtoAndDeserialize();
 
         assertEquals(1, mDecodedProto.connectionEvent.length);
@@ -1770,24 +1992,187 @@
         assertFalse(mDecodedProto.connectionEvent[0].isOsuProvisioned);
     }
 
+    @Test
+    public void testStart2ConnectionEventsOnDifferentIfaces_endOneAndDump_endOtherAndDump()
+            throws Exception {
+        WifiConfiguration config1 = WifiConfigurationTestUtil.createPskNetwork();
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, config1, "RED",
+                WifiMetricsProto.ConnectionEvent.ROAM_DBDC);
+        WifiConfiguration config2 = WifiConfigurationTestUtil.createOpenNetwork();
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME2, config2, "BLUE",
+                WifiMetricsProto.ConnectionEvent.ROAM_USER_SELECTED);
+
+        mWifiMetrics.setConnectionScanDetail(TEST_IFACE_NAME, mock(ScanDetail.class));
+        mWifiMetrics.setConnectionPmkCache(TEST_IFACE_NAME, false);
+        mWifiMetrics.setConnectionMaxSupportedLinkSpeedMbps(TEST_IFACE_NAME, 100, 50);
+
+        mWifiMetrics.setConnectionScanDetail(TEST_IFACE_NAME2, mock(ScanDetail.class));
+        mWifiMetrics.setConnectionPmkCache(TEST_IFACE_NAME2, true);
+        mWifiMetrics.setConnectionMaxSupportedLinkSpeedMbps(TEST_IFACE_NAME2, 400, 200);
+
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME2,
+                WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_TIMED_OUT,
+                WifiMetricsProto.ConnectionEvent.HLF_NONE,
+                WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_TIMEOUT, 5745);
+
+        dumpProtoAndDeserialize();
+
+        assertEquals(1, mDecodedProto.connectionEvent.length);
+        WifiMetricsProto.ConnectionEvent connectionEvent = mDecodedProto.connectionEvent[0];
+        assertEquals(TEST_IFACE_NAME2, connectionEvent.interfaceName);
+        assertTrue(connectionEvent.routerFingerprint.pmkCacheEnabled);
+        assertEquals(400, connectionEvent.routerFingerprint.maxSupportedTxLinkSpeedMbps);
+        assertEquals(200, connectionEvent.routerFingerprint.maxSupportedRxLinkSpeedMbps);
+        assertEquals(WifiMetricsProto.ConnectionEvent.ROAM_USER_SELECTED, connectionEvent.roamType);
+        assertEquals(WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_TIMEOUT,
+                connectionEvent.level2FailureReason);
+
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
+                WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_REJECTION,
+                WifiMetricsProto.ConnectionEvent.HLF_DHCP,
+                WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_WRONG_PSWD, 2412);
+
+        dumpProtoAndDeserialize();
+
+        assertEquals(1, mDecodedProto.connectionEvent.length);
+        connectionEvent = mDecodedProto.connectionEvent[0];
+        assertEquals(TEST_IFACE_NAME, connectionEvent.interfaceName);
+        assertFalse(connectionEvent.routerFingerprint.pmkCacheEnabled);
+        assertEquals(100, connectionEvent.routerFingerprint.maxSupportedTxLinkSpeedMbps);
+        assertEquals(50, connectionEvent.routerFingerprint.maxSupportedRxLinkSpeedMbps);
+        assertEquals(WifiMetricsProto.ConnectionEvent.ROAM_DBDC, connectionEvent.roamType);
+        assertEquals(WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_WRONG_PSWD,
+                connectionEvent.level2FailureReason);
+    }
+
+    @Test
+    public void testStart2ConnectionEventsOnDifferentIfaces_end2AndDump() throws Exception {
+        WifiConfiguration config1 = WifiConfigurationTestUtil.createPskNetwork();
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, config1, "RED",
+                WifiMetricsProto.ConnectionEvent.ROAM_DBDC);
+        WifiConfiguration config2 = WifiConfigurationTestUtil.createOpenNetwork();
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME2, config2, "BLUE",
+                WifiMetricsProto.ConnectionEvent.ROAM_USER_SELECTED);
+
+        mWifiMetrics.setConnectionScanDetail(TEST_IFACE_NAME, mock(ScanDetail.class));
+        mWifiMetrics.setConnectionPmkCache(TEST_IFACE_NAME, false);
+        mWifiMetrics.setConnectionMaxSupportedLinkSpeedMbps(TEST_IFACE_NAME, 100, 50);
+
+        mWifiMetrics.setConnectionScanDetail(TEST_IFACE_NAME2, mock(ScanDetail.class));
+        mWifiMetrics.setConnectionPmkCache(TEST_IFACE_NAME2, true);
+        mWifiMetrics.setConnectionMaxSupportedLinkSpeedMbps(TEST_IFACE_NAME2, 400, 200);
+
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
+                WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_REJECTION,
+                WifiMetricsProto.ConnectionEvent.HLF_DHCP,
+                WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_WRONG_PSWD, 2412);
+
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME2,
+                WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_TIMED_OUT,
+                WifiMetricsProto.ConnectionEvent.HLF_NONE,
+                WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_TIMEOUT, 5745);
+
+        dumpProtoAndDeserialize();
+
+        assertEquals(2, mDecodedProto.connectionEvent.length);
+
+        WifiMetricsProto.ConnectionEvent connectionEvent = mDecodedProto.connectionEvent[0];
+        assertEquals(TEST_IFACE_NAME, connectionEvent.interfaceName);
+        assertFalse(connectionEvent.routerFingerprint.pmkCacheEnabled);
+        assertEquals(100, connectionEvent.routerFingerprint.maxSupportedTxLinkSpeedMbps);
+        assertEquals(50, connectionEvent.routerFingerprint.maxSupportedRxLinkSpeedMbps);
+        assertEquals(WifiMetricsProto.ConnectionEvent.ROAM_DBDC, connectionEvent.roamType);
+        assertEquals(WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_WRONG_PSWD,
+                connectionEvent.level2FailureReason);
+
+        connectionEvent = mDecodedProto.connectionEvent[1];
+        assertEquals(TEST_IFACE_NAME2, connectionEvent.interfaceName);
+        assertTrue(connectionEvent.routerFingerprint.pmkCacheEnabled);
+        assertEquals(400, connectionEvent.routerFingerprint.maxSupportedTxLinkSpeedMbps);
+        assertEquals(200, connectionEvent.routerFingerprint.maxSupportedRxLinkSpeedMbps);
+        assertEquals(WifiMetricsProto.ConnectionEvent.ROAM_USER_SELECTED, connectionEvent.roamType);
+        assertEquals(WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_TIMEOUT,
+                connectionEvent.level2FailureReason);
+    }
+
+    @Test
+    public void testStartAndEnd2ConnectionEventsOnDifferentIfacesAndDump() throws Exception {
+        WifiConfiguration config1 = WifiConfigurationTestUtil.createPskNetwork();
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, config1, "RED",
+                WifiMetricsProto.ConnectionEvent.ROAM_DBDC);
+        mWifiMetrics.setConnectionScanDetail(TEST_IFACE_NAME, mock(ScanDetail.class));
+        mWifiMetrics.setConnectionPmkCache(TEST_IFACE_NAME, false);
+        mWifiMetrics.setConnectionMaxSupportedLinkSpeedMbps(TEST_IFACE_NAME, 100, 50);
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
+                WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_REJECTION,
+                WifiMetricsProto.ConnectionEvent.HLF_DHCP,
+                WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_WRONG_PSWD, 2412);
+
+        WifiConfiguration config2 = WifiConfigurationTestUtil.createOpenNetwork();
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME2, config2, "BLUE",
+                WifiMetricsProto.ConnectionEvent.ROAM_USER_SELECTED);
+        mWifiMetrics.setConnectionScanDetail(TEST_IFACE_NAME2, mock(ScanDetail.class));
+        mWifiMetrics.setConnectionPmkCache(TEST_IFACE_NAME2, true);
+        mWifiMetrics.setConnectionMaxSupportedLinkSpeedMbps(TEST_IFACE_NAME2, 400, 200);
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME2,
+                WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_TIMED_OUT,
+                WifiMetricsProto.ConnectionEvent.HLF_NONE,
+                WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_TIMEOUT, 5745);
+
+        dumpProtoAndDeserialize();
+
+        assertEquals(2, mDecodedProto.connectionEvent.length);
+
+        WifiMetricsProto.ConnectionEvent connectionEvent = mDecodedProto.connectionEvent[0];
+        assertEquals(TEST_IFACE_NAME, connectionEvent.interfaceName);
+        assertFalse(connectionEvent.routerFingerprint.pmkCacheEnabled);
+        assertEquals(100, connectionEvent.routerFingerprint.maxSupportedTxLinkSpeedMbps);
+        assertEquals(50, connectionEvent.routerFingerprint.maxSupportedRxLinkSpeedMbps);
+        assertEquals(WifiMetricsProto.ConnectionEvent.ROAM_DBDC, connectionEvent.roamType);
+        assertEquals(WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_WRONG_PSWD,
+                connectionEvent.level2FailureReason);
+
+        connectionEvent = mDecodedProto.connectionEvent[1];
+        assertEquals(TEST_IFACE_NAME2, connectionEvent.interfaceName);
+        assertTrue(connectionEvent.routerFingerprint.pmkCacheEnabled);
+        assertEquals(400, connectionEvent.routerFingerprint.maxSupportedTxLinkSpeedMbps);
+        assertEquals(200, connectionEvent.routerFingerprint.maxSupportedRxLinkSpeedMbps);
+        assertEquals(WifiMetricsProto.ConnectionEvent.ROAM_USER_SELECTED, connectionEvent.roamType);
+        assertEquals(WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_TIMEOUT,
+                connectionEvent.level2FailureReason);
+    }
+
+    @Test
+    public void testNonExistentConnectionEventIface_doesntCrash() throws Exception {
+        mWifiMetrics.setConnectionScanDetail("nonexistentIface", mock(ScanDetail.class));
+        mWifiMetrics.setConnectionPmkCache("nonexistentIface", false);
+        mWifiMetrics.setConnectionMaxSupportedLinkSpeedMbps("nonexistentIface", 100, 50);
+        mWifiMetrics.endConnectionEvent("nonexistentIface",
+                WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_REJECTION,
+                WifiMetricsProto.ConnectionEvent.HLF_DHCP,
+                WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_WRONG_PSWD, 2412);
+    }
+
     /**
      * Verify the ConnectionEvent is labeled with networkType Passpoint correctly.
      */
     @Test
     public void testConnectionNetworkTypePasspoint() throws Exception {
         WifiConfiguration config = WifiConfigurationTestUtil.createPasspointNetwork();
-        mWifiMetrics.startConnectionEvent(config, "RED",
-                WifiMetricsProto.ConnectionEvent.ROAM_NONE);
-        mWifiMetrics.endConnectionEvent(
+        config.carrierMerged = true;
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, config,
+                "RED", WifiMetricsProto.ConnectionEvent.ROAM_NONE);
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
                 WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_TIMED_OUT,
                 WifiMetricsProto.ConnectionEvent.HLF_NONE,
-                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
+                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, 0);
         dumpProtoAndDeserialize();
 
         assertEquals(1, mDecodedProto.connectionEvent.length);
         assertEquals(WifiMetricsProto.ConnectionEvent.TYPE_PASSPOINT,
                 mDecodedProto.connectionEvent[0].networkType);
         assertFalse(mDecodedProto.connectionEvent[0].isOsuProvisioned);
+        assertTrue(mDecodedProto.connectionEvent[0].isCarrierMerged);
     }
 
     /**
@@ -1803,32 +2188,32 @@
 
         // First network is created by the user
         config.fromWifiNetworkSuggestion = false;
-        mWifiMetrics.startConnectionEvent(config, "RED",
-                WifiMetricsProto.ConnectionEvent.ROAM_NONE);
-        mWifiMetrics.endConnectionEvent(
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, config,
+                "RED", WifiMetricsProto.ConnectionEvent.ROAM_NONE);
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
                 WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_TIMED_OUT,
                 WifiMetricsProto.ConnectionEvent.HLF_NONE,
-                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
+                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, 0);
 
         // Second network is created by a carrier app
         config.fromWifiNetworkSuggestion = true;
         config.carrierId = 123;
-        mWifiMetrics.startConnectionEvent(config, "RED",
-                WifiMetricsProto.ConnectionEvent.ROAM_NONE);
-        mWifiMetrics.endConnectionEvent(
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, config,
+                "RED", WifiMetricsProto.ConnectionEvent.ROAM_NONE);
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
                 WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_TIMED_OUT,
                 WifiMetricsProto.ConnectionEvent.HLF_NONE,
-                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
+                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, 0);
 
         // Third network is created by an unknown app
         config.fromWifiNetworkSuggestion = true;
         config.carrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
-        mWifiMetrics.startConnectionEvent(config, "RED",
-                WifiMetricsProto.ConnectionEvent.ROAM_NONE);
-        mWifiMetrics.endConnectionEvent(
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, config,
+                "RED", WifiMetricsProto.ConnectionEvent.ROAM_NONE);
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
                 WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_TIMED_OUT,
                 WifiMetricsProto.ConnectionEvent.HLF_NONE,
-                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
+                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, 0);
 
         dumpProtoAndDeserialize();
 
@@ -1846,12 +2231,12 @@
      */
     @Test
     public void testMetricsAuthenticationFailureReason() throws Exception {
-        mWifiMetrics.startConnectionEvent(null, "RED",
-                WifiMetricsProto.ConnectionEvent.ROAM_NONE);
-        mWifiMetrics.endConnectionEvent(
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, null,
+                "RED", WifiMetricsProto.ConnectionEvent.ROAM_NONE);
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
                 WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE,
                 WifiMetricsProto.ConnectionEvent.HLF_NONE,
-                WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_WRONG_PSWD);
+                WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_WRONG_PSWD, 0);
 
         //Dump proto and deserialize
         //This should clear all the metrics in mWifiMetrics,
@@ -1872,8 +2257,16 @@
     public void testBssidBlocklistMetrics() throws Exception {
         for (int i = 0; i < 3; i++) {
             mWifiMetrics.incrementNetworkSelectionFilteredBssidCount(i);
+            mWifiMetrics.incrementBssidBlocklistCount(
+                    WifiBlocklistMonitor.REASON_ASSOCIATION_TIMEOUT);
+            mWifiMetrics.incrementWificonfigurationBlocklistCount(
+                    NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION);
         }
         mWifiMetrics.incrementNetworkSelectionFilteredBssidCount(2);
+        mWifiMetrics.incrementBssidBlocklistCount(
+                WifiBlocklistMonitor.REASON_NETWORK_VALIDATION_FAILURE);
+        mWifiMetrics.incrementWificonfigurationBlocklistCount(
+                NetworkSelectionStatus.DISABLED_NO_INTERNET_TEMPORARY);
         mResources.setBoolean(R.bool.config_wifiHighMovementNetworkSelectionOptimizationEnabled,
                 true);
         mWifiMetrics.incrementNumHighMovementConnectionStarted();
@@ -1881,13 +2274,25 @@
         mWifiMetrics.incrementNumHighMovementConnectionSkipped();
         dumpProtoAndDeserialize();
 
-        Int32Count[] expectedHistogram = {
+        Int32Count[] expectedFilteredBssidHistogram = {
                 buildInt32Count(0, 1),
                 buildInt32Count(1, 1),
                 buildInt32Count(2, 2),
         };
-        assertKeyCountsEqual(expectedHistogram,
+        Int32Count[] expectedBssidBlocklistPerReasonHistogram = {
+                buildInt32Count(WifiBlocklistMonitor.REASON_NETWORK_VALIDATION_FAILURE, 1),
+                buildInt32Count(WifiBlocklistMonitor.REASON_ASSOCIATION_TIMEOUT, 3),
+        };
+        Int32Count[] expectedWificonfigBlocklistPerReasonHistogram = {
+                buildInt32Count(NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION, 3),
+                buildInt32Count(NetworkSelectionStatus.DISABLED_NO_INTERNET_TEMPORARY, 1),
+        };
+        assertKeyCountsEqual(expectedFilteredBssidHistogram,
                 mDecodedProto.bssidBlocklistStats.networkSelectionFilteredBssidCount);
+        assertKeyCountsEqual(expectedBssidBlocklistPerReasonHistogram,
+                mDecodedProto.bssidBlocklistStats.bssidBlocklistPerReasonCount);
+        assertKeyCountsEqual(expectedWificonfigBlocklistPerReasonHistogram,
+                mDecodedProto.bssidBlocklistStats.wifiConfigBlocklistPerReasonCount);
         assertEquals(true, mDecodedProto.bssidBlocklistStats
                 .highMovementMultipleScansFeatureEnabled);
         assertEquals(1, mDecodedProto.bssidBlocklistStats.numHighMovementConnectionStarted);
@@ -1900,30 +2305,30 @@
     @Test
     public void testMetricsClearedAfterProtoRequested() throws Exception {
         // Create 3 ConnectionEvents
-        mWifiMetrics.startConnectionEvent(null, "RED",
-                WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
-        mWifiMetrics.endConnectionEvent(
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, null,
+                "RED", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
                 WifiMetrics.ConnectionEvent.FAILURE_NONE,
                 WifiMetricsProto.ConnectionEvent.HLF_NONE,
-                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
-        mWifiMetrics.startConnectionEvent(null, "YELLOW",
-                WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
-        mWifiMetrics.endConnectionEvent(
+                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, 0);
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, null,
+                "YELLOW", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
                 WifiMetrics.ConnectionEvent.FAILURE_NONE,
                 WifiMetricsProto.ConnectionEvent.HLF_NONE,
-                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
-        mWifiMetrics.startConnectionEvent(null, "GREEN",
-                WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
-        mWifiMetrics.endConnectionEvent(
+                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, 0);
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, null,
+                "GREEN", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
                 WifiMetrics.ConnectionEvent.FAILURE_NONE,
                 WifiMetricsProto.ConnectionEvent.HLF_NONE,
-                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
-        mWifiMetrics.startConnectionEvent(null, "ORANGE",
-                WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
-        mWifiMetrics.endConnectionEvent(
+                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, 0);
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, null,
+                "ORANGE", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
                 WifiMetrics.ConnectionEvent.FAILURE_NONE,
                 WifiMetricsProto.ConnectionEvent.HLF_NONE,
-                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
+                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, 0);
 
         //Dump proto and deserialize
         //This should clear all the metrics in mWifiMetrics,
@@ -1934,18 +2339,18 @@
         assertEquals(0, mDecodedProto.alertReasonCount.length);
 
         // Create 2 ConnectionEvents
-        mWifiMetrics.startConnectionEvent(null,  "BLUE",
-                WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
-        mWifiMetrics.endConnectionEvent(
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, null,
+                "BLUE", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
                 WifiMetrics.ConnectionEvent.FAILURE_NONE,
                 WifiMetricsProto.ConnectionEvent.HLF_NONE,
-                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
-        mWifiMetrics.startConnectionEvent(null, "RED",
-                WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
-        mWifiMetrics.endConnectionEvent(
+                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, 0);
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, null,
+                "RED", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
                 WifiMetrics.ConnectionEvent.FAILURE_NONE,
                 WifiMetricsProto.ConnectionEvent.HLF_NONE,
-                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
+                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, 0);
 
         //Dump proto and deserialize
         dumpProtoAndDeserialize();
@@ -1958,33 +2363,23 @@
      */
     @Test
     public void testLogWifiConnectionResultStatsd() throws Exception {
-        // static mocking for WifiStatsLog
-        mSession = ExtendedMockito.mockitoSession()
-                .strictness(Strictness.LENIENT)
-                .mockStatic(WifiStatsLog.class)
-                .startMocking();
-
-        WifiConfiguration network = WifiConfigurationTestUtil.createOpenNetwork();
-        WifiConfiguration.NetworkSelectionStatus networkSelectionStatus =
-                mock(WifiConfiguration.NetworkSelectionStatus.class);
-        ScanResult scanResult = mock(ScanResult.class);
-        scanResult.level = -55;
-        when(networkSelectionStatus.getCandidate()).thenReturn(scanResult);
-        network.setNetworkSelectionStatus(networkSelectionStatus);
-
         // Start and end Connection event
-        mWifiMetrics.startConnectionEvent(network, "RED",
-                WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
-        mWifiMetrics.endConnectionEvent(
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, createComplexWifiConfig(),
+                "RED", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
                 WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE,
                 WifiMetricsProto.ConnectionEvent.HLF_DHCP,
-                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
+                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, TEST_CANDIDATE_FREQ);
 
         ExtendedMockito.verify(() -> WifiStatsLog.write(
                 WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED, false,
                 WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED__FAILURE_CODE__FAILURE_AUTHENTICATION_GENERAL,
-                -55));
-        mSession.finishMocking();
+                -80, 0,
+                WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED__BAND__BAND_2G,
+                WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED__AUTH_TYPE__AUTH_TYPE_WPA2_PSK,
+                WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED__TRIGGER__AUTOCONNECT_BOOT,
+                true,
+                0));
     }
 
     /**
@@ -1994,20 +2389,20 @@
     @Test
     public void testCurrentConnectionEventNotClearedAfterProtoRequested() throws Exception {
         // Create 2 complete ConnectionEvents and 1 ongoing un-ended ConnectionEvent
-        mWifiMetrics.startConnectionEvent(null, "RED",
-                WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
-        mWifiMetrics.endConnectionEvent(
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, null,
+                "RED", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
                 WifiMetrics.ConnectionEvent.FAILURE_NONE,
                 WifiMetricsProto.ConnectionEvent.HLF_NONE,
-                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
-        mWifiMetrics.startConnectionEvent(null, "YELLOW",
-                WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
-        mWifiMetrics.endConnectionEvent(
+                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, 0);
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, null,
+                "YELLOW", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
                 WifiMetrics.ConnectionEvent.FAILURE_NONE,
                 WifiMetricsProto.ConnectionEvent.HLF_NONE,
-                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
-        mWifiMetrics.startConnectionEvent(null, "GREEN",
-                WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, 0);
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, null,
+                "GREEN", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
 
         // Dump proto and deserialize
         // This should clear the metrics in mWifiMetrics,
@@ -2015,10 +2410,10 @@
         assertEquals(2, mDecodedProto.connectionEvent.length);
 
         // End the ongoing ConnectionEvent
-        mWifiMetrics.endConnectionEvent(
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
                 WifiMetrics.ConnectionEvent.FAILURE_NONE,
                 WifiMetricsProto.ConnectionEvent.HLF_NONE,
-                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
+                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, 0);
 
         dumpProtoAndDeserialize();
         assertEquals(1, mDecodedProto.connectionEvent.length);
@@ -2187,8 +2582,8 @@
 
     private static final int DEAUTH_REASON = 7;
     private static final int ASSOC_STATUS = 11;
-    private static final int ASSOC_TIMEOUT = 1;
-    private static final int LOCAL_GEN = 1;
+    private static final boolean ASSOC_TIMEOUT = true;
+    private static final boolean LOCAL_GEN = true;
     private static final int AUTH_FAILURE_REASON = WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD;
     private static final int NUM_TEST_STA_EVENTS = 19;
     private static final String   sSSID = "\"SomeTestSsid\"";
@@ -2207,10 +2602,10 @@
     private final WifiConfiguration mTestWifiConfig = createComplexWifiConfig();
     // <msg.what> <msg.arg1> <msg.arg2>
     private int[][] mTestStaMessageInts = {
-        {WifiMonitor.ASSOCIATION_REJECTION_EVENT,   ASSOC_TIMEOUT,       ASSOC_STATUS},
+        {WifiMonitor.ASSOCIATION_REJECTION_EVENT,   0,                   0},
         {WifiMonitor.AUTHENTICATION_FAILURE_EVENT,  AUTH_FAILURE_REASON, -1},
         {WifiMonitor.NETWORK_CONNECTION_EVENT,      0,                   0},
-        {WifiMonitor.NETWORK_DISCONNECTION_EVENT,   LOCAL_GEN,           DEAUTH_REASON},
+        {WifiMonitor.NETWORK_DISCONNECTION_EVENT,   0,                   0},
         {WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0,                   0},
         {WifiMonitor.ASSOCIATED_BSSID_EVENT,        0,                   0},
         {WifiMonitor.TARGET_BSSID_EVENT,            0,                   0},
@@ -2218,10 +2613,10 @@
         {WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0,                   0}
     };
     private Object[] mTestStaMessageObjs = {
+        new AssocRejectEventInfo(sSSID, sBSSID, ASSOC_STATUS, ASSOC_TIMEOUT),
         null,
         null,
-        null,
-        null,
+        new DisconnectEventInfo(sSSID, sBSSID, DEAUTH_REASON, LOCAL_GEN),
         mStateDisconnected,
         null,
         null,
@@ -2250,12 +2645,12 @@
     // <auth_fail_reason>, <assoc_timed_out> <supplicantStateChangeBitmask> <1|0>(has ConfigInfo)
     private int[][] mExpectedValues = {
         {StaEvent.TYPE_ASSOCIATION_REJECTION_EVENT,     -1,  ASSOC_STATUS,         0,
-            /**/                               0, ASSOC_TIMEOUT,        0, 0},    /**/
+            /**/                               0, ASSOC_TIMEOUT ? 1 : 0,        0, 0},    /**/
         {StaEvent.TYPE_AUTHENTICATION_FAILURE_EVENT,    -1,            -1,         0,
             /**/StaEvent.AUTH_FAILURE_WRONG_PSWD,             0,        0, 0},    /**/
         {StaEvent.TYPE_NETWORK_CONNECTION_EVENT,        -1,            -1,         0,
             /**/                               0,             0,        0, 0},    /**/
-        {StaEvent.TYPE_NETWORK_DISCONNECTION_EVENT, DEAUTH_REASON,     -1, LOCAL_GEN,
+        {StaEvent.TYPE_NETWORK_DISCONNECTION_EVENT, DEAUTH_REASON,     -1, LOCAL_GEN ? 1 : 0,
             /**/                               0,             0,        0, 0},    /**/
         {StaEvent.TYPE_CMD_ASSOCIATED_BSSID,            -1,            -1,         0,
             /**/                               0,             0,  mSupBm1, 0},    /**/
@@ -2293,19 +2688,21 @@
      * Generates events from all the rows in mTestStaMessageInts, and then mTestStaLogInts
      */
     private void generateStaEvents(WifiMetrics wifiMetrics) {
-        Handler handler = wifiMetrics.getHandler();
+        Handler handler = mHandlerCaptor.getValue();
         for (int i = 0; i < mTestStaMessageInts.length; i++) {
             int[] mia = mTestStaMessageInts[i];
-            handler.sendMessage(
-                    handler.obtainMessage(mia[0], mia[1], mia[2], mTestStaMessageObjs[i]));
+            Message message = handler.obtainMessage(mia[0], mia[1], mia[2], mTestStaMessageObjs[i]);
+            message.getData().putString(WifiMonitor.KEY_IFACE, TEST_IFACE_NAME);
+            handler.sendMessage(message);
         }
         mTestLooper.dispatchAll();
-        wifiMetrics.setScreenState(true);
+        setScreenState(true);
         when(mWifiDataStall.isCellularDataAvailable()).thenReturn(true);
         wifiMetrics.setAdaptiveConnectivityState(true);
         for (int i = 0; i < mTestStaLogInts.length; i++) {
             int[] lia = mTestStaLogInts[i];
-            wifiMetrics.logStaEvent(lia[0], lia[1], lia[2] == 1 ? mTestWifiConfig : null);
+            wifiMetrics.logStaEvent(
+                    TEST_IFACE_NAME, lia[0], lia[1], lia[2] == 1 ? mTestWifiConfig : null);
         }
     }
     private void verifyDeserializedStaEvents(WifiMetricsProto.WifiLog wifiLog) {
@@ -2367,7 +2764,7 @@
     @Test
     public void testStaEventBounding() throws Exception {
         for (int i = 0; i < (WifiMetrics.MAX_STA_EVENTS + 10); i++) {
-            mWifiMetrics.logStaEvent(StaEvent.TYPE_CMD_START_CONNECT);
+            mWifiMetrics.logStaEvent(TEST_IFACE_NAME, StaEvent.TYPE_CMD_START_CONNECT);
         }
         dumpProtoAndDeserialize();
         assertEquals(WifiMetrics.MAX_STA_EVENTS, mDecodedProto.staEventList.length);
@@ -2380,11 +2777,11 @@
     @Test
     public void testLinkProbeStaEventBounding() throws Exception {
         for (int i = 0; i < WifiMetrics.MAX_LINK_PROBE_STA_EVENTS; i++) {
-            mWifiMetrics.logLinkProbeSuccess(0, 0, 0, 0);
-            mWifiMetrics.logLinkProbeFailure(0, 0, 0, 0);
+            mWifiMetrics.logLinkProbeSuccess(TEST_IFACE_NAME, 0, 0, 0, 0);
+            mWifiMetrics.logLinkProbeFailure(TEST_IFACE_NAME, 0, 0, 0, 0);
         }
         for (int i = 0; i < 10; i++) {
-            mWifiMetrics.logStaEvent(StaEvent.TYPE_CMD_START_CONNECT);
+            mWifiMetrics.logStaEvent(TEST_IFACE_NAME, StaEvent.TYPE_CMD_START_CONNECT);
         }
 
         dumpProtoAndDeserialize();
@@ -2443,7 +2840,7 @@
         WifiInfo wifiInfo = mock(WifiInfo.class);
         when(wifiInfo.getRssi()).thenReturn(expectedRssi);
         when(wifiInfo.getNetworkId()).thenReturn(testNetworkId);
-        mWifiMetrics.handlePollResult(wifiInfo);
+        mWifiMetrics.handlePollResult(TEST_IFACE_NAME, wifiInfo);
         mWifiMetrics.incrementThroughputKbpsCount(expectedTx, expectedRx, RSSI_POLL_FREQUENCY);
         mWifiMetrics.setNominatorForNetwork(testNetworkId,
                 WifiMetricsProto.ConnectionEvent.NOMINATOR_SAVED_USER_CONNECT_CHOICE);
@@ -2480,8 +2877,8 @@
 
         // Also setup the same BSSID level failure
         Set<Integer> testBssidBlocklistReasons = new HashSet<>();
-        testBssidBlocklistReasons.add(BssidBlocklistMonitor.REASON_ASSOCIATION_REJECTION);
-        when(mBssidBlocklistMonitor.getFailureReasonsForSsid(anyString()))
+        testBssidBlocklistReasons.add(WifiBlocklistMonitor.REASON_ASSOCIATION_REJECTION);
+        when(mWifiBlocklistMonitor.getFailureReasonsForSsid(anyString()))
                 .thenReturn(testBssidBlocklistReasons);
 
         // Logging the user action event
@@ -2838,7 +3235,7 @@
         // This should clear most metrics in mWifiMetrics
         dumpProtoAndDeserialize();
         assertEquals(SIZE_OPEN_NETWORK_RECOMMENDER_BLOCKLIST,
-                mDecodedProto.openNetworkRecommenderBlacklistSize);
+                mDecodedProto.openNetworkRecommenderBlocklistSize);
         assertEquals(IS_WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
                 mDecodedProto.isWifiNetworksAvailableNotificationOn);
         assertEquals(NUM_OPEN_NETWORK_RECOMMENDATION_UPDATES,
@@ -2848,7 +3245,7 @@
         // others do not.
         dumpProtoAndDeserialize();
         assertEquals(SIZE_OPEN_NETWORK_RECOMMENDER_BLOCKLIST,
-                mDecodedProto.openNetworkRecommenderBlacklistSize);
+                mDecodedProto.openNetworkRecommenderBlocklistSize);
         assertEquals(IS_WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
                 mDecodedProto.isWifiNetworksAvailableNotificationOn);
         assertEquals(0, mDecodedProto.numOpenNetworkRecommendationUpdates);
@@ -2861,12 +3258,12 @@
     public void testNetworkSelectorExperimentId() throws Exception {
         final int id = 42888888;
         mWifiMetrics.setNetworkSelectorExperimentId(id);
-        mWifiMetrics.startConnectionEvent(mTestWifiConfig, "TestNetwork",
-                WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
-        mWifiMetrics.endConnectionEvent(
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, mTestWifiConfig,
+                "TestNetwork", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
                 WifiMetrics.ConnectionEvent.FAILURE_NONE,
                 WifiMetricsProto.ConnectionEvent.HLF_NONE,
-                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
+                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, 0);
         dumpProtoAndDeserialize();
         assertEquals(id, mDecodedProto.connectionEvent[0].networkSelectorExperimentId);
     }
@@ -2876,13 +3273,13 @@
      */
     @Test
     public void testConnectionWithPmkCache() throws Exception {
-        mWifiMetrics.startConnectionEvent(mTestWifiConfig, "TestNetwork",
-                WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
-        mWifiMetrics.setConnectionPmkCache(true);
-        mWifiMetrics.endConnectionEvent(
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, mTestWifiConfig,
+                "TestNetwork", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+        mWifiMetrics.setConnectionPmkCache(TEST_IFACE_NAME, true);
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
                 WifiMetrics.ConnectionEvent.FAILURE_NONE,
                 WifiMetricsProto.ConnectionEvent.HLF_NONE,
-                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
+                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, 0);
         dumpProtoAndDeserialize();
         assertEquals(true, mDecodedProto.connectionEvent[0].routerFingerprint.pmkCacheEnabled);
     }
@@ -2892,17 +3289,17 @@
      */
     @Test
     public void testConnectionMaxSupportedLinkSpeedConsecutiveFailureCnt() throws Exception {
-        mWifiMetrics.setScreenState(true);
+        setScreenState(true);
         when(mNetworkConnectionStats.getCount(WifiScoreCard.CNT_CONSECUTIVE_CONNECTION_FAILURE))
                 .thenReturn(2);
-        mWifiMetrics.startConnectionEvent(mTestWifiConfig, "TestNetwork",
-                WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
-        mWifiMetrics.setConnectionMaxSupportedLinkSpeedMbps(MAX_SUPPORTED_TX_LINK_SPEED_MBPS,
-                MAX_SUPPORTED_RX_LINK_SPEED_MBPS);
-        mWifiMetrics.endConnectionEvent(
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, mTestWifiConfig,
+                "TestNetwork", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+        mWifiMetrics.setConnectionMaxSupportedLinkSpeedMbps(TEST_IFACE_NAME,
+                MAX_SUPPORTED_TX_LINK_SPEED_MBPS, MAX_SUPPORTED_RX_LINK_SPEED_MBPS);
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
                 WifiMetrics.ConnectionEvent.FAILURE_NONE,
                 WifiMetricsProto.ConnectionEvent.HLF_NONE,
-                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
+                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, 0);
         dumpProtoAndDeserialize();
         assertEquals(MAX_SUPPORTED_TX_LINK_SPEED_MBPS, mDecodedProto.connectionEvent[0]
                 .routerFingerprint.maxSupportedTxLinkSpeedMbps);
@@ -2974,19 +3371,19 @@
         config.allowedKeyManagement = new BitSet();
         when(networkSelectionStat.getCandidate()).thenReturn(scanResult);
         when(config.getNetworkSelectionStatus()).thenReturn(networkSelectionStat);
-        mWifiMetrics.startConnectionEvent(config, "TestNetwork",
-                WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, config,
+                "TestNetwork", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
         if (completeConnectionEvent) {
             if (successfulConnectionEvent) {
-                mWifiMetrics.endConnectionEvent(
+                mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
                         WifiMetrics.ConnectionEvent.FAILURE_NONE,
                         WifiMetricsProto.ConnectionEvent.HLF_NONE,
-                        WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
+                        WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, 0);
             } else {
-                mWifiMetrics.endConnectionEvent(
+                mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
                         WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE,
                         WifiMetricsProto.ConnectionEvent.HLF_NONE,
-                        WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
+                        WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, 0);
             }
         }
         when(mClock.getElapsedSinceBootMillis()).thenReturn(interArrivalTime);
@@ -3019,13 +3416,13 @@
         return stream.toString();
     }
 
-    private static final int TEST_ALLOWED_KEY_MANAGEMENT = 83;
+    private static final int TEST_ALLOWED_KEY_MANAGEMENT = 16;
     private static final int TEST_ALLOWED_PROTOCOLS = 22;
     private static final int TEST_ALLOWED_AUTH_ALGORITHMS = 11;
     private static final int TEST_ALLOWED_PAIRWISE_CIPHERS = 67;
     private static final int TEST_ALLOWED_GROUP_CIPHERS = 231;
     private static final int TEST_CANDIDATE_LEVEL = -80;
-    private static final int TEST_CANDIDATE_FREQ = 2345;
+    private static final int TEST_CANDIDATE_FREQ = 2450;
 
     private WifiConfiguration createComplexWifiConfig() {
         WifiConfiguration config = new WifiConfiguration();
@@ -3115,22 +3512,22 @@
         when(mFacade.getMobileRxBytes()).thenReturn((long) trigger[10]);
         when(mFacade.getTotalTxBytes()).thenReturn((long) trigger[11]);
         when(mFacade.getTotalRxBytes()).thenReturn((long) trigger[12]);
-        mWifiMetrics.incrementWifiScoreCount(trigger[1]);
-        mWifiMetrics.incrementWifiUsabilityScoreCount(1, trigger[8], 15);
+        mWifiMetrics.incrementWifiScoreCount(TEST_IFACE_NAME, trigger[1]);
+        mWifiMetrics.incrementWifiUsabilityScoreCount(TEST_IFACE_NAME, 1, trigger[8], 15);
         mWifiMetrics.updateWifiIsUnusableLinkLayerStats(trigger[2], trigger[3], trigger[4],
                 trigger[5], trigger[6]);
-        mWifiMetrics.setScreenState(true);
+        setScreenState(true);
         switch(trigger[0]) {
             case WifiIsUnusableEvent.TYPE_DATA_STALL_BAD_TX:
             case WifiIsUnusableEvent.TYPE_DATA_STALL_TX_WITHOUT_RX:
             case WifiIsUnusableEvent.TYPE_DATA_STALL_BOTH:
-                mWifiMetrics.logWifiIsUnusableEvent(trigger[0]);
+                mWifiMetrics.logWifiIsUnusableEvent(TEST_IFACE_NAME, trigger[0]);
                 break;
             case WifiIsUnusableEvent.TYPE_FIRMWARE_ALERT:
-                mWifiMetrics.logWifiIsUnusableEvent(trigger[0], trigger[7]);
+                mWifiMetrics.logWifiIsUnusableEvent(TEST_IFACE_NAME, trigger[0], trigger[7]);
                 break;
             case WifiIsUnusableEvent.TYPE_IP_REACHABILITY_LOST:
-                mWifiMetrics.logWifiIsUnusableEvent(trigger[0]);
+                mWifiMetrics.logWifiIsUnusableEvent(TEST_IFACE_NAME, trigger[0]);
                 break;
             default:
                 break;
@@ -3386,6 +3783,66 @@
         out.on_time_roam_scan = current.on_time_roam_scan + nextRandInt();
         out.on_time_pno_scan = current.on_time_pno_scan + nextRandInt();
         out.on_time_hs20_scan = current.on_time_hs20_scan + nextRandInt();
+        out.timeSliceDutyCycleInPercent =
+                (short) ((current.timeSliceDutyCycleInPercent + nextRandInt()) % 101);
+        out.peerInfo = createNewPeerInfo(current.peerInfo);
+        out.radioStats = createNewRadioStat(current.radioStats);
+        return out;
+    }
+
+    private PeerInfo[] createNewPeerInfo(PeerInfo[] current) {
+        if (current == null) {
+            return null;
+        }
+        PeerInfo[] out = new PeerInfo[current.length];
+        for (int i = 0; i < current.length; i++) {
+            int numRates = 0;
+            if (current[i].rateStats != null) {
+                numRates = current[i].rateStats.length;
+            }
+            RateStat[] rateStats = new RateStat[numRates];
+            for (int j = 0; j < numRates; j++) {
+                RateStat curRate = current[i].rateStats[j];
+                RateStat newRate = new RateStat();
+                newRate.preamble = curRate.preamble;
+                newRate.nss = curRate.nss;
+                newRate.bw = curRate.bw;
+                newRate.rateMcsIdx = curRate.rateMcsIdx;
+                newRate.bitRateInKbps = curRate.bitRateInKbps;
+                newRate.txMpdu = curRate.txMpdu + nextRandInt();
+                newRate.rxMpdu = curRate.rxMpdu + nextRandInt();
+                newRate.mpduLost = curRate.mpduLost + nextRandInt();
+                newRate.retries = curRate.retries + nextRandInt();
+                rateStats[j] = newRate;
+            }
+            out[i] = new PeerInfo();
+            out[i].rateStats = rateStats;
+            out[i].staCount = (short) (current[i].staCount + nextRandInt() % 10);
+            out[i].chanUtil = (short) ((current[i].chanUtil + nextRandInt()) % 100);
+        }
+        return out;
+    }
+
+    private RadioStat[] createNewRadioStat(RadioStat[] current) {
+        if (current == null) {
+            return null;
+        }
+        RadioStat[] out = new RadioStat[current.length];
+        for (int i = 0; i < current.length; i++) {
+            RadioStat currentRadio = current[i];
+            RadioStat newRadio = new RadioStat();
+            newRadio.radio_id = currentRadio.radio_id;
+            newRadio.on_time = currentRadio.on_time + nextRandInt();
+            newRadio.tx_time = currentRadio.tx_time + nextRandInt();
+            newRadio.rx_time = currentRadio.rx_time + nextRandInt();
+            newRadio.on_time_scan = currentRadio.on_time_scan + nextRandInt();
+            newRadio.on_time_nan_scan = currentRadio.on_time_nan_scan + nextRandInt();
+            newRadio.on_time_background_scan = currentRadio.on_time_background_scan + nextRandInt();
+            newRadio.on_time_roam_scan = currentRadio.on_time_roam_scan + nextRandInt();
+            newRadio.on_time_pno_scan = currentRadio.on_time_pno_scan + nextRandInt();
+            newRadio.on_time_hs20_scan = currentRadio.on_time_hs20_scan + nextRandInt();
+            out[i] = newRadio;
+        }
         return out;
     }
 
@@ -3411,7 +3868,40 @@
                 mDecodedProto.wifiLinkLayerUsageStats.radioPnoScanTimeMs);
         assertEquals(newStats.on_time_hs20_scan - oldStats.on_time_hs20_scan,
                 mDecodedProto.wifiLinkLayerUsageStats.radioHs20ScanTimeMs);
+    }
 
+    private void assertPerRadioStatsUsageHasDiff(WifiLinkLayerStats oldStats,
+            WifiLinkLayerStats newStats) {
+        assertEquals(oldStats.radioStats.length, newStats.radioStats.length);
+        assertEquals(newStats.radioStats.length,
+                mDecodedProto.wifiLinkLayerUsageStats.radioStats.length);
+        for (int i = 0; i < oldStats.radioStats.length; i++) {
+            RadioStat oldRadioStats = oldStats.radioStats[i];
+            RadioStat newRadioStats = newStats.radioStats[i];
+            RadioStats radioStats =
+                    mDecodedProto.wifiLinkLayerUsageStats.radioStats[i];
+            assertEquals(oldRadioStats.radio_id, newRadioStats.radio_id);
+            assertEquals(newRadioStats.radio_id, radioStats.radioId);
+            assertEquals(newRadioStats.on_time - oldRadioStats.on_time,
+                    radioStats.totalRadioOnTimeMs);
+            assertEquals(newRadioStats.tx_time - oldRadioStats.tx_time,
+                    radioStats.totalRadioTxTimeMs);
+            assertEquals(newRadioStats.rx_time - oldRadioStats.rx_time,
+                    radioStats.totalRadioRxTimeMs);
+            assertEquals(newRadioStats.on_time_scan - oldRadioStats.on_time_scan,
+                    radioStats.totalScanTimeMs);
+            assertEquals(newRadioStats.on_time_nan_scan - oldRadioStats.on_time_nan_scan,
+                    radioStats.totalNanScanTimeMs);
+            assertEquals(newRadioStats.on_time_background_scan
+                    - oldRadioStats.on_time_background_scan,
+                    radioStats.totalBackgroundScanTimeMs);
+            assertEquals(newRadioStats.on_time_roam_scan - oldRadioStats.on_time_roam_scan,
+                    radioStats.totalRoamScanTimeMs);
+            assertEquals(newRadioStats.on_time_pno_scan - oldRadioStats.on_time_pno_scan,
+                    radioStats.totalPnoScanTimeMs);
+            assertEquals(newRadioStats.on_time_hs20_scan - oldRadioStats.on_time_hs20_scan,
+                    radioStats.totalHotspot2ScanTimeMs);
+        }
     }
 
     /**
@@ -3421,16 +3911,17 @@
      */
     @Test
     public void testWifiLinkLayerUsageStats() throws Exception {
-        WifiLinkLayerStats stat1 = nextRandomStats(new WifiLinkLayerStats());
+        WifiLinkLayerStats stat1 = nextRandomStats(createNewWifiLinkLayerStats());
         WifiLinkLayerStats stat2 = nextRandomStats(stat1);
         WifiLinkLayerStats stat3 = nextRandomStats(stat2);
-        mWifiMetrics.incrementWifiLinkLayerUsageStats(stat1);
-        mWifiMetrics.incrementWifiLinkLayerUsageStats(stat2);
-        mWifiMetrics.incrementWifiLinkLayerUsageStats(stat3);
+        mWifiMetrics.incrementWifiLinkLayerUsageStats(TEST_IFACE_NAME, stat1);
+        mWifiMetrics.incrementWifiLinkLayerUsageStats(TEST_IFACE_NAME, stat2);
+        mWifiMetrics.incrementWifiLinkLayerUsageStats(TEST_IFACE_NAME, stat3);
         dumpProtoAndDeserialize();
 
         // After 2 increments, the counters should have difference between |stat1| and |stat3|
         assertWifiLinkLayerUsageHasDiff(stat1, stat3);
+        assertPerRadioStatsUsageHasDiff(stat1, stat3);
     }
 
     /**
@@ -3439,14 +3930,15 @@
      */
     @Test
     public void testWifiLinkLayerUsageStatsNullInput() throws Exception {
-        WifiLinkLayerStats stat1 = nextRandomStats(new WifiLinkLayerStats());
+        WifiLinkLayerStats stat1 = nextRandomStats(createNewWifiLinkLayerStats());
         WifiLinkLayerStats stat2 = null;
-        mWifiMetrics.incrementWifiLinkLayerUsageStats(stat1);
-        mWifiMetrics.incrementWifiLinkLayerUsageStats(stat2);
+        mWifiMetrics.incrementWifiLinkLayerUsageStats(TEST_IFACE_NAME, stat1);
+        mWifiMetrics.incrementWifiLinkLayerUsageStats(TEST_IFACE_NAME, stat2);
         dumpProtoAndDeserialize();
 
         // Counter should be zero
         assertWifiLinkLayerUsageHasDiff(stat1, stat1);
+        assertNotNull(mDecodedProto.wifiLinkLayerUsageStats.radioStats);
     }
 
     /**
@@ -3456,19 +3948,20 @@
      */
     @Test
     public void testWifiLinkLayerUsageStatsChipReset() throws Exception {
-        WifiLinkLayerStats stat1 = nextRandomStats(new WifiLinkLayerStats());
+        WifiLinkLayerStats stat1 = nextRandomStats(createNewWifiLinkLayerStats());
         WifiLinkLayerStats stat2 = nextRandomStats(stat1);
         stat2.on_time = stat1.on_time - 1;
         WifiLinkLayerStats stat3 = nextRandomStats(stat2);
         WifiLinkLayerStats stat4 = nextRandomStats(stat3);
-        mWifiMetrics.incrementWifiLinkLayerUsageStats(stat1);
-        mWifiMetrics.incrementWifiLinkLayerUsageStats(stat2);
-        mWifiMetrics.incrementWifiLinkLayerUsageStats(stat3);
-        mWifiMetrics.incrementWifiLinkLayerUsageStats(stat4);
+        mWifiMetrics.incrementWifiLinkLayerUsageStats(TEST_IFACE_NAME, stat1);
+        mWifiMetrics.incrementWifiLinkLayerUsageStats(TEST_IFACE_NAME, stat2);
+        mWifiMetrics.incrementWifiLinkLayerUsageStats(TEST_IFACE_NAME, stat3);
+        mWifiMetrics.incrementWifiLinkLayerUsageStats(TEST_IFACE_NAME, stat4);
         dumpProtoAndDeserialize();
 
         // Should only count the difference between |stat3| and |stat4|
         assertWifiLinkLayerUsageHasDiff(stat3, stat4);
+        assertPerRadioStatsUsageHasDiff(stat3, stat4);
     }
 
     private void assertUsabilityStatsAssignment(WifiInfo info, WifiLinkLayerStats stats,
@@ -3485,6 +3978,21 @@
                 usabilityStats.totalTxBad);
         assertEquals(stats.rxmpdu_be + stats.rxmpdu_bk + stats.rxmpdu_vi + stats.rxmpdu_vo,
                 usabilityStats.totalRxSuccess);
+        assertEquals(stats.radioStats.length, usabilityStats.radioStats.length);
+        for (int i = 0; i < stats.radioStats.length; i++) {
+            RadioStat radio = stats.radioStats[i];
+            RadioStats radioStats = usabilityStats.radioStats[i];
+            assertEquals(radio.radio_id, radioStats.radioId);
+            assertEquals(radio.on_time, radioStats.totalRadioOnTimeMs);
+            assertEquals(radio.tx_time, radioStats.totalRadioTxTimeMs);
+            assertEquals(radio.rx_time, radioStats.totalRadioRxTimeMs);
+            assertEquals(radio.on_time_scan, radioStats.totalScanTimeMs);
+            assertEquals(radio.on_time_nan_scan, radioStats.totalNanScanTimeMs);
+            assertEquals(radio.on_time_background_scan, radioStats.totalBackgroundScanTimeMs);
+            assertEquals(radio.on_time_roam_scan, radioStats.totalRoamScanTimeMs);
+            assertEquals(radio.on_time_pno_scan, radioStats.totalPnoScanTimeMs);
+            assertEquals(radio.on_time_hs20_scan, radioStats.totalHotspot2ScanTimeMs);
+        }
         assertEquals(stats.on_time, usabilityStats.totalRadioOnTimeMs);
         assertEquals(stats.tx_time, usabilityStats.totalRadioTxTimeMs);
         assertEquals(stats.rx_time, usabilityStats.totalRadioRxTimeMs);
@@ -3495,6 +4003,57 @@
         assertEquals(stats.on_time_pno_scan, usabilityStats.totalPnoScanTimeMs);
         assertEquals(stats.on_time_hs20_scan, usabilityStats.totalHotspot2ScanTimeMs);
         assertEquals(stats.beacon_rx, usabilityStats.totalBeaconRx);
+        assertEquals(stats.timeSliceDutyCycleInPercent, usabilityStats.timeSliceDutyCycleInPercent);
+        assertEquals(stats.contentionTimeMinBeInUsec,
+                usabilityStats.contentionTimeStats[0].contentionTimeMinMicros);
+        assertEquals(stats.contentionTimeMaxBeInUsec,
+                usabilityStats.contentionTimeStats[0].contentionTimeMaxMicros);
+        assertEquals(stats.contentionTimeAvgBeInUsec,
+                usabilityStats.contentionTimeStats[0].contentionTimeAvgMicros);
+        assertEquals(stats.contentionNumSamplesBe,
+                usabilityStats.contentionTimeStats[0].contentionNumSamples);
+        assertEquals(stats.contentionTimeMinBkInUsec,
+                usabilityStats.contentionTimeStats[1].contentionTimeMinMicros);
+        assertEquals(stats.contentionTimeMaxBkInUsec,
+                usabilityStats.contentionTimeStats[1].contentionTimeMaxMicros);
+        assertEquals(stats.contentionTimeAvgBkInUsec,
+                usabilityStats.contentionTimeStats[1].contentionTimeAvgMicros);
+        assertEquals(stats.contentionNumSamplesBk,
+                usabilityStats.contentionTimeStats[1].contentionNumSamples);
+        assertEquals(stats.contentionTimeMinViInUsec,
+                usabilityStats.contentionTimeStats[2].contentionTimeMinMicros);
+        assertEquals(stats.contentionTimeMaxViInUsec,
+                usabilityStats.contentionTimeStats[2].contentionTimeMaxMicros);
+        assertEquals(stats.contentionTimeAvgViInUsec,
+                usabilityStats.contentionTimeStats[2].contentionTimeAvgMicros);
+        assertEquals(stats.contentionNumSamplesVi,
+                usabilityStats.contentionTimeStats[2].contentionNumSamples);
+        assertEquals(stats.contentionTimeMinVoInUsec,
+                usabilityStats.contentionTimeStats[3].contentionTimeMinMicros);
+        assertEquals(stats.contentionTimeMaxVoInUsec,
+                usabilityStats.contentionTimeStats[3].contentionTimeMaxMicros);
+        assertEquals(stats.contentionTimeAvgVoInUsec,
+                usabilityStats.contentionTimeStats[3].contentionTimeAvgMicros);
+        assertEquals(stats.contentionNumSamplesVo,
+                usabilityStats.contentionTimeStats[3].contentionNumSamples);
+        for (int i = 0; i < stats.peerInfo.length; i++) {
+            PeerInfo curPeer = stats.peerInfo[i];
+            assertEquals(curPeer.staCount, usabilityStats.staCount);
+            assertEquals(curPeer.chanUtil, usabilityStats.channelUtilization);
+            for (int j = 0; j < curPeer.rateStats.length; j++) {
+                RateStat rate = curPeer.rateStats[j];
+                RateStats usabilityRate = usabilityStats.rateStats[j];
+                assertEquals(rate.preamble, usabilityRate.preamble);
+                assertEquals(rate.nss, usabilityRate.nss);
+                assertEquals(rate.bw, usabilityRate.bw);
+                assertEquals(rate.rateMcsIdx, usabilityRate.rateMcsIdx);
+                assertEquals(rate.bitRateInKbps, usabilityRate.bitRateInKbps);
+                assertEquals(rate.txMpdu, usabilityRate.txMpdu);
+                assertEquals(rate.rxMpdu, usabilityRate.rxMpdu);
+                assertEquals(rate.mpduLost, usabilityRate.mpduLost);
+                assertEquals(rate.retries, usabilityRate.retries);
+            }
+        }
     }
 
     // Simulate adding a LABEL_GOOD WifiUsabilityStats
@@ -3504,7 +4063,7 @@
         when(info.getLinkSpeed()).thenReturn(nextRandInt());
         WifiLinkLayerStats stats = start;
         for (int i = 0; i < WifiMetrics.NUM_WIFI_USABILITY_STATS_ENTRIES_PER_WIFI_GOOD; i++) {
-            mWifiMetrics.updateWifiUsabilityStatsEntries(info, stats);
+            mWifiMetrics.updateWifiUsabilityStatsEntries(TEST_IFACE_NAME, info, stats);
             stats = nextRandomStats(stats);
         }
         return stats;
@@ -3517,9 +4076,9 @@
         when(info.getLinkSpeed()).thenReturn(nextRandInt());
         WifiLinkLayerStats stats1 = start;
         WifiLinkLayerStats stats2 = nextRandomStats(stats1);
-        mWifiMetrics.updateWifiUsabilityStatsEntries(info, stats1);
-        mWifiMetrics.updateWifiUsabilityStatsEntries(info, stats2);
-        mWifiMetrics.addToWifiUsabilityStatsList(WifiUsabilityStats.LABEL_BAD,
+        mWifiMetrics.updateWifiUsabilityStatsEntries(TEST_IFACE_NAME, info, stats1);
+        mWifiMetrics.updateWifiUsabilityStatsEntries(TEST_IFACE_NAME, info, stats2);
+        mWifiMetrics.addToWifiUsabilityStatsList(TEST_IFACE_NAME, WifiUsabilityStats.LABEL_BAD,
                 WifiUsabilityStats.TYPE_DATA_STALL_BAD_TX, -1);
         return nextRandomStats(stats2);
     }
@@ -3540,21 +4099,26 @@
         when(info.getRxLinkSpeedMbps()).thenReturn(nextRandInt());
         when(info.getBSSID()).thenReturn("Wifi");
         when(info.getFrequency()).thenReturn(5745);
+        when(mWifiDataStall.isCellularDataAvailable()).thenReturn(true);
+        when(mWifiDataStall.isThroughputSufficient()).thenReturn(false);
+        when(mWifiChannelUtilization.getUtilizationRatio(anyInt())).thenReturn(150);
+        when(mWifiSettingsStore.isWifiScoringEnabled()).thenReturn(true);
 
-        WifiLinkLayerStats stats1 = nextRandomStats(new WifiLinkLayerStats());
+        WifiLinkLayerStats stats1 = nextRandomStats(createNewWifiLinkLayerStats());
         WifiLinkLayerStats stats2 = nextRandomStats(stats1);
-        mWifiMetrics.incrementWifiScoreCount(60);
-        mWifiMetrics.incrementWifiUsabilityScoreCount(2, 55, 15);
-        mWifiMetrics.logLinkProbeSuccess(nextRandInt(), nextRandInt(), nextRandInt(), 12);
-        mWifiMetrics.updateWifiUsabilityStatsEntries(info, stats1);
-        mWifiMetrics.incrementWifiScoreCount(58);
-        mWifiMetrics.incrementWifiUsabilityScoreCount(3, 56, 15);
-        mWifiMetrics.logLinkProbeFailure(nextRandInt(), nextRandInt(),
+        mWifiMetrics.incrementWifiScoreCount(TEST_IFACE_NAME, 60);
+        mWifiMetrics.incrementWifiUsabilityScoreCount(TEST_IFACE_NAME, 2, 55, 15);
+        mWifiMetrics.logLinkProbeSuccess(
+                TEST_IFACE_NAME, nextRandInt(), nextRandInt(), nextRandInt(), 12);
+        mWifiMetrics.updateWifiUsabilityStatsEntries(TEST_IFACE_NAME, info, stats1);
+        mWifiMetrics.incrementWifiScoreCount(TEST_IFACE_NAME, 58);
+        mWifiMetrics.incrementWifiUsabilityScoreCount(TEST_IFACE_NAME, 3, 56, 15);
+        mWifiMetrics.logLinkProbeFailure(TEST_IFACE_NAME, nextRandInt(), nextRandInt(),
                 nextRandInt(), nextRandInt());
         mWifiMetrics.enterDeviceMobilityState(DEVICE_MOBILITY_STATE_HIGH_MVMT);
 
-        mWifiMetrics.updateWifiUsabilityStatsEntries(info, stats2);
-        mWifiMetrics.addToWifiUsabilityStatsList(WifiUsabilityStats.LABEL_BAD,
+        mWifiMetrics.updateWifiUsabilityStatsEntries(TEST_IFACE_NAME, info, stats2);
+        mWifiMetrics.addToWifiUsabilityStatsList(TEST_IFACE_NAME, WifiUsabilityStats.LABEL_BAD,
                 WifiUsabilityStats.TYPE_DATA_STALL_BAD_TX, -1);
 
         // Add 2 LABEL_GOOD but only 1 should remain in the converted proto
@@ -3596,6 +4160,36 @@
         assertEquals(DEVICE_MOBILITY_STATE_HIGH_MVMT, mDecodedProto.wifiUsabilityStatsList[1]
                 .stats[mDecodedProto.wifiUsabilityStatsList[1].stats.length - 1]
                 .deviceMobilityState);
+        assertEquals(true, mDecodedProto.wifiUsabilityStatsList[0].stats[0].isWifiScoringEnabled);
+        assertEquals(true,
+                mDecodedProto.wifiUsabilityStatsList[1].stats[0].isCellularDataAvailable);
+        assertEquals(false,
+                mDecodedProto.wifiUsabilityStatsList[1].stats[1].isThroughputSufficient);
+        assertEquals(150,
+                mDecodedProto.wifiUsabilityStatsList[0].stats[0].channelUtilizationRatio);
+    }
+
+    private WifiLinkLayerStats createNewWifiLinkLayerStats() {
+        WifiLinkLayerStats stats = new WifiLinkLayerStats();
+        RateStat[] rateStats = new RateStat[1];
+        rateStats[0] = new RateStat();
+        rateStats[0].preamble = 1;
+        rateStats[0].nss = 1;
+        rateStats[0].bw = 2;
+        rateStats[0].rateMcsIdx = 5;
+        rateStats[0].bitRateInKbps = 2000;
+        PeerInfo[] peerInfo = new PeerInfo[1];
+        peerInfo[0] = new PeerInfo();
+        peerInfo[0].rateStats = rateStats;
+        stats.peerInfo = peerInfo;
+        RadioStat[] radioStats = new RadioStat[2];
+        for (int i = 0; i < 2; i++) {
+            RadioStat radio = new RadioStat();
+            radio.radio_id = i;
+            radioStats[i] = radio;
+        }
+        stats.radioStats = radioStats;
+        return stats;
     }
 
     /**
@@ -3697,19 +4291,19 @@
         WifiLinkLayerStats stats3 = new WifiLinkLayerStats();
         WifiLinkLayerStats stats4 = new WifiLinkLayerStats();
         for (int i = 0; i < WifiMetrics.MAX_WIFI_USABILITY_STATS_ENTRIES_LIST_SIZE - 1; i++) {
-            mWifiMetrics.updateWifiUsabilityStatsEntries(info, stats3);
+            mWifiMetrics.updateWifiUsabilityStatsEntries(TEST_IFACE_NAME, info, stats3);
             stats3 = nextRandomStats(stats3);
         }
-        mWifiMetrics.updateWifiUsabilityStatsEntries(info, stats3);
-        mWifiMetrics.addToWifiUsabilityStatsList(WifiUsabilityStats.LABEL_BAD,
+        mWifiMetrics.updateWifiUsabilityStatsEntries(TEST_IFACE_NAME, info, stats3);
+        mWifiMetrics.addToWifiUsabilityStatsList(TEST_IFACE_NAME, WifiUsabilityStats.LABEL_BAD,
                 WifiUsabilityStats.TYPE_DATA_STALL_BAD_TX, -1);
         for (int i = 0; i < WifiMetrics.MAX_WIFI_USABILITY_STATS_ENTRIES_LIST_SIZE - 1; i++) {
-            mWifiMetrics.updateWifiUsabilityStatsEntries(info, stats4);
+            mWifiMetrics.updateWifiUsabilityStatsEntries(TEST_IFACE_NAME, info, stats4);
             stats4 = nextRandomStats(stats4);
         }
         stats4.timeStampInMs = stats3.timeStampInMs - 1 + WifiMetrics.MIN_DATA_STALL_WAIT_MS;
-        mWifiMetrics.updateWifiUsabilityStatsEntries(info, stats4);
-        mWifiMetrics.addToWifiUsabilityStatsList(WifiUsabilityStats.LABEL_BAD,
+        mWifiMetrics.updateWifiUsabilityStatsEntries(TEST_IFACE_NAME, info, stats4);
+        mWifiMetrics.addToWifiUsabilityStatsList(TEST_IFACE_NAME, WifiUsabilityStats.LABEL_BAD,
                 WifiUsabilityStats.TYPE_DATA_STALL_BAD_TX, -1);
         dumpProtoAndDeserialize();
         assertEquals(2, mDecodedProto.wifiUsabilityStatsList.length);
@@ -3736,19 +4330,19 @@
         WifiLinkLayerStats stats3 = new WifiLinkLayerStats();
         WifiLinkLayerStats stats4 = new WifiLinkLayerStats();
         for (int i = 0; i < WifiMetrics.MAX_WIFI_USABILITY_STATS_ENTRIES_LIST_SIZE - 1; i++) {
-            mWifiMetrics.updateWifiUsabilityStatsEntries(info, stats3);
+            mWifiMetrics.updateWifiUsabilityStatsEntries(TEST_IFACE_NAME, info, stats3);
             stats3 = nextRandomStats(stats3);
         }
-        mWifiMetrics.updateWifiUsabilityStatsEntries(info, stats3);
-        mWifiMetrics.addToWifiUsabilityStatsList(WifiUsabilityStats.LABEL_BAD,
+        mWifiMetrics.updateWifiUsabilityStatsEntries(TEST_IFACE_NAME, info, stats3);
+        mWifiMetrics.addToWifiUsabilityStatsList(TEST_IFACE_NAME, WifiUsabilityStats.LABEL_BAD,
                 WifiUsabilityStats.TYPE_DATA_STALL_BAD_TX, -1);
         for (int i = 0; i < WifiMetrics.MAX_WIFI_USABILITY_STATS_ENTRIES_LIST_SIZE - 1; i++) {
-            mWifiMetrics.updateWifiUsabilityStatsEntries(info, stats4);
+            mWifiMetrics.updateWifiUsabilityStatsEntries(TEST_IFACE_NAME, info, stats4);
             stats4 = nextRandomStats(stats4);
         }
         stats4.timeStampInMs = stats3.timeStampInMs + 1 + WifiMetrics.MIN_DATA_STALL_WAIT_MS;
-        mWifiMetrics.updateWifiUsabilityStatsEntries(info, stats4);
-        mWifiMetrics.addToWifiUsabilityStatsList(WifiUsabilityStats.LABEL_BAD,
+        mWifiMetrics.updateWifiUsabilityStatsEntries(TEST_IFACE_NAME, info, stats4);
+        mWifiMetrics.addToWifiUsabilityStatsList(TEST_IFACE_NAME, WifiUsabilityStats.LABEL_BAD,
                 WifiUsabilityStats.TYPE_DATA_STALL_BAD_TX, -1);
         dumpProtoAndDeserialize();
         assertEquals(4, mDecodedProto.wifiUsabilityStatsList.length);
@@ -3885,14 +4479,13 @@
         // Register Client for verification.
         ArgumentCaptor<android.net.wifi.WifiUsabilityStatsEntry> usabilityStats =
                 ArgumentCaptor.forClass(android.net.wifi.WifiUsabilityStatsEntry.class);
-        mWifiMetrics.addOnWifiUsabilityListener(mAppBinder, mOnWifiUsabilityStatsListener,
-                TEST_WIFI_USABILITY_STATS_LISTENER_IDENTIFIER);
+        mWifiMetrics.addOnWifiUsabilityListener(mOnWifiUsabilityStatsListener);
         WifiInfo info = mock(WifiInfo.class);
         when(info.getRssi()).thenReturn(nextRandInt());
         when(info.getLinkSpeed()).thenReturn(nextRandInt());
 
         WifiLinkLayerStats linkLayerStats = nextRandomStats(new WifiLinkLayerStats());
-        mWifiMetrics.updateWifiUsabilityStatsEntries(info, linkLayerStats);
+        mWifiMetrics.updateWifiUsabilityStatsEntries(TEST_IFACE_NAME, info, linkLayerStats);
 
         // Client should get the stats.
         verify(mOnWifiUsabilityStatsListener).onWifiUsabilityStats(anyInt(), anyBoolean(),
@@ -3912,16 +4505,15 @@
     @Test
     public void testRemoveClient() throws RemoteException {
         // Register Client for verification.
-        mWifiMetrics.addOnWifiUsabilityListener(mAppBinder, mOnWifiUsabilityStatsListener,
-                TEST_WIFI_USABILITY_STATS_LISTENER_IDENTIFIER);
-        mWifiMetrics.removeOnWifiUsabilityListener(TEST_WIFI_USABILITY_STATS_LISTENER_IDENTIFIER);
+        mWifiMetrics.addOnWifiUsabilityListener(mOnWifiUsabilityStatsListener);
+        mWifiMetrics.removeOnWifiUsabilityListener(mOnWifiUsabilityStatsListener);
         verify(mAppBinder).unlinkToDeath(any(), anyInt());
 
         WifiInfo info = mock(WifiInfo.class);
         when(info.getRssi()).thenReturn(nextRandInt());
         when(info.getLinkSpeed()).thenReturn(nextRandInt());
         WifiLinkLayerStats linkLayerStats = nextRandomStats(new WifiLinkLayerStats());
-        mWifiMetrics.updateWifiUsabilityStatsEntries(info, linkLayerStats);
+        mWifiMetrics.updateWifiUsabilityStatsEntries(TEST_IFACE_NAME, info, linkLayerStats);
 
         verify(mOnWifiUsabilityStatsListener, never()).onWifiUsabilityStats(anyInt(),
                 anyBoolean(), any());
@@ -3932,8 +4524,7 @@
      */
     @Test
     public void testAddsForBinderDeathOnAddClient() throws Exception {
-        mWifiMetrics.addOnWifiUsabilityListener(mAppBinder, mOnWifiUsabilityStatsListener,
-                TEST_WIFI_USABILITY_STATS_LISTENER_IDENTIFIER);
+        mWifiMetrics.addOnWifiUsabilityListener(mOnWifiUsabilityStatsListener);
         verify(mAppBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
     }
 
@@ -3944,15 +4535,14 @@
     public void testAddsListenerFailureOnLinkToDeath() throws Exception {
         doThrow(new RemoteException())
                 .when(mAppBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
-        mWifiMetrics.addOnWifiUsabilityListener(mAppBinder, mOnWifiUsabilityStatsListener,
-                TEST_WIFI_USABILITY_STATS_LISTENER_IDENTIFIER);
+        mWifiMetrics.addOnWifiUsabilityListener(mOnWifiUsabilityStatsListener);
         verify(mAppBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
 
         WifiInfo info = mock(WifiInfo.class);
         when(info.getRssi()).thenReturn(nextRandInt());
         when(info.getLinkSpeed()).thenReturn(nextRandInt());
         WifiLinkLayerStats linkLayerStats = nextRandomStats(new WifiLinkLayerStats());
-        mWifiMetrics.updateWifiUsabilityStatsEntries(info, linkLayerStats);
+        mWifiMetrics.updateWifiUsabilityStatsEntries(TEST_IFACE_NAME, info, linkLayerStats);
 
         // Client should not get any message listener add failed.
         verify(mOnWifiUsabilityStatsListener, never()).onWifiUsabilityStats(anyInt(),
@@ -3972,12 +4562,12 @@
         long eventTimeMs = nextRandInt();
         when(mClock.getElapsedSinceBootMillis()).thenReturn(eventTimeMs);
         WifiLinkLayerStats stats1 = nextRandomStats(new WifiLinkLayerStats());
-        mWifiMetrics.updateWifiUsabilityStatsEntries(info, stats1);
+        mWifiMetrics.updateWifiUsabilityStatsEntries(TEST_IFACE_NAME, info, stats1);
 
         // Add 1 LABEL_GOOD
         WifiLinkLayerStats statsGood = addGoodWifiUsabilityStats(nextRandomStats(stats1));
         // Firmware alert occurs
-        mWifiMetrics.logFirmwareAlert(2);
+        mWifiMetrics.logFirmwareAlert(TEST_IFACE_NAME, 2);
 
         dumpProtoAndDeserialize();
         assertEquals(2, mDecodedProto.wifiUsabilityStatsList.length);
@@ -4003,12 +4593,12 @@
         long eventTimeMs = nextRandInt();
         when(mClock.getElapsedSinceBootMillis()).thenReturn(eventTimeMs);
         WifiLinkLayerStats stats1 = nextRandomStats(new WifiLinkLayerStats());
-        mWifiMetrics.updateWifiUsabilityStatsEntries(info, stats1);
+        mWifiMetrics.updateWifiUsabilityStatsEntries(TEST_IFACE_NAME, info, stats1);
 
         // Add 1 LABEL_GOOD
         WifiLinkLayerStats statsGood = addGoodWifiUsabilityStats(nextRandomStats(stats1));
         // Wifi data stall occurs
-        mWifiMetrics.addToWifiUsabilityStatsList(WifiUsabilityStats.LABEL_BAD,
+        mWifiMetrics.addToWifiUsabilityStatsList(TEST_IFACE_NAME, WifiUsabilityStats.LABEL_BAD,
                 WifiIsUnusableEvent.TYPE_DATA_STALL_BAD_TX, -1);
 
         dumpProtoAndDeserialize();
@@ -4098,14 +4688,14 @@
      */
     @Test
     public void testLogLinkProbeMetrics() throws Exception {
-        mWifiMetrics.logLinkProbeSuccess(10000, -75, 50, 5);
-        mWifiMetrics.logLinkProbeFailure(30000, -80, 10,
+        mWifiMetrics.logLinkProbeSuccess(TEST_IFACE_NAME, 10000, -75, 50, 5);
+        mWifiMetrics.logLinkProbeFailure(TEST_IFACE_NAME, 30000, -80, 10,
                 WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_NO_ACK);
-        mWifiMetrics.logLinkProbeSuccess(3000, -71, 160, 12);
-        mWifiMetrics.logLinkProbeFailure(40000, -80, 6,
+        mWifiMetrics.logLinkProbeSuccess(TEST_IFACE_NAME, 3000, -71, 160, 12);
+        mWifiMetrics.logLinkProbeFailure(TEST_IFACE_NAME, 40000, -80, 6,
                 WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_NO_ACK);
-        mWifiMetrics.logLinkProbeSuccess(5000, -73, 160, 10);
-        mWifiMetrics.logLinkProbeFailure(2000, -78, 6,
+        mWifiMetrics.logLinkProbeSuccess(TEST_IFACE_NAME, 5000, -73, 160, 10);
+        mWifiMetrics.logLinkProbeFailure(TEST_IFACE_NAME, 2000, -78, 6,
                 WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_TIMEOUT);
 
         dumpProtoAndDeserialize();
@@ -4271,7 +4861,17 @@
         mWifiMetrics.incrementNetworkRequestApiMatchSizeHistogram(0);
         mWifiMetrics.incrementNetworkRequestApiMatchSizeHistogram(1);
 
-        mWifiMetrics.incrementNetworkRequestApiNumConnectSuccess();
+        mWifiMetrics.incrementNetworkRequestApiNumConnectSuccessOnPrimaryIface();
+        mWifiMetrics.incrementNetworkRequestApiNumConnectSuccessOnPrimaryIface();
+
+        mWifiMetrics.incrementNetworkRequestApiNumConnectSuccessOnSecondaryIface();
+
+        mWifiMetrics.incrementNetworkRequestApiNumConnectOnPrimaryIface();
+        mWifiMetrics.incrementNetworkRequestApiNumConnectOnPrimaryIface();
+
+        mWifiMetrics.incrementNetworkRequestApiNumConnectOnSecondaryIface();
+        mWifiMetrics.incrementNetworkRequestApiNumConnectOnSecondaryIface();
+        mWifiMetrics.incrementNetworkRequestApiNumConnectOnSecondaryIface();
 
         mWifiMetrics.incrementNetworkRequestApiNumUserApprovalBypass();
         mWifiMetrics.incrementNetworkRequestApiNumUserApprovalBypass();
@@ -4280,10 +4880,26 @@
 
         mWifiMetrics.incrementNetworkRequestApiNumApps();
 
+        mWifiMetrics.incrementNetworkRequestApiConnectionDurationSecOnPrimaryIfaceHistogram(40);
+        mWifiMetrics.incrementNetworkRequestApiConnectionDurationSecOnPrimaryIfaceHistogram(670);
+        mWifiMetrics.incrementNetworkRequestApiConnectionDurationSecOnPrimaryIfaceHistogram(1801);
+
+        mWifiMetrics.incrementNetworkRequestApiConnectionDurationSecOnSecondaryIfaceHistogram(100);
+        mWifiMetrics.incrementNetworkRequestApiConnectionDurationSecOnSecondaryIfaceHistogram(350);
+        mWifiMetrics.incrementNetworkRequestApiConnectionDurationSecOnSecondaryIfaceHistogram(750);
+
+        mWifiMetrics.incrementNetworkRequestApiConcurrentConnectionDurationSecHistogram(10);
+        mWifiMetrics.incrementNetworkRequestApiConcurrentConnectionDurationSecHistogram(589);
+        mWifiMetrics.incrementNetworkRequestApiConcurrentConnectionDurationSecHistogram(2900);
+        mWifiMetrics.incrementNetworkRequestApiConcurrentConnectionDurationSecHistogram(145);
+
         dumpProtoAndDeserialize();
 
         assertEquals(3, mDecodedProto.wifiNetworkRequestApiLog.numRequest);
-        assertEquals(1, mDecodedProto.wifiNetworkRequestApiLog.numConnectSuccess);
+        assertEquals(2, mDecodedProto.wifiNetworkRequestApiLog.numConnectSuccessOnPrimaryIface);
+        assertEquals(1, mDecodedProto.wifiNetworkRequestApiLog.numConnectSuccessOnSecondaryIface);
+        assertEquals(2, mDecodedProto.wifiNetworkRequestApiLog.numConnectOnPrimaryIface);
+        assertEquals(3, mDecodedProto.wifiNetworkRequestApiLog.numConnectOnSecondaryIface);
         assertEquals(2, mDecodedProto.wifiNetworkRequestApiLog.numUserApprovalBypass);
         assertEquals(1, mDecodedProto.wifiNetworkRequestApiLog.numUserReject);
         assertEquals(1, mDecodedProto.wifiNetworkRequestApiLog.numApps);
@@ -4295,6 +4911,38 @@
         };
         assertHistogramBucketsEqual(expectedNetworkMatchSizeHistogram,
                 mDecodedProto.wifiNetworkRequestApiLog.networkMatchSizeHistogram);
+
+        HistogramBucketInt32[] expectedConnectionDurationOnPrimarySec = {
+                buildHistogramBucketInt32(0, toIntExact(Duration.ofMinutes(3).getSeconds()), 1),
+                buildHistogramBucketInt32(toIntExact(Duration.ofMinutes(10).getSeconds()),
+                        toIntExact(Duration.ofMinutes(30).getSeconds()), 1),
+                buildHistogramBucketInt32(toIntExact(Duration.ofMinutes(30).getSeconds()),
+                        toIntExact(Duration.ofHours(1).getSeconds()), 1)
+        };
+        assertHistogramBucketsEqual(expectedConnectionDurationOnPrimarySec,
+                mDecodedProto.wifiNetworkRequestApiLog
+                        .connectionDurationSecOnPrimaryIfaceHistogram);
+
+        HistogramBucketInt32[] expectedConnectionDurationOnSecondarySec = {
+                buildHistogramBucketInt32(0, toIntExact(Duration.ofMinutes(3).getSeconds()), 1),
+                buildHistogramBucketInt32(toIntExact(Duration.ofMinutes(3).getSeconds()),
+                        toIntExact(Duration.ofMinutes(10).getSeconds()), 1),
+                buildHistogramBucketInt32(toIntExact(Duration.ofMinutes(10).getSeconds()),
+                        toIntExact(Duration.ofMinutes(30).getSeconds()), 1),
+        };
+        assertHistogramBucketsEqual(expectedConnectionDurationOnSecondarySec,
+                mDecodedProto.wifiNetworkRequestApiLog
+                        .connectionDurationSecOnSecondaryIfaceHistogram);
+
+        HistogramBucketInt32[] expectedConcurrentConnectionDuration = {
+                buildHistogramBucketInt32(0, toIntExact(Duration.ofMinutes(3).getSeconds()), 2),
+                buildHistogramBucketInt32(toIntExact(Duration.ofMinutes(3).getSeconds()),
+                        toIntExact(Duration.ofMinutes(10).getSeconds()), 1),
+                buildHistogramBucketInt32(toIntExact(Duration.ofMinutes(30).getSeconds()),
+                        toIntExact(Duration.ofHours(1).getSeconds()), 1)
+        };
+        assertHistogramBucketsEqual(expectedConcurrentConnectionDuration,
+                mDecodedProto.wifiNetworkRequestApiLog.concurrentConnectionDurationSecHistogram);
     }
 
     /**
@@ -4343,6 +4991,12 @@
         mWifiMetrics.incrementNetworkSuggestionUserRevokePermission();
         mWifiMetrics.incrementNetworkSuggestionUserRevokePermission();
 
+        mWifiMetrics.addSuggestionExistsForSavedNetwork("savedNetwork");
+        mWifiMetrics.incrementNetworkSuggestionMoreThanOneSuggestionForSingleScanResult();
+        mWifiMetrics.addNetworkSuggestionPriorityGroup(0);
+        mWifiMetrics.addNetworkSuggestionPriorityGroup(1);
+        mWifiMetrics.addNetworkSuggestionPriorityGroup(1);
+
         dumpProtoAndDeserialize();
 
         assertEquals(4, mDecodedProto.wifiNetworkSuggestionApiLog.numModification);
@@ -4368,6 +5022,10 @@
         assertEquals(WifiMetricsProto.WifiNetworkSuggestionApiLog.TYPE_NON_PRIVILEGED,
                 mDecodedProto.wifiNetworkSuggestionApiLog.appCountPerType[2].appType);
         assertEquals(3, mDecodedProto.wifiNetworkSuggestionApiLog.appCountPerType[2].count);
+        assertEquals(1, mDecodedProto.wifiNetworkSuggestionApiLog.numMultipleSuggestions);
+        assertEquals(1, mDecodedProto.wifiNetworkSuggestionApiLog
+                .numSavedNetworksWithConfiguredSuggestion);
+        assertEquals(1, mDecodedProto.wifiNetworkSuggestionApiLog.numPriorityGroups);
     }
 
     /**
@@ -4451,12 +5109,12 @@
         long eventTimeMs = nextRandInt();
         when(mClock.getElapsedSinceBootMillis()).thenReturn(eventTimeMs);
         WifiLinkLayerStats stats1 = nextRandomStats(new WifiLinkLayerStats());
-        mWifiMetrics.updateWifiUsabilityStatsEntries(info, stats1);
+        mWifiMetrics.updateWifiUsabilityStatsEntries(TEST_IFACE_NAME, info, stats1);
 
         // Add 1 LABEL_GOOD
         WifiLinkLayerStats statsGood = addGoodWifiUsabilityStats(nextRandomStats(stats1));
         // IP reachability lost occurs
-        mWifiMetrics.addToWifiUsabilityStatsList(WifiUsabilityStats.LABEL_BAD,
+        mWifiMetrics.addToWifiUsabilityStatsList(TEST_IFACE_NAME, WifiUsabilityStats.LABEL_BAD,
                 WifiUsabilityStats.TYPE_IP_REACHABILITY_LOST, -1);
 
         dumpProtoAndDeserialize();
@@ -4761,7 +5419,7 @@
         when(info.getRssi()).thenReturn(nextRandInt());
         when(info.getLinkSpeed()).thenReturn(nextRandInt());
         WifiLinkLayerStats stats2 = new WifiLinkLayerStats();
-        mWifiMetrics.setWifiState(WifiMetricsProto.WifiLog.WIFI_ASSOCIATED);
+        mWifiMetrics.setWifiState(TEST_IFACE_NAME, WifiMetricsProto.WifiLog.WIFI_ASSOCIATED);
 
         addOneBadWifiUsabilityStats(info);
         if (isWifiScore) {
@@ -4770,17 +5428,18 @@
             stats2 = wifiUsabilityScoreBreachesLow(info, stats2);
         }
         if (isThereBadEvent) {
-            mWifiMetrics.logWifiIsUnusableEvent(WifiIsUnusableEvent.TYPE_DATA_STALL_BAD_TX, -1);
+            mWifiMetrics.logWifiIsUnusableEvent(TEST_IFACE_NAME,
+                    WifiIsUnusableEvent.TYPE_DATA_STALL_BAD_TX, -1);
         }
         when(mClock.getElapsedSinceBootMillis()).thenReturn(elapsedTimeAfterBreach);
-        mWifiMetrics.updateWifiUsabilityStatsEntries(info, stats2);
+        mWifiMetrics.updateWifiUsabilityStatsEntries(TEST_IFACE_NAME, info, stats2);
     }
 
     // Simulate adding one LABEL_BAD WifiUsabilityStats
     private void addOneBadWifiUsabilityStats(WifiInfo info) {
         WifiLinkLayerStats stats1 = new WifiLinkLayerStats();
-        mWifiMetrics.updateWifiUsabilityStatsEntries(info, stats1);
-        mWifiMetrics.addToWifiUsabilityStatsList(WifiUsabilityStats.LABEL_BAD,
+        mWifiMetrics.updateWifiUsabilityStatsEntries(TEST_IFACE_NAME, info, stats1);
+        mWifiMetrics.addToWifiUsabilityStatsList(TEST_IFACE_NAME, WifiUsabilityStats.LABEL_BAD,
                 WifiUsabilityStats.TYPE_DATA_STALL_BAD_TX, -1);
     }
 
@@ -4788,14 +5447,14 @@
     private WifiLinkLayerStats wifiScoreBreachesLow(WifiInfo info, WifiLinkLayerStats stats2) {
         int upper = WifiMetrics.LOW_WIFI_SCORE + 7;
         int lower = WifiMetrics.LOW_WIFI_SCORE - 8;
-        mWifiMetrics.incrementWifiScoreCount(upper);
-        mWifiMetrics.updateWifiUsabilityStatsEntries(info, stats2);
+        mWifiMetrics.incrementWifiScoreCount(TEST_IFACE_NAME, upper);
+        mWifiMetrics.updateWifiUsabilityStatsEntries(TEST_IFACE_NAME, info, stats2);
         stats2 = nextRandomStats(stats2);
         long timeMs = 0;
         when(mClock.getElapsedSinceBootMillis()).thenReturn(timeMs);
         // Wifi score breaches low
-        mWifiMetrics.incrementWifiScoreCount(lower);
-        mWifiMetrics.updateWifiUsabilityStatsEntries(info, stats2);
+        mWifiMetrics.incrementWifiScoreCount(TEST_IFACE_NAME, lower);
+        mWifiMetrics.updateWifiUsabilityStatsEntries(TEST_IFACE_NAME, info, stats2);
         stats2 = nextRandomStats(stats2);
         return stats2;
     }
@@ -4805,14 +5464,14 @@
             WifiLinkLayerStats stats2) {
         int upper = WifiMetrics.LOW_WIFI_USABILITY_SCORE + 7;
         int lower = WifiMetrics.LOW_WIFI_USABILITY_SCORE - 8;
-        mWifiMetrics.incrementWifiUsabilityScoreCount(1, upper, 30);
-        mWifiMetrics.updateWifiUsabilityStatsEntries(info, stats2);
+        mWifiMetrics.incrementWifiUsabilityScoreCount(TEST_IFACE_NAME, 1, upper, 30);
+        mWifiMetrics.updateWifiUsabilityStatsEntries(TEST_IFACE_NAME, info, stats2);
         stats2 = nextRandomStats(stats2);
         long timeMs = 0;
         when(mClock.getElapsedSinceBootMillis()).thenReturn(timeMs);
         // Wifi usability score breaches low
-        mWifiMetrics.incrementWifiUsabilityScoreCount(2, lower, 30);
-        mWifiMetrics.updateWifiUsabilityStatsEntries(info, stats2);
+        mWifiMetrics.incrementWifiUsabilityScoreCount(TEST_IFACE_NAME, 2, lower, 30);
+        mWifiMetrics.updateWifiUsabilityStatsEntries(TEST_IFACE_NAME, info, stats2);
         stats2 = nextRandomStats(stats2);
         return stats2;
     }
@@ -4851,23 +5510,23 @@
      */
     @Test
     public void verifyLabelBadStatsAreNotSavedIfScreenIsOff() throws Exception {
-        mWifiMetrics.setScreenState(false);
+        setScreenState(false);
         WifiInfo info = mock(WifiInfo.class);
         when(info.getRssi()).thenReturn(nextRandInt());
         when(info.getLinkSpeed()).thenReturn(nextRandInt());
         WifiLinkLayerStats stats1 = nextRandomStats(new WifiLinkLayerStats());
-        mWifiMetrics.updateWifiUsabilityStatsEntries(info, stats1);
+        mWifiMetrics.updateWifiUsabilityStatsEntries(TEST_IFACE_NAME, info, stats1);
 
         // Add 1 LABEL_GOOD
         WifiLinkLayerStats statsGood = addGoodWifiUsabilityStats(nextRandomStats(stats1));
         // IP reachability lost occurs
-        mWifiMetrics.addToWifiUsabilityStatsList(WifiUsabilityStats.LABEL_BAD,
+        mWifiMetrics.addToWifiUsabilityStatsList(TEST_IFACE_NAME, WifiUsabilityStats.LABEL_BAD,
                 WifiUsabilityStats.TYPE_IP_REACHABILITY_LOST, -1);
         // Wifi data stall occurs
-        mWifiMetrics.addToWifiUsabilityStatsList(WifiUsabilityStats.LABEL_BAD,
+        mWifiMetrics.addToWifiUsabilityStatsList(TEST_IFACE_NAME, WifiUsabilityStats.LABEL_BAD,
                 WifiIsUnusableEvent.TYPE_DATA_STALL_BAD_TX, -1);
         // Firmware alert occurs
-        mWifiMetrics.logFirmwareAlert(2);
+        mWifiMetrics.logFirmwareAlert(TEST_IFACE_NAME, 2);
 
         dumpProtoAndDeserialize();
         assertEquals(0, mDecodedProto.wifiUsabilityStatsList.length);
@@ -4879,6 +5538,9 @@
     @Test
     public void testConnectionDurationStats() throws Exception {
         for (int i = 0; i < 2; i++) {
+            mWifiMetrics.incrementWifiScoreCount(TEST_IFACE_NAME, 52);
+            mWifiMetrics.incrementConnectionDuration(5000, false, true);
+            mWifiMetrics.incrementWifiScoreCount(TEST_IFACE_NAME, 40);
             mWifiMetrics.incrementConnectionDuration(5000, false, true);
             mWifiMetrics.incrementConnectionDuration(3000, true, true);
             mWifiMetrics.incrementConnectionDuration(1000, false, false);
@@ -4888,8 +5550,10 @@
 
         assertEquals(6000,
                 mDecodedProto.connectionDurationStats.totalTimeSufficientThroughputMs);
-        assertEquals(10000,
+        assertEquals(20000,
                 mDecodedProto.connectionDurationStats.totalTimeInsufficientThroughputMs);
+        assertEquals(10000,
+                mDecodedProto.connectionDurationStats.totalTimeInsufficientThroughputDefaultWifiMs);
         assertEquals(3000,
                 mDecodedProto.connectionDurationStats.totalTimeCellularDataOffMs);
     }
@@ -5110,22 +5774,22 @@
     public void testOverlappingConnectionEvent() throws Exception {
         // Connection event 1
         when(mClock.getElapsedSinceBootMillis()).thenReturn((long) 0);
-        mWifiMetrics.startConnectionEvent(mTestWifiConfig, "TestNetwork",
-                WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, mTestWifiConfig,
+                "TestNetwork", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
         when(mClock.getElapsedSinceBootMillis()).thenReturn((long) 1000);
         // Connection event 2 overlaps with 1
-        assertEquals(1000, mWifiMetrics.startConnectionEvent(mTestWifiConfig, "TestNetwork",
-                WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE));
+        assertEquals(1000, mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, mTestWifiConfig,
+                "TestNetwork", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE));
 
         // Connection event 2 ends
-        mWifiMetrics.endConnectionEvent(
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
                 WifiMetrics.ConnectionEvent.FAILURE_NONE,
                 WifiMetricsProto.ConnectionEvent.HLF_NONE,
-                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
+                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, 0);
         when(mClock.getElapsedSinceBootMillis()).thenReturn((long) 2000);
         // Connection event 3 doesn't overlap with 2
-        assertEquals(0, mWifiMetrics.startConnectionEvent(mTestWifiConfig, "TestNetwork",
-                WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE));
+        assertEquals(0, mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, mTestWifiConfig,
+                "TestNetwork", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE));
     }
 
     @Test
@@ -5153,12 +5817,12 @@
     public void testConnectionNetworkTypePasspointFromOsu() throws Exception {
         WifiConfiguration config = WifiConfigurationTestUtil.createPasspointNetwork();
         config.updateIdentifier = "7";
-        mWifiMetrics.startConnectionEvent(config, "RED",
-                WifiMetricsProto.ConnectionEvent.ROAM_NONE);
-        mWifiMetrics.endConnectionEvent(
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, config,
+                "RED", WifiMetricsProto.ConnectionEvent.ROAM_NONE);
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
                 WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_TIMED_OUT,
                 WifiMetricsProto.ConnectionEvent.HLF_NONE,
-                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
+                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, 0);
         dumpProtoAndDeserialize();
 
         assertEquals(1, mDecodedProto.connectionEvent.length);
@@ -5166,4 +5830,708 @@
                 mDecodedProto.connectionEvent[0].networkType);
         assertTrue(mDecodedProto.connectionEvent[0].isOsuProvisioned);
     }
+
+    @Test
+    public void testFirstConnectAfterBootStats() throws Exception {
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(1000L);
+        mWifiMetrics.noteWifiEnabledDuringBoot(true);
+
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(2000L);
+        mWifiMetrics.noteFirstNetworkSelectionAfterBoot(true);
+
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(3000L);
+        mWifiMetrics.noteFirstL2ConnectionAfterBoot(true);
+
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(4000L);
+        mWifiMetrics.noteFirstL3ConnectionAfterBoot(true);
+
+        dumpProtoAndDeserialize();
+
+        assertEquals(1000, mDecodedProto
+                .firstConnectAfterBootStats.wifiEnabledAtBoot.timestampSinceBootMillis);
+        assertTrue(mDecodedProto.firstConnectAfterBootStats.wifiEnabledAtBoot.isSuccess);
+        assertEquals(2000, mDecodedProto
+                .firstConnectAfterBootStats.firstNetworkSelection.timestampSinceBootMillis);
+        assertTrue(mDecodedProto.firstConnectAfterBootStats.firstNetworkSelection.isSuccess);
+        assertEquals(3000, mDecodedProto
+                .firstConnectAfterBootStats.firstL2Connection.timestampSinceBootMillis);
+        assertTrue(mDecodedProto.firstConnectAfterBootStats.firstL2Connection.isSuccess);
+        assertEquals(4000, mDecodedProto
+                .firstConnectAfterBootStats.firstL3Connection.timestampSinceBootMillis);
+        assertTrue(mDecodedProto.firstConnectAfterBootStats.firstL3Connection.isSuccess);
+    }
+
+    @Test
+    public void testFirstConnectAfterBootStats_firstCallWins() throws Exception {
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(1000L);
+        mWifiMetrics.noteWifiEnabledDuringBoot(true);
+
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(2000L);
+        mWifiMetrics.noteWifiEnabledDuringBoot(false);
+
+        dumpProtoAndDeserialize();
+
+        assertEquals(1000, mDecodedProto
+                .firstConnectAfterBootStats.wifiEnabledAtBoot.timestampSinceBootMillis);
+        assertTrue(mDecodedProto.firstConnectAfterBootStats.wifiEnabledAtBoot.isSuccess);
+    }
+
+    @Test
+    public void testFirstConnectAfterBootStats_secondDumpNull() throws Exception {
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(1000L);
+        mWifiMetrics.noteWifiEnabledDuringBoot(true);
+
+        dumpProtoAndDeserialize();
+
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(2000L);
+        mWifiMetrics.noteWifiEnabledDuringBoot(false);
+
+        dumpProtoAndDeserialize();
+
+        assertNull(mDecodedProto.firstConnectAfterBootStats);
+    }
+
+    @Test
+    public void testFirstConnectAfterBootStats_falseInvalidatesSubsequentCalls() throws Exception {
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(1000L);
+        mWifiMetrics.noteWifiEnabledDuringBoot(false);
+
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(2000L);
+        mWifiMetrics.noteFirstNetworkSelectionAfterBoot(true);
+
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(3000L);
+        mWifiMetrics.noteFirstL2ConnectionAfterBoot(true);
+
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(4000L);
+        mWifiMetrics.noteFirstL3ConnectionAfterBoot(true);
+
+        dumpProtoAndDeserialize();
+
+        assertEquals(1000, mDecodedProto
+                .firstConnectAfterBootStats.wifiEnabledAtBoot.timestampSinceBootMillis);
+        assertFalse(mDecodedProto.firstConnectAfterBootStats.wifiEnabledAtBoot.isSuccess);
+        assertNull(mDecodedProto.firstConnectAfterBootStats.firstNetworkSelection);
+        assertNull(mDecodedProto.firstConnectAfterBootStats.firstL2Connection);
+        assertNull(mDecodedProto.firstConnectAfterBootStats.firstL3Connection);
+    }
+
+    @Test
+    public void testWifiConnectionResultAtomNotEmittedWithNoConnectionEndEvent() {
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, createComplexWifiConfig(),
+                "RED", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+
+        ExtendedMockito.verify(() -> WifiStatsLog.write(
+                anyInt(), anyBoolean(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(),
+                anyBoolean(), anyInt()),
+                times(0));
+    }
+
+    @Test
+    public void testWifiConnectionResultAtomNotEmittedWithNoConnectionStartEvent() {
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
+                WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE,
+                WifiMetricsProto.ConnectionEvent.HLF_DHCP,
+                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, TEST_CANDIDATE_FREQ);
+
+        ExtendedMockito.verify(() -> WifiStatsLog.write(
+                anyInt(), anyBoolean(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(),
+                anyBoolean(), anyInt()),
+                times(0));
+    }
+
+    @Test
+    public void testWifiConnectionResultAtomEmittedOnlyOnceWithMultipleConnectionEndEvents() {
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, createComplexWifiConfig(),
+                "RED", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+
+        for (int i = 0; i < 5; i++) {
+            mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
+                    WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE,
+                    WifiMetricsProto.ConnectionEvent.HLF_DHCP,
+                    WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, TEST_CANDIDATE_FREQ);
+        }
+
+        ExtendedMockito.verify(() -> WifiStatsLog.write(
+                WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED, false,
+                WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED__FAILURE_CODE__FAILURE_AUTHENTICATION_GENERAL,
+                -80, 0,
+                WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED__BAND__BAND_2G,
+                WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED__AUTH_TYPE__AUTH_TYPE_WPA2_PSK,
+                WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED__TRIGGER__AUTOCONNECT_BOOT,
+                true,
+                0),
+                times(1));
+    }
+
+    @Test
+    public void testWifiConnectionResultAtomNewSessionOverwritesPreviousSession() {
+
+        WifiConfiguration config1 = createComplexWifiConfig();
+        config1.getNetworkSelectionStatus().getCandidate().level = -50;
+
+        WifiConfiguration config2 = createComplexWifiConfig();
+        config2.getNetworkSelectionStatus().getCandidate().level = -60;
+
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, config1,
+                "RED", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, config2,
+                "RED", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
+                WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE,
+                WifiMetricsProto.ConnectionEvent.HLF_DHCP,
+                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, TEST_CANDIDATE_FREQ);
+
+        ExtendedMockito.verify(() -> WifiStatsLog.write(
+                WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED, false,
+                WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED__FAILURE_CODE__FAILURE_AUTHENTICATION_GENERAL,
+                -60, 0,
+                WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED__BAND__BAND_2G,
+                WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED__AUTH_TYPE__AUTH_TYPE_WPA2_PSK,
+                WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED__TRIGGER__AUTOCONNECT_BOOT,
+                true,
+                0),
+                times(1));
+    }
+
+    @Test
+    public void testWifiConnectionResultAtomHasCorrectTriggers() {
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, createComplexWifiConfig(),
+                "RED", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
+                WifiMetrics.ConnectionEvent.FAILURE_NONE,
+                WifiMetricsProto.ConnectionEvent.HLF_NONE,
+                WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_NONE, TEST_CANDIDATE_FREQ);
+
+        mWifiMetrics.reportNetworkDisconnect(TEST_IFACE_NAME, 0, 0, 0);
+
+        ExtendedMockito.verify(() -> WifiStatsLog.write(
+                eq(WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED), anyBoolean(),
+                anyInt(), anyInt(), anyInt(), anyInt(), anyInt(),
+                eq(WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED__TRIGGER__AUTOCONNECT_BOOT),
+                anyBoolean(), anyInt()));
+
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, createComplexWifiConfig(),
+                "RED", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
+                WifiMetrics.ConnectionEvent.FAILURE_NONE,
+                WifiMetricsProto.ConnectionEvent.HLF_NONE,
+                WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_NONE, TEST_CANDIDATE_FREQ);
+
+        mWifiMetrics.reportNetworkDisconnect(TEST_IFACE_NAME, 0, 0, 0);
+
+        ExtendedMockito.verify(() -> WifiStatsLog.write(
+                eq(WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED), anyBoolean(),
+                anyInt(), anyInt(), anyInt(), anyInt(), anyInt(),
+                eq(WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED__TRIGGER__RECONNECT_SAME_NETWORK),
+                anyBoolean(), anyInt()));
+
+        WifiConfiguration configOtherNetwork = createComplexWifiConfig();
+        configOtherNetwork.networkId = 21;
+        configOtherNetwork.SSID = "OtherNetwork";
+        mWifiMetrics.setNominatorForNetwork(configOtherNetwork.networkId,
+                WifiMetricsProto.ConnectionEvent.NOMINATOR_SAVED);
+
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, configOtherNetwork,
+                "RED", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
+                WifiMetrics.ConnectionEvent.FAILURE_NONE,
+                WifiMetricsProto.ConnectionEvent.HLF_NONE,
+                WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_NONE, TEST_CANDIDATE_FREQ);
+
+        mWifiMetrics.reportNetworkDisconnect(TEST_IFACE_NAME, 0, 0, 0);
+
+        ExtendedMockito.verify(() -> WifiStatsLog.write(
+                eq(WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED), anyBoolean(),
+                anyInt(), anyInt(), anyInt(), anyInt(), anyInt(),
+                eq(WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED__TRIGGER__AUTOCONNECT_CONFIGURED_NETWORK),
+                anyBoolean(), anyInt()));
+
+        WifiConfiguration config = createComplexWifiConfig();
+        config.networkId = 42;
+        mWifiMetrics.setNominatorForNetwork(config.networkId,
+                WifiMetricsProto.ConnectionEvent.NOMINATOR_MANUAL);
+
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, config,
+                "GREEN", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
+                WifiMetrics.ConnectionEvent.FAILURE_NONE,
+                WifiMetricsProto.ConnectionEvent.HLF_NONE,
+                WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_NONE, TEST_CANDIDATE_FREQ);
+
+        ExtendedMockito.verify(() -> WifiStatsLog.write(
+                eq(WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED), anyBoolean(),
+                anyInt(), anyInt(), anyInt(), anyInt(), anyInt(),
+                eq(WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED__TRIGGER__MANUAL),
+                anyBoolean(), anyInt()));
+    }
+
+    @Test
+    public void testWifiDisconnectAtomEmittedOnDisconnectFromSuccessfulSession() {
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, createComplexWifiConfig(),
+                "RED", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
+                WifiMetrics.ConnectionEvent.FAILURE_NONE,
+                WifiMetricsProto.ConnectionEvent.HLF_NONE,
+                WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_NONE, TEST_CANDIDATE_FREQ);
+
+        int linkSpeed = 100;
+        int reason = 42;
+        mWifiMetrics.reportNetworkDisconnect(TEST_IFACE_NAME, reason, TEST_CANDIDATE_LEVEL,
+                linkSpeed);
+
+        ExtendedMockito.verify(() -> WifiStatsLog.write(
+                WifiStatsLog.WIFI_DISCONNECT_REPORTED,
+                0,
+                reason,
+                WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED__BAND__BAND_2G,
+                WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED__AUTH_TYPE__AUTH_TYPE_WPA2_PSK,
+                TEST_CANDIDATE_LEVEL,
+                linkSpeed));
+    }
+
+    @Test
+    public void testWifiDisconnectAtomNotEmittedOnDisconnectFromNotConnectedSession() {
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, createComplexWifiConfig(),
+                "RED", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
+                WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE,
+                WifiMetricsProto.ConnectionEvent.HLF_DHCP,
+                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, TEST_CANDIDATE_FREQ);
+
+
+        int linkSpeed = 100;
+        int reason = 42;
+        mWifiMetrics.reportNetworkDisconnect(TEST_IFACE_NAME, reason, TEST_CANDIDATE_LEVEL,
+                linkSpeed);
+
+        ExtendedMockito.verify(() -> WifiStatsLog.write(
+                eq(WifiStatsLog.WIFI_DISCONNECT_REPORTED),
+                anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt()),
+                times(0));
+    }
+
+    @Test
+    public void testWifiDisconnectAtomNotEmittedWithNoSession() {
+        mWifiMetrics.reportNetworkDisconnect(TEST_IFACE_NAME, 0, TEST_CANDIDATE_LEVEL, 0);
+
+        ExtendedMockito.verify(() -> WifiStatsLog.write(
+                eq(WifiStatsLog.WIFI_DISCONNECT_REPORTED),
+                anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt()),
+                times(0));
+    }
+
+    @Test
+    public void testWifiStateChangedAtomEmittedOnSuccessfulConnectAndDisconnect() {
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, createComplexWifiConfig(),
+                "RED", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
+                WifiMetrics.ConnectionEvent.FAILURE_NONE,
+                WifiMetricsProto.ConnectionEvent.HLF_NONE,
+                WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_NONE, TEST_CANDIDATE_FREQ);
+
+        // TRUE must be emitted
+        ExtendedMockito.verify(() -> WifiStatsLog.write(
+                WifiStatsLog.WIFI_CONNECTION_STATE_CHANGED,
+                true,
+                WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED__BAND__BAND_2G,
+                WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED__AUTH_TYPE__AUTH_TYPE_WPA2_PSK));
+
+        int linkSpeed = 100;
+        int reason = 42;
+        mWifiMetrics.reportNetworkDisconnect(TEST_IFACE_NAME, reason, TEST_CANDIDATE_LEVEL,
+                linkSpeed);
+
+        // FALSE must be emitted
+        ExtendedMockito.verify(() -> WifiStatsLog.write(
+                WifiStatsLog.WIFI_CONNECTION_STATE_CHANGED,
+                false,
+                WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED__BAND__BAND_2G,
+                WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED__AUTH_TYPE__AUTH_TYPE_WPA2_PSK));
+    }
+
+    @Test
+    public void testWifiStateChangedAtomNotEmittedOnNotSuccessfulConnectAndDisconnect() {
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, createComplexWifiConfig(),
+                "RED", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
+                WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE,
+                WifiMetricsProto.ConnectionEvent.HLF_DHCP,
+                WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN, TEST_CANDIDATE_FREQ);
+
+        // TRUE must not be emitted
+        ExtendedMockito.verify(() -> WifiStatsLog.write(
+                eq(WifiStatsLog.WIFI_CONNECTION_STATE_CHANGED),
+                anyBoolean(), anyInt(), anyInt()),
+                times(0));
+
+        int linkSpeed = 100;
+        int reason = 42;
+        mWifiMetrics.reportNetworkDisconnect(TEST_IFACE_NAME, reason, TEST_CANDIDATE_LEVEL,
+                linkSpeed);
+
+        // But we still expect FALSE to be emitted
+        ExtendedMockito.verify(() -> WifiStatsLog.write(
+                WifiStatsLog.WIFI_CONNECTION_STATE_CHANGED,
+                false,
+                0,
+                0));
+    }
+
+    @Test
+    public void testWifiConnectionResultTimeSinceLastConnectionCorrect() {
+        when(mClock.getElapsedSinceBootMillis()).thenReturn((long) 10 * 1000);
+
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, createComplexWifiConfig(),
+                "RED", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
+                WifiMetrics.ConnectionEvent.FAILURE_NONE,
+                WifiMetricsProto.ConnectionEvent.HLF_NONE,
+                WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_NONE, TEST_CANDIDATE_FREQ);
+
+        ExtendedMockito.verify(() -> WifiStatsLog.write(
+                eq(WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED), anyBoolean(),
+                anyInt(), anyInt(), anyInt(), anyInt(), anyInt(),
+                eq(WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED__TRIGGER__AUTOCONNECT_BOOT),
+                anyBoolean(), eq(10)));
+
+        mWifiMetrics.reportNetworkDisconnect(TEST_IFACE_NAME, 0, 0, 0);
+
+        when(mClock.getElapsedSinceBootMillis()).thenReturn((long) 30 * 1000);
+
+        mWifiMetrics.startConnectionEvent(TEST_IFACE_NAME, createComplexWifiConfig(),
+                "RED", WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+
+        mWifiMetrics.endConnectionEvent(TEST_IFACE_NAME,
+                WifiMetrics.ConnectionEvent.FAILURE_NONE,
+                WifiMetricsProto.ConnectionEvent.HLF_NONE,
+                WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_NONE, TEST_CANDIDATE_FREQ);
+
+        ExtendedMockito.verify(() -> WifiStatsLog.write(
+                eq(WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED), anyBoolean(),
+                anyInt(), anyInt(), anyInt(), anyInt(), anyInt(),
+                eq(WifiStatsLog.WIFI_CONNECTION_RESULT_REPORTED__TRIGGER__RECONNECT_SAME_NETWORK),
+                anyBoolean(), eq(20)));
+
+        mWifiMetrics.reportNetworkDisconnect(TEST_IFACE_NAME, 0, 0, 0);
+    }
+
+    @Test
+    public void testWifiScanEmittedOnSuccess() {
+        WifiMetrics.ScanMetrics scanMetrics = mWifiMetrics.getScanMetrics();
+
+        scanMetrics.setImportance(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND);
+        scanMetrics.logScanStarted(WifiMetrics.ScanMetrics.SCAN_TYPE_SINGLE);
+        scanMetrics.logScanSucceeded(WifiMetrics.ScanMetrics.SCAN_TYPE_SINGLE, 4);
+
+        ExtendedMockito.verify(() -> WifiStatsLog.write(
+                WifiStatsLog.WIFI_SCAN_REPORTED,
+                WifiStatsLog.WIFI_SCAN_REPORTED__TYPE__TYPE_SINGLE,
+                WifiStatsLog.WIFI_SCAN_REPORTED__RESULT__RESULT_SUCCESS,
+                WifiStatsLog.WIFI_SCAN_REPORTED__SOURCE__SOURCE_NO_WORK_SOURCE,
+                WifiStatsLog.WIFI_SCAN_REPORTED__IMPORTANCE__IMPORTANCE_FOREGROUND,
+                0, 4));
+    }
+
+    @Test
+    public void testWifiScanEmittedOnFailedToStart() {
+        WifiMetrics.ScanMetrics scanMetrics = mWifiMetrics.getScanMetrics();
+
+        scanMetrics.logScanFailedToStart(WifiMetrics.ScanMetrics.SCAN_TYPE_SINGLE);
+
+        ExtendedMockito.verify(() -> WifiStatsLog.write(
+                WifiStatsLog.WIFI_SCAN_REPORTED,
+                WifiStatsLog.WIFI_SCAN_REPORTED__TYPE__TYPE_SINGLE,
+                WifiStatsLog.WIFI_SCAN_REPORTED__RESULT__RESULT_FAILED_TO_START,
+                WifiStatsLog.WIFI_SCAN_REPORTED__SOURCE__SOURCE_NO_WORK_SOURCE,
+                WifiStatsLog.WIFI_SCAN_REPORTED__IMPORTANCE__IMPORTANCE_UNKNOWN,
+                0, 0));
+    }
+
+    @Test
+    public void testWifiScanEmittedOnFailure() {
+        WifiMetrics.ScanMetrics scanMetrics = mWifiMetrics.getScanMetrics();
+
+        scanMetrics.logScanStarted(WifiMetrics.ScanMetrics.SCAN_TYPE_SINGLE);
+        scanMetrics.logScanFailed(WifiMetrics.ScanMetrics.SCAN_TYPE_SINGLE);
+
+        ExtendedMockito.verify(() -> WifiStatsLog.write(
+                WifiStatsLog.WIFI_SCAN_REPORTED,
+                WifiStatsLog.WIFI_SCAN_REPORTED__TYPE__TYPE_SINGLE,
+                WifiStatsLog.WIFI_SCAN_REPORTED__RESULT__RESULT_FAILED_TO_SCAN,
+                WifiStatsLog.WIFI_SCAN_REPORTED__SOURCE__SOURCE_NO_WORK_SOURCE,
+                WifiStatsLog.WIFI_SCAN_REPORTED__IMPORTANCE__IMPORTANCE_UNKNOWN,
+                0, 0));
+    }
+
+    @Test
+    public void testWifiScanNotEmittedWithNoStart() {
+        WifiMetrics.ScanMetrics scanMetrics = mWifiMetrics.getScanMetrics();
+
+        scanMetrics.logScanSucceeded(WifiMetrics.ScanMetrics.SCAN_TYPE_SINGLE, 4);
+
+        ExtendedMockito.verify(() -> WifiStatsLog.write(
+                eq(WifiStatsLog.WIFI_SCAN_REPORTED),
+                anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt()), times(0));
+    }
+
+    @Test
+    public void testWifiScanEmittedOnlyOnce() {
+        WifiMetrics.ScanMetrics scanMetrics = mWifiMetrics.getScanMetrics();
+
+        scanMetrics.logScanStarted(WifiMetrics.ScanMetrics.SCAN_TYPE_SINGLE);
+        scanMetrics.logScanSucceeded(WifiMetrics.ScanMetrics.SCAN_TYPE_SINGLE, 4);
+        scanMetrics.logScanSucceeded(WifiMetrics.ScanMetrics.SCAN_TYPE_SINGLE, 5);
+        scanMetrics.logScanSucceeded(WifiMetrics.ScanMetrics.SCAN_TYPE_SINGLE, 6);
+
+        ExtendedMockito.verify(() -> WifiStatsLog.write(
+                eq(WifiStatsLog.WIFI_SCAN_REPORTED),
+                anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), eq(4)), times(1));
+    }
+
+    @Test
+    public void testWifiScanStatePreservedAfterStart() {
+        WifiMetrics.ScanMetrics scanMetrics = mWifiMetrics.getScanMetrics();
+
+        scanMetrics.setImportance(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND);
+        scanMetrics.logScanStarted(WifiMetrics.ScanMetrics.SCAN_TYPE_SINGLE);
+        scanMetrics.setImportance(ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE);
+        scanMetrics.logScanSucceeded(WifiMetrics.ScanMetrics.SCAN_TYPE_SINGLE, 4);
+
+        ExtendedMockito.verify(() -> WifiStatsLog.write(
+                WifiStatsLog.WIFI_SCAN_REPORTED,
+                WifiStatsLog.WIFI_SCAN_REPORTED__TYPE__TYPE_SINGLE,
+                WifiStatsLog.WIFI_SCAN_REPORTED__RESULT__RESULT_SUCCESS,
+                WifiStatsLog.WIFI_SCAN_REPORTED__SOURCE__SOURCE_NO_WORK_SOURCE,
+                WifiStatsLog.WIFI_SCAN_REPORTED__IMPORTANCE__IMPORTANCE_FOREGROUND,
+                0, 4));
+    }
+
+    @Test
+    public void testWifiScanOverlappingRequestsOverwriteStateForSameType() {
+        WifiMetrics.ScanMetrics scanMetrics = mWifiMetrics.getScanMetrics();
+
+        scanMetrics.setImportance(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND);
+        scanMetrics.logScanStarted(WifiMetrics.ScanMetrics.SCAN_TYPE_SINGLE);
+
+        scanMetrics.setImportance(ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE);
+        scanMetrics.logScanStarted(WifiMetrics.ScanMetrics.SCAN_TYPE_SINGLE);
+
+        scanMetrics.logScanSucceeded(WifiMetrics.ScanMetrics.SCAN_TYPE_SINGLE, 42);
+        scanMetrics.logScanSucceeded(WifiMetrics.ScanMetrics.SCAN_TYPE_SINGLE, 21);
+
+        ExtendedMockito.verify(() -> WifiStatsLog.write(
+                WifiStatsLog.WIFI_SCAN_REPORTED,
+                WifiStatsLog.WIFI_SCAN_REPORTED__TYPE__TYPE_SINGLE,
+                WifiStatsLog.WIFI_SCAN_REPORTED__RESULT__RESULT_SUCCESS,
+                WifiStatsLog.WIFI_SCAN_REPORTED__SOURCE__SOURCE_NO_WORK_SOURCE,
+                WifiStatsLog.WIFI_SCAN_REPORTED__IMPORTANCE__IMPORTANCE_BACKGROUND,
+                0, 42));
+    }
+
+    @Test
+    public void testWifiScanOverlappingRequestsSeparateStatesForDifferentTypes() {
+        WifiMetrics.ScanMetrics scanMetrics = mWifiMetrics.getScanMetrics();
+
+        scanMetrics.setImportance(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND);
+        scanMetrics.logScanStarted(WifiMetrics.ScanMetrics.SCAN_TYPE_SINGLE);
+
+        scanMetrics.setImportance(ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE);
+        scanMetrics.logScanStarted(WifiMetrics.ScanMetrics.SCAN_TYPE_BACKGROUND);
+
+        scanMetrics.logScanSucceeded(WifiMetrics.ScanMetrics.SCAN_TYPE_SINGLE, 42);
+        scanMetrics.logScanSucceeded(WifiMetrics.ScanMetrics.SCAN_TYPE_BACKGROUND, 21);
+
+        ExtendedMockito.verify(() -> WifiStatsLog.write(
+                WifiStatsLog.WIFI_SCAN_REPORTED,
+                WifiStatsLog.WIFI_SCAN_REPORTED__TYPE__TYPE_SINGLE,
+                WifiStatsLog.WIFI_SCAN_REPORTED__RESULT__RESULT_SUCCESS,
+                WifiStatsLog.WIFI_SCAN_REPORTED__SOURCE__SOURCE_NO_WORK_SOURCE,
+                WifiStatsLog.WIFI_SCAN_REPORTED__IMPORTANCE__IMPORTANCE_FOREGROUND,
+                0, 42));
+
+        ExtendedMockito.verify(() -> WifiStatsLog.write(
+                WifiStatsLog.WIFI_SCAN_REPORTED,
+                WifiStatsLog.WIFI_SCAN_REPORTED__TYPE__TYPE_BACKGROUND,
+                WifiStatsLog.WIFI_SCAN_REPORTED__RESULT__RESULT_SUCCESS,
+                WifiStatsLog.WIFI_SCAN_REPORTED__SOURCE__SOURCE_NO_WORK_SOURCE,
+                WifiStatsLog.WIFI_SCAN_REPORTED__IMPORTANCE__IMPORTANCE_BACKGROUND,
+                0, 21));
+    }
+
+    private void setScreenState(boolean screenOn) {
+        BroadcastReceiver broadcastReceiver = mBroadcastReceiverCaptor.getValue();
+        assertNotNull(broadcastReceiver);
+        Intent intent = new Intent(screenOn  ? ACTION_SCREEN_ON : ACTION_SCREEN_OFF);
+        broadcastReceiver.onReceive(mContext, intent);
+    }
+
+    @Test
+    public void testWifiToWifiSwitchMetrics() throws Exception {
+        // initially all 0
+        dumpProtoAndDeserialize();
+
+        assertFalse(mDecodedProto.wifiToWifiSwitchStats.isMakeBeforeBreakSupported);
+        assertEquals(0, mDecodedProto.wifiToWifiSwitchStats.wifiToWifiSwitchTriggerCount);
+        assertEquals(0, mDecodedProto.wifiToWifiSwitchStats.makeBeforeBreakTriggerCount);
+        assertEquals(0, mDecodedProto.wifiToWifiSwitchStats.makeBeforeBreakNoInternetCount);
+        assertEquals(0, mDecodedProto.wifiToWifiSwitchStats.makeBeforeBreakRecoverPrimaryCount);
+        assertEquals(0, mDecodedProto.wifiToWifiSwitchStats.makeBeforeBreakInternetValidatedCount);
+        assertEquals(0, mDecodedProto.wifiToWifiSwitchStats.makeBeforeBreakSuccessCount);
+        assertEquals(0, mDecodedProto.wifiToWifiSwitchStats.makeBeforeBreakLingerCompletedCount);
+        assertEquals(0,
+                mDecodedProto.wifiToWifiSwitchStats.makeBeforeBreakLingerDurationSeconds.length);
+
+        // increment everything
+        mWifiMetrics.setIsMakeBeforeBreakSupported(true);
+        mWifiMetrics.incrementWifiToWifiSwitchTriggerCount();
+        mWifiMetrics.incrementMakeBeforeBreakTriggerCount();
+        mWifiMetrics.incrementMakeBeforeBreakNoInternetCount();
+        mWifiMetrics.incrementMakeBeforeBreakRecoverPrimaryCount();
+        mWifiMetrics.incrementMakeBeforeBreakInternetValidatedCount();
+        mWifiMetrics.incrementMakeBeforeBreakSuccessCount();
+        mWifiMetrics.incrementMakeBeforeBreakLingerCompletedCount(1000);
+
+        dumpProtoAndDeserialize();
+
+        // should be all 1
+        assertTrue(mDecodedProto.wifiToWifiSwitchStats.isMakeBeforeBreakSupported);
+        assertEquals(1, mDecodedProto.wifiToWifiSwitchStats.wifiToWifiSwitchTriggerCount);
+        assertEquals(1, mDecodedProto.wifiToWifiSwitchStats.makeBeforeBreakTriggerCount);
+        assertEquals(1, mDecodedProto.wifiToWifiSwitchStats.makeBeforeBreakNoInternetCount);
+        assertEquals(1, mDecodedProto.wifiToWifiSwitchStats.makeBeforeBreakRecoverPrimaryCount);
+        assertEquals(1, mDecodedProto.wifiToWifiSwitchStats.makeBeforeBreakInternetValidatedCount);
+        assertEquals(1, mDecodedProto.wifiToWifiSwitchStats.makeBeforeBreakSuccessCount);
+        assertEquals(1, mDecodedProto.wifiToWifiSwitchStats.makeBeforeBreakLingerCompletedCount);
+        assertEquals(1,
+                mDecodedProto.wifiToWifiSwitchStats.makeBeforeBreakLingerDurationSeconds.length);
+        assertEquals(1,
+                mDecodedProto.wifiToWifiSwitchStats.makeBeforeBreakLingerDurationSeconds[0].key);
+        assertEquals(1,
+                mDecodedProto.wifiToWifiSwitchStats.makeBeforeBreakLingerDurationSeconds[0].count);
+
+        // dump again
+        dumpProtoAndDeserialize();
+
+        // everything should be reset
+        assertFalse(mDecodedProto.wifiToWifiSwitchStats.isMakeBeforeBreakSupported);
+        assertEquals(0, mDecodedProto.wifiToWifiSwitchStats.wifiToWifiSwitchTriggerCount);
+        assertEquals(0, mDecodedProto.wifiToWifiSwitchStats.makeBeforeBreakTriggerCount);
+        assertEquals(0, mDecodedProto.wifiToWifiSwitchStats.makeBeforeBreakNoInternetCount);
+        assertEquals(0, mDecodedProto.wifiToWifiSwitchStats.makeBeforeBreakRecoverPrimaryCount);
+        assertEquals(0, mDecodedProto.wifiToWifiSwitchStats.makeBeforeBreakInternetValidatedCount);
+        assertEquals(0, mDecodedProto.wifiToWifiSwitchStats.makeBeforeBreakSuccessCount);
+        assertEquals(0, mDecodedProto.wifiToWifiSwitchStats.makeBeforeBreakLingerCompletedCount);
+        assertEquals(0,
+                mDecodedProto.wifiToWifiSwitchStats.makeBeforeBreakLingerDurationSeconds.length);
+    }
+
+    @Test
+    public void testPasspointConnectionMetrics() throws Exception {
+        // initially all 0
+        dumpProtoAndDeserialize();
+
+        assertEquals(0, mDecodedProto.totalNumberOfPasspointConnectionsWithVenueUrl);
+        assertEquals(0, mDecodedProto.totalNumberOfPasspointConnectionsWithTermsAndConditionsUrl);
+        assertEquals(0, mDecodedProto.totalNumberOfPasspointAcceptanceOfTermsAndConditions);
+        assertEquals(0, mDecodedProto.totalNumberOfPasspointProfilesWithDecoratedIdentity);
+        assertEquals(0, mDecodedProto.passpointDeauthImminentScope.length);
+
+        // increment everything
+        mWifiMetrics.incrementTotalNumberOfPasspointConnectionsWithVenueUrl();
+        mWifiMetrics.incrementTotalNumberOfPasspointConnectionsWithTermsAndConditionsUrl();
+        mWifiMetrics.incrementTotalNumberOfPasspointAcceptanceOfTermsAndConditions();
+        mWifiMetrics.incrementTotalNumberOfPasspointProfilesWithDecoratedIdentity();
+        mWifiMetrics.incrementPasspointDeauthImminentScope(true);
+        mWifiMetrics.incrementPasspointDeauthImminentScope(false);
+        mWifiMetrics.incrementPasspointDeauthImminentScope(false);
+
+        dumpProtoAndDeserialize();
+
+        Int32Count[] expectedDeauthImminentScope = {
+                buildInt32Count(WifiMetrics.PASSPOINT_DEAUTH_IMMINENT_SCOPE_ESS, 1),
+                buildInt32Count(WifiMetrics.PASSPOINT_DEAUTH_IMMINENT_SCOPE_BSS, 2),
+        };
+
+        assertEquals(1, mDecodedProto.totalNumberOfPasspointConnectionsWithVenueUrl);
+        assertEquals(1, mDecodedProto.totalNumberOfPasspointConnectionsWithTermsAndConditionsUrl);
+        assertEquals(1, mDecodedProto.totalNumberOfPasspointAcceptanceOfTermsAndConditions);
+        assertEquals(1, mDecodedProto.totalNumberOfPasspointProfilesWithDecoratedIdentity);
+        assertKeyCountsEqual(expectedDeauthImminentScope,
+                mDecodedProto.passpointDeauthImminentScope);
+
+        // dump again
+        dumpProtoAndDeserialize();
+
+        // everything should be reset
+        assertEquals(0, mDecodedProto.totalNumberOfPasspointConnectionsWithVenueUrl);
+        assertEquals(0, mDecodedProto.totalNumberOfPasspointConnectionsWithTermsAndConditionsUrl);
+        assertEquals(0, mDecodedProto.totalNumberOfPasspointAcceptanceOfTermsAndConditions);
+        assertEquals(0, mDecodedProto.totalNumberOfPasspointProfilesWithDecoratedIdentity);
+        assertEquals(0, mDecodedProto.passpointDeauthImminentScope.length);
+    }
+
+    @Test
+    public void testWifiStatsHealthStatWrite() throws Exception {
+        WifiInfo wifiInfo = mock(WifiInfo.class);
+        when(wifiInfo.getFrequency()).thenReturn(5810);
+        mWifiMetrics.incrementWifiScoreCount("",  60);
+        mWifiMetrics.handlePollResult(TEST_IFACE_NAME, wifiInfo);
+        mWifiMetrics.incrementConnectionDuration(3000, true, true);
+        ExtendedMockito.verify(() -> WifiStatsLog.write(
+                WifiStatsLog.WIFI_HEALTH_STAT_REPORTED, 3000, true, true,
+                WifiStatsLog.WIFI_HEALTH_STAT_REPORTED__BAND__BAND_5G_HIGH));
+
+        when(wifiInfo.getFrequency()).thenReturn(2412);
+        mWifiMetrics.incrementWifiScoreCount("",  30);
+        mWifiMetrics.handlePollResult(TEST_IFACE_NAME, wifiInfo);
+        mWifiMetrics.incrementConnectionDuration(2000, false, true);
+        ExtendedMockito.verify(() -> WifiStatsLog.write(
+                WifiStatsLog.WIFI_HEALTH_STAT_REPORTED, 2000, true, true,
+                WifiStatsLog.WIFI_HEALTH_STAT_REPORTED__BAND__BAND_2G));
+    }
+
+    /**
+     * Test number of times connection failure status reported per
+     * WifiConfiguration.RecentFailureReason
+     */
+    @Test
+    public void testRecentFailureAssociationStatusCount() throws Exception {
+        mWifiMetrics.incrementRecentFailureAssociationStatusCount(
+                WifiConfiguration.RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA);
+        mWifiMetrics.incrementRecentFailureAssociationStatusCount(
+                WifiConfiguration.RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA);
+        mWifiMetrics.incrementRecentFailureAssociationStatusCount(
+                WifiConfiguration.RECENT_FAILURE_OCE_RSSI_BASED_ASSOCIATION_REJECTION);
+        mWifiMetrics.incrementRecentFailureAssociationStatusCount(
+                WifiConfiguration.RECENT_FAILURE_MBO_ASSOC_DISALLOWED_AIR_INTERFACE_OVERLOADED);
+        mWifiMetrics.incrementRecentFailureAssociationStatusCount(
+                WifiConfiguration.RECENT_FAILURE_MBO_ASSOC_DISALLOWED_AIR_INTERFACE_OVERLOADED);
+
+        dumpProtoAndDeserialize();
+
+        Int32Count[] expectedRecentFailureAssociationStatus = {
+                buildInt32Count(WifiConfiguration.RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA,
+                        2),
+                buildInt32Count(
+                        WifiConfiguration
+                                .RECENT_FAILURE_MBO_ASSOC_DISALLOWED_AIR_INTERFACE_OVERLOADED, 2),
+                buildInt32Count(
+                        WifiConfiguration.RECENT_FAILURE_OCE_RSSI_BASED_ASSOCIATION_REJECTION, 1),
+        };
+
+        assertKeyCountsEqual(expectedRecentFailureAssociationStatus,
+                mDecodedProto.recentFailureAssociationStatus);
+
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiMetricsTestUtil.java b/service/tests/wifitests/src/com/android/server/wifi/WifiMetricsTestUtil.java
index 2be5538..9bd580d 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiMetricsTestUtil.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiMetricsTestUtil.java
@@ -185,6 +185,7 @@
         probe.type = StaEvent.TYPE_LINK_PROBE;
         probe.linkProbeWasSuccess = true;
         probe.linkProbeSuccessElapsedTimeMs = elapsedTimeMs;
+        probe.interfaceName = WifiMetricsTest.TEST_IFACE_NAME;
         return probe;
     }
 
@@ -196,6 +197,7 @@
         probe.type = StaEvent.TYPE_LINK_PROBE;
         probe.linkProbeWasSuccess = false;
         probe.linkProbeFailureReason = reason;
+        probe.interfaceName = WifiMetricsTest.TEST_IFACE_NAME;
         return probe;
     }
 
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiMonitorTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiMonitorTest.java
index c4acd73..38e69f9 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiMonitorTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiMonitorTest.java
@@ -18,8 +18,10 @@
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
-import static org.mockito.Mockito.mock;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -39,6 +41,7 @@
 import com.android.server.wifi.MboOceController.BtmFrameData;
 import com.android.server.wifi.hotspot2.AnqpEvent;
 import com.android.server.wifi.hotspot2.IconEvent;
+import com.android.server.wifi.hotspot2.WnmData;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -56,6 +59,8 @@
     private static final String BSSID = "fe:45:23:12:12:0a";
     private static final int NETWORK_ID = 5;
     private static final String SSID = "\"test124\"";
+    private static final long BSSID_LONG = 0xf3452312120aL;
+    private static final String PASSPOINT_URL = "https://www.google.com/";
     private WifiMonitor mWifiMonitor;
     private TestLooper mLooper;
     private Handler mHandlerSpy;
@@ -63,7 +68,7 @@
 
     @Before
     public void setUp() throws Exception {
-        mWifiMonitor = new WifiMonitor(mock(WifiInjector.class));
+        mWifiMonitor = new WifiMonitor();
         mLooper = new TestLooper();
         mHandlerSpy = spy(new Handler(mLooper.getLooper()));
         mSecondHandlerSpy = spy(new Handler(mLooper.getLooper()));
@@ -84,8 +89,11 @@
 
         ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
         verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        assertEquals(WifiMonitor.WPS_FAIL_EVENT, messageCaptor.getValue().what);
-        assertEquals(WifiManager.WPS_TKIP_ONLY_PROHIBITED, messageCaptor.getValue().arg1);
+
+        Message message = messageCaptor.getValue();
+        assertEquals(WifiMonitor.WPS_FAIL_EVENT, message.what);
+        assertEquals(WifiManager.WPS_TKIP_ONLY_PROHIBITED, message.arg1);
+        assertEquals(WLAN_IFACE_NAME, message.getData().getString(WifiMonitor.KEY_IFACE));
     }
 
     /**
@@ -385,16 +393,27 @@
         mWifiMonitor.registerHandler(
                 WLAN_IFACE_NAME, WifiMonitor.ASSOCIATION_REJECTION_EVENT, mHandlerSpy);
         int status = 5;
-        String bssid = BSSID;
-        mWifiMonitor.broadcastAssociationRejectionEvent(WLAN_IFACE_NAME, status, false, bssid);
+        int deltaRssi = 10;
+        int retryDelay = 25;
+        AssocRejectEventInfo assocRejectInfo = new AssocRejectEventInfo(
+                SSID,
+                BSSID,
+                status, false);
+        mWifiMonitor.broadcastAssociationRejectionEvent(WLAN_IFACE_NAME, assocRejectInfo);
         mLooper.dispatchAll();
 
         ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
         verify(mHandlerSpy).handleMessage(messageCaptor.capture());
         assertEquals(WifiMonitor.ASSOCIATION_REJECTION_EVENT, messageCaptor.getValue().what);
-        assertEquals(0, messageCaptor.getValue().arg1);
-        assertEquals(status, messageCaptor.getValue().arg2);
-        assertEquals(bssid, (String) messageCaptor.getValue().obj);
+        AssocRejectEventInfo assocRejectEventInfo =
+                (AssocRejectEventInfo) messageCaptor.getValue().obj;
+        assertNotNull(assocRejectEventInfo);
+        assertEquals(status, assocRejectEventInfo.statusCode);
+        assertFalse(assocRejectEventInfo.timedOut);
+        assertEquals(SSID, assocRejectEventInfo.ssid);
+        assertEquals(BSSID, assocRejectEventInfo.bssid);
+        assertNull(assocRejectEventInfo.oceRssiBasedAssocRejectInfo);
+        assertNull(assocRejectEventInfo.mboAssocDisallowedInfo);
     }
 
     /**
@@ -422,16 +441,20 @@
         mWifiMonitor.registerHandler(
                 WLAN_IFACE_NAME, WifiMonitor.NETWORK_CONNECTION_EVENT, mHandlerSpy);
         int networkId = NETWORK_ID;
+        WifiSsid wifiSsid = WifiSsid.createFromByteArray(new byte[]{'a', 'b', 'c'});
         String bssid = BSSID;
-        mWifiMonitor.broadcastNetworkConnectionEvent(WLAN_IFACE_NAME, networkId, false, bssid);
+        mWifiMonitor.broadcastNetworkConnectionEvent(WLAN_IFACE_NAME, networkId, false,
+                wifiSsid, bssid);
         mLooper.dispatchAll();
 
         ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
         verify(mHandlerSpy).handleMessage(messageCaptor.capture());
         assertEquals(WifiMonitor.NETWORK_CONNECTION_EVENT, messageCaptor.getValue().what);
-        assertEquals(networkId, messageCaptor.getValue().arg1);
-        assertEquals(0, messageCaptor.getValue().arg2);
-        assertEquals(bssid, (String) messageCaptor.getValue().obj);
+        NetworkConnectionEventInfo info = (NetworkConnectionEventInfo) messageCaptor.getValue().obj;
+        assertEquals(networkId, info.networkId);
+        assertFalse(info.isFilsConnection);
+        assertEquals(wifiSsid, info.wifiSsid);
+        assertEquals(bssid, info.bssid);
     }
 
     /**
@@ -441,18 +464,24 @@
     public void testBroadcastNetworkDisconnectionEvent() {
         mWifiMonitor.registerHandler(
                 WLAN_IFACE_NAME, WifiMonitor.NETWORK_DISCONNECTION_EVENT, mHandlerSpy);
-        int local = 1;
+        boolean local = true;
         int reason  = 5;
+        String ssid = SSID;
         String bssid = BSSID;
-        mWifiMonitor.broadcastNetworkDisconnectionEvent(WLAN_IFACE_NAME, local, reason, bssid);
+        mWifiMonitor.broadcastNetworkDisconnectionEvent(
+                WLAN_IFACE_NAME, local, reason, ssid, bssid);
         mLooper.dispatchAll();
 
         ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
         verify(mHandlerSpy).handleMessage(messageCaptor.capture());
         assertEquals(WifiMonitor.NETWORK_DISCONNECTION_EVENT, messageCaptor.getValue().what);
-        assertEquals(local, messageCaptor.getValue().arg1);
-        assertEquals(reason, messageCaptor.getValue().arg2);
-        assertEquals(bssid, (String) messageCaptor.getValue().obj);
+        DisconnectEventInfo disconnectEventInfo =
+                (DisconnectEventInfo) messageCaptor.getValue().obj;
+        assertNotNull(disconnectEventInfo);
+        assertEquals(local, disconnectEventInfo.locallyGenerated);
+        assertEquals(reason, disconnectEventInfo.reasonCode);
+        assertEquals(ssid, disconnectEventInfo.ssid);
+        assertEquals(bssid, disconnectEventInfo.bssid);
     }
 
     /**
@@ -476,69 +505,43 @@
         StateChangeResult result = (StateChangeResult) messageCaptor.getValue().obj;
         assertEquals(networkId, result.networkId);
         assertEquals(wifiSsid, result.wifiSsid);
-        assertEquals(bssid, result.BSSID);
+        assertEquals(bssid, result.bssid);
         assertEquals(newState, result.state);
     }
 
     /**
-     * Broadcast supplicant connection test.
-     */
-    @Test
-    public void testBroadcastSupplicantConnectionEvent() {
-        mWifiMonitor.registerHandler(
-                WLAN_IFACE_NAME, WifiMonitor.SUP_CONNECTION_EVENT, mHandlerSpy);
-        mWifiMonitor.broadcastSupplicantConnectionEvent(WLAN_IFACE_NAME);
-        mLooper.dispatchAll();
-
-        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
-        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        assertEquals(WifiMonitor.SUP_CONNECTION_EVENT, messageCaptor.getValue().what);
-    }
-    /**
-     * Broadcast supplicant disconnection test.
-     */
-    @Test
-    public void testBroadcastSupplicantDisconnectionEvent() {
-        mWifiMonitor.registerHandler(
-                WLAN_IFACE_NAME, WifiMonitor.SUP_DISCONNECTION_EVENT, mHandlerSpy);
-        mWifiMonitor.broadcastSupplicantDisconnectionEvent(WLAN_IFACE_NAME);
-        mLooper.dispatchAll();
-
-        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
-        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        assertEquals(WifiMonitor.SUP_DISCONNECTION_EVENT, messageCaptor.getValue().what);
-    }
-    /**
      * Broadcast message to two handlers test.
      */
     @Test
     public void testBroadcastEventToTwoHandlers() {
         mWifiMonitor.registerHandler(
-                WLAN_IFACE_NAME, WifiMonitor.SUP_CONNECTION_EVENT, mHandlerSpy);
+                WLAN_IFACE_NAME, WifiMonitor.SUP_REQUEST_SIM_AUTH, mHandlerSpy);
         mWifiMonitor.registerHandler(
-                WLAN_IFACE_NAME, WifiMonitor.SUP_CONNECTION_EVENT, mSecondHandlerSpy);
-        mWifiMonitor.broadcastSupplicantConnectionEvent(WLAN_IFACE_NAME);
+                WLAN_IFACE_NAME, WifiMonitor.SUP_REQUEST_SIM_AUTH, mSecondHandlerSpy);
+        mWifiMonitor.broadcastNetworkGsmAuthRequestEvent(
+                WLAN_IFACE_NAME, NETWORK_ID, SSID, GSM_AUTH_DATA);
         mLooper.dispatchAll();
 
         ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
         verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        assertEquals(WifiMonitor.SUP_CONNECTION_EVENT, messageCaptor.getValue().what);
+        assertEquals(WifiMonitor.SUP_REQUEST_SIM_AUTH, messageCaptor.getValue().what);
         verify(mSecondHandlerSpy).handleMessage(messageCaptor.capture());
-        assertEquals(WifiMonitor.SUP_CONNECTION_EVENT, messageCaptor.getValue().what);
+        assertEquals(WifiMonitor.SUP_REQUEST_SIM_AUTH, messageCaptor.getValue().what);
     }
+
     /**
      * Broadcast message when iface is null.
      */
     @Test
     public void testBroadcastEventWhenIfaceIsNull() {
         mWifiMonitor.registerHandler(
-                WLAN_IFACE_NAME, WifiMonitor.SUP_DISCONNECTION_EVENT, mHandlerSpy);
-        mWifiMonitor.broadcastSupplicantDisconnectionEvent(null);
+                WLAN_IFACE_NAME, WifiMonitor.SUP_REQUEST_SIM_AUTH, mHandlerSpy);
+        mWifiMonitor.broadcastNetworkGsmAuthRequestEvent(null, NETWORK_ID, SSID, GSM_AUTH_DATA);
         mLooper.dispatchAll();
 
         ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
         verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        assertEquals(WifiMonitor.SUP_DISCONNECTION_EVENT, messageCaptor.getValue().what);
+        assertEquals(WifiMonitor.SUP_REQUEST_SIM_AUTH, messageCaptor.getValue().what);
     }
     /**
      * Broadcast message when iface handler is null.
@@ -546,13 +549,14 @@
     @Test
     public void testBroadcastEventWhenIfaceHandlerIsNull() {
         mWifiMonitor.registerHandler(
-                WLAN_IFACE_NAME, WifiMonitor.SUP_DISCONNECTION_EVENT, mHandlerSpy);
-        mWifiMonitor.broadcastSupplicantDisconnectionEvent(SECOND_WLAN_IFACE_NAME);
+                WLAN_IFACE_NAME, WifiMonitor.SUP_REQUEST_SIM_AUTH, mHandlerSpy);
+        mWifiMonitor.broadcastNetworkGsmAuthRequestEvent(
+                WLAN_IFACE_NAME, NETWORK_ID, SSID, GSM_AUTH_DATA);
         mLooper.dispatchAll();
 
         ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
         verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        assertEquals(WifiMonitor.SUP_DISCONNECTION_EVENT, messageCaptor.getValue().what);
+        assertEquals(WifiMonitor.SUP_REQUEST_SIM_AUTH, messageCaptor.getValue().what);
     }
 
     @Test
@@ -610,15 +614,113 @@
         mWifiMonitor.registerHandler(
                 WLAN_IFACE_NAME, WifiMonitor.NETWORK_CONNECTION_EVENT, mHandlerSpy);
         int networkId = NETWORK_ID;
+        WifiSsid wifiSsid = WifiSsid.createFromByteArray(new byte[]{'a', 'b', 'c'});
         String bssid = BSSID;
-        mWifiMonitor.broadcastNetworkConnectionEvent(WLAN_IFACE_NAME, networkId, true, bssid);
+        mWifiMonitor.broadcastNetworkConnectionEvent(WLAN_IFACE_NAME, networkId, true,
+                wifiSsid, bssid);
         mLooper.dispatchAll();
 
         ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
         verify(mHandlerSpy).handleMessage(messageCaptor.capture());
         assertEquals(WifiMonitor.NETWORK_CONNECTION_EVENT, messageCaptor.getValue().what);
-        assertEquals(networkId, messageCaptor.getValue().arg1);
-        assertEquals(1, messageCaptor.getValue().arg2);
-        assertEquals(bssid, (String) messageCaptor.getValue().obj);
+        NetworkConnectionEventInfo info = (NetworkConnectionEventInfo) messageCaptor.getValue().obj;
+        assertEquals(networkId, info.networkId);
+        assertTrue(info.isFilsConnection);
+        assertEquals(wifiSsid, info.wifiSsid);
+        assertEquals(bssid, info.bssid);
+    }
+
+    /**
+     * Broadcast Passpoint remediation event test.
+     */
+    @Test
+    public void testBroadcastPasspointRemediationEvent() {
+        mWifiMonitor.registerHandler(
+                WLAN_IFACE_NAME, WifiMonitor.HS20_REMEDIATION_EVENT, mHandlerSpy);
+        WnmData wnmData = WnmData.createRemediationEvent(BSSID_LONG, PASSPOINT_URL, 0);
+        mWifiMonitor.broadcastWnmEvent(WLAN_IFACE_NAME, wnmData);
+
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiMonitor.HS20_REMEDIATION_EVENT, messageCaptor.getValue().what);
+        assertTrue(wnmData.equals(messageCaptor.getValue().obj));
+    }
+
+    /**
+     * Broadcast Passpoint deauth imminent event test.
+     */
+    @Test
+    public void testBroadcastPasspointDeauthImminentEvent() {
+        mWifiMonitor.registerHandler(
+                WLAN_IFACE_NAME, WifiMonitor.HS20_DEAUTH_IMMINENT_EVENT, mHandlerSpy);
+        WnmData wnmData = WnmData.createDeauthImminentEvent(BSSID_LONG, PASSPOINT_URL, true, 10);
+        mWifiMonitor.broadcastWnmEvent(WLAN_IFACE_NAME, wnmData);
+
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiMonitor.HS20_DEAUTH_IMMINENT_EVENT, messageCaptor.getValue().what);
+        assertTrue(wnmData.equals(messageCaptor.getValue().obj));
+    }
+
+    /**
+     * Broadcast Passpoint terms & conditions acceptance required event test.
+     */
+    @Test
+    public void testBroadcastPasspointTermsAndConditionsRequiredEvent() {
+        mWifiMonitor.registerHandler(
+                WLAN_IFACE_NAME, WifiMonitor.HS20_TERMS_AND_CONDITIONS_ACCEPTANCE_REQUIRED_EVENT,
+                mHandlerSpy);
+        WnmData wnmData =
+                WnmData.createTermsAndConditionsAccetanceRequiredEvent(BSSID_LONG, PASSPOINT_URL);
+        mWifiMonitor.broadcastWnmEvent(WLAN_IFACE_NAME, wnmData);
+
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiMonitor.HS20_TERMS_AND_CONDITIONS_ACCEPTANCE_REQUIRED_EVENT,
+                messageCaptor.getValue().what);
+        assertTrue(wnmData.equals(messageCaptor.getValue().obj));
+    }
+
+    /**
+     * Broadcast message when iface handler is null.
+     */
+    @Test
+    public void testBroadcastTransitionDisableEvent() {
+        final int indication = WifiMonitor.TDI_USE_WPA3_PERSONAL
+                | WifiMonitor.TDI_USE_SAE_PK;
+        mWifiMonitor.registerHandler(
+                WLAN_IFACE_NAME, WifiMonitor.TRANSITION_DISABLE_INDICATION, mHandlerSpy);
+        mWifiMonitor.broadcastTransitionDisableEvent(
+                WLAN_IFACE_NAME, NETWORK_ID, indication);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiMonitor.TRANSITION_DISABLE_INDICATION, messageCaptor.getValue().what);
+        assertEquals(NETWORK_ID, messageCaptor.getValue().arg1);
+        assertEquals(indication, messageCaptor.getValue().arg2);
+    }
+
+    /**
+     * Broadcast Network not found event test.
+     */
+    @Test
+    public void testBroadcastNetworkNotFoundEvent() {
+        mWifiMonitor.registerHandler(
+                WLAN_IFACE_NAME, WifiMonitor.NETWORK_NOT_FOUND_EVENT, mHandlerSpy);
+        mWifiMonitor.broadcastNetworkNotFoundEvent(WLAN_IFACE_NAME, SSID);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiMonitor.NETWORK_NOT_FOUND_EVENT, messageCaptor.getValue().what);
+        String ssid = (String) messageCaptor.getValue().obj;
+        assertEquals(SSID, ssid);
     }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiMulticastLockManagerTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiMulticastLockManagerTest.java
index f640161..cde1d2a 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiMulticastLockManagerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiMulticastLockManagerTest.java
@@ -16,6 +16,9 @@
 
 package com.android.server.wifi;
 
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_PRIMARY;
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SECONDARY_TRANSIENT;
+
 import static org.junit.Assert.*;
 import static org.mockito.Mockito.*;
 
@@ -27,23 +30,33 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.wifi.ActiveModeWarden.PrimaryClientModeManagerChangedCallback;
+import com.android.server.wifi.WifiMulticastLockManager.FilterController;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
 
 /**
- * Unit tests for {@link com.android.server.wifi.WifiConfigStoreData}.
+ * Unit tests for {@link com.android.server.wifi.WifiMulticastLockManager}.
  */
 @SmallTest
 public class WifiMulticastLockManagerTest extends WifiBaseTest {
     private static final String WL_1_TAG = "Wakelock-1";
     private static final String WL_2_TAG = "Wakelock-2";
 
-    @Mock WifiMulticastLockManager.FilterController mHandler;
+    @Mock ConcreteClientModeManager mClientModeManager;
+    @Mock ConcreteClientModeManager mClientModeManager2;
+    @Spy FakeFilterController mFilterController = new FakeFilterController();
+    @Spy FakeFilterController mFilterController2 = new FakeFilterController();
     @Mock BatteryStatsManager mBatteryStats;
+    @Mock ActiveModeWarden mActiveModeWarden;
+    @Captor ArgumentCaptor<PrimaryClientModeManagerChangedCallback> mPrimaryChangedCallbackCaptor;
     WifiMulticastLockManager mManager;
 
     /**
@@ -52,7 +65,20 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mManager = new WifiMulticastLockManager(mHandler, mBatteryStats);
+
+        when(mClientModeManager.getMcastLockManagerFilterController())
+                .thenReturn(mFilterController);
+        when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_PRIMARY);
+
+        when(mClientModeManager2.getMcastLockManagerFilterController())
+                .thenReturn(mFilterController2);
+        when(mClientModeManager2.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_TRANSIENT);
+
+        when(mActiveModeWarden.getPrimaryClientModeManager()).thenReturn(mClientModeManager);
+        mManager = new WifiMulticastLockManager(mActiveModeWarden, mBatteryStats);
+
+        verify(mActiveModeWarden).registerPrimaryClientModeManagerChangedCallback(
+                mPrimaryChangedCallbackCaptor.capture());
     }
 
     /**
@@ -62,20 +88,20 @@
     public void noLocks() {
         assertFalse(mManager.isMulticastEnabled());
         mManager.initializeFiltering();
-        verify(mHandler, times(1)).startFilteringMulticastPackets();
+        verify(mFilterController, times(1)).startFilteringMulticastPackets();
     }
 
     /**
-     * Test behavior when one lock is aquired then released.
+     * Test behavior when one lock is acquired then released.
      */
     @Test
     public void oneLock() throws RemoteException {
         IBinder binder = mock(IBinder.class);
         mManager.acquireLock(binder, WL_1_TAG);
         assertTrue(mManager.isMulticastEnabled());
-        verify(mHandler).stopFilteringMulticastPackets();
+        verify(mFilterController).stopFilteringMulticastPackets();
         mManager.initializeFiltering();
-        verify(mHandler, times(0)).startFilteringMulticastPackets();
+        verify(mFilterController, times(0)).startFilteringMulticastPackets();
         ArgumentCaptor<WorkSource> wsCaptor = ArgumentCaptor.forClass(WorkSource.class);
         verify(mBatteryStats).reportWifiMulticastEnabled(wsCaptor.capture());
         assertNotNull(wsCaptor.getValue());
@@ -89,17 +115,78 @@
         assertFalse(mManager.isMulticastEnabled());
     }
 
+    private static class FakeFilterController implements FilterController {
+
+        /** filters by default */
+        private boolean mIsFilteringStarted = true;
+
+        @Override
+        public void startFilteringMulticastPackets() {
+            mIsFilteringStarted = true;
+        }
+
+        @Override
+        public void stopFilteringMulticastPackets() {
+            mIsFilteringStarted = false;
+        }
+
+        public boolean isFilteringStarted() {
+            return mIsFilteringStarted;
+        }
+    }
+
     /**
-     * Test behavior when one lock is aquired then released with the wrong tag.
+     * Test behavior when one lock is acquired, the primary ClientModeManager is changed, then
+     * the lock is released.
+     */
+    @Test
+    public void oneLock_changePrimaryClientModeManager() throws RemoteException {
+        // CMM1 filter started by default
+        assertTrue(mFilterController.isFilteringStarted());
+        // CMM2 filter started by default
+        assertTrue(mFilterController2.isFilteringStarted());
+
+        IBinder binder = mock(IBinder.class);
+        mManager.acquireLock(binder, WL_1_TAG);
+        assertTrue(mManager.isMulticastEnabled());
+        // CMM1 filtering stopped
+        assertFalse(mFilterController.isFilteringStarted());
+        // CMM2 still started
+        assertTrue(mFilterController2.isFilteringStarted());
+
+        // switch CMM1 to secondary
+        when(mClientModeManager.getRole()).thenReturn(ROLE_CLIENT_SECONDARY_TRANSIENT);
+        mPrimaryChangedCallbackCaptor.getValue().onChange(mClientModeManager, null);
+        // switch CMM2 to primary
+        when(mClientModeManager2.getRole()).thenReturn(ROLE_CLIENT_PRIMARY);
+        when(mActiveModeWarden.getPrimaryClientModeManager()).thenReturn(mClientModeManager2);
+        mPrimaryChangedCallbackCaptor.getValue().onChange(null, mClientModeManager2);
+
+        assertTrue(mManager.isMulticastEnabled());
+        // CMM1 filter started
+        assertTrue(mFilterController.isFilteringStarted());
+        // CMM2 filter stopped
+        assertFalse(mFilterController2.isFilteringStarted());
+
+        mManager.releaseLock(WL_1_TAG);
+        assertFalse(mManager.isMulticastEnabled());
+        // CMM1 filter started
+        assertTrue(mFilterController.isFilteringStarted());
+        // CMM2 filter started
+        assertTrue(mFilterController2.isFilteringStarted());
+    }
+
+    /**
+     * Test behavior when one lock is acquired then released with the wrong tag.
      */
     @Test
     public void oneLock_wrongName() throws RemoteException {
         IBinder binder = mock(IBinder.class);
         mManager.acquireLock(binder, WL_1_TAG);
         assertTrue(mManager.isMulticastEnabled());
-        verify(mHandler).stopFilteringMulticastPackets();
+        verify(mFilterController).stopFilteringMulticastPackets();
         mManager.initializeFiltering();
-        verify(mHandler, never()).startFilteringMulticastPackets();
+        verify(mFilterController, never()).startFilteringMulticastPackets();
         verify(mBatteryStats).reportWifiMulticastEnabled(any());
         verify(mBatteryStats, never()).reportWifiMulticastDisabled(any());
 
@@ -109,35 +196,35 @@
     }
 
     /**
-     * Test behavior when multiple locks are aquired then released in nesting order.
+     * Test behavior when multiple locks are acquired then released in nesting order.
      */
     @Test
     public void multipleLocksInOrder() throws RemoteException {
         IBinder binder = mock(IBinder.class);
 
-        InOrder inOrderHandler = inOrder(mHandler);
+        InOrder inOrderHandler = inOrder(mFilterController);
         InOrder inOrderBatteryStats = inOrder(mBatteryStats);
 
         mManager.acquireLock(binder, WL_1_TAG);
-        inOrderHandler.verify(mHandler).stopFilteringMulticastPackets();
+        inOrderHandler.verify(mFilterController).stopFilteringMulticastPackets();
         inOrderBatteryStats.verify(mBatteryStats).reportWifiMulticastEnabled(any());
         assertTrue(mManager.isMulticastEnabled());
 
         mManager.acquireLock(binder, WL_2_TAG);
-        inOrderHandler.verify(mHandler).stopFilteringMulticastPackets();
+        inOrderHandler.verify(mFilterController).stopFilteringMulticastPackets();
         inOrderBatteryStats.verify(mBatteryStats).reportWifiMulticastEnabled(any());
         assertTrue(mManager.isMulticastEnabled());
 
         mManager.initializeFiltering();
-        inOrderHandler.verify(mHandler, never()).startFilteringMulticastPackets();
+        inOrderHandler.verify(mFilterController, never()).startFilteringMulticastPackets();
 
         mManager.releaseLock(WL_2_TAG);
-        inOrderHandler.verify(mHandler, never()).startFilteringMulticastPackets();
+        inOrderHandler.verify(mFilterController, never()).startFilteringMulticastPackets();
         inOrderBatteryStats.verify(mBatteryStats).reportWifiMulticastDisabled(any());
         assertTrue(mManager.isMulticastEnabled());
 
         mManager.releaseLock(WL_1_TAG);
-        inOrderHandler.verify(mHandler).startFilteringMulticastPackets();
+        inOrderHandler.verify(mFilterController).startFilteringMulticastPackets();
         inOrderBatteryStats.verify(mBatteryStats).reportWifiMulticastDisabled(any());
         assertFalse(mManager.isMulticastEnabled());
     }
@@ -149,29 +236,29 @@
     public void multipleLocksOutOfOrder() throws RemoteException {
         IBinder binder = mock(IBinder.class);
 
-        InOrder inOrderHandler = inOrder(mHandler);
+        InOrder inOrderHandler = inOrder(mFilterController);
         InOrder inOrderBatteryStats = inOrder(mBatteryStats);
 
         mManager.acquireLock(binder, WL_1_TAG);
-        inOrderHandler.verify(mHandler).stopFilteringMulticastPackets();
+        inOrderHandler.verify(mFilterController).stopFilteringMulticastPackets();
         inOrderBatteryStats.verify(mBatteryStats).reportWifiMulticastEnabled(any());
         assertTrue(mManager.isMulticastEnabled());
 
         mManager.acquireLock(binder, WL_2_TAG);
-        inOrderHandler.verify(mHandler).stopFilteringMulticastPackets();
+        inOrderHandler.verify(mFilterController).stopFilteringMulticastPackets();
         inOrderBatteryStats.verify(mBatteryStats).reportWifiMulticastEnabled(any());
         assertTrue(mManager.isMulticastEnabled());
 
         mManager.initializeFiltering();
-        inOrderHandler.verify(mHandler, never()).startFilteringMulticastPackets();
+        inOrderHandler.verify(mFilterController, never()).startFilteringMulticastPackets();
 
         mManager.releaseLock(WL_1_TAG);
-        inOrderHandler.verify(mHandler, never()).startFilteringMulticastPackets();
+        inOrderHandler.verify(mFilterController, never()).startFilteringMulticastPackets();
         inOrderBatteryStats.verify(mBatteryStats).reportWifiMulticastDisabled(any());
         assertTrue(mManager.isMulticastEnabled());
 
         mManager.releaseLock(WL_2_TAG);
-        inOrderHandler.verify(mHandler).startFilteringMulticastPackets();
+        inOrderHandler.verify(mFilterController).startFilteringMulticastPackets();
         inOrderBatteryStats.verify(mBatteryStats).reportWifiMulticastDisabled(any());
         assertFalse(mManager.isMulticastEnabled());
     }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiNativeInterfaceManagementTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiNativeInterfaceManagementTest.java
index 7d76eac..b7082e2 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiNativeInterfaceManagementTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiNativeInterfaceManagementTest.java
@@ -18,16 +18,18 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -38,10 +40,12 @@
 import android.net.wifi.WifiScanner;
 import android.net.wifi.nl80211.WifiNl80211Manager;
 import android.os.Handler;
+import android.os.WorkSource;
 import android.os.test.TestLooper;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.HalDeviceManager.InterfaceDestroyedListener;
 import com.android.server.wifi.WifiNative.SupplicantDeathEventHandler;
 import com.android.server.wifi.WifiNative.VendorHalDeathEventHandler;
@@ -56,8 +60,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.Arrays;
-import java.util.HashSet;
+import java.util.ArrayList;
+import java.util.Set;
 
 /**
  * Unit tests for the interface management operations in
@@ -67,6 +71,7 @@
 public class WifiNativeInterfaceManagementTest extends WifiBaseTest {
     private static final String IFACE_NAME_0 = "mockWlan0";
     private static final String IFACE_NAME_1 = "mockWlan1";
+    private static final WorkSource TEST_WORKSOURCE = new WorkSource();
 
     @Mock private WifiVendorHal mWifiVendorHal;
     @Mock private WifiNl80211Manager mWificondControl;
@@ -76,6 +81,7 @@
     @Mock private NetdWrapper mNetdWrapper;
     @Mock private PropertyService mPropertyService;
     @Mock private WifiMetrics mWifiMetrics;
+    @Mock BuildProperties mBuildProperties;
     @Mock private WifiInjector mWifiInjector;
 
     @Mock private WifiNative.StatusListener mStatusListener;
@@ -118,11 +124,20 @@
         doNothing().when(mWifiVendorHal).registerRadioModeChangeHandler(
                 mWifiVendorHalRadioModeChangeHandlerCaptor.capture());
         when(mWifiVendorHal.isVendorHalSupported()).thenReturn(true);
+        when(mWifiVendorHal.isVendorHalReady()).thenReturn(true);
         when(mWifiVendorHal.startVendorHal()).thenReturn(true);
-        when(mWifiVendorHal.createStaIface(any())).thenReturn(IFACE_NAME_0);
-        when(mWifiVendorHal.createApIface(any())).thenReturn(IFACE_NAME_0);
+        when(mWifiVendorHal.createStaIface(any(), any())).thenReturn(IFACE_NAME_0);
+        when(mWifiVendorHal.createApIface(any(), any(), anyInt(),
+                anyBoolean())).thenReturn(IFACE_NAME_0);
+        when(mWifiVendorHal.getBridgedApInstances(any())).thenReturn(
+                new ArrayList<String>() {{ add((IFACE_NAME_0)); }});
         when(mWifiVendorHal.removeStaIface(any())).thenReturn(true);
         when(mWifiVendorHal.removeApIface(any())).thenReturn(true);
+        when(mWifiVendorHal.replaceStaIfaceRequestorWs(any(), any())).thenReturn(true);
+
+        when(mBuildProperties.isEngBuild()).thenReturn(false);
+        when(mBuildProperties.isUserdebugBuild()).thenReturn(false);
+        when(mBuildProperties.isUserBuild()).thenReturn(true);
 
         when(mWificondControl.setupInterfaceForClientMode(any(), any(), any(), any())).thenReturn(
                 true);
@@ -149,8 +164,9 @@
         when(mHostapdHal.isInitializationStarted()).thenReturn(false);
         when(mHostapdHal.isInitializationComplete()).thenReturn(true);
         when(mHostapdHal.startDaemon()).thenReturn(true);
-        when(mHostapdHal.addAccessPoint(any(), any(), any())).thenReturn(true);
+        when(mHostapdHal.addAccessPoint(any(), any(), anyBoolean(), any())).thenReturn(true);
         when(mHostapdHal.removeAccessPoint(any())).thenReturn(true);
+        when(mHostapdHal.registerApCallback(any(), any())).thenReturn(true);
 
         when(mWifiInjector.makeNetdWrapper()).thenReturn(mNetdWrapper);
 
@@ -160,7 +176,7 @@
         mWifiNative = new WifiNative(
                 mWifiVendorHal, mSupplicantStaIfaceHal, mHostapdHal, mWificondControl,
                 mWifiMonitor, mPropertyService, mWifiMetrics,
-                new Handler(mLooper.getLooper()), null, mWifiInjector);
+                new Handler(mLooper.getLooper()), null, mBuildProperties, mWifiInjector);
         mWifiNative.initialize();
         mWifiNative.registerStatusListener(mStatusListener);
 
@@ -186,7 +202,7 @@
         executeAndValidateSetupClientInterface(
                 false, false, IFACE_NAME_0, mIfaceCallback0, mIfaceDestroyedListenerCaptor0,
                 mNetworkObserverCaptor0);
-        assertEquals(IFACE_NAME_0, mWifiNative.getClientInterfaceName());
+        assertEquals(Set.of(IFACE_NAME_0), mWifiNative.getClientInterfaceNames());
     }
 
     /**
@@ -197,7 +213,7 @@
         executeAndValidateSetupClientInterfaceForScan(
                 false, false, IFACE_NAME_0, mIfaceCallback0, mIfaceDestroyedListenerCaptor0,
                 mNetworkObserverCaptor0);
-        assertEquals(IFACE_NAME_0, mWifiNative.getClientInterfaceName());
+        assertEquals(Set.of(IFACE_NAME_0), mWifiNative.getClientInterfaceNames());
         verifyNoMoreInteractions(mWifiVendorHal, mWificondControl, mSupplicantStaIfaceHal,
                 mHostapdHal, mNetdWrapper, mIfaceCallback0, mIfaceCallback1, mWifiMetrics);
     }
@@ -208,9 +224,23 @@
     @Test
     public void testSetupSoftApInterface() throws Exception {
         executeAndValidateSetupSoftApInterface(
-                false, false, IFACE_NAME_0, mIfaceCallback0, mIfaceDestroyedListenerCaptor0,
+                false, false, IFACE_NAME_0,
+                mIfaceCallback0, mIfaceDestroyedListenerCaptor0,
                 mNetworkObserverCaptor0);
-        assertNull(mWifiNative.getClientInterfaceName());
+        assertEquals(Set.of(IFACE_NAME_0), mWifiNative.getSoftApInterfaceNames());
+    }
+
+    /**
+     * Verifies the setup of a single softAp interface.
+     */
+    @Test
+    public void testSetupSoftApInterfaceInBridgedMode() throws Exception {
+        executeAndValidateSetupSoftApInterface(
+                false, false, IFACE_NAME_0,
+                mIfaceCallback0, mIfaceDestroyedListenerCaptor0,
+                mNetworkObserverCaptor0, true);
+        assertEquals(Set.of(IFACE_NAME_0), mWifiNative.getSoftApInterfaceNames());
+        assertEquals(Set.of(), mWifiNative.getClientInterfaceNames());
     }
 
     /**
@@ -221,7 +251,7 @@
         executeAndValidateSetupClientInterface(
                 false, false, IFACE_NAME_0, mIfaceCallback0, mIfaceDestroyedListenerCaptor0,
                 mNetworkObserverCaptor0);
-        assertEquals(IFACE_NAME_0, mWifiNative.getClientInterfaceName());
+        assertEquals(Set.of(IFACE_NAME_0), mWifiNative.getClientInterfaceNames());
         executeAndValidateTeardownClientInterface(false, false, IFACE_NAME_0, mIfaceCallback0,
                 mIfaceDestroyedListenerCaptor0.getValue(), mNetworkObserverCaptor0.getValue());
     }
@@ -234,7 +264,7 @@
         executeAndValidateSetupClientInterfaceForScan(
                 false, false, IFACE_NAME_0, mIfaceCallback0, mIfaceDestroyedListenerCaptor0,
                 mNetworkObserverCaptor0);
-        assertEquals(IFACE_NAME_0, mWifiNative.getClientInterfaceName());
+        assertEquals(Set.of(IFACE_NAME_0), mWifiNative.getClientInterfaceNames());
         executeAndValidateTeardownClientInterfaceForScan(false, false, IFACE_NAME_0,
                 mIfaceCallback0, mIfaceDestroyedListenerCaptor0.getValue(),
                 mNetworkObserverCaptor0.getValue());
@@ -250,7 +280,8 @@
         executeAndValidateSetupSoftApInterface(
                 false, false, IFACE_NAME_0, mIfaceCallback0, mIfaceDestroyedListenerCaptor0,
                 mNetworkObserverCaptor0);
-        assertNull(mWifiNative.getClientInterfaceName());
+        assertEquals(Set.of(IFACE_NAME_0), mWifiNative.getSoftApInterfaceNames());
+        assertEquals(Set.of(), mWifiNative.getClientInterfaceNames());
         executeAndValidateTeardownSoftApInterface(false, false, IFACE_NAME_0, mIfaceCallback0,
                 mIfaceDestroyedListenerCaptor0.getValue(), mNetworkObserverCaptor0.getValue());
     }
@@ -272,7 +303,7 @@
         executeAndValidateSetupSoftApInterface(
                 true, false, IFACE_NAME_1, mIfaceCallback1, mIfaceDestroyedListenerCaptor1,
                 mNetworkObserverCaptor1);
-        assertEquals(IFACE_NAME_0, mWifiNative.getClientInterfaceName());
+        assertEquals(Set.of(IFACE_NAME_0), mWifiNative.getClientInterfaceNames());
         executeAndValidateTeardownClientInterface(false, true, IFACE_NAME_0, mIfaceCallback0,
                 mIfaceDestroyedListenerCaptor0.getValue(), mNetworkObserverCaptor0.getValue());
         executeAndValidateTeardownSoftApInterface(false, false, IFACE_NAME_1, mIfaceCallback1,
@@ -296,7 +327,7 @@
         executeAndValidateSetupSoftApInterface(
                 true, false, IFACE_NAME_1, mIfaceCallback1, mIfaceDestroyedListenerCaptor1,
                 mNetworkObserverCaptor1);
-        assertEquals(IFACE_NAME_0, mWifiNative.getClientInterfaceName());
+        assertEquals(Set.of(IFACE_NAME_0), mWifiNative.getClientInterfaceNames());
         executeAndValidateTeardownSoftApInterface(true, false, IFACE_NAME_1, mIfaceCallback1,
                 mIfaceDestroyedListenerCaptor1.getValue(), mNetworkObserverCaptor1.getValue());
         executeAndValidateTeardownClientInterface(false, false, IFACE_NAME_0, mIfaceCallback0,
@@ -320,7 +351,8 @@
         executeAndValidateSetupClientInterface(
                 false, true, IFACE_NAME_1, mIfaceCallback1, mIfaceDestroyedListenerCaptor1,
                 mNetworkObserverCaptor1);
-        assertEquals(IFACE_NAME_1, mWifiNative.getClientInterfaceName());
+        assertEquals(Set.of(IFACE_NAME_0), mWifiNative.getSoftApInterfaceNames());
+        assertEquals(Set.of(IFACE_NAME_1), mWifiNative.getClientInterfaceNames());
         executeAndValidateTeardownSoftApInterface(true, false, IFACE_NAME_0, mIfaceCallback0,
                 mIfaceDestroyedListenerCaptor0.getValue(), mNetworkObserverCaptor0.getValue());
         executeAndValidateTeardownClientInterface(false, false, IFACE_NAME_1, mIfaceCallback1,
@@ -344,7 +376,8 @@
         executeAndValidateSetupClientInterface(
                 false, true, IFACE_NAME_1, mIfaceCallback1, mIfaceDestroyedListenerCaptor1,
                 mNetworkObserverCaptor1);
-        assertEquals(IFACE_NAME_1, mWifiNative.getClientInterfaceName());
+        assertEquals(Set.of(IFACE_NAME_0), mWifiNative.getSoftApInterfaceNames());
+        assertEquals(Set.of(IFACE_NAME_1), mWifiNative.getClientInterfaceNames());
         executeAndValidateTeardownClientInterface(false, true, IFACE_NAME_1, mIfaceCallback1,
                 mIfaceDestroyedListenerCaptor1.getValue(), mNetworkObserverCaptor1.getValue());
         executeAndValidateTeardownSoftApInterface(false, false, IFACE_NAME_0, mIfaceCallback0,
@@ -370,8 +403,8 @@
                 mNetworkObserverCaptor1);
 
         // Assert that a client & softap interface is present.
-        assertNotNull(mWifiNative.getClientInterfaceName());
-        assertNotNull(mWifiNative.getSoftApInterfaceName());
+        assertEquals(Set.of(IFACE_NAME_0), mWifiNative.getSoftApInterfaceNames());
+        assertEquals(Set.of(IFACE_NAME_1), mWifiNative.getClientInterfaceNames());
 
         mWifiNative.teardownAllInterfaces();
 
@@ -399,11 +432,12 @@
         verify(mWifiVendorHal).stopVendorHal();
         verify(mIfaceCallback0).onDestroyed(IFACE_NAME_0);
 
+        verify(mWifiVendorHal, atLeastOnce()).isVendorHalReady();
         verify(mWifiVendorHal, atLeastOnce()).isVendorHalSupported();
 
         // Assert that the client & softap interface is no more there.
-        assertNull(mWifiNative.getClientInterfaceName());
-        assertNull(mWifiNative.getSoftApInterfaceName());
+        assertEquals(Set.of(), mWifiNative.getClientInterfaceNames());
+        assertEquals(Set.of(), mWifiNative.getSoftApInterfaceNames());
     }
 
     /**
@@ -416,17 +450,17 @@
         executeAndValidateSetupClientInterface(
                 false, false, IFACE_NAME_0, mIfaceCallback0, mIfaceDestroyedListenerCaptor0,
                 mNetworkObserverCaptor0);
-
         // Trigger the STA interface teardown when AP interface is created.
         // The iface name will remain the same.
         doAnswer(new MockAnswerUtil.AnswerWithArguments() {
-            public String answer(InterfaceDestroyedListener destroyedListener) {
+            public String answer(InterfaceDestroyedListener destroyedListener, WorkSource ws,
+                    int band, boolean isBridged) {
                 mIfaceDestroyedListenerCaptor0.getValue().onDestroyed(IFACE_NAME_0);
                 return IFACE_NAME_0;
             }
-        }).when(mWifiVendorHal).createApIface(any());
-
-        assertEquals(IFACE_NAME_0, mWifiNative.setupInterfaceForSoftApMode(mIfaceCallback1));
+        }).when(mWifiVendorHal).createApIface(any(), any(), anyInt(), eq(false));
+        assertEquals(IFACE_NAME_0, mWifiNative.setupInterfaceForSoftApMode(
+                mIfaceCallback1, TEST_WORKSOURCE, SoftApConfiguration.BAND_2GHZ, false));
 
         mInOrder.verify(mHostapdHal).isInitializationStarted();
         mInOrder.verify(mHostapdHal).initialize();
@@ -434,7 +468,8 @@
         mInOrder.verify(mHostapdHal).isInitializationComplete();
         mInOrder.verify(mHostapdHal).registerDeathHandler(any());
         mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
-        mInOrder.verify(mWifiVendorHal).createApIface(mIfaceDestroyedListenerCaptor1.capture());
+        mInOrder.verify(mWifiVendorHal).createApIface(
+                mIfaceDestroyedListenerCaptor1.capture(), eq(TEST_WORKSOURCE), anyInt(), eq(false));
         // Creation of AP interface should trigger the STA interface destroy
         validateOnDestroyedClientInterface(
                 false, true, IFACE_NAME_0, mIfaceCallback0, mNetworkObserverCaptor0.getValue());
@@ -442,7 +477,7 @@
         mInOrder.verify(mWificondControl).setupInterfaceForSoftApMode(IFACE_NAME_0);
         mInOrder.verify(mNetdWrapper).registerObserver(mNetworkObserverCaptor1.capture());
         mInOrder.verify(mNetdWrapper).isInterfaceUp(IFACE_NAME_0);
-        mInOrder.verify(mSupplicantStaIfaceHal).getAdvancedKeyMgmtCapabilities(IFACE_NAME_0);
+        mInOrder.verify(mSupplicantStaIfaceHal).getAdvancedCapabilities(IFACE_NAME_0);
         mInOrder.verify(mWifiVendorHal).getSupportedFeatureSet(IFACE_NAME_0);
         mInOrder.verify(mSupplicantStaIfaceHal).getWpaDriverFeatureSet(IFACE_NAME_0);
 
@@ -465,14 +500,15 @@
         // Trigger the AP interface teardown when STA interface is created.
         // The iface name will remain the same.
         doAnswer(new MockAnswerUtil.AnswerWithArguments() {
-            public String answer(InterfaceDestroyedListener destroyedListener) {
+            public String answer(InterfaceDestroyedListener destroyedListener, WorkSource ws) {
                 mIfaceDestroyedListenerCaptor0.getValue().onDestroyed(IFACE_NAME_0);
                 return IFACE_NAME_0;
             }
-        }).when(mWifiVendorHal).createStaIface(any());
+        }).when(mWifiVendorHal).createStaIface(any(), any());
 
         assertEquals(IFACE_NAME_0,
-                mWifiNative.setupInterfaceForClientInConnectivityMode(mIfaceCallback1));
+                mWifiNative.setupInterfaceForClientInConnectivityMode(
+                        mIfaceCallback1, TEST_WORKSOURCE));
 
         mInOrder.verify(mSupplicantStaIfaceHal).isInitializationStarted();
         mInOrder.verify(mSupplicantStaIfaceHal).initialize();
@@ -481,7 +517,7 @@
         mInOrder.verify(mSupplicantStaIfaceHal).registerDeathHandler(any());
         mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
         mInOrder.verify(mWifiVendorHal).createStaIface(
-                mIfaceDestroyedListenerCaptor1.capture());
+                mIfaceDestroyedListenerCaptor1.capture(), eq(TEST_WORKSOURCE));
         // Creation of STA interface should trigger the AP interface destroy.
         validateOnDestroyedSoftApInterface(
                 true, false, IFACE_NAME_0, mIfaceCallback0, mNetworkObserverCaptor0.getValue());
@@ -495,7 +531,7 @@
         mInOrder.verify(mNetdWrapper).clearInterfaceAddresses(IFACE_NAME_0);
         mInOrder.verify(mNetdWrapper).setInterfaceIpv6PrivacyExtensions(IFACE_NAME_0, true);
         mInOrder.verify(mNetdWrapper).disableIpv6(IFACE_NAME_0);
-        mInOrder.verify(mSupplicantStaIfaceHal).getAdvancedKeyMgmtCapabilities(IFACE_NAME_0);
+        mInOrder.verify(mSupplicantStaIfaceHal).getAdvancedCapabilities(IFACE_NAME_0);
         mInOrder.verify(mWifiVendorHal).getSupportedFeatureSet(IFACE_NAME_0);
         mInOrder.verify(mSupplicantStaIfaceHal).getWpaDriverFeatureSet(IFACE_NAME_0);
 
@@ -685,21 +721,23 @@
         // Trigger the STA interface teardown when AP interface is created.
         // The iface name will remain the same.
         doAnswer(new MockAnswerUtil.AnswerWithArguments() {
-            public String answer(InterfaceDestroyedListener destroyedListener) {
+            public String answer(InterfaceDestroyedListener destroyedListener, WorkSource ws,
+                    int band, boolean isBriger) {
                 mIfaceDestroyedListenerCaptor0.getValue().onDestroyed(IFACE_NAME_0);
                 return IFACE_NAME_0;
             }
-        }).when(mWifiVendorHal).createApIface(any());
+        }).when(mWifiVendorHal).createApIface(any(), any(), anyInt(), eq(false));
 
-        assertEquals(IFACE_NAME_0, mWifiNative.setupInterfaceForSoftApMode(mIfaceCallback1));
-
+        assertEquals(IFACE_NAME_0, mWifiNative.setupInterfaceForSoftApMode(
+                mIfaceCallback1, TEST_WORKSOURCE, SoftApConfiguration.BAND_2GHZ, false));
         mInOrder.verify(mHostapdHal).isInitializationStarted();
         mInOrder.verify(mHostapdHal).initialize();
         mInOrder.verify(mHostapdHal).startDaemon();
         mInOrder.verify(mHostapdHal).isInitializationComplete();
         mInOrder.verify(mHostapdHal).registerDeathHandler(any());
         mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
-        mInOrder.verify(mWifiVendorHal).createApIface(mIfaceDestroyedListenerCaptor1.capture());
+        mInOrder.verify(mWifiVendorHal).createApIface(
+                mIfaceDestroyedListenerCaptor1.capture(), eq(TEST_WORKSOURCE), anyInt(), eq(false));
         // Creation of AP interface should trigger the STA interface destroy
         validateOnDestroyedClientInterface(
                 false, true, IFACE_NAME_0, mIfaceCallback0, mNetworkObserverCaptor0.getValue());
@@ -707,7 +745,7 @@
         mInOrder.verify(mWificondControl).setupInterfaceForSoftApMode(IFACE_NAME_0);
         mInOrder.verify(mNetdWrapper).registerObserver(mNetworkObserverCaptor1.capture());
         mInOrder.verify(mNetdWrapper).isInterfaceUp(IFACE_NAME_0);
-        mInOrder.verify(mSupplicantStaIfaceHal).getAdvancedKeyMgmtCapabilities(IFACE_NAME_0);
+        mInOrder.verify(mSupplicantStaIfaceHal).getAdvancedCapabilities(IFACE_NAME_0);
         mInOrder.verify(mWifiVendorHal).getSupportedFeatureSet(IFACE_NAME_0);
         mInOrder.verify(mSupplicantStaIfaceHal).getWpaDriverFeatureSet(IFACE_NAME_0);
 
@@ -752,12 +790,14 @@
                 mNetworkObserverCaptor0);
 
         // Trigger vendor HAL death
+
         mWifiVendorHalDeathHandlerCaptor.getValue().onDeath();
 
         mInOrder.verify(mWifiMetrics).incrementNumHalCrashes();
 
         verify(mStatusListener).onStatusChanged(false);
         verify(mStatusListener).onStatusChanged(true);
+
     }
 
     /**
@@ -782,16 +822,46 @@
      */
     @Test
     public void testStartSoftApAndHostapdDied() throws Exception {
+        when(mHostapdHal.isApInfoCallbackSupported()).thenReturn(true);
         executeAndValidateSetupSoftApInterface(
                 false, false, IFACE_NAME_0, mIfaceCallback0, mIfaceDestroyedListenerCaptor0,
-                mNetworkObserverCaptor0);
+                mNetworkObserverCaptor0, false);
 
         // Start softap
         assertTrue(mWifiNative.startSoftAp(IFACE_NAME_0, new SoftApConfiguration.Builder().build(),
-                mock(WifiNative.SoftApListener.class)));
+                true, mock(WifiNative.SoftApListener.class)));
 
+        mInOrder.verify(mHostapdHal).isApInfoCallbackSupported();
+        mInOrder.verify(mHostapdHal).registerApCallback(any(), any());
+        mInOrder.verify(mHostapdHal).addAccessPoint(any(), any(), anyBoolean(), any());
+
+        // Trigger vendor HAL death
+        mHostapdDeathHandlerCaptor.getValue().onDeath();
+
+        mInOrder.verify(mWifiMetrics).incrementNumHostapdCrashes();
+
+        verify(mStatusListener).onStatusChanged(false);
+        verify(mStatusListener).onStatusChanged(true);
+        verify(mWificondControl, never()).registerApCallback(any(), any(), any());
+    }
+
+    /**
+     * Verifies the setup of a soft ap interface and hostapd death handling.
+     */
+    @Test
+    public void testStartSoftApWithWifiCondCallbackAndHostapdDied() throws Exception {
+        executeAndValidateSetupSoftApInterface(
+                false, false, IFACE_NAME_0, mIfaceCallback0, mIfaceDestroyedListenerCaptor0,
+                mNetworkObserverCaptor0, false);
+
+        // Start softap
+        assertTrue(mWifiNative.startSoftAp(IFACE_NAME_0, new SoftApConfiguration.Builder().build(),
+                true, mock(WifiNative.SoftApListener.class)));
+
+        mInOrder.verify(mHostapdHal).isApInfoCallbackSupported();
         mInOrder.verify(mWificondControl).registerApCallback(any(), any(), any());
-        mInOrder.verify(mHostapdHal).addAccessPoint(any(), any(), any());
+        verify(mHostapdHal, never()).registerApCallback(any(), any());
+        mInOrder.verify(mHostapdHal).addAccessPoint(any(), any(), anyBoolean(), any());
 
         // Trigger vendor HAL death
         mHostapdDeathHandlerCaptor.getValue().onDeath();
@@ -808,7 +878,8 @@
     @Test
     public void testSetupClientInterfaceFailureInStartHal() throws Exception {
         when(mWifiVendorHal.startVendorHal()).thenReturn(false);
-        assertNull(mWifiNative.setupInterfaceForClientInConnectivityMode(mIfaceCallback0));
+        assertNull(mWifiNative.setupInterfaceForClientInConnectivityMode(
+                mIfaceCallback0, TEST_WORKSOURCE));
 
         mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
         mInOrder.verify(mWifiVendorHal).startVendorHal();
@@ -826,10 +897,15 @@
     @Test
     public void testSetupClientInterfaceFailureInStartSupplicant() throws Exception {
         when(mSupplicantStaIfaceHal.startDaemon()).thenReturn(false);
-        assertNull(mWifiNative.setupInterfaceForClientInConnectivityMode(mIfaceCallback0));
+        assertNull(mWifiNative.setupInterfaceForClientInConnectivityMode(
+                mIfaceCallback0, TEST_WORKSOURCE));
 
         mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
         mInOrder.verify(mWifiVendorHal).startVendorHal();
+        if (SdkLevel.isAtLeastS()) {
+            mInOrder.verify(mWifiVendorHal).setCoexUnsafeChannels(any(), anyInt());
+            mInOrder.verify(mWificondControl).registerCountryCodeChangedListener(any(), any());
+        }
         mInOrder.verify(mSupplicantStaIfaceHal).isInitializationStarted();
         mInOrder.verify(mSupplicantStaIfaceHal).initialize();
         mInOrder.verify(mSupplicantStaIfaceHal).startDaemon();
@@ -846,18 +922,23 @@
      */
     @Test
     public void testSetupClientInterfaceFailureInHalCreateStaIface() throws Exception {
-        when(mWifiVendorHal.createStaIface(any())).thenReturn(null);
-        assertNull(mWifiNative.setupInterfaceForClientInConnectivityMode(mIfaceCallback0));
+        when(mWifiVendorHal.createStaIface(any(), any())).thenReturn(null);
+        assertNull(mWifiNative.setupInterfaceForClientInConnectivityMode(
+                mIfaceCallback0, TEST_WORKSOURCE));
 
         mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
         mInOrder.verify(mWifiVendorHal).startVendorHal();
+        if (SdkLevel.isAtLeastS()) {
+            mInOrder.verify(mWifiVendorHal).setCoexUnsafeChannels(any(), anyInt());
+            mInOrder.verify(mWificondControl).registerCountryCodeChangedListener(any(), any());
+        }
         mInOrder.verify(mSupplicantStaIfaceHal).isInitializationStarted();
         mInOrder.verify(mSupplicantStaIfaceHal).initialize();
         mInOrder.verify(mSupplicantStaIfaceHal).startDaemon();
         mInOrder.verify(mSupplicantStaIfaceHal).isInitializationComplete();
         mInOrder.verify(mSupplicantStaIfaceHal).registerDeathHandler(any());
         mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
-        mInOrder.verify(mWifiVendorHal).createStaIface(any());
+        mInOrder.verify(mWifiVendorHal).createStaIface(any(), any());
         mInOrder.verify(mWifiMetrics).incrementNumSetupClientInterfaceFailureDueToHal();
 
         // To test if the failure is handled cleanly, invoke teardown and ensure that
@@ -874,10 +955,15 @@
             throws Exception {
         when(mWificondControl.setupInterfaceForClientMode(any(), any(), any(), any())).thenReturn(
                 false);
-        assertNull(mWifiNative.setupInterfaceForClientInConnectivityMode(mIfaceCallback0));
+        assertNull(mWifiNative.setupInterfaceForClientInConnectivityMode(
+                mIfaceCallback0, TEST_WORKSOURCE));
 
         mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
         mInOrder.verify(mWifiVendorHal).startVendorHal();
+        if (SdkLevel.isAtLeastS()) {
+            mInOrder.verify(mWifiVendorHal).setCoexUnsafeChannels(any(), anyInt());
+            mInOrder.verify(mWificondControl).registerCountryCodeChangedListener(any(), any());
+        }
         mInOrder.verify(mSupplicantStaIfaceHal).isInitializationStarted();
         mInOrder.verify(mSupplicantStaIfaceHal).initialize();
         mInOrder.verify(mSupplicantStaIfaceHal).startDaemon();
@@ -885,7 +971,7 @@
         mInOrder.verify(mSupplicantStaIfaceHal).registerDeathHandler(any());
         mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
         mInOrder.verify(mWifiVendorHal).createStaIface(
-                mIfaceDestroyedListenerCaptor0.capture());
+                mIfaceDestroyedListenerCaptor0.capture(), eq(TEST_WORKSOURCE));
         mInOrder.verify(mWificondControl).setupInterfaceForClientMode(any(), any(), any(), any());
         mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
         mInOrder.verify(mWifiVendorHal).removeStaIface(any());
@@ -908,10 +994,15 @@
     @Test
     public void testSetupClientInterfaceFailureInSupplicantSetupIface() throws Exception {
         when(mSupplicantStaIfaceHal.setupIface(any())).thenReturn(false);
-        assertNull(mWifiNative.setupInterfaceForClientInConnectivityMode(mIfaceCallback0));
+        assertNull(mWifiNative.setupInterfaceForClientInConnectivityMode(
+                mIfaceCallback0, TEST_WORKSOURCE));
 
         mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
         mInOrder.verify(mWifiVendorHal).startVendorHal();
+        if (SdkLevel.isAtLeastS()) {
+            mInOrder.verify(mWifiVendorHal).setCoexUnsafeChannels(any(), anyInt());
+            mInOrder.verify(mWificondControl).registerCountryCodeChangedListener(any(), any());
+        }
         mInOrder.verify(mSupplicantStaIfaceHal).isInitializationStarted();
         mInOrder.verify(mSupplicantStaIfaceHal).initialize();
         mInOrder.verify(mSupplicantStaIfaceHal).startDaemon();
@@ -919,7 +1010,7 @@
         mInOrder.verify(mSupplicantStaIfaceHal).registerDeathHandler(any());
         mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
         mInOrder.verify(mWifiVendorHal).createStaIface(
-                mIfaceDestroyedListenerCaptor0.capture());
+                mIfaceDestroyedListenerCaptor0.capture(), eq(TEST_WORKSOURCE));
         mInOrder.verify(mWificondControl).setupInterfaceForClientMode(any(), any(), any(), any());
         mInOrder.verify(mSupplicantStaIfaceHal).setupIface(any());
         mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
@@ -943,7 +1034,8 @@
     @Test
     public void testSetupSoftApInterfaceFailureInStartHal() throws Exception {
         when(mWifiVendorHal.startVendorHal()).thenReturn(false);
-        assertNull(mWifiNative.setupInterfaceForSoftApMode(mIfaceCallback0));
+        assertNull(mWifiNative.setupInterfaceForSoftApMode(mIfaceCallback0, TEST_WORKSOURCE,
+                  SoftApConfiguration.BAND_2GHZ, false));
 
         mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
         mInOrder.verify(mWifiVendorHal).startVendorHal();
@@ -961,10 +1053,15 @@
     @Test
     public void testSetupSoftApInterfaceFailureInStartHostapd() throws Exception {
         when(mHostapdHal.startDaemon()).thenReturn(false);
-        assertNull(mWifiNative.setupInterfaceForSoftApMode(mIfaceCallback0));
+        assertNull(mWifiNative.setupInterfaceForSoftApMode(mIfaceCallback0, TEST_WORKSOURCE,
+                SoftApConfiguration.BAND_2GHZ, false));
 
         mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
         mInOrder.verify(mWifiVendorHal).startVendorHal();
+        if (SdkLevel.isAtLeastS()) {
+            mInOrder.verify(mWifiVendorHal).setCoexUnsafeChannels(any(), anyInt());
+            mInOrder.verify(mWificondControl).registerCountryCodeChangedListener(any(), any());
+        }
         mInOrder.verify(mHostapdHal).isInitializationStarted();
         mInOrder.verify(mHostapdHal).initialize();
         mInOrder.verify(mHostapdHal).startDaemon();
@@ -981,18 +1078,23 @@
      */
     @Test
     public void testSetupSoftApInterfaceFailureInHalCreateApIface() throws Exception {
-        when(mWifiVendorHal.createApIface(any())).thenReturn(null);
-        assertNull(mWifiNative.setupInterfaceForSoftApMode(mIfaceCallback0));
+        when(mWifiVendorHal.createApIface(any(), any(), anyInt(), anyBoolean())).thenReturn(null);
+        assertNull(mWifiNative.setupInterfaceForSoftApMode(
+                mIfaceCallback0, TEST_WORKSOURCE, SoftApConfiguration.BAND_2GHZ, false));
 
         mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
         mInOrder.verify(mWifiVendorHal).startVendorHal();
+        if (SdkLevel.isAtLeastS()) {
+            mInOrder.verify(mWifiVendorHal).setCoexUnsafeChannels(any(), anyInt());
+            mInOrder.verify(mWificondControl).registerCountryCodeChangedListener(any(), any());
+        }
         mInOrder.verify(mHostapdHal).isInitializationStarted();
         mInOrder.verify(mHostapdHal).initialize();
         mInOrder.verify(mHostapdHal).startDaemon();
         mInOrder.verify(mHostapdHal).isInitializationComplete();
         mInOrder.verify(mHostapdHal).registerDeathHandler(any());
         mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
-        mInOrder.verify(mWifiVendorHal).createApIface(any());
+        mInOrder.verify(mWifiVendorHal).createApIface(any(), any(), anyInt(), anyBoolean());
         mInOrder.verify(mWifiMetrics).incrementNumSetupSoftApInterfaceFailureDueToHal();
 
         // To test if the failure is handled cleanly, invoke teardown and ensure that
@@ -1005,20 +1107,66 @@
      * Verifies failure handling in setup of a softAp interface.
      */
     @Test
+    public void testSetupSoftApInterfaceFailureInHalGetBridgedInstances() throws Exception {
+        when(mWifiVendorHal.getBridgedApInstances(any())).thenReturn(null);
+        assertNull(mWifiNative.setupInterfaceForSoftApMode(
+                mIfaceCallback0, TEST_WORKSOURCE, SoftApConfiguration.BAND_2GHZ, true));
+
+        mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
+        mInOrder.verify(mWifiVendorHal).startVendorHal();
+        if (SdkLevel.isAtLeastS()) {
+            mInOrder.verify(mWifiVendorHal).setCoexUnsafeChannels(any(), anyInt());
+            mInOrder.verify(mWificondControl).registerCountryCodeChangedListener(any(), any());
+        }
+        mInOrder.verify(mHostapdHal).isInitializationStarted();
+        mInOrder.verify(mHostapdHal).initialize();
+        mInOrder.verify(mHostapdHal).startDaemon();
+        mInOrder.verify(mHostapdHal).isInitializationComplete();
+        mInOrder.verify(mHostapdHal).registerDeathHandler(any());
+        mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
+        mInOrder.verify(mWifiVendorHal).createApIface(
+                mIfaceDestroyedListenerCaptor0.capture(), eq(TEST_WORKSOURCE), anyInt(), eq(true));
+        mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
+        mInOrder.verify(mWifiVendorHal).getBridgedApInstances(any());
+        mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
+        mInOrder.verify(mWifiVendorHal).removeApIface(any());
+        mInOrder.verify(mWifiMetrics).incrementNumSetupSoftApInterfaceFailureDueToHal();
+
+        // Trigger the HAL interface destroyed callback to verify the whole removal sequence.
+        mIfaceDestroyedListenerCaptor0.getValue().onDestroyed(IFACE_NAME_0);
+        validateOnDestroyedSoftApInterface(false, false, IFACE_NAME_0, mIfaceCallback0,
+                null);
+
+        // To test if the failure is handled cleanly, invoke teardown and ensure that
+        // none of the mocks are used because the iface does not exist in the internal
+        // database.
+        mWifiNative.teardownInterface(IFACE_NAME_0);
+    }
+
+    /**
+     * Verifies failure handling in setup of a softAp interface.
+     */
+    @Test
     public void testSetupSoftApInterfaceFailureInWificondSetupInterfaceForSoftapMode()
             throws Exception {
         when(mWificondControl.setupInterfaceForSoftApMode(any())).thenReturn(false);
-        assertNull(mWifiNative.setupInterfaceForSoftApMode(mIfaceCallback0));
+        assertNull(mWifiNative.setupInterfaceForSoftApMode(
+                mIfaceCallback0, TEST_WORKSOURCE, SoftApConfiguration.BAND_2GHZ, false));
 
         mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
         mInOrder.verify(mWifiVendorHal).startVendorHal();
+        if (SdkLevel.isAtLeastS()) {
+            mInOrder.verify(mWifiVendorHal).setCoexUnsafeChannels(any(), anyInt());
+            mInOrder.verify(mWificondControl).registerCountryCodeChangedListener(any(), any());
+        }
         mInOrder.verify(mHostapdHal).isInitializationStarted();
         mInOrder.verify(mHostapdHal).initialize();
         mInOrder.verify(mHostapdHal).startDaemon();
         mInOrder.verify(mHostapdHal).isInitializationComplete();
         mInOrder.verify(mHostapdHal).registerDeathHandler(any());
         mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
-        mInOrder.verify(mWifiVendorHal).createApIface(mIfaceDestroyedListenerCaptor0.capture());
+        mInOrder.verify(mWifiVendorHal).createApIface(
+                mIfaceDestroyedListenerCaptor0.capture(), eq(TEST_WORKSOURCE), anyInt(), eq(false));
         mInOrder.verify(mWificondControl).setupInterfaceForSoftApMode(any());
         mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
         mInOrder.verify(mWifiVendorHal).removeApIface(any());
@@ -1058,7 +1206,7 @@
      */
     @Test
     public void testGetClientInterfaceNameWithNoInterfacesSetup() throws Exception {
-        assertNull(mWifiNative.getClientInterfaceName());
+        assertEquals(Set.of(), mWifiNative.getClientInterfaceNames());
     }
 
     /**
@@ -1069,7 +1217,7 @@
         executeAndValidateSetupSoftApInterface(
                 false, false, IFACE_NAME_0, mIfaceCallback0, mIfaceDestroyedListenerCaptor0,
                 mNetworkObserverCaptor0);
-        assertNull(mWifiNative.getClientInterfaceName());
+        assertEquals(Set.of(), mWifiNative.getClientInterfaceNames());
     }
 
     /**
@@ -1080,7 +1228,7 @@
         executeAndValidateSetupClientInterface(
                 false, false, IFACE_NAME_0, mIfaceCallback0, mIfaceDestroyedListenerCaptor0,
                 mNetworkObserverCaptor0);
-        assertEquals(IFACE_NAME_0, mWifiNative.getClientInterfaceName());
+        assertEquals(Set.of(IFACE_NAME_0), mWifiNative.getClientInterfaceNames());
     }
 
     /**
@@ -1095,9 +1243,7 @@
         executeAndValidateSetupClientInterface(
                 true, false, IFACE_NAME_1, mIfaceCallback1, mIfaceDestroyedListenerCaptor1,
                 mNetworkObserverCaptor1);
-        String interfaceName = mWifiNative.getClientInterfaceName();
-        assertNotNull(interfaceName);
-        assertTrue(interfaceName.equals(IFACE_NAME_0) || interfaceName.equals(IFACE_NAME_1));
+        assertEquals(Set.of(IFACE_NAME_0, IFACE_NAME_1), mWifiNative.getClientInterfaceNames());
     }
 
     /*
@@ -1113,9 +1259,13 @@
 
         // First setup a STA interface and verify.
         assertEquals(IFACE_NAME_0,
-                mWifiNative.setupInterfaceForClientInConnectivityMode(mIfaceCallback0));
+                mWifiNative.setupInterfaceForClientInConnectivityMode(
+                        mIfaceCallback0, TEST_WORKSOURCE));
 
         mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
+        if (SdkLevel.isAtLeastS()) {
+            mInOrder.verify(mWificondControl).registerCountryCodeChangedListener(any(), any());
+        }
         mInOrder.verify(mSupplicantStaIfaceHal).isInitializationStarted();
         mInOrder.verify(mSupplicantStaIfaceHal).initialize();
         mInOrder.verify(mSupplicantStaIfaceHal).startDaemon();
@@ -1131,12 +1281,13 @@
         mInOrder.verify(mNetdWrapper).clearInterfaceAddresses(IFACE_NAME_0);
         mInOrder.verify(mNetdWrapper).setInterfaceIpv6PrivacyExtensions(IFACE_NAME_0, true);
         mInOrder.verify(mNetdWrapper).disableIpv6(IFACE_NAME_0);
-        mInOrder.verify(mSupplicantStaIfaceHal).getAdvancedKeyMgmtCapabilities(IFACE_NAME_0);
+        mInOrder.verify(mSupplicantStaIfaceHal).getAdvancedCapabilities(IFACE_NAME_0);
         mInOrder.verify(mWifiVendorHal).getSupportedFeatureSet(IFACE_NAME_0);
         mInOrder.verify(mSupplicantStaIfaceHal).getWpaDriverFeatureSet(IFACE_NAME_0);
 
         // Now setup an AP interface.
-        assertEquals(IFACE_NAME_0, mWifiNative.setupInterfaceForSoftApMode(mIfaceCallback1));
+        assertEquals(IFACE_NAME_0, mWifiNative.setupInterfaceForSoftApMode(
+                mIfaceCallback1, TEST_WORKSOURCE, SoftApConfiguration.BAND_2GHZ, false));
 
         mInOrder.verify(mHostapdHal).isInitializationStarted();
         mInOrder.verify(mHostapdHal).initialize();
@@ -1152,12 +1303,13 @@
         mInOrder.verify(mWificondControl).tearDownClientInterface(IFACE_NAME_0);
         mInOrder.verify(mSupplicantStaIfaceHal).deregisterDeathHandler();
         mInOrder.verify(mSupplicantStaIfaceHal).terminate();
+        mInOrder.verify(mWifiVendorHal).isVendorHalReady();
         mInOrder.verify(mIfaceCallback0).onDestroyed(IFACE_NAME_0);
         // Now continue with rest of AP interface setup.
         mInOrder.verify(mWificondControl).setupInterfaceForSoftApMode(IFACE_NAME_0);
         mInOrder.verify(mNetdWrapper).registerObserver(mNetworkObserverCaptor1.capture());
         mInOrder.verify(mNetdWrapper).isInterfaceUp(IFACE_NAME_0);
-        mInOrder.verify(mSupplicantStaIfaceHal).getAdvancedKeyMgmtCapabilities(IFACE_NAME_0);
+        mInOrder.verify(mSupplicantStaIfaceHal).getAdvancedCapabilities(IFACE_NAME_0);
         mInOrder.verify(mWifiVendorHal).getSupportedFeatureSet(IFACE_NAME_0);
         mInOrder.verify(mSupplicantStaIfaceHal).getWpaDriverFeatureSet(IFACE_NAME_0);
     }
@@ -1174,9 +1326,13 @@
         when(mPropertyService.getString(any(), any())).thenReturn(IFACE_NAME_0);
 
         // First setup an AP interface and verify.
-        assertEquals(IFACE_NAME_0, mWifiNative.setupInterfaceForSoftApMode(mIfaceCallback0));
+        assertEquals(IFACE_NAME_0, mWifiNative.setupInterfaceForSoftApMode(
+                mIfaceCallback0, TEST_WORKSOURCE, SoftApConfiguration.BAND_2GHZ, false));
 
         mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
+        if (SdkLevel.isAtLeastS()) {
+            mInOrder.verify(mWificondControl).registerCountryCodeChangedListener(any(), any());
+        }
         mInOrder.verify(mHostapdHal).isInitializationStarted();
         mInOrder.verify(mHostapdHal).initialize();
         mInOrder.verify(mHostapdHal).startDaemon();
@@ -1186,13 +1342,14 @@
         mInOrder.verify(mWificondControl).setupInterfaceForSoftApMode(IFACE_NAME_0);
         mInOrder.verify(mNetdWrapper).registerObserver(mNetworkObserverCaptor0.capture());
         mInOrder.verify(mNetdWrapper).isInterfaceUp(IFACE_NAME_0);
-        mInOrder.verify(mSupplicantStaIfaceHal).getAdvancedKeyMgmtCapabilities(IFACE_NAME_0);
+        mInOrder.verify(mSupplicantStaIfaceHal).getAdvancedCapabilities(IFACE_NAME_0);
         mInOrder.verify(mWifiVendorHal).getSupportedFeatureSet(IFACE_NAME_0);
         mInOrder.verify(mSupplicantStaIfaceHal).getWpaDriverFeatureSet(IFACE_NAME_0);
 
         // Now setup a STA interface.
         assertEquals(IFACE_NAME_0,
-                mWifiNative.setupInterfaceForClientInConnectivityMode(mIfaceCallback1));
+                mWifiNative.setupInterfaceForClientInConnectivityMode(
+                        mIfaceCallback1, TEST_WORKSOURCE));
 
         mInOrder.verify(mSupplicantStaIfaceHal).isInitializationStarted();
         mInOrder.verify(mSupplicantStaIfaceHal).initialize();
@@ -1207,6 +1364,7 @@
         mInOrder.verify(mWificondControl).tearDownSoftApInterface(IFACE_NAME_0);
         mInOrder.verify(mHostapdHal).deregisterDeathHandler();
         mInOrder.verify(mHostapdHal).terminate();
+        mInOrder.verify(mWifiVendorHal).isVendorHalReady();
         mInOrder.verify(mIfaceCallback0).onDestroyed(IFACE_NAME_0);
         // Now continue with rest of STA interface setup.
         mInOrder.verify(mWificondControl).setupInterfaceForClientMode(eq(IFACE_NAME_0), any(),
@@ -1218,7 +1376,7 @@
         mInOrder.verify(mNetdWrapper).clearInterfaceAddresses(IFACE_NAME_0);
         mInOrder.verify(mNetdWrapper).setInterfaceIpv6PrivacyExtensions(IFACE_NAME_0, true);
         mInOrder.verify(mNetdWrapper).disableIpv6(IFACE_NAME_0);
-        mInOrder.verify(mSupplicantStaIfaceHal).getAdvancedKeyMgmtCapabilities(IFACE_NAME_0);
+        mInOrder.verify(mSupplicantStaIfaceHal).getAdvancedCapabilities(IFACE_NAME_0);
         mInOrder.verify(mWifiVendorHal).getSupportedFeatureSet(IFACE_NAME_0);
         mInOrder.verify(mSupplicantStaIfaceHal).getWpaDriverFeatureSet(IFACE_NAME_0);
     }
@@ -1252,12 +1410,14 @@
         executeAndValidateSetupClientInterface(
                 false, false, IFACE_NAME_0, mIfaceCallback0, mIfaceDestroyedListenerCaptor0,
                 mNetworkObserverCaptor0);
-        assertTrue(mWifiNative.switchClientInterfaceToScanMode(IFACE_NAME_0));
+        assertTrue(mWifiNative.switchClientInterfaceToScanMode(IFACE_NAME_0, TEST_WORKSOURCE));
 
+        mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
+        mInOrder.verify(mWifiVendorHal).replaceStaIfaceRequestorWs(IFACE_NAME_0, TEST_WORKSOURCE);
         mInOrder.verify(mSupplicantStaIfaceHal).teardownIface(IFACE_NAME_0);
         mInOrder.verify(mSupplicantStaIfaceHal).deregisterDeathHandler();
         mInOrder.verify(mSupplicantStaIfaceHal).terminate();
-        mInOrder.verify(mSupplicantStaIfaceHal).getAdvancedKeyMgmtCapabilities(IFACE_NAME_0);
+        mInOrder.verify(mSupplicantStaIfaceHal).getAdvancedCapabilities(IFACE_NAME_0);
         mInOrder.verify(mWifiVendorHal).getSupportedFeatureSet(IFACE_NAME_0);
         mInOrder.verify(mSupplicantStaIfaceHal).getWpaDriverFeatureSet(IFACE_NAME_0);
     }
@@ -1270,7 +1430,7 @@
         executeAndValidateSetupClientInterfaceForScan(
                 false, false, IFACE_NAME_0, mIfaceCallback0, mIfaceDestroyedListenerCaptor0,
                 mNetworkObserverCaptor0);
-        assertTrue(mWifiNative.switchClientInterfaceToScanMode(IFACE_NAME_0));
+        assertTrue(mWifiNative.switchClientInterfaceToScanMode(IFACE_NAME_0, TEST_WORKSOURCE));
     }
 
     /**
@@ -1281,15 +1441,18 @@
         executeAndValidateSetupClientInterfaceForScan(
                 false, false, IFACE_NAME_0, mIfaceCallback0, mIfaceDestroyedListenerCaptor0,
                 mNetworkObserverCaptor0);
-        assertTrue(mWifiNative.switchClientInterfaceToConnectivityMode(IFACE_NAME_0));
+        assertTrue(mWifiNative.switchClientInterfaceToConnectivityMode(
+                IFACE_NAME_0, TEST_WORKSOURCE));
 
+        mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
+        mInOrder.verify(mWifiVendorHal).replaceStaIfaceRequestorWs(IFACE_NAME_0, TEST_WORKSOURCE);
         mInOrder.verify(mSupplicantStaIfaceHal).isInitializationStarted();
         mInOrder.verify(mSupplicantStaIfaceHal).initialize();
         mInOrder.verify(mSupplicantStaIfaceHal).startDaemon();
         mInOrder.verify(mSupplicantStaIfaceHal).isInitializationComplete();
         mInOrder.verify(mSupplicantStaIfaceHal).registerDeathHandler(any());
         mInOrder.verify(mSupplicantStaIfaceHal).setupIface(IFACE_NAME_0);
-        mInOrder.verify(mSupplicantStaIfaceHal).getAdvancedKeyMgmtCapabilities(IFACE_NAME_0);
+        mInOrder.verify(mSupplicantStaIfaceHal).getAdvancedCapabilities(IFACE_NAME_0);
         mInOrder.verify(mWifiVendorHal).getSupportedFeatureSet(IFACE_NAME_0);
         mInOrder.verify(mSupplicantStaIfaceHal).getWpaDriverFeatureSet(IFACE_NAME_0);
     }
@@ -1303,7 +1466,8 @@
         executeAndValidateSetupClientInterface(
                 false, false, IFACE_NAME_0, mIfaceCallback0, mIfaceDestroyedListenerCaptor0,
                 mNetworkObserverCaptor0);
-        assertTrue(mWifiNative.switchClientInterfaceToConnectivityMode(IFACE_NAME_0));
+        assertTrue(mWifiNative.switchClientInterfaceToConnectivityMode(
+                IFACE_NAME_0, TEST_WORKSOURCE));
     }
 
     /**
@@ -1314,24 +1478,22 @@
         executeAndValidateSetupClientInterface(
                 false, false, IFACE_NAME_0, mIfaceCallback0, mIfaceDestroyedListenerCaptor0,
                 mNetworkObserverCaptor0);
-        assertEquals(new HashSet<>(Arrays.asList(IFACE_NAME_0)),
-                mWifiNative.getClientInterfaceNames());
+        assertEquals(Set.of(IFACE_NAME_0), mWifiNative.getClientInterfaceNames());
 
         executeAndValidateSetupClientInterface(
                 true, false, IFACE_NAME_1, mIfaceCallback1, mIfaceDestroyedListenerCaptor1,
                 mNetworkObserverCaptor1);
-        assertEquals(new HashSet<>(Arrays.asList(IFACE_NAME_0, IFACE_NAME_1)),
-                mWifiNative.getClientInterfaceNames());
+        assertEquals(Set.of(IFACE_NAME_0, IFACE_NAME_1), mWifiNative.getClientInterfaceNames());
     }
 
-
     private void executeAndValidateSetupClientInterface(
             boolean existingStaIface, boolean existingApIface,
             String ifaceName, @Mock WifiNative.InterfaceCallback callback,
             ArgumentCaptor<InterfaceDestroyedListener> destroyedListenerCaptor,
             ArgumentCaptor<NetdEventObserver> networkObserverCaptor) throws Exception {
-        when(mWifiVendorHal.createStaIface(any())).thenReturn(ifaceName);
-        assertEquals(ifaceName, mWifiNative.setupInterfaceForClientInConnectivityMode(callback));
+        when(mWifiVendorHal.createStaIface(any(), any())).thenReturn(ifaceName);
+        assertEquals(ifaceName, mWifiNative.setupInterfaceForClientInConnectivityMode(
+                callback, TEST_WORKSOURCE));
 
         validateSetupClientInterface(
                 existingStaIface, existingApIface, ifaceName, destroyedListenerCaptor,
@@ -1345,6 +1507,10 @@
         if (!existingStaIface && !existingApIface) {
             mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
             mInOrder.verify(mWifiVendorHal).startVendorHal();
+            if (SdkLevel.isAtLeastS()) {
+                mInOrder.verify(mWifiVendorHal).setCoexUnsafeChannels(any(), anyInt());
+                mInOrder.verify(mWificondControl).registerCountryCodeChangedListener(any(), any());
+            }
         }
         if (!existingStaIface) {
             mInOrder.verify(mSupplicantStaIfaceHal).isInitializationStarted();
@@ -1355,7 +1521,7 @@
         }
         mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
         mInOrder.verify(mWifiVendorHal).createStaIface(
-                destroyedListenerCaptor.capture());
+                destroyedListenerCaptor.capture(), eq(TEST_WORKSOURCE));
         mInOrder.verify(mWificondControl).setupInterfaceForClientMode(eq(ifaceName), any(), any(),
                 any());
         mInOrder.verify(mSupplicantStaIfaceHal).setupIface(ifaceName);
@@ -1365,7 +1531,7 @@
         mInOrder.verify(mNetdWrapper).clearInterfaceAddresses(ifaceName);
         mInOrder.verify(mNetdWrapper).setInterfaceIpv6PrivacyExtensions(ifaceName, true);
         mInOrder.verify(mNetdWrapper).disableIpv6(ifaceName);
-        mInOrder.verify(mSupplicantStaIfaceHal).getAdvancedKeyMgmtCapabilities(ifaceName);
+        mInOrder.verify(mSupplicantStaIfaceHal).getAdvancedCapabilities(ifaceName);
         mInOrder.verify(mWifiVendorHal).getSupportedFeatureSet(ifaceName);
         mInOrder.verify(mSupplicantStaIfaceHal).getWpaDriverFeatureSet(ifaceName);
     }
@@ -1407,6 +1573,7 @@
             mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
             mInOrder.verify(mWifiVendorHal).stopVendorHal();
         }
+        mInOrder.verify(mWifiVendorHal).isVendorHalReady();
         mInOrder.verify(callback).onDestroyed(ifaceName);
     }
 
@@ -1415,8 +1582,9 @@
             String ifaceName, @Mock WifiNative.InterfaceCallback callback,
             ArgumentCaptor<InterfaceDestroyedListener> destroyedListenerCaptor,
             ArgumentCaptor<NetdEventObserver> networkObserverCaptor) throws Exception {
-        when(mWifiVendorHal.createStaIface(any())).thenReturn(ifaceName);
-        assertEquals(ifaceName, mWifiNative.setupInterfaceForClientInScanMode(callback));
+        when(mWifiVendorHal.createStaIface(any(), any())).thenReturn(ifaceName);
+        assertEquals(ifaceName, mWifiNative.setupInterfaceForClientInScanMode(
+                callback, TEST_WORKSOURCE));
 
         validateSetupClientInterfaceForScan(
                 existingStaIface, existingApIface, ifaceName, destroyedListenerCaptor,
@@ -1430,16 +1598,20 @@
         if (!existingStaIface && !existingApIface) {
             mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
             mInOrder.verify(mWifiVendorHal).startVendorHal();
+            if (SdkLevel.isAtLeastS()) {
+                mInOrder.verify(mWifiVendorHal).setCoexUnsafeChannels(any(), anyInt());
+                mInOrder.verify(mWificondControl).registerCountryCodeChangedListener(any(), any());
+            }
         }
         mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
         mInOrder.verify(mWifiVendorHal).createStaIface(
-                destroyedListenerCaptor.capture());
+                destroyedListenerCaptor.capture(), eq(TEST_WORKSOURCE));
         mInOrder.verify(mWificondControl).setupInterfaceForClientMode(eq(ifaceName), any(), any(),
                 any());
         mInOrder.verify(mNetdWrapper).registerObserver(networkObserverCaptor.capture());
         mInOrder.verify(mWifiMonitor).startMonitoring(ifaceName);
         mInOrder.verify(mNetdWrapper).isInterfaceUp(ifaceName);
-        mInOrder.verify(mSupplicantStaIfaceHal).getAdvancedKeyMgmtCapabilities(ifaceName);
+        mInOrder.verify(mSupplicantStaIfaceHal).getAdvancedCapabilities(ifaceName);
         mInOrder.verify(mWifiVendorHal).getSupportedFeatureSet(ifaceName);
         mInOrder.verify(mSupplicantStaIfaceHal).getWpaDriverFeatureSet(ifaceName);
     }
@@ -1476,6 +1648,7 @@
             mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
             mInOrder.verify(mWifiVendorHal).stopVendorHal();
         }
+        mInOrder.verify(mWifiVendorHal).isVendorHalReady();
         mInOrder.verify(callback).onDestroyed(ifaceName);
     }
 
@@ -1484,21 +1657,38 @@
             String ifaceName, @Mock WifiNative.InterfaceCallback callback,
             ArgumentCaptor<InterfaceDestroyedListener> destroyedListenerCaptor,
             ArgumentCaptor<NetdEventObserver> networkObserverCaptor) throws Exception {
-        when(mWifiVendorHal.createApIface(any())).thenReturn(ifaceName);
-        assertEquals(ifaceName, mWifiNative.setupInterfaceForSoftApMode(callback));
+        executeAndValidateSetupSoftApInterface(existingStaIface, existingApIface, ifaceName,
+                callback, destroyedListenerCaptor, networkObserverCaptor, false);
+    }
+
+    private void executeAndValidateSetupSoftApInterface(
+            boolean existingStaIface, boolean existingApIface,
+            String ifaceName, @Mock WifiNative.InterfaceCallback callback,
+            ArgumentCaptor<InterfaceDestroyedListener> destroyedListenerCaptor,
+            ArgumentCaptor<NetdEventObserver> networkObserverCaptor, boolean isBridged)
+            throws Exception {
+        when(mWifiVendorHal.createApIface(any(), any(), anyInt(), eq(isBridged)))
+                .thenReturn(ifaceName);
+        assertEquals(ifaceName, mWifiNative.setupInterfaceForSoftApMode(
+                callback, TEST_WORKSOURCE, SoftApConfiguration.BAND_2GHZ, isBridged));
 
         validateSetupSoftApInterface(
                 existingStaIface, existingApIface, ifaceName, destroyedListenerCaptor,
-                networkObserverCaptor);
+                networkObserverCaptor, isBridged);
     }
 
     private void validateSetupSoftApInterface(
             boolean existingStaIface, boolean existingApIface,
             String ifaceName, ArgumentCaptor<InterfaceDestroyedListener> destroyedListenerCaptor,
-            ArgumentCaptor<NetdEventObserver> networkObserverCaptor) throws Exception {
+            ArgumentCaptor<NetdEventObserver> networkObserverCaptor, boolean isBridged)
+            throws Exception {
         if (!existingStaIface && !existingApIface) {
             mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
             mInOrder.verify(mWifiVendorHal).startVendorHal();
+            if (SdkLevel.isAtLeastS()) {
+                mInOrder.verify(mWifiVendorHal).setCoexUnsafeChannels(any(), anyInt());
+                mInOrder.verify(mWificondControl).registerCountryCodeChangedListener(any(), any());
+            }
         }
         if (!existingApIface) {
             mInOrder.verify(mHostapdHal).isInitializationStarted();
@@ -1508,11 +1698,17 @@
             mInOrder.verify(mHostapdHal).registerDeathHandler(any());
         }
         mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
-        mInOrder.verify(mWifiVendorHal).createApIface(destroyedListenerCaptor.capture());
+        mInOrder.verify(mWifiVendorHal).createApIface(
+                destroyedListenerCaptor.capture(), eq(TEST_WORKSOURCE),
+                eq(SoftApConfiguration.BAND_2GHZ), eq(isBridged));
+        if (isBridged) {
+            mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
+            mInOrder.verify(mWifiVendorHal).getBridgedApInstances(eq(ifaceName));
+        }
         mInOrder.verify(mWificondControl).setupInterfaceForSoftApMode(ifaceName);
         mInOrder.verify(mNetdWrapper).registerObserver(networkObserverCaptor.capture());
         mInOrder.verify(mNetdWrapper).isInterfaceUp(ifaceName);
-        mInOrder.verify(mSupplicantStaIfaceHal).getAdvancedKeyMgmtCapabilities(ifaceName);
+        mInOrder.verify(mSupplicantStaIfaceHal).getAdvancedCapabilities(ifaceName);
         mInOrder.verify(mWifiVendorHal).getSupportedFeatureSet(ifaceName);
         mInOrder.verify(mSupplicantStaIfaceHal).getWpaDriverFeatureSet(ifaceName);
     }
@@ -1553,6 +1749,7 @@
             mInOrder.verify(mWifiVendorHal).isVendorHalSupported();
             mInOrder.verify(mWifiVendorHal).stopVendorHal();
         }
+        mInOrder.verify(mWifiVendorHal).isVendorHalReady();
         mInOrder.verify(callback).onDestroyed(ifaceName);
     }
 
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiNativeTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiNativeTest.java
index 46a3de8..9df7ae1 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiNativeTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiNativeTest.java
@@ -16,22 +16,31 @@
 
 package com.android.server.wifi;
 
+import static android.net.wifi.WifiScanner.WIFI_BAND_24_GHZ;
+import static android.net.wifi.WifiScanner.WIFI_BAND_5_GHZ;
+
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.net.MacAddress;
+import android.net.wifi.CoexUnsafeChannel;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiScanner;
@@ -39,10 +48,15 @@
 import android.net.wifi.nl80211.RadioChainInfo;
 import android.net.wifi.nl80211.WifiNl80211Manager;
 import android.net.wifi.nl80211.WifiNl80211Manager.SendMgmtFrameCallback;
+import android.os.Bundle;
 import android.os.Handler;
+import android.os.WorkSource;
+import android.text.TextUtils;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.modules.utils.build.SdkLevel;
+import com.android.server.wifi.coex.CoexManager;
 import com.android.server.wifi.util.NativeUtil;
 import com.android.server.wifi.util.NetdWrapper;
 
@@ -55,6 +69,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Random;
@@ -220,6 +235,8 @@
 
     private static final RadioChainInfo MOCK_NATIVE_RADIO_CHAIN_INFO_1 = new RadioChainInfo(1, -89);
     private static final RadioChainInfo MOCK_NATIVE_RADIO_CHAIN_INFO_2 = new RadioChainInfo(0, -78);
+    private static final WorkSource TEST_WORKSOURCE = new WorkSource();
+    private static final WorkSource TEST_WORKSOURCE2 = new WorkSource();
 
     @Mock private WifiVendorHal mWifiVendorHal;
     @Mock private WifiNl80211Manager mWificondControl;
@@ -233,6 +250,10 @@
     @Mock private Random mRandom;
     @Mock private WifiInjector mWifiInjector;
     @Mock private NetdWrapper mNetdWrapper;
+    @Mock private CoexManager mCoexManager;
+    @Mock BuildProperties mBuildProperties;
+    @Mock private WifiNative.InterfaceCallback mInterfaceCallback;
+    @Mock private WifiCountryCode.ChangeListener mWifiCountryCodeChangeListener;
 
     ArgumentCaptor<WifiNl80211Manager.ScanEventCallback> mScanCallbackCaptor =
             ArgumentCaptor.forClass(WifiNl80211Manager.ScanEventCallback.class);
@@ -248,7 +269,11 @@
         when(mWifiVendorHal.startVendorHal()).thenReturn(true);
         when(mWifiVendorHal.startVendorHalSta()).thenReturn(true);
         when(mWifiVendorHal.startVendorHalAp()).thenReturn(true);
-        when(mWifiVendorHal.createStaIface(any())).thenReturn(WIFI_IFACE_NAME);
+        when(mWifiVendorHal.createStaIface(any(), any())).thenReturn(WIFI_IFACE_NAME);
+
+        when(mBuildProperties.isEngBuild()).thenReturn(false);
+        when(mBuildProperties.isUserdebugBuild()).thenReturn(false);
+        when(mBuildProperties.isUserBuild()).thenReturn(true);
 
         when(mWificondControl.setupInterfaceForClientMode(any(), any(), any(), any())).thenReturn(
                 true);
@@ -260,11 +285,12 @@
         when(mStaIfaceHal.setupIface(any())).thenReturn(true);
 
         when(mWifiInjector.makeNetdWrapper()).thenReturn(mNetdWrapper);
+        when(mWifiInjector.getCoexManager()).thenReturn(mCoexManager);
 
         mWifiNative = new WifiNative(
                 mWifiVendorHal, mStaIfaceHal, mHostapdHal, mWificondControl,
                 mWifiMonitor, mPropertyService, mWifiMetrics,
-                mHandler, mRandom, mWifiInjector);
+                mHandler, mRandom, mBuildProperties, mWifiInjector);
         mWifiNative.initialize();
     }
 
@@ -559,9 +585,8 @@
      */
     @Test
     public void testGetTxPktFatesReturnsErrorWhenHalIsNotStarted() {
-        WifiNative.TxFateReport[] fateReports = null;
         assertFalse(mWifiNative.isHalStarted());
-        assertFalse(mWifiNative.getTxPktFates(WIFI_IFACE_NAME, fateReports));
+        assertEquals(0, mWifiNative.getTxPktFates(WIFI_IFACE_NAME).size());
     }
 
     /**
@@ -569,9 +594,8 @@
      */
     @Test
     public void testGetRxPktFatesReturnsErrorWhenHalIsNotStarted() {
-        WifiNative.RxFateReport[] fateReports = null;
         assertFalse(mWifiNative.isHalStarted());
-        assertFalse(mWifiNative.getRxPktFates(WIFI_IFACE_NAME, fateReports));
+        assertEquals(0, mWifiNative.getRxPktFates(WIFI_IFACE_NAME).size());
     }
 
     // TODO(quiche): Add tests for the success cases (when HAL has been started). Specifically:
@@ -596,7 +620,7 @@
      */
     @Test
     public void testClientModeScanSuccess() {
-        mWifiNative.setupInterfaceForClientInConnectivityMode(null);
+        mWifiNative.setupInterfaceForClientInConnectivityMode(null, TEST_WORKSOURCE);
         verify(mWificondControl).setupInterfaceForClientMode(eq(WIFI_IFACE_NAME), any(),
                 mScanCallbackCaptor.capture(), any());
 
@@ -609,7 +633,7 @@
      */
     @Test
     public void testClientModeScanFailure() {
-        mWifiNative.setupInterfaceForClientInConnectivityMode(null);
+        mWifiNative.setupInterfaceForClientInConnectivityMode(null, TEST_WORKSOURCE);
         verify(mWificondControl).setupInterfaceForClientMode(eq(WIFI_IFACE_NAME), any(),
                 mScanCallbackCaptor.capture(), any());
 
@@ -622,7 +646,7 @@
      */
     @Test
     public void testClientModePnoScanSuccess() {
-        mWifiNative.setupInterfaceForClientInConnectivityMode(null);
+        mWifiNative.setupInterfaceForClientInConnectivityMode(null, TEST_WORKSOURCE);
         verify(mWificondControl).setupInterfaceForClientMode(eq(WIFI_IFACE_NAME), any(),
                 any(), mScanCallbackCaptor.capture());
 
@@ -636,7 +660,7 @@
      */
     @Test
     public void testClientModePnoScanFailure() {
-        mWifiNative.setupInterfaceForClientInConnectivityMode(null);
+        mWifiNative.setupInterfaceForClientInConnectivityMode(null, TEST_WORKSOURCE);
         verify(mWificondControl).setupInterfaceForClientMode(eq(WIFI_IFACE_NAME), any(),
                 any(), mScanCallbackCaptor.capture());
 
@@ -649,7 +673,7 @@
      */
     @Test
     public void testScanModeScanSuccess() {
-        mWifiNative.setupInterfaceForClientInScanMode(null);
+        mWifiNative.setupInterfaceForClientInScanMode(null, TEST_WORKSOURCE);
         verify(mWificondControl).setupInterfaceForClientMode(eq(WIFI_IFACE_NAME), any(),
                 mScanCallbackCaptor.capture(), any());
 
@@ -662,7 +686,7 @@
      */
     @Test
     public void testScanModeScanFailure() {
-        mWifiNative.setupInterfaceForClientInScanMode(null);
+        mWifiNative.setupInterfaceForClientInScanMode(null, TEST_WORKSOURCE);
         verify(mWificondControl).setupInterfaceForClientMode(eq(WIFI_IFACE_NAME), any(),
                 mScanCallbackCaptor.capture(), any());
 
@@ -675,7 +699,7 @@
      */
     @Test
     public void testScanModePnoScanSuccess() {
-        mWifiNative.setupInterfaceForClientInScanMode(null);
+        mWifiNative.setupInterfaceForClientInScanMode(null, TEST_WORKSOURCE);
         verify(mWificondControl).setupInterfaceForClientMode(eq(WIFI_IFACE_NAME), any(),
                 any(), mScanCallbackCaptor.capture());
 
@@ -689,7 +713,7 @@
      */
     @Test
     public void testScanModePnoScanFailure() {
-        mWifiNative.setupInterfaceForClientInScanMode(null);
+        mWifiNative.setupInterfaceForClientInScanMode(null, TEST_WORKSOURCE);
         verify(mWificondControl).setupInterfaceForClientMode(eq(WIFI_IFACE_NAME), any(),
                 any(), mScanCallbackCaptor.capture());
 
@@ -697,6 +721,31 @@
         verify(mWifiMetrics).incrementPnoScanFailedCount();
     }
 
+    /**
+     * Verifies starting the hal results in coex unsafe channels being updated with cached values.
+     */
+    @Test
+    public void testStartHalUpdatesCoexUnsafeChannels() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        final List<CoexUnsafeChannel> unsafeChannels = new ArrayList<>();
+        unsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6));
+        unsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 36));
+        final int restrictions = 0;
+        when(mCoexManager.getCoexUnsafeChannels()).thenReturn(unsafeChannels);
+        when(mCoexManager.getCoexRestrictions()).thenReturn(restrictions);
+        mWifiNative.setCoexUnsafeChannels(unsafeChannels, restrictions);
+
+        mWifiNative.setupInterfaceForClientInConnectivityMode(null, TEST_WORKSOURCE);
+        verify(mWifiVendorHal, times(2)).setCoexUnsafeChannels(unsafeChannels, restrictions);
+
+        mWifiNative.teardownAllInterfaces();
+        mWifiNative.setupInterfaceForClientInScanMode(null, TEST_WORKSOURCE);
+        verify(mWifiVendorHal, times(3)).setCoexUnsafeChannels(unsafeChannels, restrictions);
+
+        mWifiNative.teardownAllInterfaces();
+        mWifiNative.setupInterfaceForSoftApMode(null, TEST_WORKSOURCE, WIFI_BAND_24_GHZ, false);
+        verify(mWifiVendorHal, times(4)).setCoexUnsafeChannels(unsafeChannels, restrictions);
+    }
 
     /**
      * Verifies that signalPoll() calls underlying WificondControl.
@@ -721,8 +770,10 @@
      */
     @Test
     public void testScan() throws Exception {
+        // This test will not run if the device has SDK level S or later
+        assumeFalse(SdkLevel.isAtLeastS());
         mWifiNative.scan(WIFI_IFACE_NAME, WifiScanner.SCAN_TYPE_HIGH_ACCURACY, SCAN_FREQ_SET,
-                SCAN_HIDDEN_NETWORK_SSID_SET);
+                SCAN_HIDDEN_NETWORK_SSID_SET, false);
         ArgumentCaptor<List<byte[]>> ssidSetCaptor = ArgumentCaptor.forClass(List.class);
         verify(mWificondControl).startScan(
                 eq(WIFI_IFACE_NAME), eq(WifiScanner.SCAN_TYPE_HIGH_ACCURACY),
@@ -732,6 +783,27 @@
     }
 
     /**
+     * Verifies that scan() calls the new startScan API with a Bundle when the Sdk level
+     * is S or above.
+     */
+    @Test
+    public void testScanWithBundle() throws Exception {
+        // This test will only run if the device has SDK level S and later.
+        assumeTrue(SdkLevel.isAtLeastS());
+        mWifiNative.scan(WIFI_IFACE_NAME, WifiScanner.SCAN_TYPE_HIGH_ACCURACY, SCAN_FREQ_SET,
+                SCAN_HIDDEN_NETWORK_SSID_SET, true);
+        ArgumentCaptor<List<byte[]>> ssidSetCaptor = ArgumentCaptor.forClass(List.class);
+        ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(mWificondControl).startScan(
+                eq(WIFI_IFACE_NAME), eq(WifiScanner.SCAN_TYPE_HIGH_ACCURACY),
+                eq(SCAN_FREQ_SET), ssidSetCaptor.capture(), bundleCaptor.capture());
+        List<byte[]> ssidSet = ssidSetCaptor.getValue();
+        assertArrayEquals(ssidSet.toArray(), SCAN_HIDDEN_NETWORK_BYTE_SSID_SET.toArray());
+        Bundle bundle = bundleCaptor.getValue();
+        assertTrue(bundle.getBoolean(WifiNl80211Manager.SCANNING_PARAM_ENABLE_6GHZ_RNR));
+    }
+
+    /**
      * Verifies that startPnoscan() calls underlying WificondControl.
      */
     @Test
@@ -856,22 +928,70 @@
     }
 
     /**
+     * Verifies that removeIfaceInstanceFromBridgedApIface() calls underlying WifiVendorHal.
+     */
+    @Test
+    public void testRemoveIfaceInstanceFromBridgedApIface() throws Exception {
+        mWifiNative.removeIfaceInstanceFromBridgedApIface(
+                "br_" + WIFI_IFACE_NAME, WIFI_IFACE_NAME);
+        verify(mWifiVendorHal).removeIfaceInstanceFromBridgedApIface(
+                "br_" + WIFI_IFACE_NAME, WIFI_IFACE_NAME);
+    }
+
+    /**
      * Verifies that setMacAddress() calls underlying WifiVendorHal.
      */
     @Test
-    public void testSetMacAddress() throws Exception {
-        mWifiNative.setMacAddress(WIFI_IFACE_NAME, TEST_MAC_ADDRESS);
+    public void testStaSetMacAddress() throws Exception {
+        mWifiNative.setStaMacAddress(WIFI_IFACE_NAME, TEST_MAC_ADDRESS);
         verify(mStaIfaceHal).disconnect(WIFI_IFACE_NAME);
-        verify(mWifiVendorHal).setMacAddress(WIFI_IFACE_NAME, TEST_MAC_ADDRESS);
+        verify(mWifiVendorHal).setStaMacAddress(WIFI_IFACE_NAME, TEST_MAC_ADDRESS);
+    }
+
+    /**
+     * Verifies that setMacAddress() calls underlying WifiVendorHal.
+     */
+    @Test
+    public void testApSetMacAddress() throws Exception {
+        mWifiNative.setApMacAddress(WIFI_IFACE_NAME, TEST_MAC_ADDRESS);
+        verify(mWifiVendorHal).setApMacAddress(WIFI_IFACE_NAME, TEST_MAC_ADDRESS);
+    }
+
+    /**
+     * Verifies that resetApMacToFactoryMacAddress() calls underlying WifiVendorHal.
+     */
+    @Test
+    public void testResetApMacToFactoryMacAddress() throws Exception {
+        mWifiNative.resetApMacToFactoryMacAddress(WIFI_IFACE_NAME);
+        verify(mWifiVendorHal).resetApMacToFactoryMacAddress(WIFI_IFACE_NAME);
+    }
+
+    /**
+     * Verifies that setCoexUnsafeChannels() calls underlying WifiVendorHal.
+     */
+    @Test
+    public void testSetCoexUnsafeChannels() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        mWifiNative.setCoexUnsafeChannels(Collections.emptyList(), 0);
+        verify(mWifiVendorHal).setCoexUnsafeChannels(Collections.emptyList(), 0);
     }
 
     /**
      * Verifies that isSetMacAddressSupported() calls underlying WifiVendorHal.
      */
     @Test
-    public void testIsSetMacAddressSupported() throws Exception {
-        mWifiNative.isSetMacAddressSupported(WIFI_IFACE_NAME);
-        verify(mWifiVendorHal).isSetMacAddressSupported(WIFI_IFACE_NAME);
+    public void testIsStaSetMacAddressSupported() throws Exception {
+        mWifiNative.isStaSetMacAddressSupported(WIFI_IFACE_NAME);
+        verify(mWifiVendorHal).isStaSetMacAddressSupported(WIFI_IFACE_NAME);
+    }
+
+    /**
+     * Verifies that isSetMacAddressSupported() calls underlying WifiVendorHal.
+     */
+    @Test
+    public void testIsApSetMacAddressSupported() throws Exception {
+        mWifiNative.isApSetMacAddressSupported(WIFI_IFACE_NAME);
+        verify(mWifiVendorHal).isApSetMacAddressSupported(WIFI_IFACE_NAME);
     }
 
     /**
@@ -946,10 +1066,19 @@
     }
 
     @Test
-    public void testGetFactoryMacAddress() throws Exception {
-        when(mWifiVendorHal.getFactoryMacAddress(any())).thenReturn(MacAddress.BROADCAST_ADDRESS);
-        assertNotNull(mWifiNative.getFactoryMacAddress(WIFI_IFACE_NAME));
-        verify(mWifiVendorHal).getFactoryMacAddress(any());
+    public void testStaGetFactoryMacAddress() throws Exception {
+        when(mWifiVendorHal.getStaFactoryMacAddress(any()))
+                .thenReturn(MacAddress.BROADCAST_ADDRESS);
+        assertNotNull(mWifiNative.getStaFactoryMacAddress(WIFI_IFACE_NAME));
+        verify(mWifiVendorHal).getStaFactoryMacAddress(any());
+    }
+
+
+    @Test
+    public void testGetApFactoryMacAddress() throws Exception {
+        when(mWifiVendorHal.getApFactoryMacAddress(any())).thenReturn(MacAddress.BROADCAST_ADDRESS);
+        assertNotNull(mWifiNative.getApFactoryMacAddress(WIFI_IFACE_NAME));
+        verify(mWifiVendorHal).getApFactoryMacAddress(any());
     }
 
     /**
@@ -1058,4 +1187,165 @@
 
         verify(mStaIfaceHal).flushAllHlp(eq(WIFI_IFACE_NAME));
     }
+
+    @Test
+    public void testIsItPossibleToCreateIface() {
+        when(mWifiVendorHal.isItPossibleToCreateApIface(any())).thenReturn(true);
+        assertTrue(mWifiNative.isItPossibleToCreateApIface(new WorkSource()));
+
+        when(mWifiVendorHal.isItPossibleToCreateStaIface(any())).thenReturn(true);
+        assertTrue(mWifiNative.isItPossibleToCreateStaIface(new WorkSource()));
+    }
+
+    @Test
+    public void testReplaceStaIfaceRequestorWs() {
+        assertEquals(WIFI_IFACE_NAME,
+                mWifiNative.setupInterfaceForClientInConnectivityMode(
+                        mInterfaceCallback, TEST_WORKSOURCE));
+        when(mWifiVendorHal.replaceStaIfaceRequestorWs(WIFI_IFACE_NAME, TEST_WORKSOURCE2))
+                .thenReturn(true);
+
+        assertTrue(mWifiNative.replaceStaIfaceRequestorWs(WIFI_IFACE_NAME, TEST_WORKSOURCE2));
+        verify(mWifiVendorHal).replaceStaIfaceRequestorWs(
+                eq(WIFI_IFACE_NAME), same(TEST_WORKSOURCE2));
+    }
+
+    /**
+     * Verifies that updateLinkedNetworks() calls underlying SupplicantStaIfaceHal.
+     */
+    @Test
+    public void testUpdateLinkedNetworks() {
+        when(mStaIfaceHal.updateLinkedNetworks(any(), anyInt(), any())).thenReturn(true);
+
+        assertTrue(mWifiNative.updateLinkedNetworks(WIFI_IFACE_NAME, 0, null));
+        verify(mStaIfaceHal).updateLinkedNetworks(WIFI_IFACE_NAME, 0, null);
+    }
+
+    /**
+     * Verifies that getEapAnonymousIdentity() works as expected.
+     */
+    @Test
+    public void testGetEapAnonymousIdentity() {
+        // Verify the empty use case
+        when(mStaIfaceHal.getCurrentNetworkEapAnonymousIdentity(WIFI_IFACE_NAME))
+                .thenReturn("");
+        assertTrue(TextUtils.isEmpty(mWifiNative.getEapAnonymousIdentity(WIFI_IFACE_NAME)));
+
+        // Verify with an anonymous identity
+        final String anonymousId = "anonymous@homerealm.example.org";
+        when(mStaIfaceHal.getCurrentNetworkEapAnonymousIdentity(WIFI_IFACE_NAME))
+                .thenReturn(anonymousId);
+        assertEquals(anonymousId, mWifiNative.getEapAnonymousIdentity(WIFI_IFACE_NAME));
+
+        // Verify with a pseudonym identity
+        final String pseudonymId = "a4624bc22490da3@homerealm.example.org";
+        when(mStaIfaceHal.getCurrentNetworkEapAnonymousIdentity(WIFI_IFACE_NAME))
+                .thenReturn(pseudonymId);
+        assertEquals(pseudonymId, mWifiNative.getEapAnonymousIdentity(WIFI_IFACE_NAME));
+
+        // Verify that decorated anonymous identity is truncated
+        when(mStaIfaceHal.getCurrentNetworkEapAnonymousIdentity(WIFI_IFACE_NAME))
+                .thenReturn("otherrealm.example.net!" + anonymousId);
+        assertEquals(anonymousId, mWifiNative.getEapAnonymousIdentity(WIFI_IFACE_NAME));
+
+        // Verify that recursive decorated anonymous identity is truncated
+        when(mStaIfaceHal.getCurrentNetworkEapAnonymousIdentity(WIFI_IFACE_NAME))
+                .thenReturn("proxyrealm.example.com!otherrealm.example.net!" + anonymousId);
+        assertEquals(anonymousId, mWifiNative.getEapAnonymousIdentity(WIFI_IFACE_NAME));
+
+        // Verify an invalid decoration with no identity use cases
+        when(mStaIfaceHal.getCurrentNetworkEapAnonymousIdentity(WIFI_IFACE_NAME))
+                .thenReturn("otherrealm.example.net!");
+        assertNull(mWifiNative.getEapAnonymousIdentity(WIFI_IFACE_NAME));
+    }
+
+
+    @Test
+    public void testCountryCodeChangedListener() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        final String testCountryCode = "US";
+        ArgumentCaptor<WifiNl80211Manager.CountryCodeChangedListener>
+                mCountryCodeChangedListenerCaptor = ArgumentCaptor.forClass(
+                WifiNl80211Manager.CountryCodeChangedListener.class);
+        mWifiNative.registerCountryCodeEventListener(mWifiCountryCodeChangeListener);
+        verify(mWificondControl).registerCountryCodeChangedListener(any(),
+                mCountryCodeChangedListenerCaptor.capture());
+        mCountryCodeChangedListenerCaptor.getValue().onCountryCodeChanged(testCountryCode);
+        verify(mWifiCountryCodeChangeListener).onDriverCountryCodeChanged(testCountryCode);
+    }
+
+    @Test
+    public void testSetStaCountryCodeSuccessful() {
+        when(mStaIfaceHal.setCountryCode(any(), any())).thenReturn(true);
+        final String testCountryCode = "US";
+        mWifiNative.registerCountryCodeEventListener(mWifiCountryCodeChangeListener);
+        mWifiNative.setStaCountryCode(WIFI_IFACE_NAME, testCountryCode);
+        verify(mStaIfaceHal).setCountryCode(WIFI_IFACE_NAME, testCountryCode);
+        if (SdkLevel.isAtLeastS()) {
+            verify(mWifiCountryCodeChangeListener).onSetCountryCodeSucceeded(testCountryCode);
+        }
+    }
+
+    @Test
+    public void testSetStaCountryCodeFailure() {
+        when(mStaIfaceHal.setCountryCode(any(), any())).thenReturn(false);
+        final String testCountryCode = "US";
+        mWifiNative.registerCountryCodeEventListener(mWifiCountryCodeChangeListener);
+        mWifiNative.setStaCountryCode(WIFI_IFACE_NAME, testCountryCode);
+        verify(mStaIfaceHal).setCountryCode(WIFI_IFACE_NAME, testCountryCode);
+        if (SdkLevel.isAtLeastS()) {
+            verify(mWifiCountryCodeChangeListener, never())
+                    .onSetCountryCodeSucceeded(testCountryCode);
+        }
+    }
+
+    @Test
+    public void testSetApCountryCodeSuccessful() {
+        when(mWifiVendorHal.setApCountryCode(any(), any())).thenReturn(true);
+        final String testCountryCode = "US";
+        mWifiNative.registerCountryCodeEventListener(mWifiCountryCodeChangeListener);
+        mWifiNative.setApCountryCode(WIFI_IFACE_NAME, testCountryCode);
+        verify(mWifiVendorHal).setApCountryCode(WIFI_IFACE_NAME, testCountryCode);
+        if (SdkLevel.isAtLeastS()) {
+            verify(mWifiCountryCodeChangeListener).onSetCountryCodeSucceeded(testCountryCode);
+        }
+    }
+
+    @Test
+    public void testSetApCountryCodeFailure() {
+        when(mWifiVendorHal.setApCountryCode(any(), any())).thenReturn(false);
+        final String testCountryCode = "US";
+        mWifiNative.registerCountryCodeEventListener(mWifiCountryCodeChangeListener);
+        mWifiNative.setApCountryCode(WIFI_IFACE_NAME, testCountryCode);
+        verify(mWifiVendorHal).setApCountryCode(WIFI_IFACE_NAME, testCountryCode);
+        if (SdkLevel.isAtLeastS()) {
+            verify(mWifiCountryCodeChangeListener, never())
+                    .onSetCountryCodeSucceeded(testCountryCode);
+        }
+    }
+
+    @Test
+    public void testSetChipCountryCodeSuccessful() {
+        when(mWifiVendorHal.setChipCountryCode(any())).thenReturn(true);
+        final String testCountryCode = "US";
+        mWifiNative.registerCountryCodeEventListener(mWifiCountryCodeChangeListener);
+        mWifiNative.setChipCountryCode(testCountryCode);
+        verify(mWifiVendorHal).setChipCountryCode(testCountryCode);
+        if (SdkLevel.isAtLeastS()) {
+            verify(mWifiCountryCodeChangeListener).onSetCountryCodeSucceeded(testCountryCode);
+        }
+    }
+
+    @Test
+    public void testSetChipCountryCodeFailure() {
+        when(mWifiVendorHal.setChipCountryCode(any())).thenReturn(false);
+        final String testCountryCode = "US";
+        mWifiNative.registerCountryCodeEventListener(mWifiCountryCodeChangeListener);
+        mWifiNative.setChipCountryCode(testCountryCode);
+        verify(mWifiVendorHal).setChipCountryCode(testCountryCode);
+        if (SdkLevel.isAtLeastS()) {
+            verify(mWifiCountryCodeChangeListener, never())
+                .onSetCountryCodeSucceeded(testCountryCode);
+        }
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java
index 516dd32..64a1d3d 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java
@@ -19,29 +19,37 @@
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
+import static android.content.Intent.ACTION_SCREEN_OFF;
+import static android.content.Intent.ACTION_SCREEN_ON;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.server.wifi.WifiNetworkFactory.PERIODIC_SCAN_INTERVAL_MS;
 import static com.android.server.wifi.util.NativeUtil.addEnclosingQuotes;
 
 import static org.junit.Assert.*;
 import static org.mockito.Mockito.*;
 
+import static java.lang.Math.toIntExact;
+
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.AlarmManager.OnAlarmListener;
 import android.app.AppOpsManager;
+import android.app.test.MockAnswerUtil;
 import android.companion.CompanionDeviceManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.res.Resources;
 import android.net.ConnectivityManager;
 import android.net.MacAddress;
 import android.net.NetworkCapabilities;
 import android.net.NetworkProvider;
 import android.net.NetworkRequest;
-import android.net.wifi.IActionListener;
+import android.net.NetworkSpecifier;
 import android.net.wifi.INetworkRequestMatchCallback;
 import android.net.wifi.INetworkRequestUserSelectionCallback;
 import android.net.wifi.ScanResult;
@@ -52,9 +60,9 @@
 import android.net.wifi.WifiScanner.ScanListener;
 import android.net.wifi.WifiScanner.ScanSettings;
 import android.net.wifi.WifiSsid;
-import android.os.Binder;
 import android.os.IBinder;
 import android.os.PatternMatcher;
+import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -65,26 +73,32 @@
 import android.util.Xml;
 
 import com.android.internal.util.FastXmlSerializer;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.WifiNetworkFactory.AccessPoint;
 import com.android.server.wifi.proto.nano.WifiMetricsProto;
+import com.android.server.wifi.util.ActionListenerWrapper;
 import com.android.server.wifi.util.ScanResultUtil;
 import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
 import com.android.server.wifi.util.WifiPermissionsUtil;
+import com.android.wifi.resources.R;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatcher;
+import org.mockito.Captor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlSerializer;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -92,6 +106,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Unit tests for {@link com.android.server.wifi.WifiNetworkFactory}.
@@ -101,7 +116,6 @@
     private static final int TEST_NETWORK_ID_1 = 104;
     private static final int TEST_UID_1 = 10423;
     private static final int TEST_UID_2 = 10424;
-    private static final int TEST_CALLBACK_IDENTIFIER = 123;
     private static final String TEST_PACKAGE_NAME_1 = "com.test.networkrequest.1";
     private static final String TEST_PACKAGE_NAME_2 = "com.test.networkrequest.2";
     private static final String TEST_APP_NAME = "app";
@@ -118,6 +132,7 @@
     private static final String TEST_WPA_PRESHARED_KEY = "\"password123\"";
 
     @Mock Context mContext;
+    @Mock Resources mResources;
     @Mock ActivityManager mActivityManager;
     @Mock AlarmManager mAlarmManager;
     @Mock AppOpsManager mAppOpsManager;
@@ -132,10 +147,17 @@
     @Mock PackageManager mPackageManager;
     @Mock IBinder mAppBinder;
     @Mock INetworkRequestMatchCallback mNetworkRequestMatchCallback;
-    @Mock ClientModeImpl mClientModeImpl;
+    @Mock ConcreteClientModeManager mClientModeManager;
     @Mock ConnectivityManager mConnectivityManager;
     @Mock WifiMetrics mWifiMetrics;
     @Mock NetworkProvider mNetworkProvider;
+    @Mock ActiveModeWarden mActiveModeWarden;
+    @Mock ConnectHelper mConnectHelper;
+    @Mock PowerManager mPowerManager;
+    @Mock ClientModeImplMonitor mCmiMonitor;
+    private @Mock ClientModeManager mPrimaryClientModeManager;
+    private @Mock WifiGlobals mWifiGlobals;
+    private MockitoSession mStaticMockSession = null;
     NetworkCapabilities mNetworkCapabilities;
     TestLooper mLooper;
     NetworkRequest mNetworkRequest;
@@ -153,8 +175,12 @@
             ArgumentCaptor.forClass(OnAlarmListener.class);
     ArgumentCaptor<ScanListener> mScanListenerArgumentCaptor =
             ArgumentCaptor.forClass(ScanListener.class);
-    ArgumentCaptor<IActionListener> mConnectListenerArgumentCaptor =
-            ArgumentCaptor.forClass(IActionListener.class);
+    ArgumentCaptor<ActionListenerWrapper> mConnectListenerArgumentCaptor =
+            ArgumentCaptor.forClass(ActionListenerWrapper.class);
+    ArgumentCaptor<ActiveModeWarden.ModeChangeCallback> mModeChangeCallbackCaptor =
+            ArgumentCaptor.forClass(ActiveModeWarden.ModeChangeCallback.class);
+    @Captor ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor;
+    @Captor ArgumentCaptor<ClientModeImplListener> mCmiListenerCaptor;
     InOrder mInOrder;
 
     private WifiNetworkFactory mWifiNetworkFactory;
@@ -167,16 +193,23 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        mStaticMockSession = mockitoSession()
+                .mockStatic(WifiInjector.class)
+                .startMocking();
+        lenient().when(WifiInjector.getInstance()).thenReturn(mWifiInjector);
 
         mLooper = new TestLooper();
         mNetworkCapabilities = new NetworkCapabilities();
         mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
-        // NOT_VCN_MANAGED is not part of default network capabilities and needs to be manually
-        // added for non-VCN-underlying network factory/agent implementations.
-        mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
+        if (SdkLevel.isAtLeastS()) {
+            // NOT_VCN_MANAGED is not part of default network capabilities and needs to be manually
+            // added for non-VCN-underlying network factory/agent implementations.
+            mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
+        }
         mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
         mTestScanDatas = ScanTestUtil.createScanDatas(new int[][]{ { 2417, 2427, 5180, 5170 } });
 
+        when(mContext.getResources()).thenReturn(mResources);
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         when(mContext.getSystemService(eq(Context.CONNECTIVITY_SERVICE)))
                 .thenReturn(mConnectivityManager);
@@ -184,6 +217,10 @@
                 .thenReturn(mConnectivityManager);
         when(mContext.getSystemService(CompanionDeviceManager.class))
                 .thenReturn(mCompanionDeviceManager);
+        when(mContext.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
+        when(mResources.getBoolean(
+                eq(R.bool.config_wifiUseHalApiToDisableFwRoaming)))
+                .thenReturn(true);
         when(mPackageManager.getNameForUid(TEST_UID_1)).thenReturn(TEST_PACKAGE_NAME_1);
         when(mPackageManager.getNameForUid(TEST_UID_2)).thenReturn(TEST_PACKAGE_NAME_2);
         when(mPackageManager.getApplicationInfoAsUser(any(), anyInt(), any()))
@@ -194,15 +231,37 @@
         when(mActivityManager.getPackageImportance(TEST_PACKAGE_NAME_2))
                 .thenReturn(IMPORTANCE_FOREGROUND_SERVICE);
         when(mWifiInjector.getWifiScanner()).thenReturn(mWifiScanner);
-        when(mWifiInjector.getClientModeImpl()).thenReturn(mClientModeImpl);
+        when(mWifiInjector.getActiveModeWarden()).thenReturn(mActiveModeWarden);
+        when(mWifiInjector.getWifiGlobals()).thenReturn(mWifiGlobals);
+        when(mWifiGlobals.isWpa3SaeUpgradeEnabled()).thenReturn(true);
+        when(mWifiGlobals.isOweUpgradeEnabled()).thenReturn(true);
         when(mWifiConfigManager.addOrUpdateNetwork(any(), anyInt(), anyString()))
                 .thenReturn(new NetworkUpdateResult(TEST_NETWORK_ID_1));
         when(mWifiScanner.getSingleScanResults()).thenReturn(Collections.emptyList());
+        when(mNetworkRequestMatchCallback.asBinder()).thenReturn(mAppBinder);
+
+        when(mActiveModeWarden.hasPrimaryClientModeManager()).thenReturn(true);
+        when(mActiveModeWarden.getPrimaryClientModeManager()).thenReturn(mPrimaryClientModeManager);
+        doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+            public void answer(
+                    ActiveModeWarden.ExternalClientModeManagerRequestListener requestListener,
+                    WorkSource ws, String ssid, String bssid) {
+                requestListener.onAnswer(mClientModeManager);
+            }
+        }).when(mActiveModeWarden).requestLocalOnlyClientModeManager(any(), any(), any(), any());
+        when(mClientModeManager.getRole()).thenReturn(ActiveModeManager.ROLE_CLIENT_PRIMARY);
+
+        when(mPrimaryClientModeManager.getSupportedFeatures()).thenReturn(
+                WifiManager.WIFI_FEATURE_WPA3_SAE | WifiManager.WIFI_FEATURE_OWE);
 
         mWifiNetworkFactory = new WifiNetworkFactory(mLooper.getLooper(), mContext,
                 mNetworkCapabilities, mActivityManager, mAlarmManager, mAppOpsManager,
                 mClock, mWifiInjector, mWifiConnectivityManager,
-                mWifiConfigManager, mWifiConfigStore, mWifiPermissionsUtil, mWifiMetrics);
+                mWifiConfigManager, mWifiConfigStore, mWifiPermissionsUtil, mWifiMetrics,
+                mActiveModeWarden, mConnectHelper, mCmiMonitor);
+
+        verify(mContext, atLeastOnce()).registerReceiver(
+                mBroadcastReceiverCaptor.capture(), any(), any(), any());
 
         ArgumentCaptor<NetworkRequestStoreData.DataSource> dataSourceArgumentCaptor =
                 ArgumentCaptor.forClass(NetworkRequestStoreData.DataSource.class);
@@ -211,6 +270,10 @@
         assertNotNull(mDataSource);
         mNetworkRequestStoreData = new NetworkRequestStoreData(mDataSource);
 
+        verify(mActiveModeWarden).registerModeChangeCallback(
+                mModeChangeCallbackCaptor.capture());
+        assertNotNull(mModeChangeCallbackCaptor.getValue());
+
         // Register factory with connectivity manager.
         mWifiNetworkFactory.register();
         ArgumentCaptor<NetworkProvider> networkProviderArgumentCaptor =
@@ -226,8 +289,7 @@
                 .build();
 
         // Setup with wifi on.
-        mWifiNetworkFactory.setWifiState(true);
-        mWifiNetworkFactory.enableVerboseLogging(1);
+        mWifiNetworkFactory.enableVerboseLogging(true);
     }
 
     /**
@@ -236,6 +298,9 @@
     @After
     public void cleanup() {
         validateMockitoUsage();
+        if (null != mStaticMockSession) {
+            mStaticMockSession.finishMocking();
+        }
     }
 
     /**
@@ -282,10 +347,47 @@
     }
 
     /**
+     * Validates handling of acceptNetwork with an unsupported network specifier
+     */
+    @Test
+    public void testHandleAcceptNetworkRequestFromWithUnsupportedSpecifier() throws Exception {
+        // Attach an unsupported specifier.
+        mNetworkCapabilities.setNetworkSpecifier(mock(NetworkSpecifier.class));
+        mNetworkRequest = new NetworkRequest.Builder()
+                .setCapabilities(mNetworkCapabilities)
+                .build();
+
+        // request should be rejected, but not released.
+        assertFalse(mWifiNetworkFactory.acceptRequest(mNetworkRequest));
+        mLooper.dispatchAll();
+        verify(mConnectivityManager, never()).declareNetworkRequestUnfulfillable(any());
+    }
+
+    /**
+     * Validates that requests that specify a frequency band are rejected.
+     */
+    @Test
+    public void testHandleAcceptNetworkRequestWithBand() throws Exception {
+        WifiNetworkSpecifier specifier = new WifiNetworkSpecifier.Builder()
+                .setBand(ScanResult.WIFI_BAND_5_GHZ)
+                .build();
+        mNetworkCapabilities.setNetworkSpecifier(specifier);
+        mNetworkRequest = new NetworkRequest.Builder()
+                .setCapabilities(mNetworkCapabilities)
+                .build();
+
+        // request should be rejected and released.
+        assertFalse(mWifiNetworkFactory.acceptRequest(mNetworkRequest));
+        mLooper.dispatchAll();
+        verify(mConnectivityManager).declareNetworkRequestUnfulfillable(any());
+    }
+
+    /**
      * Validates handling of acceptNetwork with a network specifier with invalid uid/package name.
      */
     @Test
-    public void testHandleAcceptNetworkRequestFromWithInvalidSpecifier() throws Exception {
+    public void testHandleAcceptNetworkRequestFromWithInvalidWifiNetworkSpecifier()
+            throws Exception {
         when(mActivityManager.getPackageImportance(TEST_PACKAGE_NAME_1))
                 .thenReturn(IMPORTANCE_GONE);
         when(mWifiPermissionsUtil.checkNetworkSettingsPermission(TEST_UID_1))
@@ -451,6 +553,31 @@
 
     /**
      * Validates handling of acceptNetwork with a network specifier from a foreground
+     * service when we're in the midst of processing the same request from a foreground app.
+     * Caused by the app transitioning to a fg service & connectivity stack triggering a
+     * re-evaluation.
+     */
+    @Test
+    public void
+            testHandleAcceptNetworkRequestFromFgSvcWithSpecifierWithSamePendingRequestFromFgApp()
+            throws Exception {
+        when(mActivityManager.getPackageImportance(TEST_PACKAGE_NAME_1))
+                .thenReturn(IMPORTANCE_FOREGROUND);
+        when(mActivityManager.getPackageImportance(TEST_PACKAGE_NAME_2))
+                .thenReturn(IMPORTANCE_FOREGROUND_SERVICE);
+
+        // Handle request 1.
+        attachDefaultWifiNetworkSpecifierAndAppInfo(TEST_UID_1, false);
+        mWifiNetworkFactory.needNetworkFor(mNetworkRequest);
+
+        // Resend the request from a fg service (should be accepted since it is already being
+        // processed).
+        assertTrue(mWifiNetworkFactory.acceptRequest(mNetworkRequest));
+        mLooper.dispatchAll();
+    }
+
+    /**
+     * Validates handling of acceptNetwork with a network specifier from a foreground
      * app when we're connected to a request from a foreground app.
      */
     @Test
@@ -467,7 +594,7 @@
         // Send network connection success indication.
         assertNotNull(mSelectedNetwork);
         mWifiNetworkFactory.handleConnectionAttemptEnded(
-                WifiMetrics.ConnectionEvent.FAILURE_NONE, mSelectedNetwork);
+                WifiMetrics.ConnectionEvent.FAILURE_NONE, mSelectedNetwork, TEST_BSSID_1);
 
         // Make request 2 which will be accepted because a fg app request can
         // override an existing fg app request.
@@ -493,7 +620,7 @@
         // Send network connection success indication.
         assertNotNull(mSelectedNetwork);
         mWifiNetworkFactory.handleConnectionAttemptEnded(
-                WifiMetrics.ConnectionEvent.FAILURE_NONE, mSelectedNetwork);
+                WifiMetrics.ConnectionEvent.FAILURE_NONE, mSelectedNetwork, TEST_BSSID_1);
 
         // Make request 2 which will be rejected because a fg service request cannot
         // override a fg app request.
@@ -504,6 +631,32 @@
     }
 
     /**
+     * Validates handling of acceptNetwork with a network specifier from a foreground
+     * service when we're in the connected to the same request from a foreground app.
+     * Caused by the app transitioning to a fg service & connectivity stack triggering a
+     * re-evaluation.
+     */
+    @Test
+    public void
+            testHandleAcceptNetworkRequestFromFgSvcWithSpecifierWithSameConnectedRequestFromFgApp()
+            throws Exception {
+        when(mActivityManager.getPackageImportance(TEST_PACKAGE_NAME_1))
+                .thenReturn(IMPORTANCE_FOREGROUND);
+        when(mActivityManager.getPackageImportance(TEST_PACKAGE_NAME_2))
+                .thenReturn(IMPORTANCE_FOREGROUND_SERVICE);
+
+        // Connect to request 1
+        sendNetworkRequestAndSetupForConnectionStatus(TEST_SSID_1);
+        // Send network connection success indication.
+        assertNotNull(mSelectedNetwork);
+        mWifiNetworkFactory.handleConnectionAttemptEnded(
+                WifiMetrics.ConnectionEvent.FAILURE_NONE, mSelectedNetwork, TEST_BSSID_1);
+
+        // Resend the request from a fg service (should be accepted since it is already connected).
+        assertTrue(mWifiNetworkFactory.acceptRequest(mNetworkRequest));
+    }
+
+    /**
      * Verify handling of new network request with network specifier.
      */
     @Test
@@ -523,6 +676,25 @@
     }
 
     /**
+     * Validates handling of new network request with unsupported network specifier.
+     */
+    @Test
+    public void testHandleNetworkRequestWithUnsupportedSpecifier() throws Exception {
+        // Attach an unsupported specifier.
+        mNetworkCapabilities.setNetworkSpecifier(mock(NetworkSpecifier.class));
+        mNetworkRequest = new NetworkRequest.Builder()
+                .setCapabilities(mNetworkCapabilities)
+                .build();
+
+        // Ignore the request, but don't release it.
+        mWifiNetworkFactory.needNetworkFor(mNetworkRequest);
+        mLooper.dispatchAll();
+        verify(mConnectivityManager, never()).declareNetworkRequestUnfulfillable(any());
+
+        verifyNoMoreInteractions(mWifiScanner, mWifiConnectivityManager, mWifiMetrics);
+    }
+
+    /**
      * Validates handling of new network request with network specifier with internet capability.
      */
     @Test
@@ -606,9 +778,7 @@
         // Release the network request.
         mWifiNetworkFactory.releaseNetworkFor(mNetworkRequest);
         // Verify that we did not trigger a disconnect because we've not yet connected.
-        verify(mClientModeImpl, never()).disconnectCommand();
-        // Re-enable connectivity manager .
-        verify(mWifiConnectivityManager).setSpecificNetworkRequestInProgress(false);
+        verify(mClientModeManager, never()).disconnect();
 
         verify(mWifiMetrics).incrementNetworkRequestApiNumRequest();
     }
@@ -656,8 +826,7 @@
         attachDefaultWifiNetworkSpecifierAndAppInfo(TEST_UID_1, false);
         mWifiNetworkFactory.needNetworkFor(mNetworkRequest);
 
-        mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
-                TEST_CALLBACK_IDENTIFIER);
+        mWifiNetworkFactory.addCallback(mNetworkRequestMatchCallback);
         verify(mNetworkRequestMatchCallback).onUserSelectionCallbackRegistration(
                 mNetworkRequestUserSelectionCallback.capture());
 
@@ -684,14 +853,14 @@
         attachDefaultWifiNetworkSpecifierAndAppInfo(TEST_UID_1, false);
         mWifiNetworkFactory.needNetworkFor(mNetworkRequest);
 
-        mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
-                TEST_CALLBACK_IDENTIFIER);
+        mWifiNetworkFactory.addCallback(mNetworkRequestMatchCallback);
 
         //Ensure that we register the user selection callback using the newly registered callback.
         verify(mNetworkRequestMatchCallback).onUserSelectionCallbackRegistration(
                 any(INetworkRequestUserSelectionCallback.class));
 
-        mWifiNetworkFactory.removeCallback(TEST_CALLBACK_IDENTIFIER);
+        mWifiNetworkFactory.removeCallback(mNetworkRequestMatchCallback);
+        verify(mNetworkRequestMatchCallback, atLeastOnce()).asBinder();
 
         verifyNoMoreInteractions(mNetworkRequestMatchCallback);
     }
@@ -705,13 +874,12 @@
         mWifiNetworkFactory.needNetworkFor(mNetworkRequest);
         mWifiNetworkFactory.releaseNetworkFor(mNetworkRequest);
 
-        mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
-                TEST_CALLBACK_IDENTIFIER);
+        mWifiNetworkFactory.addCallback(mNetworkRequestMatchCallback);
 
         //Ensure that we trigger the onAbort callback & nothing else.
         verify(mNetworkRequestMatchCallback).onAbort();
 
-        mWifiNetworkFactory.removeCallback(TEST_CALLBACK_IDENTIFIER);
+        mWifiNetworkFactory.removeCallback(mNetworkRequestMatchCallback);
 
         verifyNoMoreInteractions(mNetworkRequestMatchCallback);
     }
@@ -732,7 +900,7 @@
         Pair<MacAddress, MacAddress> bssidPatternMatch =
                 Pair.create(WifiManager.ALL_ZEROS_MAC_ADDRESS, WifiManager.ALL_ZEROS_MAC_ADDRESS);
         WifiConfiguration wifiConfiguration = new WifiConfiguration();
-        wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+        wifiConfiguration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
         wifiConfiguration.preSharedKey = TEST_WPA_PRESHARED_KEY;
         attachWifiNetworkSpecifierAndAppInfo(
                 ssidPatternMatch, bssidPatternMatch, wifiConfiguration, TEST_UID_1,
@@ -741,8 +909,7 @@
 
         validateUiStartParams(true);
 
-        mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
-                TEST_CALLBACK_IDENTIFIER);
+        mWifiNetworkFactory.addCallback(mNetworkRequestMatchCallback);
 
         verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS);
 
@@ -774,7 +941,7 @@
         Pair<MacAddress, MacAddress> bssidPatternMatch =
                 Pair.create(WifiManager.ALL_ZEROS_MAC_ADDRESS, WifiManager.ALL_ZEROS_MAC_ADDRESS);
         WifiConfiguration wifiConfiguration = new WifiConfiguration();
-        wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+        wifiConfiguration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
         wifiConfiguration.preSharedKey = TEST_WPA_PRESHARED_KEY;
         attachWifiNetworkSpecifierAndAppInfo(
                 ssidPatternMatch, bssidPatternMatch, wifiConfiguration, TEST_UID_1,
@@ -783,8 +950,7 @@
 
         validateUiStartParams(true);
 
-        mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
-                TEST_CALLBACK_IDENTIFIER);
+        mWifiNetworkFactory.addCallback(mNetworkRequestMatchCallback);
 
         verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS);
 
@@ -816,7 +982,7 @@
         Pair<MacAddress, MacAddress> bssidPatternMatch =
                 Pair.create(WifiManager.ALL_ZEROS_MAC_ADDRESS, WifiManager.ALL_ZEROS_MAC_ADDRESS);
         WifiConfiguration wifiConfiguration = new WifiConfiguration();
-        wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+        wifiConfiguration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN);
         wifiConfiguration.preSharedKey = TEST_WPA_PRESHARED_KEY;
         attachWifiNetworkSpecifierAndAppInfo(
                 ssidPatternMatch, bssidPatternMatch, wifiConfiguration, TEST_UID_1,
@@ -825,8 +991,7 @@
 
         validateUiStartParams(false);
 
-        mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
-                TEST_CALLBACK_IDENTIFIER);
+        mWifiNetworkFactory.addCallback(mNetworkRequestMatchCallback);
 
         verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS);
 
@@ -859,7 +1024,7 @@
         Pair<MacAddress, MacAddress> bssidPatternMatch =
                 Pair.create(MacAddress.fromString(TEST_BSSID_1), MacAddress.BROADCAST_ADDRESS);
         WifiConfiguration wifiConfiguration = new WifiConfiguration();
-        wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+        wifiConfiguration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
         wifiConfiguration.preSharedKey = TEST_WPA_PRESHARED_KEY;
         attachWifiNetworkSpecifierAndAppInfo(
                 ssidPatternMatch, bssidPatternMatch, wifiConfiguration, TEST_UID_1,
@@ -868,8 +1033,7 @@
 
         validateUiStartParams(true);
 
-        mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
-                TEST_CALLBACK_IDENTIFIER);
+        mWifiNetworkFactory.addCallback(mNetworkRequestMatchCallback);
 
         verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS);
 
@@ -902,7 +1066,7 @@
                 Pair.create(MacAddress.fromString(TEST_BSSID_1_2_OUI),
                         MacAddress.fromString(TEST_BSSID_OUI_MASK));
         WifiConfiguration wifiConfiguration = new WifiConfiguration();
-        wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
+        wifiConfiguration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
         wifiConfiguration.preSharedKey = TEST_WPA_PRESHARED_KEY;
         attachWifiNetworkSpecifierAndAppInfo(
                 ssidPatternMatch, bssidPatternMatch, wifiConfiguration, TEST_UID_1,
@@ -911,8 +1075,7 @@
 
         validateUiStartParams(false);
 
-        mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
-                TEST_CALLBACK_IDENTIFIER);
+        mWifiNetworkFactory.addCallback(mNetworkRequestMatchCallback);
 
         verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS);
 
@@ -946,7 +1109,7 @@
         Pair<MacAddress, MacAddress> bssidPatternMatch =
                 Pair.create(WifiManager.ALL_ZEROS_MAC_ADDRESS, WifiManager.ALL_ZEROS_MAC_ADDRESS);
         WifiConfiguration wifiConfiguration = new WifiConfiguration();
-        wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+        wifiConfiguration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
         wifiConfiguration.preSharedKey = TEST_WPA_PRESHARED_KEY;
         attachWifiNetworkSpecifierAndAppInfo(
                 ssidPatternMatch, bssidPatternMatch, wifiConfiguration, TEST_UID_1,
@@ -955,8 +1118,7 @@
 
         validateUiStartParams(true);
 
-        mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
-                TEST_CALLBACK_IDENTIFIER);
+        mWifiNetworkFactory.addCallback(mNetworkRequestMatchCallback);
 
         verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS);
 
@@ -992,7 +1154,7 @@
         Pair<MacAddress, MacAddress> bssidPatternMatch =
                 Pair.create(WifiManager.ALL_ZEROS_MAC_ADDRESS, WifiManager.ALL_ZEROS_MAC_ADDRESS);
         WifiConfiguration wifiConfiguration = new WifiConfiguration();
-        wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+        wifiConfiguration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
         wifiConfiguration.preSharedKey = TEST_WPA_PRESHARED_KEY;
         attachWifiNetworkSpecifierAndAppInfo(
                 ssidPatternMatch, bssidPatternMatch, wifiConfiguration, TEST_UID_1,
@@ -1001,8 +1163,7 @@
 
         validateUiStartParams(true);
 
-        mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
-                TEST_CALLBACK_IDENTIFIER);
+        mWifiNetworkFactory.addCallback(mNetworkRequestMatchCallback);
 
         verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS);
 
@@ -1031,7 +1192,7 @@
         Pair<MacAddress, MacAddress> bssidPatternMatch =
                 Pair.create(WifiManager.ALL_ZEROS_MAC_ADDRESS, WifiManager.ALL_ZEROS_MAC_ADDRESS);
         WifiConfiguration wifiConfiguration = new WifiConfiguration();
-        wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+        wifiConfiguration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
         wifiConfiguration.preSharedKey = TEST_WPA_PRESHARED_KEY;
         attachWifiNetworkSpecifierAndAppInfo(
                 ssidPatternMatch, bssidPatternMatch, wifiConfiguration, TEST_UID_1,
@@ -1040,8 +1201,7 @@
 
         validateUiStartParams(true);
 
-        mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
-                TEST_CALLBACK_IDENTIFIER);
+        mWifiNetworkFactory.addCallback(mNetworkRequestMatchCallback);
 
         verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS);
 
@@ -1063,8 +1223,6 @@
 
         // Now release the active network request.
         mWifiNetworkFactory.releaseNetworkFor(mNetworkRequest);
-        // Re-enable connectivity manager (if it was disabled).
-        verify(mWifiConnectivityManager).setSpecificNetworkRequestInProgress(false);
 
         // Now trigger user selection to some network.
         WifiConfiguration selectedNetwork = WifiConfigurationTestUtil.createOpenNetwork();
@@ -1072,7 +1230,7 @@
         mLooper.dispatchAll();
 
         // Verify we did not attempt to trigger a connection or disable connectivity manager.
-        verifyNoMoreInteractions(mClientModeImpl, mWifiConnectivityManager);
+        verifyNoMoreInteractions(mClientModeManager, mWifiConnectivityManager);
     }
 
     /**
@@ -1088,7 +1246,8 @@
         assertNotNull(networkRequestUserSelectionCallback);
 
         // Now send another network request.
-        mWifiNetworkFactory.needNetworkFor(new NetworkRequest(mNetworkRequest));
+        attachDefaultWifiNetworkSpecifierAndAppInfo(TEST_UID_2, false);
+        mWifiNetworkFactory.needNetworkFor(mNetworkRequest);
 
         // Now trigger user selection to some network.
         WifiConfiguration selectedNetwork = WifiConfigurationTestUtil.createOpenNetwork();
@@ -1096,7 +1255,7 @@
         mLooper.dispatchAll();
 
         // Verify we did not attempt to trigger a connection or disable connectivity manager.
-        verifyNoMoreInteractions(mClientModeImpl, mWifiConnectivityManager, mWifiConfigManager);
+        verifyNoMoreInteractions(mClientModeManager, mWifiConnectivityManager, mWifiConfigManager);
     }
 
     /**
@@ -1126,7 +1285,10 @@
         verify(mWifiMetrics).setNominatorForNetwork(anyInt(),
                 eq(WifiMetricsProto.ConnectionEvent.NOMINATOR_SPECIFIER));
 
-
+        verify(mClientModeManager).disconnect();
+        verify(mConnectHelper).connectToNetwork(eq(mClientModeManager),
+                eq(new NetworkUpdateResult(TEST_NETWORK_ID_1)),
+                mConnectListenerArgumentCaptor.capture(), anyInt());
     }
 
     /**
@@ -1136,7 +1298,7 @@
     @Test
     public void testNetworkSpecifierHandleUserSelectionConnectToNetworkExceedApprovedListCapacity()
             throws Exception {
-        int userApproveAccessPointCapacity = mWifiNetworkFactory.NUM_OF_ACCESS_POINT_LIMIT_PER_APP;
+        int userApproveAccessPointCapacity = WifiNetworkFactory.NUM_OF_ACCESS_POINT_LIMIT_PER_APP;
         int numOfApPerSsid = userApproveAccessPointCapacity / 2 + 1;
         String[] testIds = new String[]{TEST_SSID_1, TEST_SSID_2};
 
@@ -1156,8 +1318,7 @@
         // request network, trigger scan and get matched set.
         mWifiNetworkFactory.needNetworkFor(mNetworkRequest);
 
-        mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
-                TEST_CALLBACK_IDENTIFIER);
+        mWifiNetworkFactory.addCallback(mNetworkRequestMatchCallback);
         verify(mNetworkRequestMatchCallback).onUserSelectionCallbackRegistration(
                 mNetworkRequestUserSelectionCallback.capture());
 
@@ -1213,7 +1374,7 @@
         // Have a saved network with the same configuration.
         WifiConfiguration matchingSavedNetwork = new WifiConfiguration(mSelectedNetwork);
         matchingSavedNetwork.networkId = TEST_NETWORK_ID_1;
-        when(mWifiConfigManager.getConfiguredNetwork(mSelectedNetwork.getKey()))
+        when(mWifiConfigManager.getConfiguredNetwork(mSelectedNetwork.getProfileKey()))
                 .thenReturn(matchingSavedNetwork);
 
         // Now trigger user selection to one of the network.
@@ -1228,9 +1389,10 @@
         // verify we don't try to add the network to WifiConfigManager.
         verify(mWifiConfigManager, never()).addOrUpdateNetwork(any(), anyInt(), anyString());
 
-        verify(mClientModeImpl).disconnectCommand();
-        verify(mClientModeImpl).connect(eq(null), eq(TEST_NETWORK_ID_1), any(Binder.class),
-                mConnectListenerArgumentCaptor.capture(), anyInt(), anyInt());
+        verify(mClientModeManager).disconnect();
+        verify(mConnectHelper).connectToNetwork(eq(mClientModeManager),
+                eq(new NetworkUpdateResult(TEST_NETWORK_ID_1)),
+                mConnectListenerArgumentCaptor.capture(), anyInt());
     }
 
     /**
@@ -1252,14 +1414,13 @@
                 Pair.create(MacAddress.fromString(matchingScanResult.BSSID),
                         MacAddress.BROADCAST_ADDRESS);
         WifiConfiguration wifiConfiguration = new WifiConfiguration();
-        wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+        wifiConfiguration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
         wifiConfiguration.preSharedKey = TEST_WPA_PRESHARED_KEY;
         attachWifiNetworkSpecifierAndAppInfo(
                 ssidPatternMatch, bssidPatternMatch, wifiConfiguration, TEST_UID_1,
                 TEST_PACKAGE_NAME_1);
         mWifiNetworkFactory.needNetworkFor(mNetworkRequest);
-        mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
-                TEST_CALLBACK_IDENTIFIER);
+        mWifiNetworkFactory.addCallback(mNetworkRequestMatchCallback);
         verify(mNetworkRequestMatchCallback).onUserSelectionCallbackRegistration(
                 mNetworkRequestUserSelectionCallback.capture());
         verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS);
@@ -1278,9 +1439,10 @@
         verify(mWifiMetrics).setNominatorForNetwork(anyInt(),
                 eq(WifiMetricsProto.ConnectionEvent.NOMINATOR_SPECIFIER));
 
-        verify(mClientModeImpl).disconnectCommand();
-        verify(mClientModeImpl).connect(eq(null), eq(TEST_NETWORK_ID_1), any(Binder.class),
-                mConnectListenerArgumentCaptor.capture(), anyInt(), anyInt());
+        verify(mClientModeManager).disconnect();
+        verify(mConnectHelper).connectToNetwork(eq(mClientModeManager),
+                eq(new NetworkUpdateResult(TEST_NETWORK_ID_1)),
+                mConnectListenerArgumentCaptor.capture(), anyInt());
     }
 
     /**
@@ -1300,14 +1462,13 @@
         Pair<MacAddress, MacAddress> bssidPatternMatch =
                 Pair.create(WifiManager.ALL_ZEROS_MAC_ADDRESS, WifiManager.ALL_ZEROS_MAC_ADDRESS);
         WifiConfiguration wifiConfiguration = new WifiConfiguration();
-        wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+        wifiConfiguration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
         wifiConfiguration.preSharedKey = TEST_WPA_PRESHARED_KEY;
         attachWifiNetworkSpecifierAndAppInfo(
                 ssidPatternMatch, bssidPatternMatch, wifiConfiguration, TEST_UID_1,
                 TEST_PACKAGE_NAME_1);
         mWifiNetworkFactory.needNetworkFor(mNetworkRequest);
-        mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
-                TEST_CALLBACK_IDENTIFIER);
+        mWifiNetworkFactory.addCallback(mNetworkRequestMatchCallback);
         verify(mNetworkRequestMatchCallback).onUserSelectionCallbackRegistration(
                 mNetworkRequestUserSelectionCallback.capture());
         verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS);
@@ -1329,9 +1490,10 @@
         verify(mWifiMetrics).setNominatorForNetwork(anyInt(),
                 eq(WifiMetricsProto.ConnectionEvent.NOMINATOR_SPECIFIER));
 
-        verify(mClientModeImpl).disconnectCommand();
-        verify(mClientModeImpl).connect(eq(null), eq(TEST_NETWORK_ID_1), any(Binder.class),
-                mConnectListenerArgumentCaptor.capture(), anyInt(), anyInt());
+        verify(mClientModeManager).disconnect();
+        verify(mConnectHelper).connectToNetwork(eq(mClientModeManager),
+                eq(new NetworkUpdateResult(TEST_NETWORK_ID_1)),
+                mConnectListenerArgumentCaptor.capture(), anyInt());
     }
 
     /**
@@ -1361,14 +1523,13 @@
                 Pair.create(MacAddress.fromString(matchingScanResult.BSSID),
                         MacAddress.BROADCAST_ADDRESS);
         WifiConfiguration wifiConfiguration = new WifiConfiguration();
-        wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+        wifiConfiguration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
         wifiConfiguration.preSharedKey = TEST_WPA_PRESHARED_KEY;
         attachWifiNetworkSpecifierAndAppInfo(
                 ssidPatternMatch, bssidPatternMatch, wifiConfiguration, TEST_UID_1,
                 TEST_PACKAGE_NAME_1);
         mWifiNetworkFactory.needNetworkFor(mNetworkRequest);
-        mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
-                TEST_CALLBACK_IDENTIFIER);
+        mWifiNetworkFactory.addCallback(mNetworkRequestMatchCallback);
         verify(mNetworkRequestMatchCallback).onUserSelectionCallbackRegistration(
                 mNetworkRequestUserSelectionCallback.capture());
         verifyPeriodicScans(
@@ -1390,9 +1551,10 @@
         verify(mWifiMetrics).setNominatorForNetwork(anyInt(),
                 eq(WifiMetricsProto.ConnectionEvent.NOMINATOR_SPECIFIER));
 
-        verify(mClientModeImpl).disconnectCommand();
-        verify(mClientModeImpl).connect(eq(null), eq(TEST_NETWORK_ID_1), any(Binder.class),
-                mConnectListenerArgumentCaptor.capture(), anyInt(), anyInt());
+        verify(mClientModeManager).disconnect();
+        verify(mConnectHelper).connectToNetwork(eq(mClientModeManager),
+                eq(new NetworkUpdateResult(TEST_NETWORK_ID_1)),
+                mConnectListenerArgumentCaptor.capture(), anyInt());
     }
 
     /**
@@ -1422,14 +1584,13 @@
                 Pair.create(MacAddress.fromString(matchingScanResult.BSSID),
                         MacAddress.BROADCAST_ADDRESS);
         WifiConfiguration wifiConfiguration = new WifiConfiguration();
-        wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+        wifiConfiguration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
         wifiConfiguration.preSharedKey = TEST_WPA_PRESHARED_KEY;
         attachWifiNetworkSpecifierAndAppInfo(
                 ssidPatternMatch, bssidPatternMatch, wifiConfiguration, TEST_UID_1,
                 TEST_PACKAGE_NAME_1);
         mWifiNetworkFactory.needNetworkFor(mNetworkRequest);
-        mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
-                TEST_CALLBACK_IDENTIFIER);
+        mWifiNetworkFactory.addCallback(mNetworkRequestMatchCallback);
         verify(mNetworkRequestMatchCallback).onUserSelectionCallbackRegistration(
                 mNetworkRequestUserSelectionCallback.capture());
 
@@ -1457,9 +1618,10 @@
         verify(mWifiMetrics).setNominatorForNetwork(anyInt(),
                 eq(WifiMetricsProto.ConnectionEvent.NOMINATOR_SPECIFIER));
 
-        verify(mClientModeImpl).disconnectCommand();
-        verify(mClientModeImpl).connect(eq(null), eq(TEST_NETWORK_ID_1), any(Binder.class),
-                mConnectListenerArgumentCaptor.capture(), anyInt(), anyInt());
+        verify(mClientModeManager).disconnect();
+        verify(mConnectHelper).connectToNetwork(eq(mClientModeManager),
+                eq(new NetworkUpdateResult(TEST_NETWORK_ID_1)),
+                mConnectListenerArgumentCaptor.capture(), anyInt());
     }
 
     /**
@@ -1480,13 +1642,12 @@
         // Cancel periodic scans.
         verify(mAlarmManager).cancel(any(OnAlarmListener.class));
         // Verify we reset the network request handling.
-        verify(mWifiConnectivityManager).setSpecificNetworkRequestInProgress(false);
         verify(mConnectivityManager).declareNetworkRequestUnfulfillable(eq(mNetworkRequest));
 
         verify(mWifiMetrics).incrementNetworkRequestApiNumUserReject();
 
         // Verify we did not attempt to trigger a connection.
-        verifyNoMoreInteractions(mClientModeImpl, mWifiConfigManager);
+        verifyNoMoreInteractions(mClientModeManager, mWifiConfigManager);
     }
 
     /**
@@ -1503,7 +1664,7 @@
             mLooper.dispatchAll();
         }
 
-        mInOrder = inOrder(mAlarmManager, mClientModeImpl);
+        mInOrder = inOrder(mAlarmManager, mClientModeManager, mConnectHelper);
         validateConnectionRetryAttempts(true);
 
         // Fail the request after all the retries are exhausted.
@@ -1513,6 +1674,7 @@
                 argThat(new WifiConfigMatcher(mSelectedNetwork)));
         // Verify we reset the network request handling.
         verify(mWifiConnectivityManager).setSpecificNetworkRequestInProgress(false);
+        verify(mActiveModeWarden).removeClientModeManager(any());
         verify(mConnectivityManager).declareNetworkRequestUnfulfillable(eq(mNetworkRequest));
     }
 
@@ -1527,11 +1689,11 @@
         // Send failure message beyond the retry limit to trigger the failure handling.
         for (int i = 0; i <= WifiNetworkFactory.USER_SELECTED_NETWORK_CONNECT_RETRY_MAX; i++) {
             assertNotNull(mConnectionTimeoutAlarmListenerArgumentCaptor.getValue());
-            mConnectListenerArgumentCaptor.getValue().onFailure(WifiManager.ERROR);
+            mConnectListenerArgumentCaptor.getValue().sendFailure(WifiManager.ERROR);
         }
         mLooper.dispatchAll();
 
-        mInOrder = inOrder(mAlarmManager, mClientModeImpl);
+        mInOrder = inOrder(mAlarmManager, mClientModeManager, mConnectHelper);
         validateConnectionRetryAttempts(false);
 
         // Fail the request after all the retries are exhausted.
@@ -1544,6 +1706,7 @@
                 mConnectionTimeoutAlarmListenerArgumentCaptor.getValue());
         // Verify we reset the network request handling.
         verify(mWifiConnectivityManager).setSpecificNetworkRequestInProgress(false);
+        verify(mActiveModeWarden).removeClientModeManager(any());
         verify(mConnectivityManager).declareNetworkRequestUnfulfillable(eq(mNetworkRequest));
     }
 
@@ -1561,11 +1724,11 @@
         // handling.
         for (int i = 0; i <= WifiNetworkFactory.USER_SELECTED_NETWORK_CONNECT_RETRY_MAX; i++) {
             mWifiNetworkFactory.handleConnectionAttemptEnded(
-                    WifiMetrics.ConnectionEvent.FAILURE_DHCP, mSelectedNetwork);
+                    WifiMetrics.ConnectionEvent.FAILURE_DHCP, mSelectedNetwork, TEST_BSSID_1);
             mLooper.dispatchAll();
         }
 
-        mInOrder = inOrder(mAlarmManager, mClientModeImpl);
+        mInOrder = inOrder(mAlarmManager, mClientModeManager, mConnectHelper);
         validateConnectionRetryAttempts(false);
 
         verify(mNetworkRequestMatchCallback).onAbort();
@@ -1577,6 +1740,7 @@
                 mConnectionTimeoutAlarmListenerArgumentCaptor.getValue());
         // Verify we reset the network request handling.
         verify(mWifiConnectivityManager).setSpecificNetworkRequestInProgress(false);
+        verify(mActiveModeWarden).removeClientModeManager(any());
         verify(mConnectivityManager).declareNetworkRequestUnfulfillable(eq(mNetworkRequest));
     }
 
@@ -1590,9 +1754,9 @@
         // Send network connection failure to a different network indication.
         assertNotNull(mSelectedNetwork);
         WifiConfiguration connectedNetwork = new WifiConfiguration(mSelectedNetwork);
-        connectedNetwork.SSID += "test";
+        connectedNetwork.SSID = TEST_SSID_2;
         mWifiNetworkFactory.handleConnectionAttemptEnded(
-                WifiMetrics.ConnectionEvent.FAILURE_DHCP, connectedNetwork);
+                WifiMetrics.ConnectionEvent.FAILURE_DHCP, connectedNetwork, TEST_BSSID_1);
 
         // Verify that we did not send the connection failure callback.
         verify(mNetworkRequestMatchCallback, never()).onUserSelectionConnectFailure(any());
@@ -1607,11 +1771,11 @@
         // handling.
         for (int i = 0; i <= WifiNetworkFactory.USER_SELECTED_NETWORK_CONNECT_RETRY_MAX; i++) {
             mWifiNetworkFactory.handleConnectionAttemptEnded(
-                    WifiMetrics.ConnectionEvent.FAILURE_DHCP, mSelectedNetwork);
+                    WifiMetrics.ConnectionEvent.FAILURE_DHCP, mSelectedNetwork, TEST_BSSID_1);
             mLooper.dispatchAll();
         }
 
-        mInOrder = inOrder(mAlarmManager, mClientModeImpl);
+        mInOrder = inOrder(mAlarmManager, mClientModeManager, mConnectHelper);
         validateConnectionRetryAttempts(false);
 
         // Verify that we sent the connection failure callback.
@@ -1622,6 +1786,51 @@
                 mConnectionTimeoutAlarmListenerArgumentCaptor.getValue());
         // Verify we reset the network request handling.
         verify(mWifiConnectivityManager).setSpecificNetworkRequestInProgress(false);
+        verify(mActiveModeWarden).removeClientModeManager(any());
+        verify(mConnectivityManager).declareNetworkRequestUnfulfillable(eq(mNetworkRequest));
+    }
+
+    /**
+     * Verify handling of connection failure to a different bssid.
+     */
+    @Test
+    public void testNetworkSpecifierHandleConnectionFailureToWrongBssid() throws Exception {
+        sendNetworkRequestAndSetupForConnectionStatus();
+
+        // Send network connection failure to a different bssid indication.
+        assertNotNull(mSelectedNetwork);
+        mWifiNetworkFactory.handleConnectionAttemptEnded(
+                WifiMetrics.ConnectionEvent.FAILURE_DHCP, mSelectedNetwork, TEST_BSSID_2);
+
+        // Verify that we did not send the connection failure callback.
+        verify(mNetworkRequestMatchCallback, never()).onUserSelectionConnectFailure(any());
+        // verify we canceled the timeout alarm.
+        verify(mAlarmManager, never())
+                .cancel(mConnectionTimeoutAlarmListenerArgumentCaptor.getValue());
+        // Verify we don't reset the network request handling.
+        verify(mWifiConnectivityManager, never())
+                .setSpecificNetworkRequestInProgress(false);
+
+        // Send network connection failure indication beyond the retry limit to trigger the failure
+        // handling.
+        for (int i = 0; i <= WifiNetworkFactory.USER_SELECTED_NETWORK_CONNECT_RETRY_MAX; i++) {
+            mWifiNetworkFactory.handleConnectionAttemptEnded(
+                    WifiMetrics.ConnectionEvent.FAILURE_DHCP, mSelectedNetwork, TEST_BSSID_1);
+            mLooper.dispatchAll();
+        }
+
+        mInOrder = inOrder(mAlarmManager, mClientModeManager, mConnectHelper);
+        validateConnectionRetryAttempts(false);
+
+        // Verify that we sent the connection failure callback.
+        verify(mNetworkRequestMatchCallback).onUserSelectionConnectFailure(
+                argThat(new WifiConfigMatcher(mSelectedNetwork)));
+        // verify we canceled the timeout alarm.
+        mInOrder.verify(mAlarmManager).cancel(
+                mConnectionTimeoutAlarmListenerArgumentCaptor.getValue());
+        // Verify we reset the network request handling.
+        verify(mWifiConnectivityManager).setSpecificNetworkRequestInProgress(false);
+        verify(mActiveModeWarden).removeClientModeManager(any());
         verify(mConnectivityManager).declareNetworkRequestUnfulfillable(eq(mNetworkRequest));
     }
 
@@ -1635,15 +1844,89 @@
         // Send network connection success indication.
         assertNotNull(mSelectedNetwork);
         mWifiNetworkFactory.handleConnectionAttemptEnded(
-                WifiMetrics.ConnectionEvent.FAILURE_NONE, mSelectedNetwork);
+                WifiMetrics.ConnectionEvent.FAILURE_NONE, mSelectedNetwork, TEST_BSSID_1);
 
         // Verify that we sent the connection success callback.
         verify(mNetworkRequestMatchCallback).onUserSelectionConnectSuccess(
                 argThat(new WifiConfigMatcher(mSelectedNetwork)));
         // verify we canceled the timeout alarm.
         verify(mAlarmManager).cancel(mConnectionTimeoutAlarmListenerArgumentCaptor.getValue());
+        // Verify we disabled fw roaming.
+        verify(mClientModeManager).enableRoaming(false);
 
-        verify(mWifiMetrics).incrementNetworkRequestApiNumConnectSuccess();
+        // Now release the active network request.
+        mWifiNetworkFactory.releaseNetworkFor(mNetworkRequest);
+
+        verify(mWifiMetrics).incrementNetworkRequestApiNumConnectSuccessOnPrimaryIface();
+        // Ensure that we toggle auto-join state.
+        verify(mWifiConnectivityManager).setSpecificNetworkRequestInProgress(true);
+        verify(mWifiConnectivityManager).setSpecificNetworkRequestInProgress(false);
+        verify(mClientModeManager).enableRoaming(true);
+    }
+
+    /**
+     * Verify handling of connection success.
+     */
+    @Test
+    public void testNetworkSpecifierHandleConnectionSuccessWhenUseHalApiIsDisabled()
+            throws Exception {
+        when(mResources.getBoolean(
+                eq(R.bool.config_wifiUseHalApiToDisableFwRoaming)))
+                .thenReturn(false);
+        sendNetworkRequestAndSetupForConnectionStatus();
+
+        // Send network connection success indication.
+        assertNotNull(mSelectedNetwork);
+        mWifiNetworkFactory.handleConnectionAttemptEnded(
+                WifiMetrics.ConnectionEvent.FAILURE_NONE, mSelectedNetwork, TEST_BSSID_1);
+
+        // Verify that we sent the connection success callback.
+        verify(mNetworkRequestMatchCallback).onUserSelectionConnectSuccess(
+                argThat(new WifiConfigMatcher(mSelectedNetwork)));
+        // verify we canceled the timeout alarm.
+        verify(mAlarmManager).cancel(mConnectionTimeoutAlarmListenerArgumentCaptor.getValue());
+        // Verify we disabled fw roaming.
+        verify(mClientModeManager, never()).enableRoaming(false);
+
+        // Now release the active network request.
+        mWifiNetworkFactory.releaseNetworkFor(mNetworkRequest);
+
+        verify(mWifiMetrics).incrementNetworkRequestApiNumConnectSuccessOnPrimaryIface();
+        // Ensure that we toggle auto-join state.
+        verify(mWifiConnectivityManager).setSpecificNetworkRequestInProgress(true);
+        verify(mWifiConnectivityManager).setSpecificNetworkRequestInProgress(false);
+        verify(mClientModeManager, never()).enableRoaming(true);
+    }
+
+    /**
+     * Verify handling of connection success.
+     */
+    @Test
+    public void testNetworkSpecifierHandleConnectionSuccessOnSecondaryCmm()
+            throws Exception {
+        when(mClientModeManager.getRole()).thenReturn(ActiveModeManager.ROLE_CLIENT_LOCAL_ONLY);
+        sendNetworkRequestAndSetupForConnectionStatus();
+
+        // Send network connection success indication.
+        assertNotNull(mSelectedNetwork);
+        mWifiNetworkFactory.handleConnectionAttemptEnded(
+                WifiMetrics.ConnectionEvent.FAILURE_NONE, mSelectedNetwork, TEST_BSSID_1);
+
+        // Verify that we sent the connection success callback.
+        verify(mNetworkRequestMatchCallback).onUserSelectionConnectSuccess(
+                argThat(new WifiConfigMatcher(mSelectedNetwork)));
+        // verify we canceled the timeout alarm.
+        verify(mAlarmManager).cancel(mConnectionTimeoutAlarmListenerArgumentCaptor.getValue());
+        // Verify we disabled fw roaming.
+        verify(mClientModeManager).enableRoaming(false);
+
+        // Now release the active network request.
+        mWifiNetworkFactory.releaseNetworkFor(mNetworkRequest);
+
+        verify(mWifiMetrics).incrementNetworkRequestApiNumConnectSuccessOnSecondaryIface();
+        // Don't toggle auto-join state.
+        verify(mWifiConnectivityManager, never()).setSpecificNetworkRequestInProgress(anyBoolean());
+        verify(mClientModeManager).enableRoaming(true);
     }
 
     /**
@@ -1657,7 +1940,7 @@
         // Send network connection success indication.
         assertNotNull(mSelectedNetwork);
         mWifiNetworkFactory.handleConnectionAttemptEnded(
-                WifiMetrics.ConnectionEvent.FAILURE_NONE, mSelectedNetwork);
+                WifiMetrics.ConnectionEvent.FAILURE_NONE, mSelectedNetwork, TEST_BSSID_1);
 
         // Verify that we sent the connection success callback.
         verify(mNetworkRequestMatchCallback).onUserSelectionConnectSuccess(
@@ -1665,11 +1948,13 @@
         // verify we canceled the timeout alarm.
         verify(mAlarmManager).cancel(mConnectionTimeoutAlarmListenerArgumentCaptor.getValue());
 
-        verify(mWifiMetrics).incrementNetworkRequestApiNumConnectSuccess();
+        verify(mWifiMetrics).incrementNetworkRequestApiNumConnectSuccessOnPrimaryIface();
+        verify(mNetworkRequestMatchCallback, atLeastOnce()).asBinder();
 
         // Send second network connection success indication which should be ignored.
         mWifiNetworkFactory.handleConnectionAttemptEnded(
-                WifiMetrics.ConnectionEvent.FAILURE_NONE, mSelectedNetwork);
+                WifiMetrics.ConnectionEvent.FAILURE_NONE, mSelectedNetwork, TEST_BSSID_1);
+
         verifyNoMoreInteractions(mNetworkRequestMatchCallback);
     }
 
@@ -1685,7 +1970,7 @@
         // Send network connection success indication.
         assertNotNull(mSelectedNetwork);
         mWifiNetworkFactory.handleConnectionAttemptEnded(
-                WifiMetrics.ConnectionEvent.FAILURE_NONE, mSelectedNetwork);
+                WifiMetrics.ConnectionEvent.FAILURE_NONE, mSelectedNetwork, TEST_BSSID_1);
 
         // Verify that we sent the connection success callback.
         verify(mNetworkRequestMatchCallback).onUserSelectionConnectSuccess(
@@ -1693,13 +1978,14 @@
         // verify we canceled the timeout alarm.
         verify(mAlarmManager).cancel(mConnectionTimeoutAlarmListenerArgumentCaptor.getValue());
 
-        verify(mWifiMetrics).incrementNetworkRequestApiNumConnectSuccess();
+        verify(mWifiMetrics).incrementNetworkRequestApiNumConnectSuccessOnPrimaryIface();
+        verify(mNetworkRequestMatchCallback, atLeastOnce()).asBinder();
 
         // Send a network connection failure indication which should be ignored (beyond the retry
         // limit to trigger the failure handling).
         for (int i = 0; i <= WifiNetworkFactory.USER_SELECTED_NETWORK_CONNECT_RETRY_MAX; i++) {
             mWifiNetworkFactory.handleConnectionAttemptEnded(
-                    WifiMetrics.ConnectionEvent.FAILURE_DHCP, mSelectedNetwork);
+                    WifiMetrics.ConnectionEvent.FAILURE_DHCP, mSelectedNetwork, TEST_BSSID_1);
             mLooper.dispatchAll();
         }
         // Verify that we ignore the second connection failure callback.
@@ -1716,9 +2002,9 @@
         // Send network connection success to a different network indication.
         assertNotNull(mSelectedNetwork);
         WifiConfiguration connectedNetwork = new WifiConfiguration(mSelectedNetwork);
-        connectedNetwork.SSID += "test";
+        connectedNetwork.SSID = TEST_SSID_2;
         mWifiNetworkFactory.handleConnectionAttemptEnded(
-                WifiMetrics.ConnectionEvent.FAILURE_NONE, connectedNetwork);
+                WifiMetrics.ConnectionEvent.FAILURE_NONE, connectedNetwork, TEST_BSSID_1);
 
         // verify that we did not send out the success callback and did not stop the alarm timeout.
         verify(mNetworkRequestMatchCallback, never()).onUserSelectionConnectSuccess(any());
@@ -1727,7 +2013,35 @@
 
         // Send network connection success to the correct network indication.
         mWifiNetworkFactory.handleConnectionAttemptEnded(
-                WifiMetrics.ConnectionEvent.FAILURE_NONE, mSelectedNetwork);
+                WifiMetrics.ConnectionEvent.FAILURE_NONE, mSelectedNetwork, TEST_BSSID_1);
+
+        // Verify that we sent the connection success callback.
+        verify(mNetworkRequestMatchCallback).onUserSelectionConnectSuccess(
+                argThat(new WifiConfigMatcher(mSelectedNetwork)));
+        // verify we canceled the timeout alarm.
+        verify(mAlarmManager).cancel(mConnectionTimeoutAlarmListenerArgumentCaptor.getValue());
+    }
+
+    /**
+     * Verify handling of connection success to a different BSSID.
+     */
+    @Test
+    public void testNetworkSpecifierHandleConnectionSuccessToWrongBssid() throws Exception {
+        sendNetworkRequestAndSetupForConnectionStatus();
+
+        // Send network connection success to a different network indication.
+        assertNotNull(mSelectedNetwork);
+        mWifiNetworkFactory.handleConnectionAttemptEnded(
+                WifiMetrics.ConnectionEvent.FAILURE_NONE, mSelectedNetwork, TEST_BSSID_2);
+
+        // verify that we did not send out the success callback and did not stop the alarm timeout.
+        verify(mNetworkRequestMatchCallback, never()).onUserSelectionConnectSuccess(any());
+        verify(mAlarmManager, never())
+                .cancel(mConnectionTimeoutAlarmListenerArgumentCaptor.getValue());
+
+        // Send network connection success to the correct network indication.
+        mWifiNetworkFactory.handleConnectionAttemptEnded(
+                WifiMetrics.ConnectionEvent.FAILURE_NONE, mSelectedNetwork, TEST_BSSID_1);
 
         // Verify that we sent the connection success callback.
         verify(mNetworkRequestMatchCallback).onUserSelectionConnectSuccess(
@@ -1753,17 +2067,18 @@
         wcmNetwork.shared = false;
         wcmNetwork.fromWifiNetworkSpecifier = true;
         wcmNetwork.ephemeral = true;
-        when(mWifiConfigManager.getConfiguredNetwork(wcmNetwork.getKey()))
+        when(mWifiConfigManager.getConfiguredNetwork(wcmNetwork.getProfileKey()))
                 .thenReturn(wcmNetwork);
         mWifiNetworkFactory.releaseNetworkFor(mNetworkRequest);
         // verify we canceled the timeout alarm.
         verify(mAlarmManager).cancel(mConnectionTimeoutAlarmListenerArgumentCaptor.getValue());
         // Verify that we triggered a disconnect.
-        verify(mClientModeImpl, times(2)).disconnectCommand();
+        verify(mClientModeManager, times(2)).disconnect();
         verify(mWifiConfigManager).removeNetwork(
                 TEST_NETWORK_ID_1, TEST_UID_1, TEST_PACKAGE_NAME_1);
         // Re-enable connectivity manager .
         verify(mWifiConnectivityManager).setSpecificNetworkRequestInProgress(false);
+        verify(mActiveModeWarden).removeClientModeManager(any());
     }
 
     /**
@@ -1771,18 +2086,23 @@
      */
     @Test
     public void testHandleNetworkReleaseWithSpecifierAfterConnectionSuccess() throws Exception {
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(0L);
         sendNetworkRequestAndSetupForConnectionStatus();
 
         // Send network connection success indication.
         assertNotNull(mSelectedNetwork);
         mWifiNetworkFactory.handleConnectionAttemptEnded(
-                WifiMetrics.ConnectionEvent.FAILURE_NONE, mSelectedNetwork);
+                WifiMetrics.ConnectionEvent.FAILURE_NONE, mSelectedNetwork, TEST_BSSID_1);
 
         // Verify that we sent the connection success callback.
         verify(mNetworkRequestMatchCallback).onUserSelectionConnectSuccess(
                 argThat(new WifiConfigMatcher(mSelectedNetwork)));
         // verify we canceled the timeout alarm.
         verify(mAlarmManager).cancel(mConnectionTimeoutAlarmListenerArgumentCaptor.getValue());
+        verify(mWifiMetrics).incrementNetworkRequestApiNumConnectSuccessOnPrimaryIface();
+
+        long connectionDurationMillis = 5665L;
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(connectionDurationMillis);
 
         // Now release the network request.
         WifiConfiguration wcmNetwork = new WifiConfiguration(mSelectedNetwork);
@@ -1792,27 +2112,169 @@
         wcmNetwork.shared = false;
         wcmNetwork.fromWifiNetworkSpecifier = true;
         wcmNetwork.ephemeral = true;
-        when(mWifiConfigManager.getConfiguredNetwork(wcmNetwork.getKey()))
+        when(mWifiConfigManager.getConfiguredNetwork(wcmNetwork.getProfileKey()))
                 .thenReturn(wcmNetwork);
         mWifiNetworkFactory.releaseNetworkFor(mNetworkRequest);
         // Verify that we triggered a disconnect.
-        verify(mClientModeImpl, times(2)).disconnectCommand();
+        verify(mClientModeManager, times(2)).disconnect();
         verify(mWifiConfigManager).removeNetwork(
                 TEST_NETWORK_ID_1, TEST_UID_1, TEST_PACKAGE_NAME_1);
         // Re-enable connectivity manager .
         verify(mWifiConnectivityManager).setSpecificNetworkRequestInProgress(false);
+        verify(mActiveModeWarden).removeClientModeManager(any());
+        verify(mWifiMetrics).incrementNetworkRequestApiConnectionDurationSecOnPrimaryIfaceHistogram(
+                toIntExact(TimeUnit.MILLISECONDS.toSeconds(connectionDurationMillis)));
     }
 
     /**
+     * Verify handling of request release after connecting to the network.
+     */
+    @Test
+    public void testHandleNetworkReleaseWithSpecifierAfterConnectionSuccessOnSecondaryCmm()
+            throws Exception {
+        when(mClientModeManager.getRole()).thenReturn(ActiveModeManager.ROLE_CLIENT_LOCAL_ONLY);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(0L);
+        sendNetworkRequestAndSetupForConnectionStatus();
+
+        // Send network connection success indication.
+        assertNotNull(mSelectedNetwork);
+        mWifiNetworkFactory.handleConnectionAttemptEnded(
+                WifiMetrics.ConnectionEvent.FAILURE_NONE, mSelectedNetwork, TEST_BSSID_1);
+
+        // Verify that we sent the connection success callback.
+        verify(mNetworkRequestMatchCallback).onUserSelectionConnectSuccess(
+                argThat(new WifiConfigMatcher(mSelectedNetwork)));
+        // verify we canceled the timeout alarm.
+        verify(mAlarmManager).cancel(mConnectionTimeoutAlarmListenerArgumentCaptor.getValue());
+        verify(mWifiMetrics).incrementNetworkRequestApiNumConnectSuccessOnSecondaryIface();
+
+        // Now release the network request.
+        long connectionDurationMillis = 5665L;
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(connectionDurationMillis);
+        WifiConfiguration wcmNetwork = new WifiConfiguration(mSelectedNetwork);
+        wcmNetwork.networkId = TEST_NETWORK_ID_1;
+        wcmNetwork.creatorUid = TEST_UID_1;
+        wcmNetwork.creatorName = TEST_PACKAGE_NAME_1;
+        wcmNetwork.shared = false;
+        wcmNetwork.fromWifiNetworkSpecifier = true;
+        wcmNetwork.ephemeral = true;
+        when(mWifiConfigManager.getConfiguredNetwork(wcmNetwork.getProfileKey()))
+                .thenReturn(wcmNetwork);
+        mWifiNetworkFactory.releaseNetworkFor(mNetworkRequest);
+        // Verify that we triggered a disconnect.
+        verify(mClientModeManager, times(2)).disconnect();
+        verify(mWifiConfigManager).removeNetwork(
+                TEST_NETWORK_ID_1, TEST_UID_1, TEST_PACKAGE_NAME_1);
+        // Re-enable connectivity manager .
+        verify(mActiveModeWarden).removeClientModeManager(any());
+        verify(mWifiMetrics)
+                .incrementNetworkRequestApiConnectionDurationSecOnSecondaryIfaceHistogram(
+                        toIntExact(TimeUnit.MILLISECONDS.toSeconds(connectionDurationMillis)));
+    }
+
+    @Test
+    public void testMetricsUpdateForConcurrentConnections() throws Exception {
+        when(mClientModeManager.getRole()).thenReturn(ActiveModeManager.ROLE_CLIENT_LOCAL_ONLY);
+        when(mActiveModeWarden.getClientModeManagers()).thenReturn(
+                Arrays.asList(mClientModeManager));
+
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(0L);
+        sendNetworkRequestAndSetupForConnectionStatus();
+
+        // Send network connection success indication.
+        assertNotNull(mSelectedNetwork);
+        mWifiNetworkFactory.handleConnectionAttemptEnded(
+                WifiMetrics.ConnectionEvent.FAILURE_NONE, mSelectedNetwork, TEST_BSSID_1);
+
+        verify(mCmiMonitor).registerListener(mCmiListenerCaptor.capture());
+
+        // Verify that we sent the connection success callback.
+        verify(mNetworkRequestMatchCallback).onUserSelectionConnectSuccess(
+                argThat(new WifiConfigMatcher(mSelectedNetwork)));
+        // verify we canceled the timeout alarm.
+        verify(mAlarmManager).cancel(mConnectionTimeoutAlarmListenerArgumentCaptor.getValue());
+        verify(mWifiMetrics).incrementNetworkRequestApiNumConnectSuccessOnSecondaryIface();
+
+        // Indicate secondary connection via CMI listener.
+        when(mClientModeManager.isConnected()).thenReturn(true);
+        mCmiListenerCaptor.getValue().onL3Connected(mClientModeManager);
+
+        // Now indicate connection on the primary STA.
+        ConcreteClientModeManager primaryCmm = mock(ConcreteClientModeManager.class);
+        when(primaryCmm.isConnected()).thenReturn(true);
+        when(primaryCmm.getRole()).thenReturn(ActiveModeManager.ROLE_CLIENT_PRIMARY);
+        when(mActiveModeWarden.getClientModeManagers()).thenReturn(
+                Arrays.asList(mClientModeManager, primaryCmm));
+        mCmiListenerCaptor.getValue().onL3Connected(primaryCmm);
+
+        // verify metrics update for concurrent connection count.
+        verify(mWifiMetrics).incrementNetworkRequestApiNumConcurrentConnection();
+
+        // Now indicate end of connection on primary STA
+        long primaryConnectionDurationMillis1 = 5665L;
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(primaryConnectionDurationMillis1);
+        when(primaryCmm.isConnected()).thenReturn(false);
+        mCmiListenerCaptor.getValue().onConnectionEnd(primaryCmm);
+        // verify metrics update for concurrent connection duration.
+        verify(mWifiMetrics)
+                .incrementNetworkRequestApiConcurrentConnectionDurationSecHistogram(
+                        toIntExact(TimeUnit.MILLISECONDS.toSeconds(
+                                primaryConnectionDurationMillis1)));
+
+        // Indicate a new connection on primary STA.
+        when(primaryCmm.isConnected()).thenReturn(true);
+        mCmiListenerCaptor.getValue().onL3Connected(primaryCmm);
+
+        // verify that we did not update metrics gain for concurrent connection count.
+        verify(mWifiMetrics, times(1)).incrementNetworkRequestApiNumConcurrentConnection();
+
+        // Now indicate end of new connection on primary STA
+        long primaryConnectionDurationMillis2 = 5665L;
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(primaryConnectionDurationMillis2);
+        when(primaryCmm.isConnected()).thenReturn(false);
+        mCmiListenerCaptor.getValue().onConnectionEnd(primaryCmm);
+        // verify metrics update for concurrent connection duration.
+        verify(mWifiMetrics)
+                .incrementNetworkRequestApiConcurrentConnectionDurationSecHistogram(
+                        toIntExact(TimeUnit.MILLISECONDS.toSeconds(
+                                primaryConnectionDurationMillis2)));
+
+        // Now release the network request.
+        long secondaryConnectionDurationMillis = 5665L;
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(secondaryConnectionDurationMillis);
+        WifiConfiguration wcmNetwork = new WifiConfiguration(mSelectedNetwork);
+        wcmNetwork.networkId = TEST_NETWORK_ID_1;
+        wcmNetwork.creatorUid = TEST_UID_1;
+        wcmNetwork.creatorName = TEST_PACKAGE_NAME_1;
+        wcmNetwork.shared = false;
+        wcmNetwork.fromWifiNetworkSpecifier = true;
+        wcmNetwork.ephemeral = true;
+        when(mWifiConfigManager.getConfiguredNetwork(wcmNetwork.getProfileKey()))
+                .thenReturn(wcmNetwork);
+        mWifiNetworkFactory.releaseNetworkFor(mNetworkRequest);
+        // Verify that we triggered a disconnect.
+        verify(mClientModeManager, times(2)).disconnect();
+        verify(mWifiConfigManager).removeNetwork(
+                TEST_NETWORK_ID_1, TEST_UID_1, TEST_PACKAGE_NAME_1);
+        // Re-enable connectivity manager .
+        verify(mActiveModeWarden).removeClientModeManager(any());
+        verify(mWifiMetrics)
+                .incrementNetworkRequestApiConnectionDurationSecOnSecondaryIfaceHistogram(
+                        toIntExact(TimeUnit.MILLISECONDS.toSeconds(
+                                secondaryConnectionDurationMillis)));
+    }
+
+
+    /**
      * Verify we return the correct UID when processing network request with network specifier.
      */
     @Test
     public void testHandleNetworkRequestWithSpecifierGetUid() throws Exception {
         assertEquals(Integer.valueOf(Process.INVALID_UID),
                 mWifiNetworkFactory.getSpecificNetworkRequestUidAndPackageName(
-                        new WifiConfiguration()).first);
+                        new WifiConfiguration(), new String()).first);
         assertTrue(mWifiNetworkFactory.getSpecificNetworkRequestUidAndPackageName(
-                new WifiConfiguration()).second.isEmpty());
+                new WifiConfiguration(), new String()).second.isEmpty());
 
         sendNetworkRequestAndSetupForConnectionStatus();
         assertNotNull(mSelectedNetwork);
@@ -1822,18 +2284,84 @@
         connectedNetwork.SSID += "test";
         assertEquals(Integer.valueOf(Process.INVALID_UID),
                 mWifiNetworkFactory.getSpecificNetworkRequestUidAndPackageName(
-                        new WifiConfiguration()).first);
+                        new WifiConfiguration(), new String()).first);
         assertTrue(mWifiNetworkFactory.getSpecificNetworkRequestUidAndPackageName(
-                new WifiConfiguration()).second.isEmpty());
+                new WifiConfiguration(), new String()).second.isEmpty());
 
         // connected to the correct network.
         connectedNetwork = new WifiConfiguration(mSelectedNetwork);
+        String connectedBssid = TEST_BSSID_1;
         assertEquals(Integer.valueOf(TEST_UID_1),
                 mWifiNetworkFactory.getSpecificNetworkRequestUidAndPackageName(
-                        connectedNetwork).first);
+                        connectedNetwork, connectedBssid).first);
         assertEquals(TEST_PACKAGE_NAME_1,
                 mWifiNetworkFactory.getSpecificNetworkRequestUidAndPackageName(
-                        connectedNetwork).second);
+                        connectedNetwork, connectedBssid).second);
+    }
+
+    /**
+     *  Verify handling for new network request while processing another one.
+     */
+    @Test
+    public void testHandleNewNetworkRequestWithSpecifierWhenAwaitingCmRetrieval() throws Exception {
+        doNothing().when(mActiveModeWarden).requestLocalOnlyClientModeManager(
+                any(), any(), any(), any());
+
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(0L);
+
+        sendNetworkRequestAndSetupForUserSelection();
+
+        INetworkRequestUserSelectionCallback networkRequestUserSelectionCallback =
+                mNetworkRequestUserSelectionCallback.getValue();
+        assertNotNull(networkRequestUserSelectionCallback);
+
+        // Now trigger user selection to one of the network.
+        mSelectedNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        mSelectedNetwork.SSID = "\"" + TEST_SSID_1 + "\"";
+        sendUserSelectionSelect(networkRequestUserSelectionCallback, mSelectedNetwork);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<ActiveModeWarden.ExternalClientModeManagerRequestListener> cmListenerCaptor =
+                ArgumentCaptor.forClass(
+                        ActiveModeWarden.ExternalClientModeManagerRequestListener.class);
+        verify(mActiveModeWarden).requestLocalOnlyClientModeManager(
+                cmListenerCaptor.capture(), eq(new WorkSource(TEST_UID_1, TEST_PACKAGE_NAME_1)),
+                eq("\"" + TEST_SSID_1 + "\""), eq(TEST_BSSID_1));
+        assertNotNull(cmListenerCaptor.getValue());
+
+        NetworkRequest oldRequest = new NetworkRequest(mNetworkRequest);
+        // Send second request.
+        attachDefaultWifiNetworkSpecifierAndAppInfo(TEST_UID_2, false);
+        mWifiNetworkFactory.needNetworkFor(mNetworkRequest);
+        mLooper.dispatchAll();
+
+        // Verify that we aborted the old request.
+        verify(mNetworkRequestMatchCallback).onAbort();
+        verify(mConnectivityManager).declareNetworkRequestUnfulfillable(eq(oldRequest));
+
+        mWifiNetworkFactory.addCallback(mNetworkRequestMatchCallback);
+        verify(mNetworkRequestMatchCallback, times(2)).onUserSelectionCallbackRegistration(
+                mNetworkRequestUserSelectionCallback.capture());
+
+        // Now trigger user selection to one of the network.
+        networkRequestUserSelectionCallback = mNetworkRequestUserSelectionCallback.getValue();
+        assertNotNull(networkRequestUserSelectionCallback);
+        sendUserSelectionSelect(networkRequestUserSelectionCallback, mSelectedNetwork);
+        mLooper.dispatchAll();
+
+        // Ensure we request a new ClientModeManager.
+        verify(mActiveModeWarden, times(2)).requestLocalOnlyClientModeManager(
+                any(), any(), any(), any());
+
+        // Now return the CM instance for the previous request.
+        cmListenerCaptor.getValue().onAnswer(mClientModeManager);
+
+        // Ensure we continued processing the new request.
+        verify(mClientModeManager).disconnect();
+        verify(mConnectHelper).connectToNetwork(
+                eq(mClientModeManager),
+                eq(new NetworkUpdateResult(TEST_NETWORK_ID_1)),
+                mConnectListenerArgumentCaptor.capture(), anyInt());
     }
 
     /**
@@ -1845,8 +2373,7 @@
         mWifiNetworkFactory.needNetworkFor(mNetworkRequest);
 
         // Register callback.
-        mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
-                TEST_CALLBACK_IDENTIFIER);
+        mWifiNetworkFactory.addCallback(mNetworkRequestMatchCallback);
         verify(mNetworkRequestMatchCallback).onUserSelectionCallbackRegistration(any());
 
         NetworkRequest oldRequest = new NetworkRequest(mNetworkRequest);
@@ -1856,6 +2383,7 @@
         mLooper.dispatchAll();
 
         verify(mNetworkRequestMatchCallback).onAbort();
+        verify(mNetworkRequestMatchCallback, atLeastOnce()).asBinder();
         verify(mWifiScanner, times(2)).getSingleScanResults();
         verify(mWifiScanner, times(2)).startScan(any(), any(), any(), any());
         verify(mConnectivityManager).declareNetworkRequestUnfulfillable(eq(oldRequest));
@@ -1863,13 +2391,11 @@
         // Remove the stale request1 & ensure nothing happens.
         mWifiNetworkFactory.releaseNetworkFor(oldRequest);
 
-        verifyNoMoreInteractions(mWifiConnectivityManager, mWifiScanner, mClientModeImpl,
+        verifyNoMoreInteractions(mWifiConnectivityManager, mWifiScanner, mClientModeManager,
                 mAlarmManager, mNetworkRequestMatchCallback);
 
-        // Remove the active request2 & ensure auto-join is re-enabled.
+        // Remove the active request2.
         mWifiNetworkFactory.releaseNetworkFor(mNetworkRequest);
-
-        verify(mWifiConnectivityManager).setSpecificNetworkRequestInProgress(false);
     }
 
     /**
@@ -1890,12 +2416,17 @@
         attachDefaultWifiNetworkSpecifierAndAppInfo(TEST_UID_2, false);
         mWifiNetworkFactory.needNetworkFor(mNetworkRequest);
 
+        // Ensure we don't request a new ClientModeManager.
+        verify(mActiveModeWarden, never()).requestLocalOnlyClientModeManager(
+                any(), any(), any(), any());
+
         // Ignore stale callbacks.
         WifiConfiguration selectedNetwork = WifiConfigurationTestUtil.createOpenNetwork();
         sendUserSelectionSelect(networkRequestUserSelectionCallback, selectedNetwork);
         mLooper.dispatchAll();
 
         verify(mNetworkRequestMatchCallback).onAbort();
+        verify(mNetworkRequestMatchCallback, atLeastOnce()).asBinder();
         verify(mWifiScanner, times(2)).getSingleScanResults();
         verify(mWifiScanner, times(2)).startScan(any(), any(), any(), any());
         verify(mAlarmManager).cancel(mPeriodicScanListenerArgumentCaptor.getValue());
@@ -1904,13 +2435,11 @@
         // Remove the stale request1 & ensure nothing happens.
         mWifiNetworkFactory.releaseNetworkFor(oldRequest);
 
-        verifyNoMoreInteractions(mWifiConnectivityManager, mWifiScanner, mClientModeImpl,
+        verifyNoMoreInteractions(mWifiConnectivityManager, mWifiScanner, mClientModeManager,
                 mAlarmManager, mNetworkRequestMatchCallback);
 
-        // Remove the active request2 & ensure auto-join is re-enabled.
+        // Remove the active request2.
         mWifiNetworkFactory.releaseNetworkFor(mNetworkRequest);
-
-        verify(mWifiConnectivityManager).setSpecificNetworkRequestInProgress(false);
     }
 
     /**
@@ -1927,22 +2456,29 @@
         attachDefaultWifiNetworkSpecifierAndAppInfo(TEST_UID_2, false);
         mWifiNetworkFactory.needNetworkFor(mNetworkRequest);
 
+        // Ensure we don't request a new ClientModeManager.
+        verify(mActiveModeWarden, times(1)).requestLocalOnlyClientModeManager(
+                any(), any(), any(), any());
+
         verify(mNetworkRequestMatchCallback).onAbort();
+        verify(mNetworkRequestMatchCallback, atLeastOnce()).asBinder();
         verify(mWifiConnectivityManager, times(1)).setSpecificNetworkRequestInProgress(true);
         verify(mWifiScanner, times(2)).getSingleScanResults();
         verify(mWifiScanner, times(2)).startScan(any(), any(), any(), any());
         verify(mAlarmManager).cancel(mConnectionTimeoutAlarmListenerArgumentCaptor.getValue());
+        verify(mClientModeManager, times(3)).getRole();
 
         // Remove the stale request1 & ensure nothing happens.
         mWifiNetworkFactory.releaseNetworkFor(oldRequest);
 
-        verifyNoMoreInteractions(mWifiConnectivityManager, mWifiScanner, mClientModeImpl,
+        verifyNoMoreInteractions(mWifiConnectivityManager, mWifiScanner, mClientModeManager,
                 mAlarmManager, mNetworkRequestMatchCallback);
 
         // Remove the active request2 & ensure auto-join is re-enabled.
         mWifiNetworkFactory.releaseNetworkFor(mNetworkRequest);
 
         verify(mWifiConnectivityManager).setSpecificNetworkRequestInProgress(false);
+        verify(mActiveModeWarden).removeClientModeManager(any());
     }
 
     /**
@@ -1957,38 +2493,70 @@
         // Send network connection success indication.
         assertNotNull(mSelectedNetwork);
         mWifiNetworkFactory.handleConnectionAttemptEnded(
-                WifiMetrics.ConnectionEvent.FAILURE_NONE, mSelectedNetwork);
+                WifiMetrics.ConnectionEvent.FAILURE_NONE, mSelectedNetwork, TEST_BSSID_1);
         // Cancel the connection timeout.
         verify(mAlarmManager).cancel(mConnectionTimeoutAlarmListenerArgumentCaptor.getValue());
+        verify(mClientModeManager).enableRoaming(false);
 
         NetworkRequest oldRequest = new NetworkRequest(mNetworkRequest);
         // Send second request.
         attachDefaultWifiNetworkSpecifierAndAppInfo(TEST_UID_2, false);
         mWifiNetworkFactory.needNetworkFor(mNetworkRequest);
 
+        // Ensure we don't request a new ClientModeManager.
+        verify(mActiveModeWarden, times(1)).requestLocalOnlyClientModeManager(
+                any(), any(), any(), any());
+
         verify(mWifiConnectivityManager, times(1)).setSpecificNetworkRequestInProgress(true);
         verify(mWifiScanner, times(2)).getSingleScanResults();
         verify(mWifiScanner, times(2)).startScan(any(), any(), any(), any());
         // we shouldn't disconnect until the user accepts the next request.
-        verify(mClientModeImpl, times(1)).disconnectCommand();
+        verify(mClientModeManager, times(1)).disconnect();
 
         // Remove the connected request1 & ensure we disconnect.
         mWifiNetworkFactory.releaseNetworkFor(oldRequest);
-        verify(mClientModeImpl, times(2)).disconnectCommand();
+        verify(mClientModeManager, times(2)).disconnect();
+        verify(mClientModeManager, times(3)).getRole();
 
-        verifyNoMoreInteractions(mWifiConnectivityManager, mWifiScanner, mClientModeImpl,
+        verifyNoMoreInteractions(mWifiConnectivityManager, mWifiScanner, mClientModeManager,
                 mAlarmManager);
 
         // Now remove the active request2 & ensure auto-join is re-enabled.
         mWifiNetworkFactory.releaseNetworkFor(mNetworkRequest);
 
+        verify(mClientModeManager, times(3)).getRole();
         verify(mWifiConnectivityManager).setSpecificNetworkRequestInProgress(false);
+        verify(mClientModeManager).enableRoaming(true);
+        verify(mActiveModeWarden).removeClientModeManager(any());
 
-        verifyNoMoreInteractions(mWifiConnectivityManager, mWifiScanner, mClientModeImpl,
+        verifyNoMoreInteractions(mWifiConnectivityManager, mWifiScanner, mClientModeManager,
                 mAlarmManager);
     }
 
     /**
+     *  Verify handling for same network request while connected to it.
+     */
+    @Test
+    public void testHandleSameNetworkRequestWithSpecifierAfterConnectionSuccess() throws Exception {
+        sendNetworkRequestAndSetupForConnectionStatus();
+
+        // Send network connection success indication.
+        assertNotNull(mSelectedNetwork);
+        mWifiNetworkFactory.handleConnectionAttemptEnded(
+                WifiMetrics.ConnectionEvent.FAILURE_NONE, mSelectedNetwork, TEST_BSSID_1);
+        // Cancel the connection timeout.
+        verify(mAlarmManager).cancel(mConnectionTimeoutAlarmListenerArgumentCaptor.getValue());
+
+        clearInvocations(mWifiConnectivityManager, mWifiScanner, mClientModeManager, mAlarmManager);
+
+        // Send same request again (nothing should happen).
+        mWifiNetworkFactory.needNetworkFor(mNetworkRequest);
+        mLooper.dispatchAll();
+
+        verifyNoMoreInteractions(mWifiConnectivityManager, mWifiScanner, mClientModeManager,
+                mAlarmManager);
+    }
+    /**
      *  Verify handling for new network request while processing another one.
      */
     @Test
@@ -2001,9 +2569,10 @@
         // Send network connection success indication.
         assertNotNull(mSelectedNetwork);
         mWifiNetworkFactory.handleConnectionAttemptEnded(
-                WifiMetrics.ConnectionEvent.FAILURE_NONE, mSelectedNetwork);
+                WifiMetrics.ConnectionEvent.FAILURE_NONE, mSelectedNetwork, TEST_BSSID_1);
         // Cancel the connection timeout.
         verify(mAlarmManager).cancel(mConnectionTimeoutAlarmListenerArgumentCaptor.getValue());
+        verify(mClientModeManager).enableRoaming(false);
 
         // Send second request & we simulate the user selecting the request & connecting to it.
         reset(mNetworkRequestMatchCallback, mWifiScanner, mAlarmManager);
@@ -2012,29 +2581,34 @@
                 (WifiNetworkSpecifier) mNetworkRequest.networkCapabilities.getNetworkSpecifier();
         assertNotNull(mSelectedNetwork);
         mWifiNetworkFactory.handleConnectionAttemptEnded(
-                WifiMetrics.ConnectionEvent.FAILURE_NONE, mSelectedNetwork);
+                WifiMetrics.ConnectionEvent.FAILURE_NONE, mSelectedNetwork, TEST_BSSID_2);
+        verify(mClientModeManager, times(2)).enableRoaming(false);
         // Cancel the connection timeout.
         verify(mAlarmManager).cancel(mConnectionTimeoutAlarmListenerArgumentCaptor.getValue());
 
         // We shouldn't explicitly disconnect, the new connection attempt will implicitly disconnect
         // from the connected network.
-        verify(mClientModeImpl, times(2)).disconnectCommand();
+        verify(mClientModeManager, times(2)).disconnect();
+        verify(mClientModeManager, times(6)).getRole();
 
         // Remove the stale request1 & ensure nothing happens (because it was replaced by the
         // second request)
         mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier1);
         mWifiNetworkFactory.releaseNetworkFor(mNetworkRequest);
 
-        verifyNoMoreInteractions(mWifiConnectivityManager, mWifiScanner, mClientModeImpl,
+        verifyNoMoreInteractions(mWifiConnectivityManager, mWifiScanner, mClientModeManager,
                 mAlarmManager);
 
         // Now remove the rejected request2, ensure we disconnect & re-enable auto-join.
         mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier2);
         mWifiNetworkFactory.releaseNetworkFor(mNetworkRequest);
-        verify(mClientModeImpl, times(3)).disconnectCommand();
+        verify(mClientModeManager, times(3)).disconnect();
+        verify(mClientModeManager, times(6)).getRole();
         verify(mWifiConnectivityManager).setSpecificNetworkRequestInProgress(false);
+        verify(mClientModeManager).enableRoaming(true);
+        verify(mActiveModeWarden).removeClientModeManager(any());
 
-        verifyNoMoreInteractions(mWifiConnectivityManager, mWifiScanner, mClientModeImpl,
+        verifyNoMoreInteractions(mWifiConnectivityManager, mWifiScanner, mClientModeManager,
                 mAlarmManager);
     }
 
@@ -2051,7 +2625,7 @@
         // Send network connection success indication.
         assertNotNull(mSelectedNetwork);
         mWifiNetworkFactory.handleConnectionAttemptEnded(
-                WifiMetrics.ConnectionEvent.FAILURE_NONE, mSelectedNetwork);
+                WifiMetrics.ConnectionEvent.FAILURE_NONE, mSelectedNetwork, TEST_BSSID_1);
         // Cancel the connection timeout.
         verify(mAlarmManager).cancel(mConnectionTimeoutAlarmListenerArgumentCaptor.getValue());
 
@@ -2064,25 +2638,30 @@
         mLooper.dispatchAll();
         // cancel periodic scans.
         verify(mAlarmManager).cancel(mPeriodicScanListenerArgumentCaptor.getValue());
+        // Verify we disabled fw roaming.
+        verify(mClientModeManager).enableRoaming(false);
 
         // we shouldn't disconnect/re-enable auto-join until the connected request is released.
         verify(mWifiConnectivityManager, never()).setSpecificNetworkRequestInProgress(false);
-        verify(mClientModeImpl, times(1)).disconnectCommand();
+        verify(mClientModeManager, times(1)).disconnect();
 
         // Remove the connected request1 & ensure we disconnect & ensure auto-join is re-enabled.
         mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier1);
         mWifiNetworkFactory.releaseNetworkFor(mNetworkRequest);
-        verify(mClientModeImpl, times(2)).disconnectCommand();
+        verify(mClientModeManager, times(2)).disconnect();
+        verify(mClientModeManager, times(3)).getRole();
         verify(mWifiConnectivityManager).setSpecificNetworkRequestInProgress(false);
+        verify(mClientModeManager).enableRoaming(true);
+        verify(mActiveModeWarden).removeClientModeManager(any());
 
-        verifyNoMoreInteractions(mWifiConnectivityManager, mWifiScanner, mClientModeImpl,
+        verifyNoMoreInteractions(mWifiConnectivityManager, mWifiScanner, mClientModeManager,
                 mAlarmManager);
 
         // Now remove the rejected request2 & ensure nothing happens
         mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier2);
         mWifiNetworkFactory.releaseNetworkFor(mNetworkRequest);
 
-        verifyNoMoreInteractions(mWifiConnectivityManager, mWifiScanner, mClientModeImpl,
+        verifyNoMoreInteractions(mWifiConnectivityManager, mWifiScanner, mClientModeManager,
                 mAlarmManager);
     }
 
@@ -2095,7 +2674,7 @@
         sendNetworkRequestAndSetupForUserSelection();
 
         // Turn off screen.
-        mWifiNetworkFactory.handleScreenStateChanged(false);
+        setScreenState(false);
 
         // 1. Cancel the scan timer.
         mInOrder.verify(mAlarmManager).cancel(
@@ -2107,7 +2686,7 @@
         mInOrder.verifyNoMoreInteractions();
 
         // Now, turn the screen on.
-        mWifiNetworkFactory.handleScreenStateChanged(true);
+        setScreenState(true);
 
         // Verify that we resumed periodic scanning.
         mInOrder.verify(mWifiScanner).startScan(any(), any(), any(), any());
@@ -2127,10 +2706,10 @@
                 mPeriodicScanListenerArgumentCaptor.getValue());
 
         // Turn off screen.
-        mWifiNetworkFactory.handleScreenStateChanged(false);
+        setScreenState(false);
 
         // Now, turn the screen on.
-        mWifiNetworkFactory.handleScreenStateChanged(true);
+        setScreenState(true);
 
         // Ensure that we did not pause or resume scanning.
         mInOrder.verifyNoMoreInteractions();
@@ -2144,48 +2723,30 @@
         sendNetworkRequestAndSetupForConnectionStatus();
 
         // Turn off screen.
-        mWifiNetworkFactory.handleScreenStateChanged(false);
+        setScreenState(false);
 
         // Now, turn the screen on.
-        mWifiNetworkFactory.handleScreenStateChanged(true);
+        setScreenState(true);
 
         // Ensure that we did not pause or resume scanning.
         mInOrder.verifyNoMoreInteractions();
     }
 
     /**
-     * Verify we don't accept specific network request when wifi is off.
-     */
-    @Test
-    public void testHandleAcceptNetworkRequestWithSpecifierWhenWifiOff() throws Exception {
-        when(mActivityManager.getPackageImportance(TEST_PACKAGE_NAME_1))
-                .thenReturn(IMPORTANCE_FOREGROUND);
-
-        attachDefaultWifiNetworkSpecifierAndAppInfo(TEST_UID_1, false);
-
-        // set wifi off.
-        mWifiNetworkFactory.setWifiState(false);
-        assertFalse(mWifiNetworkFactory.acceptRequest(mNetworkRequest));
-
-        // set wifi on.
-        mWifiNetworkFactory.setWifiState(true);
-        assertTrue(mWifiNetworkFactory.acceptRequest(mNetworkRequest));
-    }
-
-    /**
      * Verify handling of new network request with network specifier when wifi is off.
+     * The request should be rejected immediately.
      */
     @Test
     public void testHandleNetworkRequestWithSpecifierWhenWifiOff() {
         attachDefaultWifiNetworkSpecifierAndAppInfo(TEST_UID_1, false);
 
-        // set wifi off
-        mWifiNetworkFactory.setWifiState(false);
+        // wifi off
+        when(mActiveModeWarden.hasPrimaryClientModeManager()).thenReturn(false);
         mWifiNetworkFactory.needNetworkFor(mNetworkRequest);
         verify(mWifiScanner, never()).startScan(any(), any(), any(), any());
 
-        // set wifi on
-        mWifiNetworkFactory.setWifiState(true);
+        // wifi on
+        when(mActiveModeWarden.hasPrimaryClientModeManager()).thenReturn(true);
         mWifiNetworkFactory.needNetworkFor(mNetworkRequest);
         verify(mWifiScanner).startScan(any(), any(), any(), any());
     }
@@ -2195,20 +2756,15 @@
      */
     @Test
     public void testHandleNetworkRequestWithSpecifierWifiOffWhenScanning() throws Exception {
-        attachDefaultWifiNetworkSpecifierAndAppInfo(TEST_UID_1, false);
-        mWifiNetworkFactory.needNetworkFor(mNetworkRequest);
+        sendNetworkRequestAndSetupForUserSelection();
 
-        // Register callback.
-        mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
-                TEST_CALLBACK_IDENTIFIER);
-        verify(mNetworkRequestMatchCallback).onUserSelectionCallbackRegistration(any());
-
-        verify(mWifiScanner).startScan(any(), any(), any(), any());
-
-        // toggle wifi off & verify we aborted ongoing request.
-        mWifiNetworkFactory.setWifiState(false);
+        // toggle wifi off & verify we aborted ongoing request (CMM not retrieved yet).
+        when(mActiveModeWarden.hasPrimaryClientModeManager()).thenReturn(false);
+        when(mClientModeManager.getRole()).thenReturn(null); // Role returned is null on removal.
+        mModeChangeCallbackCaptor.getValue().onActiveModeManagerRemoved(
+                mock(ConcreteClientModeManager.class));
+        mLooper.dispatchAll();
         verify(mNetworkRequestMatchCallback).onAbort();
-        verify(mWifiConnectivityManager).setSpecificNetworkRequestInProgress(false);
     }
 
     /**
@@ -2216,13 +2772,19 @@
      */
     @Test
     public void testHandleNetworkRequestWithSpecifierWifiOffAfterConnect() throws Exception {
+        mWifiNetworkFactory.enableVerboseLogging(true);
         sendNetworkRequestAndSetupForConnectionStatus();
 
         // toggle wifi off & verify we aborted ongoing request.
-        mWifiNetworkFactory.setWifiState(false);
+        when(mClientModeManager.getRole()).thenReturn(null); // Role returned is null on removal.
+        mModeChangeCallbackCaptor.getValue().onActiveModeManagerRemoved(mClientModeManager);
+        mLooper.dispatchAll();
+
         verify(mAlarmManager).cancel(mConnectionTimeoutAlarmListenerArgumentCaptor.getValue());
         verify(mNetworkRequestMatchCallback).onAbort();
         verify(mWifiConnectivityManager).setSpecificNetworkRequestInProgress(false);
+        verify(mConnectivityManager).declareNetworkRequestUnfulfillable(eq(mNetworkRequest));
+        verify(mActiveModeWarden).removeClientModeManager(any());
     }
 
     /**
@@ -2234,13 +2796,12 @@
         attachDefaultWifiNetworkSpecifierAndAppInfo(TEST_UID_1, false);
 
         // wifi off
-        mWifiNetworkFactory.setWifiState(false);
+        when(mActiveModeWarden.hasPrimaryClientModeManager()).thenReturn(false);
         // Add the request, should do nothing.
         mWifiNetworkFactory.needNetworkFor(mNetworkRequest);
         mLooper.dispatchAll();
         verify(mWifiScanner, never()).startScan(any(), any(), any(), any());
-        // TODO: Send an immediate failure when wifi is off.
-        // verify(mConnectivityManager).declareNetworkRequestUnfulfillable(eq(mNetworkRequest));
+        verify(mConnectivityManager).declareNetworkRequestUnfulfillable(eq(mNetworkRequest));
     }
 
     /**
@@ -2253,8 +2814,10 @@
         // 1. First request (no user approval bypass)
         sendNetworkRequestAndSetupForConnectionStatus();
 
-        mWifiNetworkFactory.removeCallback(TEST_CALLBACK_IDENTIFIER);
-        reset(mNetworkRequestMatchCallback, mWifiScanner, mAlarmManager, mClientModeImpl);
+        mWifiNetworkFactory.removeCallback(mNetworkRequestMatchCallback);
+        mWifiNetworkFactory.releaseNetworkFor(mNetworkRequest);
+        reset(mNetworkRequestMatchCallback, mWifiScanner, mAlarmManager, mClientModeManager,
+                mConnectHelper);
 
         // 2. Simulate user forgeting the network.
         when(mWifiConfigManager.isNetworkTemporarilyDisabledByUser(
@@ -2272,19 +2835,18 @@
                 ssidPatternMatch, bssidPatternMatch,
                 WifiConfigurationTestUtil.createPskNetwork(), TEST_UID_1, TEST_PACKAGE_NAME_1);
         mWifiNetworkFactory.needNetworkFor(mNetworkRequest);
-        mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
-                TEST_CALLBACK_IDENTIFIER);
+        when(mNetworkRequestMatchCallback.asBinder()).thenReturn(mAppBinder);
+        mWifiNetworkFactory.addCallback(mNetworkRequestMatchCallback);
         verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS);
         ArgumentCaptor<List<ScanResult>> matchedScanResultsCaptor =
                 ArgumentCaptor.forClass(List.class);
         // Verify we triggered the match callback.
-        matchedScanResultsCaptor = ArgumentCaptor.forClass(List.class);
         verify(mNetworkRequestMatchCallback).onMatch(matchedScanResultsCaptor.capture());
         assertNotNull(matchedScanResultsCaptor.getValue());
         validateScanResults(matchedScanResultsCaptor.getValue(), matchingScanResult);
-        // Verify that we did not send a connection attempt to ClientModeImpl.
-        verify(mClientModeImpl, never()).connect(eq(null), anyInt(),
-                any(Binder.class), mConnectListenerArgumentCaptor.capture(), anyInt(), anyInt());
+        // Verify that we did not send a connection attempt to ModeImplProxy.
+        verify(mConnectHelper, never()).connectToNetwork(any(), any(),
+                mConnectListenerArgumentCaptor.capture(), anyInt());
     }
 
     /**
@@ -2297,8 +2859,10 @@
         // 1. First request (no user approval bypass)
         sendNetworkRequestAndSetupForConnectionStatus();
 
-        mWifiNetworkFactory.removeCallback(TEST_CALLBACK_IDENTIFIER);
-        reset(mNetworkRequestMatchCallback, mWifiScanner, mAlarmManager, mClientModeImpl);
+        mWifiNetworkFactory.removeCallback(mNetworkRequestMatchCallback);
+        mWifiNetworkFactory.releaseNetworkFor(mNetworkRequest);
+        reset(mNetworkRequestMatchCallback, mWifiScanner, mAlarmManager, mClientModeManager,
+                mConnectHelper);
 
         // 2. Second request for a different access point (but same network).
         setupScanData(SCAN_RESULT_TYPE_WPA_PSK,
@@ -2313,8 +2877,8 @@
                 ssidPatternMatch, bssidPatternMatch, WifiConfigurationTestUtil.createPskNetwork(),
                 TEST_UID_1, TEST_PACKAGE_NAME_1);
         mWifiNetworkFactory.needNetworkFor(mNetworkRequest);
-        mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
-                TEST_CALLBACK_IDENTIFIER);
+        when(mNetworkRequestMatchCallback.asBinder()).thenReturn(mAppBinder);
+        mWifiNetworkFactory.addCallback(mNetworkRequestMatchCallback);
         verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS);
         ArgumentCaptor<List<ScanResult>> matchedScanResultsCaptor =
                 ArgumentCaptor.forClass(List.class);
@@ -2323,9 +2887,9 @@
         verify(mNetworkRequestMatchCallback).onMatch(matchedScanResultsCaptor.capture());
         assertNotNull(matchedScanResultsCaptor.getValue());
         validateScanResults(matchedScanResultsCaptor.getValue(), matchingScanResult);
-        // Verify that we did not send a connection attempt to ClientModeImpl.
-        verify(mClientModeImpl, never()).connect(eq(null), anyInt(),
-                any(Binder.class), mConnectListenerArgumentCaptor.capture(), anyInt(), anyInt());
+        // Verify that we did not send a connection attempt to ClientModeManager.
+        verify(mConnectHelper, never()).connectToNetwork(any(), any(),
+                mConnectListenerArgumentCaptor.capture(), anyInt());
     }
 
     /**
@@ -2338,8 +2902,10 @@
         // 1. First request (no user approval bypass)
         sendNetworkRequestAndSetupForConnectionStatus();
 
-        mWifiNetworkFactory.removeCallback(TEST_CALLBACK_IDENTIFIER);
-        reset(mNetworkRequestMatchCallback, mWifiScanner, mAlarmManager, mClientModeImpl);
+        mWifiNetworkFactory.removeCallback(mNetworkRequestMatchCallback);
+        mWifiNetworkFactory.releaseNetworkFor(mNetworkRequest);
+        reset(mNetworkRequestMatchCallback, mWifiScanner, mAlarmManager, mClientModeManager,
+                mConnectHelper);
 
         // 2. Second request for the same network (but not specific access point)
         ScanResult matchingScanResult = mTestScanDatas[0].getResults()[0];
@@ -2352,8 +2918,8 @@
                 ssidPatternMatch, bssidPatternMatch, WifiConfigurationTestUtil.createPskNetwork(),
                 TEST_UID_1, TEST_PACKAGE_NAME_1);
         mWifiNetworkFactory.needNetworkFor(mNetworkRequest);
-        mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
-                TEST_CALLBACK_IDENTIFIER);
+        when(mNetworkRequestMatchCallback.asBinder()).thenReturn(mAppBinder);
+        mWifiNetworkFactory.addCallback(mNetworkRequestMatchCallback);
         verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS);
         ArgumentCaptor<List<ScanResult>> matchedScanResultsCaptor =
                 ArgumentCaptor.forClass(List.class);
@@ -2362,9 +2928,9 @@
         verify(mNetworkRequestMatchCallback).onMatch(matchedScanResultsCaptor.capture());
         assertNotNull(matchedScanResultsCaptor.getValue());
         validateScanResults(matchedScanResultsCaptor.getValue(), matchingScanResult);
-        // Verify that we did not send a connection attempt to ClientModeImpl.
-        verify(mClientModeImpl, never()).connect(eq(null), anyInt(),
-                any(Binder.class), mConnectListenerArgumentCaptor.capture(), anyInt(), anyInt());
+        // Verify that we did not send a connection attempt to ClientModeManager.
+        verify(mConnectHelper, never()).connectToNetwork(any(), any(),
+                mConnectListenerArgumentCaptor.capture(), anyInt());
     }
 
     /**
@@ -2378,8 +2944,10 @@
         // 1. First request (no user approval bypass)
         sendNetworkRequestAndSetupForConnectionStatus();
 
-        mWifiNetworkFactory.removeCallback(TEST_CALLBACK_IDENTIFIER);
-        reset(mNetworkRequestMatchCallback, mWifiScanner, mAlarmManager, mClientModeImpl);
+        mWifiNetworkFactory.removeCallback(mNetworkRequestMatchCallback);
+        mWifiNetworkFactory.releaseNetworkFor(mNetworkRequest);
+        reset(mNetworkRequestMatchCallback, mWifiScanner, mAlarmManager, mClientModeManager,
+                mConnectHelper);
 
         // 2. Remove all approvals for the app.
         mWifiNetworkFactory.removeUserApprovedAccessPointsForApp(TEST_PACKAGE_NAME_1);
@@ -2395,8 +2963,8 @@
                 ssidPatternMatch, bssidPatternMatch, WifiConfigurationTestUtil.createPskNetwork(),
                 TEST_UID_1, TEST_PACKAGE_NAME_1);
         mWifiNetworkFactory.needNetworkFor(mNetworkRequest);
-        mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
-                TEST_CALLBACK_IDENTIFIER);
+        when(mNetworkRequestMatchCallback.asBinder()).thenReturn(mAppBinder);
+        mWifiNetworkFactory.addCallback(mNetworkRequestMatchCallback);
         verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS);
         ArgumentCaptor<List<ScanResult>> matchedScanResultsCaptor =
                 ArgumentCaptor.forClass(List.class);
@@ -2405,9 +2973,9 @@
         verify(mNetworkRequestMatchCallback).onMatch(matchedScanResultsCaptor.capture());
         assertNotNull(matchedScanResultsCaptor.getValue());
         validateScanResults(matchedScanResultsCaptor.getValue(), matchingScanResult);
-        // Verify that we did not send a connection attempt to ClientModeImpl.
-        verify(mClientModeImpl, never()).connect(eq(null), anyInt(),
-                any(Binder.class), mConnectListenerArgumentCaptor.capture(), anyInt(), anyInt());
+        // Verify that we did not send a connection attempt to ClientModeManager.
+        verify(mConnectHelper, never()).connectToNetwork(any(), any(),
+                mConnectListenerArgumentCaptor.capture(), anyInt());
     }
 
     /**
@@ -2420,8 +2988,10 @@
         // 1. First request (no user approval bypass)
         sendNetworkRequestAndSetupForConnectionStatus();
 
-        mWifiNetworkFactory.removeCallback(TEST_CALLBACK_IDENTIFIER);
-        reset(mNetworkRequestMatchCallback, mWifiScanner, mAlarmManager, mClientModeImpl);
+        mWifiNetworkFactory.removeCallback(mNetworkRequestMatchCallback);
+        mWifiNetworkFactory.releaseNetworkFor(mNetworkRequest);
+        reset(mNetworkRequestMatchCallback, mWifiScanner, mAlarmManager, mClientModeManager,
+                mConnectHelper);
 
         // 2. Remove all approvals.
         mWifiNetworkFactory.clear();
@@ -2437,8 +3007,8 @@
                 ssidPatternMatch, bssidPatternMatch, WifiConfigurationTestUtil.createPskNetwork(),
                 TEST_UID_1, TEST_PACKAGE_NAME_1);
         mWifiNetworkFactory.needNetworkFor(mNetworkRequest);
-        mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
-                TEST_CALLBACK_IDENTIFIER);
+        when(mNetworkRequestMatchCallback.asBinder()).thenReturn(mAppBinder);
+        mWifiNetworkFactory.addCallback(mNetworkRequestMatchCallback);
         verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS);
         ArgumentCaptor<List<ScanResult>> matchedScanResultsCaptor =
                 ArgumentCaptor.forClass(List.class);
@@ -2447,9 +3017,9 @@
         verify(mNetworkRequestMatchCallback).onMatch(matchedScanResultsCaptor.capture());
         assertNotNull(matchedScanResultsCaptor.getValue());
         validateScanResults(matchedScanResultsCaptor.getValue(), matchingScanResult);
-        // Verify that we did not send a connection attempt to ClientModeImpl.
-        verify(mClientModeImpl, never()).connect(eq(null), anyInt(),
-                any(Binder.class), mConnectListenerArgumentCaptor.capture(), anyInt(), anyInt());
+        // Verify that we did not send a connection attempt to ClientModeManager.
+        verify(mConnectHelper, never()).connectToNetwork(any(), any(),
+                mConnectListenerArgumentCaptor.capture(), anyInt());
     }
 
     /**
@@ -2504,16 +3074,15 @@
                 ssidPatternMatch, bssidPatternMatch, WifiConfigurationTestUtil.createPskNetwork(),
                 TEST_UID_1, TEST_PACKAGE_NAME_1);
         mWifiNetworkFactory.needNetworkFor(mNetworkRequest);
-        mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
-                TEST_CALLBACK_IDENTIFIER);
+        mWifiNetworkFactory.addCallback(mNetworkRequestMatchCallback);
         // Ensure we triggered a connect without issuing any scans.
         verify(mWifiScanner, never()).startScan(any(), any(), any(), any());
 
         // Verify we did not trigger the match callback.
         verify(mNetworkRequestMatchCallback, never()).onMatch(anyList());
-        // Verify that we sent a connection attempt to ClientModeImpl
-        verify(mClientModeImpl).connect(eq(null), anyInt(),
-                any(Binder.class), mConnectListenerArgumentCaptor.capture(), anyInt(), anyInt());
+        // Verify that we sent a connection attempt to ClientModeManager
+        verify(mConnectHelper).connectToNetwork(eq(mClientModeManager),  any(),
+                mConnectListenerArgumentCaptor.capture(), anyInt());
     }
 
     /**
@@ -2555,8 +3124,9 @@
         // 1. First request (no user approval bypass)
         sendNetworkRequestAndSetupForConnectionStatus();
 
-        mWifiNetworkFactory.removeCallback(TEST_CALLBACK_IDENTIFIER);
-        reset(mNetworkRequestMatchCallback, mWifiScanner, mAlarmManager, mClientModeImpl);
+        mWifiNetworkFactory.removeCallback(mNetworkRequestMatchCallback);
+        reset(mNetworkRequestMatchCallback, mWifiScanner, mAlarmManager, mClientModeManager,
+                mConnectHelper);
 
         // 2. Second request for the same access point (user approval bypass).
         ScanResult matchingScanResult = mTestScanDatas[0].getResults()[0];
@@ -2577,9 +3147,9 @@
         verify(mWifiScanner, never()).startScan(any(), any(), any(), any());
         // Verify we did not trigger the match callback.
         verify(mNetworkRequestMatchCallback, never()).onMatch(anyList());
-        // Verify that we sent a connection attempt to ClientModeImpl
-        verify(mClientModeImpl).connect(eq(null), anyInt(),
-                any(Binder.class), mConnectListenerArgumentCaptor.capture(), anyInt(), anyInt());
+        // Verify that we sent a connection attempt to ClientModeManager
+        verify(mConnectHelper).connectToNetwork(eq(mClientModeManager),  any(),
+                mConnectListenerArgumentCaptor.capture(), anyInt());
 
         verify(mWifiMetrics).incrementNetworkRequestApiNumUserApprovalBypass();
     }
@@ -2621,9 +3191,9 @@
         verify(mWifiScanner, never()).startScan(any(), any(), any(), any());
         // Verify we did not trigger the match callback.
         verify(mNetworkRequestMatchCallback, never()).onMatch(anyList());
-        // Verify that we sent a connection attempt to ClientModeImpl
-        verify(mClientModeImpl).connect(eq(null), anyInt(),
-                any(Binder.class), mConnectListenerArgumentCaptor.capture(), anyInt(), anyInt());
+        // Verify that we sent a connection attempt to ClientModeManager
+        verify(mConnectHelper).connectToNetwork(eq(mClientModeManager),  any(),
+                mConnectListenerArgumentCaptor.capture(), anyInt());
 
         verify(mWifiMetrics).incrementNetworkRequestApiNumUserApprovalBypass();
     }
@@ -2663,9 +3233,9 @@
         verify(mWifiScanner, never()).startScan(any(), any(), any(), any());
         // Verify we did not trigger the match callback.
         verify(mNetworkRequestMatchCallback, never()).onMatch(anyList());
-        // Verify that we sent a connection attempt to ClientModeImpl
-        verify(mClientModeImpl).connect(eq(null), anyInt(),
-                any(Binder.class), mConnectListenerArgumentCaptor.capture(), anyInt(), anyInt());
+        // Verify that we sent a connection attempt to ClientModeManager
+        verify(mConnectHelper).connectToNetwork(eq(mClientModeManager),  any(),
+                mConnectListenerArgumentCaptor.capture(), anyInt());
 
         verify(mWifiMetrics).incrementNetworkRequestApiNumUserApprovalBypass();
     }
@@ -2687,7 +3257,7 @@
         Pair<MacAddress, MacAddress> bssidPatternMatch =
                 Pair.create(WifiManager.ALL_ZEROS_MAC_ADDRESS, WifiManager.ALL_ZEROS_MAC_ADDRESS);
         WifiConfiguration wifiConfiguration = new WifiConfiguration();
-        wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+        wifiConfiguration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
         wifiConfiguration.preSharedKey = TEST_WPA_PRESHARED_KEY;
         attachWifiNetworkSpecifierAndAppInfo(
                 ssidPatternMatch, bssidPatternMatch, wifiConfiguration, TEST_UID_1,
@@ -2702,8 +3272,7 @@
         verify(mNetworkRequestMatchCallback, never()).onMatch(any());
 
         // Register the callback & ensure we triggered the on match callback.
-        mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
-                TEST_CALLBACK_IDENTIFIER);
+        mWifiNetworkFactory.addCallback(mNetworkRequestMatchCallback);
         ArgumentCaptor<List<ScanResult>> matchedScanResultsCaptor =
                 ArgumentCaptor.forClass(List.class);
         verify(mNetworkRequestMatchCallback).onMatch(matchedScanResultsCaptor.capture());
@@ -2737,16 +3306,31 @@
         sendUserSelectionSelect(networkRequestUserSelectionCallback, mSelectedNetwork);
         mLooper.dispatchAll();
 
+        verify(mActiveModeWarden, atLeastOnce()).requestLocalOnlyClientModeManager(
+                any(), any(), any(), any());
+
         // Cancel the periodic scan timer.
         mInOrder.verify(mAlarmManager).cancel(mPeriodicScanListenerArgumentCaptor.getValue());
         // Disable connectivity manager
-        verify(mWifiConnectivityManager, atLeastOnce()).setSpecificNetworkRequestInProgress(true);
+        if (mClientModeManager.getRole() == ActiveModeManager.ROLE_CLIENT_PRIMARY) {
+            verify(mWifiConnectivityManager, atLeastOnce())
+                    .setSpecificNetworkRequestInProgress(true);
+        }
         // Increment the number of unique apps.
         verify(mWifiMetrics).incrementNetworkRequestApiNumApps();
 
-        verify(mClientModeImpl, atLeastOnce()).disconnectCommand();
-        verify(mClientModeImpl, atLeastOnce()).connect(eq(null), eq(TEST_NETWORK_ID_1),
-                any(Binder.class), mConnectListenerArgumentCaptor.capture(), anyInt(), anyInt());
+        verify(mClientModeManager, atLeastOnce()).disconnect();
+        verify(mConnectHelper, atLeastOnce()).connectToNetwork(
+                eq(mClientModeManager),
+                eq(new NetworkUpdateResult(TEST_NETWORK_ID_1)),
+                mConnectListenerArgumentCaptor.capture(), anyInt());
+        if (mClientModeManager.getRole() == ActiveModeManager.ROLE_CLIENT_PRIMARY) {
+            verify(mWifiMetrics, atLeastOnce())
+                    .incrementNetworkRequestApiNumConnectOnPrimaryIface();
+        } else {
+            verify(mWifiMetrics, atLeastOnce())
+                    .incrementNetworkRequestApiNumConnectOnSecondaryIface();
+        }
 
         // Start the connection timeout alarm.
         mInOrder.verify(mAlarmManager).set(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
@@ -2780,8 +3364,8 @@
 
         validateUiStartParams(true);
 
-        mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
-                TEST_CALLBACK_IDENTIFIER);
+        when(mNetworkRequestMatchCallback.asBinder()).thenReturn(mAppBinder);
+        mWifiNetworkFactory.addCallback(mNetworkRequestMatchCallback);
         verify(mNetworkRequestMatchCallback).onUserSelectionCallbackRegistration(
                 mNetworkRequestUserSelectionCallback.capture());
 
@@ -2882,7 +3466,8 @@
         mNetworkCapabilities.setRequestorUid(uid);
         mNetworkCapabilities.setRequestorPackageName(packageName);
         mNetworkCapabilities.setNetworkSpecifier(
-                new WifiNetworkSpecifier(ssidPatternMatch, bssidPatternMatch, wifiConfiguration));
+                new WifiNetworkSpecifier(ssidPatternMatch, bssidPatternMatch,
+                        ScanResult.UNSPECIFIED, wifiConfiguration));
         mNetworkRequest = new NetworkRequest.Builder()
                 .setCapabilities(mNetworkCapabilities)
                 .build();
@@ -2971,8 +3556,10 @@
             }
 
             // Trigger new connection.
-            mInOrder.verify(mClientModeImpl).connect(eq(null), eq(TEST_NETWORK_ID_1),
-                    any(Binder.class), mConnectListenerArgumentCaptor.capture(), anyInt(),
+            mInOrder.verify(mConnectHelper).connectToNetwork(
+                    eq(mClientModeManager),
+                    eq(new NetworkUpdateResult(TEST_NETWORK_ID_1)),
+                    mConnectListenerArgumentCaptor.capture(),
                     anyInt());
 
             // Start the new connection timeout alarm.
@@ -3012,7 +3599,7 @@
         @Override
         public boolean matches(WifiConfiguration otherConfig) {
             if (otherConfig == null) return false;
-            return mConfig.getKey().equals(otherConfig.getKey());
+            return mConfig.getProfileKey().equals(otherConfig.getProfileKey());
         }
     }
 
@@ -3115,4 +3702,11 @@
         selectedNetworkinCb.SSID = selectedNetwork.SSID;
         callback.select(selectedNetworkinCb);
     }
+
+    private void setScreenState(boolean screenOn) {
+        BroadcastReceiver broadcastReceiver = mBroadcastReceiverCaptor.getValue();
+        assertNotNull(broadcastReceiver);
+        Intent intent = new Intent(screenOn  ? ACTION_SCREEN_ON : ACTION_SCREEN_OFF);
+        broadcastReceiver.onReceive(mContext, intent);
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTest.java
index 1507e9c..368aad4 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTest.java
@@ -17,10 +17,13 @@
 package com.android.server.wifi;
 
 import static android.net.wifi.WifiManager.WIFI_FEATURE_OWE;
+import static android.net.wifi.WifiManager.WIFI_FEATURE_WPA3_SAE;
 
 import static com.android.server.wifi.WifiConfigurationTestUtil.SECURITY_EAP;
 import static com.android.server.wifi.WifiConfigurationTestUtil.SECURITY_NONE;
+import static com.android.server.wifi.WifiConfigurationTestUtil.SECURITY_OWE;
 import static com.android.server.wifi.WifiConfigurationTestUtil.SECURITY_PSK;
+import static com.android.server.wifi.WifiConfigurationTestUtil.SECURITY_SAE;
 import static com.android.server.wifi.WifiNetworkSelector.experimentIdFromIdentifier;
 
 import static org.hamcrest.Matchers.*;
@@ -32,15 +35,15 @@
 import android.net.wifi.ScanResult;
 import android.net.wifi.SupplicantState;
 import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
 import android.net.wifi.WifiInfo;
 import android.os.SystemClock;
 import android.util.LocalLog;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.server.wifi.WifiNetworkSelector.ClientModeManagerState;
 import com.android.server.wifi.WifiNetworkSelectorTestUtil.ScanDetailsAndWifiConfigs;
-import com.android.server.wifi.hotspot2.NetworkDetail;
 import com.android.server.wifi.proto.nano.WifiMetricsProto;
 import com.android.wifi.resources.R;
 
@@ -50,12 +53,15 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
 import org.mockito.Spy;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Unit tests for {@link com.android.server.wifi.WifiNetworkSelector}.
@@ -63,42 +69,59 @@
 @SmallTest
 public class WifiNetworkSelectorTest extends WifiBaseTest {
     private static final int RSSI_BUMP = 1;
-    private static final int DUMMY_NOMINATOR_ID_1 = -2; // lowest index
-    private static final int DUMMY_NOMINATOR_ID_2 = -1;
+    private static final int PLACEHOLDER_NOMINATOR_ID_1 = -2; // lowest index
+    private static final int PLACEHOLDER_NOMINATOR_ID_2 = -1;
     private static final int WAIT_JUST_A_MINUTE = 60_000;
-    private static final HashSet<String> EMPTY_BLACKLIST = new HashSet<>();
+    private static final HashSet<String> EMPTY_BLOCKLIST = new HashSet<>();
+    private static final String TEST_IFACE_NAME = "mockWlan0";
+    private static final String TEST_IFACE_NAME_SECONDARY = "mockWlan1";
+    private static final String TEST_AUTO_UPGRADE_SSID = "\"auto-upgrade-network\"";
+
+    private MockitoSession mSession;
 
     /** Sets up test. */
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        // static mocking
+        mSession = ExtendedMockito.mockitoSession()
+                .mockStatic(WifiInjector.class, withSettings().lenient())
+                .startMocking();
+        when(WifiInjector.getInstance()).thenReturn(mWifiInjector);
         setupContext();
         setupResources();
         setupWifiConfigManager();
         setupWifiInfo();
+        setupWifiGlobals();
 
         mScoringParams = new ScoringParams();
         setupThresholds();
 
         mLocalLog = new LocalLog(512);
 
-        mWifiNetworkSelector = new WifiNetworkSelector(mContext,
+        mWifiNetworkSelector = new WifiNetworkSelector(
+                mContext,
                 mWifiScoreCard,
                 mScoringParams,
                 mWifiConfigManager, mClock,
                 mLocalLog,
                 mWifiMetrics,
-                mWifiNative,
-                mThroughputPredictor);
+                mWifiInjector,
+                mThroughputPredictor,
+                mWifiChannelUtilization,
+                mWifiGlobals,
+                mScanRequestProxy);
 
-        mWifiNetworkSelector.registerNetworkNominator(mDummyNominator);
-        mDummyNominator.setNominatorToSelectCandidate(true);
+        mWifiNetworkSelector.registerNetworkNominator(mPlaceholderNominator);
+        mPlaceholderNominator.setNominatorToSelectCandidate(true);
         when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime());
         when(mWifiScoreCard.lookupBssid(any(), any())).thenReturn(mPerBssid);
         mCompatibilityScorer = new CompatibilityScorer(mScoringParams);
         mScoreCardBasedScorer = new ScoreCardBasedScorer(mScoringParams);
         mThroughputScorer = new ThroughputScorer(mScoringParams);
-        when(mWifiNative.getClientInterfaceName()).thenReturn("wlan0");
+        when(mWifiInjector.getActiveModeWarden()).thenReturn(mActiveModeWarden);
+        when(mWifiInjector.getWifiGlobals()).thenReturn(mWifiGlobals);
+        when(mActiveModeWarden.getPrimaryClientModeManager()).thenReturn(mClientModeManager);
         if (WifiNetworkSelector.PRESET_CANDIDATE_SCORER_NAME.equals(
                 mThroughputScorer.getIdentifier())) {
             mWifiNetworkSelector.registerCandidateScorer(mThroughputScorer);
@@ -111,6 +134,9 @@
     @After
     public void cleanup() {
         validateMockitoUsage();
+        if (mSession != null) {
+            mSession.finishMocking();
+        }
     }
 
     /**
@@ -139,8 +165,8 @@
 
         @Override
         public void nominateNetworks(List<ScanDetail> scanDetails,
-                WifiConfiguration currentNetwork, String currentBssid, boolean connected,
-                boolean untrustedNetworkAllowed,
+                boolean untrustedNetworkAllowed, boolean oemPaidNetworkAllowed,
+                boolean oemPrivateNetworkAllowed,
                 @NonNull OnConnectableListener onConnectableListener) {
             List<ScanDetail> myScanDetails = mScanDetailsAndWifiConfigs.getScanDetails();
             WifiConfiguration[] configs = mScanDetailsAndWifiConfigs.getWifiConfigs();
@@ -152,23 +178,24 @@
 
 
     /**
-     * All this dummy network Nominator does is to pick the specified network in the scan results.
+     * All this placeholder does is to pick the specified network in the scan results.
      */
-    public class DummyNetworkNominator implements WifiNetworkSelector.NetworkNominator {
-        private static final String NAME = "DummyNetworkNominator";
+    public class PlaceholderNominator implements WifiNetworkSelector.NetworkNominator {
+        public static final int RETURN_ALL_INDEX = -1;
+        private static final String NAME = "PlaceholderNominator";
 
         private boolean mNominatorShouldSelectCandidate = true;
 
         private int mNetworkIndexToReturn;
         private int mNominatorIdToReturn;
 
-        public DummyNetworkNominator(int networkIndexToReturn, int nominatorIdToReturn) {
+        public PlaceholderNominator(int networkIndexToReturn, int nominatorIdToReturn) {
             mNetworkIndexToReturn = networkIndexToReturn;
             mNominatorIdToReturn = nominatorIdToReturn;
         }
 
-        public DummyNetworkNominator() {
-            this(0, DUMMY_NOMINATOR_ID_1);
+        public PlaceholderNominator() {
+            this(0, PLACEHOLDER_NOMINATOR_ID_1);
         }
 
         public int getNetworkIndexToReturn() {
@@ -208,36 +235,39 @@
          */
         @Override
         public void nominateNetworks(List<ScanDetail> scanDetails,
-                    WifiConfiguration currentNetwork, String currentBssid, boolean connected,
-                    boolean untrustedNetworkAllowed,
+                    boolean untrustedNetworkAllowed, boolean oemPaidNetworkAllowed,
+                    boolean oemPrivateNetworkAllowed,
                     @NonNull OnConnectableListener onConnectableListener) {
             if (!mNominatorShouldSelectCandidate) {
                 return;
             }
-            for (ScanDetail scanDetail : scanDetails) {
+            for (int index = 0; index < scanDetails.size(); index++) {
+                ScanDetail scanDetail = scanDetails.get(index);
                 WifiConfiguration config =
-                        mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(scanDetail);
+                        mWifiConfigManager.getSavedNetworkForScanDetailAndCache(scanDetail);
                 mWifiConfigManager.setNetworkCandidateScanResult(
-                        config.networkId, scanDetail.getScanResult(), 100);
+                        config.networkId, scanDetail.getScanResult(), 100, null);
+                if (RETURN_ALL_INDEX == mNetworkIndexToReturn || index == mNetworkIndexToReturn) {
+                    WifiConfiguration configToReturn  =
+                            mWifiConfigManager.getSavedNetworkForScanDetailAndCache(scanDetail);
+                    assertNotNull("Saved network must not be null", configToReturn);
+                    onConnectableListener.onConnectable(scanDetail, configToReturn);
+                }
             }
-            ScanDetail scanDetailToReturn = scanDetails.get(mNetworkIndexToReturn);
-            WifiConfiguration configToReturn  =
-                    mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
-                            scanDetailToReturn);
-            assertNotNull("Saved network must not be null", configToReturn);
-            onConnectableListener.onConnectable(scanDetailToReturn, configToReturn);
         }
     }
 
     private WifiNetworkSelector mWifiNetworkSelector = null;
-    private DummyNetworkNominator mDummyNominator = new DummyNetworkNominator();
+    private PlaceholderNominator mPlaceholderNominator = new PlaceholderNominator();
     @Mock private WifiConfigManager mWifiConfigManager;
     @Mock private Context mContext;
     @Mock private WifiScoreCard mWifiScoreCard;
     @Mock private WifiScoreCard.PerBssid mPerBssid;
     @Mock private WifiCandidates.CandidateScorer mCandidateScorer;
     @Mock private WifiMetrics mWifiMetrics;
-    @Mock private WifiNative mWifiNative;
+    @Mock private WifiInjector mWifiInjector;
+    @Mock private ActiveModeWarden mActiveModeWarden;
+    @Mock private ClientModeManager mClientModeManager;
     @Mock private WifiNetworkSelector.NetworkNominator mNetworkNominator;
 
     // For simulating the resources, we use a Spy on a MockResource
@@ -245,9 +275,12 @@
     // This is so that we get errors on any calls that we have not explicitly set up.
     @Spy private MockResources mResource = new MockResources();
     @Mock private WifiInfo mWifiInfo;
+    @Mock private WifiInfo mSecondaryWifiInfo;
     @Mock private Clock mClock;
-    @Mock private NetworkDetail mNetworkDetail;
     @Mock private ThroughputPredictor mThroughputPredictor;
+    @Mock private WifiChannelUtilization mWifiChannelUtilization;
+    @Mock private WifiGlobals mWifiGlobals;
+    @Mock private ScanRequestProxy mScanRequestProxy;
     private ScoringParams mScoringParams;
     private LocalLog mLocalLog;
     private int mThresholdMinimumRssi2G;
@@ -272,8 +305,6 @@
     private void setupResources() {
         doReturn(true).when(mResource).getBoolean(
                 R.bool.config_wifi_framework_enable_associated_network_selection);
-        mMinPacketRateActiveTraffic = setupIntegerResource(
-                R.integer.config_wifiFrameworkMinPacketPerSecondActiveTraffic, 16);
         mSufficientDurationAfterUserSelection = setupIntegerResource(
                 R.integer.config_wifiSufficientDurationAfterUserSelectionMilliseconds,
                 WAIT_JUST_A_MINUTE);
@@ -289,6 +320,7 @@
                 ScanResult.BAND_24_GHZ_START_FREQ_MHZ);
         mThresholdQualifiedRssi5G = mScoringParams.getSufficientRssi(
                 ScanResult.BAND_5_GHZ_START_FREQ_MHZ);
+        mMinPacketRateActiveTraffic = mScoringParams.getActiveTrafficPacketsPerSecond();
     }
 
     private void setupWifiInfo() {
@@ -302,6 +334,12 @@
         when(mWifiInfo.getBSSID()).thenReturn(null);
     }
 
+    private void setupWifiGlobals() {
+        when(mWifiGlobals.isWpa3SaeUpgradeEnabled()).thenReturn(true);
+        when(mWifiGlobals.isWpa3SaeUpgradeOffloadEnabled()).thenReturn(true);
+        when(mWifiGlobals.isOweUpgradeEnabled()).thenReturn(true);
+    }
+
     private void setupWifiConfigManager() {
         setupWifiConfigManager(WifiConfiguration.INVALID_NETWORK_ID);
     }
@@ -332,9 +370,11 @@
                 WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
                     freqs, caps, levels, securities, mWifiConfigManager, mClock);
         List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
-        HashSet<String> blacklist = new HashSet<String>();
+        HashSet<String> blocklist = new HashSet<String>();
         List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
-                scanDetails, blacklist, mWifiInfo, false, true, false);
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
         WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(candidates);
         assertEquals("Expect null configuration", null, candidate);
         assertTrue(mWifiNetworkSelector.getConnectableScanDetails().isEmpty());
@@ -355,7 +395,7 @@
         String[] ssids = {"\"test1\"", "\"test2\""};
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2437, 5180};
-        String[] caps = {"[WPA2-PSK][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-PSK][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {mThresholdMinimumRssi2G - 1, mThresholdMinimumRssi5G - 1};
         int[] securities = {SECURITY_PSK, SECURITY_EAP};
 
@@ -363,9 +403,11 @@
                 WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
                     freqs, caps, levels, securities, mWifiConfigManager, mClock);
         List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
-        HashSet<String> blacklist = new HashSet<String>();
+        HashSet<String> blocklist = new HashSet<String>();
         List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
-                scanDetails, blacklist, mWifiInfo, false, true, false);
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
         WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(candidates);
         assertEquals("Expect null configuration", null, candidate);
         assertTrue(mWifiNetworkSelector.getConnectableScanDetails().isEmpty());
@@ -386,7 +428,7 @@
         String[] ssids = {"\"test1\"", "\"test2\""};
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2437, 5180};
-        String[] caps = {"[WPA2-PSK][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-PSK][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {mThresholdMinimumRssi2G + RSSI_BUMP, mThresholdMinimumRssi5G + RSSI_BUMP};
         int[] securities = {SECURITY_PSK, SECURITY_EAP};
 
@@ -395,9 +437,11 @@
                 WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
                         freqs, caps, levels, securities, mWifiConfigManager, mClock);
         List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
-        HashSet<String> blacklist = new HashSet<String>();
+        HashSet<String> blocklist = new HashSet<String>();
         List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
-                scanDetails, blacklist, mWifiInfo, false, true, false);
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
         WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(candidates);
 
         when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime()
@@ -405,13 +449,16 @@
 
         // Do another network selection with CMI in CONNECTED state.
         candidates = mWifiNetworkSelector.getCandidatesFromScan(
-                scanDetails, blacklist, mWifiInfo, true, false, false);
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, true, false, mWifiInfo)),
+                false, true, true);
         candidate = mWifiNetworkSelector.selectNetwork(candidates);
 
         assertEquals("Expect null configuration", null, candidate);
         assertTrue(mWifiNetworkSelector.getConnectableScanDetails().isEmpty());
 
-        verify(mWifiConfigManager, atLeast(2)).updateScanDetailCacheFromScanDetail(any());
+        verify(mWifiConfigManager, atLeast(2))
+                .updateScanDetailCacheFromScanDetailForSavedNetwork(any());
     }
 
     /**
@@ -429,7 +476,7 @@
         String[] ssids = {"\"test1\"", "\"test2\""};
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2437, 5180};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {mThresholdMinimumRssi2G + RSSI_BUMP, mThresholdMinimumRssi5G + RSSI_BUMP};
         int[] securities = {SECURITY_EAP, SECURITY_EAP};
 
@@ -439,9 +486,11 @@
                     freqs, caps, levels, securities, mWifiConfigManager, mClock);
         List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
         WifiConfiguration[] savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
-        HashSet<String> blacklist = new HashSet<String>();
+        HashSet<String> blocklist = new HashSet<String>();
         List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
-                scanDetails, blacklist, mWifiInfo, false, true, false);
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
         WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(candidates);
         WifiConfigurationTestUtil.assertConfigurationEqual(savedConfigs[0], candidate);
 
@@ -450,7 +499,9 @@
 
         // Do another network selection with CMI in DISCONNECTED state.
         candidates = mWifiNetworkSelector.getCandidatesFromScan(
-                scanDetails, blacklist, mWifiInfo, false, true, false);
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
         candidate = mWifiNetworkSelector.selectNetwork(candidates);
 
         ScanResult chosenScanResult = scanDetails.get(0).getScanResult();
@@ -482,12 +533,14 @@
                 WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
                     freqs, caps, levels, securities, mWifiConfigManager, mClock);
         List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
-        HashSet<String> blacklist = new HashSet<String>();
+        HashSet<String> blocklist = new HashSet<String>();
         WifiConfiguration[] savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
 
         // connect to test1
         List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
-                scanDetails, blacklist, mWifiInfo, false, true, false);
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
         WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(candidates);
         when(mWifiInfo.getSupplicantState()).thenReturn(SupplicantState.COMPLETED);
         when(mWifiInfo.getNetworkId()).thenReturn(0);
@@ -502,7 +555,9 @@
 
         // Do another network selection.
         candidates = mWifiNetworkSelector.getCandidatesFromScan(
-                scanDetails, blacklist, mWifiInfo, true, false, false);
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, true, false, mWifiInfo)),
+                false, true, true);
         candidate = mWifiNetworkSelector.selectNetwork(candidates);
 
         ScanResult chosenScanResult = scanDetails.get(0).getScanResult();
@@ -533,12 +588,14 @@
                 WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
                         freqs, caps, levels, securities, mWifiConfigManager, mClock);
         List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
-        HashSet<String> blacklist = new HashSet<String>();
+        HashSet<String> blocklist = new HashSet<String>();
         WifiConfiguration[] savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
 
         // connect to test1
         List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
-                scanDetails, blacklist, mWifiInfo, false, true, false);
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
         WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(candidates);
         when(mWifiInfo.getSupplicantState()).thenReturn(SupplicantState.COMPLETED);
         when(mWifiInfo.getNetworkId()).thenReturn(0);
@@ -560,7 +617,9 @@
 
         // Do another network selection.
         candidates = mWifiNetworkSelector.getCandidatesFromScan(
-                scanDetails, blacklist, mWifiInfo, true, false, false);
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, true, false, mWifiInfo)),
+                false, true, true);
         candidate = mWifiNetworkSelector.selectNetwork(candidates);
 
         ScanResult chosenScanResult = scanDetails.get(0).getScanResult();
@@ -578,7 +637,7 @@
         String[] ssids = {"\"test1\"", "\"test2\""};
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2437, 2457};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-PSK][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-PSK][ESS]"};
         int[] levels = {mThresholdMinimumRssi2G + 20, mThresholdMinimumRssi2G + RSSI_BUMP};
         int[] securities = {SECURITY_EAP, SECURITY_PSK};
 
@@ -586,12 +645,14 @@
                 WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
                         freqs, caps, levels, securities, mWifiConfigManager, mClock);
         List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
-        HashSet<String> blacklist = new HashSet<String>();
+        HashSet<String> blocklist = new HashSet<String>();
         WifiConfiguration[] savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
 
         // Do network selection.
         List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
-                scanDetails, blacklist, mWifiInfo, true, false, false);
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, true, false, mWifiInfo)),
+                false, true, true);
         WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(candidates);
         verify(mWifiMetrics).incrementNetworkSelectionFilteredBssidCount(0);
 
@@ -603,15 +664,15 @@
     }
 
     /**
-     * Blacklisted BSSID is filtered out for network selection.
+     * Blocklisted BSSID is filtered out for network selection.
      *
      * ClientModeImpl is disconnected.
-     * scanDetails contains a network which is blacklisted.
+     * scanDetails contains a network which is blocklisted.
      *
      * Expected behavior: no network recommended by Network Selector
      */
     @Test
-    public void filterOutBlacklistedBssid() {
+    public void filterOutBlocklistedBssid() {
         String[] ssids = {"\"test1\""};
         String[] bssids = {"6c:f3:7f:ae:8c:f3"};
         int[] freqs = {5180};
@@ -623,11 +684,13 @@
                 WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
                     freqs, caps, levels, securities, mWifiConfigManager, mClock);
         List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
-        HashSet<String> blacklist = new HashSet<String>();
-        blacklist.add(bssids[0]);
+        HashSet<String> blocklist = new HashSet<String>();
+        blocklist.add(bssids[0]);
 
         List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
-                scanDetails, blacklist, mWifiInfo, false, true, false);
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
         WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(candidates);
         verify(mWifiMetrics).incrementNetworkSelectionFilteredBssidCount(1);
         assertEquals("Expect null configuration", null, candidate);
@@ -649,7 +712,7 @@
         String[] ssids = {"\"test1\"", "\"test2\""};
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2437, 2457};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-PSK][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-PSK][ESS]"};
         int[] levels = {mThresholdMinimumRssi2G + 20, mThresholdMinimumRssi2G + RSSI_BUMP};
         int[] securities = {SECURITY_EAP, SECURITY_PSK};
 
@@ -658,9 +721,11 @@
                 WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
                     freqs, caps, levels, securities, mWifiConfigManager, mClock);
         List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
-        HashSet<String> blacklist = new HashSet<String>();
+        HashSet<String> blocklist = new HashSet<String>();
         List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
-                scanDetails, blacklist, mWifiInfo, false, true, false);
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
         WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(candidates);
 
         when(mWifiInfo.getSupplicantState()).thenReturn(SupplicantState.COMPLETED);
@@ -678,66 +743,22 @@
         String[] ssidsNew = {"\"test2\""};
         String[] bssidsNew = {"6c:f3:7f:ae:8c:f4"};
         int[] freqsNew = {2457};
-        String[] capsNew = {"[WPA2-EAP-CCMP][ESS]"};
+        String[] capsNew = {"[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levelsNew = {mThresholdMinimumRssi2G + 40};
         scanDetails = WifiNetworkSelectorTestUtil.buildScanDetails(ssidsNew, bssidsNew,
                 freqsNew, capsNew, levelsNew, mClock);
         candidates = mWifiNetworkSelector.getCandidatesFromScan(
-                scanDetails, blacklist, mWifiInfo, true, false, false);
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, true, false, mWifiInfo)),
+                false, true, true);
         candidate = mWifiNetworkSelector.selectNetwork(candidates);
 
         // The second network selection is skipped since current connected network is
         // missing from the scan results.
         assertEquals("Expect null configuration", null, candidate);
         assertTrue(mWifiNetworkSelector.getConnectableScanDetails().isEmpty());
-        verify(mWifiConfigManager, atLeast(1)).updateScanDetailCacheFromScanDetail(any());
-    }
-
-    /**
-     * Ensures that setting the user connect choice updates the
-     * NetworkSelectionStatus#mConnectChoice for all other WifiConfigurations in range in the last
-     * round of network selection.
-     *
-     * Expected behavior: WifiConfiguration.NetworkSelectionStatus#mConnectChoice is set to
-     *                    test1's configkey for test2. test3's WifiConfiguration is unchanged.
-     */
-    @Test
-    public void setUserConnectChoice() {
-        String[] ssids = {"\"test1\"", "\"test2\"", "\"test3\""};
-        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4", "6c:f3:7f:ae:8c:f5"};
-        int[] freqs = {2437, 5180, 5181};
-        String[] caps = {"[WPA2-PSK][ESS]", "[WPA2-EAP-CCMP][ESS]", "[WPA2-PSK][ESS]"};
-        int[] levels = {mThresholdMinimumRssi2G + RSSI_BUMP, mThresholdMinimumRssi5G + RSSI_BUMP,
-                mThresholdMinimumRssi5G + RSSI_BUMP};
-        int[] securities = {SECURITY_PSK, SECURITY_EAP, SECURITY_PSK};
-
-        ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
-                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
-                        freqs, caps, levels, securities, mWifiConfigManager, mClock);
-
-        WifiConfiguration selectedWifiConfig = scanDetailsAndConfigs.getWifiConfigs()[0];
-        selectedWifiConfig.getNetworkSelectionStatus()
-                .setCandidate(scanDetailsAndConfigs.getScanDetails().get(0).getScanResult());
-        selectedWifiConfig.getNetworkSelectionStatus().setNetworkSelectionStatus(
-                NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED);
-        selectedWifiConfig.getNetworkSelectionStatus().setConnectChoice("bogusKey");
-
-        WifiConfiguration configInLastNetworkSelection = scanDetailsAndConfigs.getWifiConfigs()[1];
-        configInLastNetworkSelection.getNetworkSelectionStatus()
-                .setSeenInLastQualifiedNetworkSelection(true);
-
-        WifiConfiguration configNotInLastNetworkSelection =
-                scanDetailsAndConfigs.getWifiConfigs()[2];
-
-        assertTrue(mWifiNetworkSelector.setUserConnectChoice(selectedWifiConfig.networkId));
-
-        verify(mWifiConfigManager).updateNetworkSelectionStatus(selectedWifiConfig.networkId,
-                NetworkSelectionStatus.DISABLED_NONE);
-        verify(mWifiConfigManager).clearNetworkConnectChoice(selectedWifiConfig.networkId);
-        verify(mWifiConfigManager).setNetworkConnectChoice(configInLastNetworkSelection.networkId,
-                selectedWifiConfig.getKey());
-        verify(mWifiConfigManager, never()).setNetworkConnectChoice(
-                configNotInLastNetworkSelection.networkId, selectedWifiConfig.getKey());
+        verify(mWifiConfigManager, atLeast(1))
+                .updateScanDetailCacheFromScanDetailForSavedNetwork(any());
     }
 
     /**
@@ -749,7 +770,7 @@
         String[] ssids = {"\"test1\"", "\"test2\""};
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2437, 5120};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-PSK][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-PSK][ESS]"};
         int[] levels = {mThresholdMinimumRssi2G + 10, mThresholdMinimumRssi2G + 20};
         int[] securities = {SECURITY_EAP, SECURITY_PSK};
         // VHT cap IE
@@ -765,7 +786,9 @@
         assertEquals(2, scanDetails.size());
         HashSet<String> blocklist = new HashSet<String>();
         List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
-                scanDetails, blocklist, mWifiInfo, false, true, false);
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
         WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(candidates);
 
         when(mWifiInfo.getSupplicantState()).thenReturn(SupplicantState.COMPLETED);
@@ -775,17 +798,24 @@
         when(mWifiInfo.getScore()).thenReturn(ConnectedScore.WIFI_TRANSITION_SCORE);
         when(mWifiInfo.is5GHz()).thenReturn(false);
         when(mWifiInfo.getFrequency()).thenReturn(2400);
-        when(mWifiInfo.getRssi()).thenReturn(levels[0]);
+        when(mWifiInfo.getRssi()).thenReturn(mThresholdMinimumRssi2G - 1);
+        when(mWifiInfo.getSuccessfulTxPacketsPerSecond())
+                .thenReturn(mMinPacketRateActiveTraffic - 1.0);
+        when(mWifiInfo.getSuccessfulRxPacketsPerSecond())
+                .thenReturn(mMinPacketRateActiveTraffic - 1.0);
+
         when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime()
                 + WifiNetworkSelector.MINIMUM_NETWORK_SELECTION_INTERVAL_MS + 2000);
 
         when(mThroughputPredictor.predictThroughput(any(), anyInt(), anyInt(), anyInt(),
                 anyInt(), anyInt(), anyInt(), anyInt(), anyBoolean())).thenReturn(100);
         // Force to return 2nd network in the network nominator
-        mDummyNominator.setNetworkIndexToReturn(1);
+        mPlaceholderNominator.setNetworkIndexToReturn(1);
 
         candidates = mWifiNetworkSelector.getCandidatesFromScan(
-                scanDetails, blocklist, mWifiInfo, true, false, false);
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, true, false, mWifiInfo)),
+                false, true, true);
         assertEquals(2, candidates.size());
         assertEquals(100, candidates.get(0).getPredictedThroughputMbps());
     }
@@ -805,31 +835,32 @@
         String[] caps = {"[WPA2-PSK][ESS]", "[WPA2-PSK][ESS]"};
         int[] levels = {mThresholdMinimumRssi2G + RSSI_BUMP, mThresholdMinimumRssi5G + RSSI_BUMP};
         int[] securities = {SECURITY_PSK, SECURITY_PSK};
-
         ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
                 WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
                         freqs, caps, levels, securities, mWifiConfigManager, mClock);
         List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
-        HashSet<String> blacklist = new HashSet<String>();
+        WifiConfiguration[] wifiConfigs = scanDetailsAndConfigs.getWifiConfigs();
+        HashSet<String> blocklist = new HashSet<>();
 
-        // DummyNominator always selects the first network in the list.
-        WifiConfiguration networkSelectorChoice = scanDetailsAndConfigs.getWifiConfigs()[0];
+        // PlaceholderNominator always selects the first network in the list.
+        WifiConfiguration networkSelectorChoice = wifiConfigs[0];
         networkSelectorChoice.getNetworkSelectionStatus()
                 .setSeenInLastQualifiedNetworkSelection(true);
 
-        WifiConfiguration userChoice = scanDetailsAndConfigs.getWifiConfigs()[1];
-        userChoice.getNetworkSelectionStatus()
-                .setCandidate(scanDetailsAndConfigs.getScanDetails().get(1).getScanResult());
+        WifiConfiguration userChoice = wifiConfigs[1];
+        userChoice.getNetworkSelectionStatus().setCandidate(scanDetails.get(1).getScanResult());
 
         // With no user choice set, networkSelectorChoice should be chosen.
         List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
-                scanDetails, blacklist, mWifiInfo, false, true, false);
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
         WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(candidates);
 
         ArgumentCaptor<Integer> nominatorIdCaptor = ArgumentCaptor.forClass(int.class);
         verify(mWifiMetrics, atLeastOnce()).setNominatorForNetwork(eq(candidate.networkId),
                 nominatorIdCaptor.capture());
-        // unknown because DummyNominator does not have a nominator ID
+        // unknown because PlaceholderNominator does not have a nominator ID
         // getValue() returns the argument from the *last* call
         assertEquals(WifiMetricsProto.ConnectionEvent.NOMINATOR_UNKNOWN,
                 nominatorIdCaptor.getValue().intValue());
@@ -839,11 +870,16 @@
         when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime()
                 + WifiNetworkSelector.MINIMUM_NETWORK_SELECTION_INTERVAL_MS + 2000);
 
-        assertTrue(mWifiNetworkSelector.setUserConnectChoice(userChoice.networkId));
+        // set user connect choice
+        userChoice.getNetworkSelectionStatus().setConnectChoice(null);
+        networkSelectorChoice.getNetworkSelectionStatus()
+                .setConnectChoice(userChoice.getProfileKey());
 
         // After user connect choice is set, userChoice should override networkSelectorChoice.
         candidates = mWifiNetworkSelector.getCandidatesFromScan(
-                scanDetails, blacklist, mWifiInfo, false, true, false);
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
         candidate = mWifiNetworkSelector.selectNetwork(candidates);
 
         verify(mWifiMetrics, atLeastOnce()).setNominatorForNetwork(eq(candidate.networkId),
@@ -855,17 +891,130 @@
     }
 
     /**
+     * Verify NetworkSelectionStatus#setConnectChoiceRssi(int rssi) causes the user connect choice
+     * logic to ignore connect choice networks with RSSI lower than the threshold set.
+     */
+    @Test
+    public void testUserConnectChoiceDoesNotOverrideWhenRssiLow() {
+        String[] ssids = {"\"test1\"", "\"test2\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {2437, 5180};
+        String[] caps = {"[WPA2-PSK][ESS]", "[WPA2-PSK][ESS]"};
+        int observedUserChoiceRssi = mThresholdMinimumRssi5G + RSSI_BUMP;
+        int[] levels = {mThresholdMinimumRssi2G + RSSI_BUMP, observedUserChoiceRssi};
+        int[] securities = {SECURITY_PSK, SECURITY_PSK};
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                        freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        WifiConfiguration[] wifiConfigs = scanDetailsAndConfigs.getWifiConfigs();
+        HashSet<String> blocklist = new HashSet<>();
+
+        // PlaceholderNominator should select the first network in the list.
+        WifiConfiguration networkSelectorChoice = wifiConfigs[0];
+        networkSelectorChoice.getNetworkSelectionStatus()
+                .setSeenInLastQualifiedNetworkSelection(true);
+
+        // But setup UCC so that the second network gets selected
+        WifiConfiguration userChoice = wifiConfigs[1];
+        userChoice.getNetworkSelectionStatus().setCandidate(scanDetails.get(1).getScanResult());
+        userChoice.getNetworkSelectionStatus().setConnectChoice(null);
+        networkSelectorChoice.getNetworkSelectionStatus()
+                .setConnectChoice(userChoice.getProfileKey());
+        networkSelectorChoice.getNetworkSelectionStatus()
+                .setConnectChoiceRssi(observedUserChoiceRssi
+                        + mScoringParams.getEstimateRssiErrorMargin());
+        assertEquals(observedUserChoiceRssi,
+                userChoice.getNetworkSelectionStatus().getCandidate().level);
+
+        // Verify that the user connect choice network is chosen.
+        List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
+        WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(candidates);
+        WifiConfigurationTestUtil.assertConfigurationEqual(userChoice, candidate);
+
+        // Now increase the connectChoiceRssi over the threshold and verify the user choice is no
+        // longer selected.
+        networkSelectorChoice.getNetworkSelectionStatus()
+                .setConnectChoiceRssi(observedUserChoiceRssi
+                        + mScoringParams.getEstimateRssiErrorMargin() + 1);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime()
+                + WifiNetworkSelector.MINIMUM_NETWORK_SELECTION_INTERVAL_MS + 2000);
+        candidates = mWifiNetworkSelector.getCandidatesFromScan(
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
+        candidate = mWifiNetworkSelector.selectNetwork(candidates);
+        WifiConfigurationTestUtil.assertConfigurationEqual(networkSelectorChoice, candidate);
+    }
+
+    /**
+     * Verify that the user connect choice algorithm does not choose a network that unexpectedly
+     * has no internet.
+     */
+    @Test
+    public void userConnectChoiceDoesNotOverrideWhenUnexpectedNoInternet() {
+        String[] ssids = {"\"test1\"", "\"test2\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {2437, 5180};
+        String[] caps = {"[WPA2-PSK][ESS]", "[WPA2-PSK][ESS]"};
+        int[] levels = {mThresholdMinimumRssi2G + RSSI_BUMP, mThresholdMinimumRssi5G + RSSI_BUMP};
+        int[] securities = {SECURITY_PSK, SECURITY_PSK};
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                        freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        WifiConfiguration[] wifiConfigs = scanDetailsAndConfigs.getWifiConfigs();
+        HashSet<String> blocklist = new HashSet<>();
+
+        // PlaceholderNominator should select the first network in the list.
+        WifiConfiguration networkSelectorChoice = wifiConfigs[0];
+        networkSelectorChoice.getNetworkSelectionStatus()
+                .setSeenInLastQualifiedNetworkSelection(true);
+
+        // But setup UCC so that the second network gets selected
+        WifiConfiguration userChoice = wifiConfigs[1];
+        userChoice.getNetworkSelectionStatus().setCandidate(scanDetails.get(1).getScanResult());
+        userChoice.getNetworkSelectionStatus().setConnectChoice(null);
+        networkSelectorChoice.getNetworkSelectionStatus()
+                .setConnectChoice(userChoice.getProfileKey());
+
+        // Verify that the user connect choice network is chosen.
+        List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
+        WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(candidates);
+        WifiConfigurationTestUtil.assertConfigurationEqual(userChoice, candidate);
+
+        // Now label the user connect choice network as unexpected no internet
+        userChoice.numNoInternetAccessReports = 1;
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime()
+                + WifiNetworkSelector.MINIMUM_NETWORK_SELECTION_INTERVAL_MS + 2000);
+        candidates = mWifiNetworkSelector.getCandidatesFromScan(
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
+        candidate = mWifiNetworkSelector.selectNetwork(candidates);
+
+        // Should now select the non user choice network.
+        WifiConfigurationTestUtil.assertConfigurationEqual(networkSelectorChoice, candidate);
+    }
+
+    /**
      * Tests when multiple Nominators nominate the same candidate, any one of the nominator IDs is
      * acceptable.
      */
     @Test
     public void testMultipleNominatorsSetsNominatorIdCorrectly() {
-        // first dummy Nominator is registered in setup, returns index 0
+        // first placeholder Nominator is registered in setup, returns index 0
         // register a second network Nominator that also returns index 0, but with a different ID
-        mWifiNetworkSelector.registerNetworkNominator(new DummyNetworkNominator(0,
+        mWifiNetworkSelector.registerNetworkNominator(new PlaceholderNominator(0,
                 WifiNetworkSelector.NetworkNominator.NOMINATOR_ID_SCORED));
         // register a third network Nominator that also returns index 0, but with a different ID
-        mWifiNetworkSelector.registerNetworkNominator(new DummyNetworkNominator(0,
+        mWifiNetworkSelector.registerNetworkNominator(new PlaceholderNominator(0,
                 WifiNetworkSelector.NetworkNominator.NOMINATOR_ID_SAVED));
 
         String[] ssids = {"\"test1\"", "\"test2\""};
@@ -879,9 +1028,9 @@
                 WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
                         freqs, caps, levels, securities, mWifiConfigManager, mClock);
         List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
-        HashSet<String> blacklist = new HashSet<>();
+        HashSet<String> blocklist = new HashSet<>();
 
-        // DummyNominator always selects the first network in the list.
+        // PlaceholderNominator always selects the first network in the list.
         WifiConfiguration networkSelectorChoice = scanDetailsAndConfigs.getWifiConfigs()[0];
         networkSelectorChoice.getNetworkSelectionStatus()
                 .setSeenInLastQualifiedNetworkSelection(true);
@@ -891,7 +1040,9 @@
                 .setCandidate(scanDetailsAndConfigs.getScanDetails().get(1).getScanResult());
 
         List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
-                scanDetails, blacklist, mWifiInfo, false, true, false);
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
         WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(candidates);
 
         ArgumentCaptor<Integer> nominatorIdCaptor = ArgumentCaptor.forClass(int.class);
@@ -908,17 +1059,19 @@
     }
 
     /**
-     * Wifi network selector performs network selection when current network has high
+     * Wifi network selector does not perform network selection when current network has high
      * quality but no active stream
      *
-     * Expected behavior: network selection is performed
+     * Expected behavior: network selection is skipped
      */
     @Test
-    public void testNoActiveStream() {
+    public void testHighRssiNoActiveStream() {
         // Rssi after connected.
         when(mWifiInfo.getRssi()).thenReturn(mThresholdQualifiedRssi2G + 1);
-        when(mWifiInfo.getSuccessfulTxPacketsPerSecond()).thenReturn(0.0);
-        when(mWifiInfo.getSuccessfulRxPacketsPerSecond()).thenReturn(0.0);
+        when(mWifiInfo.getSuccessfulTxPacketsPerSecond())
+                .thenReturn(mMinPacketRateActiveTraffic - 1.0);
+        when(mWifiInfo.getSuccessfulRxPacketsPerSecond())
+                .thenReturn(mMinPacketRateActiveTraffic - 1.0);
 
         testStayOrTryToSwitch(
                 // Parameters for network1:
@@ -931,11 +1084,41 @@
                 true /* a 5G network */,
                 false /* not open network */,
                 // Should try to switch.
+                false);
+    }
+
+    /**
+     * Wifi network selector performs network selection when current network has low
+     * quality and no active stream
+     *
+     * Expected behavior: network selection is performed
+     */
+    @Test
+    public void testLowRssiNoActiveStream() {
+        // Rssi after connected.
+        when(mWifiInfo.getRssi()).thenReturn(mThresholdQualifiedRssi2G - 1);
+        when(mWifiInfo.getSuccessfulTxPacketsPerSecond())
+                .thenReturn(mMinPacketRateActiveTraffic - 1.0);
+        when(mWifiInfo.getSuccessfulRxPacketsPerSecond())
+                .thenReturn(mMinPacketRateActiveTraffic - 1.0);
+
+        testStayOrTryToSwitch(
+                // Parameters for network1:
+                mThresholdQualifiedRssi2G - 1 /* rssi before connected */,
+                false /* not a 5G network */,
+                false /* not open network */,
+                false /* not a osu */,
+                // Parameters for network2:
+                mThresholdQualifiedRssi5G + 1 /* rssi */,
+                true /* a 5G network */,
+                false /* not open network */,
+                // Should try to switch.
                 true);
     }
 
     /**
      * Wifi network selector skips network selection when current network is osu and has low RSSI
+     * and low traffic.
      *
      * Expected behavior: network selection is skipped
      */
@@ -943,8 +1126,10 @@
     public void testOsuIsSufficient() {
         // Rssi after connected.
         when(mWifiInfo.getRssi()).thenReturn(mThresholdQualifiedRssi5G - 1);
-        when(mWifiInfo.getSuccessfulTxPacketsPerSecond()).thenReturn(0.0);
-        when(mWifiInfo.getSuccessfulRxPacketsPerSecond()).thenReturn(0.0);
+        when(mWifiInfo.getSuccessfulTxPacketsPerSecond())
+                .thenReturn(mMinPacketRateActiveTraffic - 1.0);
+        when(mWifiInfo.getSuccessfulRxPacketsPerSecond())
+                .thenReturn(mMinPacketRateActiveTraffic - 1.0);
 
         testStayOrTryToSwitch(
                 // Parameters for network1:
@@ -961,6 +1146,64 @@
     }
 
     /**
+     * Wifi network selector does network selection when current network is oem paid and has low
+     * RSSI
+     *
+     * Expected behavior: network selection is performed
+     */
+    @Test
+    public void testOemPaidIsNotSufficient() {
+        // Rssi after connected.
+        when(mWifiInfo.getRssi()).thenReturn(mThresholdQualifiedRssi5G - 1);
+        when(mWifiInfo.getSuccessfulTxPacketsPerSecond()).thenReturn(0.0);
+        when(mWifiInfo.getSuccessfulRxPacketsPerSecond()).thenReturn(0.0);
+
+        testStayOrTryToSwitch(
+                // Parameters for network1:
+                mThresholdQualifiedRssi5G - 1 /* rssi before connected */,
+                false /* not a 5G network */,
+                false /* not open network */,
+                false /* osu */,
+                true /* oem paid*/,
+                false /* oem private */,
+                // Parameters for network2:
+                mThresholdQualifiedRssi5G + 1 /* rssi */,
+                true /* a 5G network */,
+                false /* not open network */,
+                // Should not try to switch.
+                true);
+    }
+
+    /**
+     * Wifi network selector does network selection when current network is oem private and has low
+     * RSSI
+     *
+     * Expected behavior: network selection is performed
+     */
+    @Test
+    public void testOemPrivateIsNotSufficient() {
+        // Rssi after connected.
+        when(mWifiInfo.getRssi()).thenReturn(mThresholdQualifiedRssi5G - 1);
+        when(mWifiInfo.getSuccessfulTxPacketsPerSecond()).thenReturn(0.0);
+        when(mWifiInfo.getSuccessfulRxPacketsPerSecond()).thenReturn(0.0);
+
+        testStayOrTryToSwitch(
+                // Parameters for network1:
+                mThresholdQualifiedRssi5G - 1 /* rssi before connected */,
+                false /* not a 5G network */,
+                false /* not open network */,
+                false /* osu */,
+                false /* oem paid*/,
+                true /* oem private */,
+                // Parameters for network2:
+                mThresholdQualifiedRssi5G + 1 /* rssi */,
+                true /* a 5G network */,
+                false /* not open network */,
+                // Should not try to switch.
+                true);
+    }
+
+    /**
      * Wifi network selector will not perform network selection when current network has high
      * quality and active stream
      *
@@ -1013,13 +1256,14 @@
     }
 
     /**
-     * New network selection is performed if the currently connected network has bad rssi.
+     * New network selection is not performed if the currently connected network has bad rssi but
+     * active traffic.
      *
      * Expected behavior: Network Selector perform network selection after connected
      * to the first one.
      */
     @Test
-    public void testBadRssi() {
+    public void testBadRssiButHasActiveTraffic() {
         // Rssi after connected.
         when(mWifiInfo.getRssi()).thenReturn(mThresholdQualifiedRssi2G - 1);
         when(mWifiInfo.getSuccessfulTxPacketsPerSecond())
@@ -1032,7 +1276,7 @@
                 false /* not a 5G network */,
                 false /* not open network */,
                 // Should try to switch.
-                true);
+                false);
     }
 
     /**
@@ -1047,6 +1291,24 @@
             boolean isFirstNetworkOsu,
             int rssiNetwork2, boolean is5GHzNetwork2, boolean isOpenNetwork2,
             boolean shouldSelect) {
+        testStayOrTryToSwitch(rssiNetwork1, is5GHzNetwork1, isOpenNetwork1, isFirstNetworkOsu,
+                false, false, rssiNetwork2, is5GHzNetwork2, isOpenNetwork2,
+                shouldSelect);
+    }
+
+    /**
+     * This is a meta-test that given two scan results of various types, will
+     * determine whether or not network selection should be performed.
+     *
+     * It sets up two networks, connects to the first, and then ensures that
+     * both are available in the scan results for the NetworkSelector.
+     */
+    private void testStayOrTryToSwitch(
+            int rssiNetwork1, boolean is5GHzNetwork1, boolean isOpenNetwork1,
+            boolean isFirstNetworkOsu, boolean isFirstNetworkOemPaid,
+            boolean isFirtNetworkOemPrivate,
+            int rssiNetwork2, boolean is5GHzNetwork2, boolean isOpenNetwork2,
+            boolean shouldSelect) {
         String[] ssids = {"\"test1\"", "\"test2\""};
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {is5GHzNetwork1 ? 5180 : 2437, is5GHzNetwork2 ? 5180 : 2437};
@@ -1056,7 +1318,7 @@
         int[] securities = {isOpenNetwork1 ? SECURITY_NONE : SECURITY_PSK,
                             isOpenNetwork2 ? SECURITY_NONE : SECURITY_PSK};
         testStayOrTryToSwitchImpl(ssids, bssids, freqs, caps, levels, securities, isFirstNetworkOsu,
-                shouldSelect);
+                isFirstNetworkOemPaid, isFirtNetworkOemPrivate, shouldSelect);
     }
 
     /**
@@ -1067,31 +1329,32 @@
      * the scan results for the NetworkSelector.
      */
     private void testStayOrTryToSwitch(
-            int rssi, boolean is5GHz, boolean isOpenNetwork,
-            boolean shouldSelect) {
+            int rssi, boolean is5GHz, boolean isOpenNetwork, boolean shouldSelect) {
         String[] ssids = {"\"test1\""};
         String[] bssids = {"6c:f3:7f:ae:8c:f3"};
         int[] freqs = {is5GHz ? 5180 : 2437};
         String[] caps = {isOpenNetwork ? "[ESS]" : "[WPA2-PSK][ESS]"};
         int[] levels = {rssi};
         int[] securities = {isOpenNetwork ? SECURITY_NONE : SECURITY_PSK};
-        testStayOrTryToSwitchImpl(ssids, bssids, freqs, caps, levels, securities, false,
-                shouldSelect);
+        testStayOrTryToSwitchImpl(ssids, bssids, freqs, caps, levels, securities, false, false,
+                false, shouldSelect);
     }
 
     private void testStayOrTryToSwitchImpl(String[] ssids, String[] bssids, int[] freqs,
             String[] caps, int[] levels, int[] securities, boolean isFirstNetworkOsu,
-            boolean shouldSelect) {
+            boolean isFirstNetworkOemPaid, boolean isFirstNetworkOemPrivate, boolean shouldSelect) {
         // Make a network selection to connect to test1.
         ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
                 WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
                         freqs, caps, levels, securities, mWifiConfigManager, mClock);
         List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
-        HashSet<String> blacklist = new HashSet<String>();
-        // DummyNetworkNominator always return the first network in the scan results
+        HashSet<String> blocklist = new HashSet<String>();
+        // PlaceholderNominator always return the first network in the scan results
         // for connection, so this should connect to the first network.
         List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
-                scanDetails, blacklist, mWifiInfo, false, true, true);
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                true, true, true);
         WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(candidates);
         assertNotNull("Result should be not null", candidate);
         WifiNetworkSelectorTestUtil.verifySelectedScanResult(mWifiConfigManager,
@@ -1103,22 +1366,39 @@
         when(mWifiInfo.is24GHz()).thenReturn(!ScanResult.is5GHz(freqs[0]));
         when(mWifiInfo.is5GHz()).thenReturn(ScanResult.is5GHz(freqs[0]));
         when(mWifiInfo.getFrequency()).thenReturn(freqs[0]);
+
+        // Both of these should not be set.
+        assertFalse(isFirstNetworkOsu && isFirstNetworkOemPaid);
         if (isFirstNetworkOsu) {
             WifiConfiguration[] configs = scanDetailsAndConfigs.getWifiConfigs();
             // Force 1st network to OSU
             configs[0].osu = true;
             when(mWifiConfigManager.getConfiguredNetwork(mWifiInfo.getNetworkId()))
                     .thenReturn(configs[0]);
+        } else if (isFirstNetworkOemPaid) {
+            WifiConfiguration[] configs = scanDetailsAndConfigs.getWifiConfigs();
+            // Force 1st network to OEM paid
+            configs[0].oemPaid = true;
+            when(mWifiConfigManager.getConfiguredNetwork(mWifiInfo.getNetworkId()))
+                    .thenReturn(configs[0]);
+        } else if (isFirstNetworkOemPrivate) {
+            WifiConfiguration[] configs = scanDetailsAndConfigs.getWifiConfigs();
+            // Force 1st network to OEM paid
+            configs[0].oemPrivate = true;
+            when(mWifiConfigManager.getConfiguredNetwork(mWifiInfo.getNetworkId()))
+                    .thenReturn(configs[0]);
         }
 
         when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime()
                 + WifiNetworkSelector.MINIMUM_NETWORK_SELECTION_INTERVAL_MS + 2000);
 
         candidates = mWifiNetworkSelector.getCandidatesFromScan(
-                scanDetails, blacklist, mWifiInfo, true, false, false);
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, true, false, mWifiInfo)),
+                true, true, true);
         candidate = mWifiNetworkSelector.selectNetwork(candidates);
 
-        // DummyNetworkNominator always return the first network in the scan results
+        // PlaceholderNominator always return the first network in the scan results
         // for connection, so if network selection is performed, the first network should
         // be returned as candidate.
         if (shouldSelect) {
@@ -1141,16 +1421,18 @@
         String[] ssids = {"\"test1\"", "\"test2\""};
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2437, 5180};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[ESS]"};
         int[] levels = {mThresholdMinimumRssi2G + RSSI_BUMP, mThresholdMinimumRssi5G + RSSI_BUMP};
-        mDummyNominator.setNominatorToSelectCandidate(false);
+        mPlaceholderNominator.setNominatorToSelectCandidate(false);
 
         List<ScanDetail> scanDetails = WifiNetworkSelectorTestUtil.buildScanDetails(
                 ssids, bssids, freqs, caps, levels, mClock);
-        HashSet<String> blacklist = new HashSet<>();
+        HashSet<String> blocklist = new HashSet<>();
 
         List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
-                scanDetails, blacklist, mWifiInfo, false, true, false);
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
         WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(candidates);
         List<ScanDetail> expectedOpenUnsavedNetworks = new ArrayList<>();
         expectedOpenUnsavedNetworks.add(scanDetails.get(1));
@@ -1175,14 +1457,16 @@
         String[] caps = {"[ESS]"};
         int[] levels = {mThresholdMinimumRssi2G + RSSI_BUMP};
         int[] securities = {SECURITY_NONE};
-        mDummyNominator.setNominatorToSelectCandidate(false);
+        mPlaceholderNominator.setNominatorToSelectCandidate(false);
 
         List<ScanDetail> unSavedScanDetails = WifiNetworkSelectorTestUtil.buildScanDetails(
                 ssids, bssids, freqs, caps, levels, mClock);
-        HashSet<String> blacklist = new HashSet<>();
+        HashSet<String> blocklist = new HashSet<>();
 
         List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
-                unSavedScanDetails, blacklist, mWifiInfo, false, true, false);
+                unSavedScanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
         WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(candidates);
         assertEquals("Expect open unsaved networks",
                 unSavedScanDetails,
@@ -1194,7 +1478,9 @@
         List<ScanDetail> savedScanDetails = scanDetailsAndConfigs.getScanDetails();
 
         candidates = mWifiNetworkSelector.getCandidatesFromScan(
-                savedScanDetails, blacklist, mWifiInfo, false, true, false);
+                savedScanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
         candidate = mWifiNetworkSelector.selectNetwork(candidates);
         // Saved networks are filtered out.
         assertTrue(mWifiNetworkSelector.getFilteredScanDetailsForOpenUnsavedNetworks().isEmpty());
@@ -1202,26 +1488,28 @@
 
     /**
      * {@link WifiNetworkSelector#getFilteredScanDetailsForOpenUnsavedNetworks()} should filter out
-     * bssid blacklisted networks.
+     * bssid blocklisted networks.
      *
-     * Expected behavior: do not return blacklisted network
+     * Expected behavior: do not return blocklisted network
      */
     @Test
-    public void getfilterOpenUnsavedNetworks_filtersOutBlacklistedNetworks() {
+    public void getfilterOpenUnsavedNetworks_filtersOutBlocklistedNetworks() {
         String[] ssids = {"\"test1\"", "\"test2\""};
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2437, 5180};
         String[] caps = {"[ESS]", "[ESS]"};
         int[] levels = {mThresholdMinimumRssi2G + RSSI_BUMP, mThresholdMinimumRssi5G + RSSI_BUMP};
-        mDummyNominator.setNominatorToSelectCandidate(false);
+        mPlaceholderNominator.setNominatorToSelectCandidate(false);
 
         List<ScanDetail> scanDetails = WifiNetworkSelectorTestUtil.buildScanDetails(
                 ssids, bssids, freqs, caps, levels, mClock);
-        HashSet<String> blacklist = new HashSet<>();
-        blacklist.add(bssids[0]);
+        HashSet<String> blocklist = new HashSet<>();
+        blocklist.add(bssids[0]);
 
         List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
-                scanDetails, blacklist, mWifiInfo, false, true, false);
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
         WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(candidates);
         List<ScanDetail> expectedOpenUnsavedNetworks = new ArrayList<>();
         expectedOpenUnsavedNetworks.add(scanDetails.get(1));
@@ -1241,16 +1529,18 @@
         String[] ssids = {"\"test1\"", "\"test2\""};
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2437, 5180};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {mThresholdMinimumRssi2G + RSSI_BUMP, mThresholdMinimumRssi5G + RSSI_BUMP};
-        mDummyNominator.setNominatorToSelectCandidate(false);
+        mPlaceholderNominator.setNominatorToSelectCandidate(false);
 
         List<ScanDetail> scanDetails = WifiNetworkSelectorTestUtil.buildScanDetails(
                 ssids, bssids, freqs, caps, levels, mClock);
-        HashSet<String> blacklist = new HashSet<>();
+        HashSet<String> blocklist = new HashSet<>();
 
         List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
-                scanDetails, blacklist, mWifiInfo, false, true, false);
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
         WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(candidates);
         assertTrue(mWifiNetworkSelector.getFilteredScanDetailsForOpenUnsavedNetworks().isEmpty());
     }
@@ -1278,19 +1568,20 @@
         String[] ssids = {"\"test1\"", "\"test2\"", "\"test3\""};
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4", "6c:f3:7f:ae:8c:f5"};
         int[] freqs = {2437, 5180, 2414};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[ESS]", "[RSN-OWE-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[ESS]", "[RSN-OWE-CCMP][ESS]"};
         int[] levels = {mThresholdMinimumRssi2G, mThresholdMinimumRssi5G + RSSI_BUMP,
                 mThresholdMinimumRssi2G + RSSI_BUMP};
-        mDummyNominator.setNominatorToSelectCandidate(false);
-        when(mWifiNative.getSupportedFeatureSet(anyString()))
-                .thenReturn(new Long(WIFI_FEATURE_OWE));
+        mPlaceholderNominator.setNominatorToSelectCandidate(false);
+        when(mClientModeManager.getSupportedFeatures()).thenReturn(WIFI_FEATURE_OWE);
 
         List<ScanDetail> scanDetails = WifiNetworkSelectorTestUtil.buildScanDetails(
                 ssids, bssids, freqs, caps, levels, mClock);
-        HashSet<String> blacklist = new HashSet<>();
+        HashSet<String> blocklist = new HashSet<>();
 
         List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
-                scanDetails, blacklist, mWifiInfo, false, true, false);
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
         WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(candidates);
         List<ScanDetail> expectedOpenUnsavedNetworks = new ArrayList<>();
         expectedOpenUnsavedNetworks.add(scanDetails.get(1));
@@ -1312,19 +1603,20 @@
         String[] ssids = {"\"test1\"", "\"test2\"", "\"test3\""};
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4", "6c:f3:7f:ae:8c:f5"};
         int[] freqs = {2437, 5180, 2414};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[ESS]", "[RSN-OWE-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[ESS]", "[RSN-OWE-CCMP][ESS]"};
         int[] levels = {mThresholdMinimumRssi2G, mThresholdMinimumRssi5G + RSSI_BUMP,
                 mThresholdMinimumRssi2G + RSSI_BUMP};
-        mDummyNominator.setNominatorToSelectCandidate(false);
-        when(mWifiNative.getSupportedFeatureSet(anyString()))
-                .thenReturn(new Long(~WIFI_FEATURE_OWE));
+        mPlaceholderNominator.setNominatorToSelectCandidate(false);
+        when(mClientModeManager.getSupportedFeatures()).thenReturn(~WIFI_FEATURE_OWE);
 
         List<ScanDetail> scanDetails = WifiNetworkSelectorTestUtil.buildScanDetails(
                 ssids, bssids, freqs, caps, levels, mClock);
-        HashSet<String> blacklist = new HashSet<>();
+        HashSet<String> blocklist = new HashSet<>();
 
         List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
-                scanDetails, blacklist, mWifiInfo, false, true, false);
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
         WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(candidates);
         List<ScanDetail> expectedOpenUnsavedNetworks = new ArrayList<>();
         expectedOpenUnsavedNetworks.add(scanDetails.get(1));
@@ -1352,7 +1644,9 @@
 
         List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
                 setUpTwoNetworks(-35, -40),
-                EMPTY_BLACKLIST, mWifiInfo, false, true, true);
+                EMPTY_BLOCKLIST,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                true, true, true);
         WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(candidates);
 
         verify(mCandidateScorer, atLeastOnce()).scoreCandidates(any());
@@ -1363,7 +1657,7 @@
      */
     @Test
     public void testCandidateScorerMetrics_onlyOneScorer() {
-        testNoActiveStream();
+        testLowRssiNoActiveStream();
 
         verify(mWifiMetrics, never()).logNetworkSelectionDecision(
                 anyInt(), anyInt(), anyBoolean(), anyInt());
@@ -1406,17 +1700,17 @@
 
         // add a second NetworkNominator that returns the second network in the scan list
         mWifiNetworkSelector.registerNetworkNominator(
-                new DummyNetworkNominator(1, DUMMY_NOMINATOR_ID_2));
+                new PlaceholderNominator(1, PLACEHOLDER_NOMINATOR_ID_2));
 
         int compatibilityExpId = experimentIdFromIdentifier(mCompatibilityScorer.getIdentifier());
         mScoringParams.update("expid=" + compatibilityExpId);
         assertEquals(compatibilityExpId, mScoringParams.getExperimentIdentifier());
 
-        testNoActiveStream();
+        testLowRssiNoActiveStream();
 
         int nullScorerId = experimentIdFromIdentifier(NULL_SCORER.getIdentifier());
 
-        // Wanted 2 times since testNoActiveStream() calls
+        // Wanted 2 times since testLowRssiNoActiveStream() calls
         // WifiNetworkSelector.selectNetwork() twice
         verify(mWifiMetrics, times(2)).logNetworkSelectionDecision(nullScorerId,
                 compatibilityExpId, false, 2);
@@ -1440,14 +1734,14 @@
 
         // add a second NetworkNominator that returns the second network in the scan list
         mWifiNetworkSelector.registerNetworkNominator(
-                new DummyNetworkNominator(1, DUMMY_NOMINATOR_ID_2));
+                new PlaceholderNominator(1, PLACEHOLDER_NOMINATOR_ID_2));
 
-        testNoActiveStream();
+        testLowRssiNoActiveStream();
 
         int throughputExpId = experimentIdFromIdentifier(mThroughputScorer.getIdentifier());
         int compatibilityExpId = experimentIdFromIdentifier(mCompatibilityScorer.getIdentifier());
 
-        // Wanted 2 times since testNoActiveStream() calls
+        // Wanted 2 times since testLowRssiNoActiveStream() calls
         // WifiNetworkSelector.selectNetwork() twice
         if (WifiNetworkSelector.PRESET_CANDIDATE_SCORER_NAME.equals(
                 mThroughputScorer.getIdentifier())) {
@@ -1467,10 +1761,10 @@
         String[] ssids = {"\"test1\"", "\"test2\""};
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2437, 5180};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {mThresholdMinimumRssi2G + 1, mThresholdMinimumRssi5G + 1};
         int[] securities = {SECURITY_EAP, SECURITY_EAP};
-        HashSet<String> blacklist = new HashSet<>();
+        HashSet<String> blocklist = new HashSet<>();
         ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
                 WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
                         freqs, caps, levels, securities, mWifiConfigManager, mClock);
@@ -1482,9 +1776,11 @@
         when(mWifiConfigManager.getConfiguredNetwork(configs[0].networkId))
                 .thenReturn(existingConfig);
         mWifiNetworkSelector.registerNetworkNominator(
-                new DummyNetworkNominator(0, DUMMY_NOMINATOR_ID_2));
+                new PlaceholderNominator(0, PLACEHOLDER_NOMINATOR_ID_2));
         List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
-                scanDetails, blacklist, mWifiInfo, false, true, true);
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                true, true, true);
         WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(candidates);
         // Check if the wifiConfig is updated with the latest
         verify(mWifiConfigManager).addOrUpdateNetwork(existingConfig,
@@ -1497,10 +1793,10 @@
         String[] ssids = {"\"test1\"", "\"test2\""};
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2437, 5180};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-EAP/SHA1-CCMP][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {mThresholdMinimumRssi2G + 1, mThresholdMinimumRssi5G + 1};
         int[] securities = {SECURITY_EAP, SECURITY_EAP};
-        HashSet<String> blacklist = new HashSet<>();
+        HashSet<String> blocklist = new HashSet<>();
         ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
                 WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
                         freqs, caps, levels, securities, mWifiConfigManager, mClock);
@@ -1512,7 +1808,9 @@
         mWifiNetworkSelector.registerNetworkNominator(
                 new AllNetworkNominator(scanDetailsAndConfigs));
         List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
-                scanDetails, blacklist, mWifiInfo, false, true, true);
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
         // Expect one privileged and one regular candidate.
         assertEquals(2, candidates.size());
         boolean foundCarrierOrPrivilegedAppCandidate = false;
@@ -1551,7 +1849,7 @@
                         (byte) 0x50, (byte) 0x6F, (byte) 0x9A, (byte) 0x16,
                         (byte) 0x01, (byte) 0x01, (byte) 0x40,
                         (byte) 0x04, (byte) 0x01, (byte) 0x03}};
-        HashSet<String> blacklist = new HashSet<String>();
+        HashSet<String> blocklist = new HashSet<String>();
 
         ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
                 WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
@@ -1559,7 +1857,9 @@
         List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
 
         List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
-                scanDetails, blacklist, mWifiInfo, false, true, false);
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
         WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(candidates);
         verify(mWifiMetrics, times(1))
                 .incrementNetworkSelectionFilteredBssidCountDueToMboAssocDisallowInd();
@@ -1579,7 +1879,7 @@
         String[] ssids = {"\"test1\"", "\"test2\""};
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2437, 2412};
-        String[] caps = {"[WPA2-PSK][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        String[] caps = {"[WPA2-PSK][ESS]", "[WPA2-EAP/SHA1-CCMP][ESS]"};
         int[] levels = {mThresholdMinimumRssi2G + 1, mThresholdMinimumRssi2G + 1};
         int[] securities = {SECURITY_PSK, SECURITY_EAP};
 
@@ -1632,16 +1932,499 @@
         assertEquals(0, mWifiNetworkSelector.getKnownMeteredNetworkIds().size());
     }
 
+    /**
+     * New network selection is performed if the currently connected network on secondary CMM
+     * has low RSSI value even though the primary CMM is also connected and has good RSSI.
+     *
+     * Primary ClientModeImpl is connected to a good RSSI 5GHz network.
+     * Secondary ClientModeImpl is connected to a low RSSI 5GHz network.
+     * scanDetails contains a valid networks.
+     *
+     * Expected behavior: the first network is recommended by Network Selector
+     */
+    @Test
+    public void networkSelectionPerformedWhenAnyCmmIsNotSufficient() {
+        String[] ssids = {"\"test1\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3"};
+        int[] freqs = {5180};
+        String[] caps = {"[WPA2-PSK][ESS]"};
+        int[] levels = {mThresholdQualifiedRssi5G - 2};
+        int[] securities = {SECURITY_PSK};
+
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                        freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        HashSet<String> blocklist = new HashSet<String>();
+
+        // primary STA is connected and above threshold.
+        when(mWifiInfo.getSupplicantState()).thenReturn(SupplicantState.COMPLETED);
+        when(mWifiInfo.getNetworkId()).thenReturn(0);
+        when(mWifiInfo.getBSSID()).thenReturn(bssids[0]);
+        when(mWifiInfo.is24GHz()).thenReturn(false);
+        when(mWifiInfo.is5GHz()).thenReturn(true);
+        when(mWifiInfo.getFrequency()).thenReturn(5000);
+        when(mWifiInfo.getRssi()).thenReturn(mThresholdQualifiedRssi5G + 2);
+
+        // Secondary STA is connected, but below threshold.
+        when(mSecondaryWifiInfo.getSupplicantState()).thenReturn(SupplicantState.COMPLETED);
+        when(mSecondaryWifiInfo.getNetworkId()).thenReturn(0);
+        when(mSecondaryWifiInfo.getBSSID()).thenReturn(bssids[0]);
+        when(mSecondaryWifiInfo.is24GHz()).thenReturn(false);
+        when(mSecondaryWifiInfo.is5GHz()).thenReturn(true);
+        when(mSecondaryWifiInfo.getFrequency()).thenReturn(5000);
+        when(mWifiInfo.getRssi()).thenReturn(mThresholdQualifiedRssi5G - 2);
+
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime()
+                + WifiNetworkSelector.MINIMUM_NETWORK_SELECTION_INTERVAL_MS + 2000);
+
+        // Do network selection.
+        List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(
+                        TEST_IFACE_NAME, true, false, mWifiInfo),
+                        new ClientModeManagerState(
+                                TEST_IFACE_NAME_SECONDARY, true, false, mSecondaryWifiInfo)),
+                false, true, true);
+        WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(candidates);
+
+        ScanResult chosenScanResult = scanDetails.get(0).getScanResult();
+        WifiNetworkSelectorTestUtil.verifySelectedScanResult(mWifiConfigManager,
+                chosenScanResult, candidate);
+    }
+
+    /**
+     * New network selection is not performed if the currently connected network on secondary and
+     * primary CMM has good RSSI.
+     *
+     * Primary ClientModeImpl is connected to a good RSSI 5GHz network.
+     * Secondary ClientModeImpl is connected to a good RSSI 5GHz network.
+     * scanDetails contains a valid networks.
+     *
+     * Expected behavior: no network recommended by Network Selector
+     */
+    @Test
+    public void networkSelectionNotPerformedWhenAllCmmIsSufficient() {
+        String[] ssids = {"\"test1\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3"};
+        int[] freqs = {5180};
+        String[] caps = {"[WPA2-PSK][ESS]"};
+        int[] levels = {mThresholdQualifiedRssi5G + 2};
+        int[] securities = {SECURITY_PSK};
+
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                        freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        HashSet<String> blocklist = new HashSet<String>();
+
+        // primary STA is connected and above threshold.
+        when(mWifiInfo.getSupplicantState()).thenReturn(SupplicantState.COMPLETED);
+        when(mWifiInfo.getNetworkId()).thenReturn(0);
+        when(mWifiInfo.getBSSID()).thenReturn(bssids[0]);
+        when(mWifiInfo.is24GHz()).thenReturn(false);
+        when(mWifiInfo.is5GHz()).thenReturn(true);
+        when(mWifiInfo.getFrequency()).thenReturn(5000);
+        when(mWifiInfo.getRssi()).thenReturn(mThresholdQualifiedRssi5G + 2);
+        when(mWifiInfo.getSuccessfulRxPacketsPerSecond())
+                .thenReturn(mMinPacketRateActiveTraffic + 1.0);
+
+        // Secondary STA is connected and above threshold.
+        when(mSecondaryWifiInfo.getSupplicantState()).thenReturn(SupplicantState.COMPLETED);
+        when(mSecondaryWifiInfo.getNetworkId()).thenReturn(0);
+        when(mSecondaryWifiInfo.getBSSID()).thenReturn(bssids[0]);
+        when(mSecondaryWifiInfo.is24GHz()).thenReturn(false);
+        when(mSecondaryWifiInfo.is5GHz()).thenReturn(true);
+        when(mSecondaryWifiInfo.getFrequency()).thenReturn(5000);
+        when(mSecondaryWifiInfo.getRssi()).thenReturn(mThresholdQualifiedRssi5G + 2);
+        when(mSecondaryWifiInfo.getSuccessfulRxPacketsPerSecond())
+                .thenReturn(mMinPacketRateActiveTraffic + 1.0);
+
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime()
+                + WifiNetworkSelector.MINIMUM_NETWORK_SELECTION_INTERVAL_MS + 2000);
+
+        // Do network selection.
+        List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(
+                                TEST_IFACE_NAME, true, false, mWifiInfo),
+                        new ClientModeManagerState(
+                                TEST_IFACE_NAME_SECONDARY, true, false, mSecondaryWifiInfo)),
+                false, true, true);
+        assertNull(mWifiNetworkSelector.selectNetwork(candidates));
+    }
+
     private void runNetworkSelectionWith(ScanDetailsAndWifiConfigs scanDetailsAndConfigs) {
         List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
                 scanDetailsAndConfigs.getScanDetails(),
                 new HashSet<>(), // blocklist
-                mWifiInfo, // wifiInfo
-                false, // connected
-                true, // disconnected
-                true // untrustedNetworkAllowed
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                true, // untrustedNetworkAllowed
+                true, // oemPaid
+                true // oemPrivate
         );
         WifiConfiguration wifiConfiguration = mWifiNetworkSelector.selectNetwork(candidates);
         assertNotNull(wifiConfiguration);
     }
+
+    private ScanDetailsAndWifiConfigs setupAutoUpgradeNetworks(
+            WifiConfiguration config, String[] networkCaps) {
+        String[] ssids = {TEST_AUTO_UPGRADE_SSID, TEST_AUTO_UPGRADE_SSID};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {2437, 2437};
+        String[] caps = networkCaps;
+        // Prefer the first one.
+        int[] levels = {mThresholdMinimumRssi2G + RSSI_BUMP * 10,
+                mThresholdMinimumRssi2G + RSSI_BUMP};
+        // Let PlaceholderNominator return all networks.
+        mPlaceholderNominator.setNetworkIndexToReturn(PlaceholderNominator.RETURN_ALL_INDEX);
+
+        List<ScanDetail> scanDetails = WifiNetworkSelectorTestUtil.buildScanDetails(
+                ssids, bssids, freqs, caps, levels, mClock);
+        config.networkId = 0;
+        WifiConfiguration[] savedConfigs = new WifiConfiguration[] {config};
+
+        WifiNetworkSelectorTestUtil.prepareConfigStore(mWifiConfigManager, savedConfigs);
+        when(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(any()))
+                .thenReturn(savedConfigs[0]);
+        savedConfigs[0].getNetworkSelectionStatus()
+                .setSeenInLastQualifiedNetworkSelection(true);
+        return new ScanDetailsAndWifiConfigs(scanDetails, savedConfigs);
+    }
+
+    /**
+     * Verify that PSK type or SAE type is selected for a transition network
+     * under different conditions with auto-upgrade enabled.
+     * - offload is supported with legacy networks.
+     * - offload is not supported with legacy networks.
+     * - offload is not supported without legacy networks.
+     */
+    @Test
+    public void testSaeAutoUpgradeWithPskNetworkWhenAutoUpgradeEnabled() {
+        when(mClientModeManager.getSupportedFeatures()).thenReturn(WIFI_FEATURE_WPA3_SAE);
+        when(mWifiGlobals.isWpa3SaeUpgradeEnabled()).thenReturn(true);
+        when(mWifiGlobals.isWpa3SaeUpgradeOffloadEnabled()).thenReturn(true);
+
+        when(mScanRequestProxy.isWpa2PersonalOnlyNetworkInRange(eq(TEST_AUTO_UPGRADE_SSID)))
+                .thenReturn(true);
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs = setupAutoUpgradeNetworks(
+                WifiConfigurationTestUtil.createPskSaeNetwork(TEST_AUTO_UPGRADE_SSID),
+                new String[] {"[WPA2-PSK][SAE][ESS]", "[WPA2-PSK][ESS]"});
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        WifiConfiguration[] savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
+        WifiConfiguration networkSelectorChoice = savedConfigs[0];
+
+        List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
+                scanDetails, new HashSet<>(),
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
+        assertEquals(2, candidates.size());
+
+        // Verify that SAE network is selected if offload is supported.
+        WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(candidates);
+        WifiConfigurationTestUtil.assertConfigurationEqual(networkSelectorChoice, candidate);
+        assertTrue(networkSelectorChoice.getNetworkSelectionStatus().getCandidateSecurityParams()
+                .isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE));
+
+        // Verify that PSK network is selected if offload is not supported.
+        when(mWifiGlobals.isWpa3SaeUpgradeOffloadEnabled()).thenReturn(false);
+        candidate = mWifiNetworkSelector.selectNetwork(candidates);
+        WifiConfigurationTestUtil.assertConfigurationEqual(networkSelectorChoice, candidate);
+        assertTrue(networkSelectorChoice.getNetworkSelectionStatus().getCandidateSecurityParams()
+                .isSecurityType(WifiConfiguration.SECURITY_TYPE_PSK));
+
+        // Verify that SAE network is selected if offload is not supported
+        // and no PSK network is shown.
+        when(mScanRequestProxy.isWpa2PersonalOnlyNetworkInRange(eq(networkSelectorChoice.SSID)))
+                .thenReturn(false);
+        candidate = mWifiNetworkSelector.selectNetwork(candidates);
+        WifiConfigurationTestUtil.assertConfigurationEqual(networkSelectorChoice, candidate);
+        assertTrue(networkSelectorChoice.getNetworkSelectionStatus().getCandidateSecurityParams()
+                .isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE));
+        assertFalse(networkSelectorChoice.getNetworkSelectionStatus().getCandidateSecurityParams()
+                .isRequirePmf());
+    }
+
+    /**
+     * Verify that SAE type is not selected for a transition network
+     * if auto-upgrade is disabled.
+     */
+    @Test
+    public void testSaeNoAutoUpgradeWithPskNetworkWhenAutoUpgradeDisabled() {
+        when(mClientModeManager.getSupportedFeatures()).thenReturn(WIFI_FEATURE_WPA3_SAE);
+        when(mWifiGlobals.isWpa3SaeUpgradeEnabled()).thenReturn(false);
+        when(mWifiGlobals.isWpa3SaeUpgradeOffloadEnabled()).thenReturn(false);
+
+        when(mScanRequestProxy.isWpa2PersonalOnlyNetworkInRange(eq(TEST_AUTO_UPGRADE_SSID)))
+                .thenReturn(true);
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs = setupAutoUpgradeNetworks(
+                WifiConfigurationTestUtil.createPskSaeNetwork(TEST_AUTO_UPGRADE_SSID),
+                new String[] {"[WPA2-PSK][SAE][ESS]", "[WPA2-PSK][ESS]"});
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        WifiConfiguration[] savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
+        WifiConfiguration networkSelectorChoice = savedConfigs[0];
+
+        List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
+                scanDetails, new HashSet<>(),
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
+        assertEquals(2, candidates.size());
+
+        // Verify that PSK network is selected.
+        WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(candidates);
+        WifiConfigurationTestUtil.assertConfigurationEqual(networkSelectorChoice, candidate);
+        assertTrue(networkSelectorChoice.getNetworkSelectionStatus().getCandidateSecurityParams()
+                .isSecurityType(WifiConfiguration.SECURITY_TYPE_PSK));
+    }
+
+    /**
+     * Verify that SAE network is not selected by the auto-upgrade configuration
+     * if SAE is not supported.
+     */
+    @Test
+    public void testSaeNoAutoUpgradeWithPskNetworkWhenSaeNotSupported() {
+        when(mClientModeManager.getSupportedFeatures()).thenReturn(0L);
+        when(mWifiGlobals.isWpa3SaeUpgradeEnabled()).thenReturn(true);
+        when(mWifiGlobals.isWpa3SaeUpgradeOffloadEnabled()).thenReturn(true);
+
+        when(mScanRequestProxy.isWpa2PersonalOnlyNetworkInRange(eq(TEST_AUTO_UPGRADE_SSID)))
+                .thenReturn(true);
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs = setupAutoUpgradeNetworks(
+                WifiConfigurationTestUtil.createPskSaeNetwork(TEST_AUTO_UPGRADE_SSID),
+                new String[] {"[SAE][ESS]", "[WPA2-PSK][SAE][ESS]"});
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        WifiConfiguration[] savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
+        WifiConfiguration networkSelectorChoice = savedConfigs[0];
+
+        List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
+                scanDetails, new HashSet<>(),
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
+        // The SAE-only network should be filtered.
+        assertEquals(1, candidates.size());
+
+        // Verify that PSK network is selected.
+        WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(candidates);
+        WifiConfigurationTestUtil.assertConfigurationEqual(networkSelectorChoice, candidate);
+        assertTrue(networkSelectorChoice.getNetworkSelectionStatus().getCandidateSecurityParams()
+                .isSecurityType(WifiConfiguration.SECURITY_TYPE_PSK));
+    }
+
+    /**
+     * Verify that Open type or OWE type is selected under different conditions
+     * with auto-upgrade enabled.
+     * - legacy networks exist.
+     * - no legacy network exists.
+     */
+    @Test
+    public void testOweAutoUpgradeWithOpenNetworkWhenAutoUpgradeEnabled() {
+        when(mClientModeManager.getSupportedFeatures()).thenReturn(WIFI_FEATURE_OWE);
+        when(mWifiGlobals.isOweUpgradeEnabled()).thenReturn(true);
+
+        when(mScanRequestProxy.isOpenOnlyNetworkInRange(eq(TEST_AUTO_UPGRADE_SSID)))
+                .thenReturn(true);
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs = setupAutoUpgradeNetworks(
+                WifiConfigurationTestUtil.createOpenOweNetwork(TEST_AUTO_UPGRADE_SSID),
+                new String[] {"[RSN-OWE_TRANSITION-CCMP][ESS]", "[ESS]"});
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        WifiConfiguration[] savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
+        WifiConfiguration networkSelectorChoice = savedConfigs[0];
+
+        List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
+                scanDetails, new HashSet<>(),
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
+        assertEquals(2, candidates.size());
+
+        // Verify that OWE network is selected (assume offload is not supported.).
+        WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(candidates);
+        WifiConfigurationTestUtil.assertConfigurationEqual(networkSelectorChoice, candidate);
+        assertTrue(networkSelectorChoice.getNetworkSelectionStatus().getCandidateSecurityParams()
+                .isSecurityType(WifiConfiguration.SECURITY_TYPE_OPEN));
+
+        // Verify that Open network is selected if no OPEN network is shown.
+        when(mScanRequestProxy.isOpenOnlyNetworkInRange(eq(networkSelectorChoice.SSID)))
+                .thenReturn(false);
+        candidate = mWifiNetworkSelector.selectNetwork(candidates);
+        WifiConfigurationTestUtil.assertConfigurationEqual(networkSelectorChoice, candidate);
+        assertTrue(networkSelectorChoice.getNetworkSelectionStatus().getCandidateSecurityParams()
+                .isSecurityType(WifiConfiguration.SECURITY_TYPE_OWE));
+    }
+
+    /**
+     * Verify that OWE type is not selected for a transition network
+     * if auto-upgrade is disabled.
+     */
+    @Test
+    public void testOweNoAutoUpgradeWithOpenNetworkWhenAutoUpgradeDisabled() {
+        when(mClientModeManager.getSupportedFeatures()).thenReturn(WIFI_FEATURE_OWE);
+        when(mWifiGlobals.isOweUpgradeEnabled()).thenReturn(false);
+
+        when(mScanRequestProxy.isOpenOnlyNetworkInRange(eq(TEST_AUTO_UPGRADE_SSID)))
+                .thenReturn(true);
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs = setupAutoUpgradeNetworks(
+                WifiConfigurationTestUtil.createOpenOweNetwork(TEST_AUTO_UPGRADE_SSID),
+                new String[] {"[RSN-OWE_TRANSITION-CCMP][ESS]", "[ESS]"});
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        WifiConfiguration[] savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
+        WifiConfiguration networkSelectorChoice = savedConfigs[0];
+
+        List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
+                scanDetails, new HashSet<>(),
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
+        assertEquals(2, candidates.size());
+
+        // Verify that OPEN network is selected.
+        WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(candidates);
+        WifiConfigurationTestUtil.assertConfigurationEqual(networkSelectorChoice, candidate);
+        assertTrue(networkSelectorChoice.getNetworkSelectionStatus().getCandidateSecurityParams()
+                .isSecurityType(WifiConfiguration.SECURITY_TYPE_OPEN));
+    }
+
+    /**
+     * Verify that OWE type is not selected for a transition type
+     * if OWE is not supported.
+     */
+    @Test
+    public void testOweNoAutoUpgradeWithOweNetworkWhenOweNotSupported() {
+        when(mClientModeManager.getSupportedFeatures()).thenReturn(0L);
+        when(mWifiGlobals.isOweUpgradeEnabled()).thenReturn(true);
+
+        when(mScanRequestProxy.isOpenOnlyNetworkInRange(eq(TEST_AUTO_UPGRADE_SSID)))
+                .thenReturn(true);
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs = setupAutoUpgradeNetworks(
+                WifiConfigurationTestUtil.createOpenOweNetwork(TEST_AUTO_UPGRADE_SSID),
+                new String[] {"[RSN-OWE-CCMP][ESS]",
+                        "[RSN-OWE_TRANSITION-CCMP][ESS]"});
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        WifiConfiguration[] savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
+        WifiConfiguration networkSelectorChoice = savedConfigs[0];
+
+        List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
+                scanDetails, new HashSet<>(),
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
+        // The OWE-only network should be filtered.
+        assertEquals(1, candidates.size());
+
+        // Verify that OPEN network is selected.
+        WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(candidates);
+        WifiConfigurationTestUtil.assertConfigurationEqual(networkSelectorChoice, candidate);
+        assertTrue(networkSelectorChoice.getNetworkSelectionStatus().getCandidateSecurityParams()
+                .isSecurityType(WifiConfiguration.SECURITY_TYPE_OPEN));
+    }
+
+    /**
+     * Verify that WPA2 Enterprise type or WPA3 Enterprise type is selected
+     * under different conditions with auto-upgrade enabled.
+     * - legacy networks exist.
+     * - no legacy network exists.
+     */
+    @Test
+    public void testWpa3EnterpriseAutoUpgradeWithWpa2EnterpriseNetwork() {
+        when(mScanRequestProxy.isWpa2EnterpriseOnlyNetworkInRange(eq(TEST_AUTO_UPGRADE_SSID)))
+                .thenReturn(true);
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs = setupAutoUpgradeNetworks(
+                WifiConfigurationTestUtil.createWpa2Wpa3EnterpriseNetwork(TEST_AUTO_UPGRADE_SSID),
+                new String[] {"[RSN-EAP/SHA1-CCMP][RSN-EAP/SHA256-CCMP][ESS][MFPC]",
+                        "[RSN-EAP/SHA1-TKIP+CCMP][ESS]"});
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        WifiConfiguration[] savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
+        WifiConfiguration networkSelectorChoice = savedConfigs[0];
+
+        List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
+                scanDetails, new HashSet<>(),
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
+        assertEquals(2, candidates.size());
+
+        // Verify that WPA2 Enterprise network is selected (assume offload is not supported.).
+        WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(candidates);
+        WifiConfigurationTestUtil.assertConfigurationEqual(networkSelectorChoice, candidate);
+        assertTrue(networkSelectorChoice.getNetworkSelectionStatus().getCandidateSecurityParams()
+                .isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP));
+
+        // Verify that WPA3 Enterprise network is selected if no OPEN network is shown.
+        when(mScanRequestProxy.isWpa2EnterpriseOnlyNetworkInRange(eq(networkSelectorChoice.SSID)))
+                .thenReturn(false);
+        candidate = mWifiNetworkSelector.selectNetwork(candidates);
+        WifiConfigurationTestUtil.assertConfigurationEqual(networkSelectorChoice, candidate);
+        assertTrue(networkSelectorChoice.getNetworkSelectionStatus().getCandidateSecurityParams()
+                .isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE));
+        assertFalse(networkSelectorChoice.getNetworkSelectionStatus().getCandidateSecurityParams()
+                .isRequirePmf());
+    }
+
+    @Test
+    public void verifySecurityParamsSelectionForPskSaeConfigAndSaeScan() {
+        when(mClientModeManager.getSupportedFeatures()).thenReturn(WIFI_FEATURE_WPA3_SAE);
+        when(mWifiGlobals.isWpa3SaeUpgradeEnabled()).thenReturn(true);
+        setupMultiConfigAndSingleScanAndVerify("[RSN-SAE-CCMP][ESS][MFPR]",
+                SECURITY_PSK | SECURITY_SAE, WifiConfiguration.SECURITY_TYPE_SAE);
+    }
+
+    @Test
+    public void verifySecurityParamsSelectionForPskSaeConfigAndSaeScanNegative() {
+        when(mClientModeManager.getSupportedFeatures()).thenReturn(~WIFI_FEATURE_WPA3_SAE);
+        setupMultiConfigAndSingleScanAndVerify("[RSN-SAE-CCMP][ESS][MFPR]",
+                SECURITY_PSK | SECURITY_SAE, -1);
+    }
+
+    @Test
+    public void verifySecurityParamsSelectionForOpenOweConfigAndOweScan() {
+        when(mClientModeManager.getSupportedFeatures()).thenReturn(WIFI_FEATURE_OWE);
+        when(mWifiGlobals.isOweUpgradeEnabled()).thenReturn(true);
+        setupMultiConfigAndSingleScanAndVerify("[OWE-SAE-CCMP][ESS][MFPR]",
+                SECURITY_NONE | SECURITY_OWE, WifiConfiguration.SECURITY_TYPE_OWE);
+    }
+
+    @Test
+    public void verifySecurityParamsSelectionForOpenOweConfigAndOweScanNegative() {
+        when(mClientModeManager.getSupportedFeatures()).thenReturn(~WIFI_FEATURE_OWE);
+        setupMultiConfigAndSingleScanAndVerify("[OWE-SAE-CCMP][ESS][MFPR]",
+                SECURITY_NONE | SECURITY_OWE, -1);
+    }
+
+    private void setupMultiConfigAndSingleScanAndVerify(String capabilities, int securityTypes,
+            int expectedSecurityParamType) {
+        String[] ssids = {"\"Some SSID\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3"};
+        int[] freqs = {2437};
+        String[] caps = {capabilities};
+        int[] levels = {mThresholdMinimumRssi2G + RSSI_BUMP};
+        int[] securities = {securityTypes};
+
+        // Let PlaceholderNominator return all networks.
+        mPlaceholderNominator.setNetworkIndexToReturn(PlaceholderNominator.RETURN_ALL_INDEX);
+
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                        freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        Set<String> blocklist = new HashSet<>();
+        WifiConfiguration[] savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
+        when(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(any()))
+                .thenReturn(savedConfigs[0]);
+        savedConfigs[0].getNetworkSelectionStatus()
+                .setSeenInLastQualifiedNetworkSelection(true);
+
+        mWifiNetworkSelector.registerNetworkNominator(
+                new AllNetworkNominator(scanDetailsAndConfigs));
+        List<WifiCandidates.Candidate> candidates = mWifiNetworkSelector.getCandidatesFromScan(
+                scanDetails, blocklist,
+                Arrays.asList(new ClientModeManagerState(TEST_IFACE_NAME, false, true, mWifiInfo)),
+                false, true, true);
+        assertNotNull(candidates);
+        if (expectedSecurityParamType == -1) {
+            assertEquals(0, candidates.size());
+        } else {
+            assertEquals(1, candidates.size());
+            WifiConfiguration network = mWifiNetworkSelector.selectNetwork(candidates);
+
+            assertNotNull(network);
+            assertEquals(network.getSecurityParams(expectedSecurityParamType),
+                    network.getNetworkSelectionStatus().getCandidateSecurityParams());
+        }
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTestUtil.java b/service/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTestUtil.java
index 6f98f6a..f134ee5 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTestUtil.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTestUtil.java
@@ -30,6 +30,7 @@
 import android.net.ScoredNetwork;
 import android.net.WifiKey;
 import android.net.wifi.ScanResult;
+import android.net.wifi.SecurityParams;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiSsid;
@@ -137,7 +138,7 @@
         assertNotNull(ssids);
         String[] caps = new String[ssids.length];
         for (int i = 0; i < ssids.length; i++) {
-            caps[i] = "[EAP][ESS]";
+            caps[i] = "[EAP/SHA1][ESS]";
         }
         List<ScanDetail> scanDetails = buildScanDetails(ssids, bssids, freqs, caps, levels, clock);
         WifiConfiguration[] savedConfigs = new WifiConfiguration[ssids.length];
@@ -171,9 +172,12 @@
         for (int i = 0; i < savedConfigs.length; i++) {
             ScanResult scanResult = scanDetails.get(i).getScanResult();
             WifiConfiguration config = savedConfigs[i];
-            assertEquals("Problem in entry " + i,
-                    ScanResultMatchInfo.fromScanResult(scanResult),
-                    ScanResultMatchInfo.fromWifiConfiguration(config));
+            // Can check this only for configs with a single security type
+            if (config.getSecurityParamsList().size() < 2) {
+                assertEquals("Problem in entry " + i,
+                        ScanResultMatchInfo.fromScanResult(scanResult),
+                        ScanResultMatchInfo.fromWifiConfiguration(config));
+            }
         }
     }
 
@@ -187,7 +191,8 @@
     public static void verifySelectedScanResult(WifiConfigManager wifiConfigManager,
             ScanResult chosenScanResult, WifiConfiguration chosenCandidate) {
         verify(wifiConfigManager, atLeastOnce()).setNetworkCandidateScanResult(
-                eq(chosenCandidate.networkId), eq(chosenScanResult), anyInt());
+                eq(chosenCandidate.networkId), eq(chosenScanResult), anyInt(),
+                eq(chosenCandidate.getSecurityParamsList().get(0)));
     }
 
 
@@ -268,20 +273,18 @@
 
         WifiConfiguration[] configs = new WifiConfiguration[ssids.length];
         for (int index = 0; index < ssids.length; index++) {
-            String configKey = ssids[index] + Integer.toString(securities[index]);
-            Integer id;
-
-            id = netIdMap.get(configKey);
+            String configKey = ssids[index] + securities[index];
+            Integer id = netIdMap.get(configKey);
             if (id == null) {
-                id = new Integer(netId);
+                id = netId;
                 netIdMap.put(configKey, id);
                 netId++;
             }
 
-            configs[index] = generateWifiConfig(id.intValue(), 0, ssids[index], false, true, null,
+            configs[index] = generateWifiConfig(id, 0, ssids[index], false, true, null,
                     null, securities[index]);
-            if (securities[index] == SECURITY_PSK || securities[index] == SECURITY_SAE
-                    || securities[index] == SECURITY_WAPI_PSK) {
+            if ((securities[index] & SECURITY_PSK) != 0 || (securities[index] & SECURITY_SAE) != 0
+                    || (securities[index] & SECURITY_WAPI_PSK) != 0) {
                 configs[index].preSharedKey = "\"PA55W0RD\""; // needed to validate with PSK
             }
             if (!WifiConfigurationUtil.validate(configs[index], true)) {
@@ -300,8 +303,20 @@
      * @param wifiConfigManager the mocked WifiConfigManager
      * @param configs input configuration need to be added to WifiConfigureStore
      */
-    private static void prepareConfigStore(final WifiConfigManager wifiConfigManager,
+    public static void prepareConfigStore(final WifiConfigManager wifiConfigManager,
                 final WifiConfiguration[] configs) {
+        when(wifiConfigManager.getSavedNetworkForScanDetail(any(ScanDetail.class)))
+                .then(new AnswerWithArguments() {
+                    public WifiConfiguration answer(ScanDetail scanDetail) {
+                        for (WifiConfiguration config : configs) {
+                            if (TextUtils.equals(config.SSID,
+                                    ScanResultUtil.createQuotedSSID(scanDetail.getSSID()))) {
+                                return config;
+                            }
+                        }
+                        return null;
+                    }
+                });
         when(wifiConfigManager.getConfiguredNetwork(anyInt()))
                 .then(new AnswerWithArguments() {
                     public WifiConfiguration answer(int netId) {
@@ -317,7 +332,7 @@
                 .then(new AnswerWithArguments() {
                     public WifiConfiguration answer(String configKey) {
                         for (WifiConfiguration config : configs) {
-                            if (TextUtils.equals(config.getKey(), configKey)) {
+                            if (TextUtils.equals(config.getProfileKey(), configKey)) {
                                 return new WifiConfiguration(config);
                             }
                         }
@@ -342,6 +357,8 @@
                             configs[netId].getNetworkSelectionStatus()
                                     .setCandidateScore(Integer.MIN_VALUE);
                             configs[netId].getNetworkSelectionStatus()
+                                .setCandidateSecurityParams(null);
+                            configs[netId].getNetworkSelectionStatus()
                                     .setSeenInLastQualifiedNetworkSelection(false);
                             return true;
                         } else {
@@ -350,13 +367,16 @@
                     }
                 });
         when(wifiConfigManager.setNetworkCandidateScanResult(
-                anyInt(), any(ScanResult.class), anyInt()))
+                anyInt(), any(ScanResult.class), anyInt(), any()))
                 .then(new AnswerWithArguments() {
-                    public boolean answer(int netId, ScanResult scanResult, int score) {
+                    public boolean answer(int netId, ScanResult scanResult, int score,
+                            SecurityParams params) {
                         if (netId >= 0 && netId < configs.length) {
                             configs[netId].getNetworkSelectionStatus().setCandidate(scanResult);
                             configs[netId].getNetworkSelectionStatus().setCandidateScore(score);
                             configs[netId].getNetworkSelectionStatus()
+                                    .setCandidateSecurityParams(params);
+                            configs[netId].getNetworkSelectionStatus()
                                     .setSeenInLastQualifiedNetworkSelection(true);
                             return true;
                         } else {
@@ -364,28 +384,6 @@
                         }
                     }
                 });
-        when(wifiConfigManager.clearNetworkConnectChoice(anyInt()))
-                .then(new AnswerWithArguments() {
-                    public boolean answer(int netId) {
-                        if (netId >= 0 && netId < configs.length) {
-                            configs[netId].getNetworkSelectionStatus().setConnectChoice(null);
-                            return true;
-                        } else {
-                            return false;
-                        }
-                    }
-                });
-        when(wifiConfigManager.setNetworkConnectChoice(anyInt(), anyString()))
-                .then(new AnswerWithArguments() {
-                    public boolean answer(int netId, String configKey) {
-                        if (netId >= 0 && netId < configs.length) {
-                            configs[netId].getNetworkSelectionStatus().setConnectChoice(configKey);
-                            return true;
-                        } else {
-                            return false;
-                        }
-                    }
-                });
     }
 
 
@@ -409,19 +407,19 @@
         if (scanDetails.size() <= configs.length) {
             for (int i = 0; i < scanDetails.size(); i++) {
                 ScanDetail scanDetail = scanDetails.get(i);
-                when(wifiConfigManager.getConfiguredNetworkForScanDetailAndCache(eq(scanDetail)))
+                when(wifiConfigManager.getSavedNetworkForScanDetailAndCache(eq(scanDetail)))
                         .thenReturn(configs[i]);
             }
         } else {
             for (int i = 0; i < configs.length; i++) {
                 ScanDetail scanDetail = scanDetails.get(i);
-                when(wifiConfigManager.getConfiguredNetworkForScanDetailAndCache(eq(scanDetail)))
+                when(wifiConfigManager.getSavedNetworkForScanDetailAndCache(eq(scanDetail)))
                         .thenReturn(configs[i]);
             }
 
             // associated the remaining scan details with a NULL config.
             for (int i = configs.length; i < scanDetails.size(); i++) {
-                when(wifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+                when(wifiConfigManager.getSavedNetworkForScanDetailAndCache(
                         eq(scanDetails.get(i)))).thenReturn(null);
             }
         }
@@ -488,7 +486,7 @@
         config.networkId = networkId;
         config.meteredHint = meteredHint;
 
-        when(wifiConfigManager.getConfiguredNetworkForScanDetailAndCache(eq(scanDetail)))
+        when(wifiConfigManager.getSavedNetworkForScanDetailAndCache(eq(scanDetail)))
                 .thenReturn(new WifiConfiguration(config));
         when(wifiConfigManager.getConfiguredNetwork(eq(networkId)))
                 .then(new AnswerWithArguments() {
@@ -497,12 +495,15 @@
                     }
                 });
         when(wifiConfigManager.setNetworkCandidateScanResult(
-                eq(networkId), any(ScanResult.class), anyInt()))
+                eq(networkId), any(ScanResult.class), anyInt(), any()))
                 .then(new AnswerWithArguments() {
-                    public boolean answer(int netId, ScanResult scanResult, int score) {
+                    public boolean answer(int netId, ScanResult scanResult, int score,
+                            SecurityParams params) {
                         config.getNetworkSelectionStatus().setCandidate(scanResult);
                         config.getNetworkSelectionStatus().setCandidateScore(score);
                         config.getNetworkSelectionStatus()
+                                .setCandidateSecurityParams(params);
+                        config.getNetworkSelectionStatus()
                                 .setSeenInLastQualifiedNetworkSelection(true);
                         return true;
                     }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiNetworkSuggestionsManagerTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiNetworkSuggestionsManagerTest.java
index eb81d3a..13c8077 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiNetworkSuggestionsManagerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiNetworkSuggestionsManagerTest.java
@@ -24,6 +24,7 @@
 import static android.app.AppOpsManager.OPSTR_CHANGE_WIFI_STATE;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 import static com.android.server.wifi.WifiNetworkSuggestionsManager.NOTIFICATION_USER_ALLOWED_APP_INTENT_ACTION;
 import static com.android.server.wifi.WifiNetworkSuggestionsManager.NOTIFICATION_USER_DISALLOWED_APP_INTENT_ACTION;
@@ -32,15 +33,15 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.Mockito.*;
 
 import android.app.ActivityManager;
 import android.app.AlertDialog;
 import android.app.AppOpsManager;
 import android.app.Notification;
-import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -52,6 +53,7 @@
 import android.net.NetworkScoreManager;
 import android.net.wifi.EAPConstants;
 import android.net.wifi.ISuggestionConnectionStatusListener;
+import android.net.wifi.ISuggestionUserApprovalStatusListener;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiEnterpriseConfig;
@@ -64,9 +66,10 @@
 import android.net.wifi.hotspot2.pps.HomeSp;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.os.test.TestLooper;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.view.LayoutInflater;
@@ -74,6 +77,7 @@
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.MacAddressUtils;
 import com.android.server.wifi.WifiNetworkSuggestionsManager.ExtendedWifiNetworkSuggestion;
 import com.android.server.wifi.WifiNetworkSuggestionsManager.PerAppInfo;
@@ -82,9 +86,11 @@
 import com.android.server.wifi.util.WifiPermissionsUtil;
 import com.android.wifi.resources.R;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
 import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -93,6 +99,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -119,17 +126,21 @@
     private static final String TEST_CARRIER_NAME = "test_carrier";
     private static final int TEST_UID_1 = 5667;
     private static final int TEST_UID_2 = 4537;
-    private static final int NETWORK_CALLBACK_ID = 1100;
     private static final int VALID_CARRIER_ID = 100;
     private static final int TEST_SUBID = 1;
     private static final int TEST_NETWORK_ID = 110;
     private static final int TEST_CARRIER_ID = 1911;
     private static final String TEST_IMSI = "123456*";
+    private static final int DEFAULT_PRIORITY_GROUP = 0;
+    private static final int TEST_PRIORITY_GROUP = 1;
+    private static final String TEST_ANONYMOUS_IDENTITY = "AnonymousIdentity";
+    private static final String USER_CONNECT_CHOICE = "SomeNetworkProfileId";
+    private static final int TEST_RSSI = -50;
 
     private @Mock WifiContext mContext;
     private @Mock Resources mResources;
     private @Mock AppOpsManager mAppOpsManager;
-    private @Mock NotificationManager mNotificationManger;
+    private @Mock WifiNotificationManager mWifiNotificationManager;
     private @Mock NetworkScoreManager mNetworkScoreManager;
     private @Mock PackageManager mPackageManager;
     private @Mock WifiPermissionsUtil mWifiPermissionsUtil;
@@ -141,7 +152,8 @@
     private @Mock WifiMetrics mWifiMetrics;
     private @Mock WifiCarrierInfoManager mWifiCarrierInfoManager;
     private @Mock PasspointManager mPasspointManager;
-    private @Mock ISuggestionConnectionStatusListener mListener;
+    private @Mock ISuggestionConnectionStatusListener mConnectionStatusListener;
+    private @Mock ISuggestionUserApprovalStatusListener mUserApprovalStatusListener;
     private @Mock IBinder mBinder;
     private @Mock ActivityManager mActivityManager;
     private @Mock WifiScoreCard mWifiScoreCard;
@@ -151,15 +163,21 @@
     private @Mock Notification.Builder mNotificationBuilder;
     private @Mock Notification mNotification;
     private @Mock LruConnectionTracker mLruConnectionTracker;
-    private @Mock UserManager mUserManager;
+    private @Mock ActiveModeWarden mActiveModeWarden;
+    private @Mock ClientModeManager mPrimaryClientModeManager;
+    private @Mock WifiGlobals mWifiGlobals;
+    private @Mock Clock mClock;
+    private MockitoSession mStaticMockSession = null;
     private TestLooper mLooper;
-    private ArgumentCaptor<AppOpsManager.OnOpChangedListener> mAppOpChangedListenerCaptor =
+    private final ArgumentCaptor<AppOpsManager.OnOpChangedListener> mAppOpChangedListenerCaptor =
             ArgumentCaptor.forClass(AppOpsManager.OnOpChangedListener.class);
-    private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor =
+    private final ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor =
             ArgumentCaptor.forClass(BroadcastReceiver.class);
-    private ArgumentCaptor<WifiCarrierInfoManager.OnUserApproveCarrierListener>
+    private final ArgumentCaptor<WifiCarrierInfoManager.OnUserApproveCarrierListener>
             mUserApproveCarrierListenerArgumentCaptor = ArgumentCaptor.forClass(
             WifiCarrierInfoManager.OnUserApproveCarrierListener.class);
+    private final ArgumentCaptor<WifiConfigManager.OnNetworkUpdateListener> mNetworkListenerCaptor =
+            ArgumentCaptor.forClass(WifiConfigManager.OnNetworkUpdateListener.class);
 
     private InOrder mInorder;
 
@@ -172,6 +190,10 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        mStaticMockSession = mockitoSession()
+                .mockStatic(WifiInjector.class)
+                .startMocking();
+        lenient().when(WifiInjector.getInstance()).thenReturn(mWifiInjector);
         mLooper = new TestLooper();
 
         mInorder = inOrder(mContext, mWifiPermissionsUtil);
@@ -181,6 +203,9 @@
         when(mWifiInjector.getFrameworkFacade()).thenReturn(mFrameworkFacade);
         when(mWifiInjector.getPasspointManager()).thenReturn(mPasspointManager);
         when(mWifiInjector.getWifiScoreCard()).thenReturn(mWifiScoreCard);
+        when(mWifiInjector.getActiveModeWarden()).thenReturn(mActiveModeWarden);
+        when(mWifiInjector.getWifiGlobals()).thenReturn(mWifiGlobals);
+        when(mWifiInjector.getWifiNotificationManager()).thenReturn(mWifiNotificationManager);
         when(mAlertDialogBuilder.setTitle(any())).thenReturn(mAlertDialogBuilder);
         when(mAlertDialogBuilder.setMessage(any())).thenReturn(mAlertDialogBuilder);
         when(mAlertDialogBuilder.setPositiveButton(any(), any())).thenReturn(mAlertDialogBuilder);
@@ -198,6 +223,7 @@
         when(mNotificationBuilder.setLocalOnly(anyBoolean())).thenReturn(mNotificationBuilder);
         when(mNotificationBuilder.setColor(anyInt())).thenReturn(mNotificationBuilder);
         when(mNotificationBuilder.addAction(any())).thenReturn(mNotificationBuilder);
+        when(mNotificationBuilder.setTimeoutAfter(anyLong())).thenReturn(mNotificationBuilder);
         when(mNotificationBuilder.build()).thenReturn(mNotification);
         when(mFrameworkFacade.makeAlertDialogBuilder(any()))
                 .thenReturn(mAlertDialogBuilder);
@@ -207,8 +233,6 @@
                 .thenReturn(mock(PendingIntent.class));
         when(mContext.getResources()).thenReturn(mResources);
         when(mContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager);
-        when(mContext.getSystemService(Context.NOTIFICATION_SERVICE))
-                .thenReturn(mNotificationManger);
         when(mContext.getSystemService(NetworkScoreManager.class)).thenReturn(mNetworkScoreManager);
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         when(mContext.getSystemService(ActivityManager.class)).thenReturn(mActivityManager);
@@ -219,6 +243,13 @@
         when(mActivityManager.getPackageImportance(any())).thenReturn(
                 IMPORTANCE_FOREGROUND_SERVICE);
         when(mWifiPermissionsUtil.doesUidBelongToCurrentUser(anyInt())).thenReturn(true);
+        when(mActiveModeWarden.getPrimaryClientModeManager()).thenReturn(mPrimaryClientModeManager);
+        when(mPrimaryClientModeManager.getSupportedFeatures()).thenReturn(
+                WifiManager.WIFI_FEATURE_WPA3_SAE | WifiManager.WIFI_FEATURE_OWE);
+        when(mWifiGlobals.isWpa3SaeUpgradeEnabled()).thenReturn(true);
+        when(mWifiGlobals.isOweUpgradeEnabled()).thenReturn(true);
+        when(mConnectionStatusListener.asBinder()).thenReturn(mBinder);
+        when(mUserApprovalStatusListener.asBinder()).thenReturn(mBinder);
 
         // setup resource strings for notification.
         when(mResources.getString(eq(R.string.wifi_suggestion_title), anyString()))
@@ -245,6 +276,13 @@
         when(mPackageManager.getApplicationLabel(appInfO2)).thenReturn(TEST_APP_NAME_2);
         when(mWifiCarrierInfoManager.getCarrierIdForPackageWithCarrierPrivileges(any())).thenReturn(
                 TelephonyManager.UNKNOWN_CARRIER_ID);
+        when(mWifiCarrierInfoManager.isSubIdMatchingCarrierId(anyInt(), anyInt())).thenReturn(true);
+        when(mWifiCarrierInfoManager.getBestMatchSubscriptionId(any())).thenReturn(
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        when(mWifiCarrierInfoManager.isSimReady(SubscriptionManager.INVALID_SUBSCRIPTION_ID))
+                .thenReturn(false);
+        when(mWifiCarrierInfoManager.areMergedCarrierWifiNetworksAllowed(anyInt())).thenReturn(
+                false);
 
         when(mWifiKeyStore.updateNetworkKeys(any(), any())).thenReturn(true);
 
@@ -252,10 +290,9 @@
                 new WifiNetworkSuggestionsManager(mContext, new Handler(mLooper.getLooper()),
                         mWifiInjector, mWifiPermissionsUtil, mWifiConfigManager, mWifiConfigStore,
                         mWifiMetrics, mWifiCarrierInfoManager, mWifiKeyStore,
-                        mLruConnectionTracker);
+                        mLruConnectionTracker, mClock);
         verify(mContext).getResources();
         verify(mContext).getSystemService(Context.APP_OPS_SERVICE);
-        verify(mContext).getSystemService(Context.NOTIFICATION_SERVICE);
         verify(mContext).getPackageManager();
         verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(), any(), any(), any());
 
@@ -265,14 +302,22 @@
         verify(mWifiInjector).makeNetworkSuggestionStoreData(dataSourceArgumentCaptor.capture());
         mDataSource = dataSourceArgumentCaptor.getValue();
         assertNotNull(mDataSource);
-        mDataSource.fromDeserialized(Collections.EMPTY_MAP);
+        mDataSource.fromDeserialized(Map.of());
 
         verify(mWifiCarrierInfoManager).addImsiExemptionUserApprovalListener(
                 mUserApproveCarrierListenerArgumentCaptor.capture());
+        verify(mWifiConfigManager).addOnNetworkUpdateListener(mNetworkListenerCaptor.capture());
 
         mWifiNetworkSuggestionsManager.enableVerboseLogging(1);
     }
 
+    @After
+    public void cleanUp() throws Exception {
+        if (null != mStaticMockSession) {
+            mStaticMockSession.finishMocking();
+        }
+    }
+
     /**
      * Verify successful addition of network suggestions.
      */
@@ -280,12 +325,14 @@
     public void testAddNetworkSuggestionsSuccess() {
         PasspointConfiguration passpointConfiguration =
                 createTestConfigWithUserCredential(TEST_FQDN, TEST_FRIENDLY_NAME);
-        WifiConfiguration dummyConfiguration = createDummyWifiConfigurationForPasspoint(TEST_FQDN);
-        dummyConfiguration.FQDN = TEST_FQDN;
-        WifiNetworkSuggestion networkSuggestion1 = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
-        WifiNetworkSuggestion networkSuggestion2 = new WifiNetworkSuggestion(
-                dummyConfiguration, passpointConfiguration, false, false, true, true);
+        WifiConfiguration placeholderConfig = createPlaceholderConfigForPasspoint(TEST_FQDN,
+                passpointConfiguration.getUniqueId());
+        WifiNetworkSuggestion networkSuggestion1 = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        WifiNetworkSuggestion networkSuggestion2 = createWifiNetworkSuggestion(
+                placeholderConfig, passpointConfiguration, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
 
         List<WifiNetworkSuggestion> networkSuggestionList1 =
                 new ArrayList<WifiNetworkSuggestion>() {{
@@ -318,6 +365,7 @@
         assertEquals(expectedAllNetworkSuggestions, allNetworkSuggestions);
 
         verify(mWifiMetrics, times(2)).incrementNetworkSuggestionApiNumModification();
+        verify(mWifiMetrics, times(2)).addNetworkSuggestionPriorityGroup(anyInt());
         ArgumentCaptor<List<Integer>> maxSizesCaptor = ArgumentCaptor.forClass(List.class);
         verify(mWifiMetrics, times(2)).noteNetworkSuggestionApiListSizeHistogram(
                 maxSizesCaptor.capture());
@@ -332,12 +380,15 @@
     public void testRemoveNetworkSuggestionsSuccess() {
         PasspointConfiguration passpointConfiguration =
                 createTestConfigWithUserCredential(TEST_FQDN, TEST_FRIENDLY_NAME);
-        WifiConfiguration dummyConfiguration = createDummyWifiConfigurationForPasspoint(TEST_FQDN);
-        dummyConfiguration.setPasspointUniqueId(passpointConfiguration.getUniqueId());
-        WifiNetworkSuggestion networkSuggestion1 = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
-        WifiNetworkSuggestion networkSuggestion2 = new WifiNetworkSuggestion(
-                dummyConfiguration, passpointConfiguration, false, false, true, true);
+        WifiConfiguration placeholderConfig = createPlaceholderConfigForPasspoint(TEST_FQDN,
+                passpointConfiguration.getUniqueId());
+        placeholderConfig.setPasspointUniqueId(passpointConfiguration.getUniqueId());
+        WifiNetworkSuggestion networkSuggestion1 = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        WifiNetworkSuggestion networkSuggestion2 = createWifiNetworkSuggestion(
+                placeholderConfig, passpointConfiguration, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
 
         List<WifiNetworkSuggestion> networkSuggestionList1 =
                 new ArrayList<WifiNetworkSuggestion>() {{
@@ -371,9 +422,13 @@
         assertTrue(mWifiNetworkSuggestionsManager.getAllNetworkSuggestions().isEmpty());
 
         verify(mWifiMetrics, times(4)).incrementNetworkSuggestionApiNumModification();
+        verify(mWifiMetrics, times(2)).addNetworkSuggestionPriorityGroup(anyInt());
         ArgumentCaptor<List<Integer>> maxSizesCaptor = ArgumentCaptor.forClass(List.class);
         verify(mWifiMetrics, times(4)).noteNetworkSuggestionApiListSizeHistogram(
                 maxSizesCaptor.capture());
+        // Only non-passpoint suggestion will trigger remove connect choice, passpoint suggestion
+        // will trigger this in passpointManager
+        verify(mWifiConfigManager).removeConnectChoiceFromAllNetworks(anyString());
         assertNotNull(maxSizesCaptor.getValue());
         assertEquals(maxSizesCaptor.getValue(), new ArrayList<Integer>() {{ add(1); add(1); }});
     }
@@ -385,8 +440,9 @@
     public void testAddRemoveSuggestionBeforeUserDataLoaded() {
         // Clear the data source, and user data store is not loaded
         mDataSource.reset();
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList = Arrays.asList(networkSuggestion);
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL,
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
@@ -398,10 +454,12 @@
 
     @Test
     public void testAddRemoveEnterpriseNetworkSuggestion() {
-        WifiNetworkSuggestion networkSuggestion1 = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createEapNetwork(), null, false, false, true, true);
-        WifiNetworkSuggestion networkSuggestion2 = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createEapNetwork(), null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion1 = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createEapNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        WifiNetworkSuggestion networkSuggestion2 = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createEapNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
 
         List<WifiNetworkSuggestion> networkSuggestionList =
                 new ArrayList<WifiNetworkSuggestion>() {{
@@ -419,8 +477,9 @@
         Set<WifiNetworkSuggestion> allNetworkSuggestions =
                 mWifiNetworkSuggestionsManager.getAllNetworkSuggestions();
         assertEquals(1, allNetworkSuggestions.size());
-        WifiNetworkSuggestion removingSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createEapNetwork(), null, false, false, true, true);
+        WifiNetworkSuggestion removingSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createEapNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         removingSuggestion.wifiConfiguration.SSID = networkSuggestion1.wifiConfiguration.SSID;
         // Remove with the networkSuggestion from external.
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
@@ -433,17 +492,61 @@
     }
 
     @Test
+    public void testAddNetworkSuggestionWithInvalidKeyChainKeyAlias() {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        final WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork();
+        config.enterpriseConfig.setClientKeyPairAlias("some-alias");
+        final WifiNetworkSuggestion networkSuggestion1 = createWifiNetworkSuggestion(
+                config, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
+        final List<WifiNetworkSuggestion> networkSuggestionList =
+                new ArrayList<WifiNetworkSuggestion>() {{
+                    add(networkSuggestion1);
+                }};
+        when(mWifiKeyStore.updateNetworkKeys(eq(networkSuggestion1.wifiConfiguration), any()))
+                .thenReturn(true);
+        when(mWifiKeyStore.validateKeyChainAlias(any(String.class), anyInt())).thenReturn(false);
+
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_INVALID,
+                mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
+                        TEST_PACKAGE_1, TEST_FEATURE));
+    }
+
+    @Test
+    public void testAddNetworkSuggestionWithValidKeyChainKeyAlias() {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        final WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork();
+        config.enterpriseConfig.setClientKeyPairAlias("some-alias");
+        final WifiNetworkSuggestion networkSuggestion1 = createWifiNetworkSuggestion(
+                config, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
+        final List<WifiNetworkSuggestion> networkSuggestionList =
+                new ArrayList<WifiNetworkSuggestion>() {{
+                    add(networkSuggestion1);
+                }};
+        when(mWifiKeyStore.updateNetworkKeys(eq(networkSuggestion1.wifiConfiguration), any()))
+                .thenReturn(true);
+        when(mWifiKeyStore.validateKeyChainAlias(any(String.class), anyInt())).thenReturn(true);
+
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
+                mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
+                        TEST_PACKAGE_1, TEST_FEATURE));
+    }
+
+    @Test
     public void testAddInsecureEnterpriseNetworkSuggestion() {
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createEapNetwork(), null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createEapNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         networkSuggestion.wifiConfiguration.enterpriseConfig.setCaPath(null);
         List<WifiNetworkSuggestion> networkSuggestionList = Arrays.asList(networkSuggestion);
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_INVALID,
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
                         TEST_PACKAGE_1, TEST_FEATURE));
 
-        networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createEapNetwork(), null, false, false, true, true);
+        networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createEapNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         networkSuggestion.wifiConfiguration.enterpriseConfig.setDomainSuffixMatch("");
         networkSuggestionList = Arrays.asList(networkSuggestion);
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_INVALID,
@@ -451,6 +554,60 @@
                         TEST_PACKAGE_1, TEST_FEATURE));
     }
 
+    @Test
+    public void testAddOemPaidNetworkSuggestionOnPreSDevices() {
+        assumeFalse(SdkLevel.isAtLeastS());
+
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createEapNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        networkSuggestion.wifiConfiguration.oemPaid = true;
+        List<WifiNetworkSuggestion> networkSuggestionList = Arrays.asList(networkSuggestion);
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_INVALID,
+                mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
+                        TEST_PACKAGE_1, TEST_FEATURE));
+    }
+
+
+    @Test
+    public void testAddOemPrivateNetworkSuggestionOnPreSDevices() {
+        assumeFalse(SdkLevel.isAtLeastS());
+
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createEapNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        networkSuggestion.wifiConfiguration.oemPrivate = true;
+        List<WifiNetworkSuggestion> networkSuggestionList = Arrays.asList(networkSuggestion);
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_INVALID,
+                mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
+                        TEST_PACKAGE_1, TEST_FEATURE));
+    }
+
+    @Test
+    public void testSetSubscriptionIdOnPreSDevices() {
+        assumeFalse(SdkLevel.isAtLeastS());
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createEapNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        networkSuggestion.wifiConfiguration.subscriptionId = TEST_SUBID;
+        List<WifiNetworkSuggestion> networkSuggestionList = Arrays.asList(networkSuggestion);
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_INVALID,
+                mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
+                        TEST_PACKAGE_1, TEST_FEATURE));
+    }
+
+    @Test
+    public void testSetPriorityGroupOnPreSDevices() {
+        assumeFalse(SdkLevel.isAtLeastS());
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createEapNetwork(), null, false, false, true, true,
+                TEST_PRIORITY_GROUP);
+        List<WifiNetworkSuggestion> networkSuggestionList = Arrays.asList(networkSuggestion);
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_INVALID,
+                mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
+                        TEST_PACKAGE_1, TEST_FEATURE));
+    }
+
     /**
      * Verify successful removal of all network suggestions.
      */
@@ -458,11 +615,14 @@
     public void testRemoveAllNetworkSuggestionsSuccess() {
         PasspointConfiguration passpointConfiguration =
                 createTestConfigWithUserCredential(TEST_FQDN, TEST_FRIENDLY_NAME);
-        WifiConfiguration dummyConfiguration = createDummyWifiConfigurationForPasspoint(TEST_FQDN);
-        WifiNetworkSuggestion networkSuggestion1 = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
-        WifiNetworkSuggestion networkSuggestion2 = new WifiNetworkSuggestion(
-                dummyConfiguration, passpointConfiguration, false, false, true, true);
+        WifiConfiguration placeholderConfig = createPlaceholderConfigForPasspoint(TEST_FQDN,
+                passpointConfiguration.getUniqueId());
+        WifiNetworkSuggestion networkSuggestion1 = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        WifiNetworkSuggestion networkSuggestion2 = createWifiNetworkSuggestion(
+                placeholderConfig, passpointConfiguration, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
 
 
         List<WifiNetworkSuggestion> networkSuggestionList1 =
@@ -502,8 +662,9 @@
      */
     @Test
     public void testReplaceNetworkSuggestionsSuccess() {
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
 
         List<WifiNetworkSuggestion> networkSuggestionList1 =
                 new ArrayList<WifiNetworkSuggestion>() {{
@@ -531,39 +692,99 @@
 
     /**
      * Verify that modify networks that are already active is allowed.
+     * - Adding two suggestions and not add into the WifiConfigManager - before network selection
+     * - Set user connect choice, Anonymous Identity and auto-join on suggestion
+     * - Adding the suggestions with same profile should succeed. And no WifiConfigManager update.
+     * - After in-place modify, user connect choice, Anonymous Identity and auto-join should be
+     *   preserved.
      */
     @Test
     public void testAddNetworkSuggestionsSuccessOnInPlaceModificationWhenNotInWcm() {
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
+        when(mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(TEST_UID_1))
+                .thenReturn(true);
+
+        // Create an EAP-SIM suggestion and a passpoint suggestion
+        PasspointConfiguration passpointConfiguration =
+                createTestConfigWithUserCredential(TEST_FQDN, TEST_FRIENDLY_NAME);
+        WifiConfiguration placeholderConfig = createPlaceholderConfigForPasspoint(TEST_FQDN,
+                passpointConfiguration.getUniqueId());
+        WifiConfiguration eapSimConfig = WifiConfigurationTestUtil.createEapNetwork(
+                WifiEnterpriseConfig.Eap.SIM, WifiEnterpriseConfig.Phase2.NONE);
+        WifiNetworkSuggestion networkSuggestion1 = createWifiNetworkSuggestion(
+                eapSimConfig, null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        WifiNetworkSuggestion networkSuggestion2 = createWifiNetworkSuggestion(
+                placeholderConfig, passpointConfiguration, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList1 =
                 new ArrayList<WifiNetworkSuggestion>() {{
-                add(networkSuggestion);
-            }};
+                    add(networkSuggestion1);
+                    add(networkSuggestion2);
+                }};
 
+        // Verify adding suggestion succeed.
+        when(mPasspointManager.addOrUpdateProvider(any(),
+                anyInt(), anyString(), eq(true), anyBoolean())).thenReturn(true);
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList1, TEST_UID_1,
                         TEST_PACKAGE_1, TEST_FEATURE));
-
-        // Assert that the original config was not metered.
-        assertEquals(WifiConfiguration.METERED_OVERRIDE_NONE,
-                networkSuggestion.wifiConfiguration.meteredOverride);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
 
         // Nothing in WCM.
-        when(mWifiConfigManager.getConfiguredNetwork(networkSuggestion.wifiConfiguration.getKey()))
-                .thenReturn(null);
+        when(mWifiConfigManager.getConfiguredNetwork(networkSuggestion1.wifiConfiguration
+                .getProfileKey())).thenReturn(null);
+        when(mWifiConfigManager.getConfiguredNetwork(networkSuggestion2.wifiConfiguration
+                .getProfileKey())).thenReturn(null);
 
-        // Modify the original suggestion to mark it metered.
-        networkSuggestion.wifiConfiguration.meteredOverride =
-                WifiConfiguration.METERED_OVERRIDE_METERED;
+        // Set user connect choice, Anonymous Identity and auto join.
+        WifiConfiguration config = new WifiConfiguration(eapSimConfig);
+        config.fromWifiNetworkSuggestion = true;
+        config.ephemeral = true;
+        config.creatorName = TEST_PACKAGE_1;
+        config.creatorUid = TEST_UID_1;
+        config.enterpriseConfig.setAnonymousIdentity(TEST_ANONYMOUS_IDENTITY);
+        WifiConfigManager.OnNetworkUpdateListener listener = mNetworkListenerCaptor.getValue();
+        listener.onConnectChoiceSet(List.of(config), USER_CONNECT_CHOICE, TEST_RSSI);
+        mWifiNetworkSuggestionsManager.setAnonymousIdentity(config);
+        mWifiNetworkSuggestionsManager.allowNetworkSuggestionAutojoin(config, false);
+
+        // Keep the same suggestion as auto-join enabled, but user already mark it disabled.
+        WifiNetworkSuggestion networkSuggestion3 = createWifiNetworkSuggestion(
+                eapSimConfig, null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        // Modify the same suggestion to mark it auto-join disabled.
+        WifiNetworkSuggestion networkSuggestion4 = createWifiNetworkSuggestion(
+                placeholderConfig, passpointConfiguration, false, false, true, false,
+                DEFAULT_PRIORITY_GROUP);
+        List<WifiNetworkSuggestion> networkSuggestionList2 =
+                new ArrayList<WifiNetworkSuggestion>() {{
+                    add(networkSuggestion3);
+                    add(networkSuggestion4);
+                }};
 
         // Replace attempt should success.
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
-                mWifiNetworkSuggestionsManager.add(networkSuggestionList1, TEST_UID_1,
+                mWifiNetworkSuggestionsManager.add(networkSuggestionList2, TEST_UID_1,
                         TEST_PACKAGE_1, TEST_FEATURE));
-        assertEquals(WifiConfiguration.METERED_OVERRIDE_METERED,
-                mWifiNetworkSuggestionsManager
-                        .get(TEST_PACKAGE_1, TEST_UID_1).get(0).wifiConfiguration.meteredOverride);
+
+        Set<ExtendedWifiNetworkSuggestion> matchedPasspointSuggestions =
+                mWifiNetworkSuggestionsManager.getNetworkSuggestionsForFqdn(TEST_FQDN);
+        assertEquals(1, matchedPasspointSuggestions.size());
+        // As user didn't change the auto-join, will follow the newly added one.
+        assertFalse(matchedPasspointSuggestions.iterator().next().isAutojoinEnabled);
+
+        ScanDetail scanDetail = createScanDetailForNetwork(eapSimConfig);
+        Set<ExtendedWifiNetworkSuggestion> matchedSuggestions =
+                mWifiNetworkSuggestionsManager.getNetworkSuggestionsForScanDetail(scanDetail);
+        assertEquals(1, matchedSuggestions.size());
+        ExtendedWifiNetworkSuggestion matchedSuggestion = matchedSuggestions.iterator().next();
+        // As user changes the auto-join, will keep the user choice.
+        assertFalse(matchedSuggestion.isAutojoinEnabled);
+        // Verify user connect choice and Anonymous Identity are preserved during the modify.
+        assertEquals(TEST_ANONYMOUS_IDENTITY, matchedSuggestion.anonymousIdentity);
+        assertEquals(USER_CONNECT_CHOICE, matchedSuggestion.connectChoice);
+        assertEquals(TEST_RSSI, matchedSuggestion.connectChoiceRssi);
+
         // Verify we did not update config in WCM.
         verify(mWifiConfigManager, never()).addOrUpdateNetwork(any(), anyInt(), any());
     }
@@ -574,8 +795,9 @@
      */
     @Test
     public void testAddNetworkSuggestionsSuccessOnInPlaceModificationWhenInWcm() {
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList1 =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -630,8 +852,9 @@
         // Add the max per app first.
         List<WifiNetworkSuggestion> networkSuggestionList = new ArrayList<>();
         for (int i = 0; i < WifiManager.NETWORK_SUGGESTIONS_MAX_PER_APP_HIGH_RAM; i++) {
-            networkSuggestionList.add(new WifiNetworkSuggestion(
-                    WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true));
+            networkSuggestionList.add(createWifiNetworkSuggestion(
+                    WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                    DEFAULT_PRIORITY_GROUP));
         }
         // The first add should succeed.
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
@@ -642,8 +865,9 @@
         // Now add 3 more.
         networkSuggestionList = new ArrayList<>();
         for (int i = 0; i < 3; i++) {
-            networkSuggestionList.add(new WifiNetworkSuggestion(
-                    WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true));
+            networkSuggestionList.add(createWifiNetworkSuggestion(
+                    WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                    DEFAULT_PRIORITY_GROUP));
         }
         // The second add should fail.
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_EXCEEDS_MAX_PER_APP,
@@ -663,8 +887,9 @@
         // Now add 2 more.
         networkSuggestionList = new ArrayList<>();
         for (int i = 0; i < 2; i++) {
-            networkSuggestionList.add(new WifiNetworkSuggestion(
-                    WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true));
+            networkSuggestionList.add(createWifiNetworkSuggestion(
+                    WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                    DEFAULT_PRIORITY_GROUP));
         }
         // This add should now succeed.
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
@@ -672,15 +897,34 @@
                         TEST_PACKAGE_1, TEST_FEATURE));
     }
 
+    @Test
+    public void testAddNetworkSuggestionWithMismatchBetweenCarrierIdAndSubId() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createEapNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        networkSuggestion.wifiConfiguration.carrierId = TEST_CARRIER_ID;
+        networkSuggestion.wifiConfiguration.subscriptionId = TEST_SUBID;
+        when(mWifiCarrierInfoManager
+                .isSubIdMatchingCarrierId(anyInt(), anyInt())).thenReturn(false);
+        List<WifiNetworkSuggestion> networkSuggestionList = Arrays.asList(networkSuggestion);
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_NOT_ALLOWED,
+                mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
+                        TEST_PACKAGE_1, TEST_FEATURE));
+
+    }
+
     /**
      * Verify that an attempt to remove an invalid set of network suggestions is rejected.
      */
     @Test
     public void testRemoveNetworkSuggestionsFailureOnInvalid() {
-        WifiNetworkSuggestion networkSuggestion1 = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
-        WifiNetworkSuggestion networkSuggestion2 = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion1 = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        WifiNetworkSuggestion networkSuggestion2 = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
 
         List<WifiNetworkSuggestion> networkSuggestionList1 =
                 new ArrayList<WifiNetworkSuggestion>() {{
@@ -705,8 +949,9 @@
     @Test
     public void
             testGetNetworkSuggestionsForScanDetailSuccessWithOneMatchForCarrierProvisioningApp() {
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList1 =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -739,10 +984,11 @@
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_INVALID,
                 mWifiNetworkSuggestionsManager.add(Collections.singletonList(null),
                         TEST_UID_1, TEST_PACKAGE_1, TEST_FEATURE));
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
-                mWifiNetworkSuggestionsManager.add(Collections.singletonList(networkSuggestion),
+                mWifiNetworkSuggestionsManager.add(List.of(networkSuggestion),
                         TEST_UID_1, TEST_PACKAGE_1, TEST_FEATURE));
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID,
                 mWifiNetworkSuggestionsManager.remove(Collections.singletonList(null),
@@ -754,8 +1000,9 @@
      */
     @Test
     public void testGetNetworkSuggestionsForScanDetailSuccessWithOneMatch() {
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList1 =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -763,7 +1010,82 @@
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList1, TEST_UID_1,
                         TEST_PACKAGE_1, TEST_FEATURE));
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
+
+        ScanDetail scanDetail = createScanDetailForNetwork(networkSuggestion.wifiConfiguration);
+
+        Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestions =
+                mWifiNetworkSuggestionsManager.getNetworkSuggestionsForScanDetail(scanDetail);
+        Set<WifiNetworkSuggestion> expectedMatchingNetworkSuggestions =
+                new HashSet<WifiNetworkSuggestion>() {{
+                    add(networkSuggestion);
+                }};
+        assertSuggestionsEquals(expectedMatchingNetworkSuggestions, matchingExtNetworkSuggestions);
+    }
+
+    /**
+     * Verify a successful lookup of a single network suggestion matching the provided scan detail.
+     *
+     * The wifi configuration in the network suggestion is a type which could have upgradable types.
+     */
+    @Test
+    public void testGetNetworkSuggestionsForScanDetailSuccessWithOneMatchForUpgradableConfig() {
+        WifiConfiguration upgradableConfig = new WifiConfiguration();
+        upgradableConfig.SSID = "\"test\"";
+        upgradableConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
+        upgradableConfig.preSharedKey = "\"PassW0rd\"";
+
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                upgradableConfig, null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        List<WifiNetworkSuggestion> networkSuggestionList1 =
+                new ArrayList<WifiNetworkSuggestion>() {{
+                    add(networkSuggestion);
+                }};
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
+                mWifiNetworkSuggestionsManager.add(networkSuggestionList1, TEST_UID_1,
+                        TEST_PACKAGE_1, TEST_FEATURE));
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
+
+        ScanDetail scanDetail = createScanDetailForNetwork(networkSuggestion.wifiConfiguration);
+
+        Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestions =
+                mWifiNetworkSuggestionsManager.getNetworkSuggestionsForScanDetail(scanDetail);
+        Set<WifiNetworkSuggestion> expectedMatchingNetworkSuggestions =
+                new HashSet<WifiNetworkSuggestion>() {{
+                    add(networkSuggestion);
+                }};
+        assertSuggestionsEquals(expectedMatchingNetworkSuggestions, matchingExtNetworkSuggestions);
+    }
+
+
+    /**
+     * Verify a successful lookup of a single network suggestion matching the provided scan detail.
+     *
+     * The wifi configuration in the network suggestion is a leagcy object, says no security params
+     * list, and only raw fields are set.
+     */
+    @Test
+    public void testGetNetworkSuggestionsForScanDetailSuccessWithOneMatchForLegacyConfig() {
+        WifiConfiguration legacyConfig = new WifiConfiguration();
+        legacyConfig.SSID = "\"test\"";
+        legacyConfig.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
+        legacyConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+        legacyConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
+        legacyConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
+        legacyConfig.preSharedKey = "\"PassW0rd\"";
+
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                legacyConfig, null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        List<WifiNetworkSuggestion> networkSuggestionList1 =
+                new ArrayList<WifiNetworkSuggestion>() {{
+                    add(networkSuggestion);
+                }};
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
+                mWifiNetworkSuggestionsManager.add(networkSuggestionList1, TEST_UID_1,
+                        TEST_PACKAGE_1, TEST_FEATURE));
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
 
         ScanDetail scanDetail = createScanDetailForNetwork(networkSuggestion.wifiConfiguration);
 
@@ -782,11 +1104,11 @@
     @Test
     public void testGetNetworkSuggestionsForScanDetailSuccessWithMultipleMatch() {
         WifiConfiguration wifiConfiguration = WifiConfigurationTestUtil.createOpenNetwork();
-        WifiNetworkSuggestion networkSuggestion1 = new WifiNetworkSuggestion(
-                wifiConfiguration, null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion1 = createWifiNetworkSuggestion(
+                wifiConfiguration, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
         // Reuse the same network credentials to ensure they both match.
-        WifiNetworkSuggestion networkSuggestion2 = new WifiNetworkSuggestion(
-                wifiConfiguration, null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion2 = createWifiNetworkSuggestion(
+                wifiConfiguration, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
 
         List<WifiNetworkSuggestion> networkSuggestionList1 =
                 new ArrayList<WifiNetworkSuggestion>() {{
@@ -803,8 +1125,8 @@
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList2, TEST_UID_2,
                         TEST_PACKAGE_2, TEST_FEATURE));
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_2);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_2, TEST_PACKAGE_2);
 
         ScanDetail scanDetail = createScanDetailForNetwork(networkSuggestion1.wifiConfiguration);
 
@@ -827,8 +1149,8 @@
         ScanDetail scanDetail = createScanDetailForNetwork(wifiConfiguration);
         wifiConfiguration.BSSID = scanDetail.getBSSIDString();
 
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                wifiConfiguration, null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                wifiConfiguration, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList1 =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -836,7 +1158,7 @@
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList1, TEST_UID_1,
                         TEST_PACKAGE_1, TEST_FEATURE));
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
 
         Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestions =
                 mWifiNetworkSuggestionsManager.getNetworkSuggestionsForScanDetail(scanDetail);
@@ -856,11 +1178,11 @@
         ScanDetail scanDetail = createScanDetailForNetwork(wifiConfiguration);
         wifiConfiguration.BSSID = scanDetail.getBSSIDString();
 
-        WifiNetworkSuggestion networkSuggestion1 = new WifiNetworkSuggestion(
-                wifiConfiguration, null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion1 = createWifiNetworkSuggestion(
+                wifiConfiguration, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
         // Reuse the same network credentials to ensure they both match.
-        WifiNetworkSuggestion networkSuggestion2 = new WifiNetworkSuggestion(
-                wifiConfiguration, null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion2 = createWifiNetworkSuggestion(
+                wifiConfiguration, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
 
         List<WifiNetworkSuggestion> networkSuggestionList1 =
                 new ArrayList<WifiNetworkSuggestion>() {{
@@ -877,8 +1199,8 @@
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList2, TEST_UID_2,
                         TEST_PACKAGE_2, TEST_FEATURE));
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_2);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_2, TEST_PACKAGE_2);
 
         Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestions =
                 mWifiNetworkSuggestionsManager.getNetworkSuggestionsForScanDetail(scanDetail);
@@ -900,11 +1222,11 @@
         ScanDetail scanDetail = createScanDetailForNetwork(wifiConfiguration);
         wifiConfiguration.BSSID = scanDetail.getBSSIDString();
 
-        WifiNetworkSuggestion networkSuggestion1 = new WifiNetworkSuggestion(
-                wifiConfiguration, null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion1 = createWifiNetworkSuggestion(
+                wifiConfiguration, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
         // Reuse the same network credentials to ensure they both match.
-        WifiNetworkSuggestion networkSuggestion2 = new WifiNetworkSuggestion(
-                wifiConfiguration, null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion2 = createWifiNetworkSuggestion(
+                wifiConfiguration, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
 
         List<WifiNetworkSuggestion> networkSuggestionList =
                 new ArrayList<WifiNetworkSuggestion>() {{
@@ -915,7 +1237,7 @@
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
                         TEST_PACKAGE_1, TEST_FEATURE));
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
 
         Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestions =
                 mWifiNetworkSuggestionsManager.getNetworkSuggestionsForScanDetail(scanDetail);
@@ -938,11 +1260,11 @@
         WifiConfiguration wifiConfiguration2 = new WifiConfiguration(wifiConfiguration1);
         wifiConfiguration2.BSSID = scanDetail.getBSSIDString();
 
-        WifiNetworkSuggestion networkSuggestion1 = new WifiNetworkSuggestion(
-                wifiConfiguration1, null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion1 = createWifiNetworkSuggestion(
+                wifiConfiguration1, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
         // Reuse the same network credentials to ensure they both match.
-        WifiNetworkSuggestion networkSuggestion2 = new WifiNetworkSuggestion(
-                wifiConfiguration2, null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion2 = createWifiNetworkSuggestion(
+                wifiConfiguration2, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
 
         List<WifiNetworkSuggestion> networkSuggestionList1 =
                 new ArrayList<WifiNetworkSuggestion>() {{
@@ -959,8 +1281,8 @@
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList2, TEST_UID_2,
                         TEST_PACKAGE_2, TEST_FEATURE));
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_2);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_2, TEST_PACKAGE_2);
 
         Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestions =
                 mWifiNetworkSuggestionsManager.getNetworkSuggestionsForScanDetail(scanDetail);
@@ -989,8 +1311,9 @@
      */
     @Test
     public void testGetNetworkSuggestionsForScanDetailFailureOnAppNotApproved() {
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList1 =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -1002,7 +1325,8 @@
 
         ScanDetail scanDetail = createScanDetailForNetwork(networkSuggestion.wifiConfiguration);
 
-        assertNull(mWifiNetworkSuggestionsManager.getNetworkSuggestionsForScanDetail(scanDetail));
+        assertTrue(mWifiNetworkSuggestionsManager
+                .getNetworkSuggestionsForScanDetail(scanDetail).isEmpty());
     }
 
     /**
@@ -1011,8 +1335,8 @@
     @Test
     public void testGetNetworkSuggestionsForScanDetailFailureOnSuggestionRemoval() {
         WifiConfiguration wifiConfiguration = WifiConfigurationTestUtil.createOpenNetwork();
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                wifiConfiguration, null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                wifiConfiguration, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
         ScanDetail scanDetail = createScanDetailForNetwork(wifiConfiguration);
         List<WifiNetworkSuggestion> networkSuggestionList1 =
                 new ArrayList<WifiNetworkSuggestion>() {{
@@ -1023,15 +1347,16 @@
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList1, TEST_UID_1,
                         TEST_PACKAGE_1, TEST_FEATURE));
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
         assertNotNull(mWifiNetworkSuggestionsManager.getNetworkSuggestionsForScanDetail(
                 scanDetail));
 
         // remove the suggestion & ensure lookup fails.
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
-                mWifiNetworkSuggestionsManager.remove(Collections.EMPTY_LIST, TEST_UID_1,
+                mWifiNetworkSuggestionsManager.remove(List.of(), TEST_UID_1,
                         TEST_PACKAGE_1));
-        assertNull(mWifiNetworkSuggestionsManager.getNetworkSuggestionsForScanDetail(scanDetail));
+        assertTrue(mWifiNetworkSuggestionsManager
+                .getNetworkSuggestionsForScanDetail(scanDetail).isEmpty());
     }
 
     /**
@@ -1039,8 +1364,9 @@
      */
     @Test
     public void testGetNetworkSuggestionsForScanDetailFailureOnWrongNetwork() {
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList1 =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -1048,13 +1374,14 @@
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList1, TEST_UID_1,
                         TEST_PACKAGE_1, TEST_FEATURE));
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
 
         // Create a scan result corresponding to a different network.
         ScanDetail scanDetail = createScanDetailForNetwork(
                 WifiConfigurationTestUtil.createPskNetwork());
 
-        assertNull(mWifiNetworkSuggestionsManager.getNetworkSuggestionsForScanDetail(scanDetail));
+        assertTrue(mWifiNetworkSuggestionsManager
+                .getNetworkSuggestionsForScanDetail(scanDetail).isEmpty());
     }
 
     /**
@@ -1067,11 +1394,11 @@
      */
     @Test
     public void testOnNetworkConnectionSuccessWithOneMatch() throws Exception {
-        assertTrue(mWifiNetworkSuggestionsManager
-                .registerSuggestionConnectionStatusListener(mBinder, mListener,
-                        NETWORK_CALLBACK_ID, TEST_PACKAGE_1, TEST_UID_1));
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true);
+        assertTrue(mWifiNetworkSuggestionsManager.registerSuggestionConnectionStatusListener(
+                mConnectionStatusListener, TEST_PACKAGE_1, TEST_UID_1));
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -1079,7 +1406,7 @@
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
                         TEST_PACKAGE_1, TEST_FEATURE));
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
 
         // Simulate connecting to the network.
         WifiConfiguration connectNetwork =
@@ -1104,13 +1431,13 @@
 
     @Test
     public void testOnNetworkConnectionSuccessWithOneMatchFromCarrierPrivilegedApp() {
-        assertTrue(mWifiNetworkSuggestionsManager
-                .registerSuggestionConnectionStatusListener(mBinder, mListener,
-                        NETWORK_CALLBACK_ID, TEST_PACKAGE_1, TEST_UID_1));
+        assertTrue(mWifiNetworkSuggestionsManager.registerSuggestionConnectionStatusListener(
+                mConnectionStatusListener, TEST_PACKAGE_1, TEST_UID_1));
         when(mWifiCarrierInfoManager.getCarrierIdForPackageWithCarrierPrivileges(TEST_PACKAGE_1))
                 .thenReturn(TEST_CARRIER_ID);
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createPskNetwork(), null, true, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createPskNetwork(), null, true, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -1140,7 +1467,8 @@
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiNetworkSuggestionsManager.remove(networkSuggestionList, TEST_UID_1,
                         TEST_PACKAGE_1));
-        verify(mWifiConfigManager).removeSuggestionConfiguredNetwork(anyString());
+        verify(mWifiConfigManager).removeSuggestionConfiguredNetwork(
+                argThat(new WifiConfigMatcher(networkSuggestion.wifiConfiguration)));
         mInorder.verify(mWifiPermissionsUtil).doesUidBelongToCurrentUser(eq(TEST_UID_1));
 
         // Verify no more broadcast were sent out.
@@ -1154,14 +1482,14 @@
      */
     @Test
     public void testOnSavedOpenNetworkConnectionSuccessWithMultipleMatch() throws Exception {
-        assertTrue(mWifiNetworkSuggestionsManager
-                .registerSuggestionConnectionStatusListener(mBinder, mListener,
-                        NETWORK_CALLBACK_ID, TEST_PACKAGE_1, TEST_UID_1));
+        assertTrue(mWifiNetworkSuggestionsManager.registerSuggestionConnectionStatusListener(
+                mConnectionStatusListener, TEST_PACKAGE_1, TEST_UID_1));
         when(mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(TEST_UID_1))
                 .thenReturn(true);
-        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
-        WifiNetworkSuggestion networkSuggestion1 = new WifiNetworkSuggestion(
-                new WifiConfiguration(config), null, true, false, true, true);
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenOweNetwork();
+        WifiNetworkSuggestion networkSuggestion1 = createWifiNetworkSuggestion(
+                new WifiConfiguration(config), null, true, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList1 =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion1);
@@ -1170,8 +1498,9 @@
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList1, TEST_UID_1,
                         TEST_PACKAGE_1, TEST_FEATURE));
 
-        WifiNetworkSuggestion networkSuggestion2 = new WifiNetworkSuggestion(
-                new WifiConfiguration(config), null, true, false, true, true);
+        WifiNetworkSuggestion networkSuggestion2 = createWifiNetworkSuggestion(
+                new WifiConfiguration(config), null, true, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList2 =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion2);
@@ -1179,7 +1508,7 @@
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList2, TEST_UID_2,
                         TEST_PACKAGE_2, TEST_FEATURE));
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_2);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_2, TEST_PACKAGE_2);
 
         // Simulate connecting to the user saved open network.
         WifiConfiguration connectNetwork = new WifiConfiguration(config);
@@ -1201,12 +1530,12 @@
             throws Exception {
         ArgumentCaptor<IBinder.DeathRecipient> drCaptor =
                 ArgumentCaptor.forClass(IBinder.DeathRecipient.class);
-        assertTrue(mWifiNetworkSuggestionsManager
-                .registerSuggestionConnectionStatusListener(mBinder, mListener,
-                        NETWORK_CALLBACK_ID, TEST_PACKAGE_1, TEST_UID_1));
+        assertTrue(mWifiNetworkSuggestionsManager.registerSuggestionConnectionStatusListener(
+                mConnectionStatusListener, TEST_PACKAGE_1, TEST_UID_1));
         verify(mBinder).linkToDeath(drCaptor.capture(), anyInt());
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -1214,17 +1543,16 @@
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
                         TEST_PACKAGE_1, TEST_FEATURE));
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
         // Simulate binder was died.
         drCaptor.getValue().binderDied();
         mLooper.dispatchAll();
-        verify(mBinder).unlinkToDeath(any(), anyInt());
         // Simulate connecting to the network.
         WifiConfiguration connectNetwork =
                 new WifiConfiguration(networkSuggestion.wifiConfiguration);
         connectNetwork.fromWifiNetworkSuggestion = true;
         connectNetwork.ephemeral = true;
-        connectNetwork.creatorName = TEST_APP_NAME_1;
+        connectNetwork.creatorName = TEST_PACKAGE_1;
         connectNetwork.creatorUid = TEST_UID_1;
         mWifiNetworkSuggestionsManager.handleConnectionAttemptEnded(
                 WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE,
@@ -1234,7 +1562,7 @@
         // Verify no connection failure event was sent out.
         mInorder.verify(mWifiPermissionsUtil, never()).enforceCanAccessScanResults(
                 eq(TEST_PACKAGE_1), eq(TEST_FEATURE), eq(TEST_UID_1), nullable(String.class));
-        verify(mListener, never()).onConnectionStatus(any(), anyInt());
+        verify(mConnectionStatusListener, never()).onConnectionStatus(any(), anyInt());
 
         // Verify no more broadcast were sent out.
         mInorder.verify(mContext,  never()).sendBroadcastAsUser(
@@ -1251,11 +1579,11 @@
      */
     @Test
     public void testOnNetworkConnectionFailureWithOneMatch() throws Exception {
-        assertTrue(mWifiNetworkSuggestionsManager
-                .registerSuggestionConnectionStatusListener(mBinder, mListener,
-                        NETWORK_CALLBACK_ID, TEST_PACKAGE_1, TEST_UID_1));
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true);
+        assertTrue(mWifiNetworkSuggestionsManager.registerSuggestionConnectionStatusListener(
+                mConnectionStatusListener, TEST_PACKAGE_1, TEST_UID_1));
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -1263,12 +1591,12 @@
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
                         TEST_PACKAGE_1, TEST_FEATURE));
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
         WifiConfiguration connectNetwork =
                 new WifiConfiguration(networkSuggestion.wifiConfiguration);
         connectNetwork.fromWifiNetworkSuggestion = true;
         connectNetwork.ephemeral = true;
-        connectNetwork.creatorName = TEST_APP_NAME_1;
+        connectNetwork.creatorName = TEST_PACKAGE_1;
         connectNetwork.creatorUid = TEST_UID_1;
         // Simulate connecting to the network.
         mWifiNetworkSuggestionsManager.handleConnectionAttemptEnded(
@@ -1277,7 +1605,7 @@
         // Verify right callback were sent out.
         mInorder.verify(mWifiPermissionsUtil).enforceCanAccessScanResults(eq(TEST_PACKAGE_1),
                 eq(TEST_FEATURE), eq(TEST_UID_1), nullable(String.class));
-        verify(mListener)
+        verify(mConnectionStatusListener)
                 .onConnectionStatus(networkSuggestion,
                         WifiManager.STATUS_SUGGESTION_CONNECTION_FAILURE_IP_PROVISIONING);
         verify(mWifiMetrics).incrementNetworkSuggestionApiNumConnectFailure();
@@ -1296,15 +1624,17 @@
      */
     @Test
     public void testOnNetworkConnectionSuccessWithMultipleMatch() {
-        WifiConfiguration wifiConfiguration = WifiConfigurationTestUtil.createOpenNetwork();
-        WifiNetworkSuggestion networkSuggestion1 = new WifiNetworkSuggestion(
-                wifiConfiguration, null, true, false, true, true);
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+        WifiNetworkSuggestion networkSuggestion1 = createWifiNetworkSuggestion(
+                new WifiConfiguration(config), null, true, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList1 =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion1);
                 }};
-        WifiNetworkSuggestion networkSuggestion2 = new WifiNetworkSuggestion(
-                wifiConfiguration, null, true, false, true, true);
+        WifiNetworkSuggestion networkSuggestion2 = createWifiNetworkSuggestion(
+                new WifiConfiguration(config), null, true, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList2 =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion2);
@@ -1316,8 +1646,8 @@
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList2, TEST_UID_2,
                         TEST_PACKAGE_2, TEST_FEATURE));
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_2);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_2, TEST_PACKAGE_2);
 
         WifiConfiguration connectNetwork =
                 new WifiConfiguration(networkSuggestion1.wifiConfiguration);
@@ -1356,16 +1686,18 @@
      */
     @Test
     public void testOnNetworkConnectionSuccessWithBssidMultipleMatch() {
-        WifiConfiguration wifiConfiguration = WifiConfigurationTestUtil.createOpenNetwork();
-        wifiConfiguration.BSSID = TEST_BSSID;
-        WifiNetworkSuggestion networkSuggestion1 = new WifiNetworkSuggestion(
-                wifiConfiguration, null, true, false, true, true);
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenOweNetwork();
+        config.BSSID = TEST_BSSID;
+        WifiNetworkSuggestion networkSuggestion1 = createWifiNetworkSuggestion(
+                new WifiConfiguration(config), null, true, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList1 =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion1);
                 }};
-        WifiNetworkSuggestion networkSuggestion2 = new WifiNetworkSuggestion(
-                wifiConfiguration, null, true, false, true, true);
+        WifiNetworkSuggestion networkSuggestion2 = createWifiNetworkSuggestion(
+                new WifiConfiguration(config), null, true, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList2 =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion2);
@@ -1377,10 +1709,9 @@
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList2, TEST_UID_2,
                         TEST_PACKAGE_2, TEST_FEATURE));
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_2);
-        WifiConfiguration connectNetwork =
-                new WifiConfiguration(networkSuggestion1.wifiConfiguration);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_2, TEST_PACKAGE_2);
+        WifiConfiguration connectNetwork = new WifiConfiguration(config);
         connectNetwork.fromWifiNetworkSuggestion = true;
         connectNetwork.ephemeral = true;
         connectNetwork.creatorName = TEST_PACKAGE_1;
@@ -1419,14 +1750,14 @@
         WifiConfiguration wifiConfiguration1 = WifiConfigurationTestUtil.createOpenNetwork();
         WifiConfiguration wifiConfiguration2 = new WifiConfiguration(wifiConfiguration1);
         wifiConfiguration2.BSSID = TEST_BSSID;
-        WifiNetworkSuggestion networkSuggestion1 = new WifiNetworkSuggestion(
-                wifiConfiguration1, null, true, false, true, true);
+        WifiNetworkSuggestion networkSuggestion1 = createWifiNetworkSuggestion(
+                wifiConfiguration1, null, true, false, true, true, DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList1 =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion1);
                 }};
-        WifiNetworkSuggestion networkSuggestion2 = new WifiNetworkSuggestion(
-                wifiConfiguration2, null, true, false, true, true);
+        WifiNetworkSuggestion networkSuggestion2 = createWifiNetworkSuggestion(
+                wifiConfiguration2, null, true, false, true, true, DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList2 =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion2);
@@ -1438,8 +1769,8 @@
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList2, TEST_UID_2,
                         TEST_PACKAGE_2, TEST_FEATURE));
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_2);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_2, TEST_PACKAGE_2);
 
         WifiConfiguration connectNetwork =
                 new WifiConfiguration(networkSuggestion1.wifiConfiguration);
@@ -1480,8 +1811,9 @@
      */
     @Test
     public void testOnNetworkConnectionWhenAppNotApproved() {
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -1497,7 +1829,7 @@
                 new WifiConfiguration(networkSuggestion.wifiConfiguration);
         connectNetwork.fromWifiNetworkSuggestion = true;
         connectNetwork.ephemeral = true;
-        connectNetwork.creatorName = TEST_APP_NAME_1;
+        connectNetwork.creatorName = TEST_PACKAGE_1;
         connectNetwork.creatorUid = TEST_UID_1;
 
         // Simulate connecting to the network.
@@ -1520,8 +1852,9 @@
      */
     @Test
     public void testOnNetworkConnectionWhenIsAppInteractionRequiredNotSet() {
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -1531,13 +1864,13 @@
                         TEST_PACKAGE_1, TEST_FEATURE));
         verify(mWifiPermissionsUtil, times(2))
                 .checkNetworkCarrierProvisioningPermission(TEST_UID_1);
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
 
         WifiConfiguration connectNetwork =
                 new WifiConfiguration(networkSuggestion.wifiConfiguration);
         connectNetwork.fromWifiNetworkSuggestion = true;
         connectNetwork.ephemeral = true;
-        connectNetwork.creatorName = TEST_APP_NAME_1;
+        connectNetwork.creatorName = TEST_PACKAGE_1;
         connectNetwork.creatorUid = TEST_UID_1;
 
         // Simulate connecting to the network.
@@ -1560,8 +1893,9 @@
      */
     @Test
     public void testOnNetworkConnectionWhenAppDoesNotHoldLocationPermission() {
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -1571,7 +1905,7 @@
                         TEST_PACKAGE_1, TEST_FEATURE));
         verify(mWifiPermissionsUtil, times(2))
                 .checkNetworkCarrierProvisioningPermission(TEST_UID_1);
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
 
         doThrow(new SecurityException()).when(mWifiPermissionsUtil).enforceCanAccessScanResults(
                 eq(TEST_PACKAGE_1), eq(TEST_FEATURE), eq(TEST_UID_1), nullable(String.class));
@@ -1580,7 +1914,7 @@
                 new WifiConfiguration(networkSuggestion.wifiConfiguration);
         connectNetwork.fromWifiNetworkSuggestion = true;
         connectNetwork.ephemeral = true;
-        connectNetwork.creatorName = TEST_APP_NAME_1;
+        connectNetwork.creatorName = TEST_PACKAGE_1;
         connectNetwork.creatorUid = TEST_UID_1;
 
         // Simulate connecting to the network.
@@ -1599,8 +1933,9 @@
      */
     @Test
     public void testAddNetworkSuggestionsConfigStoreWrite() {
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
 
         List<WifiNetworkSuggestion> networkSuggestionList =
                 new ArrayList<WifiNetworkSuggestion>() {{
@@ -1618,8 +1953,8 @@
         assertEquals(1, networkSuggestionsMapToWrite.size());
         assertTrue(networkSuggestionsMapToWrite.keySet().contains(TEST_PACKAGE_1));
         assertFalse(networkSuggestionsMapToWrite.get(TEST_PACKAGE_1).hasUserApproved);
-        Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestionsToWrite =
-                networkSuggestionsMapToWrite.get(TEST_PACKAGE_1).extNetworkSuggestions;
+        Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestionsToWrite =
+                networkSuggestionsMapToWrite.get(TEST_PACKAGE_1).extNetworkSuggestions.values();
         Set<WifiNetworkSuggestion> expectedAllNetworkSuggestions =
                 new HashSet<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -1640,8 +1975,9 @@
      */
     @Test
     public void testRemoveNetworkSuggestionsConfigStoreWrite() {
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
 
         List<WifiNetworkSuggestion> networkSuggestionList =
                 new ArrayList<WifiNetworkSuggestion>() {{
@@ -1671,23 +2007,68 @@
     }
 
     /**
+     * Verify that the internally used WifiConfiguration created by
+     * ExtendedWifiNetworkSuggestion#createInternalWifiConfiguration forces MAC randomization off
+     * if MAC randomization should be disabled for that particular config.
+     */
+    @Test
+    public void testCarrierConfigSsidListToDisableMacRandomizationDisabled() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        PerAppInfo appInfo = new PerAppInfo(TEST_UID_1, TEST_PACKAGE_1, TEST_FEATURE);
+        appInfo.hasUserApproved = true;
+
+        // Create 2 WifiNetworkSuggestion and mock CarrierConfigManager to include the SSID
+        // of the first suggetion in the MAC randomization disabled list.
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        WifiNetworkSuggestion networkSuggestion2 = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+
+        when(mWifiCarrierInfoManager.shouldDisableMacRandomization(
+                eq(networkSuggestion.getWifiConfiguration().SSID), anyInt(),
+                anyInt())).thenReturn(true);
+        ExtendedWifiNetworkSuggestion extendedWifiNetworkSuggestion =
+                ExtendedWifiNetworkSuggestion.fromWns(networkSuggestion, appInfo, true);
+        // Verify MAC randomization is disabled for the first suggestion network.
+        assertEquals(WifiConfiguration.RANDOMIZATION_NONE,
+                extendedWifiNetworkSuggestion.createInternalWifiConfiguration(
+                        mWifiCarrierInfoManager).macRandomizationSetting);
+
+        ExtendedWifiNetworkSuggestion extendedWifiNetworkSuggestion2 =
+                ExtendedWifiNetworkSuggestion.fromWns(networkSuggestion2, appInfo, true);
+        // For simplicity, the networkSuggestion2 is created through the constructor and has
+        // macRandomizationSetting = RANDOMIZATION_AUTO. Suggestions created through the formal
+        // Builder API should have RANDOMIZATION_PERSISTENT as default.
+        assertEquals(WifiConfiguration.RANDOMIZATION_AUTO,
+                extendedWifiNetworkSuggestion2.createInternalWifiConfiguration(
+                        mWifiCarrierInfoManager).macRandomizationSetting);
+    }
+
+    /**
      * Verify handling of initial config store read.
      */
     @Test
     public void testNetworkSuggestionsConfigStoreLoad() {
         PasspointConfiguration passpointConfiguration =
                 createTestConfigWithUserCredential(TEST_FQDN, TEST_FRIENDLY_NAME);
-        WifiConfiguration dummyConfiguration = createDummyWifiConfigurationForPasspoint(TEST_FQDN);
+        WifiConfiguration placeholderConfig = createPlaceholderConfigForPasspoint(TEST_FQDN,
+                passpointConfiguration.getUniqueId());
         PerAppInfo appInfo = new PerAppInfo(TEST_UID_1, TEST_PACKAGE_1, TEST_FEATURE);
         appInfo.hasUserApproved = true;
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
-        WifiNetworkSuggestion networkSuggestion1 = new WifiNetworkSuggestion(
-                dummyConfiguration, passpointConfiguration, false, false, true, true);
-        appInfo.extNetworkSuggestions.add(
-                ExtendedWifiNetworkSuggestion.fromWns(networkSuggestion, appInfo, true));
-        appInfo.extNetworkSuggestions.add(
-                ExtendedWifiNetworkSuggestion.fromWns(networkSuggestion1, appInfo, true));
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        WifiNetworkSuggestion networkSuggestion1 = createWifiNetworkSuggestion(
+                placeholderConfig, passpointConfiguration, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        ExtendedWifiNetworkSuggestion ewns1 =
+                ExtendedWifiNetworkSuggestion.fromWns(networkSuggestion, appInfo, true);
+        appInfo.extNetworkSuggestions.put(ewns1.hashCode(), ewns1);
+        ExtendedWifiNetworkSuggestion ewns2 =
+                ExtendedWifiNetworkSuggestion.fromWns(networkSuggestion1, appInfo, true);
+        appInfo.extNetworkSuggestions.put(ewns2.hashCode(), ewns2);
         mDataSource.fromDeserialized(new HashMap<String, PerAppInfo>() {{
                         put(TEST_PACKAGE_1, appInfo);
                 }});
@@ -1739,10 +2120,12 @@
         // Read the store initially.
         PerAppInfo appInfo1 = new PerAppInfo(TEST_UID_1, TEST_PACKAGE_1, TEST_FEATURE);
         appInfo1.hasUserApproved = true;
-        WifiNetworkSuggestion networkSuggestion1 = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
-        appInfo1.extNetworkSuggestions.add(
-                ExtendedWifiNetworkSuggestion.fromWns(networkSuggestion1, appInfo1, true));
+        WifiNetworkSuggestion networkSuggestion1 = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        ExtendedWifiNetworkSuggestion ewns =
+                ExtendedWifiNetworkSuggestion.fromWns(networkSuggestion1, appInfo1, true);
+        appInfo1.extNetworkSuggestions.put(ewns.hashCode(), ewns);
         mDataSource.fromDeserialized(new HashMap<String, PerAppInfo>() {{
                     put(TEST_PACKAGE_1, appInfo1);
                 }});
@@ -1752,10 +2135,12 @@
         mDataSource.reset();
         PerAppInfo appInfo2 = new PerAppInfo(TEST_UID_2, TEST_PACKAGE_2, TEST_FEATURE);
         appInfo2.hasUserApproved = true;
-        WifiNetworkSuggestion networkSuggestion2 = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
-        appInfo2.extNetworkSuggestions.add(
-                ExtendedWifiNetworkSuggestion.fromWns(networkSuggestion2, appInfo2, true));
+        WifiNetworkSuggestion networkSuggestion2 = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        ExtendedWifiNetworkSuggestion ewns2 =
+                ExtendedWifiNetworkSuggestion.fromWns(networkSuggestion2, appInfo2, true);
+        appInfo2.extNetworkSuggestions.put(ewns2.hashCode(), ewns2);
         mDataSource.fromDeserialized(new HashMap<String, PerAppInfo>() {{
                     put(TEST_PACKAGE_2, appInfo2);
                 }});
@@ -1780,7 +2165,8 @@
 
         // Ensure that the previous network can no longer be looked up.
         ScanDetail scanDetail1 = createScanDetailForNetwork(networkSuggestion1.wifiConfiguration);
-        assertNull(mWifiNetworkSuggestionsManager.getNetworkSuggestionsForScanDetail(scanDetail1));
+        assertTrue(mWifiNetworkSuggestionsManager
+                .getNetworkSuggestionsForScanDetail(scanDetail1).isEmpty());
     }
 
     /**
@@ -1790,8 +2176,9 @@
     @Test
     public void
             testRemoveNetworkSuggestionsMatchingConnectionSuccessWithOneMatch() {
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -1799,13 +2186,13 @@
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
                         TEST_PACKAGE_1, TEST_FEATURE));
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
         // Simulate connecting to the network.
         WifiConfiguration connectNetwork =
                 new WifiConfiguration(networkSuggestion.wifiConfiguration);
         connectNetwork.fromWifiNetworkSuggestion = true;
         connectNetwork.ephemeral = true;
-        connectNetwork.creatorName = TEST_APP_NAME_1;
+        connectNetwork.creatorName = TEST_PACKAGE_1;
         connectNetwork.creatorUid = TEST_UID_1;
         mWifiNetworkSuggestionsManager.handleConnectionAttemptEnded(
                 WifiMetrics.ConnectionEvent.FAILURE_NONE, connectNetwork, TEST_BSSID);
@@ -1815,7 +2202,7 @@
                 mWifiNetworkSuggestionsManager.remove(networkSuggestionList, TEST_UID_1,
                         TEST_PACKAGE_1));
         verify(mWifiConfigManager).removeSuggestionConfiguredNetwork(
-                networkSuggestion.wifiConfiguration.getKey());
+                argThat(new WifiConfigMatcher(connectNetwork)));
     }
 
     /**
@@ -1825,8 +2212,9 @@
     @Test
     public void
             testRemoveAllNetworkSuggestionsMatchingConnectionSuccessWithOneMatch() {
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -1834,13 +2222,13 @@
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
                         TEST_PACKAGE_1, TEST_FEATURE));
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
         // Simulate connecting to the network.
         WifiConfiguration connectNetwork =
                 new WifiConfiguration(networkSuggestion.wifiConfiguration);
         connectNetwork.fromWifiNetworkSuggestion = true;
         connectNetwork.ephemeral = true;
-        connectNetwork.creatorName = TEST_APP_NAME_1;
+        connectNetwork.creatorName = TEST_PACKAGE_1;
         connectNetwork.creatorUid = TEST_UID_1;
         mWifiNetworkSuggestionsManager.handleConnectionAttemptEnded(
                 WifiMetrics.ConnectionEvent.FAILURE_NONE, connectNetwork, TEST_BSSID);
@@ -1850,26 +2238,27 @@
                 mWifiNetworkSuggestionsManager.remove(new ArrayList<>(), TEST_UID_1,
                         TEST_PACKAGE_1));
         verify(mWifiConfigManager).removeSuggestionConfiguredNetwork(
-                networkSuggestion.wifiConfiguration.getKey());
+                argThat(new WifiConfigMatcher(connectNetwork)));
     }
 
 
     /**
-     * Verify that we do not disconnect from the network if removed suggestions is not currently
-     * connected. Verify that we do disconnect from the network is removed suggestions is currently
-     * connected.
+     * Verify that we remove the profile from WifiConfigManager no matter if it is currently
+     * connected
      */
     @Test
     public void testRemoveAppMatchingConnectionSuccessWithMultipleMatch() {
-        WifiConfiguration wifiConfiguration = WifiConfigurationTestUtil.createOpenNetwork();
-        WifiNetworkSuggestion networkSuggestion1 = new WifiNetworkSuggestion(
-                wifiConfiguration, null, true, false, true, true);
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenOweNetwork();
+        WifiNetworkSuggestion networkSuggestion1 = createWifiNetworkSuggestion(
+                new WifiConfiguration(config), null, true, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList1 =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion1);
                 }};
-        WifiNetworkSuggestion networkSuggestion2 = new WifiNetworkSuggestion(
-                wifiConfiguration, null, true, false, true, true);
+        WifiNetworkSuggestion networkSuggestion2 = createWifiNetworkSuggestion(
+                new WifiConfiguration(config), null, true, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList2 =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion2);
@@ -1881,90 +2270,27 @@
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList2, TEST_UID_2,
                         TEST_PACKAGE_2, TEST_FEATURE));
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_2);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_2, TEST_PACKAGE_2);
 
         // Simulate connecting to the network.
-        WifiConfiguration connectNetwork =
-                new WifiConfiguration(wifiConfiguration);
+        WifiConfiguration connectNetwork = new WifiConfiguration(config);
         connectNetwork.fromWifiNetworkSuggestion = true;
         connectNetwork.ephemeral = true;
-        connectNetwork.creatorName = TEST_APP_NAME_1;
+        connectNetwork.creatorName = TEST_PACKAGE_1;
         connectNetwork.creatorUid = TEST_UID_1;
         mWifiNetworkSuggestionsManager.handleConnectionAttemptEnded(
                 WifiMetrics.ConnectionEvent.FAILURE_NONE, connectNetwork, TEST_BSSID);
 
-        // Now remove the current connected app and ensure we did trigger a disconnect.
+        // Now remove the current connected app and ensure we remove from WifiConfigManager.
         mWifiNetworkSuggestionsManager.removeApp(TEST_PACKAGE_1);
         verify(mWifiConfigManager).removeSuggestionConfiguredNetwork(
-                networkSuggestion1.wifiConfiguration.getKey());
+                argThat(new WifiConfigMatcher(connectNetwork)));
 
-        // Now remove the other app and ensure we did not trigger a disconnect.
+        // Now remove the other app and ensure we remove from WifiConfigManager.
         mWifiNetworkSuggestionsManager.removeApp(TEST_PACKAGE_2);
-        verify(mWifiConfigManager).removeSuggestionConfiguredNetwork(anyString());
-    }
-
-    /**
-     * Verify that we do not disconnect from the network if there are no network suggestion matching
-     * the connected network when one of the app is removed.
-     */
-    @Test
-    public void testRemoveAppNotMatchingConnectionSuccess() {
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
-        List<WifiNetworkSuggestion> networkSuggestionList =
-                new ArrayList<WifiNetworkSuggestion>() {{
-                    add(networkSuggestion);
-                }};
-        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
-                mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
-                        TEST_PACKAGE_1, TEST_FEATURE));
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
-
-        // Simulate connecting to some other network.
-        mWifiNetworkSuggestionsManager.handleConnectionAttemptEnded(
-                WifiMetrics.ConnectionEvent.FAILURE_NONE,
-                WifiConfigurationTestUtil.createEapNetwork(), TEST_BSSID);
-
-        // Now remove the app and ensure we did not trigger a disconnect.
-        mWifiNetworkSuggestionsManager.removeApp(TEST_PACKAGE_1);
-        verify(mWifiConfigManager, never()).removeSuggestionConfiguredNetwork(anyString());
-    }
-
-    /**
-     * Verify that we do not disconnect from the network if there are no network suggestion matching
-     * the connected network when one of them is removed.
-     */
-    @Test
-    public void testRemoveNetworkSuggestionsNotMatchingConnectionSuccessAfterConnectionFailure() {
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
-        List<WifiNetworkSuggestion> networkSuggestionList =
-                new ArrayList<WifiNetworkSuggestion>() {{
-                    add(networkSuggestion);
-                }};
-        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
-                mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
-                        TEST_PACKAGE_1, TEST_FEATURE));
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
-        WifiConfiguration connectNetwork =
-                new WifiConfiguration(networkSuggestion.wifiConfiguration);
-        connectNetwork.fromWifiNetworkSuggestion = true;
-        connectNetwork.ephemeral = true;
-        connectNetwork.creatorName = TEST_APP_NAME_1;
-        connectNetwork.creatorUid = TEST_UID_1;
-        // Simulate failing connection to the network.
-        mWifiNetworkSuggestionsManager.handleConnectionAttemptEnded(
-                WifiMetrics.ConnectionEvent.FAILURE_DHCP, connectNetwork, TEST_BSSID);
-
-        // Simulate connecting to some other network.
-        mWifiNetworkSuggestionsManager.handleConnectionAttemptEnded(
-                WifiMetrics.ConnectionEvent.FAILURE_NONE,
-                WifiConfigurationTestUtil.createEapNetwork(), TEST_BSSID);
-
-        // Now remove the app and ensure we did not trigger a disconnect.
-        mWifiNetworkSuggestionsManager.removeApp(TEST_PACKAGE_1);
-        verify(mWifiConfigManager, never()).removeSuggestionConfiguredNetwork(anyString());
+        verify(mWifiConfigManager, times(2)).removeSuggestionConfiguredNetwork(
+                argThat(new WifiConfigMatcher(networkSuggestion2.wifiConfiguration)));
     }
 
     /**
@@ -1973,10 +2299,12 @@
      */
     @Test
     public void testAddRemoveNetworkSuggestionsStartStopAppOpsWatch() {
-        WifiNetworkSuggestion networkSuggestion1 = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
-        WifiNetworkSuggestion networkSuggestion2 = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion1 = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        WifiNetworkSuggestion networkSuggestion2 = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
 
         List<WifiNetworkSuggestion> networkSuggestionList1 =
                 new ArrayList<WifiNetworkSuggestion>() {{
@@ -2020,8 +2348,9 @@
      */
     @Test
     public void testAppOpsChangeAfterSuggestionsAdd() {
-        WifiNetworkSuggestion networkSuggestion1 = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion1 = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion1);
@@ -2072,10 +2401,12 @@
     @Test
     public void testAppOpsChangeAfterConfigStoreLoad() {
         PerAppInfo appInfo = new PerAppInfo(TEST_UID_1, TEST_PACKAGE_1, TEST_FEATURE);
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
-        appInfo.extNetworkSuggestions.add(
-                ExtendedWifiNetworkSuggestion.fromWns(networkSuggestion, appInfo, true));
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        ExtendedWifiNetworkSuggestion ewns =
+                ExtendedWifiNetworkSuggestion.fromWns(networkSuggestion, appInfo, true);
+        appInfo.extNetworkSuggestions.put(ewns.hashCode(), ewns);
         mDataSource.fromDeserialized(new HashMap<String, PerAppInfo>() {{
                     put(TEST_PACKAGE_1, appInfo);
                 }});
@@ -2118,8 +2449,9 @@
      */
     @Test
     public void testAppOpsChangeWrongUid() {
-        WifiNetworkSuggestion networkSuggestion1 = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion1 = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion1);
@@ -2160,10 +2492,12 @@
      */
     @Test
     public void testRemoveApp() {
-        WifiNetworkSuggestion networkSuggestion1 = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
-        WifiNetworkSuggestion networkSuggestion2 = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion1 = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        WifiNetworkSuggestion networkSuggestion2 = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
 
         List<WifiNetworkSuggestion> networkSuggestionList1 =
                 new ArrayList<WifiNetworkSuggestion>() {{
@@ -2224,10 +2558,12 @@
      */
     @Test
     public void testClear() {
-        WifiNetworkSuggestion networkSuggestion1 = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
-        WifiNetworkSuggestion networkSuggestion2 = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion1 = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        WifiNetworkSuggestion networkSuggestion2 = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
 
         List<WifiNetworkSuggestion> networkSuggestionList1 =
                 new ArrayList<WifiNetworkSuggestion>() {{
@@ -2271,6 +2607,8 @@
 
         // Verify that we stopped watching these apps for app-ops changes.
         verify(mAppOpsManager, times(2)).stopWatchingMode(any());
+
+        verify(mWifiNotificationManager).cancel(SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE);
     }
 
     /**
@@ -2279,8 +2617,9 @@
      */
     @Test
     public void testUserApprovalNotificationDismissalWhenGetScanResult() {
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -2292,7 +2631,7 @@
         // Simulate user dismissal notification.
         sendBroadcastForUserActionOnApp(
                 NOTIFICATION_USER_DISMISSED_INTENT_ACTION, TEST_PACKAGE_1, TEST_UID_1);
-        reset(mNotificationManger);
+        reset(mWifiNotificationManager);
         verify(mWifiMetrics).addUserApprovalSuggestionAppUiReaction(
                 WifiNetworkSuggestionsManager.ACTION_USER_DISMISS, false);
         // Simulate finding the network in scan results.
@@ -2305,13 +2644,13 @@
         sendBroadcastForUserActionOnApp(
                 NOTIFICATION_USER_DISMISSED_INTENT_ACTION, TEST_PACKAGE_1, TEST_UID_1);
 
-        reset(mNotificationManger);
+        reset(mWifiNotificationManager);
         // We should resend the notification next time the network is found in scan results.
         mWifiNetworkSuggestionsManager.getNetworkSuggestionsForScanDetail(
                 createScanDetailForNetwork(networkSuggestion.wifiConfiguration));
 
         validateUserApprovalNotification(TEST_APP_NAME_1);
-        verifyNoMoreInteractions(mNotificationManger);
+        verifyNoMoreInteractions(mWifiNotificationManager);
     }
 
     /**
@@ -2319,9 +2658,14 @@
      * the user approval notification when framework gets scan results.
      */
     @Test
-    public void testUserApprovalNotificationClickOnAllowWhenGetScanResult() {
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true);
+    public void testUserApprovalNotificationClickOnAllowWhenGetScanResult() throws RemoteException {
+        mWifiNetworkSuggestionsManager.addSuggestionUserApprovalStatusListener(
+                mUserApprovalStatusListener, TEST_PACKAGE_1, TEST_UID_1);
+        verify(mUserApprovalStatusListener)
+                .onUserApprovalStatusChange(WifiManager.STATUS_SUGGESTION_APPROVAL_UNKNOWN);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -2330,11 +2674,13 @@
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
                         TEST_PACKAGE_1, TEST_FEATURE));
         validateUserApprovalNotification(TEST_APP_NAME_1);
+        verify(mUserApprovalStatusListener)
+                .onUserApprovalStatusChange(WifiManager.STATUS_SUGGESTION_APPROVAL_PENDING);
 
         // Simulate user dismissal notification.
         sendBroadcastForUserActionOnApp(
                 NOTIFICATION_USER_DISMISSED_INTENT_ACTION, TEST_PACKAGE_1, TEST_UID_1);
-        reset(mNotificationManger);
+        reset(mWifiNotificationManager);
 
         // Simulate finding the network in scan results.
         mWifiNetworkSuggestionsManager.getNetworkSuggestionsForScanDetail(
@@ -2346,7 +2692,7 @@
         sendBroadcastForUserActionOnApp(
                 NOTIFICATION_USER_ALLOWED_APP_INTENT_ACTION, TEST_PACKAGE_1, TEST_UID_1);
         // Cancel the notification.
-        verify(mNotificationManger).cancel(SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE);
+        verify(mWifiNotificationManager).cancel(SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE);
 
         // Verify config store interactions.
         verify(mWifiConfigManager, times(2)).saveToStore(true);
@@ -2354,11 +2700,13 @@
         verify(mWifiMetrics).addUserApprovalSuggestionAppUiReaction(
                 WifiNetworkSuggestionsManager.ACTION_USER_ALLOWED_APP, false);
 
-        reset(mNotificationManger);
+        reset(mWifiNotificationManager);
         // We should not resend the notification next time the network is found in scan results.
         mWifiNetworkSuggestionsManager.getNetworkSuggestionsForScanDetail(
                 createScanDetailForNetwork(networkSuggestion.wifiConfiguration));
-        verifyNoMoreInteractions(mNotificationManger);
+        verifyNoMoreInteractions(mWifiNotificationManager);
+        verify(mUserApprovalStatusListener).onUserApprovalStatusChange(
+                WifiManager.STATUS_SUGGESTION_APPROVAL_APPROVED_BY_USER);
     }
 
     /**
@@ -2366,9 +2714,15 @@
      * the user approval notification when framework gets scan results.
      */
     @Test
-    public void testUserApprovalNotificationClickOnDisallowWhenGetScanResult() {
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true);
+    public void testUserApprovalNotificationClickOnDisallowWhenGetScanResult()
+            throws RemoteException {
+        mWifiNetworkSuggestionsManager.addSuggestionUserApprovalStatusListener(
+                mUserApprovalStatusListener,  TEST_PACKAGE_1, TEST_UID_1);
+        verify(mUserApprovalStatusListener)
+                .onUserApprovalStatusChange(WifiManager.STATUS_SUGGESTION_APPROVAL_UNKNOWN);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -2383,13 +2737,15 @@
         // Simulate user dismissal notification.
         sendBroadcastForUserActionOnApp(
                 NOTIFICATION_USER_DISMISSED_INTENT_ACTION, TEST_PACKAGE_1, TEST_UID_1);
-        reset(mNotificationManger);
+        reset(mWifiNotificationManager);
 
         // Simulate finding the network in scan results.
         mWifiNetworkSuggestionsManager.getNetworkSuggestionsForScanDetail(
                 createScanDetailForNetwork(networkSuggestion.wifiConfiguration));
 
         validateUserApprovalNotification(TEST_APP_NAME_1);
+        verify(mUserApprovalStatusListener)
+                .onUserApprovalStatusChange(WifiManager.STATUS_SUGGESTION_APPROVAL_PENDING);
 
         // Simulate user clicking on disallow in the notification.
         sendBroadcastForUserActionOnApp(
@@ -2398,7 +2754,7 @@
         verify(mAppOpsManager).setMode(
                 OPSTR_CHANGE_WIFI_STATE, TEST_UID_1, TEST_PACKAGE_1, MODE_IGNORED);
         // Cancel the notification.
-        verify(mNotificationManger).cancel(SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE);
+        verify(mWifiNotificationManager).cancel(SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE);
 
         // Verify config store interactions.
         verify(mWifiConfigManager, times(2)).saveToStore(true);
@@ -2406,7 +2762,7 @@
         verify(mWifiMetrics).addUserApprovalSuggestionAppUiReaction(
                 WifiNetworkSuggestionsManager.ACTION_USER_DISALLOWED_APP, false);
 
-        reset(mNotificationManger);
+        reset(mWifiNotificationManager);
 
         // Now trigger the app-ops callback to ensure we remove all of their suggestions.
         AppOpsManager.OnOpChangedListener listener = mAppOpChangedListenerCaptor.getValue();
@@ -2427,9 +2783,10 @@
         // We should resend the notification when the network is again found in scan results.
         mWifiNetworkSuggestionsManager.getNetworkSuggestionsForScanDetail(
                 createScanDetailForNetwork(networkSuggestion.wifiConfiguration));
-
+        verify(mUserApprovalStatusListener).onUserApprovalStatusChange(
+                WifiManager.STATUS_SUGGESTION_APPROVAL_REJECTED_BY_USER);
         validateUserApprovalNotification(TEST_APP_NAME_1);
-        verifyNoMoreInteractions(mNotificationManger);
+        verifyNoMoreInteractions(mWifiNotificationManager);
     }
 
     /**
@@ -2437,8 +2794,9 @@
      */
     @Test
     public void testUserApprovalNotificationWhilePreviousNotificationActive() {
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -2453,12 +2811,12 @@
 
         validateUserApprovalNotification(TEST_APP_NAME_1);
 
-        reset(mNotificationManger);
+        reset(mWifiNotificationManager);
         // We should not resend the notification next time the network is found in scan results.
         mWifiNetworkSuggestionsManager.getNetworkSuggestionsForScanDetail(
                 createScanDetailForNetwork(networkSuggestion.wifiConfiguration));
 
-        verifyNoMoreInteractions(mNotificationManger);
+        verifyNoMoreInteractions(mWifiNotificationManager);
     }
 
     /**
@@ -2475,14 +2833,18 @@
         assertEquals(storedNetworkSuggestionListPerApp.size(), 0);
 
         // App add network suggestions then get stored suggestions.
-        WifiNetworkSuggestion networkSuggestion1 = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
-        WifiNetworkSuggestion networkSuggestion2 = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOweNetwork(), null, false, false, true, true);
-        WifiNetworkSuggestion networkSuggestion3 = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createSaeNetwork(), null, false, false, true, true);
-        WifiNetworkSuggestion networkSuggestion4 = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createPskNetwork(), null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion1 = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        WifiNetworkSuggestion networkSuggestion2 = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOweNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        WifiNetworkSuggestion networkSuggestion3 = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createSaeNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        WifiNetworkSuggestion networkSuggestion4 = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createPskNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList = new ArrayList<>();
         networkSuggestionList.add(networkSuggestion1);
         networkSuggestionList.add(networkSuggestion2);
@@ -2491,7 +2853,7 @@
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
                         TEST_PACKAGE_1, TEST_FEATURE));
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
         storedNetworkSuggestionListPerApp =
                 mWifiNetworkSuggestionsManager.get(TEST_PACKAGE_1, TEST_UID_1);
         assertEquals(new HashSet<>(networkSuggestionList),
@@ -2512,12 +2874,15 @@
     @Test
     public void testGetHiddenNetworks() {
 
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true);
-        WifiNetworkSuggestion hiddenNetworkSuggestion1 = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createPskHiddenNetwork(), null, true, false, true, true);
-        WifiNetworkSuggestion hiddenNetworkSuggestion2 = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createPskHiddenNetwork(), null, true, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        WifiNetworkSuggestion hiddenNetworkSuggestion1 = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createPskHiddenNetwork(), null, true, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        WifiNetworkSuggestion hiddenNetworkSuggestion2 = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createPskHiddenNetwork(), null, true, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList1 =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -2533,8 +2898,8 @@
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList2, TEST_UID_2,
                         TEST_PACKAGE_2, TEST_FEATURE));
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(false, TEST_PACKAGE_2);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(false, TEST_UID_2, TEST_PACKAGE_2);
         List<WifiScanner.ScanSettings.HiddenNetwork> hiddenNetworks =
                 mWifiNetworkSuggestionsManager.retrieveHiddenNetworkList();
         assertEquals(1, hiddenNetworks.size());
@@ -2550,8 +2915,9 @@
         // Fg app
         when(mActivityManager.getPackageImportance(any())).thenReturn(IMPORTANCE_FOREGROUND);
 
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -2579,7 +2945,7 @@
         // We should not resend the notification next time the network is found in scan results.
         mWifiNetworkSuggestionsManager.getNetworkSuggestionsForScanDetail(
                 createScanDetailForNetwork(networkSuggestion.wifiConfiguration));
-        verifyNoMoreInteractions(mNotificationManger);
+        verifyNoMoreInteractions(mWifiNotificationManager);
     }
 
     /**
@@ -2591,8 +2957,9 @@
         // Fg app
         when(mActivityManager.getPackageImportance(any())).thenReturn(IMPORTANCE_FOREGROUND);
 
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false,  true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false,  true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -2638,7 +3005,7 @@
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
                         TEST_PACKAGE_1, TEST_FEATURE));
         validateUserApprovalDialog(TEST_APP_NAME_1);
-        verifyNoMoreInteractions(mNotificationManger);
+        verifyNoMoreInteractions(mWifiNotificationManager);
     }
 
     /**
@@ -2650,8 +3017,9 @@
         // Fg app
         when(mActivityManager.getPackageImportance(any())).thenReturn(IMPORTANCE_FOREGROUND);
 
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -2689,8 +3057,9 @@
         // Active scorer
         when(mNetworkScoreManager.getActiveScorerPackage()).thenReturn(TEST_PACKAGE_1);
 
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -2701,7 +3070,7 @@
                         TEST_PACKAGE_1, TEST_FEATURE));
 
         verifyZeroInteractions(mAlertDialog);
-        verifyZeroInteractions(mNotificationManger);
+        verifyZeroInteractions(mWifiNotificationManager);
     }
 
     @Test
@@ -2711,8 +3080,9 @@
         // Active scorer
         when(mNetworkScoreManager.getActiveScorerPackage()).thenReturn(TEST_PACKAGE_1);
 
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -2723,7 +3093,7 @@
                         TEST_PACKAGE_1, TEST_FEATURE));
 
         verifyZeroInteractions(mAlertDialog);
-        verifyZeroInteractions(mNotificationManger);
+        verifyZeroInteractions(mWifiNotificationManager);
     }
 
     /**
@@ -2732,8 +3102,9 @@
      */
     @Test
     public void testUserApprovalNotificationClickOnAllowDuringAddingSuggestionsFromNonFgApp() {
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -2747,24 +3118,25 @@
         sendBroadcastForUserActionOnApp(
                 NOTIFICATION_USER_ALLOWED_APP_INTENT_ACTION, TEST_PACKAGE_1, TEST_UID_1);
         // Cancel the notification.
-        verify(mNotificationManger).cancel(SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE);
+        verify(mWifiNotificationManager).cancel(SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE);
 
         // Verify config store interactions.
         verify(mWifiConfigManager, times(2)).saveToStore(true);
         assertTrue(mDataSource.hasNewDataToSerialize());
 
-        reset(mNotificationManger);
+        reset(mWifiNotificationManager);
         // We should not resend the notification next time the network is found in scan results.
         mWifiNetworkSuggestionsManager.getNetworkSuggestionsForScanDetail(
                 createScanDetailForNetwork(networkSuggestion.wifiConfiguration));
-        verifyNoMoreInteractions(mNotificationManger);
+        verifyNoMoreInteractions(mWifiNotificationManager);
     }
 
     @Test
     public void getNetworkSuggestionsForScanDetail_exemptsActiveScorerFromUserApproval() {
         when(mNetworkScoreManager.getActiveScorerPackage()).thenReturn(TEST_PACKAGE_1);
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -2776,7 +3148,7 @@
         mWifiNetworkSuggestionsManager.getNetworkSuggestionsForScanDetail(
                 createScanDetailForNetwork(networkSuggestion.wifiConfiguration));
 
-        verifyZeroInteractions(mNotificationManger);
+        verifyZeroInteractions(mWifiNotificationManager);
         verifyZeroInteractions(mAlertDialog);
     }
 
@@ -2786,8 +3158,9 @@
      */
     @Test
     public void testUserApprovalNotificationClickOnDisallowWhenAddSuggestionsFromNonFgApp() {
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false,  true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false,  true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -2806,13 +3179,13 @@
         verify(mAppOpsManager).setMode(
                 OPSTR_CHANGE_WIFI_STATE, TEST_UID_1, TEST_PACKAGE_1, MODE_IGNORED);
         // Cancel the notification.
-        verify(mNotificationManger).cancel(SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE);
+        verify(mWifiNotificationManager).cancel(SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE);
 
         // Verify config store interactions.
         verify(mWifiConfigManager, times(2)).saveToStore(true);
         assertTrue(mDataSource.hasNewDataToSerialize());
 
-        reset(mNotificationManger);
+        reset(mWifiNotificationManager);
 
         // Now trigger the app-ops callback to ensure we remove all of their suggestions.
         AppOpsManager.OnOpChangedListener listener = mAppOpChangedListenerCaptor.getValue();
@@ -2830,7 +3203,7 @@
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
                         TEST_PACKAGE_1, TEST_FEATURE));
         validateUserApprovalNotification(TEST_APP_NAME_1);
-        verifyNoMoreInteractions(mNotificationManger);
+        verifyNoMoreInteractions(mWifiNotificationManager);
     }
 
     /**
@@ -2845,9 +3218,12 @@
     public void testOnPasspointNetworkConnectionSuccessWithOneMatch() {
         PasspointConfiguration passpointConfiguration =
                 createTestConfigWithUserCredential(TEST_FQDN, TEST_FRIENDLY_NAME);
-        WifiConfiguration dummyConfiguration = createDummyWifiConfigurationForPasspoint(TEST_FQDN);
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                dummyConfiguration, passpointConfiguration, true, false, true, true);
+        WifiConfiguration placeholderConfig = createPlaceholderConfigForPasspoint(TEST_FQDN,
+                passpointConfiguration.getUniqueId());
+        placeholderConfig.setPasspointUniqueId(passpointConfiguration.getUniqueId());
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                placeholderConfig, passpointConfiguration, true, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -2858,7 +3234,7 @@
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
                         TEST_PACKAGE_1, TEST_FEATURE));
 
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
 
         // Simulate connecting to the network.
         WifiConfiguration connectNetwork = WifiConfigurationTestUtil.createPasspointNetwork();
@@ -2866,7 +3242,7 @@
         connectNetwork.providerFriendlyName = TEST_FRIENDLY_NAME;
         connectNetwork.fromWifiNetworkSuggestion = true;
         connectNetwork.ephemeral = true;
-        connectNetwork.creatorName = TEST_APP_NAME_1;
+        connectNetwork.creatorName = TEST_PACKAGE_1;
         connectNetwork.creatorUid = TEST_UID_1;
         connectNetwork.setPasspointUniqueId(passpointConfiguration.getUniqueId());
 
@@ -2929,7 +3305,7 @@
     }
 
     private void validateUserApprovalNotification(String... anyOfExpectedAppNames) {
-        verify(mNotificationManger, atLeastOnce()).notify(
+        verify(mWifiNotificationManager, atLeastOnce()).notify(
                 eq(SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE),
                 eq(mNotification));
         ArgumentCaptor<Notification.BigTextStyle> contentCaptor =
@@ -2959,8 +3335,8 @@
     public void testAddSuggestionWithValidCarrierIdWithCarrierProvisionPermission() {
         WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
         config.carrierId = VALID_CARRIER_ID;
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                config, null, true, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                config, null, true, false, true, true, DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList = new ArrayList<>();
         networkSuggestionList.add(networkSuggestion);
         when(mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(TEST_UID_1))
@@ -2978,8 +3354,8 @@
     public void testAddSuggestionWithValidCarrierIdWithoutCarrierProvisionPermission() {
         WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
         config.carrierId = VALID_CARRIER_ID;
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                config, null, true, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                config, null, true, false, true, true, DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList = new ArrayList<>();
         networkSuggestionList.add(networkSuggestion);
         when(mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(TEST_UID_1))
@@ -2995,8 +3371,8 @@
     @Test
     public void testAddSuggestionWithDefaultCarrierIdWithoutCarrierProvisionPermission() {
         WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                config, null, true, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                config, null, true, false, true, true, DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList = new ArrayList<>();
         networkSuggestionList.add(networkSuggestion);
         when(mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(TEST_UID_1))
@@ -3015,17 +3391,18 @@
     public void testGetPasspointSuggestionFromFqdnWithUserApproval() {
         PasspointConfiguration passpointConfiguration =
                 createTestConfigWithUserCredential(TEST_FQDN, TEST_FRIENDLY_NAME);
-        WifiConfiguration dummyConfiguration = createDummyWifiConfigurationForPasspoint(TEST_FQDN);
-        dummyConfiguration.FQDN = TEST_FQDN;
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(dummyConfiguration,
-                passpointConfiguration, true, false, true, true);
+        WifiConfiguration placeholderConfig = createPlaceholderConfigForPasspoint(TEST_FQDN,
+                passpointConfiguration.getUniqueId());
+        placeholderConfig.FQDN = TEST_FQDN;
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(placeholderConfig,
+                passpointConfiguration, true, false, true, true, DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList = new ArrayList<>();
         networkSuggestionList.add(networkSuggestion);
         when(mPasspointManager.addOrUpdateProvider(any(PasspointConfiguration.class),
                 anyInt(), anyString(), eq(true), eq(true))).thenReturn(true);
         assertEquals(mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
                 TEST_PACKAGE_1, TEST_FEATURE), WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS);
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
         Set<ExtendedWifiNetworkSuggestion> ewns =
                 mWifiNetworkSuggestionsManager.getNetworkSuggestionsForFqdn(TEST_FQDN);
         assertEquals(1, ewns.size());
@@ -3039,20 +3416,21 @@
     public void testGetPasspointSuggestionFromFqdnWithoutUserApproval() {
         PasspointConfiguration passpointConfiguration =
                 createTestConfigWithUserCredential(TEST_FQDN, TEST_FRIENDLY_NAME);
-        WifiConfiguration dummyConfiguration = createDummyWifiConfigurationForPasspoint(TEST_FQDN);
-        dummyConfiguration.FQDN = TEST_FQDN;
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(dummyConfiguration,
-                passpointConfiguration, true, false, true, true);
+        WifiConfiguration placeholderConfig = createPlaceholderConfigForPasspoint(TEST_FQDN,
+                passpointConfiguration.getUniqueId());
+        placeholderConfig.FQDN = TEST_FQDN;
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(placeholderConfig,
+                passpointConfiguration, true, false, true, true, DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList = new ArrayList<>();
         networkSuggestionList.add(networkSuggestion);
-        dummyConfiguration.creatorUid = TEST_UID_1;
+        placeholderConfig.creatorUid = TEST_UID_1;
         when(mPasspointManager.addOrUpdateProvider(any(PasspointConfiguration.class),
                 anyInt(), anyString(), eq(true), eq(true))).thenReturn(true);
         assertEquals(mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
                 TEST_PACKAGE_1, TEST_FEATURE), WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS);
         Set<ExtendedWifiNetworkSuggestion> ewns =
                 mWifiNetworkSuggestionsManager.getNetworkSuggestionsForFqdn(TEST_FQDN);
-        assertNull(ewns);
+        assertTrue(ewns.isEmpty());
     }
 
     @Test
@@ -3060,12 +3438,13 @@
         when(mNetworkScoreManager.getActiveScorerPackage()).thenReturn(TEST_PACKAGE_1);
         PasspointConfiguration passpointConfiguration =
                 createTestConfigWithUserCredential(TEST_FQDN, TEST_FRIENDLY_NAME);
-        WifiConfiguration dummyConfiguration = createDummyWifiConfigurationForPasspoint(TEST_FQDN);
-        dummyConfiguration.FQDN = TEST_FQDN;
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(dummyConfiguration,
-                passpointConfiguration, true, false, true, true);
+        WifiConfiguration placeholderConfig = createPlaceholderConfigForPasspoint(TEST_FQDN,
+                passpointConfiguration.getUniqueId());
+        placeholderConfig.FQDN = TEST_FQDN;
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(placeholderConfig,
+                passpointConfiguration, true, false, true, true, DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList = Arrays.asList(networkSuggestion);
-        dummyConfiguration.creatorUid = TEST_UID_1;
+        placeholderConfig.creatorUid = TEST_UID_1;
         when(mPasspointManager.addOrUpdateProvider(any(PasspointConfiguration.class),
                 anyInt(), anyString(), eq(true), eq(true))).thenReturn(true);
         assertEquals(mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
@@ -3076,7 +3455,7 @@
 
         assertEquals(1, ewns.size());
         verifyZeroInteractions(mAlertDialog);
-        verifyZeroInteractions(mNotificationManger);
+        verifyZeroInteractions(mWifiNotificationManager);
     }
 
     /**
@@ -3086,30 +3465,44 @@
     public void testIsPasspointSuggestionSharedWithUserSetToTrue() {
         PasspointConfiguration passpointConfiguration =
                 createTestConfigWithUserCredential(TEST_FQDN, TEST_FRIENDLY_NAME);
-        WifiConfiguration dummyConfiguration = createDummyWifiConfigurationForPasspoint(TEST_FQDN);
-        dummyConfiguration.FQDN = TEST_FQDN;
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(dummyConfiguration,
-                passpointConfiguration, true, false, true, true);
+        WifiConfiguration placeholderConfig = createPlaceholderConfigForPasspoint(TEST_FQDN,
+                passpointConfiguration.getUniqueId());
+        placeholderConfig.FQDN = TEST_FQDN;
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(placeholderConfig,
+                passpointConfiguration, true, false, true, true, DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList = new ArrayList<>();
         networkSuggestionList.add(networkSuggestion);
-        dummyConfiguration.creatorUid = TEST_UID_1;
+        placeholderConfig.creatorUid = TEST_UID_1;
         when(mPasspointManager.addOrUpdateProvider(any(PasspointConfiguration.class),
                 anyInt(), anyString(), eq(true), eq(true))).thenReturn(true);
         assertEquals(mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
                 TEST_PACKAGE_1, TEST_FEATURE), WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS);
 
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(false, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(false, TEST_UID_1, TEST_PACKAGE_1);
         assertFalse(mWifiNetworkSuggestionsManager
-                .isPasspointSuggestionSharedWithUser(dummyConfiguration));
+                .isPasspointSuggestionSharedWithUser(placeholderConfig));
 
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
         assertTrue(mWifiNetworkSuggestionsManager
-                .isPasspointSuggestionSharedWithUser(dummyConfiguration));
-        dummyConfiguration.meteredHint = true;
-        when(mWifiCarrierInfoManager.isCarrierNetworkFromNonDefaultDataSim(dummyConfiguration))
+                .isPasspointSuggestionSharedWithUser(placeholderConfig));
+
+        placeholderConfig.carrierId = TEST_CARRIER_ID;
+        placeholderConfig.subscriptionId = TEST_SUBID;
+        when(mWifiCarrierInfoManager.getBestMatchSubscriptionId(placeholderConfig))
+                .thenReturn(TEST_SUBID);
+        when(mWifiCarrierInfoManager.isSimReady(TEST_SUBID)).thenReturn(false);
+        assertFalse(mWifiNetworkSuggestionsManager
+                .isPasspointSuggestionSharedWithUser(placeholderConfig));
+
+        when(mWifiCarrierInfoManager.isSimReady(TEST_SUBID)).thenReturn(true);
+        assertTrue(mWifiNetworkSuggestionsManager
+                .isPasspointSuggestionSharedWithUser(placeholderConfig));
+
+        placeholderConfig.meteredHint = true;
+        when(mWifiCarrierInfoManager.isCarrierNetworkFromNonDefaultDataSim(placeholderConfig))
                 .thenReturn(true);
         assertFalse(mWifiNetworkSuggestionsManager
-                .isPasspointSuggestionSharedWithUser(dummyConfiguration));
+                .isPasspointSuggestionSharedWithUser(placeholderConfig));
     }
 
     /**
@@ -3119,10 +3512,11 @@
     public void testIsPasspointSuggestionSharedWithUserSetToFalse() {
         PasspointConfiguration passpointConfiguration =
                 createTestConfigWithUserCredential(TEST_FQDN, TEST_FRIENDLY_NAME);
-        WifiConfiguration dummyConfiguration = createDummyWifiConfigurationForPasspoint(TEST_FQDN);
-        dummyConfiguration.FQDN = TEST_FQDN;
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(dummyConfiguration,
-                passpointConfiguration, true, false, false, true);
+        WifiConfiguration placeholderConfig = createPlaceholderConfigForPasspoint(TEST_FQDN,
+                passpointConfiguration.getUniqueId());
+        placeholderConfig.FQDN = TEST_FQDN;
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(placeholderConfig,
+                passpointConfiguration, true, false, false, true, DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList = new ArrayList<>();
         networkSuggestionList.add(networkSuggestion);
         when(mPasspointManager.addOrUpdateProvider(any(PasspointConfiguration.class),
@@ -3130,13 +3524,13 @@
         assertEquals(mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
                 TEST_PACKAGE_1, TEST_FEATURE), WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS);
 
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(false, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(false, TEST_UID_1, TEST_PACKAGE_1);
         assertFalse(mWifiNetworkSuggestionsManager
-                .isPasspointSuggestionSharedWithUser(dummyConfiguration));
+                .isPasspointSuggestionSharedWithUser(placeholderConfig));
 
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
         assertFalse(mWifiNetworkSuggestionsManager
-                .isPasspointSuggestionSharedWithUser(dummyConfiguration));
+                .isPasspointSuggestionSharedWithUser(placeholderConfig));
     }
 
     /**
@@ -3148,12 +3542,15 @@
      */
     @Test
     public void testGetWifiConfigForMatchedNetworkSuggestionsSharedWithUser() {
-        WifiNetworkSuggestion networkSuggestion1 = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
-        WifiNetworkSuggestion networkSuggestion2 = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createPskNetwork(), null, false, false, false, true);
-        WifiNetworkSuggestion networkSuggestion3 = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createPskNetwork(), null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion1 = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        WifiNetworkSuggestion networkSuggestion2 = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createPskNetwork(), null, false, false, false, true,
+                DEFAULT_PRIORITY_GROUP);
+        WifiNetworkSuggestion networkSuggestion3 = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createPskNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<ScanResult> scanResults = new ArrayList<>();
         scanResults.add(
                 createScanDetailForNetwork(networkSuggestion1.wifiConfiguration).getScanResult());
@@ -3173,22 +3570,99 @@
                     add(networkSuggestion2);
                     add(networkSuggestion3);
                 }};
+        networkSuggestion1.wifiConfiguration.fromWifiNetworkSuggestion = true;
+        networkSuggestion2.wifiConfiguration.fromWifiNetworkSuggestion = true;
+        networkSuggestion3.wifiConfiguration.fromWifiNetworkSuggestion = true;
+        networkSuggestion1.wifiConfiguration.creatorName = TEST_PACKAGE_1;
+        networkSuggestion2.wifiConfiguration.creatorName = TEST_PACKAGE_1;
+        networkSuggestion3.wifiConfiguration.creatorName = TEST_PACKAGE_1;
+
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
                         TEST_PACKAGE_1, TEST_FEATURE));
         setupGetConfiguredNetworksFromWcm(networkSuggestion1.wifiConfiguration,
                 networkSuggestion2.wifiConfiguration, networkSuggestion3.wifiConfiguration);
         // When app is not approved, empty list will be returned
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(false, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(false, TEST_UID_1, TEST_PACKAGE_1);
         List<WifiConfiguration> wifiConfigurationList = mWifiNetworkSuggestionsManager
                 .getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(scanResults);
         assertEquals(0, wifiConfigurationList.size());
         // App is approved, only allow user connect, not open network will return.
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
         wifiConfigurationList = mWifiNetworkSuggestionsManager
                 .getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(scanResults);
         assertEquals(1, wifiConfigurationList.size());
-        assertEquals(networkSuggestion3.wifiConfiguration, wifiConfigurationList.get(0));
+        networkSuggestion3.wifiConfiguration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
+        WifiConfigurationTestUtil.assertConfigurationEqual(
+                networkSuggestion3.wifiConfiguration, wifiConfigurationList.get(0));
+    }
+
+    /**
+     * test getWifiConfigForMatchedNetworkSuggestionsSharedWithUser on carrier network.
+     *  - SIM is not present will not be return
+     */
+    @Test
+    public void testGetWifiConfigForMatchedCarrierNetworkSuggestionsSharedWithUser() {
+        when(mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(TEST_UID_1))
+                .thenReturn(true);
+        WifiNetworkSuggestion networkSuggestion1 = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createPskNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        WifiNetworkSuggestion networkSuggestion2 = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createPskNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        networkSuggestion1.wifiConfiguration.carrierId = TEST_CARRIER_ID;
+        networkSuggestion2.wifiConfiguration.carrierId = TEST_CARRIER_ID;
+        WifiConfiguration configuration = networkSuggestion1.wifiConfiguration;
+
+        // Suggestion1 has SIM present, suggestion2 has SIM absent
+        when(mWifiCarrierInfoManager
+                .getBestMatchSubscriptionId(argThat(new WifiConfigMatcher(configuration))))
+                .thenReturn(TEST_SUBID);
+        when(mWifiCarrierInfoManager.isSimReady(TEST_SUBID)).thenReturn(true);
+        List<ScanResult> scanResults = new ArrayList<>();
+        scanResults.add(
+                createScanDetailForNetwork(networkSuggestion1.wifiConfiguration).getScanResult());
+        scanResults.add(
+                createScanDetailForNetwork(networkSuggestion2.wifiConfiguration).getScanResult());
+
+        List<WifiNetworkSuggestion> networkSuggestionList =
+                new ArrayList<WifiNetworkSuggestion>() {{
+                    add(networkSuggestion1);
+                    add(networkSuggestion2);
+                }};
+
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
+                mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
+                        TEST_PACKAGE_1, TEST_FEATURE));
+        networkSuggestion1.wifiConfiguration.fromWifiNetworkSuggestion = true;
+        networkSuggestion2.wifiConfiguration.fromWifiNetworkSuggestion = true;
+        networkSuggestion1.wifiConfiguration.creatorName = TEST_PACKAGE_1;
+        networkSuggestion2.wifiConfiguration.creatorName = TEST_PACKAGE_1;
+        networkSuggestion1.wifiConfiguration.subscriptionId = TEST_SUBID;
+        setupGetConfiguredNetworksFromWcm(networkSuggestion1.wifiConfiguration,
+                networkSuggestion2.wifiConfiguration);
+        List<WifiConfiguration> wifiConfigurationList = mWifiNetworkSuggestionsManager
+                .getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(scanResults);
+        assertEquals(1, wifiConfigurationList.size());
+        networkSuggestion1.wifiConfiguration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
+        WifiConfigurationTestUtil.assertConfigurationEqual(
+                networkSuggestion1.wifiConfiguration, wifiConfigurationList.get(0));
+    }
+
+    class WifiConfigMatcher implements ArgumentMatcher<WifiConfiguration> {
+        private final WifiConfiguration mConfig;
+
+        WifiConfigMatcher(WifiConfiguration config) {
+            assertNotNull(config);
+            mConfig = config;
+        }
+
+        @Override
+        public boolean matches(WifiConfiguration otherConfig) {
+            if (otherConfig == null) return false;
+            return mConfig.SSID.equals(otherConfig.SSID);
+        }
     }
 
     private void assertSuggestionsEquals(Set<WifiNetworkSuggestion> expectedSuggestions,
@@ -3202,8 +3676,10 @@
     private void setupGetConfiguredNetworksFromWcm(WifiConfiguration...configs) {
         for (int i = 0; i < configs.length; i++) {
             WifiConfiguration config = configs[i];
-            when(mWifiConfigManager.getConfiguredNetwork(config.getKey())).thenReturn(config);
+            when(mWifiConfigManager.getConfiguredNetwork(config.getProfileKey()))
+                    .thenReturn(config);
         }
+        when(mWifiConfigManager.getConfiguredNetworks()).thenReturn(Arrays.asList(configs));
     }
 
     /**
@@ -3214,8 +3690,11 @@
         WifiConfiguration config =
                 WifiConfigurationTestUtil.createEapNetwork(WifiEnterpriseConfig.Eap.SIM,
                         WifiEnterpriseConfig.Phase2.NONE);
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                config, null, true, false, true, true);
+        if (SdkLevel.isAtLeastS()) {
+            config.subscriptionId = TEST_SUBID;
+        }
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                config, null, true, false, true, true, DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList = new ArrayList<>();
         networkSuggestionList.add(networkSuggestion);
         when(mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(TEST_UID_1))
@@ -3225,7 +3704,7 @@
         int status = mWifiNetworkSuggestionsManager
                 .add(networkSuggestionList, TEST_UID_1, TEST_PACKAGE_1, TEST_FEATURE);
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_NOT_ALLOWED, status);
-        verify(mNotificationManger, never()).notify(anyInt(), any());
+        verify(mWifiNotificationManager, never()).notify(anyInt(), any());
         assertEquals(0, mWifiNetworkSuggestionsManager.get(TEST_PACKAGE_1, TEST_UID_1).size());
         verify(mWifiMetrics, never()).incrementNetworkSuggestionApiUsageNumOfAppInType(anyInt());
     }
@@ -3234,12 +3713,18 @@
      * Verify success when add SIM-based network from app has carrier privileges.
      */
     @Test
-    public void testAddSimCredentialNetworkWithCarrierPrivileges() {
+    public void testAddSimCredentialNetworkWithCarrierPrivileges() throws RemoteException {
+        mWifiNetworkSuggestionsManager.addSuggestionUserApprovalStatusListener(
+                mUserApprovalStatusListener,  TEST_PACKAGE_1, TEST_UID_1);
         WifiConfiguration config =
                 WifiConfigurationTestUtil.createEapNetwork(WifiEnterpriseConfig.Eap.SIM,
                         WifiEnterpriseConfig.Phase2.NONE);
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                config, null, true, false, true, true);
+        if (SdkLevel.isAtLeastS()) {
+            config.subscriptionId = TEST_SUBID;
+        }
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                new WifiConfiguration(config), null, true, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList = new ArrayList<>();
         networkSuggestionList.add(networkSuggestion);
         when(mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(TEST_UID_1))
@@ -3249,10 +3734,19 @@
         int status = mWifiNetworkSuggestionsManager
                 .add(networkSuggestionList, TEST_UID_1, TEST_PACKAGE_1, TEST_FEATURE);
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS, status);
-        verify(mNotificationManger, never()).notify(anyInt(), any());
+        verify(mWifiNotificationManager, never()).notify(anyInt(), any());
         assertEquals(1,  mWifiNetworkSuggestionsManager.get(TEST_PACKAGE_1, TEST_UID_1).size());
         verify(mWifiMetrics).incrementNetworkSuggestionApiUsageNumOfAppInType(
                 WifiNetworkSuggestionsManager.APP_TYPE_CARRIER_PRIVILEGED);
+        verify(mUserApprovalStatusListener).onUserApprovalStatusChange(
+                WifiManager.STATUS_SUGGESTION_APPROVAL_APPROVED_BY_CARRIER_PRIVILEGE);
+
+        WifiNetworkSuggestion suggestionToRemove = createWifiNetworkSuggestion(
+                new WifiConfiguration(config), null, true, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
+                mWifiNetworkSuggestionsManager.remove(List.of(suggestionToRemove), TEST_UID_1,
+                        TEST_PACKAGE_1));
     }
 
     /**
@@ -3263,8 +3757,11 @@
         WifiConfiguration config =
                 WifiConfigurationTestUtil.createEapNetwork(WifiEnterpriseConfig.Eap.SIM,
                         WifiEnterpriseConfig.Phase2.NONE);
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                config, null, true, false, true, true);
+        if (SdkLevel.isAtLeastS()) {
+            config.subscriptionId = TEST_SUBID;
+        }
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                config, null, true, false, true, true, DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList = new ArrayList<>();
         networkSuggestionList.add(networkSuggestion);
         when(mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(TEST_UID_1))
@@ -3273,8 +3770,8 @@
                 .thenReturn(TelephonyManager.UNKNOWN_CARRIER_ID);
         int status = mWifiNetworkSuggestionsManager
                 .add(networkSuggestionList, TEST_UID_1, TEST_PACKAGE_1, TEST_FEATURE);
-        assertEquals(status, WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS);
-        verify(mNotificationManger, never()).notify(anyInt(), any());
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS, status);
+        verify(mWifiNotificationManager, never()).notify(anyInt(), any());
         assertEquals(1,  mWifiNetworkSuggestionsManager.get(TEST_PACKAGE_1, TEST_UID_1).size());
     }
 
@@ -3286,8 +3783,8 @@
         WifiConfiguration config =
                 WifiConfigurationTestUtil.createEapNetwork(WifiEnterpriseConfig.Eap.SIM,
                         WifiEnterpriseConfig.Phase2.NONE);
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                config, null, true, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                config, null, true, false, true, true, DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList = new ArrayList<>();
         networkSuggestionList.add(networkSuggestion);
         when(mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(TEST_UID_1))
@@ -3295,7 +3792,7 @@
         when(mWifiCarrierInfoManager.getCarrierIdForPackageWithCarrierPrivileges(TEST_PACKAGE_1))
                 .thenReturn(VALID_CARRIER_ID);
         when(mWifiCarrierInfoManager.getMatchingSubId(VALID_CARRIER_ID)).thenReturn(TEST_SUBID);
-        when(mWifiCarrierInfoManager.isSimPresent(TEST_SUBID)).thenReturn(true);
+        when(mWifiCarrierInfoManager.isSimReady(TEST_SUBID)).thenReturn(true);
         when(mWifiCarrierInfoManager.requiresImsiEncryption(TEST_SUBID)).thenReturn(true);
         when(mWifiCarrierInfoManager.isImsiEncryptionInfoAvailable(TEST_SUBID)).thenReturn(true);
         ScanDetail scanDetail = createScanDetailForNetwork(config);
@@ -3309,7 +3806,7 @@
                 new HashSet<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
                 }};
-        verify(mNotificationManger, never()).notify(anyInt(), any());
+        verify(mWifiNotificationManager, never()).notify(anyInt(), any());
         assertSuggestionsEquals(expectedMatchingNetworkSuggestions, matchingExtNetworkSuggestions);
     }
 
@@ -3322,8 +3819,8 @@
         WifiConfiguration config =
                 WifiConfigurationTestUtil.createEapNetwork(WifiEnterpriseConfig.Eap.SIM,
                         WifiEnterpriseConfig.Phase2.NONE);
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                config, null, true, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                config, null, true, false, true, true, DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList = new ArrayList<>();
         networkSuggestionList.add(networkSuggestion);
         when(mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(TEST_UID_1))
@@ -3333,7 +3830,7 @@
         int status = mWifiNetworkSuggestionsManager
                 .add(networkSuggestionList, TEST_UID_1, TEST_PACKAGE_1, TEST_FEATURE);
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS, status);
-        verify(mNotificationManger, never()).notify(anyInt(), any());
+        verify(mWifiNotificationManager, never()).notify(anyInt(), any());
         when(mWifiCarrierInfoManager.getCarrierIdForPackageWithCarrierPrivileges(TEST_PACKAGE_1))
                 .thenReturn(TelephonyManager.UNKNOWN_CARRIER_ID);
         mWifiNetworkSuggestionsManager.resetCarrierPrivilegedApps();
@@ -3343,12 +3840,13 @@
                 .add(networkSuggestionList, TEST_UID_1, TEST_PACKAGE_1, TEST_FEATURE);
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_NOT_ALLOWED, status);
         networkSuggestionList.clear();
-        networkSuggestionList.add(new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true));
+        networkSuggestionList.add(createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, true, false, true, true,
+                DEFAULT_PRIORITY_GROUP));
         status = mWifiNetworkSuggestionsManager
                 .add(networkSuggestionList, TEST_UID_1, TEST_PACKAGE_1, TEST_FEATURE);
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS, status);
-        verify(mNotificationManger).notify(anyInt(), any());
+        verify(mWifiNotificationManager).notify(anyInt(), any());
     }
 
     /**
@@ -3356,28 +3854,29 @@
      */
     @Test
     public void testSetAllowAutoJoinOnSuggestionNetwork()  {
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenOweNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
                 }};
-        // No matching will return false.
-        assertFalse(mWifiNetworkSuggestionsManager
-                .allowNetworkSuggestionAutojoin(networkSuggestion.wifiConfiguration, false));
-        verify(mWifiConfigManager, never()).saveToStore(true);
-        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
-                mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
-                        TEST_PACKAGE_1, TEST_FEATURE));
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
-        verify(mWifiConfigManager, times(2)).saveToStore(true);
-        reset(mWifiConfigManager);
         WifiConfiguration configuration =
                 new WifiConfiguration(networkSuggestion.wifiConfiguration);
         configuration.fromWifiNetworkSuggestion = true;
         configuration.ephemeral = true;
         configuration.creatorName = TEST_PACKAGE_1;
         configuration.creatorUid = TEST_UID_1;
+        // No matching will return false.
+        assertFalse(mWifiNetworkSuggestionsManager
+                .allowNetworkSuggestionAutojoin(configuration, false));
+        verify(mWifiConfigManager, never()).saveToStore(true);
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
+                mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
+                        TEST_PACKAGE_1, TEST_FEATURE));
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
+        verify(mWifiConfigManager, times(2)).saveToStore(true);
+        reset(mWifiConfigManager);
 
         assertTrue(mWifiNetworkSuggestionsManager
                 .allowNetworkSuggestionAutojoin(configuration, false));
@@ -3397,9 +3896,11 @@
     public void testSetAllowAutoJoinOnPasspointSuggestionNetwork() {
         PasspointConfiguration passpointConfiguration =
                 createTestConfigWithUserCredential(TEST_FQDN, TEST_FRIENDLY_NAME);
-        WifiConfiguration dummyConfiguration = createDummyWifiConfigurationForPasspoint(TEST_FQDN);
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                dummyConfiguration, passpointConfiguration, false, false, true, true);
+        WifiConfiguration placeholderConfig = createPlaceholderConfigForPasspoint(TEST_FQDN,
+                passpointConfiguration.getUniqueId());
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                placeholderConfig, passpointConfiguration, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -3409,7 +3910,7 @@
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
                         TEST_PACKAGE_1, TEST_FEATURE));
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
         verify(mWifiConfigManager, times(2)).saveToStore(true);
         reset(mWifiConfigManager);
         // Create WifiConfiguration for Passpoint network.
@@ -3447,14 +3948,13 @@
      */
     @Test
     public void getMatchingScanResultsTestWithPasspointAndNonPasspointMatch() {
-        WifiConfiguration dummyConfiguration = createDummyWifiConfigurationForPasspoint(TEST_FQDN);
-        dummyConfiguration.FQDN = TEST_FQDN;
+        WifiConfiguration placeholderConfig = createPlaceholderConfigForPasspoint(TEST_FQDN, null);
         PasspointConfiguration mockPasspoint = mock(PasspointConfiguration.class);
-        WifiNetworkSuggestion passpointSuggestion = new WifiNetworkSuggestion(
-                dummyConfiguration, mockPasspoint, false, false, true, true);
-        WifiNetworkSuggestion nonPasspointSuggestion = new WifiNetworkSuggestion(
+        WifiNetworkSuggestion passpointSuggestion = createWifiNetworkSuggestion(
+                placeholderConfig, mockPasspoint, false, false, true, true, DEFAULT_PRIORITY_GROUP);
+        WifiNetworkSuggestion nonPasspointSuggestion = createWifiNetworkSuggestion(
                 WifiConfigurationTestUtil.createOpenNetwork(),
-                null, false, false, true, true);
+                null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> suggestions = new ArrayList<>() {{
                 add(passpointSuggestion);
                 add(nonPasspointSuggestion);
@@ -3504,9 +4004,9 @@
      */
     @Test
     public void getMatchingScanResultsTestWithMatchNothing() {
-        WifiNetworkSuggestion nonPasspointSuggestion = new WifiNetworkSuggestion(
+        WifiNetworkSuggestion nonPasspointSuggestion = createWifiNetworkSuggestion(
                 WifiConfigurationTestUtil.createOpenNetwork(),
-                null, false, false, true, true);
+                null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> suggestions = new ArrayList<>() {{
                 add(nonPasspointSuggestion);
                 }};
@@ -3541,9 +4041,9 @@
      */
     @Test
     public void getMatchingScanResultsTestWithInvalidWifiConfiguration() {
-        WifiNetworkSuggestion nonPasspointSuggestion = new WifiNetworkSuggestion(
+        WifiNetworkSuggestion nonPasspointSuggestion = createWifiNetworkSuggestion(
                 WifiConfigurationTestUtil.createOpenNetwork(),
-                null, false, false, true, true);
+                null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> suggestions = new ArrayList<>() {{
                 add(nonPasspointSuggestion);
             }};
@@ -3578,7 +4078,7 @@
         when(mWifiCarrierInfoManager.getCarrierIdForPackageWithCarrierPrivileges(TEST_PACKAGE_1))
                 .thenReturn(TEST_CARRIER_ID);
         when(mWifiCarrierInfoManager.getMatchingSubId(TEST_CARRIER_ID)).thenReturn(TEST_SUBID);
-        when(mWifiCarrierInfoManager.getCarrierNameforSubId(TEST_SUBID))
+        when(mWifiCarrierInfoManager.getCarrierNameForSubId(TEST_SUBID))
                 .thenReturn(TEST_CARRIER_NAME);
         when(mWifiCarrierInfoManager.requiresImsiEncryption(TEST_SUBID)).thenReturn(false);
         when(mWifiCarrierInfoManager.hasUserApprovedImsiPrivacyExemptionForCarrier(TEST_CARRIER_ID))
@@ -3590,12 +4090,14 @@
                 WifiEnterpriseConfig.Eap.SIM, WifiEnterpriseConfig.Phase2.NONE);
         PasspointConfiguration passpointConfiguration =
                 createTestConfigWithSimCredential(TEST_FQDN, TEST_IMSI, TEST_REALM);
-        WifiConfiguration dummyConfiguration = createDummyWifiConfigurationForPasspoint(TEST_FQDN);
-        dummyConfiguration.setPasspointUniqueId(passpointConfiguration.getUniqueId());
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                eapSimConfig, null, true, false, true, true);
-        WifiNetworkSuggestion passpointSuggestion = new WifiNetworkSuggestion(
-                dummyConfiguration, passpointConfiguration, true, false, true, true);
+        WifiConfiguration placeholderConfig = createPlaceholderConfigForPasspoint(TEST_FQDN,
+                passpointConfiguration.getUniqueId());
+        placeholderConfig.setPasspointUniqueId(passpointConfiguration.getUniqueId());
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                eapSimConfig, null, true, false, true, true, DEFAULT_PRIORITY_GROUP);
+        WifiNetworkSuggestion passpointSuggestion = createWifiNetworkSuggestion(
+                placeholderConfig, passpointConfiguration, true, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList =
                 Arrays.asList(networkSuggestion, passpointSuggestion);
 
@@ -3603,26 +4105,35 @@
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
                         TEST_PACKAGE_1, TEST_FEATURE));
 
-        verifyNoMoreInteractions(mNotificationManger);
-        Set<ExtendedWifiNetworkSuggestion> matchedSuggestion = mWifiNetworkSuggestionsManager
+        verifyNoMoreInteractions(mWifiNotificationManager);
+        Set<ExtendedWifiNetworkSuggestion> matchedSuggestions = mWifiNetworkSuggestionsManager
                 .getNetworkSuggestionsForScanDetail(createScanDetailForNetwork(eapSimConfig));
         verify(mWifiCarrierInfoManager)
                 .sendImsiProtectionExemptionNotificationIfRequired(TEST_CARRIER_ID);
-        for (ExtendedWifiNetworkSuggestion ewns : matchedSuggestion) {
+        for (ExtendedWifiNetworkSuggestion ewns : matchedSuggestions) {
             assertFalse(ewns.isAutojoinEnabled);
         }
 
         // Simulate user approved carrier
+        eapSimConfig.fromWifiNetworkSuggestion = true;
+        eapSimConfig.creatorUid = TEST_UID_1;
+        eapSimConfig.creatorName = TEST_PACKAGE_1;
+        when(mWifiConfigManager.getConfiguredNetwork(anyString())).thenReturn(eapSimConfig);
+        when(mWifiConfigManager.addOrUpdateNetwork(any(), anyInt(), anyString()))
+                .thenReturn(new NetworkUpdateResult(TEST_NETWORK_ID));
         mUserApproveCarrierListenerArgumentCaptor.getValue().onUserAllowed(TEST_CARRIER_ID);
         when(mWifiCarrierInfoManager.hasUserApprovedImsiPrivacyExemptionForCarrier(TEST_CARRIER_ID))
                 .thenReturn(true);
-        verify(mPasspointManager).enableAutojoin(anyString(), any(), anyBoolean());
-        matchedSuggestion = mWifiNetworkSuggestionsManager
+        verify(mPasspointManager).enableAutojoin(anyString(), any(), eq(true));
+        verify(mWifiConfigManager).addOrUpdateNetwork(any(), anyInt(), anyString());
+        verify(mWifiConfigManager).allowAutojoin(anyInt(), eq(true));
+        matchedSuggestions = mWifiNetworkSuggestionsManager
                 .getNetworkSuggestionsForScanDetail(createScanDetailForNetwork(eapSimConfig));
 
-        for (ExtendedWifiNetworkSuggestion ewns : matchedSuggestion) {
+        for (ExtendedWifiNetworkSuggestion ewns : matchedSuggestions) {
             assertTrue(ewns.isAutojoinEnabled);
         }
+        verify(mWifiConfigManager, atLeastOnce()).saveToStore(true);
     }
 
     /**
@@ -3632,8 +4143,8 @@
     public void testAddInvalidNetworkSuggestions() {
         WifiConfiguration invalidConfig = WifiConfigurationTestUtil.createOpenNetwork();
         invalidConfig.SSID = "";
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(invalidConfig,
-                null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(invalidConfig,
+                null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -3652,10 +4163,10 @@
         HomeSp homeSp = new HomeSp();
         homeSp.setFqdn(TEST_FQDN);
         passpointConfiguration.setHomeSp(homeSp);
-        WifiConfiguration dummyConfig = new WifiConfiguration();
-        dummyConfig.FQDN = TEST_FQDN;
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(dummyConfig,
-                passpointConfiguration, false, false, true, true);
+        WifiConfiguration placeholderConfig = new WifiConfiguration();
+        placeholderConfig.FQDN = TEST_FQDN;
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(placeholderConfig,
+                passpointConfiguration, false, false, true, true, DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -3675,12 +4186,13 @@
         WifiConfiguration network2 = WifiConfigurationTestUtil.createOpenNetwork();
         PasspointConfiguration passpointConfiguration =
                 createTestConfigWithUserCredential(TEST_FQDN, TEST_FRIENDLY_NAME);
-        WifiConfiguration dummyConfig = new WifiConfiguration();
-        dummyConfig.FQDN = TEST_FQDN;
+        WifiConfiguration placeholderConfig = new WifiConfiguration();
+        placeholderConfig.FQDN = TEST_FQDN;
         WifiNetworkSuggestion networkSuggestion =
-                new WifiNetworkSuggestion(network1, null, false, false, true, true);
-        WifiNetworkSuggestion passpointSuggestion = new WifiNetworkSuggestion(dummyConfig,
-                passpointConfiguration, false, false, true, true);
+                createWifiNetworkSuggestion(network1, null, false, false, true, true,
+                        DEFAULT_PRIORITY_GROUP);
+        WifiNetworkSuggestion passpointSuggestion = createWifiNetworkSuggestion(placeholderConfig,
+                passpointConfiguration, false, false, true, true, DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList =
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
@@ -3691,7 +4203,7 @@
                         .add(networkSuggestionList, TEST_UID_1, TEST_PACKAGE_1, TEST_FEATURE));
         assertTrue(mWifiNetworkSuggestionsManager
                 .getAllScanOptimizationSuggestionNetworks().isEmpty());
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
         List<WifiConfiguration> pnoNetwork =
                 mWifiNetworkSuggestionsManager.getAllScanOptimizationSuggestionNetworks();
         assertEquals(1, pnoNetwork.size());
@@ -3703,12 +4215,13 @@
         when(mNetworkScoreManager.getActiveScorerPackage()).thenReturn(TEST_PACKAGE_1);
         WifiConfiguration network = WifiConfigurationTestUtil.createOpenNetwork();
         WifiNetworkSuggestion networkSuggestion =
-                new WifiNetworkSuggestion(network, null, false, false, true, true);
+                createWifiNetworkSuggestion(network, null, false, false, true, true,
+                        DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList = Arrays.asList(networkSuggestion);
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiNetworkSuggestionsManager
                         .add(networkSuggestionList, TEST_UID_1, TEST_PACKAGE_1, TEST_FEATURE));
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(false, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(false, TEST_UID_1, TEST_PACKAGE_1);
 
         List<WifiConfiguration> networks =
                 mWifiNetworkSuggestionsManager.getAllScanOptimizationSuggestionNetworks();
@@ -3723,16 +4236,18 @@
     public void testIsMostRecentlyConnectedSuggestion() {
         WifiConfiguration network = WifiConfigurationTestUtil.createOpenNetwork();
         WifiNetworkSuggestion networkSuggestion =
-                new WifiNetworkSuggestion(network, null, false, false, true, true);
+                createWifiNetworkSuggestion(network, null, false, false, true, true,
+                        DEFAULT_PRIORITY_GROUP);
         List<WifiNetworkSuggestion> networkSuggestionList = Arrays.asList(networkSuggestion);
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiNetworkSuggestionsManager
                         .add(networkSuggestionList, TEST_UID_1, TEST_PACKAGE_1, TEST_FEATURE));
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
         when(mLruConnectionTracker.isMostRecentlyConnected(any())).thenReturn(true);
         Map<String, PerAppInfo> suggestionStore = new HashMap<>(mDataSource.toSerialize());
         PerAppInfo perAppInfo = suggestionStore.get(TEST_PACKAGE_1);
-        ExtendedWifiNetworkSuggestion ewns = perAppInfo.extNetworkSuggestions.iterator().next();
+        ExtendedWifiNetworkSuggestion ewns =
+                perAppInfo.extNetworkSuggestions.values().iterator().next();
         assertTrue(ewns.wns.wifiConfiguration.isMostRecentlyConnected);
         mDataSource.fromDeserialized(suggestionStore);
         verify(mLruConnectionTracker).addNetwork(any());
@@ -3741,7 +4256,7 @@
         when(mLruConnectionTracker.isMostRecentlyConnected(any())).thenReturn(false);
         suggestionStore = mDataSource.toSerialize();
         perAppInfo = suggestionStore.get(TEST_PACKAGE_1);
-        ewns = perAppInfo.extNetworkSuggestions.iterator().next();
+        ewns = perAppInfo.extNetworkSuggestions.values().iterator().next();
         assertFalse(ewns.wns.wifiConfiguration.isMostRecentlyConnected);
         mDataSource.fromDeserialized(suggestionStore);
         verify(mLruConnectionTracker, never()).addNetwork(any());
@@ -3753,10 +4268,9 @@
                 mock(WifiNetworkSuggestionsManager.OnSuggestionUpdateListener.class);
         mWifiNetworkSuggestionsManager.addOnSuggestionUpdateListener(listener);
 
-        WifiConfiguration dummyConfiguration = createDummyWifiConfigurationForPasspoint(TEST_FQDN);
-        dummyConfiguration.FQDN = TEST_FQDN;
-        WifiNetworkSuggestion networkSuggestion1 = new WifiNetworkSuggestion(
-                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion1 = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
 
         List<WifiNetworkSuggestion> networkSuggestionList1 =
                 new ArrayList<WifiNetworkSuggestion>() {{
@@ -3783,13 +4297,13 @@
         WifiConfiguration network1 = WifiConfigurationTestUtil.createOpenNetwork();
         ScanDetail scanDetail1 = createScanDetailForNetwork(network1);
         network1.carrierId = TEST_CARRIER_ID;
-        WifiNetworkSuggestion suggestion1 = new WifiNetworkSuggestion(
-                network1, null, false, false, true, true);
+        WifiNetworkSuggestion suggestion1 = createWifiNetworkSuggestion(
+                network1, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
         WifiConfiguration network2 = WifiConfigurationTestUtil.createPskNetwork();
         ScanDetail scanDetail2 = createScanDetailForNetwork(network2);
         network2.carrierId = TEST_CARRIER_ID;
-        WifiNetworkSuggestion suggestion2 = new WifiNetworkSuggestion(
-                network2, null, false, false, true, true);
+        WifiNetworkSuggestion suggestion2 = createWifiNetworkSuggestion(
+                network2, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
 
         List<ScanDetail> scanDetails = Arrays.asList(scanDetail1, scanDetail2);
         // Without same open suggestion in the framework, should not be ignored.
@@ -3811,13 +4325,13 @@
         WifiConfiguration network1 = WifiConfigurationTestUtil.createOpenNetwork();
         ScanDetail scanDetail1 = createScanDetailForNetwork(network1);
         network1.carrierId = TEST_CARRIER_ID;
-        WifiNetworkSuggestion suggestion1 = new WifiNetworkSuggestion(
-                network1, null, false, false, true, true);
+        WifiNetworkSuggestion suggestion1 = createWifiNetworkSuggestion(
+                network1, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
         WifiConfiguration network2 = WifiConfigurationTestUtil.createPskNetwork();
         ScanDetail scanDetail2 = createScanDetailForNetwork(network2);
         network2.carrierId = TEST_CARRIER_ID;
-        WifiNetworkSuggestion suggestion2 = new WifiNetworkSuggestion(
-                network2, null, false, false, true, true);
+        WifiNetworkSuggestion suggestion2 = createWifiNetworkSuggestion(
+                network2, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
 
         List<ScanDetail> scanDetails = Arrays.asList(scanDetail1, scanDetail2);
         // Without secure suggestion in the framework, should not be ignored.
@@ -3839,13 +4353,13 @@
         WifiConfiguration network1 = WifiConfigurationTestUtil.createOpenNetwork();
         ScanDetail scanDetail1 = createScanDetailForNetwork(network1);
         network1.carrierId = TEST_CARRIER_ID;
-        WifiNetworkSuggestion suggestion1 = new WifiNetworkSuggestion(
-                network1, null, false, false, true, true);
+        WifiNetworkSuggestion suggestion1 = createWifiNetworkSuggestion(
+                network1, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
         WifiConfiguration network2 = WifiConfigurationTestUtil.createPskNetwork();
         ScanDetail scanDetail2 = createScanDetailForNetwork(network2);
         network2.carrierId = TEST_CARRIER_ID;
-        WifiNetworkSuggestion suggestion2 = new WifiNetworkSuggestion(
-                network2, null, false, false, true, true);
+        WifiNetworkSuggestion suggestion2 = createWifiNetworkSuggestion(
+                network2, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
 
         List<ScanDetail> scanDetails = Arrays.asList(scanDetail1, scanDetail2);
         // Without CarrierProvisioningPermission, should not be ignored.
@@ -3867,13 +4381,13 @@
         WifiConfiguration network1 = WifiConfigurationTestUtil.createOpenNetwork();
         ScanDetail scanDetail1 = createScanDetailForNetwork(network1);
         network1.carrierId = VALID_CARRIER_ID;
-        WifiNetworkSuggestion suggestion1 = new WifiNetworkSuggestion(
-                network1, null, false, false, true, true);
+        WifiNetworkSuggestion suggestion1 = createWifiNetworkSuggestion(
+                network1, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
         WifiConfiguration network2 = WifiConfigurationTestUtil.createPskNetwork();
         ScanDetail scanDetail2 = createScanDetailForNetwork(network2);
         network2.carrierId = TEST_CARRIER_ID;
-        WifiNetworkSuggestion suggestion2 = new WifiNetworkSuggestion(
-                network2, null, false, false, true, true);
+        WifiNetworkSuggestion suggestion2 = createWifiNetworkSuggestion(
+                network2, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
 
         List<ScanDetail> scanDetails = Arrays.asList(scanDetail1, scanDetail2);
         // Open and secure suggestions have different carrierId, should not be ignored.
@@ -3895,13 +4409,13 @@
         WifiConfiguration network1 = WifiConfigurationTestUtil.createOpenNetwork();
         ScanDetail scanDetail1 = createScanDetailForNetwork(network1);
         network1.carrierId = TEST_CARRIER_ID;
-        WifiNetworkSuggestion suggestion1 = new WifiNetworkSuggestion(
-                network1, null, false, false, true, true);
+        WifiNetworkSuggestion suggestion1 = createWifiNetworkSuggestion(
+                network1, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
         WifiConfiguration network2 = WifiConfigurationTestUtil.createPskNetwork();
         ScanDetail scanDetail2 = createScanDetailForNetwork(network2);
         network2.carrierId = TEST_CARRIER_ID;
-        WifiNetworkSuggestion suggestion2 = new WifiNetworkSuggestion(
-                network2, null, false, false, true, false);
+        WifiNetworkSuggestion suggestion2 = createWifiNetworkSuggestion(
+                network2, null, false, false, true, false, DEFAULT_PRIORITY_GROUP);
 
         List<ScanDetail> scanDetails = Arrays.asList(scanDetail1, scanDetail2);
         // Secure suggestions is auto-join disabled, should not be ignored.
@@ -3923,14 +4437,14 @@
         WifiConfiguration network1 = WifiConfigurationTestUtil.createOpenNetwork();
         ScanDetail scanDetail1 = createScanDetailForNetwork(network1);
         network1.carrierId = TEST_CARRIER_ID;
-        WifiNetworkSuggestion suggestion1 = new WifiNetworkSuggestion(
-                network1, null, false, false, true, true);
+        WifiNetworkSuggestion suggestion1 = createWifiNetworkSuggestion(
+                network1, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
         WifiConfiguration network2 = WifiConfigurationTestUtil.createPskNetwork();
         network2.meteredOverride = WifiConfiguration.METERED_OVERRIDE_METERED;
         ScanDetail scanDetail2 = createScanDetailForNetwork(network2);
         network2.carrierId = TEST_CARRIER_ID;
-        WifiNetworkSuggestion suggestion2 = new WifiNetworkSuggestion(
-                network2, null, false, false, true, true);
+        WifiNetworkSuggestion suggestion2 = createWifiNetworkSuggestion(
+                network2, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
 
         List<ScanDetail> scanDetails = Arrays.asList(scanDetail1, scanDetail2);
         // Secure suggestions is auto-join disabled, should not be ignored.
@@ -3952,24 +4466,30 @@
         WifiConfiguration network1 = WifiConfigurationTestUtil.createOpenNetwork();
         ScanDetail scanDetail1 = createScanDetailForNetwork(network1);
         network1.carrierId = TEST_CARRIER_ID;
-        WifiNetworkSuggestion suggestion1 = new WifiNetworkSuggestion(
-                network1, null, false, false, true, true);
+        WifiNetworkSuggestion suggestion1 = createWifiNetworkSuggestion(
+                network1, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
         WifiConfiguration network2 = WifiConfigurationTestUtil.createPskNetwork();
         ScanDetail scanDetail2 = createScanDetailForNetwork(network2);
         network2.carrierId = TEST_CARRIER_ID;
-        WifiNetworkSuggestion suggestion2 = new WifiNetworkSuggestion(
-                network2, null, false, false, true, true);
+        WifiNetworkSuggestion suggestion2 = createWifiNetworkSuggestion(
+                network2, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
 
         WifiConfiguration wcmConfig = new WifiConfiguration(network2);
+        wcmConfig.fromWifiNetworkSuggestion = true;
+        wcmConfig.creatorName = TEST_PACKAGE_1;
+        wcmConfig.creatorUid = TEST_UID_1;
         WifiConfiguration.NetworkSelectionStatus status =
                 mock(WifiConfiguration.NetworkSelectionStatus.class);
         when(status.isNetworkEnabled()).thenReturn(false);
         wcmConfig.setNetworkSelectionStatus(status);
-        when(mWifiConfigManager.getConfiguredNetwork(network2.getKey())).thenReturn(wcmConfig);
+        when(mWifiConfigManager.getConfiguredNetwork(wcmConfig.getProfileKey()))
+                .thenReturn(wcmConfig);
 
         List<ScanDetail> scanDetails = Arrays.asList(scanDetail1, scanDetail2);
         // Secure suggestions is auto-join disabled, should not be ignored.
         List<WifiNetworkSuggestion> suggestionList = Arrays.asList(suggestion1, suggestion2);
+        when(mWifiConfigManager.addOrUpdateNetwork(any(), anyInt(), anyString()))
+                .thenReturn(new NetworkUpdateResult(TEST_NETWORK_ID));
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiNetworkSuggestionsManager.add(suggestionList, TEST_UID_1,
                         TEST_PACKAGE_1, TEST_FEATURE));
@@ -3987,13 +4507,13 @@
         WifiConfiguration network1 = WifiConfigurationTestUtil.createOpenNetwork();
         ScanDetail scanDetail1 = createScanDetailForNetwork(network1);
         network1.carrierId = TEST_CARRIER_ID;
-        WifiNetworkSuggestion suggestion1 = new WifiNetworkSuggestion(
-                network1, null, false, false, true, true);
+        WifiNetworkSuggestion suggestion1 = createWifiNetworkSuggestion(
+                network1, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
         WifiConfiguration network2 = WifiConfigurationTestUtil.createPskNetwork();
         ScanDetail scanDetail2 = createScanDetailForNetwork(network2);
         network2.carrierId = TEST_CARRIER_ID;
-        WifiNetworkSuggestion suggestion2 = new WifiNetworkSuggestion(
-                network2, null, false, false, true, true);
+        WifiNetworkSuggestion suggestion2 = createWifiNetworkSuggestion(
+                network2, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
 
         List<ScanDetail> scanDetails = Arrays.asList(scanDetail1, scanDetail2);
         // Both open and secure suggestions with same carrierId,
@@ -4015,13 +4535,13 @@
         WifiConfiguration network1 = WifiConfigurationTestUtil.createOpenNetwork();
         ScanDetail scanDetail1 = createScanDetailForNetwork(network1);
         network1.carrierId = TEST_CARRIER_ID;
-        WifiNetworkSuggestion suggestion1 = new WifiNetworkSuggestion(
-                network1, null, false, false, true, true);
+        WifiNetworkSuggestion suggestion1 = createWifiNetworkSuggestion(
+                network1, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
         WifiConfiguration network2 = WifiConfigurationTestUtil.createPskNetwork();
         ScanDetail scanDetail2 = createScanDetailForNetwork(network2);
         network2.carrierId = TEST_CARRIER_ID;
-        WifiNetworkSuggestion suggestion2 = new WifiNetworkSuggestion(
-                network2, null, false, false, true, true);
+        WifiNetworkSuggestion suggestion2 = createWifiNetworkSuggestion(
+                network2, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
 
         List<ScanDetail> scanDetails = Arrays.asList(scanDetail1, scanDetail2);
         // Both open and secure suggestions with same carrierId,
@@ -4035,9 +4555,8 @@
 
     @Test
     public void testUnregisterSuggestionConnectionStatusListenerNeverRegistered() {
-        int listenerIdentifier = 1234;
         mWifiNetworkSuggestionsManager.unregisterSuggestionConnectionStatusListener(
-                listenerIdentifier, TEST_PACKAGE_1, TEST_UID_1);
+                mConnectionStatusListener, TEST_PACKAGE_1, TEST_UID_1);
     }
 
     /**
@@ -4046,11 +4565,11 @@
     @Test
     public void testGetApprovedNetworkSuggestions() {
         WifiConfiguration wifiConfiguration = WifiConfigurationTestUtil.createOpenNetwork();
-        WifiNetworkSuggestion networkSuggestion1 = new WifiNetworkSuggestion(
-                wifiConfiguration, null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion1 = createWifiNetworkSuggestion(
+                wifiConfiguration, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
         // Reuse the same network credentials to ensure they both match.
-        WifiNetworkSuggestion networkSuggestion2 = new WifiNetworkSuggestion(
-                wifiConfiguration, null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion2 = createWifiNetworkSuggestion(
+                wifiConfiguration, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
 
         List<WifiNetworkSuggestion> networkSuggestionList1 =
                 new ArrayList<WifiNetworkSuggestion>() {{
@@ -4071,14 +4590,14 @@
         // nothing approved, return empty.
         assertTrue(mWifiNetworkSuggestionsManager.getAllApprovedNetworkSuggestions().isEmpty());
 
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
         // only app 1 approved.
         assertEquals(new HashSet<WifiNetworkSuggestion>() {{
                     add(networkSuggestion1);
                 }},
                 mWifiNetworkSuggestionsManager.getAllApprovedNetworkSuggestions());
 
-        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_2);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_2);
         // both app 1 & 2 approved.
         assertEquals(new HashSet<WifiNetworkSuggestion>() {{
                     add(networkSuggestion1);
@@ -4088,13 +4607,80 @@
     }
 
     /**
+     * Verify only carrier privileged app can suggest carrier merged network. A valid carrier
+     * merged network must be metered enterprise network with a valid subscription Id.
+     */
+    @Test
+    public void testAddCarrierMergedNetwork() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        WifiConfiguration eapSimConfig = WifiConfigurationTestUtil.createEapNetwork(
+                WifiEnterpriseConfig.Eap.SIM, WifiEnterpriseConfig.Phase2.NONE);
+        WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
+        eapSimConfig.carrierMerged = true;
+        eapSimConfig.meteredOverride = WifiConfiguration.METERED_OVERRIDE_METERED;
+        eapSimConfig.subscriptionId = TEST_SUBID;
+        config.carrierMerged = true;
+        config.meteredOverride = WifiConfiguration.METERED_OVERRIDE_METERED;
+        config.subscriptionId = TEST_SUBID;
+
+        // Adding carrier merged network < EAP will fail.
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                config, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_INVALID,
+                mWifiNetworkSuggestionsManager.add(Arrays.asList(networkSuggestion), TEST_UID_1,
+                        TEST_PACKAGE_1, TEST_FEATURE));
+
+        // Adding carrier merged network is not metered will fail.
+        eapSimConfig.meteredOverride = WifiConfiguration.METERED_OVERRIDE_NONE;
+        networkSuggestion = createWifiNetworkSuggestion(
+                eapSimConfig, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_INVALID,
+                mWifiNetworkSuggestionsManager.add(Arrays.asList(networkSuggestion), TEST_UID_1,
+                        TEST_PACKAGE_1, TEST_FEATURE));
+
+        // Adding carrier merged network without a valid SubID will fail.
+        eapSimConfig.meteredOverride = WifiConfiguration.METERED_OVERRIDE_METERED;
+        eapSimConfig.subscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+        networkSuggestion = createWifiNetworkSuggestion(
+                eapSimConfig, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_INVALID,
+                mWifiNetworkSuggestionsManager.add(Arrays.asList(networkSuggestion), TEST_UID_1,
+                        TEST_PACKAGE_1, TEST_FEATURE));
+
+        // Adding carrier merged network from a non carrier privileged app will not be allowed.
+        eapSimConfig.subscriptionId = TEST_SUBID;
+        networkSuggestion = createWifiNetworkSuggestion(
+                eapSimConfig, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_NOT_ALLOWED,
+                mWifiNetworkSuggestionsManager.add(Arrays.asList(networkSuggestion), TEST_UID_1,
+                        TEST_PACKAGE_1, TEST_FEATURE));
+
+        // Adding a carrier merged network when the carrier configuration doesn't indicate it will
+        // provision such networks is not allowed
+        when(mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(TEST_UID_1))
+                .thenReturn(true);
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_NOT_ALLOWED,
+                mWifiNetworkSuggestionsManager.add(Arrays.asList(networkSuggestion), TEST_UID_1,
+                        TEST_PACKAGE_1, TEST_FEATURE));
+
+        when(mWifiCarrierInfoManager.areMergedCarrierWifiNetworksAllowed(TEST_SUBID)).thenReturn(
+                true);
+        eapSimConfig.carrierId = VALID_CARRIER_ID;
+        networkSuggestion = createWifiNetworkSuggestion(
+                eapSimConfig, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
+                mWifiNetworkSuggestionsManager.add(Arrays.asList(networkSuggestion), TEST_UID_1,
+                        TEST_PACKAGE_1, TEST_FEATURE));
+    }
+
+    /**
      * Verify when calling API from background user will fail.
      */
     @Test
     public void testCallingFromBackgroundUserWillFailed() {
         WifiConfiguration wifiConfiguration = WifiConfigurationTestUtil.createOpenNetwork();
-        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
-                wifiConfiguration, null, false, false, true, true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                wifiConfiguration, null, false, false, true, true, DEFAULT_PRIORITY_GROUP);
 
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiNetworkSuggestionsManager.add(Arrays.asList(networkSuggestion), TEST_UID_1,
@@ -4109,7 +4695,7 @@
                         TEST_PACKAGE_1));
         assertTrue(mWifiNetworkSuggestionsManager.get(TEST_PACKAGE_1, TEST_UID_1).isEmpty());
         assertFalse(mWifiNetworkSuggestionsManager.registerSuggestionConnectionStatusListener(
-                mBinder, mListener, NETWORK_CALLBACK_ID, TEST_PACKAGE_1, TEST_UID_1));
+                mConnectionStatusListener, TEST_PACKAGE_1, TEST_UID_1));
 
         // When switch the user back to foreground
         when(mWifiPermissionsUtil.doesUidBelongToCurrentUser(TEST_UID_1)).thenReturn(true);
@@ -4121,7 +4707,150 @@
                 mWifiNetworkSuggestionsManager.remove(Arrays.asList(networkSuggestion), TEST_UID_1,
                         TEST_PACKAGE_1));
         assertTrue(mWifiNetworkSuggestionsManager.registerSuggestionConnectionStatusListener(
-                mBinder, mListener, NETWORK_CALLBACK_ID, TEST_PACKAGE_1, TEST_UID_1));
+                mConnectionStatusListener, TEST_PACKAGE_1, TEST_UID_1));
+    }
+
+    @Test
+    public void testSuggestionCarrierNetworkConnectionBroadcastAndDisconnectWithCarrierIdOnly() {
+        when(mWifiCarrierInfoManager.getBestMatchSubscriptionId(any())).thenReturn(TEST_SUBID);
+        when(mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(TEST_UID_1))
+                .thenReturn(true);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createPskNetwork(), null, true, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        networkSuggestion.wifiConfiguration.carrierId = TEST_CARRIER_ID;
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
+                mWifiNetworkSuggestionsManager.add(Arrays.asList(networkSuggestion), TEST_UID_1,
+                        TEST_PACKAGE_1, TEST_FEATURE));
+        mInorder.verify(mWifiPermissionsUtil).doesUidBelongToCurrentUser(anyInt());
+        assertTrue(mWifiNetworkSuggestionsManager.hasUserApprovedForApp(TEST_PACKAGE_1));
+
+        // Simulate connecting to the network.
+        WifiConfiguration connectNetwork =
+                new WifiConfiguration(networkSuggestion.wifiConfiguration);
+        connectNetwork.fromWifiNetworkSuggestion = true;
+        connectNetwork.ephemeral = true;
+        connectNetwork.creatorName = TEST_PACKAGE_1;
+        connectNetwork.creatorUid = TEST_UID_1;
+        connectNetwork.subscriptionId = TEST_SUBID;
+        mWifiNetworkSuggestionsManager.handleConnectionAttemptEnded(
+                WifiMetrics.ConnectionEvent.FAILURE_NONE, connectNetwork, TEST_BSSID);
+
+        verify(mWifiMetrics).incrementNetworkSuggestionApiNumConnectSuccess();
+
+        // Verify that the correct broadcast was sent out.
+        mInorder.verify(mWifiPermissionsUtil).enforceCanAccessScanResults(eq(TEST_PACKAGE_1),
+                eq(TEST_FEATURE), eq(TEST_UID_1), nullable(String.class));
+        validatePostConnectionBroadcastSent(TEST_PACKAGE_1, networkSuggestion);
+
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
+                mWifiNetworkSuggestionsManager.remove(Arrays.asList(networkSuggestion), TEST_UID_1,
+                        TEST_PACKAGE_1));
+        verify(mWifiConfigManager).removeSuggestionConfiguredNetwork(
+                argThat(new WifiConfigMatcher(connectNetwork)));
+        mInorder.verify(mWifiPermissionsUtil).doesUidBelongToCurrentUser(anyInt());
+
+        // Verify no more broadcast were sent out.
+        mInorder.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void testTreatAppAsCarrierProvider() {
+        assertFalse(mWifiNetworkSuggestionsManager
+                .isAppWorkingAsCrossCarrierProvider(TEST_APP_NAME_1));
+        mWifiNetworkSuggestionsManager.setAppWorkingAsCrossCarrierProvider(TEST_APP_NAME_1, true);
+        assertTrue(mWifiNetworkSuggestionsManager
+                .isAppWorkingAsCrossCarrierProvider(TEST_APP_NAME_1));
+        mWifiNetworkSuggestionsManager.setAppWorkingAsCrossCarrierProvider(TEST_APP_NAME_1, false);
+        assertFalse(mWifiNetworkSuggestionsManager
+                .isAppWorkingAsCrossCarrierProvider(TEST_APP_NAME_1));
+    }
+
+    @Test
+    public void testSetAnonymousIdentityOnSuggestionNetwork()  {
+        when(mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(TEST_UID_1))
+                .thenReturn(true);
+        WifiConfiguration eapSimConfig = WifiConfigurationTestUtil.createWpa2Wpa3EnterpriseNetwork(
+                WifiEnterpriseConfig.Eap.SIM, WifiEnterpriseConfig.Phase2.NONE);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                new WifiConfiguration(eapSimConfig), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        List<WifiNetworkSuggestion> networkSuggestionList =
+                new ArrayList<WifiNetworkSuggestion>() {{
+                    add(networkSuggestion);
+                }};
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
+                mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
+                        TEST_PACKAGE_1, TEST_FEATURE));
+        WifiConfiguration configuration =
+                new WifiConfiguration(eapSimConfig);
+        configuration.fromWifiNetworkSuggestion = true;
+        configuration.ephemeral = true;
+        configuration.creatorName = TEST_PACKAGE_1;
+        configuration.creatorUid = TEST_UID_1;
+        configuration.enterpriseConfig.setAnonymousIdentity(TEST_ANONYMOUS_IDENTITY);
+
+        mWifiNetworkSuggestionsManager.setAnonymousIdentity(configuration);
+
+        Set<ExtendedWifiNetworkSuggestion> matchedSuggestions = mWifiNetworkSuggestionsManager
+                .getNetworkSuggestionsForWifiConfiguration(configuration,
+                        TEST_BSSID);
+        for (ExtendedWifiNetworkSuggestion ewns : matchedSuggestions) {
+            assertEquals(TEST_ANONYMOUS_IDENTITY, ewns.anonymousIdentity);
+        }
+        // Reset SIM network suggestion, Anonymous Identity should gone.
+        mWifiNetworkSuggestionsManager.resetSimNetworkSuggestions();
+        matchedSuggestions = mWifiNetworkSuggestionsManager
+                .getNetworkSuggestionsForWifiConfiguration(configuration,
+                        TEST_BSSID);
+        for (ExtendedWifiNetworkSuggestion ewns : matchedSuggestions) {
+            assertEquals(null, ewns.anonymousIdentity);
+        }
+        verify(mWifiConfigManager, times(3)).saveToStore(true);
+    }
+
+    @Test
+    public void testSetUserConnectChoice() {
+        WifiConfigManager.OnNetworkUpdateListener listener = mNetworkListenerCaptor.getValue();
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenOweNetwork();
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                new WifiConfiguration(config), null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        List<WifiNetworkSuggestion> networkSuggestionList =
+                new ArrayList<WifiNetworkSuggestion>() {{
+                    add(networkSuggestion);
+                }};
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
+                mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
+                        TEST_PACKAGE_1, TEST_FEATURE));
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_UID_1, TEST_PACKAGE_1);
+        WifiConfiguration configuration =
+                new WifiConfiguration(config);
+        configuration.fromWifiNetworkSuggestion = true;
+        configuration.ephemeral = true;
+        configuration.creatorName = TEST_PACKAGE_1;
+        configuration.creatorUid = TEST_UID_1;
+
+        listener.onConnectChoiceSet(List.of(configuration),
+                USER_CONNECT_CHOICE, TEST_RSSI);
+        Set<ExtendedWifiNetworkSuggestion> matchedSuggestions = mWifiNetworkSuggestionsManager
+                .getNetworkSuggestionsForWifiConfiguration(configuration,
+                        TEST_BSSID);
+        for (ExtendedWifiNetworkSuggestion ewns : matchedSuggestions) {
+            assertEquals(USER_CONNECT_CHOICE, ewns.connectChoice);
+            assertEquals(TEST_RSSI, ewns.connectChoiceRssi);
+        }
+
+        listener.onConnectChoiceRemoved(USER_CONNECT_CHOICE);
+        matchedSuggestions = mWifiNetworkSuggestionsManager
+                .getNetworkSuggestionsForWifiConfiguration(configuration,
+                        TEST_BSSID);
+        for (ExtendedWifiNetworkSuggestion ewns : matchedSuggestions) {
+            assertEquals(null, ewns.connectChoice);
+            assertEquals(0, ewns.connectChoiceRssi);
+        }
+        // Add suggestion and change user approval have 2, set and remove user choice have 2.
+        verify(mWifiConfigManager, times(4)).saveToStore(true);
     }
 
     /**
@@ -4176,9 +4905,126 @@
         return config;
     }
 
-    private WifiConfiguration createDummyWifiConfigurationForPasspoint(String fqdn) {
+    private WifiConfiguration createPlaceholderConfigForPasspoint(String fqdn,
+            String uniqueId) {
         WifiConfiguration config = new WifiConfiguration();
         config.FQDN = fqdn;
+        config.setPasspointUniqueId(uniqueId);
         return config;
     }
+
+    @Test
+    public void testResetNotification() {
+        mWifiNetworkSuggestionsManager.resetNotification();
+        verify(mWifiNotificationManager).cancel(SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE);
+    }
+
+    /**
+     * Verify we return the merged network suggestion matches the target FQDN when merged network is
+     * allowed .
+     */
+    @Test
+    public void testGetMergedPasspointSuggestionFromFqdn() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        when(mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(TEST_UID_1))
+                .thenReturn(true);
+        when(mWifiCarrierInfoManager.areMergedCarrierWifiNetworksAllowed(TEST_SUBID))
+                .thenReturn(true);
+        PasspointConfiguration passpointConfiguration =
+                createTestConfigWithUserCredential(TEST_FQDN, TEST_FRIENDLY_NAME);
+        WifiConfiguration placeholderConfig = createPlaceholderConfigForPasspoint(TEST_FQDN,
+                passpointConfiguration.getUniqueId());
+        placeholderConfig.carrierMerged = true;
+        placeholderConfig.meteredOverride = WifiConfiguration.METERED_OVERRIDE_METERED;
+        placeholderConfig.FQDN = TEST_FQDN;
+        placeholderConfig.subscriptionId = TEST_SUBID;
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(placeholderConfig,
+                passpointConfiguration, true, false, true, true, DEFAULT_PRIORITY_GROUP);
+
+        when(mPasspointManager.addOrUpdateProvider(any(PasspointConfiguration.class),
+                anyInt(), anyString(), eq(true), eq(true))).thenReturn(true);
+        assertEquals(mWifiNetworkSuggestionsManager.add(List.of(networkSuggestion), TEST_UID_1,
+                TEST_PACKAGE_1, TEST_FEATURE), WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS);
+
+        Set<ExtendedWifiNetworkSuggestion> ewns =
+                mWifiNetworkSuggestionsManager.getNetworkSuggestionsForFqdn(TEST_FQDN);
+        assertEquals(1, ewns.size());
+        assertEquals(networkSuggestion, ewns.iterator().next().wns);
+
+        // Change to disallow merged network, no matching suggestion should return.
+        when(mWifiCarrierInfoManager.areMergedCarrierWifiNetworksAllowed(TEST_SUBID))
+                .thenReturn(false);
+        ewns = mWifiNetworkSuggestionsManager.getNetworkSuggestionsForFqdn(TEST_FQDN);
+        assertEquals(0, ewns.size());
+    }
+
+    /**
+     * Verify we return the merged network suggestion matches the target ScanDetail when merged
+     * network is allowed .
+     */
+    @Test
+    public void testGetMergedNetworkSuggestionsForScanDetail() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        when(mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(TEST_UID_1))
+                .thenReturn(true);
+        when(mWifiCarrierInfoManager.areMergedCarrierWifiNetworksAllowed(TEST_SUBID))
+                .thenReturn(true);
+        WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork();
+        ScanDetail scanDetail = createScanDetailForNetwork(config);
+        WifiNetworkSuggestion networkSuggestion = createWifiNetworkSuggestion(
+                config, null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+        config.subscriptionId = TEST_SUBID;
+        config.carrierMerged = true;
+        config.meteredOverride = WifiConfiguration.METERED_OVERRIDE_METERED;
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
+                mWifiNetworkSuggestionsManager.add(List.of(networkSuggestion), TEST_UID_1,
+                        TEST_PACKAGE_1, TEST_FEATURE));
+
+        Set<ExtendedWifiNetworkSuggestion> ewns = mWifiNetworkSuggestionsManager
+                .getNetworkSuggestionsForScanDetail(scanDetail);
+        assertEquals(1, ewns.size());
+
+        // Change to disallow merged network, no matching suggestion should return.
+        when(mWifiCarrierInfoManager.areMergedCarrierWifiNetworksAllowed(TEST_SUBID))
+                .thenReturn(false);
+        ewns = mWifiNetworkSuggestionsManager
+                .getNetworkSuggestionsForScanDetail(scanDetail);
+        assertEquals(0, ewns.size());
+    }
+
+    @Test
+    public void testIncompleteEnterpriseNetworkSuggestion() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.SSID = "\"someNetwork\"";
+        config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
+        // EAP method is kept as Eap.NONE - should not crash, but return invalid
+        WifiNetworkSuggestion networkSuggestion1 = createWifiNetworkSuggestion(
+                config, null, false, false, true, true,
+                DEFAULT_PRIORITY_GROUP);
+
+        List<WifiNetworkSuggestion> networkSuggestionList =
+                new ArrayList<WifiNetworkSuggestion>() {{
+                    add(networkSuggestion1);
+                }};
+        when(mWifiKeyStore.updateNetworkKeys(eq(networkSuggestion1.wifiConfiguration), any()))
+                .thenReturn(true);
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_INVALID,
+                mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
+                        TEST_PACKAGE_1, TEST_FEATURE));
+    }
+
+    private static WifiNetworkSuggestion createWifiNetworkSuggestion(WifiConfiguration config,
+            PasspointConfiguration passpointConfiguration,
+            boolean isAppInteractionRequired,
+            boolean isUserInteractionRequired,
+            boolean isUserAllowedToManuallyConnect,
+            boolean isInitialAutoJoinEnabled, int priorityGroup) {
+        if (!SdkLevel.isAtLeastS()) {
+            config.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_PERSISTENT;
+        }
+        return new WifiNetworkSuggestion(config, passpointConfiguration, isAppInteractionRequired,
+                isUserInteractionRequired, isUserAllowedToManuallyConnect, isInitialAutoJoinEnabled,
+                priorityGroup);
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiNotificationManagerTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiNotificationManagerTest.java
new file mode 100644
index 0000000..94dacb1
--- /dev/null
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiNotificationManagerTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link WifiNotificationManager}.
+ */
+@SmallTest
+public class WifiNotificationManagerTest extends WifiBaseTest {
+    private WifiNotificationManager mWifiNotificationManager;
+
+    private static final String NOTIFICATION_TAG = "com.android.wifi";
+    private static final int TEST_MESSAGE_ID = 10;
+
+    @Mock private Context mContext;
+    @Mock private NotificationManager mNotificationManager;
+    @Mock private Context mContextForOwner;
+    @Mock private Context mContextForAnotherUser;
+    @Mock private NotificationManager mNotificationManagerForAnotherUser;
+    @Mock private Notification mNotification;
+    @Mock private Resources mResources;
+    @Mock private StatusBarNotification mStatusBarNotification;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getPackageName()).thenReturn("WifiAPK");
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mContext.createPackageContextAsUser(anyString(), anyInt(), any(UserHandle.class)))
+                .thenReturn(mContext);
+        when(mContext.getSystemService(NotificationManager.class))
+                .thenReturn(mNotificationManager);
+        when(mContextForAnotherUser.getSystemService(NotificationManager.class))
+                .thenReturn(mNotificationManagerForAnotherUser);
+        mWifiNotificationManager = new WifiNotificationManager(mContext);
+    }
+
+    @Test
+    public void testNotify() {
+        mWifiNotificationManager.notify(TEST_MESSAGE_ID, mNotification);
+        verify(mNotificationManager, never()).notify(anyString(), anyInt(), any());
+
+        mWifiNotificationManager.createNotificationChannels();
+        verify(mNotificationManager).createNotificationChannels(any());
+        mWifiNotificationManager.notify(TEST_MESSAGE_ID, mNotification);
+        verify(mNotificationManager).notify(eq(NOTIFICATION_TAG), eq(TEST_MESSAGE_ID), any());
+    }
+
+    @Test
+    public void testCancel() {
+        mWifiNotificationManager.cancel(TEST_MESSAGE_ID);
+        verify(mNotificationManager, never()).cancel(anyString(), anyInt());
+
+        mWifiNotificationManager.createNotificationChannels();
+        verify(mNotificationManager).createNotificationChannels(any());
+        mWifiNotificationManager.cancel(TEST_MESSAGE_ID);
+        verify(mNotificationManager).cancel(eq(NOTIFICATION_TAG), eq(TEST_MESSAGE_ID));
+    }
+
+    @Test
+    public void testUserSwitchNotificationSendCorrect() throws Exception {
+        mWifiNotificationManager.createNotificationChannels();
+        verify(mNotificationManager).createNotificationChannels(any());
+        mWifiNotificationManager.notify(TEST_MESSAGE_ID, mNotification);
+        verify(mNotificationManager).notify(eq(NOTIFICATION_TAG), eq(TEST_MESSAGE_ID), any());
+        verify(mNotificationManagerForAnotherUser, never()).notify(anyString(), anyInt(), any());
+        clearInvocations(mNotificationManager);
+        when(mNotificationManager.getActiveNotifications())
+                .thenReturn(new StatusBarNotification[]{mStatusBarNotification});
+        when(mStatusBarNotification.getTag()).thenReturn(NOTIFICATION_TAG);
+        when(mStatusBarNotification.getId()).thenReturn(TEST_MESSAGE_ID);
+        // Test user switch
+        when(mContext.createPackageContextAsUser(anyString(), anyInt(), any()))
+                .thenReturn(mContextForAnotherUser);
+        mWifiNotificationManager.createNotificationChannels();
+        verify(mNotificationManager).cancel(eq(NOTIFICATION_TAG), eq(TEST_MESSAGE_ID));
+        verify(mNotificationManagerForAnotherUser).createNotificationChannels(any());
+        mWifiNotificationManager.notify(TEST_MESSAGE_ID, mNotification);
+        verify(mNotificationManagerForAnotherUser).notify(eq(NOTIFICATION_TAG), eq(TEST_MESSAGE_ID),
+                any());
+        verify(mNotificationManager, never()).notify(anyString(), anyInt(), any());
+    }
+}
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiScanAlwaysAvailableSettingsCompatibilityTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiScanAlwaysAvailableSettingsCompatibilityTest.java
index 2caa813..fd9e7f0 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiScanAlwaysAvailableSettingsCompatibilityTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiScanAlwaysAvailableSettingsCompatibilityTest.java
@@ -92,7 +92,7 @@
         ContentObserver contentObserver = mContentObserverArgumentCaptor.getValue();
         assertNotNull(contentObserver);
 
-        when(mWifiSettingsStore.isScanAlwaysAvailable()).thenReturn(false);
+        when(mWifiSettingsStore.isScanAlwaysAvailableToggleEnabled()).thenReturn(false);
         when(mFrameworkFacade.getIntegerSetting(
                 any(ContentResolver.class),
                 eq(SETTINGS_GLOBAL_WIFI_SCAN_ALWAYS_AVAILABLE),
@@ -103,7 +103,7 @@
         verify(mWifiSettingsStore).handleWifiScanAlwaysAvailableToggled(true);
         verify(mActiveModeWarden).scanAlwaysModeChanged();
 
-        when(mWifiSettingsStore.isScanAlwaysAvailable()).thenReturn(true);
+        when(mWifiSettingsStore.isScanAlwaysAvailableToggleEnabled()).thenReturn(true);
         when(mFrameworkFacade.getIntegerSetting(
                 any(ContentResolver.class),
                 eq(SETTINGS_GLOBAL_WIFI_SCAN_ALWAYS_AVAILABLE),
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiScoreCardProtoTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiScoreCardProtoTest.java
index 15a6f59..a52b61e 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiScoreCardProtoTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiScoreCardProtoTest.java
@@ -69,7 +69,7 @@
                 WifiScoreCardProto.SecurityType.EAP.getNumber());
         assertEquals(WifiConfiguration.SECURITY_TYPE_SAE,
                 WifiScoreCardProto.SecurityType.SAE.getNumber());
-        assertEquals(WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B,
+        assertEquals(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT,
                 WifiScoreCardProto.SecurityType.EAP_SUITE_B.getNumber());
         assertEquals(WifiConfiguration.SECURITY_TYPE_OWE,
                 WifiScoreCardProto.SecurityType.OWE.getNumber());
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiScoreCardTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiScoreCardTest.java
index e5739a1..d48d72a 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiScoreCardTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiScoreCardTest.java
@@ -23,6 +23,7 @@
 import static com.android.server.wifi.WifiHealthMonitor.REASON_CONNECTION_FAILURE;
 import static com.android.server.wifi.WifiHealthMonitor.REASON_DISCONNECTION_NONLOCAL;
 import static com.android.server.wifi.WifiHealthMonitor.REASON_SHORT_CONNECTION_NONLOCAL;
+import static com.android.server.wifi.WifiScoreCard.BANDWIDTH_STATS_COUNT_THR;
 import static com.android.server.wifi.WifiScoreCard.CNT_ASSOCIATION_REJECTION;
 import static com.android.server.wifi.WifiScoreCard.CNT_ASSOCIATION_TIMEOUT;
 import static com.android.server.wifi.WifiScoreCard.CNT_AUTHENTICATION_FAILURE;
@@ -31,13 +32,18 @@
 import static com.android.server.wifi.WifiScoreCard.CNT_CONNECTION_FAILURE;
 import static com.android.server.wifi.WifiScoreCard.CNT_CONSECUTIVE_CONNECTION_FAILURE;
 import static com.android.server.wifi.WifiScoreCard.CNT_DISCONNECTION_NONLOCAL;
+import static com.android.server.wifi.WifiScoreCard.CNT_DISCONNECTION_NONLOCAL_CONNECTING;
 import static com.android.server.wifi.WifiScoreCard.CNT_SHORT_CONNECTION_NONLOCAL;
+import static com.android.server.wifi.WifiScoreCard.LINK_BANDWIDTH_INIT_KBPS;
+import static com.android.server.wifi.WifiScoreCard.LINK_RX;
+import static com.android.server.wifi.WifiScoreCard.LINK_TX;
 import static com.android.server.wifi.util.NativeUtil.hexStringFromByteArray;
 
 import static org.junit.Assert.*;
 import static org.mockito.Mockito.*;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.net.MacAddress;
 import android.net.wifi.SupplicantState;
 import android.net.wifi.WifiInfo;
@@ -51,13 +57,17 @@
 import com.android.server.wifi.WifiScoreCard.NetworkConnectionStats;
 import com.android.server.wifi.WifiScoreCard.PerNetwork;
 import com.android.server.wifi.proto.WifiScoreCardProto.AccessPoint;
+import com.android.server.wifi.proto.WifiScoreCardProto.BandwidthStatsAll;
 import com.android.server.wifi.proto.WifiScoreCardProto.ConnectionStats;
 import com.android.server.wifi.proto.WifiScoreCardProto.Event;
 import com.android.server.wifi.proto.WifiScoreCardProto.Network;
 import com.android.server.wifi.proto.WifiScoreCardProto.NetworkList;
 import com.android.server.wifi.proto.WifiScoreCardProto.NetworkStats;
 import com.android.server.wifi.proto.WifiScoreCardProto.Signal;
+import com.android.server.wifi.proto.nano.WifiMetricsProto.BandwidthEstimatorStats;
 import com.android.server.wifi.util.IntHistogram;
+import com.android.server.wifi.util.RssiUtil;
+import com.android.wifi.resources.R;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -86,13 +96,23 @@
     static final double TOL = 1e-6; // for assertEquals(double, double, tolerance)
 
     static final int TEST_BSSID_FAILURE_REASON =
-            BssidBlocklistMonitor.REASON_ASSOCIATION_REJECTION;
+            WifiBlocklistMonitor.REASON_ASSOCIATION_REJECTION;
+
+    private static final String WIFI_IFACE_NAME = "wlanTest";
 
     WifiScoreCard mWifiScoreCard;
 
     @Mock Clock mClock;
     @Mock WifiScoreCard.MemoryStore mMemoryStore;
     @Mock DeviceConfigFacade mDeviceConfigFacade;
+    @Mock FrameworkFacade mFrameworkFacade;
+    @Mock Context mContext;
+    @Mock Resources mResources;
+
+    private WifiLinkLayerStats mOldLlStats;
+    private WifiLinkLayerStats mNewLlStats;
+    private long mTotalTxBytes;
+    private long mTotalRxBytes;
 
     final ArrayList<String> mKeys = new ArrayList<>();
     final ArrayList<WifiScoreCard.BlobListener> mBlobListeners = new ArrayList<>();
@@ -120,17 +140,24 @@
         mBlobListeners.clear();
         mBlobs.clear();
         mMilliSecondsSinceBoot = 0;
-        mWifiInfo = new ExtendedWifiInfo(mock(Context.class));
+        mWifiInfo = new ExtendedWifiInfo(mock(WifiGlobals.class), WIFI_IFACE_NAME);
         mWifiInfo.setSSID(TEST_SSID_1);
         mWifiInfo.setBSSID(TEST_BSSID_1.toString());
         mWifiInfo.setNetworkId(TEST_NETWORK_CONFIG_ID);
+        mWifiInfo.setMaxSupportedTxLinkSpeedMbps(866);
+        mWifiInfo.setMaxSupportedRxLinkSpeedMbps(866);
         millisecondsPass(0);
-        mWifiScoreCard = new WifiScoreCard(mClock, "some seed", mDeviceConfigFacade);
+        mWifiScoreCard = new WifiScoreCard(mClock, "some seed", mDeviceConfigFacade,
+                mFrameworkFacade, mContext);
         mWifiScoreCard.mPersistentHistograms = true; // TODO - remove when ready
         when(mDeviceConfigFacade.getConnectionFailureHighThrPercent()).thenReturn(
                 DeviceConfigFacade.DEFAULT_CONNECTION_FAILURE_HIGH_THR_PERCENT);
         when(mDeviceConfigFacade.getConnectionFailureCountMin()).thenReturn(
                 DeviceConfigFacade.DEFAULT_CONNECTION_FAILURE_COUNT_MIN);
+        when(mDeviceConfigFacade.getConnectionFailureDisconnectionHighThrPercent()).thenReturn(
+                DeviceConfigFacade.DEFAULT_CONNECTION_FAILURE_DISCONNECTION_HIGH_THR_PERCENT);
+        when(mDeviceConfigFacade.getConnectionFailureDisconnectionCountMin()).thenReturn(
+                DeviceConfigFacade.DEFAULT_CONNECTION_FAILURE_DISCONNECTION_COUNT_MIN);
         when(mDeviceConfigFacade.getAssocRejectionHighThrPercent()).thenReturn(
                 DeviceConfigFacade.DEFAULT_ASSOC_REJECTION_HIGH_THR_PERCENT);
         when(mDeviceConfigFacade.getAssocRejectionCountMin()).thenReturn(
@@ -165,7 +192,22 @@
         // Disable FW alert time check by default
         when(mDeviceConfigFacade.getHealthMonitorFwAlertValidTimeMs()).thenReturn(-1);
         when(mDeviceConfigFacade.getBugReportThresholdExtraRatio()).thenReturn(1);
+        when(mDeviceConfigFacade.getBandwidthEstimatorLargeTimeConstantSec()).thenReturn(6);
+        when(mDeviceConfigFacade.getTrafficStatsThresholdMaxKbyte()).thenReturn(4000);
         mWifiScoreCard.enableVerboseLogging(true);
+        when(mFrameworkFacade.getMobileRxBytes()).thenReturn(0L);
+        when(mFrameworkFacade.getMobileTxBytes()).thenReturn(0L);
+        when(mFrameworkFacade.getTotalRxBytes()).thenReturn(0L);
+        when(mFrameworkFacade.getTotalTxBytes()).thenReturn(0L);
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getIntArray(R.array.config_wifiRssiLevelThresholds))
+                .thenReturn(new int[]{-88, -77, -66, -55});
+        when(mResources.getInteger(R.integer.config_wifiPollRssiIntervalMilliseconds))
+                .thenReturn(3000);
+        mOldLlStats = new WifiLinkLayerStats();
+        mNewLlStats = new WifiLinkLayerStats();
+        mTotalTxBytes = 0;
+        mTotalRxBytes = 0;
     }
 
     /**
@@ -498,7 +540,8 @@
                 }
             }
         }
-        mWifiScoreCard.resetConnectionState();
+        makeUpdateLinkBandwidthExample();
+        mWifiScoreCard.resetAllConnectionStates();
 
         WifiScoreCard.PerBssid perBssid = mWifiScoreCard.fetchByBssid(TEST_BSSID_1);
         perBssid.lookupSignal(Event.SIGNAL_POLL, 2412).rssi.historicalMean = -42.0;
@@ -604,6 +647,7 @@
                     fail(signal.getEvent().toString());
             }
         }
+        checkSerializationUpdateLinkBandwidthExample(ap.getBandwidthStatsAll());
     }
 
     /**
@@ -841,7 +885,21 @@
         mWifiScoreCard.noteConnectionAttempt(mWifiInfo, -53, mWifiInfo.getSSID());
         millisecondsPass(1000);
         mWifiScoreCard.noteConnectionFailure(mWifiInfo, -53, mWifiInfo.getSSID(),
-                BssidBlocklistMonitor.REASON_ASSOCIATION_TIMEOUT);
+                WifiBlocklistMonitor.REASON_ASSOCIATION_TIMEOUT);
+    }
+
+    private void makeAssocRejectionExample() {
+        mWifiScoreCard.noteConnectionAttempt(mWifiInfo, -53, mWifiInfo.getSSID());
+        millisecondsPass(1000);
+        mWifiScoreCard.noteConnectionFailure(mWifiInfo, -53, mWifiInfo.getSSID(),
+                WifiBlocklistMonitor.REASON_ASSOCIATION_REJECTION);
+    }
+
+    private void makeApUnableToHandleNewStaExample() {
+        mWifiScoreCard.noteConnectionAttempt(mWifiInfo, -53, mWifiInfo.getSSID());
+        millisecondsPass(1000);
+        mWifiScoreCard.noteConnectionFailure(mWifiInfo, -53, mWifiInfo.getSSID(),
+                WifiBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA);
     }
 
     /**
@@ -863,23 +921,67 @@
         assertEquals(1, dailyStats.getCount(CNT_CONSECUTIVE_CONNECTION_FAILURE));
     }
 
+    /**
+     * Check network stats after association rejection.
+     */
+    @Test
+    public void testNetworkAssocRejection() throws Exception {
+        makeAssocRejectionExample();
+        makeApUnableToHandleNewStaExample();
+
+        PerNetwork perNetwork = mWifiScoreCard.fetchByNetwork(mWifiInfo.getSSID());
+        NetworkConnectionStats dailyStats = perNetwork.getRecentStats();
+
+        assertEquals(2, dailyStats.getCount(CNT_CONNECTION_ATTEMPT));
+        assertEquals(2, dailyStats.getCount(CNT_CONNECTION_FAILURE));
+        assertEquals(0, dailyStats.getCount(CNT_CONNECTION_DURATION_SEC));
+        assertEquals(1, dailyStats.getCount(CNT_ASSOCIATION_REJECTION));
+        assertEquals(0, dailyStats.getCount(CNT_ASSOCIATION_TIMEOUT));
+        assertEquals(0, dailyStats.getCount(CNT_AUTHENTICATION_FAILURE));
+        assertEquals(2, dailyStats.getCount(CNT_CONSECUTIVE_CONNECTION_FAILURE));
+    }
+
+
+    /**
+     * Check network stats after auth timeout/disconnection and a normal connection
+     */
+    @Test
+    public void testAuthTimeoutDisconnection() throws Exception {
+        makeAuthFailureExample();
+        mWifiScoreCard.resetAllConnectionStates();
+
+        PerNetwork perNetwork = mWifiScoreCard.fetchByNetwork(mWifiInfo.getSSID());
+        NetworkConnectionStats dailyStats = perNetwork.getRecentStats();
+
+        assertEquals(1, dailyStats.getCount(CNT_CONNECTION_ATTEMPT));
+        assertEquals(1, dailyStats.getCount(CNT_CONNECTION_FAILURE));
+        assertEquals(0, dailyStats.getCount(CNT_CONNECTION_DURATION_SEC));
+        assertEquals(0, dailyStats.getCount(CNT_ASSOCIATION_REJECTION));
+        assertEquals(0, dailyStats.getCount(CNT_ASSOCIATION_TIMEOUT));
+        assertEquals(1, dailyStats.getCount(CNT_AUTHENTICATION_FAILURE));
+        assertEquals(1, dailyStats.getCount(CNT_CONSECUTIVE_CONNECTION_FAILURE));
+
+        makeNormalConnectionExample();
+        assertEquals(0, dailyStats.getCount(CNT_CONSECUTIVE_CONNECTION_FAILURE));
+    }
+
     private void makeAuthFailureAndWrongPassword() {
         mWifiScoreCard.noteConnectionAttempt(mWifiInfo, -53, mWifiInfo.getSSID());
         millisecondsPass(500);
         mWifiScoreCard.noteConnectionFailure(mWifiInfo, -53, mWifiInfo.getSSID(),
-                BssidBlocklistMonitor.REASON_AUTHENTICATION_FAILURE);
+                WifiBlocklistMonitor.REASON_AUTHENTICATION_FAILURE);
         millisecondsPass(1000);
         mWifiScoreCard.noteConnectionAttempt(mWifiInfo, -53, mWifiInfo.getSSID());
         millisecondsPass(1000);
         mWifiScoreCard.noteConnectionFailure(mWifiInfo, -53, mWifiInfo.getSSID(),
-                BssidBlocklistMonitor.REASON_WRONG_PASSWORD);
+                WifiBlocklistMonitor.REASON_WRONG_PASSWORD);
     }
 
     private void makeAuthFailureExample() {
         mWifiScoreCard.noteConnectionAttempt(mWifiInfo, -53, mWifiInfo.getSSID());
         millisecondsPass(500);
         mWifiScoreCard.noteConnectionFailure(mWifiInfo, -53, mWifiInfo.getSSID(),
-                BssidBlocklistMonitor.REASON_AUTHENTICATION_FAILURE);
+                WifiBlocklistMonitor.REASON_AUTHENTICATION_FAILURE);
     }
 
     /**
@@ -903,6 +1005,50 @@
         assertEquals(0, dailyStats.getCount(CNT_CONSECUTIVE_CONNECTION_FAILURE));
     }
 
+    private void makeDisconnectionConnectingExample(boolean nonlocal) {
+        mWifiScoreCard.noteConnectionAttempt(mWifiInfo, -53, mWifiInfo.getSSID());
+        millisecondsPass(500);
+        int disconnectionReason = 3;
+        if (nonlocal) {
+            mWifiScoreCard.noteNonlocalDisconnect(WIFI_IFACE_NAME, disconnectionReason);
+        }
+        mWifiScoreCard.noteConnectionFailure(mWifiInfo, -53, mWifiInfo.getSSID(),
+                WifiBlocklistMonitor.REASON_NONLOCAL_DISCONNECT_CONNECTING);
+        millisecondsPass(500);
+    }
+
+    /**
+     * Check network stats after nonlocal disconnection in middle of connection
+     */
+    @Test
+    public void testDisconnectionConnecting() throws Exception {
+        makeDisconnectionConnectingExample(true);
+        PerNetwork perNetwork = mWifiScoreCard.fetchByNetwork(mWifiInfo.getSSID());
+        NetworkConnectionStats dailyStats = perNetwork.getRecentStats();
+
+        assertEquals(1, dailyStats.getCount(CNT_CONNECTION_ATTEMPT));
+        assertEquals(1, dailyStats.getCount(CNT_CONNECTION_FAILURE));
+        assertEquals(1, dailyStats.getCount(CNT_DISCONNECTION_NONLOCAL_CONNECTING));
+        assertEquals(0, dailyStats.getCount(CNT_CONNECTION_DURATION_SEC));
+        assertEquals(0, dailyStats.getCount(CNT_ASSOCIATION_REJECTION));
+        assertEquals(0, dailyStats.getCount(CNT_ASSOCIATION_TIMEOUT));
+        assertEquals(0, dailyStats.getCount(CNT_AUTHENTICATION_FAILURE));
+    }
+
+    /**
+     * Check network stats after local disconnection in middle of connection
+     */
+    @Test
+    public void testLocalDisconnectionConnecting() throws Exception {
+        makeDisconnectionConnectingExample(false);
+        PerNetwork perNetwork = mWifiScoreCard.fetchByNetwork(mWifiInfo.getSSID());
+        NetworkConnectionStats dailyStats = perNetwork.getRecentStats();
+
+        assertEquals(1, dailyStats.getCount(CNT_CONNECTION_ATTEMPT));
+        assertEquals(1, dailyStats.getCount(CNT_CONNECTION_FAILURE));
+        assertEquals(1, dailyStats.getCount(CNT_DISCONNECTION_NONLOCAL_CONNECTING));
+    }
+
     /**
      * Check network stats when a new connection attempt for SSID2 is issued
      * before disconnection of SSID1
@@ -923,15 +1069,15 @@
         // Disconnect from SSID_1
         millisecondsPass(100);
         int disconnectionReason = 4;
-        mWifiScoreCard.noteNonlocalDisconnect(disconnectionReason);
+        mWifiScoreCard.noteNonlocalDisconnect(WIFI_IFACE_NAME, disconnectionReason);
         millisecondsPass(100);
-        mWifiScoreCard.resetConnectionState();
+        mWifiScoreCard.resetConnectionState(WIFI_IFACE_NAME);
 
         // SSID_2 is connected and then disconnected
         millisecondsPass(2000);
         mWifiScoreCard.noteIpConfiguration(mWifiInfo);
         millisecondsPass(2000);
-        mWifiScoreCard.resetConnectionState();
+        mWifiScoreCard.resetConnectionState(WIFI_IFACE_NAME);
 
         PerNetwork perNetwork = mWifiScoreCard.fetchByNetwork(ssid1);
         assertEquals(5, perNetwork.getRecentStats().getCount(CNT_CONNECTION_DURATION_SEC));
@@ -948,12 +1094,12 @@
         mWifiScoreCard.noteConnectionAttempt(mWifiInfo, -83, mWifiInfo.getSSID());
         millisecondsPass(1000);
         mWifiScoreCard.noteConnectionFailure(mWifiInfo, -83, mWifiInfo.getSSID(),
-                BssidBlocklistMonitor.REASON_ASSOCIATION_REJECTION);
+                WifiBlocklistMonitor.REASON_ASSOCIATION_REJECTION);
         millisecondsPass(3000);
         mWifiScoreCard.noteConnectionAttempt(mWifiInfo, -83, mWifiInfo.getSSID());
         millisecondsPass(1000);
         mWifiScoreCard.noteConnectionFailure(mWifiInfo, -83, mWifiInfo.getSSID(),
-                BssidBlocklistMonitor.REASON_ASSOCIATION_REJECTION);
+                WifiBlocklistMonitor.REASON_ASSOCIATION_REJECTION);
 
         PerNetwork perNetwork = mWifiScoreCard.fetchByNetwork(mWifiInfo.getSSID());
         NetworkConnectionStats dailyStats = perNetwork.getRecentStats();
@@ -971,17 +1117,18 @@
         mWifiScoreCard.noteSignalPoll(mWifiInfo);
         millisecondsPass(2000);
         int disconnectionReason = 34;
-        mWifiScoreCard.noteNonlocalDisconnect(disconnectionReason);
+        mWifiScoreCard.noteNonlocalDisconnect(WIFI_IFACE_NAME, disconnectionReason);
         if (addFwAlert) {
             mWifiScoreCard.noteFirmwareAlert(6);
         }
         millisecondsPass(1000);
-        mWifiScoreCard.resetConnectionState();
+        mWifiScoreCard.resetAllConnectionStates();
     }
 
     private void checkShortConnectionExample(NetworkConnectionStats stats, int scale) {
         assertEquals(1 * scale, stats.getCount(CNT_CONNECTION_ATTEMPT));
         assertEquals(0, stats.getCount(CNT_CONNECTION_FAILURE));
+        assertEquals(0, stats.getCount(CNT_DISCONNECTION_NONLOCAL_CONNECTING));
         assertEquals(9 * scale, stats.getCount(CNT_CONNECTION_DURATION_SEC));
         assertEquals(0, stats.getCount(CNT_ASSOCIATION_REJECTION));
         assertEquals(0, stats.getCount(CNT_ASSOCIATION_TIMEOUT));
@@ -999,14 +1146,15 @@
         mWifiScoreCard.noteSignalPoll(mWifiInfo);
         millisecondsPass(29000);
         int disconnectionReason = 3;
-        mWifiScoreCard.noteNonlocalDisconnect(disconnectionReason);
+        mWifiScoreCard.noteNonlocalDisconnect(WIFI_IFACE_NAME, disconnectionReason);
         millisecondsPass(1000);
-        mWifiScoreCard.resetConnectionState();
+        mWifiScoreCard.resetAllConnectionStates();
     }
 
     private void checkShortConnectionOldPollingExample(NetworkConnectionStats stats) {
         assertEquals(1, stats.getCount(CNT_CONNECTION_ATTEMPT));
         assertEquals(0, stats.getCount(CNT_CONNECTION_FAILURE));
+        assertEquals(0, stats.getCount(CNT_DISCONNECTION_NONLOCAL_CONNECTING));
         assertEquals(33, stats.getCount(CNT_CONNECTION_DURATION_SEC));
         assertEquals(0, stats.getCount(CNT_ASSOCIATION_REJECTION));
         assertEquals(0, stats.getCount(CNT_ASSOCIATION_TIMEOUT));
@@ -1072,15 +1220,56 @@
         millisecondsPass(7000);
         mWifiScoreCard.noteSignalPoll(mWifiInfo);
         millisecondsPass(3000);
-        mWifiScoreCard.resetConnectionState();
+        mWifiScoreCard.noteIpConfiguration(mWifiInfo);
+        mWifiScoreCard.resetAllConnectionStates();
+    }
+
+    private void makeUpdateLinkBandwidthExample() {
+        mWifiInfo.setRssi(-79);
+        mWifiInfo.setFrequency(2437);
+        mNewLlStats.on_time = 1000;
+        mNewLlStats.timeStampInMs = 5_000;
+        long txBytes = 2_000_000L;
+        long rxBytes = 4_000_000L;
+        PerNetwork perNetwork = mWifiScoreCard.lookupNetwork(mWifiInfo.getSSID());
+        for (int i = 0; i < BANDWIDTH_STATS_COUNT_THR; i++) {
+            addTotalBytes(txBytes, rxBytes);
+            perNetwork.updateLinkBandwidth(mOldLlStats, mNewLlStats, mWifiInfo);
+        }
+        mWifiInfo.setFrequency(5210);
+        txBytes = 5_000_000L;
+        rxBytes = 1000L;
+        for (int i = 0; i < BANDWIDTH_STATS_COUNT_THR + 2; i++) {
+            addTotalBytes(txBytes, rxBytes);
+            perNetwork.updateLinkBandwidth(mOldLlStats, mNewLlStats, mWifiInfo);
+        }
+    }
+
+    private void checkSerializationUpdateLinkBandwidthExample(BandwidthStatsAll stats) {
+        assertEquals(2_000_000L * 8 / 1000 * BANDWIDTH_STATS_COUNT_THR,
+                stats.getStats2G().getTx().getLevel(1).getValue());
+        assertEquals(4_000_000L * 8 / 1000 * BANDWIDTH_STATS_COUNT_THR,
+                stats.getStats2G().getRx().getLevel(1).getValue());
+        assertEquals(BANDWIDTH_STATS_COUNT_THR,
+                stats.getStats2G().getTx().getLevel(1).getCount());
+        assertEquals(BANDWIDTH_STATS_COUNT_THR,
+                stats.getStats2G().getRx().getLevel(1).getCount());
+
+        assertEquals(5_000_000L * 8 / 1000 * (BANDWIDTH_STATS_COUNT_THR + 2),
+                stats.getStatsAbove2G().getTx().getLevel(1).getValue());
+        assertEquals(0, stats.getStatsAbove2G().getRx().getLevel(1).getValue());
+        assertEquals(BANDWIDTH_STATS_COUNT_THR + 2,
+                stats.getStatsAbove2G().getTx().getLevel(1).getCount());
+        assertEquals(0, stats.getStatsAbove2G().getRx().getLevel(1).getCount());
     }
 
     /**
      * Constructs a protobuf form of Network example.
      */
     private byte[] makeSerializedNetworkExample() {
-        makeNormalConnectionExample();
-
+        makeDisconnectionConnectingExample(true);
+        makeShortConnectionExample(true);
+        makeUpdateLinkBandwidthExample();
         PerNetwork perNetwork = mWifiScoreCard.fetchByNetwork(mWifiInfo.getSSID());
         checkSerializationNetworkExample("before serialization", perNetwork);
         // Now convert to protobuf form
@@ -1093,11 +1282,13 @@
      */
     private void checkSerializationNetworkExample(String diag, PerNetwork perNetwork) {
         NetworkConnectionStats dailyStats = perNetwork.getRecentStats();
-        assertEquals(diag, 1, dailyStats.getCount(CNT_CONNECTION_ATTEMPT));
-        assertEquals(diag, 0, dailyStats.getCount(CNT_CONNECTION_FAILURE));
-        assertEquals(diag, 11, dailyStats.getCount(CNT_CONNECTION_DURATION_SEC));
-        assertEquals(diag, 0, dailyStats.getCount(CNT_SHORT_CONNECTION_NONLOCAL));
-        assertEquals(diag, 0, dailyStats.getCount(CNT_DISCONNECTION_NONLOCAL));
+
+        assertEquals(diag, 2, dailyStats.getCount(CNT_CONNECTION_ATTEMPT));
+        assertEquals(diag, 1, dailyStats.getCount(CNT_CONNECTION_FAILURE));
+        assertEquals(diag, 1, dailyStats.getCount(CNT_DISCONNECTION_NONLOCAL_CONNECTING));
+        assertEquals(diag, 9, dailyStats.getCount(CNT_CONNECTION_DURATION_SEC));
+        assertEquals(diag, 1, dailyStats.getCount(CNT_SHORT_CONNECTION_NONLOCAL));
+        assertEquals(diag, 1, dailyStats.getCount(CNT_DISCONNECTION_NONLOCAL));
         assertEquals(diag, 0, dailyStats.getCount(CNT_ASSOCIATION_REJECTION));
         assertEquals(diag, 0, dailyStats.getCount(CNT_ASSOCIATION_TIMEOUT));
         assertEquals(diag, 0, dailyStats.getCount(CNT_AUTHENTICATION_FAILURE));
@@ -1113,14 +1304,17 @@
         // Verify by parsing it and checking that we see the expected results
         NetworkStats ns = NetworkStats.parseFrom(serialized);
         ConnectionStats dailyStats = ns.getRecentStats();
-        assertEquals(1, dailyStats.getNumConnectionAttempt());
-        assertEquals(0, dailyStats.getNumConnectionFailure());
-        assertEquals(11, dailyStats.getConnectionDurationSec());
-        assertEquals(0, dailyStats.getNumDisconnectionNonlocal());
-        assertEquals(0, dailyStats.getNumShortConnectionNonlocal());
+        assertEquals(2, dailyStats.getNumConnectionAttempt());
+        assertEquals(1, dailyStats.getNumConnectionFailure());
+        assertEquals(1, dailyStats.getNumDisconnectionNonlocalConnecting());
+        assertEquals(9, dailyStats.getConnectionDurationSec());
+        assertEquals(1, dailyStats.getNumDisconnectionNonlocal());
+        assertEquals(1, dailyStats.getNumShortConnectionNonlocal());
         assertEquals(0, dailyStats.getNumAssociationRejection());
         assertEquals(0, dailyStats.getNumAssociationTimeout());
         assertEquals(0, dailyStats.getNumAuthenticationFailure());
+
+        checkSerializationUpdateLinkBandwidthExample(ns.getBandwidthStatsAll());
     }
 
     /**
@@ -1147,7 +1341,7 @@
         mWifiScoreCard.noteConnectionAttempt(mWifiInfo, -53, mWifiInfo.getSSID());
         millisecondsPass(1000);
         mWifiScoreCard.noteConnectionFailure(mWifiInfo, -53, mWifiInfo.getSSID(),
-                BssidBlocklistMonitor.REASON_ASSOCIATION_REJECTION);
+                WifiBlocklistMonitor.REASON_ASSOCIATION_REJECTION);
         mWifiScoreCard.removeNetwork(mWifiInfo.getSSID());
 
         PerNetwork perNetwork = mWifiScoreCard.fetchByNetwork(mWifiInfo.getSSID());
@@ -1269,7 +1463,7 @@
         }
 
         assertEquals(WifiHealthMonitor.REASON_SHORT_CONNECTION_NONLOCAL,
-                mWifiScoreCard.detectAbnormalDisconnection());
+                mWifiScoreCard.detectAbnormalDisconnection(WIFI_IFACE_NAME));
         FailureStats statsDec = new FailureStats();
         FailureStats statsInc = new FailureStats();
         FailureStats statsHigh = new FailureStats();
@@ -1391,4 +1585,328 @@
         assertEquals(1, perNetwork.getFrequencies(900L).size());
         assertEquals(2432, (int) perNetwork.getFrequencies(Long.MAX_VALUE).get(0));
     }
+
+    private void addTotalBytes(long txBytes, long rxBytes) {
+        mTotalTxBytes += txBytes;
+        mTotalRxBytes += rxBytes;
+        when(mFrameworkFacade.getTotalTxBytes()).thenReturn(mTotalTxBytes);
+        when(mFrameworkFacade.getTotalRxBytes()).thenReturn(mTotalRxBytes);
+    }
+
+    private void subtractTotalBytes(long txBytes, long rxBytes) {
+        mTotalTxBytes -= txBytes;
+        mTotalRxBytes -= rxBytes;
+        when(mFrameworkFacade.getTotalTxBytes()).thenReturn(mTotalTxBytes);
+        when(mFrameworkFacade.getTotalRxBytes()).thenReturn(mTotalRxBytes);
+    }
+
+    @Test
+    public void testLinkBandwidthTwoRadioStatsVariousTxTraffic() {
+        mWifiInfo.setRssi(-70);
+        mWifiInfo.setFrequency(2437);
+        mWifiScoreCard.noteConnectionAttempt(mWifiInfo, -53, mWifiInfo.getSSID());
+        PerNetwork perNetwork = mWifiScoreCard.lookupNetwork(mWifiInfo.getSSID());
+        mNewLlStats.on_time = 3000;
+        mOldLlStats.radioStats = new WifiLinkLayerStats.RadioStat[2];
+        mOldLlStats.radioStats[0] = new WifiLinkLayerStats.RadioStat();
+        mOldLlStats.radioStats[1] = new WifiLinkLayerStats.RadioStat();
+        mNewLlStats.radioStats = new WifiLinkLayerStats.RadioStat[2];
+        mNewLlStats.radioStats[0] = new WifiLinkLayerStats.RadioStat();
+        mNewLlStats.radioStats[1] = new WifiLinkLayerStats.RadioStat();
+        mNewLlStats.radioStats[0].on_time = 500;
+        mNewLlStats.radioStats[1].on_time = 500;
+        mOldLlStats.timeStampInMs = 7_000;
+        mNewLlStats.timeStampInMs = 10_000;
+        long txBytes = 350_000L;
+        long rxBytes = 4_000_000L;
+        for (int i = 0; i < BANDWIDTH_STATS_COUNT_THR - 1; i++) {
+            addTotalBytes(txBytes, rxBytes);
+            millisecondsPass(3_000);
+            perNetwork.updateLinkBandwidth(mOldLlStats, mNewLlStats, mWifiInfo);
+        }
+
+        assertEquals(10_000, perNetwork.getTxLinkBandwidthKbps());
+        assertEquals(32_000, perNetwork.getRxLinkBandwidthKbps());
+
+        txBytes = 400_000L;
+        rxBytes = 200_000L;
+        for (int i = 0; i < BANDWIDTH_STATS_COUNT_THR - 1; i++) {
+            addTotalBytes(txBytes, rxBytes);
+            millisecondsPass(3_000);
+            perNetwork.updateLinkBandwidth(mOldLlStats, mNewLlStats, mWifiInfo);
+        }
+
+        assertEquals(3_200, perNetwork.getTxLinkBandwidthKbps());
+    }
+
+    @Test
+    public void testLinkBandwidthTwoBssidThreeSignalLevelOneBand() {
+        mWifiInfo.setRssi(-70);
+        mWifiInfo.setFrequency(2437);
+        mWifiScoreCard.noteConnectionAttempt(mWifiInfo, -53, mWifiInfo.getSSID());
+        PerNetwork perNetwork = mWifiScoreCard.lookupNetwork(mWifiInfo.getSSID());
+        mWifiScoreCard.noteIpConfiguration(mWifiInfo);
+        mNewLlStats.on_time = 1000;
+        mOldLlStats.timeStampInMs = 7_000;
+        mNewLlStats.timeStampInMs = 10_000;
+        long txBytes = 2_000_000L;
+        long rxBytes = 4_000_000L;
+        // Add BANDWIDTH_STATS_COUNT_THR - 2 polls at BSSID 1 at 1st level
+        for (int i = 0; i < BANDWIDTH_STATS_COUNT_THR - 2; i++) {
+            addTotalBytes(txBytes, rxBytes);
+            millisecondsPass(3_000);
+            perNetwork.updateLinkBandwidth(mOldLlStats, mNewLlStats, mWifiInfo);
+        }
+        // Add BANDWIDTH_STATS_COUNT_THR - 2 polls at BSSID 2 at 2nd level
+        mWifiInfo.setBSSID(TEST_BSSID_2.toString());
+        mNewLlStats.on_time = 2000;
+        mWifiInfo.setRssi(-54);
+        txBytes = 6_000_000L;
+        rxBytes = 100_000L;
+        for (int i = 0; i < BANDWIDTH_STATS_COUNT_THR - 2; i++) {
+            addTotalBytes(txBytes, rxBytes);
+            millisecondsPass(3_000);
+            perNetwork.updateLinkBandwidth(mOldLlStats, mNewLlStats, mWifiInfo);
+        }
+
+        // Add BANDWIDTH_STATS_COUNT_THR - 2 polls at BSSID 2 at 3rd level
+        rxBytes = 4_000_000L;
+        mWifiInfo.setRssi(-65);
+        for (int i = 0; i < BANDWIDTH_STATS_COUNT_THR - 2; i++) {
+            addTotalBytes(txBytes, rxBytes);
+            millisecondsPass(3_000);
+            perNetwork.updateLinkBandwidth(mOldLlStats, mNewLlStats, mWifiInfo);
+        }
+
+        assertEquals(23_619, perNetwork.getTxLinkBandwidthKbps());
+        assertEquals(16_677, perNetwork.getRxLinkBandwidthKbps());
+    }
+
+    @Test
+    public void testLinkBandwidthInvalidTrafficStats() {
+        mWifiInfo.setRssi(-70);
+        mWifiScoreCard.noteConnectionAttempt(mWifiInfo, -53, mWifiInfo.getSSID());
+        PerNetwork perNetwork = mWifiScoreCard.lookupNetwork(mWifiInfo.getSSID());
+        mWifiInfo.setFrequency(5210);
+        mWifiScoreCard.noteIpConfiguration(mWifiInfo);
+        mNewLlStats.on_time = 1000;
+        mOldLlStats.timeStampInMs = 7_000;
+        mNewLlStats.timeStampInMs = 10_000;
+        long txBytes = 2_000_000L;
+        long rxBytes = 100_000L;
+        int[] reportedKbps = new int[]{400_000, 300_000};
+        int[] l2Kbps = new int[]{800_000, 700_000};
+        // Add BANDWIDTH_STATS_COUNT_THR polls with one of them has invalid traffic stats
+        for (int i = 0; i < BANDWIDTH_STATS_COUNT_THR; i++) {
+            if (i == 1) {
+                subtractTotalBytes(txBytes, rxBytes);
+            } else {
+                addTotalBytes(txBytes, rxBytes);
+            }
+            millisecondsPass(3_000);
+            perNetwork.updateLinkBandwidth(mOldLlStats, mNewLlStats, mWifiInfo);
+            perNetwork.updateBwMetrics(reportedKbps, l2Kbps);
+        }
+
+        assertEquals(16_000, perNetwork.getTxLinkBandwidthKbps());
+        assertEquals(LINK_BANDWIDTH_INIT_KBPS[1][LINK_RX][2], perNetwork.getRxLinkBandwidthKbps());
+        BandwidthEstimatorStats stats = mWifiScoreCard.dumpBandwidthEstimatorStats();
+        assertEquals(0, stats.stats2G.tx.level.length);
+        assertEquals(0, stats.stats2G.rx.level.length);
+    }
+
+    @Test
+    public void testLinkBandwidthOneBssidTwoSignalLevelTwoBand() {
+        mWifiInfo.setRssi(-70);
+        mWifiScoreCard.noteConnectionAttempt(mWifiInfo, -53, mWifiInfo.getSSID());
+        PerNetwork perNetwork = mWifiScoreCard.lookupNetwork(mWifiInfo.getSSID());
+        mWifiInfo.setFrequency(5210);
+        mWifiScoreCard.noteIpConfiguration(mWifiInfo);
+        mNewLlStats.on_time = 1000;
+        mOldLlStats.timeStampInMs = 7_000;
+        mNewLlStats.timeStampInMs = 10_000;
+        long txBytes = 2_000_000L;
+        long rxBytes = 100_000L;
+        int [] reportedKbps = new int[]{40_000, 30_000};
+        int [] l2Kbps = new int[]{80_000, 70_000};
+        // Add BANDWIDTH_STATS_COUNT_THR polls at 1st level and 1st band
+        for (int i = 0; i < BANDWIDTH_STATS_COUNT_THR; i++) {
+            addTotalBytes(txBytes, rxBytes);
+            millisecondsPass(3_000);
+            perNetwork.updateLinkBandwidth(mOldLlStats, mNewLlStats, mWifiInfo);
+            perNetwork.updateBwMetrics(reportedKbps, l2Kbps);
+        }
+        // Add BANDWIDTH_STATS_COUNT_THR polls at 2nd level and 1st band
+        mWifiInfo.setRssi(-65);
+        rxBytes = 7_000_000L;
+        for (int i = 0; i < BANDWIDTH_STATS_COUNT_THR; i++) {
+            addTotalBytes(txBytes, rxBytes);
+            millisecondsPass(3_000);
+            perNetwork.updateLinkBandwidth(mOldLlStats, mNewLlStats, mWifiInfo);
+            perNetwork.updateBwMetrics(reportedKbps, l2Kbps);
+        }
+
+        // Add BANDWIDTH_STATS_COUNT_THR * 2 polls at 1st level and 2nd band
+        mWifiInfo.setRssi(-70);
+        mWifiInfo.setFrequency(2437);
+        txBytes = 6_000_000L;
+        mNewLlStats.on_time = 2000;
+        for (int i = 0; i < (2 * BANDWIDTH_STATS_COUNT_THR); i++) {
+            addTotalBytes(txBytes, rxBytes);
+            millisecondsPass(3_000);
+            perNetwork.updateLinkBandwidth(mOldLlStats, mNewLlStats, mWifiInfo);
+            perNetwork.updateBwMetrics(reportedKbps, l2Kbps);
+        }
+
+        // Expect stats of 1st level and 2nd band are used
+        assertEquals(23_949, perNetwork.getTxLinkBandwidthKbps());
+        assertEquals(28_173, perNetwork.getRxLinkBandwidthKbps());
+
+        BandwidthEstimatorStats stats = mWifiScoreCard.dumpBandwidthEstimatorStats();
+        assertEquals(1, stats.stats2G.tx.level.length);
+        assertEquals(1, stats.stats2G.rx.level.length);
+
+        assertEquals(2, stats.stats2G.rx.level[0].signalLevel);
+        assertEquals(BANDWIDTH_STATS_COUNT_THR - 1, stats.stats2G.rx.level[0].count);
+        assertEquals(28_000, stats.stats2G.rx.level[0].avgBandwidthKbps);
+        assertEquals(150, stats.stats2G.rx.level[0].l2ErrorPercent);
+        assertEquals(7, stats.stats2G.rx.level[0].bandwidthEstErrorPercent);
+
+        assertEquals(2, stats.stats2G.tx.level[0].signalLevel);
+        assertEquals(BANDWIDTH_STATS_COUNT_THR - 1, stats.stats2G.tx.level[0].count);
+        assertEquals(24_000, stats.stats2G.tx.level[0].avgBandwidthKbps);
+        assertEquals(233, stats.stats2G.tx.level[0].l2ErrorPercent);
+        assertEquals(66, stats.stats2G.tx.level[0].bandwidthEstErrorPercent);
+
+        assertEquals(0, stats.statsAbove2G.tx.level.length);
+        assertEquals(0, stats.statsAbove2G.rx.level.length);
+    }
+
+    @Test
+    public void testLinkBandwidthLargeByteCountReturnNonNegativeValue() {
+        mWifiInfo.setRssi(-70);
+        mWifiInfo.setMaxSupportedRxLinkSpeedMbps(200_000);
+        mWifiScoreCard.noteConnectionAttempt(mWifiInfo, -53, mWifiInfo.getSSID());
+        PerNetwork perNetwork = mWifiScoreCard.lookupNetwork(mWifiInfo.getSSID());
+        mWifiInfo.setFrequency(5210);
+        mWifiScoreCard.noteIpConfiguration(mWifiInfo);
+        mOldLlStats.timeStampInMs = 7_000;
+        mNewLlStats.timeStampInMs = 10_000;
+        long txBytes = 8_000_000_000L;
+        long rxBytes = 16_000_000_000L;
+        int [] reportedKbps = new int[]{400_000, 300_000};
+        int [] l2Kbps = new int[]{800_000, 700_000};
+
+        // Report a small on_time so that the calculated BW overflows at 5G
+        mNewLlStats.on_time = 10;
+        for (int i = 0; i < BANDWIDTH_STATS_COUNT_THR + 2; i++) {
+            addTotalBytes(txBytes, rxBytes);
+            millisecondsPass(3_000);
+            perNetwork.updateLinkBandwidth(mOldLlStats, mNewLlStats, mWifiInfo);
+            perNetwork.updateBwMetrics(reportedKbps, l2Kbps);
+        }
+        // Report a larger on_time so that the calculated BW won't overflows at 2G
+        mWifiInfo.setFrequency(2412);
+        mNewLlStats.on_time = 1000;
+        for (int i = 0; i < BANDWIDTH_STATS_COUNT_THR + 2; i++) {
+            addTotalBytes(txBytes, rxBytes);
+            millisecondsPass(3_000);
+            perNetwork.updateLinkBandwidth(mOldLlStats, mNewLlStats, mWifiInfo);
+            perNetwork.updateBwMetrics(reportedKbps, l2Kbps);
+        }
+
+        // Report cold start BW for Tx because the calculated value is higher than
+        // maxSupportedTxLinkSpeedMbps.
+        assertEquals(10_000, perNetwork.getTxLinkBandwidthKbps());
+        assertEquals(128_000_000, perNetwork.getRxLinkBandwidthKbps());
+
+        BandwidthEstimatorStats stats = mWifiScoreCard.dumpBandwidthEstimatorStats();
+        assertEquals(0, stats.statsAbove2G.tx.level.length);
+        assertEquals(0, stats.statsAbove2G.rx.level.length);
+        assertEquals(0, stats.stats2G.tx.level.length);
+        assertEquals(1, stats.stats2G.rx.level.length);
+        assertEquals(128_000_000, stats.stats2G.rx.level[0].avgBandwidthKbps);
+        assertEquals(1, stats.stats2G.rx.level[0].count);
+
+        mNewLlStats.on_time = 2000;
+        for (int i = 0; i < BANDWIDTH_STATS_COUNT_THR + 2; i++) {
+            addTotalBytes(txBytes, rxBytes);
+            millisecondsPass(3_000);
+            perNetwork.updateLinkBandwidth(mOldLlStats, mNewLlStats, mWifiInfo);
+            perNetwork.updateBwMetrics(reportedKbps, l2Kbps);
+        }
+        stats = mWifiScoreCard.dumpBandwidthEstimatorStats();
+        assertEquals(64_000_000, stats.stats2G.rx.level[0].avgBandwidthKbps);
+        assertEquals(BANDWIDTH_STATS_COUNT_THR + 2, stats.stats2G.rx.level[0].count);
+    }
+
+    @Test
+    public void testLinkBandwidthResetInvalidStats() {
+        mWifiInfo.setRssi(-70);
+        mWifiInfo.setMaxSupportedRxLinkSpeedMbps(200_000);
+        mWifiScoreCard.noteConnectionAttempt(mWifiInfo, -53, mWifiInfo.getSSID());
+        PerNetwork perNetwork = mWifiScoreCard.lookupNetwork(mWifiInfo.getSSID());
+        mWifiScoreCard.noteIpConfiguration(mWifiInfo);
+        mOldLlStats.timeStampInMs = 7_000;
+        mNewLlStats.timeStampInMs = 10_000;
+        long txBytes = 8_000_000_000L;
+        long rxBytes = 16_000_000_000L;
+        mWifiInfo.setFrequency(2412);
+        mNewLlStats.on_time = 1000;
+        for (int i = 0; i < BANDWIDTH_STATS_COUNT_THR + 2; i++) {
+            addTotalBytes(txBytes, rxBytes);
+            millisecondsPass(3_000);
+            perNetwork.updateLinkBandwidth(mOldLlStats, mNewLlStats, mWifiInfo);
+        }
+        assertEquals(128_000_000, perNetwork.getRxLinkBandwidthKbps());
+        // Reduce max supported Rx link speed so that stats in the memory become invalid
+        // and fall back to cold start values
+        mWifiInfo.setMaxSupportedRxLinkSpeedMbps(100);
+        for (int i = 0; i < BANDWIDTH_STATS_COUNT_THR + 2; i++) {
+            addTotalBytes(txBytes, rxBytes);
+            millisecondsPass(3_000);
+            perNetwork.updateLinkBandwidth(mOldLlStats, mNewLlStats, mWifiInfo);
+        }
+        assertEquals(10_070, perNetwork.getRxLinkBandwidthKbps());
+    }
+
+    @Test
+    public void testLinkBandwidthLowOnTimeHighSignalLevel() {
+        // Add polls with zero on_time and high signal level
+        mWifiInfo.setRssi(-53);
+        int signalLevel = RssiUtil.calculateSignalLevel(mContext, mWifiInfo.getRssi());
+        mWifiInfo.setFrequency(5210);
+        mWifiScoreCard.noteConnectionAttempt(mWifiInfo, -53, mWifiInfo.getSSID());
+        PerNetwork perNetwork = mWifiScoreCard.lookupNetwork(mWifiInfo.getSSID());
+        mWifiInfo.setFrequency(5210);
+        mWifiScoreCard.noteIpConfiguration(mWifiInfo);
+        mNewLlStats.on_time = 5;
+        mOldLlStats.timeStampInMs = 7_000;
+        mNewLlStats.timeStampInMs = 10_000;
+        long txBytes = 2_000_000L;
+        long rxBytes = 100_000L;
+        for (int i = 0; i < BANDWIDTH_STATS_COUNT_THR; i++) {
+            addTotalBytes(txBytes, rxBytes);
+            millisecondsPass(3_000);
+            perNetwork.updateLinkBandwidth(mOldLlStats, mNewLlStats, mWifiInfo);
+        }
+
+        // Expect cold-start value
+        assertEquals(LINK_BANDWIDTH_INIT_KBPS[1][LINK_TX][signalLevel],
+                perNetwork.getTxLinkBandwidthKbps());
+        assertEquals(LINK_BANDWIDTH_INIT_KBPS[1][LINK_RX][signalLevel],
+                perNetwork.getRxLinkBandwidthKbps());
+    }
+
+    @Test
+    public void testGetLinkBandwidthWithoutUpdateReturnLevel0Band0Value() {
+        PerNetwork perNetwork = mWifiScoreCard.lookupNetwork(mWifiInfo.getSSID());
+
+        // Call getLinkBandwidth() without updateLinkBandwidth()
+        // Expect cold-start value at level 0 and band 0
+        assertEquals(LINK_BANDWIDTH_INIT_KBPS[0][LINK_TX][0],
+                perNetwork.getTxLinkBandwidthKbps());
+        assertEquals(LINK_BANDWIDTH_INIT_KBPS[0][LINK_RX][0],
+                perNetwork.getRxLinkBandwidthKbps());
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiScoreReportTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiScoreReportTest.java
index b162760..86d58cd 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiScoreReportTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiScoreReportTest.java
@@ -18,56 +18,54 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.AdditionalAnswers.answerVoid;
 import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.argThat;
 import static org.mockito.Mockito.atMost;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.net.ConnectivityManager;
-import android.net.LinkProperties;
 import android.net.Network;
-import android.net.NetworkAgent;
-import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
-import android.net.NetworkProvider;
 import android.net.NetworkScore;
 import android.net.wifi.IScoreUpdateObserver;
 import android.net.wifi.IWifiConnectedNetworkScorer;
-import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiConnectedSessionInfo;
 import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.os.test.TestLooper;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.modules.utils.build.SdkLevel;
 import com.android.wifi.resources.R;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.verification.VerificationMode;
 
 import java.io.PrintWriter;
 
@@ -89,14 +87,15 @@
 
     private static final int TEST_NETWORK_ID = 860370;
     private static final int TEST_SESSION_ID = 8603703; // last digit is a check digit
+    private static final String TEST_IFACE_NAME = "wlan0";
+    public static final String TEST_BSSID = "00:00:00:00:00:00";
+    public static final boolean TEST_USER_SELECTED = true;
 
     FakeClock mClock;
-    WifiConfiguration mWifiConfiguration;
     WifiScoreReport mWifiScoreReport;
-    ScanDetailCache mScanDetailCache;
-    WifiInfo mWifiInfo;
+    ExtendedWifiInfo mWifiInfo;
     ScoringParams mScoringParams;
-    NetworkAgent mNetworkAgent;
+    @Mock WifiNetworkAgent mNetworkAgent;
     WifiThreadRunner mWifiThreadRunner;
     @Mock Context mContext;
     @Mock Resources mResources;
@@ -105,44 +104,33 @@
     @Mock IBinder mAppBinder;
     @Mock IWifiConnectedNetworkScorer mWifiConnectedNetworkScorer;
     @Mock WifiNative mWifiNative;
-    @Mock BssidBlocklistMonitor mBssidBlocklistMonitor;
+    @Mock WifiBlocklistMonitor mWifiBlocklistMonitor;
     @Mock Network mNetwork;
+    @Mock WifiScoreCard mWifiScoreCard;
+    @Mock WifiScoreCard.PerNetwork mPerNetwork;
     @Mock DeviceConfigFacade mDeviceConfigFacade;
-    @Mock Looper mWifiLooper;
-    @Mock FrameworkFacade mFrameworkFacade;
+    @Mock AdaptiveConnectivityEnabledSettingObserver mAdaptiveConnectivityEnabledSettingObserver;
+    @Mock ExternalScoreUpdateObserverProxy mExternalScoreUpdateObserverProxy;
+    @Mock WifiSettingsStore mWifiSettingsStore;
+    @Mock WifiGlobals mWifiGlobals;
+    @Captor ArgumentCaptor<WifiManager.ScoreUpdateObserver> mExternalScoreUpdateObserverCbCaptor;
     private TestLooper mLooper;
 
     public class WifiConnectedNetworkScorerImpl extends IWifiConnectedNetworkScorer.Stub {
-        public IScoreUpdateObserver mScoreUpdateObserver;
         public int mSessionId = -1;
 
         @Override
-        public void onStart(int sessionId) {
-            mSessionId = sessionId;
+        public void onStart(WifiConnectedSessionInfo sessionInfo) {
+            mSessionId = sessionInfo.getSessionId();
         }
         @Override
         public void onStop(int sessionId) {
         }
         @Override
         public void onSetScoreUpdateObserver(IScoreUpdateObserver observerImpl) {
-            mScoreUpdateObserver = observerImpl;
         }
     }
 
-    // NetworkAgent is abstract, so a subclass is necessary
-    private static class TestNetworkAgent extends NetworkAgent {
-        TestNetworkAgent(Context context) {
-            this(context, new TestLooper().getLooper());
-        }
-        private TestNetworkAgent(Context context, Looper looper) {
-            super(context, looper, "TestNetworkAgent", new NetworkCapabilities(),
-                    new LinkProperties(), 0, new NetworkAgentConfig.Builder().build(),
-                    new NetworkProvider(context, looper, "ScoreReportTest agent"));
-            register();
-        }
-        @Override protected void unwanted() { }
-    }
-
     /**
      * Sets up resource values for testing
      *
@@ -200,33 +188,39 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         setUpResources(mResources);
-        mWifiInfo = new WifiInfo();
+        mWifiInfo = new ExtendedWifiInfo(mWifiGlobals, TEST_IFACE_NAME);
         mWifiInfo.setFrequency(2412);
+        mWifiInfo.setBSSID(TEST_BSSID);
         mLooper = new TestLooper();
         when(mContext.getResources()).thenReturn(mResources);
-        final ConnectivityManager cm = mock(ConnectivityManager.class);
-        when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(cm);
-        when(cm.registerNetworkAgent(any(), any(), any(), any(), any(), any(), anyInt()))
-                .thenReturn(mNetwork);
         when(mNetwork.getNetId()).thenReturn(0);
-        mNetworkAgent = spy(new TestNetworkAgent(mContext));
+        when(mNetworkAgent.getNetwork()).thenReturn(mNetwork);
+        when(mNetworkAgent.getCurrentNetworkCapabilities()).thenReturn(
+                new NetworkCapabilities.Builder()
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                        .build());
         mClock = new FakeClock();
         mScoringParams = new ScoringParams();
         mWifiThreadRunner = new WifiThreadRunner(new Handler(mLooper.getLooper()));
-        when(mFrameworkFacade.getIntegerSetting(any(Context.class),
-                eq(WifiScoreReport.SETTINGS_SECURE_ADAPTIVE_CONNECTIVITY_ENABLED), eq(1)))
-                .thenReturn(1);
+        when(mAdaptiveConnectivityEnabledSettingObserver.get()).thenReturn(true);
         mWifiScoreReport = new WifiScoreReport(mScoringParams, mClock, mWifiMetrics, mWifiInfo,
-                mWifiNative, mBssidBlocklistMonitor, mWifiThreadRunner,
-                mDeviceConfigFacade, mContext, mWifiLooper, mFrameworkFacade);
+                mWifiNative, mWifiBlocklistMonitor, mWifiThreadRunner, mWifiScoreCard,
+                mDeviceConfigFacade, mContext,
+                mAdaptiveConnectivityEnabledSettingObserver, TEST_IFACE_NAME,
+                mExternalScoreUpdateObserverProxy, mWifiSettingsStore);
+        mWifiScoreReport.onRoleChanged(ActiveModeManager.ROLE_CLIENT_PRIMARY);
         mWifiScoreReport.setNetworkAgent(mNetworkAgent);
-        mWifiScoreReport.initialize();
         when(mDeviceConfigFacade.getMinConfirmationDurationSendLowScoreMs()).thenReturn(
                 DeviceConfigFacade.DEFAULT_MIN_CONFIRMATION_DURATION_SEND_LOW_SCORE_MS);
         when(mDeviceConfigFacade.getMinConfirmationDurationSendHighScoreMs()).thenReturn(
                 DeviceConfigFacade.DEFAULT_MIN_CONFIRMATION_DURATION_SEND_HIGH_SCORE_MS);
         when(mDeviceConfigFacade.getRssiThresholdNotSendLowScoreToCsDbm()).thenReturn(
                 DeviceConfigFacade.DEFAULT_RSSI_THRESHOLD_NOT_SEND_LOW_SCORE_TO_CS_DBM);
+        when(mWifiSettingsStore.isWifiScoringEnabled()).thenReturn(true);
+        when(mPerNetwork.getTxLinkBandwidthKbps()).thenReturn(40_000);
+        when(mPerNetwork.getRxLinkBandwidthKbps()).thenReturn(50_000);
+        when(mWifiScoreCard.lookupNetwork(any())).thenReturn(mPerNetwork);
     }
 
     /**
@@ -240,17 +234,197 @@
     }
 
     /**
+     * Assert a certain score was sent. Works on all SDK levels.
+     * @param score expected score
+     * @param mode times(n), never(), atLeastOnce(), etc.
+     */
+    private void verifySentNetworkScore(int score, VerificationMode mode) {
+        if (SdkLevel.isAtLeastS()) {
+            verify(mNetworkAgent, mode).sendNetworkScore(argThat(
+                    // note that a lambda doesn't work here, will cause a crash due to missing
+                    // class `NetworkScore` on R even though this code path is never reached on R.
+                    // Maybe lambdas are eagerly loaded by the classloader, while inner classes
+                    // aren't?
+                    new ArgumentMatcher<NetworkScore>() {
+                        @Override
+                        public boolean matches(NetworkScore networkScore) {
+                            return networkScore.getLegacyInt() == score;
+                        }
+                    }));
+        } else {
+            verify(mNetworkAgent, mode).sendNetworkScore(score);
+        }
+    }
+
+    private void verifySentNetworkScore(int score) {
+        verifySentNetworkScore(score, times(1));
+    }
+
+    private void verifySentAnyNetworkScore(VerificationMode mode) {
+        if (SdkLevel.isAtLeastS()) {
+            verify(mNetworkAgent, mode).sendNetworkScore(any(NetworkScore.class));
+        } else {
+            verify(mNetworkAgent, mode).sendNetworkScore(anyInt());
+        }
+    }
+
+    private void verifySentAnyNetworkScore() {
+        verifySentAnyNetworkScore(times(1));
+    }
+
+    /**
      * Test for score reporting
      *
-     * The score should be sent to both the NetworkAgent and the
-     * WifiMetrics
+     * The score should be sent to both NetworkAgent and WifiMetrics
      */
     @Test
     public void calculateAndReportScoreSucceeds() throws Exception {
+        // initially called once
+        verifySentAnyNetworkScore();
+
         mWifiInfo.setRssi(-77);
         mWifiScoreReport.calculateAndReportScore();
-        verify(mNetworkAgent).sendNetworkScore(any());
-        verify(mWifiMetrics).incrementWifiScoreCount(anyInt());
+        // called again after calculateAndReportScore()
+        verifySentAnyNetworkScore(times(2));
+        verify(mWifiMetrics).incrementWifiScoreCount(eq(TEST_IFACE_NAME), anyInt());
+    }
+
+    @Test
+    public void mbbNetworkForceKeepUp() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        reset(mNetworkAgent);
+
+        ArgumentCaptor<NetworkScore> networkScoreCaptor =
+                ArgumentCaptor.forClass(NetworkScore.class);
+
+        // start as SECONDARY_TRANSIENT
+        mWifiScoreReport.onRoleChanged(ActiveModeManager.ROLE_CLIENT_SECONDARY_TRANSIENT);
+
+        verify(mNetworkAgent).sendNetworkScore(networkScoreCaptor.capture());
+        {
+            NetworkScore networkScore = networkScoreCaptor.getValue();
+            assertNotEquals(WifiScoreReport.LINGERING_SCORE, networkScore.getLegacyInt());
+            assertFalse(networkScore.isExiting());
+            assertFalse(networkScore.isTransportPrimary());
+            // force keep up network
+            assertEquals(NetworkScore.KEEP_CONNECTED_FOR_HANDOVER,
+                    networkScore.getKeepConnectedReason());
+        }
+
+        // network validated, becomes primary
+        mWifiScoreReport.onRoleChanged(ActiveModeManager.ROLE_CLIENT_PRIMARY);
+        verify(mNetworkAgent, times(2)).sendNetworkScore(networkScoreCaptor.capture());
+        {
+            NetworkScore networkScore = networkScoreCaptor.getValue();
+            assertNotEquals(WifiScoreReport.LINGERING_SCORE, networkScore.getLegacyInt());
+            assertFalse(networkScore.isExiting());
+            assertTrue(networkScore.isTransportPrimary());
+            // no longer need to force keep up network
+            assertEquals(NetworkScore.KEEP_CONNECTED_NONE, networkScore.getKeepConnectedReason());
+        }
+    }
+
+    @Test
+    public void calculateAndReportScoreWhileLingering_sendLingeringScore() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        reset(mNetworkAgent);
+
+        ArgumentCaptor<NetworkScore> networkScoreCaptor =
+                ArgumentCaptor.forClass(NetworkScore.class);
+
+        // start as primary
+        mWifiScoreReport.onRoleChanged(ActiveModeManager.ROLE_CLIENT_PRIMARY);
+
+        verify(mNetworkAgent).sendNetworkScore(networkScoreCaptor.capture());
+        {
+            NetworkScore networkScore = networkScoreCaptor.getValue();
+            assertNotEquals(WifiScoreReport.LINGERING_SCORE, networkScore.getLegacyInt());
+            assertFalse(networkScore.isExiting());
+            assertTrue(networkScore.isTransportPrimary());
+            assertEquals(NetworkScore.KEEP_CONNECTED_NONE, networkScore.getKeepConnectedReason());
+        }
+
+        // then, role changed to SECONDARY_TRANSIENT and started lingering
+        mWifiScoreReport.onRoleChanged(ActiveModeManager.ROLE_CLIENT_SECONDARY_TRANSIENT);
+        mWifiScoreReport.setShouldReduceNetworkScore(true);
+        // upon lingering, immediately send LINGERING_SCORE
+        // capture most recent invocation
+        verify(mNetworkAgent, times(3)).sendNetworkScore(networkScoreCaptor.capture());
+        {
+            NetworkScore networkScore = networkScoreCaptor.getValue();
+            assertEquals(WifiScoreReport.LINGERING_SCORE, networkScore.getLegacyInt());
+            assertFalse(networkScore.isExiting());
+            assertFalse(networkScore.isTransportPrimary());
+            assertEquals(NetworkScore.KEEP_CONNECTED_NONE, networkScore.getKeepConnectedReason());
+        }
+
+        mWifiInfo.setRssi(-77);
+        mWifiScoreReport.calculateAndReportScore();
+        // score not sent again while lingering
+        verify(mNetworkAgent, times(3)).sendNetworkScore(any());
+
+        // disable lingering
+        mWifiScoreReport.onRoleChanged(ActiveModeManager.ROLE_CLIENT_PRIMARY);
+        mWifiScoreReport.setShouldReduceNetworkScore(false);
+        // report score again
+        mWifiInfo.setRssi(-60);
+        mWifiScoreReport.calculateAndReportScore();
+        // Some non-lingering score is sent
+        // capture most recent invocation
+        verify(mNetworkAgent, times(6)).sendNetworkScore(networkScoreCaptor.capture());
+        {
+            NetworkScore networkScore = networkScoreCaptor.getValue();
+            assertNotEquals(WifiScoreReport.LINGERING_SCORE, networkScore.getLegacyInt());
+            assertFalse(networkScore.isExiting());
+            assertTrue(networkScore.isTransportPrimary());
+            assertEquals(NetworkScore.KEEP_CONNECTED_NONE, networkScore.getKeepConnectedReason());
+        }
+    }
+
+    @Test
+    public void testExternalScorerWhileLingering_sendLingeringScore() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        mWifiScoreReport.onRoleChanged(ActiveModeManager.ROLE_CLIENT_SECONDARY_LONG_LIVED);
+
+        // Register Client for verification.
+        mWifiScoreReport.setWifiConnectedNetworkScorer(mAppBinder, mWifiConnectedNetworkScorer);
+        verify(mExternalScoreUpdateObserverProxy).registerCallback(
+                mExternalScoreUpdateObserverCbCaptor.capture());
+        when(mNetwork.getNetId()).thenReturn(TEST_NETWORK_ID);
+        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID, TEST_USER_SELECTED);
+        verify(mWifiConnectedNetworkScorer).onStart(
+                argThat(sessionInfo -> sessionInfo.getSessionId() == TEST_SESSION_ID
+                        && sessionInfo.isUserSelected() == TEST_USER_SELECTED));
+
+        reset(mNetworkAgent);
+
+        ArgumentCaptor<NetworkScore> networkScoreCaptor =
+                ArgumentCaptor.forClass(NetworkScore.class);
+        mWifiScoreReport.setShouldReduceNetworkScore(true);
+        // upon lingering, immediately send LINGERING_SCORE
+        // capture most recent invocation
+        verify(mNetworkAgent).sendNetworkScore(networkScoreCaptor.capture());
+        {
+            NetworkScore networkScore = networkScoreCaptor.getValue();
+            assertEquals(WifiScoreReport.LINGERING_SCORE, networkScore.getLegacyInt());
+            assertFalse(networkScore.isExiting());
+            assertFalse(networkScore.isTransportPrimary());
+        }
+        // upon lingering, send session end to client.
+        verify(mWifiConnectedNetworkScorer).onStop(TEST_SESSION_ID);
+
+        // send score after session has ended
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyStatusUpdate(TEST_SESSION_ID, false);
+        mLooper.dispatchAll();
+        // score not sent since session ended
+        verify(mNetworkAgent, never()).sendNetworkScore(argThat(
+                new ArgumentMatcher<NetworkScore>() {
+                    @Override
+                    public boolean matches(NetworkScore ns) {
+                        return ns.getLegacyInt() == 49 && ns.isExiting() && ns.isTransportPrimary();
+                    }
+                }));
     }
 
     /**
@@ -261,10 +435,14 @@
      */
     @Test
     public void calculateAndReportScoreDoesNotReportWhenRssiIsNotValid() throws Exception {
+        // initially called once
+        verifySentAnyNetworkScore();
+
         mWifiInfo.setRssi(WifiInfo.INVALID_RSSI);
         mWifiScoreReport.calculateAndReportScore();
-        verify(mNetworkAgent, never()).sendNetworkScore(any());
-        verify(mWifiMetrics, never()).incrementWifiScoreCount(anyInt());
+        // still only called once
+        verifySentAnyNetworkScore();
+        verify(mWifiMetrics, never()).incrementWifiScoreCount(any(), anyInt());
     }
 
     /**
@@ -278,7 +456,7 @@
         mWifiScoreReport.enableVerboseLogging(true);
         mWifiScoreReport.setNetworkAgent(null);
         mWifiScoreReport.calculateAndReportScore();
-        verify(mWifiMetrics).incrementWifiScoreCount(anyInt());
+        verify(mWifiMetrics).incrementWifiScoreCount(eq(TEST_IFACE_NAME), anyInt());
     }
 
     /**
@@ -312,6 +490,9 @@
      */
     @Test
     public void giveUpOnBadRssiWhenDataIsNotMoving() throws Exception {
+        // initially called once
+        verifySentAnyNetworkScore();
+
         mWifiInfo.setRssi(-100);
         mWifiInfo.setLinkSpeed(6); // Mbps
         mWifiInfo.setFrequency(5220);
@@ -323,8 +504,16 @@
         }
         int score = mWifiInfo.getScore();
         assertTrue(score < ConnectedScore.WIFI_TRANSITION_SCORE);
-        verify(mNetworkAgent, atLeast(1)).sendNetworkScore(argThat(ns ->
-                ns.getLegacyInt() == score && ns.isExiting() && ns.isTransportPrimary()));
+        if (SdkLevel.isAtLeastS()) {
+            ArgumentCaptor<NetworkScore> scoreCaptor = ArgumentCaptor.forClass(NetworkScore.class);
+            verify(mNetworkAgent, times(2)).sendNetworkScore(scoreCaptor.capture());
+            NetworkScore ns = scoreCaptor.getValue();
+            assertEquals(score, ns.getLegacyInt());
+            assertTrue(ns.isExiting());
+            assertTrue(ns.isTransportPrimary());
+        } else {
+            verify(mNetworkAgent).sendNetworkScore(score);
+        }
     }
 
     /**
@@ -341,8 +530,7 @@
             oops += ":" + mWifiInfo.getScore();
         }
         int score = mWifiInfo.getScore();
-        verify(mNetworkAgent, atLeast(1)).sendNetworkScore(
-                argThat(ns -> ns.getLegacyInt() == score));
+        verifySentNetworkScore(score);
         assertTrue(oops, score < ConnectedScore.WIFI_TRANSITION_SCORE);
     }
 
@@ -516,7 +704,7 @@
         }
         setupToGenerateAReportWhenPrintlnIsCalled();
         mWifiScoreReport.dump(null, mPrintWriter, null);
-        verify(mPrintWriter, times(11)).println(anyString());
+        verify(mPrintWriter, times(13)).println(anyString());
     }
 
     /**
@@ -537,7 +725,7 @@
             mWifiScoreReport.calculateAndReportScore();
         }
         mWifiScoreReport.dump(null, mPrintWriter, null);
-        verify(mPrintWriter, atMost(3601)).println(anyString());
+        verify(mPrintWriter, atMost(3603)).println(anyString());
     }
 
     /**
@@ -600,7 +788,7 @@
         // Register Client for verification.
         mWifiScoreReport.setWifiConnectedNetworkScorer(mAppBinder, mWifiConnectedNetworkScorer);
         // Client should get ScoreChangeCallback.
-        verify(mWifiConnectedNetworkScorer).onSetScoreUpdateObserver(any());
+        verify(mExternalScoreUpdateObserverProxy).registerCallback(any());
     }
 
     /**
@@ -610,11 +798,13 @@
     public void testClearClient() throws RemoteException {
         // Register Client for verification.
         mWifiScoreReport.setWifiConnectedNetworkScorer(mAppBinder, mWifiConnectedNetworkScorer);
+        verify(mExternalScoreUpdateObserverProxy).registerCallback(any());
         mWifiScoreReport.clearWifiConnectedNetworkScorer();
         verify(mAppBinder).unlinkToDeath(any(), anyInt());
+        verify(mExternalScoreUpdateObserverProxy).unregisterCallback(any());
 
-        mWifiScoreReport.startConnectedNetworkScorer(10);
-        verify(mWifiConnectedNetworkScorer, never()).onStart(anyInt());
+        mWifiScoreReport.startConnectedNetworkScorer(10, true);
+        verify(mWifiConnectedNetworkScorer, never()).onStart(any());
     }
 
     /**
@@ -623,6 +813,7 @@
     @Test
     public void testAddsForBinderDeathOnSetClient() throws Exception {
         mWifiScoreReport.setWifiConnectedNetworkScorer(mAppBinder, mWifiConnectedNetworkScorer);
+        verify(mExternalScoreUpdateObserverProxy).registerCallback(any());
         verify(mAppBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
     }
 
@@ -634,6 +825,7 @@
         doThrow(new RemoteException())
                 .when(mAppBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
         mWifiScoreReport.setWifiConnectedNetworkScorer(mAppBinder, mWifiConnectedNetworkScorer);
+        verify(mExternalScoreUpdateObserverProxy, never()).registerCallback(any());
         verify(mAppBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
 
         // Client should not get any message when scorer add failed.
@@ -666,9 +858,12 @@
     public void testClientGetSessionIdOnStart() throws Exception {
         // Register Client for verification.
         mWifiScoreReport.setWifiConnectedNetworkScorer(mAppBinder, mWifiConnectedNetworkScorer);
+        verify(mExternalScoreUpdateObserverProxy).registerCallback(any());
         when(mNetwork.getNetId()).thenReturn(TEST_NETWORK_ID);
-        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID);
-        verify(mWifiConnectedNetworkScorer).onStart(TEST_SESSION_ID);
+        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID, TEST_USER_SELECTED);
+        verify(mWifiConnectedNetworkScorer).onStart(
+                argThat(sessionInfo -> sessionInfo.getSessionId() == TEST_SESSION_ID
+                        && sessionInfo.isUserSelected() == TEST_USER_SELECTED));
     }
 
     /**
@@ -677,9 +872,12 @@
     @Test
     public void testClientStartOnRegWhileActive() throws Exception {
         when(mNetwork.getNetId()).thenReturn(TEST_NETWORK_ID);
-        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID);
+        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID, TEST_USER_SELECTED);
         mWifiScoreReport.setWifiConnectedNetworkScorer(mAppBinder, mWifiConnectedNetworkScorer);
-        verify(mWifiConnectedNetworkScorer).onStart(TEST_SESSION_ID);
+        verify(mExternalScoreUpdateObserverProxy).registerCallback(any());
+        verify(mWifiConnectedNetworkScorer).onStart(
+                argThat(sessionInfo -> sessionInfo.getSessionId() == TEST_SESSION_ID
+                        && sessionInfo.isUserSelected() == TEST_USER_SELECTED));
     }
 
     /**
@@ -689,14 +887,19 @@
     public void testClientGetSessionIdOnStop() throws Exception {
         // Register Client for verification.
         mWifiScoreReport.setWifiConnectedNetworkScorer(mAppBinder, mWifiConnectedNetworkScorer);
+        verify(mExternalScoreUpdateObserverProxy).registerCallback(any());
         when(mNetwork.getNetId()).thenReturn(TEST_NETWORK_ID);
-        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID);
-        verify(mWifiConnectedNetworkScorer).onStart(TEST_SESSION_ID);
+        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID, TEST_USER_SELECTED);
+        verify(mWifiConnectedNetworkScorer).onStart(
+                argThat(sessionInfo -> sessionInfo.getSessionId() == TEST_SESSION_ID
+                        && sessionInfo.isUserSelected() == TEST_USER_SELECTED));
         mWifiScoreReport.stopConnectedNetworkScorer();
         verify(mWifiConnectedNetworkScorer).onStop(TEST_SESSION_ID);
         // After the session stops, it should not start again (without a new NetworkAgent)
-        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID);
-        verify(mWifiConnectedNetworkScorer).onStart(anyInt());
+        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID, TEST_USER_SELECTED);
+        verify(mWifiConnectedNetworkScorer).onStart(
+                argThat(sessionInfo -> sessionInfo.getSessionId() == TEST_SESSION_ID
+                        && sessionInfo.isUserSelected() == TEST_USER_SELECTED));
     }
 
     /**
@@ -707,6 +910,7 @@
         WifiConnectedNetworkScorerImpl scorerImpl = new WifiConnectedNetworkScorerImpl();
         assertEquals(true, mWifiScoreReport.setWifiConnectedNetworkScorer(
                 mAppBinder, scorerImpl));
+        verify(mExternalScoreUpdateObserverProxy).registerCallback(any());
         assertEquals(false, mWifiScoreReport.setWifiConnectedNetworkScorer(
                 mAppBinder, scorerImpl));
     }
@@ -716,43 +920,67 @@
      */
     @Test
     public void testFrameworkGetsUpdatesScore() throws Exception {
+        assumeFalse(SdkLevel.isAtLeastS());
+        // initially called once
+        verifySentNetworkScore(60);
         WifiConnectedNetworkScorerImpl scorerImpl = new WifiConnectedNetworkScorerImpl();
         // Register Client for verification.
         mWifiScoreReport.setWifiConnectedNetworkScorer(mAppBinder, scorerImpl);
+        verify(mExternalScoreUpdateObserverProxy).registerCallback(
+                mExternalScoreUpdateObserverCbCaptor.capture());
         when(mNetwork.getNetId()).thenReturn(TEST_NETWORK_ID);
-        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID);
+        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID, TEST_USER_SELECTED);
 
         assertEquals(TEST_SESSION_ID, scorerImpl.mSessionId);
 
         // Invalid session ID
-        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(-1, 49);
-        NetworkScore score = mWifiScoreReport.getScore();
-        assertEquals(score.getLegacyInt(), ConnectedScore.WIFI_MAX_SCORE);
-        assertTrue(score.isTransportPrimary());
-        assertFalse(score.isExiting());
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(-1, 49);
+        if (SdkLevel.isAtLeastS()) {
+            NetworkScore score = mWifiScoreReport.getScore();
+            assertEquals(ConnectedScore.WIFI_MAX_SCORE, score.getLegacyInt());
+            assertTrue(score.isTransportPrimary());
+            assertFalse(score.isExiting());
+        } else {
+            assertEquals(ConnectedScore.WIFI_MAX_SCORE, mWifiScoreReport.getLegacyIntScore());
+        }
 
         // Incorrect session ID
-        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId + 10, 49);
-        score = mWifiScoreReport.getScore();
-        assertEquals(score.getLegacyInt(), ConnectedScore.WIFI_MAX_SCORE);
-        assertTrue(score.isTransportPrimary());
-        assertFalse(score.isExiting());
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(
+                scorerImpl.mSessionId + 10, 49);
+        if (SdkLevel.isAtLeastS()) {
+            NetworkScore score = mWifiScoreReport.getScore();
+            assertEquals(ConnectedScore.WIFI_MAX_SCORE, score.getLegacyInt());
+            assertTrue(score.isTransportPrimary());
+            assertFalse(score.isExiting());
+        } else {
+            assertEquals(ConnectedScore.WIFI_MAX_SCORE, mWifiScoreReport.getLegacyIntScore());
+        }
 
-        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId, 49);
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(
+                scorerImpl.mSessionId, 49);
         mLooper.dispatchAll();
-        verify(mNetworkAgent).sendNetworkScore(argThat(ns -> ns.getLegacyInt() == 49));
-        score = mWifiScoreReport.getScore();
-        assertEquals(score.getLegacyInt(), 49);
-        assertTrue(score.isTransportPrimary());
-        assertTrue(score.isExiting());
+        verifySentNetworkScore(49);
+        if (SdkLevel.isAtLeastS()) {
+            NetworkScore score = mWifiScoreReport.getScore();
+            assertEquals(score.getLegacyInt(), 49);
+            assertTrue(score.isTransportPrimary());
+            assertTrue(score.isExiting());
+        } else {
+            assertEquals(49, mWifiScoreReport.getLegacyIntScore());
+        }
 
-        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId, 59);
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(
+                scorerImpl.mSessionId, 59);
         mLooper.dispatchAll();
-        verify(mNetworkAgent).sendNetworkScore(argThat(ns -> ns.getLegacyInt() == 59));
-        score = mWifiScoreReport.getScore();
-        assertEquals(score.getLegacyInt(), 59);
-        assertTrue(score.isTransportPrimary());
-        assertFalse(score.isExiting());
+        verifySentNetworkScore(59);
+        if (SdkLevel.isAtLeastS()) {
+            NetworkScore score = mWifiScoreReport.getScore();
+            assertEquals(score.getLegacyInt(), 59);
+            assertTrue(score.isTransportPrimary());
+            assertFalse(score.isExiting());
+        } else {
+            assertEquals(59, mWifiScoreReport.getLegacyIntScore());
+        }
     }
 
     /**
@@ -763,14 +991,16 @@
         WifiConnectedNetworkScorerImpl scorerImpl = new WifiConnectedNetworkScorerImpl();
         // Register Client for verification.
         mWifiScoreReport.setWifiConnectedNetworkScorer(mAppBinder, scorerImpl);
-        mWifiScoreReport.setInterfaceName("wlan0");
+        verify(mExternalScoreUpdateObserverProxy).registerCallback(
+                mExternalScoreUpdateObserverCbCaptor.capture());
         when(mNetwork.getNetId()).thenReturn(TEST_NETWORK_ID);
-        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID);
+        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID, TEST_USER_SELECTED);
 
-        scorerImpl.mScoreUpdateObserver.triggerUpdateOfWifiUsabilityStats(scorerImpl.mSessionId);
+        mExternalScoreUpdateObserverCbCaptor.getValue().triggerUpdateOfWifiUsabilityStats(
+                scorerImpl.mSessionId);
         mLooper.dispatchAll();
-        verify(mWifiNative).getWifiLinkLayerStats("wlan0");
-        verify(mWifiNative).signalPoll("wlan0");
+        verify(mWifiNative).getWifiLinkLayerStats(TEST_IFACE_NAME);
+        verify(mWifiNative).signalPoll(TEST_IFACE_NAME);
     }
 
     /**
@@ -778,28 +1008,36 @@
      */
     @Test
     public void askForNudCheckWhenExternalScoreBreaches() throws Exception {
+        assumeFalse(SdkLevel.isAtLeastS());
         WifiConnectedNetworkScorerImpl scorerImpl = new WifiConnectedNetworkScorerImpl();
         // Register Client for verification.
         mWifiScoreReport.setWifiConnectedNetworkScorer(mAppBinder, scorerImpl);
+        verify(mExternalScoreUpdateObserverProxy).registerCallback(
+                mExternalScoreUpdateObserverCbCaptor.capture());
         when(mNetwork.getNetId()).thenReturn(TEST_NETWORK_ID);
-        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID);
+        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID, TEST_USER_SELECTED);
         mClock.mStepMillis = 0;
 
         mClock.mWallClockMillis = 5001;
-        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId, 49);
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(
+                scorerImpl.mSessionId, 49);
         mLooper.dispatchAll();
         assertTrue(mWifiScoreReport.shouldCheckIpLayer());
+        assertEquals(1, mWifiScoreReport.getNudYes());
         mWifiScoreReport.noteIpCheck();
 
         mClock.mWallClockMillis = 10000;
-        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId, 49);
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(
+                scorerImpl.mSessionId, 49);
         mLooper.dispatchAll();
         assertFalse(mWifiScoreReport.shouldCheckIpLayer());
 
         mClock.mWallClockMillis = 10001;
-        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId, 49);
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(
+                scorerImpl.mSessionId, 49);
         mLooper.dispatchAll();
         assertTrue(mWifiScoreReport.shouldCheckIpLayer());
+        assertEquals(2, mWifiScoreReport.getNudYes());
     }
 
     /**
@@ -811,19 +1049,23 @@
         WifiConnectedNetworkScorerImpl scorerImpl = new WifiConnectedNetworkScorerImpl();
         // Register Client for verification.
         mWifiScoreReport.setWifiConnectedNetworkScorer(mAppBinder, scorerImpl);
+        verify(mExternalScoreUpdateObserverProxy).registerCallback(
+                mExternalScoreUpdateObserverCbCaptor.capture());
         when(mNetwork.getNetId()).thenReturn(TEST_NETWORK_ID);
-        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID);
+        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID, TEST_USER_SELECTED);
         mClock.mStepMillis = 0;
 
         mClock.mWallClockMillis = 10;
-        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId, 49);
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(
+                scorerImpl.mSessionId, 49);
         mLooper.dispatchAll();
         mClock.mWallClockMillis = 29009;
-        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId, 49);
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(
+                scorerImpl.mSessionId, 49);
         mLooper.dispatchAll();
         mWifiScoreReport.stopConnectedNetworkScorer();
         mLooper.dispatchAll();
-        verify(mBssidBlocklistMonitor, never()).handleBssidConnectionFailure(any(), any(),
+        verify(mWifiBlocklistMonitor, never()).handleBssidConnectionFailure(any(), any(),
                 anyInt(), anyInt());
     }
 
@@ -833,23 +1075,28 @@
      */
     @Test
     public void bssidBlockListHappensWhenExitingIsLongerThanMinDuration() throws Exception {
+        assumeFalse(SdkLevel.isAtLeastS());
         WifiConnectedNetworkScorerImpl scorerImpl = new WifiConnectedNetworkScorerImpl();
         // Register Client for verification.
         mWifiScoreReport.setWifiConnectedNetworkScorer(mAppBinder, scorerImpl);
+        verify(mExternalScoreUpdateObserverProxy).registerCallback(
+                mExternalScoreUpdateObserverCbCaptor.capture());
         when(mNetwork.getNetId()).thenReturn(TEST_NETWORK_ID);
-        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID);
+        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID, TEST_USER_SELECTED);
         mClock.mStepMillis = 0;
 
         mClock.mWallClockMillis = 10;
-        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId, 49);
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(
+                scorerImpl.mSessionId, 49);
         mLooper.dispatchAll();
         mClock.mWallClockMillis = 29011;
-        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId, 49);
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(
+                scorerImpl.mSessionId, 49);
         mLooper.dispatchAll();
         mWifiScoreReport.stopConnectedNetworkScorer();
         mLooper.dispatchAll();
-        verify(mBssidBlocklistMonitor).handleBssidConnectionFailure(any(), any(),
-                eq(BssidBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE), anyInt());
+        verify(mWifiBlocklistMonitor).handleBssidConnectionFailure(any(), any(),
+                eq(WifiBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE), anyInt());
     }
 
     /**
@@ -860,22 +1107,27 @@
         WifiConnectedNetworkScorerImpl scorerImpl = new WifiConnectedNetworkScorerImpl();
         // Register Client for verification.
         mWifiScoreReport.setWifiConnectedNetworkScorer(mAppBinder, scorerImpl);
+        verify(mExternalScoreUpdateObserverProxy).registerCallback(
+                mExternalScoreUpdateObserverCbCaptor.capture());
         when(mNetwork.getNetId()).thenReturn(TEST_NETWORK_ID);
-        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID);
+        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID, TEST_USER_SELECTED);
         mClock.mStepMillis = 0;
 
         mClock.mWallClockMillis = 10;
-        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId, 49);
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(
+                scorerImpl.mSessionId, 49);
         mLooper.dispatchAll();
         mClock.mWallClockMillis = 15000;
-        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId, 51);
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(
+                scorerImpl.mSessionId, 51);
         mLooper.dispatchAll();
         mClock.mWallClockMillis = 29011;
-        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId, 49);
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(
+                scorerImpl.mSessionId, 49);
         mLooper.dispatchAll();
         mWifiScoreReport.stopConnectedNetworkScorer();
         mLooper.dispatchAll();
-        verify(mBssidBlocklistMonitor, never()).handleBssidConnectionFailure(any(), any(),
+        verify(mWifiBlocklistMonitor, never()).handleBssidConnectionFailure(any(), any(),
                 anyInt(), anyInt());
     }
 
@@ -885,7 +1137,7 @@
     @Test
     public void testOnStartInitialScoreInWifiInfoIsMaxScore() throws Exception {
         when(mNetwork.getNetId()).thenReturn(TEST_NETWORK_ID);
-        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID);
+        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID, TEST_USER_SELECTED);
         mWifiScoreReport.setWifiConnectedNetworkScorer(mAppBinder, mWifiConnectedNetworkScorer);
         assertEquals(ConnectedScore.WIFI_MAX_SCORE, mWifiInfo.getScore());
     }
@@ -895,48 +1147,61 @@
      */
     @Test
     public void confirmationDurationIsNotAddedWhenItIsNotEnabledInConfigOverlay() throws Exception {
+        assumeFalse(SdkLevel.isAtLeastS());
+        verifySentAnyNetworkScore();
         WifiConnectedNetworkScorerImpl scorerImpl = new WifiConnectedNetworkScorerImpl();
         // Register Client for verification.
         mWifiScoreReport.setWifiConnectedNetworkScorer(mAppBinder, scorerImpl);
+        verify(mExternalScoreUpdateObserverProxy).registerCallback(
+                mExternalScoreUpdateObserverCbCaptor.capture());
         when(mNetwork.getNetId()).thenReturn(TEST_NETWORK_ID);
-        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID);
+        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID, TEST_USER_SELECTED);
         mClock.mStepMillis = 0;
 
         mClock.mWallClockMillis = 10;
         mWifiInfo.setRssi(-65);
-        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId, 49);
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(
+                scorerImpl.mSessionId, 49);
         mLooper.dispatchAll();
-        verify(mNetworkAgent).sendNetworkScore(any());
+        verifySentAnyNetworkScore(times(2));
     }
 
     /**
-     * Verify confirmation duration is not added when there is no score breacht
+     * Verify confirmation duration is not added when there is no score breach
      */
     @Test
     public void confirmationDurationIsNotAddedWhenThereIsNoScoreBreach() throws Exception {
+        assumeFalse(SdkLevel.isAtLeastS());
+        // initially sent score = 60
+        verifySentNetworkScore(60);
         WifiConnectedNetworkScorerImpl scorerImpl = new WifiConnectedNetworkScorerImpl();
         // Register Client for verification.
         mWifiScoreReport.setWifiConnectedNetworkScorer(mAppBinder, scorerImpl);
+        verify(mExternalScoreUpdateObserverProxy).registerCallback(
+                mExternalScoreUpdateObserverCbCaptor.capture());
         when(mNetwork.getNetId()).thenReturn(TEST_NETWORK_ID);
-        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID);
+        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID, TEST_USER_SELECTED);
         mClock.mStepMillis = 0;
         when(mContext.getResources().getBoolean(
             R.bool.config_wifiMinConfirmationDurationSendNetworkScoreEnabled)).thenReturn(true);
 
         mClock.mWallClockMillis = 10;
-        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId, 60);
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(
+                scorerImpl.mSessionId, 60);
         mWifiInfo.setRssi(-70);
         mLooper.dispatchAll();
-        verify(mNetworkAgent).sendNetworkScore(argThat(ns -> ns.getLegacyInt() == 60));
+        verifySentNetworkScore(60, times(2));
         mClock.mWallClockMillis = 3010;
         mWifiInfo.setRssi(-65);
-        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId, 59);
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(
+                scorerImpl.mSessionId, 59);
         mLooper.dispatchAll();
-        verify(mNetworkAgent).sendNetworkScore(argThat(ns -> ns.getLegacyInt() == 59));
+        verifySentNetworkScore(59);
         mClock.mWallClockMillis = 6010;
-        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId, 58);
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(
+                scorerImpl.mSessionId, 58);
         mLooper.dispatchAll();
-        verify(mNetworkAgent).sendNetworkScore(argThat(ns -> ns.getLegacyInt() == 58));
+        verifySentNetworkScore(58);
     }
 
     /**
@@ -945,36 +1210,47 @@
      */
     @Test
     public void confirmationDurationAndRssiCheckIsAddedForSendingLowScore() throws Exception {
+        assumeFalse(SdkLevel.isAtLeastS());
+        // initially called once
+        verifySentAnyNetworkScore();
         WifiConnectedNetworkScorerImpl scorerImpl = new WifiConnectedNetworkScorerImpl();
         // Register Client for verification.
         mWifiScoreReport.setWifiConnectedNetworkScorer(mAppBinder, scorerImpl);
+        verify(mExternalScoreUpdateObserverProxy).registerCallback(
+                mExternalScoreUpdateObserverCbCaptor.capture());
         when(mNetwork.getNetId()).thenReturn(TEST_NETWORK_ID);
-        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID);
+        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID, TEST_USER_SELECTED);
         mClock.mStepMillis = 0;
         when(mContext.getResources().getBoolean(
                 R.bool.config_wifiMinConfirmationDurationSendNetworkScoreEnabled)).thenReturn(true);
 
         mClock.mWallClockMillis = 10;
-        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId, 49);
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(
+                scorerImpl.mSessionId, 49);
         mLooper.dispatchAll();
-        verify(mNetworkAgent, never()).sendNetworkScore(any());
+        // still only called once
+        verifySentAnyNetworkScore();
         mClock.mWallClockMillis = 10
                 + mDeviceConfigFacade.DEFAULT_MIN_CONFIRMATION_DURATION_SEND_LOW_SCORE_MS - 1;
-        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId, 48);
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(
+                scorerImpl.mSessionId, 48);
         mLooper.dispatchAll();
-        verify(mNetworkAgent, never()).sendNetworkScore(any());
+        // still only called once
+        verifySentAnyNetworkScore();
         mClock.mWallClockMillis = 10
                 + mDeviceConfigFacade.DEFAULT_MIN_CONFIRMATION_DURATION_SEND_LOW_SCORE_MS;
         mWifiInfo.setRssi(-65);
-        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId, 47);
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(
+                scorerImpl.mSessionId, 47);
         mLooper.dispatchAll();
-        verify(mNetworkAgent, never()).sendNetworkScore(any());
+        verifySentNetworkScore(47, never());
         mClock.mWallClockMillis = 10
                 + mDeviceConfigFacade.DEFAULT_MIN_CONFIRMATION_DURATION_SEND_LOW_SCORE_MS + 3000;
         mWifiInfo.setRssi(-68);
-        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId, 46);
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(
+                scorerImpl.mSessionId, 46);
         mLooper.dispatchAll();
-        verify(mNetworkAgent).sendNetworkScore(argThat(ns -> ns.getLegacyInt() == 46));
+        verifySentNetworkScore(46);
     }
 
     /**
@@ -982,28 +1258,38 @@
      */
     @Test
     public void confirmationDurationIsNotAddedForSendingHighScore() throws Exception {
+        assumeFalse(SdkLevel.isAtLeastS());
+        // initially called once
+        verifySentAnyNetworkScore();
         WifiConnectedNetworkScorerImpl scorerImpl = new WifiConnectedNetworkScorerImpl();
         // Register Client for verification.
         mWifiScoreReport.setWifiConnectedNetworkScorer(mAppBinder, scorerImpl);
+        verify(mExternalScoreUpdateObserverProxy).registerCallback(
+                mExternalScoreUpdateObserverCbCaptor.capture());
         when(mNetwork.getNetId()).thenReturn(TEST_NETWORK_ID);
-        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID);
+        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID, TEST_USER_SELECTED);
         mClock.mStepMillis = 0;
         when(mContext.getResources().getBoolean(
                 R.bool.config_wifiMinConfirmationDurationSendNetworkScoreEnabled)).thenReturn(true);
 
         mClock.mWallClockMillis = 10;
-        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId, 49);
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(
+                scorerImpl.mSessionId, 49);
         mLooper.dispatchAll();
-        verify(mNetworkAgent, never()).sendNetworkScore(any());
+        // still only called once
+        verifySentAnyNetworkScore();
         mClock.mWallClockMillis = 3000;
         mWifiInfo.setRssi(-70);
-        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId, 51);
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(
+                scorerImpl.mSessionId, 51);
         mLooper.dispatchAll();
-        verify(mNetworkAgent).sendNetworkScore(argThat(ns -> ns.getLegacyInt() == 51));
+        verifySentNetworkScore(51);
         mClock.mWallClockMillis = 6000;
-        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId, 52);
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(
+                scorerImpl.mSessionId, 52);
         mLooper.dispatchAll();
-        verify(mNetworkAgent).sendNetworkScore(argThat(ns -> ns.getLegacyInt() == 52));
+        verifySentNetworkScore(52);
+
     }
 
     /**
@@ -1011,32 +1297,44 @@
      */
     @Test
     public void confirmationDurationIsAddedForSendingHighScore() throws Exception {
+        assumeFalse(SdkLevel.isAtLeastS());
+        // initially called once
+        verifySentAnyNetworkScore();
         WifiConnectedNetworkScorerImpl scorerImpl = new WifiConnectedNetworkScorerImpl();
         // Register Client for verification.
         mWifiScoreReport.setWifiConnectedNetworkScorer(mAppBinder, scorerImpl);
+        verify(mExternalScoreUpdateObserverProxy).registerCallback(
+                mExternalScoreUpdateObserverCbCaptor.capture());
         when(mNetwork.getNetId()).thenReturn(TEST_NETWORK_ID);
-        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID);
+        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID, TEST_USER_SELECTED);
         mClock.mStepMillis = 0;
         when(mContext.getResources().getBoolean(
                 R.bool.config_wifiMinConfirmationDurationSendNetworkScoreEnabled)).thenReturn(true);
         when(mDeviceConfigFacade.getMinConfirmationDurationSendHighScoreMs()).thenReturn(4000);
 
         mClock.mWallClockMillis = 10;
-        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId, 49);
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(
+                scorerImpl.mSessionId, 49);
         mLooper.dispatchAll();
-        verify(mNetworkAgent, never()).sendNetworkScore(any());
+        // still only called once
+        verifySentAnyNetworkScore();
         mClock.mWallClockMillis = 3000;
-        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId, 51);
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(
+                scorerImpl.mSessionId, 51);
         mLooper.dispatchAll();
-        verify(mNetworkAgent, never()).sendNetworkScore(any());
+        // still only called once
+        verifySentAnyNetworkScore();
         mClock.mWallClockMillis = 6999;
-        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId, 52);
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(
+                scorerImpl.mSessionId, 52);
         mLooper.dispatchAll();
-        verify(mNetworkAgent, never()).sendNetworkScore(any());
+        // still only called once
+        verifySentAnyNetworkScore();
         mClock.mWallClockMillis = 7000;
-        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId, 53);
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(
+                scorerImpl.mSessionId, 53);
         mLooper.dispatchAll();
-        verify(mNetworkAgent).sendNetworkScore(argThat(ns -> ns.getLegacyInt() == 53));
+        verifySentNetworkScore(53);
     }
 
     /**
@@ -1045,19 +1343,51 @@
      */
     @Test
     public void verifyNudCheckAndScoreIfToggleOffForAospScorer() throws Exception {
+        // initially called once
+        verifySentAnyNetworkScore();
+
         mWifiInfo.setFrequency(5220);
         mWifiInfo.setRssi(-85);
-        ArgumentCaptor<ContentObserver> observer = ArgumentCaptor.forClass(ContentObserver.class);
-        verify(mFrameworkFacade).registerContentObserver(
-                any(), any(), eq(true), observer.capture());
-        when(mFrameworkFacade.getIntegerSetting(any(Context.class),
-                eq(WifiScoreReport.SETTINGS_SECURE_ADAPTIVE_CONNECTIVITY_ENABLED), eq(1)))
-                .thenReturn(0);
-        observer.getValue().onChange(true);
+        when(mAdaptiveConnectivityEnabledSettingObserver.get()).thenReturn(false);
         mWifiScoreReport.calculateAndReportScore();
         assertFalse(mWifiScoreReport.shouldCheckIpLayer());
-        verify(mNetworkAgent).sendNetworkScore(argThat(score ->
-                score.getLegacyInt() == 51 && !score.isExiting() && score.isTransportPrimary()));
+
+        if (SdkLevel.isAtLeastS()) {
+            ArgumentCaptor<NetworkScore> scoreCaptor = ArgumentCaptor.forClass(NetworkScore.class);
+            verify(mNetworkAgent, times(2)).sendNetworkScore(scoreCaptor.capture());
+            NetworkScore ns = scoreCaptor.getValue();
+            assertEquals(51, ns.getLegacyInt());
+            assertFalse(ns.isExiting());
+            assertTrue(ns.isTransportPrimary());
+        } else {
+            verify(mNetworkAgent).sendNetworkScore(51);
+        }
+    }
+
+    /**
+     * Verify NUD check is not recommended and the score of 51 is sent to connectivity service
+     * when Wifi scoring is disabled.
+     */
+    @Test
+    public void verifyNudCheckAndScoreIfScoringDisabledForAospScorer() throws Exception {
+        // initially called once
+        verifySentAnyNetworkScore();
+        mWifiInfo.setFrequency(5220);
+        mWifiInfo.setRssi(-85);
+        when(mWifiSettingsStore.isWifiScoringEnabled()).thenReturn(false);
+        mWifiScoreReport.calculateAndReportScore();
+        assertFalse(mWifiScoreReport.shouldCheckIpLayer());
+
+        if (SdkLevel.isAtLeastS()) {
+            ArgumentCaptor<NetworkScore> scoreCaptor = ArgumentCaptor.forClass(NetworkScore.class);
+            verify(mNetworkAgent, times(2)).sendNetworkScore(scoreCaptor.capture());
+            NetworkScore ns = scoreCaptor.getValue();
+            assertEquals(51, ns.getLegacyInt());
+            assertFalse(ns.isExiting());
+            assertTrue(ns.isTransportPrimary());
+        } else {
+            verify(mNetworkAgent).sendNetworkScore(51);
+        }
     }
 
     /**
@@ -1066,40 +1396,236 @@
      */
     @Test
     public void verifyNudCheckAndScoreIfToggleOffForExternalScorer() throws Exception {
+        assumeFalse(SdkLevel.isAtLeastS());
+        // initially called once
+        verifySentAnyNetworkScore();
         WifiConnectedNetworkScorerImpl scorerImpl = new WifiConnectedNetworkScorerImpl();
         // Register Client for verification.
         mWifiScoreReport.setWifiConnectedNetworkScorer(mAppBinder, scorerImpl);
+        verify(mExternalScoreUpdateObserverProxy).registerCallback(
+                mExternalScoreUpdateObserverCbCaptor.capture());
         when(mNetwork.getNetId()).thenReturn(TEST_NETWORK_ID);
-        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID);
+        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID, TEST_USER_SELECTED);
         mClock.mStepMillis = 0;
         when(mContext.getResources().getBoolean(
                 R.bool.config_wifiMinConfirmationDurationSendNetworkScoreEnabled)).thenReturn(true);
         when(mDeviceConfigFacade.getMinConfirmationDurationSendHighScoreMs()).thenReturn(4000);
 
-        ArgumentCaptor<ContentObserver> observer = ArgumentCaptor.forClass(ContentObserver.class);
-        verify(mFrameworkFacade).registerContentObserver(
-                any(), any(), eq(true), observer.capture());
-        when(mFrameworkFacade.getIntegerSetting(any(Context.class),
-                eq(WifiScoreReport.SETTINGS_SECURE_ADAPTIVE_CONNECTIVITY_ENABLED), eq(1)))
-                .thenReturn(0);
-        observer.getValue().onChange(true);
+        when(mAdaptiveConnectivityEnabledSettingObserver.get()).thenReturn(false);
 
         mClock.mWallClockMillis = 10;
-        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId, 49);
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(
+                scorerImpl.mSessionId, 49);
         mLooper.dispatchAll();
-        verify(mNetworkAgent, never()).sendNetworkScore(any());
+        // still only called once
+        verifySentAnyNetworkScore();
         assertFalse(mWifiScoreReport.shouldCheckIpLayer());
 
         mClock.mWallClockMillis = 10
                 + mDeviceConfigFacade.DEFAULT_MIN_CONFIRMATION_DURATION_SEND_LOW_SCORE_MS - 1;
-        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId, 48);
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(
+                scorerImpl.mSessionId, 48);
         mLooper.dispatchAll();
-        verify(mNetworkAgent, never()).sendNetworkScore(any());
+        // still only called once
+        verifySentAnyNetworkScore();
         mClock.mWallClockMillis = 10
                 + mDeviceConfigFacade.DEFAULT_MIN_CONFIRMATION_DURATION_SEND_LOW_SCORE_MS;
-        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId, 47);
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(
+                scorerImpl.mSessionId, 47);
         mLooper.dispatchAll();
-        verify(mNetworkAgent).sendNetworkScore(argThat(score ->
-                score.getLegacyInt() == 51 && !score.isExiting() && score.isTransportPrimary()));
+        verifySentNetworkScore(51);
+    }
+
+    /**
+     * Verify NUD check is not recommended and the score of 51 is sent to connectivity service
+     * when Wifi scoring is disabled for external Wi-Fi scorer.
+     */
+    @Test
+    public void verifyNudCheckAndScoreIfScoringDisabledForExternalScorer() throws Exception {
+        assumeFalse(SdkLevel.isAtLeastS());
+        WifiConnectedNetworkScorerImpl scorerImpl = new WifiConnectedNetworkScorerImpl();
+        // Register Client for verification.
+        mWifiScoreReport.setWifiConnectedNetworkScorer(mAppBinder, scorerImpl);
+        verify(mExternalScoreUpdateObserverProxy).registerCallback(
+                mExternalScoreUpdateObserverCbCaptor.capture());
+        when(mNetwork.getNetId()).thenReturn(TEST_NETWORK_ID);
+        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID, TEST_USER_SELECTED);
+        when(mWifiSettingsStore.isWifiScoringEnabled()).thenReturn(false);
+
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(
+                scorerImpl.mSessionId, 47);
+        mLooper.dispatchAll();
+        assertFalse(mWifiScoreReport.shouldCheckIpLayer());
+        verifySentNetworkScore(51);
+    }
+
+    /**
+     * Verify that WifiScoreReport gets updated score when notifyStoreUpdate() is called by apps.
+     */
+    @Test
+    public void testFrameworkGetsNotifiedOfUpdatedScore() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        WifiConnectedNetworkScorerImpl scorerImpl = new WifiConnectedNetworkScorerImpl();
+        // Register Client for verification.
+        mWifiScoreReport.setWifiConnectedNetworkScorer(mAppBinder, scorerImpl);
+        verify(mExternalScoreUpdateObserverProxy).registerCallback(
+                mExternalScoreUpdateObserverCbCaptor.capture());
+        when(mNetwork.getNetId()).thenReturn(TEST_NETWORK_ID);
+        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID, TEST_USER_SELECTED);
+
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyScoreUpdate(
+                scorerImpl.mSessionId, 59);
+        mLooper.dispatchAll();
+        verify(mWifiMetrics).incrementWifiScoreCount(eq(TEST_IFACE_NAME), anyInt());
+    }
+
+    /**
+     * Verify that WifiScoreReport gets updated status when notifyStatusUpdate() is called by apps.
+     */
+    @Test
+    public void testFrameworkGetsNotifiedOfUpdatedStatus() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        // initially called once
+        verify(mNetworkAgent).sendNetworkScore(any());
+        WifiConnectedNetworkScorerImpl scorerImpl = new WifiConnectedNetworkScorerImpl();
+        // Register Client for verification.
+        mWifiScoreReport.setWifiConnectedNetworkScorer(mAppBinder, scorerImpl);
+        verify(mExternalScoreUpdateObserverProxy).registerCallback(
+                mExternalScoreUpdateObserverCbCaptor.capture());
+        when(mNetwork.getNetId()).thenReturn(TEST_NETWORK_ID);
+        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID, TEST_USER_SELECTED);
+
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyStatusUpdate(
+                scorerImpl.mSessionId, true);
+        mLooper.dispatchAll();
+
+        {
+            ArgumentCaptor<NetworkScore> scoreCaptor = ArgumentCaptor.forClass(NetworkScore.class);
+            verify(mNetworkAgent, times(2)).sendNetworkScore(scoreCaptor.capture());
+            NetworkScore ns = scoreCaptor.getValue();
+            assertEquals(60, ns.getLegacyInt());
+            assertFalse(ns.isExiting());
+            assertTrue(ns.isTransportPrimary());
+        }
+
+        mExternalScoreUpdateObserverCbCaptor.getValue().notifyStatusUpdate(
+                scorerImpl.mSessionId, false);
+        mLooper.dispatchAll();
+
+        {
+            ArgumentCaptor<NetworkScore> scoreCaptor = ArgumentCaptor.forClass(NetworkScore.class);
+            verify(mNetworkAgent, times(3)).sendNetworkScore(scoreCaptor.capture());
+            NetworkScore ns = scoreCaptor.getValue();
+            assertEquals(60, ns.getLegacyInt());
+            assertTrue(ns.isExiting());
+            assertTrue(ns.isTransportPrimary());
+        }
+    }
+
+    /**
+     * Verify that WifiScoreReport gets NUD request only once when requestNudOperation() is called
+     * by apps.
+     */
+    @Test
+    public void testFrameworkGetsNotifiedOfRequestedNudOperation() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        WifiConnectedNetworkScorerImpl scorerImpl = new WifiConnectedNetworkScorerImpl();
+        // Register Client for verification.
+        mWifiScoreReport.setWifiConnectedNetworkScorer(mAppBinder, scorerImpl);
+        verify(mExternalScoreUpdateObserverProxy).registerCallback(
+                mExternalScoreUpdateObserverCbCaptor.capture());
+        when(mNetwork.getNetId()).thenReturn(TEST_NETWORK_ID);
+        mClock.mStepMillis = 0;
+        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID, TEST_USER_SELECTED);
+
+        mClock.mWallClockMillis = 5001;
+        mExternalScoreUpdateObserverCbCaptor.getValue().requestNudOperation(scorerImpl.mSessionId);
+        mLooper.dispatchAll();
+        assertTrue(mWifiScoreReport.shouldCheckIpLayer());
+        assertEquals(1, mWifiScoreReport.getNudYes());
+        mWifiScoreReport.noteIpCheck();
+        // Assert NUD is triggered only once by one request.
+        assertFalse(mWifiScoreReport.shouldCheckIpLayer());
+
+        mClock.mWallClockMillis = 10000;
+        mExternalScoreUpdateObserverCbCaptor.getValue().requestNudOperation(scorerImpl.mSessionId);
+        mLooper.dispatchAll();
+        assertFalse(mWifiScoreReport.shouldCheckIpLayer());
+    }
+
+    /**
+     * Verify that blocklisting happens when blocklistCurrentBssid() is called by apps.
+     */
+    @Test
+    public void testFrameworkGetsBlocklistCurrentBssidOperation() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        WifiConnectedNetworkScorerImpl scorerImpl = new WifiConnectedNetworkScorerImpl();
+        // Register Client for verification.
+        mWifiScoreReport.setWifiConnectedNetworkScorer(mAppBinder, scorerImpl);
+        verify(mExternalScoreUpdateObserverProxy).registerCallback(
+                mExternalScoreUpdateObserverCbCaptor.capture());
+        when(mNetwork.getNetId()).thenReturn(TEST_NETWORK_ID);
+        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID, TEST_USER_SELECTED);
+
+        mExternalScoreUpdateObserverCbCaptor.getValue().blocklistCurrentBssid(
+                scorerImpl.mSessionId);
+        mLooper.dispatchAll();
+        verify(mWifiBlocklistMonitor).handleBssidConnectionFailure(any(), any(),
+                eq(WifiBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE), anyInt());
+    }
+
+    @Test
+    public void testClientNotNotifiedForLocalOnlyConnection() throws Exception {
+        when(mNetworkAgent.getCurrentNetworkCapabilities()).thenReturn(
+                new NetworkCapabilities.Builder()
+                        // no internet
+                        .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                        .build());
+        // Register Client for verification.
+        mWifiScoreReport.setWifiConnectedNetworkScorer(mAppBinder, mWifiConnectedNetworkScorer);
+        verify(mExternalScoreUpdateObserverProxy).registerCallback(
+                mExternalScoreUpdateObserverCbCaptor.capture());
+        when(mNetwork.getNetId()).thenReturn(TEST_NETWORK_ID);
+        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID, TEST_USER_SELECTED);
+        verify(mWifiConnectedNetworkScorer, never()).onStart(any());
+    }
+
+    @Test
+    public void testClientNotNotifiedForOemPaidConnection() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        when(mNetworkAgent.getCurrentNetworkCapabilities()).thenReturn(
+                new NetworkCapabilities.Builder()
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                        // oem paid
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID)
+                        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                        .build());
+        // Register Client for verification.
+        mWifiScoreReport.setWifiConnectedNetworkScorer(mAppBinder, mWifiConnectedNetworkScorer);
+        verify(mExternalScoreUpdateObserverProxy).registerCallback(
+                mExternalScoreUpdateObserverCbCaptor.capture());
+        when(mNetwork.getNetId()).thenReturn(TEST_NETWORK_ID);
+        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID, TEST_USER_SELECTED);
+        verify(mWifiConnectedNetworkScorer, never()).onStart(any());
+    }
+
+    @Test
+    public void testClientNotNotifiedForOemPrivateConnection() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        when(mNetworkAgent.getCurrentNetworkCapabilities()).thenReturn(
+                new NetworkCapabilities.Builder()
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                        // oem private
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE)
+                        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                        .build());
+        // Register Client for verification.
+        mWifiScoreReport.setWifiConnectedNetworkScorer(mAppBinder, mWifiConnectedNetworkScorer);
+        verify(mExternalScoreUpdateObserverProxy).registerCallback(
+                mExternalScoreUpdateObserverCbCaptor.capture());
+        when(mNetwork.getNetId()).thenReturn(TEST_NETWORK_ID);
+        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID, TEST_USER_SELECTED);
+        verify(mWifiConnectedNetworkScorer, never()).onStart(any());
     }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java
index 79261ed..f8f7e65 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java
@@ -17,8 +17,17 @@
 package com.android.server.wifi;
 
 import static android.Manifest.permission.ACCESS_WIFI_STATE;
+import static android.Manifest.permission.MANAGE_WIFI_COUNTRY_CODE;
+import static android.Manifest.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS;
+import static android.Manifest.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS;
+import static android.net.wifi.WifiAvailableChannel.FILTER_REGULATORY;
+import static android.net.wifi.WifiAvailableChannel.OP_MODE_STA;
 import static android.net.wifi.WifiConfiguration.METERED_OVERRIDE_METERED;
+import static android.net.wifi.WifiManager.COEX_RESTRICTION_SOFTAP;
+import static android.net.wifi.WifiManager.COEX_RESTRICTION_WIFI_AWARE;
+import static android.net.wifi.WifiManager.COEX_RESTRICTION_WIFI_DIRECT;
 import static android.net.wifi.WifiManager.DEVICE_MOBILITY_STATE_STATIONARY;
+import static android.net.wifi.WifiManager.EASY_CONNECT_CRYPTOGRAPHY_CURVE_PRIME256V1;
 import static android.net.wifi.WifiManager.IFACE_IP_MODE_CONFIGURATION_ERROR;
 import static android.net.wifi.WifiManager.IFACE_IP_MODE_LOCAL_ONLY;
 import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED;
@@ -34,9 +43,15 @@
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED;
 import static android.net.wifi.WifiManager.WIFI_STATE_DISABLED;
+import static android.net.wifi.WifiScanner.WIFI_BAND_24_GHZ;
+import static android.net.wifi.WifiScanner.WIFI_BAND_5_GHZ;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_LOCAL_ONLY;
+import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SECONDARY_LONG_LIVED;
 import static com.android.server.wifi.LocalOnlyHotspotRequestInfo.HOTSPOT_NO_ERROR;
+import static com.android.server.wifi.SelfRecovery.REASON_API_CALL;
+import static com.android.server.wifi.WifiConfigurationTestUtil.SECURITY_NONE;
 import static com.android.server.wifi.WifiSettingsConfigStore.WIFI_VERBOSE_LOGGING_ENABLED;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -48,7 +63,9 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.AdditionalAnswers.returnsSecondArg;
+import static org.mockito.AdditionalMatchers.aryEq;
 import static org.mockito.ArgumentMatchers.notNull;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Matchers.any;
@@ -81,7 +98,8 @@
 import android.Manifest;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
-import android.app.test.MockAnswerUtil;
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
+import android.bluetooth.BluetoothAdapter;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -90,12 +108,15 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.ParceledListSlice;
 import android.content.res.Resources;
+import android.net.DhcpInfo;
+import android.net.DhcpResultsParcelable;
 import android.net.MacAddress;
 import android.net.NetworkStack;
 import android.net.Uri;
+import android.net.wifi.CoexUnsafeChannel;
 import android.net.wifi.IActionListener;
+import android.net.wifi.ICoexCallback;
 import android.net.wifi.IDppCallback;
 import android.net.wifi.ILocalOnlyHotspotCallback;
 import android.net.wifi.INetworkRequestMatchCallback;
@@ -103,10 +124,14 @@
 import android.net.wifi.IOnWifiUsabilityStatsListener;
 import android.net.wifi.IScanResultsCallback;
 import android.net.wifi.ISoftApCallback;
+import android.net.wifi.ISubsystemRestartCallback;
 import android.net.wifi.ISuggestionConnectionStatusListener;
+import android.net.wifi.ISuggestionUserApprovalStatusListener;
 import android.net.wifi.ITrafficStateCallback;
 import android.net.wifi.IWifiConnectedNetworkScorer;
+import android.net.wifi.IWifiVerboseLoggingStatusChangedListener;
 import android.net.wifi.ScanResult;
+import android.net.wifi.SecurityParams;
 import android.net.wifi.SoftApConfiguration;
 import android.net.wifi.SoftApInfo;
 import android.net.wifi.WifiClient;
@@ -116,7 +141,6 @@
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiManager.LocalOnlyHotspotCallback;
-import android.net.wifi.WifiManager.SoftApCallback;
 import android.net.wifi.WifiNetworkSuggestion;
 import android.net.wifi.WifiScanner;
 import android.net.wifi.WifiSsid;
@@ -132,13 +156,13 @@
 import android.os.IBinder;
 import android.os.IPowerManager;
 import android.os.IThermalService;
-import android.os.Message;
 import android.os.Parcel;
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.WorkSource;
 import android.os.connectivity.WifiActivityEnergyInfo;
 import android.os.test.TestLooper;
 import android.telephony.CarrierConfigManager;
@@ -149,25 +173,31 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.os.PowerProfile;
-import com.android.internal.util.AsyncChannel;
+import com.android.modules.utils.ParceledListSlice;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.WifiServiceImpl.LocalOnlyRequestorCallback;
+import com.android.server.wifi.WifiServiceImpl.SoftApCallbackInternal;
+import com.android.server.wifi.coex.CoexManager;
 import com.android.server.wifi.hotspot2.PasspointManager;
 import com.android.server.wifi.hotspot2.PasspointProvisioningTestUtil;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.UserActionEvent;
+import com.android.server.wifi.util.ActionListenerWrapper;
 import com.android.server.wifi.util.ApConfigUtil;
-import com.android.server.wifi.util.WifiAsyncChannel;
+import com.android.server.wifi.util.LastCallerInfoManager;
 import com.android.server.wifi.util.WifiPermissionsUtil;
 import com.android.server.wifi.util.WifiPermissionsWrapper;
 import com.android.wifi.resources.R;
 
+import com.google.common.base.Strings;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatcher;
+import org.mockito.Captor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
-import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.MockitoSession;
 
@@ -194,6 +224,7 @@
     private static final int DEFAULT_VERBOSE_LOGGING = 0;
     private static final String ANDROID_SYSTEM_PACKAGE = "android";
     private static final String TEST_PACKAGE_NAME = "TestPackage";
+    private static final String TEST_PACKAGE_NAME_OTHER = "TestPackageOther";
     private static final String TEST_FEATURE_ID = "TestFeature";
     private static final String SYSUI_PACKAGE_NAME = "com.android.systemui";
     private static final int TEST_PID = 6789;
@@ -201,9 +232,6 @@
     private static final int TEST_UID = 1200000;
     private static final int OTHER_TEST_UID = 1300000;
     private static final int TEST_USER_HANDLE = 13;
-    private static final int TEST_TRAFFIC_STATE_CALLBACK_IDENTIFIER = 17;
-    private static final int TEST_NETWORK_REQUEST_MATCH_CALLBACK_IDENTIFIER = 234;
-    private static final int TEST_WIFI_USABILITY_STATS_LISTENER_IDENTIFIER = 2;
     private static final int TEST_WIFI_CONNECTED_NETWORK_SCORER_IDENTIFIER = 1;
     private static final String WIFI_IFACE_NAME = "wlan0";
     private static final String WIFI_IFACE_NAME2 = "wlan1";
@@ -214,17 +242,23 @@
     private static final String TEST_FRIENDLY_NAME = "testfriendlyname";
     private static final List<WifiConfiguration> TEST_WIFI_CONFIGURATION_LIST = Arrays.asList(
             WifiConfigurationTestUtil.generateWifiConfig(
-                    0, 1000000, "\"red\"", true, true, null, null),
+                    0, 1000000, "\"red\"", true, true, null, null,
+                    SECURITY_NONE),
             WifiConfigurationTestUtil.generateWifiConfig(
-                    1, 1000001, "\"green\"", true, false, "example.com", "Green"),
+                    1, 1000001, "\"green\"", true, false, "example.com", "Green",
+                    SECURITY_NONE),
             WifiConfigurationTestUtil.generateWifiConfig(
-                    2, 1200000, "\"blue\"", false, true, null, null),
+                    2, 1200000, "\"blue\"", false, true, null, null,
+                    SECURITY_NONE),
             WifiConfigurationTestUtil.generateWifiConfig(
-                    3, 1100000, "\"cyan\"", true, true, null, null),
+                    3, 1100000, "\"cyan\"", true, true, null, null,
+                    SECURITY_NONE),
             WifiConfigurationTestUtil.generateWifiConfig(
-                    4, 1100001, "\"yellow\"", true, true, "example.org", "Yellow"),
+                    4, 1100001, "\"yellow\"", true, true, "example.org", "Yellow",
+                    SECURITY_NONE),
             WifiConfigurationTestUtil.generateWifiConfig(
-                    5, 1100002, "\"magenta\"", false, false, null, null));
+                    5, 1100002, "\"magenta\"", false, false, null, null,
+                    SECURITY_NONE));
     private static final int TEST_AP_FREQUENCY = 2412;
     private static final int TEST_AP_BANDWIDTH = SoftApInfo.CHANNEL_WIDTH_20MHZ;
     private static final int NETWORK_CALLBACK_ID = 1100;
@@ -234,38 +268,42 @@
     private static final String TEST_BSSID = "01:02:03:04:05:06";
     private static final String TEST_PACKAGE = "package";
     private static final int TEST_NETWORK_ID = 567;
+    private static final WorkSource TEST_SETTINGS_WORKSOURCE = new WorkSource();
+    private static final int TEST_SUB_ID = 1;
 
     private SoftApInfo mTestSoftApInfo;
-    private AsyncChannel mAsyncChannel;
+    private List<SoftApInfo> mTestSoftApInfoList;
+    private Map<String, List<WifiClient>> mTestSoftApClients;
+    private Map<String, SoftApInfo> mTestSoftApInfos;
     private WifiServiceImpl mWifiServiceImpl;
     private TestLooper mLooper;
+    private WifiThreadRunner mWifiThreadRunner;
     private PowerManager mPowerManager;
     private PhoneStateListener mPhoneStateListener;
     private int mPid;
     private int mPid2 = Process.myPid();
     private OsuProvider mOsuProvider;
-    private SoftApCallback mStateMachineSoftApCallback;
-    private SoftApCallback mLohsApCallback;
+    private SoftApCallbackInternal mStateMachineSoftApCallback;
+    private SoftApCallbackInternal mLohsApCallback;
     private String mLohsInterfaceName;
     private ApplicationInfo mApplicationInfo;
+    private List<ClientModeManager> mClientModeManagers;
     private static final String DPP_URI = "DPP:some_dpp_uri";
+    private static final String DPP_PRODUCT_INFO = "DPP:some_dpp_uri_info";
 
     private final ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor =
             ArgumentCaptor.forClass(BroadcastReceiver.class);
-    final ArgumentCaptor<IntentFilter> mIntentFilterCaptor =
-            ArgumentCaptor.forClass(IntentFilter.class);
 
-    final ArgumentCaptor<Message> mMessageCaptor = ArgumentCaptor.forClass(Message.class);
     final ArgumentCaptor<SoftApModeConfiguration> mSoftApModeConfigCaptor =
             ArgumentCaptor.forClass(SoftApModeConfiguration.class);
-    final ArgumentCaptor<Handler> mHandlerCaptor = ArgumentCaptor.forClass(Handler.class);
 
     @Mock Context mContext;
+    @Mock Context mContextAsUser;
     @Mock WifiInjector mWifiInjector;
     @Mock WifiCountryCode mWifiCountryCode;
     @Mock Clock mClock;
     @Mock WifiTrafficPoller mWifiTrafficPoller;
-    @Mock ClientModeImpl mClientModeImpl;
+    @Mock ConcreteClientModeManager mClientModeManager;
     @Mock ActiveModeWarden mActiveModeWarden;
     @Mock HandlerThread mHandlerThread;
     @Mock Resources mResources;
@@ -301,29 +339,58 @@
     @Mock INetworkRequestMatchCallback mNetworkRequestMatchCallback;
     @Mock WifiNetworkSuggestionsManager mWifiNetworkSuggestionsManager;
     @Mock TelephonyManager mTelephonyManager;
+    @Mock CoexManager mCoexManager;
     @Mock IOnWifiUsabilityStatsListener mOnWifiUsabilityStatsListener;
     @Mock WifiConfigManager mWifiConfigManager;
-    @Mock WifiScoreReport mWifiScoreReport;
     @Mock WifiScoreCard mWifiScoreCard;
     @Mock WifiHealthMonitor mWifiHealthMonitor;
     @Mock PasspointManager mPasspointManager;
     @Mock IDppCallback mDppCallback;
-    @Mock SarManager mSarManager;
     @Mock ILocalOnlyHotspotCallback mLohsCallback;
+    @Mock ICoexCallback mCoexCallback;
     @Mock IScanResultsCallback mScanResultsCallback;
     @Mock ISuggestionConnectionStatusListener mSuggestionConnectionStatusListener;
+    @Mock ISuggestionUserApprovalStatusListener mSuggestionUserApprovalStatusListener;
     @Mock IOnWifiActivityEnergyInfoListener mOnWifiActivityEnergyInfoListener;
+    @Mock ISubsystemRestartCallback mSubsystemRestartCallback;
     @Mock IWifiConnectedNetworkScorer mWifiConnectedNetworkScorer;
     @Mock WifiSettingsConfigStore mWifiSettingsConfigStore;
     @Mock WifiScanAlwaysAvailableSettingsCompatibility mScanAlwaysAvailableSettingsCompatibility;
     @Mock PackageInfo mPackageInfo;
+    @Mock WifiConnectivityManager mWifiConnectivityManager;
+    @Mock WifiDataStall mWifiDataStall;
+    @Mock WifiNative mWifiNative;
+    @Mock ConnectHelper mConnectHelper;
+    @Mock IActionListener mActionListener;
+    @Mock WifiNetworkFactory mWifiNetworkFactory;
+    @Mock UntrustedWifiNetworkFactory mUntrustedWifiNetworkFactory;
+    @Mock OemWifiNetworkFactory mOemWifiNetworkFactory;
+    @Mock WifiDiagnostics mWifiDiagnostics;
+    @Mock WifiP2pConnection mWifiP2pConnection;
+    @Mock SimRequiredNotifier mSimRequiredNotifier;
+    @Mock WifiGlobals mWifiGlobals;
+    @Mock AdaptiveConnectivityEnabledSettingObserver mAdaptiveConnectivityEnabledSettingObserver;
+    @Mock MakeBeforeBreakManager mMakeBeforeBreakManager;
+    @Mock WifiCarrierInfoManager mWifiCarrierInfoManager;
+    @Mock OpenNetworkNotifier mOpenNetworkNotifier;
+    @Mock WifiNotificationManager mWifiNotificationManager;
+    @Mock SarManager mSarManager;
+    @Mock SelfRecovery mSelfRecovery;
+    @Mock LastCallerInfoManager mLastCallerInfoManager;
+    @Mock BuildProperties mBuildProperties;
+    @Mock LinkProbeManager mLinkProbeManager;
 
-    WifiLog mLog = new LogcatLog(TAG);
+    @Captor ArgumentCaptor<Intent> mIntentCaptor;
+    @Captor ArgumentCaptor<Runnable> mOnStoppedListenerCaptor;
+
+    WifiConfiguration mWifiConfig;
+
+    WifiLog mLog;
 
     @Before public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        mLog = spy(new LogcatLog(TAG));
         mLooper = new TestLooper();
-        mAsyncChannel = spy(new AsyncChannel());
         mApplicationInfo = new ApplicationInfo();
         mApplicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
         when(mResources.getInteger(
@@ -335,22 +402,29 @@
         when(mWifiInjector.getUserManager()).thenReturn(mUserManager);
         when(mWifiInjector.getWifiCountryCode()).thenReturn(mWifiCountryCode);
         when(mWifiInjector.getWifiMetrics()).thenReturn(mWifiMetrics);
-        when(mWifiInjector.getClientModeImpl()).thenReturn(mClientModeImpl);
-        when(mClientModeImpl.getHandler()).thenReturn(new Handler());
+        when(mWifiInjector.getWifiNetworkFactory()).thenReturn(mWifiNetworkFactory);
+        when(mWifiInjector.getUntrustedWifiNetworkFactory())
+                .thenReturn(mUntrustedWifiNetworkFactory);
+        when(mWifiInjector.getOemWifiNetworkFactory()).thenReturn(mOemWifiNetworkFactory);
+        when(mWifiInjector.getWifiDiagnostics()).thenReturn(mWifiDiagnostics);
         when(mWifiInjector.getActiveModeWarden()).thenReturn(mActiveModeWarden);
-        when(mWifiInjector.getAsyncChannelHandlerThread()).thenReturn(mHandlerThread);
         when(mWifiInjector.getWifiHandlerThread()).thenReturn(mHandlerThread);
+        when(mWifiInjector.getMakeBeforeBreakManager()).thenReturn(mMakeBeforeBreakManager);
+        when(mWifiInjector.getWifiNotificationManager()).thenReturn(mWifiNotificationManager);
+        when(mWifiInjector.getBuildProperties()).thenReturn(mBuildProperties);
+        when(mWifiInjector.getLinkProbeManager()).thenReturn(mLinkProbeManager);
         when(mHandlerThread.getThreadHandler()).thenReturn(new Handler(mLooper.getLooper()));
         when(mHandlerThread.getLooper()).thenReturn(mLooper.getLooper());
         when(mContext.getResources()).thenReturn(mResources);
         when(mContext.getContentResolver()).thenReturn(mContentResolver);
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
-        when(mPackageManager.getApplicationInfoAsUser(any(), anyInt(), any()))
-                .thenReturn(mApplicationInfo);
         when(mPackageManager.getPackageInfo(anyString(), anyInt())).thenReturn(mPackageInfo);
+        when(mPackageManager.checkSignatures(anyInt(), anyInt()))
+                .thenReturn(PackageManager.SIGNATURE_NO_MATCH);
         when(mWifiInjector.getWifiApConfigStore()).thenReturn(mWifiApConfigStore);
         doNothing().when(mFrameworkFacade).registerContentObserver(eq(mContext), any(),
                 anyBoolean(), any());
+        when(mFrameworkFacade.getSettingsWorkSource(any())).thenReturn(TEST_SETTINGS_WORKSOURCE);
         when(mContext.getSystemService(Context.ACTIVITY_SERVICE)).thenReturn(mActivityManager);
         when(mContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager);
         IPowerManager powerManagerService = mock(IPowerManager.class);
@@ -359,9 +433,8 @@
                 new PowerManager(mContext, powerManagerService, thermalService, new Handler());
         when(mContext.getSystemServiceName(PowerManager.class)).thenReturn(Context.POWER_SERVICE);
         when(mContext.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
-        WifiAsyncChannel wifiAsyncChannel = new WifiAsyncChannel("WifiServiceImplTest");
-        wifiAsyncChannel.setWifiLog(mLog);
-        when(mFrameworkFacade.makeWifiAsyncChannel(anyString())).thenReturn(wifiAsyncChannel);
+        when(mContext.createContextAsUser(eq(UserHandle.CURRENT), anyInt()))
+                .thenReturn(mContextAsUser);
         when(mWifiInjector.getFrameworkFacade()).thenReturn(mFrameworkFacade);
         when(mWifiInjector.getWifiLockManager()).thenReturn(mLockManager);
         when(mWifiInjector.getWifiMulticastLockManager()).thenReturn(mWifiMulticastLockManager);
@@ -380,21 +453,34 @@
                 .thenReturn(mWifiNetworkSuggestionsManager);
         when(mWifiInjector.makeTelephonyManager()).thenReturn(mTelephonyManager);
         when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
+        when(mWifiInjector.getCoexManager()).thenReturn(mCoexManager);
         when(mWifiInjector.getWifiConfigManager()).thenReturn(mWifiConfigManager);
         when(mWifiInjector.getPasspointManager()).thenReturn(mPasspointManager);
-        when(mClientModeImpl.getWifiScoreReport()).thenReturn(mWifiScoreReport);
+        when(mActiveModeWarden.getPrimaryClientModeManager()).thenReturn(mClientModeManager);
+        when(mClientModeManager.getInterfaceName()).thenReturn(WIFI_IFACE_NAME);
         when(mWifiInjector.getWifiScoreCard()).thenReturn(mWifiScoreCard);
         when(mWifiInjector.getWifiHealthMonitor()).thenReturn(mWifiHealthMonitor);
-        when(mWifiInjector.getSarManager()).thenReturn(mSarManager);
         when(mWifiInjector.getWifiNetworkScoreCache())
                 .thenReturn(mock(WifiNetworkScoreCache.class));
-        when(mWifiInjector.getWifiThreadRunner())
-                .thenReturn(new WifiThreadRunner(new Handler(mLooper.getLooper())));
+
+        mWifiThreadRunner = new WifiThreadRunner(new Handler(mLooper.getLooper()));
+        mWifiThreadRunner.setTimeoutsAreErrors(true);
+        when(mWifiInjector.getWifiThreadRunner()).thenReturn(mWifiThreadRunner);
+
         when(mWifiInjector.getSettingsConfigStore()).thenReturn(mWifiSettingsConfigStore);
         when(mWifiInjector.getWifiScanAlwaysAvailableSettingsCompatibility())
                 .thenReturn(mScanAlwaysAvailableSettingsCompatibility);
-        when(mClientModeImpl.syncStartSubscriptionProvisioning(anyInt(),
-                any(OsuProvider.class), any(IProvisioningCallback.class), any())).thenReturn(true);
+        when(mWifiInjector.getWifiConnectivityManager()).thenReturn(mWifiConnectivityManager);
+        when(mWifiInjector.getWifiDataStall()).thenReturn(mWifiDataStall);
+        when(mWifiInjector.getWifiNative()).thenReturn(mWifiNative);
+        when(mWifiInjector.getConnectHelper()).thenReturn(mConnectHelper);
+        when(mWifiInjector.getWifiP2pConnection()).thenReturn(mWifiP2pConnection);
+        when(mWifiInjector.getSimRequiredNotifier()).thenReturn(mSimRequiredNotifier);
+        when(mWifiInjector.getWifiGlobals()).thenReturn(mWifiGlobals);
+        when(mWifiInjector.getAdaptiveConnectivityEnabledSettingObserver())
+                .thenReturn(mAdaptiveConnectivityEnabledSettingObserver);
+        when(mClientModeManager.syncStartSubscriptionProvisioning(anyInt(),
+                any(OsuProvider.class), any(IProvisioningCallback.class))).thenReturn(true);
         // Create an OSU provider that can be provisioned via an open OSU AP
         mOsuProvider = PasspointProvisioningTestUtil.generateOsuProvider(true);
         when(mContext.getOpPackageName()).thenReturn(TEST_PACKAGE_NAME);
@@ -410,6 +496,26 @@
         when(mScanRequestProxy.startScan(anyInt(), anyString())).thenReturn(true);
         when(mLohsCallback.asBinder()).thenReturn(mock(IBinder.class));
         when(mWifiSettingsConfigStore.get(eq(WIFI_VERBOSE_LOGGING_ENABLED))).thenReturn(true);
+        when(mWifiPermissionsUtil.isSystem(anyString(), anyInt())).thenReturn(false);
+        when(mActiveModeWarden.getClientModeManagersInRoles(
+                ROLE_CLIENT_LOCAL_ONLY, ROLE_CLIENT_SECONDARY_LONG_LIVED))
+                .thenReturn(Collections.emptyList());
+        when(mWifiPermissionsUtil.doesUidBelongToCurrentUser(anyInt())).thenReturn(true);
+        when(mWifiInjector.getWifiCarrierInfoManager()).thenReturn(mWifiCarrierInfoManager);
+        when(mWifiInjector.getOpenNetworkNotifier()).thenReturn(mOpenNetworkNotifier);
+        when(mClientSoftApCallback.asBinder()).thenReturn(mAppBinder);
+        when(mAnotherSoftApCallback.asBinder()).thenReturn(mAnotherAppBinder);
+        when(mWifiInjector.getSarManager()).thenReturn(mSarManager);
+        mClientModeManagers = Arrays.asList(mClientModeManager, mock(ClientModeManager.class));
+        when(mActiveModeWarden.getClientModeManagers()).thenReturn(mClientModeManagers);
+        when(mWifiInjector.getSelfRecovery()).thenReturn(mSelfRecovery);
+        when(mWifiInjector.getLastCallerInfoManager()).thenReturn(mLastCallerInfoManager);
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(Runnable onStoppedListener) throws Throwable {
+                onStoppedListener.run();
+            }
+        }).when(mMakeBeforeBreakManager).stopAllSecondaryTransientClientModeManagers(any());
 
         mWifiServiceImpl = makeWifiServiceImpl();
         mDppCallback = new IDppCallback() {
@@ -435,6 +541,11 @@
             }
 
             @Override
+            public void onBootstrapUriGenerated(String uri) throws RemoteException {
+
+            }
+
+            @Override
             public IBinder asBinder() {
                 return null;
             }
@@ -446,6 +557,23 @@
         mTestSoftApInfo = new SoftApInfo();
         mTestSoftApInfo.setFrequency(TEST_AP_FREQUENCY);
         mTestSoftApInfo.setBandwidth(TEST_AP_BANDWIDTH);
+        mTestSoftApInfo.setApInstanceIdentifier(WIFI_IFACE_NAME);
+        when(mWifiNative.getChannelsForBand(anyInt())).thenReturn(new int[0]);
+
+        mTestSoftApInfoList = new ArrayList<>();
+        mTestSoftApInfoList.add(mTestSoftApInfo);
+
+        mTestSoftApClients = new HashMap<>();
+        mTestSoftApClients.put(WIFI_IFACE_NAME, new ArrayList<WifiClient>());
+        mTestSoftApInfos = new HashMap<>();
+        mTestSoftApInfos.put(WIFI_IFACE_NAME, mTestSoftApInfo);
+
+        mWifiConfig = new WifiConfiguration();
+        mWifiConfig.SSID = TEST_SSID;
+        mWifiConfig.networkId = TEST_NETWORK_ID;
+
+        mWifiThreadRunner.prepareForAutoDispatch();
+        setup24GhzSupported();
     }
 
     /**
@@ -456,18 +584,24 @@
         validateMockitoUsage();
     }
 
+    private void stopAutoDispatchWithDispatchAllBeforeStopAndIgnoreExceptions(TestLooper looper) {
+        looper.dispatchAll();
+        looper.stopAutoDispatchAndIgnoreExceptions();
+    }
+
     private WifiServiceImpl makeWifiServiceImpl() {
-        reset(mActiveModeWarden);
         WifiServiceImpl wifiServiceImpl =
-                new WifiServiceImpl(mContext, mWifiInjector, mAsyncChannel);
-        ArgumentCaptor<SoftApCallback> softApCallbackCaptor =
-                ArgumentCaptor.forClass(SoftApCallback.class);
-        verify(mActiveModeWarden).registerSoftApCallback(softApCallbackCaptor.capture());
+                new WifiServiceImpl(mContext, mWifiInjector);
+        ArgumentCaptor<SoftApCallbackInternal> softApCallbackCaptor =
+                ArgumentCaptor.forClass(SoftApCallbackInternal.class);
+        verify(mActiveModeWarden, atLeastOnce()).registerSoftApCallback(
+                softApCallbackCaptor.capture());
         mStateMachineSoftApCallback = softApCallbackCaptor.getValue();
-        ArgumentCaptor<SoftApCallback> lohsCallbackCaptor =
-                ArgumentCaptor.forClass(SoftApCallback.class);
+        ArgumentCaptor<SoftApCallbackInternal> lohsCallbackCaptor =
+                ArgumentCaptor.forClass(SoftApCallbackInternal.class);
         mLohsInterfaceName = WIFI_IFACE_NAME;
-        verify(mActiveModeWarden).registerLohsCallback(lohsCallbackCaptor.capture());
+        verify(mActiveModeWarden, atLeastOnce()).registerLohsCallback(
+                lohsCallbackCaptor.capture());
         mLohsApCallback = lohsCallbackCaptor.getValue();
         mLooper.dispatchAll();
         return wifiServiceImpl;
@@ -481,6 +615,8 @@
         when(mockRunner.post(any())).thenReturn(false);
 
         when(mWifiInjector.getWifiThreadRunner()).thenReturn(mockRunner);
+        // Reset mWifiCountryCode to avoid verify failure in makeWifiServiceImpl.
+        reset(mWifiCountryCode);
         return makeWifiServiceImpl();
     }
 
@@ -509,14 +645,16 @@
      */
     @Test
     public void testWifiMetricsDump() {
+        mLooper.startAutoDispatch();
         mWifiServiceImpl.dump(new FileDescriptor(), new PrintWriter(new StringWriter()),
                 new String[]{mWifiMetrics.PROTO_DUMP_ARG});
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
         verify(mWifiMetrics).setEnhancedMacRandomizationForceEnabled(anyBoolean());
         verify(mWifiMetrics).setIsScanningAlwaysEnabled(anyBoolean());
         verify(mWifiMetrics).setVerboseLoggingEnabled(anyBoolean());
         verify(mWifiMetrics)
                 .dump(any(FileDescriptor.class), any(PrintWriter.class), any(String[].class));
-        verify(mClientModeImpl, never())
+        verify(mClientModeManager, never())
                 .dump(any(FileDescriptor.class), any(PrintWriter.class), any(String[].class));
     }
 
@@ -528,32 +666,10 @@
         mLooper.startAutoDispatch();
         mWifiServiceImpl.dump(new FileDescriptor(), new PrintWriter(new StringWriter()), null);
         mLooper.stopAutoDispatchAndIgnoreExceptions();
-    }
 
-    /**
-     * Ensure that WifiServiceImpl.dump() calls
-     * {@link ClientModeImpl#updateLinkLayerStatsRssiAndScoreReport()}, then calls
-     * mWifiInjector.getClientModeImplHandler().runWithScissors() at least once before calling
-     * {@link WifiScoreReport#dump(FileDescriptor, PrintWriter, String[])}.
-     *
-     * runWithScissors() needs to be called at least once so that we know that the async call
-     * {@link ClientModeImpl#updateLinkLayerStatsRssiAndScoreReport()} has completed, since
-     * runWithScissors() blocks the current thread until the call completes, which includes all
-     * previous calls posted to that thread.
-     *
-     * This ensures that WifiScoreReport will always get updated RSSI and link layer stats before
-     * dumping during a bug report, no matter if the screen is on or not.
-     */
-    @Test
-    public void testWifiScoreReportDump() {
-        mLooper.startAutoDispatch();
-        mWifiServiceImpl.dump(new FileDescriptor(), new PrintWriter(new StringWriter()), null);
-        mLooper.stopAutoDispatchAndIgnoreExceptions();
-
-        InOrder inOrder = inOrder(mClientModeImpl, mWifiScoreReport);
-
-        inOrder.verify(mClientModeImpl).updateLinkLayerStatsRssiAndScoreReport();
-        inOrder.verify(mWifiScoreReport).dump(any(), any(), any());
+        verify(mWifiDiagnostics).captureBugReportData(
+                WifiDiagnostics.REPORT_REASON_USER_ACTION);
+        verify(mWifiDiagnostics).dump(any(), any(), any());
     }
 
     /**
@@ -569,12 +685,16 @@
 
         InOrder inorder = inOrder(mWifiMetrics);
         assertTrue(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, true));
+        mLooper.dispatchAll();
         assertTrue(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, false));
+        mLooper.dispatchAll();
         inorder.verify(mWifiMetrics).logUserActionEvent(UserActionEvent.EVENT_TOGGLE_WIFI_ON);
         inorder.verify(mWifiMetrics).incrementNumWifiToggles(eq(true), eq(true));
         inorder.verify(mWifiMetrics).logUserActionEvent(eq(UserActionEvent.EVENT_TOGGLE_WIFI_OFF),
                 anyInt());
         inorder.verify(mWifiMetrics).incrementNumWifiToggles(eq(true), eq(false));
+        verify(mLastCallerInfoManager).put(eq(LastCallerInfoManager.WIFI_ENABLED), anyInt(),
+                anyInt(), anyInt(), anyString(), eq(false));
     }
 
     /**
@@ -623,7 +743,8 @@
         when(mSettingsStore.handleWifiToggled(eq(true))).thenReturn(true);
         when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
         assertTrue(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, true));
-        verify(mActiveModeWarden).wifiToggled();
+        verify(mActiveModeWarden).wifiToggled(
+                eq(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME)));
     }
 
     /**
@@ -637,7 +758,8 @@
         when(mSettingsStore.handleWifiToggled(eq(true))).thenReturn(true);
         when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
         assertTrue(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, true));
-        verify(mActiveModeWarden).wifiToggled();
+        verify(mActiveModeWarden).wifiToggled(
+                eq(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME)));
     }
 
     /**
@@ -656,7 +778,8 @@
         when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
         assertTrue(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, true));
 
-        verify(mActiveModeWarden).wifiToggled();
+        verify(mActiveModeWarden).wifiToggled(
+                eq(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME)));
     }
 
     /**
@@ -668,13 +791,14 @@
                 .noteOp(AppOpsManager.OPSTR_CHANGE_WIFI_STATE, Process.myUid(), TEST_PACKAGE_NAME);
         when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(),
                 eq(Build.VERSION_CODES.Q), anyInt())).thenReturn(false);
-        mApplicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
+        when(mWifiPermissionsUtil.isSystem(anyString(), anyInt())).thenReturn(true);
 
         when(mSettingsStore.handleWifiToggled(eq(true))).thenReturn(true);
         when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
         assertTrue(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, true));
 
-        verify(mActiveModeWarden).wifiToggled();
+        verify(mActiveModeWarden).wifiToggled(
+                eq(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME)));
     }
 
     /**
@@ -691,7 +815,8 @@
         when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
         assertTrue(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, true));
 
-        verify(mActiveModeWarden).wifiToggled();
+        verify(mActiveModeWarden).wifiToggled(
+                eq(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME)));
     }
 
     /**
@@ -708,7 +833,7 @@
         when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
         assertFalse(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, true));
 
-        verify(mActiveModeWarden, never()).wifiToggled();
+        verify(mActiveModeWarden, never()).wifiToggled(any());
     }
 
     /**
@@ -727,7 +852,7 @@
         } catch (SecurityException e) {
 
         }
-        verify(mActiveModeWarden, never()).wifiToggled();
+        verify(mActiveModeWarden, never()).wifiToggled(any());
     }
 
     /**
@@ -743,7 +868,7 @@
         when(mSettingsStore.handleWifiToggled(eq(true))).thenReturn(true);
 
         mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, true);
-        verify(mActiveModeWarden, never()).wifiToggled();
+        verify(mActiveModeWarden, never()).wifiToggled(any());
     }
 
     /**
@@ -759,7 +884,8 @@
                 .thenReturn(PackageManager.PERMISSION_GRANTED);
 
         assertTrue(mWifiServiceImpl.setWifiEnabled(SYSUI_PACKAGE_NAME, true));
-        verify(mActiveModeWarden).wifiToggled();
+        verify(mActiveModeWarden).wifiToggled(
+                eq(new WorkSource(Binder.getCallingUid(), SYSUI_PACKAGE_NAME)));
     }
 
     /**
@@ -779,7 +905,7 @@
                 .thenReturn(PackageManager.PERMISSION_DENIED);
 
         assertFalse(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, true));
-        verify(mActiveModeWarden, never()).wifiToggled();
+        verify(mActiveModeWarden, never()).wifiToggled(any());
     }
 
     /**
@@ -819,7 +945,8 @@
                 .thenReturn(PackageManager.PERMISSION_GRANTED);
         when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
         assertTrue(mWifiServiceImpl.setWifiEnabled(SYSUI_PACKAGE_NAME, true));
-        verify(mActiveModeWarden).wifiToggled();
+        verify(mActiveModeWarden).wifiToggled(
+                eq(new WorkSource(Binder.getCallingUid(), SYSUI_PACKAGE_NAME)));
     }
 
     /**
@@ -844,7 +971,7 @@
         when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
         assertFalse(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, true));
         verify(mSettingsStore, never()).handleWifiToggled(anyBoolean());
-        verify(mActiveModeWarden, never()).wifiToggled();
+        verify(mActiveModeWarden, never()).wifiToggled(any());
     }
 
 
@@ -857,7 +984,7 @@
         when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
                 anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
         assertTrue(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, true));
-        verify(mActiveModeWarden, never()).wifiToggled();
+        verify(mActiveModeWarden, never()).wifiToggled(any());
     }
 
     /**
@@ -885,7 +1012,8 @@
                 anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
         when(mSettingsStore.handleWifiToggled(eq(false))).thenReturn(true);
         assertTrue(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, false));
-        verify(mActiveModeWarden).wifiToggled();
+        verify(mActiveModeWarden).wifiToggled(
+                eq(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME)));
     }
 
     /**
@@ -898,7 +1026,8 @@
                 anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
         when(mSettingsStore.handleWifiToggled(eq(false))).thenReturn(true);
         assertTrue(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, false));
-        verify(mActiveModeWarden).wifiToggled();
+        verify(mActiveModeWarden).wifiToggled(
+                eq(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME)));
     }
 
     /**
@@ -917,7 +1046,8 @@
         when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
         assertTrue(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, false));
 
-        verify(mActiveModeWarden).wifiToggled();
+        verify(mActiveModeWarden).wifiToggled(
+                eq(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME)));
     }
 
     /**
@@ -929,13 +1059,14 @@
                 .noteOp(AppOpsManager.OPSTR_CHANGE_WIFI_STATE, Process.myUid(), TEST_PACKAGE_NAME);
         when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(),
                 eq(Build.VERSION_CODES.Q), anyInt())).thenReturn(false);
-        mApplicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
+        when(mWifiPermissionsUtil.isSystem(anyString(), anyInt())).thenReturn(true);
 
         when(mSettingsStore.handleWifiToggled(eq(false))).thenReturn(true);
         when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
         assertTrue(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, false));
 
-        verify(mActiveModeWarden).wifiToggled();
+        verify(mActiveModeWarden).wifiToggled(
+                eq(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME)));
     }
 
 
@@ -953,7 +1084,8 @@
         when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
         assertTrue(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, false));
 
-        verify(mActiveModeWarden).wifiToggled();
+        verify(mActiveModeWarden).wifiToggled(
+                eq(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME)));
     }
 
     /**
@@ -970,7 +1102,7 @@
         when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
         assertFalse(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, false));
 
-        verify(mActiveModeWarden, never()).wifiToggled();
+        verify(mActiveModeWarden, never()).wifiToggled(any());
     }
 
     /**
@@ -982,7 +1114,7 @@
                 anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
         when(mSettingsStore.handleWifiToggled(eq(false))).thenReturn(false);
         assertTrue(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, false));
-        verify(mActiveModeWarden, never()).wifiToggled();
+        verify(mActiveModeWarden, never()).wifiToggled(any());
     }
 
     /**
@@ -1001,6 +1133,86 @@
     }
 
     /**
+     * Verify that the restartWifiSubsystem fails w/o the NETWORK_AIRPLANE_MODE permission.
+     */
+    @Test public void testRestartWifiSubsystemWithoutPermission() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+                eq(android.Manifest.permission.RESTART_WIFI_SUBSYSTEM), eq("WifiService"));
+
+        try {
+            mWifiServiceImpl.restartWifiSubsystem();
+            fail("restartWifiSubsystem should fail w/o the APM permission!");
+        } catch (SecurityException e) {
+            // empty clause
+        }
+    }
+
+    /**
+     * Verify that a call to registerSubsystemRestartCallback throws a SecurityException if the
+     * caller does not have the ACCESS_WIFI_STATE permission.
+     */
+    @Test
+    public void testRegisterSubsystemRestartThrowsSecurityExceptionOnMissingPermissions() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        doThrow(new SecurityException()).when(mContext)
+                .enforceCallingOrSelfPermission(eq(ACCESS_WIFI_STATE),
+                        eq("WifiService"));
+        try {
+            mWifiServiceImpl.registerSubsystemRestartCallback(mSubsystemRestartCallback);
+            fail("expected SecurityException");
+        } catch (SecurityException expected) { }
+    }
+
+    /**
+     * Verify that a call to unregisterSubsystemRestartCallback throws a SecurityException if the
+     * caller does not have the ACCESS_WIFI_STATE permission.
+     */
+    @Test
+    public void testUnregisterSubsystemRestartThrowsSecurityExceptionOnMissingPermissions() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        doThrow(new SecurityException()).when(mContext)
+                .enforceCallingOrSelfPermission(eq(ACCESS_WIFI_STATE),
+                        eq("WifiService"));
+        try {
+            mWifiServiceImpl.unregisterSubsystemRestartCallback(mSubsystemRestartCallback);
+            fail("expected SecurityException");
+        } catch (SecurityException expected) { }
+    }
+
+
+    /**
+     * Test register and unregister subsystem restart callback will go to ActiveModeManager;
+     */
+    @Test
+    public void testRegisterUnregisterSubsystemRestartCallback() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        when(mCoexCallback.asBinder()).thenReturn(mAppBinder);
+        mWifiServiceImpl.registerSubsystemRestartCallback(mSubsystemRestartCallback);
+        mLooper.dispatchAll();
+        verify(mActiveModeWarden).registerSubsystemRestartCallback(mSubsystemRestartCallback);
+        mWifiServiceImpl.unregisterSubsystemRestartCallback(mSubsystemRestartCallback);
+        mLooper.dispatchAll();
+        verify(mActiveModeWarden).unregisterSubsystemRestartCallback(mSubsystemRestartCallback);
+    }
+
+    /**
+     * Verify that the restartWifiSubsystem succeeds and passes correct parameters.
+     */
+    @Test
+    public void testRestartWifiSubsystem() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        when(mContext.checkPermission(eq(android.Manifest.permission.RESTART_WIFI_SUBSYSTEM),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+
+        mWifiServiceImpl.restartWifiSubsystem();
+        mLooper.dispatchAll();
+        verify(mSelfRecovery).trigger(eq(REASON_API_CALL));
+        verify(mWifiMetrics).logUserActionEvent(eq(UserActionEvent.EVENT_RESTART_WIFI_SUB_SYSTEM),
+                anyInt());
+    }
+
+    /**
      * Ensure unpermitted callers cannot write the SoftApConfiguration.
      */
     @Test
@@ -1054,9 +1266,6 @@
      */
     @Test
     public void testSetSoftApConfigurationNotSavedWithoutPermission() throws Exception {
-        doThrow(new SecurityException()).when(mContext)
-                .enforceCallingOrSelfPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
-                        eq("WifiService"));
         SoftApConfiguration apConfig = createValidSoftApConfiguration();
         try {
             mWifiServiceImpl.setSoftApConfiguration(apConfig, TEST_PACKAGE_NAME);
@@ -1068,18 +1277,30 @@
      * Ensure softap config is written when the caller has the correct permission.
      */
     @Test
-    public void testSetSoftApConfigurationSuccess() throws Exception {
-        doNothing().when(mContext)
-                .enforceCallingOrSelfPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
-                        eq("WifiService"));
+    public void testSetSoftApConfigurationSuccessWithSettingPermission() throws Exception {
+        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(true);
         SoftApConfiguration apConfig = createValidSoftApConfiguration();
 
         assertTrue(mWifiServiceImpl.setSoftApConfiguration(apConfig, TEST_PACKAGE_NAME));
         mLooper.dispatchAll();
         verify(mWifiApConfigStore).setApConfiguration(eq(apConfig));
         verify(mActiveModeWarden).updateSoftApConfiguration(apConfig);
-        verify(mContext).enforceCallingOrSelfPermission(
-                eq(android.Manifest.permission.NETWORK_SETTINGS), eq("WifiService"));
+        verify(mWifiPermissionsUtil).checkNetworkSettingsPermission(anyInt());
+    }
+
+    /**
+     * Ensure softap config is written when the caller has the correct permission.
+     */
+    @Test
+    public void testSetSoftApConfigurationSuccessWithOverridePermission() throws Exception {
+        when(mWifiPermissionsUtil.checkConfigOverridePermission(anyInt())).thenReturn(true);
+        SoftApConfiguration apConfig = createValidSoftApConfiguration();
+
+        assertTrue(mWifiServiceImpl.setSoftApConfiguration(apConfig, TEST_PACKAGE_NAME));
+        mLooper.dispatchAll();
+        verify(mWifiApConfigStore).setApConfiguration(eq(apConfig));
+        verify(mActiveModeWarden).updateSoftApConfiguration(apConfig);
+        verify(mWifiPermissionsUtil).checkConfigOverridePermission(anyInt());
     }
 
     /**
@@ -1087,14 +1308,11 @@
      */
     @Test
     public void testSetSoftApConfigurationNullConfigNotSaved() throws Exception {
-        doNothing().when(mContext)
-                .enforceCallingOrSelfPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
-                        eq("WifiService"));
+        when(mWifiPermissionsUtil.checkConfigOverridePermission(anyInt())).thenReturn(true);
         assertFalse(mWifiServiceImpl.setSoftApConfiguration(null, TEST_PACKAGE_NAME));
         verify(mWifiApConfigStore, never()).setApConfiguration(isNull(SoftApConfiguration.class));
         verify(mActiveModeWarden, never()).updateSoftApConfiguration(any());
-        verify(mContext).enforceCallingOrSelfPermission(
-                eq(android.Manifest.permission.NETWORK_SETTINGS), eq("WifiService"));
+        verify(mWifiPermissionsUtil).checkConfigOverridePermission(anyInt());
     }
 
     /**
@@ -1102,14 +1320,11 @@
      */
     @Test
     public void testSetSoftApConfigurationWithInvalidConfigNotSaved() throws Exception {
-        doNothing().when(mContext)
-                .enforceCallingOrSelfPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
-                        eq("WifiService"));
+        when(mWifiPermissionsUtil.checkConfigOverridePermission(anyInt())).thenReturn(true);
         assertFalse(mWifiServiceImpl.setSoftApConfiguration(
                 new SoftApConfiguration.Builder().build(), TEST_PACKAGE_NAME));
         verify(mWifiApConfigStore, never()).setApConfiguration(any());
-        verify(mContext).enforceCallingOrSelfPermission(
-                eq(android.Manifest.permission.NETWORK_SETTINGS), eq("WifiService"));
+        verify(mWifiPermissionsUtil).checkConfigOverridePermission(anyInt());
     }
 
     /**
@@ -1117,9 +1332,6 @@
      */
     @Test
     public void testGetSoftApConfigurationNotReturnedWithoutPermission() throws Exception {
-        doThrow(new SecurityException()).when(mContext)
-                .enforceCallingOrSelfPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
-                        eq("WifiService"));
         try {
             mWifiServiceImpl.getSoftApConfiguration();
             fail("Expected a SecurityException");
@@ -1133,9 +1345,7 @@
     @Test
     public void testGetSoftApConfigurationSuccess() throws Exception {
         when(mSettingsStore.isWifiToggleEnabled()).thenReturn(false);
-        doNothing().when(mContext)
-                .enforceCallingOrSelfPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
-                        eq("WifiService"));
+        when(mWifiPermissionsUtil.checkConfigOverridePermission(anyInt())).thenReturn(true);
         SoftApConfiguration apConfig = createValidSoftApConfiguration();
         when(mWifiApConfigStore.getApConfiguration()).thenReturn(apConfig);
 
@@ -1227,7 +1437,7 @@
         mLooper.dispatchAll();
         verify(mWifiConfigManager).loadFromStore();
         verify(mActiveModeWarden).start();
-        verify(mActiveModeWarden, never()).wifiToggled();
+        verify(mActiveModeWarden, never()).wifiToggled(any());
     }
 
     @Test
@@ -1237,7 +1447,9 @@
         mWifiServiceImpl.checkAndStartWifi();
         mLooper.dispatchAll();
         verify(mWifiConfigManager).loadFromStore();
-        verify(mClientModeImpl).enableVerboseLogging(1);
+        verify(mActiveModeWarden).enableVerboseLogging(true);
+        // show key mode is always disabled at the beginning.
+        verify(mWifiGlobals).setShowKeyVerboseLoggingModeEnabled(eq(false));
         verify(mActiveModeWarden).start();
     }
 
@@ -1248,7 +1460,7 @@
     public void testWifiFullyStartsWhenDeviceBootsWithWifiEnabled() {
         when(mSettingsStore.handleWifiToggled(true)).thenReturn(true);
         when(mSettingsStore.isWifiToggleEnabled()).thenReturn(true);
-        when(mClientModeImpl.syncGetWifiState()).thenReturn(WIFI_STATE_DISABLED);
+        when(mClientModeManager.syncGetWifiState()).thenReturn(WIFI_STATE_DISABLED);
         when(mContext.getPackageName()).thenReturn(ANDROID_SYSTEM_PACKAGE);
         when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
                 anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
@@ -1259,13 +1471,130 @@
     }
 
     /**
+     * Verify that the setCoexUnsafeChannels calls the corresponding CoexManager API if
+     * the config_wifiDefaultCoexAlgorithmEnabled is false.
+     */
+    @Test
+    public void testSetCoexUnsafeChannelsDefaultAlgorithmDisabled() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        when(mResources.getBoolean(R.bool.config_wifiDefaultCoexAlgorithmEnabled))
+                .thenReturn(false);
+        List<CoexUnsafeChannel> unsafeChannels = new ArrayList<>();
+        unsafeChannels.addAll(Arrays.asList(new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6),
+                new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 36)));
+        int coexRestrictions = COEX_RESTRICTION_SOFTAP
+                & COEX_RESTRICTION_WIFI_AWARE & COEX_RESTRICTION_WIFI_DIRECT;
+        mWifiServiceImpl.setCoexUnsafeChannels(unsafeChannels, coexRestrictions);
+        mLooper.dispatchAll();
+        verify(mCoexManager, times(1)).setCoexUnsafeChannels(any(), anyInt());
+    }
+
+    /**
+     * Verify that the setCoexUnsafeChannels does not call the corresponding CoexManager API if
+     * the config_wifiDefaultCoexAlgorithmEnabled is true.
+     */
+    @Test
+    public void testSetCoexUnsafeChannelsDefaultAlgorithmEnabled() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        when(mResources.getBoolean(R.bool.config_wifiDefaultCoexAlgorithmEnabled))
+                .thenReturn(true);
+        List<CoexUnsafeChannel> unsafeChannels = new ArrayList<>();
+        unsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6));
+        unsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 36));
+        int coexRestrictions = COEX_RESTRICTION_SOFTAP
+                & COEX_RESTRICTION_WIFI_AWARE & COEX_RESTRICTION_WIFI_DIRECT;
+        mWifiServiceImpl.setCoexUnsafeChannels(unsafeChannels, coexRestrictions);
+        mLooper.dispatchAll();
+        verify(mCoexManager, never()).setCoexUnsafeChannels(any(), anyInt());
+    }
+
+    /**
+     * Verify that setCoexUnsafeChannels throws an IllegalArgumentException if passed a null set.
+     */
+    @Test
+    public void testSetCoexUnsafeChannelsNullSet() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        try {
+            mWifiServiceImpl.setCoexUnsafeChannels(null, 0);
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+        }
+    }
+
+    /**
+     * Test register and unregister callback will go to CoexManager;
+     */
+    @Test
+    public void testRegisterUnregisterCoexCallback() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        when(mCoexCallback.asBinder()).thenReturn(mAppBinder);
+        mWifiServiceImpl.registerCoexCallback(mCoexCallback);
+        mLooper.dispatchAll();
+        verify(mCoexManager).registerRemoteCoexCallback(mCoexCallback);
+        mWifiServiceImpl.unregisterCoexCallback(mCoexCallback);
+        mLooper.dispatchAll();
+        verify(mCoexManager).unregisterRemoteCoexCallback(mCoexCallback);
+    }
+
+    /**
+     * Verify that a call to setCoexUnsafeChannels throws a SecurityException if the caller does
+     * not have the WIFI_UPDATE_COEX_UNSAFE_CHANNELS permission.
+     */
+    @Test
+    public void testSetCoexUnsafeChannelsThrowsSecurityExceptionOnMissingPermissions() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        doThrow(new SecurityException()).when(mContext)
+                .enforceCallingOrSelfPermission(eq(WIFI_UPDATE_COEX_UNSAFE_CHANNELS),
+                        eq("WifiService"));
+        try {
+            mWifiServiceImpl.setCoexUnsafeChannels(new ArrayList<>(), 0);
+            fail("expected SecurityException");
+        } catch (SecurityException expected) { }
+    }
+
+    /**
+     * Verify that a call to registerCoexCallback throws a SecurityException if the caller does
+     * not have the WIFI_ACCESS_COEX_UNSAFE_CHANNELS permission.
+     */
+    @Test
+    public void testRegisterCoexCallbackThrowsSecurityExceptionOnMissingPermissions() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        doThrow(new SecurityException()).when(mContext)
+                .enforceCallingOrSelfPermission(eq(WIFI_ACCESS_COEX_UNSAFE_CHANNELS),
+                        eq("WifiService"));
+        try {
+            mWifiServiceImpl.registerCoexCallback(mCoexCallback);
+            fail("expected SecurityException");
+        } catch (SecurityException expected) { }
+    }
+
+    /**
+     * Verify that a call to unregisterCoexCallback throws a SecurityException if the caller does
+     * not have the WIFI_ACCESS_COEX_UNSAFE_CHANNELS permission.
+     */
+    @Test
+    public void testUnregisterCoexCallbackThrowsSecurityExceptionOnMissingPermissions() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        doThrow(new SecurityException()).when(mContext)
+                .enforceCallingOrSelfPermission(eq(WIFI_ACCESS_COEX_UNSAFE_CHANNELS),
+                        eq("WifiService"));
+        try {
+            mWifiServiceImpl.unregisterCoexCallback(mCoexCallback);
+            fail("expected SecurityException");
+        } catch (SecurityException expected) { }
+    }
+
+    /**
      * Verify caller with proper permission can call startSoftAp.
      */
     @Test
     public void testStartSoftApWithPermissionsAndNullConfig() {
-        boolean result = mWifiServiceImpl.startSoftAp(null);
+        mLooper.startAutoDispatch();
+        boolean result = mWifiServiceImpl.startSoftAp(null, TEST_PACKAGE_NAME);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
         assertTrue(result);
-        verify(mActiveModeWarden).startSoftAp(mSoftApModeConfigCaptor.capture());
+        verify(mActiveModeWarden).startSoftAp(mSoftApModeConfigCaptor.capture(),
+                eq(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME)));
         assertNull(mSoftApModeConfigCaptor.getValue().getSoftApConfiguration());
     }
 
@@ -1274,9 +1603,12 @@
      */
     @Test
     public void testStartSoftApWithPermissionsAndInvalidConfig() {
-        boolean result = mWifiServiceImpl.startSoftAp(mApConfig);
+        mLooper.startAutoDispatch();
+        boolean result = mWifiServiceImpl.startSoftAp(mApConfig, TEST_PACKAGE_NAME);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
         assertFalse(result);
-        verify(mActiveModeWarden, never()).startSoftAp(any());
+        verify(mActiveModeWarden, never()).startSoftAp(any(),
+                eq(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME)));
     }
 
     /**
@@ -1285,12 +1617,18 @@
     @Test
     public void testStartSoftApWithPermissionsAndValidConfig() {
         WifiConfiguration config = createValidWifiApConfiguration();
-        boolean result = mWifiServiceImpl.startSoftAp(config);
+        mLooper.startAutoDispatch();
+        boolean result = mWifiServiceImpl.startSoftAp(config, TEST_PACKAGE_NAME);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
         assertTrue(result);
-        verify(mActiveModeWarden).startSoftAp(mSoftApModeConfigCaptor.capture());
+        verify(mActiveModeWarden).startSoftAp(
+                mSoftApModeConfigCaptor.capture(),
+                eq(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME)));
         WifiConfigurationTestUtil.assertConfigurationEqualForSoftAp(
                 config,
                 mSoftApModeConfigCaptor.getValue().getSoftApConfiguration().toWifiConfiguration());
+        verify(mLastCallerInfoManager).put(eq(LastCallerInfoManager.SOFT_AP), anyInt(),
+                anyInt(), anyInt(), anyString(), eq(true));
     }
 
     /**
@@ -1303,7 +1641,9 @@
                 .thenReturn(PackageManager.PERMISSION_DENIED);
         doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
                 eq(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK), any());
-        mWifiServiceImpl.startSoftAp(null);
+        mLooper.startAutoDispatch();
+        mWifiServiceImpl.startSoftAp(null, TEST_PACKAGE_NAME);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
     }
 
     /**
@@ -1315,9 +1655,12 @@
         when(mContext.checkCallingOrSelfPermission(android.Manifest.permission.NETWORK_STACK))
                 .thenReturn(PackageManager.PERMISSION_DENIED);
         WifiConfiguration config = createValidWifiApConfiguration();
-        boolean result = mWifiServiceImpl.startSoftAp(config);
+        mLooper.startAutoDispatch();
+        boolean result = mWifiServiceImpl.startSoftAp(config, TEST_PACKAGE_NAME);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
         assertTrue(result);
-        verify(mActiveModeWarden).startSoftAp(mSoftApModeConfigCaptor.capture());
+        verify(mActiveModeWarden).startSoftAp(mSoftApModeConfigCaptor.capture(),
+                eq(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME)));
         WifiConfigurationTestUtil.assertConfigurationEqualForSoftAp(
                 config,
                 mSoftApModeConfigCaptor.getValue().getSoftApConfiguration().toWifiConfiguration());
@@ -1331,13 +1674,18 @@
     @Test
     public void testStartSoftApWithValidConfigSucceedsAfterFailure() {
         // First initiate a failed call
-        assertFalse(mWifiServiceImpl.startSoftAp(mApConfig));
-        verify(mActiveModeWarden, never()).startSoftAp(any());
+        mLooper.startAutoDispatch();
+        assertFalse(mWifiServiceImpl.startSoftAp(mApConfig, TEST_PACKAGE_NAME));
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+        verify(mActiveModeWarden, never()).startSoftAp(any(), any());
 
         // Next attempt a valid config
         WifiConfiguration config = createValidWifiApConfiguration();
-        assertTrue(mWifiServiceImpl.startSoftAp(config));
-        verify(mActiveModeWarden).startSoftAp(mSoftApModeConfigCaptor.capture());
+        mLooper.startAutoDispatch();
+        assertTrue(mWifiServiceImpl.startSoftAp(config, TEST_PACKAGE_NAME));
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+        verify(mActiveModeWarden).startSoftAp(mSoftApModeConfigCaptor.capture(),
+                eq(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME)));
         WifiConfigurationTestUtil.assertConfigurationEqualForSoftAp(
                 config,
                 mSoftApModeConfigCaptor.getValue().getSoftApConfiguration().toWifiConfiguration());
@@ -1348,10 +1696,15 @@
      */
     @Test
     public void testStartTetheredHotspotWithPermissionsAndNullConfig() {
-        boolean result = mWifiServiceImpl.startTetheredHotspot(null);
+        mLooper.startAutoDispatch();
+        boolean result = mWifiServiceImpl.startTetheredHotspot(null, TEST_PACKAGE_NAME);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
         assertTrue(result);
-        verify(mActiveModeWarden).startSoftAp(mSoftApModeConfigCaptor.capture());
+        verify(mActiveModeWarden).startSoftAp(mSoftApModeConfigCaptor.capture(),
+                eq(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME)));
         assertNull(mSoftApModeConfigCaptor.getValue().getSoftApConfiguration());
+        verify(mLastCallerInfoManager).put(eq(LastCallerInfoManager.TETHERED_HOTSPOT), anyInt(),
+                anyInt(), anyInt(), anyString(), eq(true));
     }
 
     /**
@@ -1359,10 +1712,12 @@
      */
     @Test
     public void testStartTetheredHotspotWithPermissionsAndInvalidConfig() {
+        mLooper.startAutoDispatch();
         boolean result = mWifiServiceImpl.startTetheredHotspot(
-                new SoftApConfiguration.Builder().build());
+                new SoftApConfiguration.Builder().build(), TEST_PACKAGE_NAME);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
         assertFalse(result);
-        verify(mActiveModeWarden, never()).startSoftAp(any());
+        verify(mActiveModeWarden, never()).startSoftAp(any(), any());
     }
 
     /**
@@ -1371,18 +1726,263 @@
     @Test
     public void testStartTetheredHotspotWithPermissionsAndValidConfig() {
         SoftApConfiguration config = createValidSoftApConfiguration();
-        boolean result = mWifiServiceImpl.startTetheredHotspot(config);
+        mLooper.startAutoDispatch();
+        boolean result = mWifiServiceImpl.startTetheredHotspot(config, TEST_PACKAGE_NAME);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
         assertTrue(result);
-        verify(mActiveModeWarden).startSoftAp(mSoftApModeConfigCaptor.capture());
+        verify(mActiveModeWarden).startSoftAp(mSoftApModeConfigCaptor.capture(),
+                eq(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME)));
         assertThat(config).isEqualTo(mSoftApModeConfigCaptor.getValue().getSoftApConfiguration());
     }
 
     /**
+     * Verify isWifiBandSupported for 24GHz with an overlay override config
+     */
+    @Test
+    public void testIsWifiBandSupported24gWithOverride() throws Exception {
+        when(mResources.getBoolean(R.bool.config_wifi24ghzSupport)).thenReturn(true);
+        mLooper.startAutoDispatch();
+        assertTrue(mWifiServiceImpl.is24GHzBandSupported());
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+        verify(mWifiNative, never()).getChannelsForBand(anyInt());
+    }
+
+    /**
+     * Verify isWifiBandSupported for 5GHz with an overlay override config
+     */
+    @Test
+    public void testIsWifiBandSupported5gWithOverride() throws Exception {
+        when(mResources.getBoolean(R.bool.config_wifi5ghzSupport)).thenReturn(true);
+        mLooper.startAutoDispatch();
+        assertTrue(mWifiServiceImpl.is5GHzBandSupported());
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+        verify(mWifiNative, never()).getChannelsForBand(anyInt());
+    }
+
+    /**
+     * Verify isWifiBandSupported for 6GHz with an overlay override config
+     */
+    @Test
+    public void testIsWifiBandSupported6gWithOverride() throws Exception {
+        when(mResources.getBoolean(R.bool.config_wifi6ghzSupport)).thenReturn(true);
+        mLooper.startAutoDispatch();
+        assertTrue(mWifiServiceImpl.is6GHzBandSupported());
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+        verify(mWifiNative, never()).getChannelsForBand(anyInt());
+    }
+
+    /**
+     * Verify isWifiBandSupported for 24GHz with no overlay override config no channels
+     */
+    @Test
+    public void testIsWifiBandSupported24gNoOverrideNoChannels() throws Exception {
+        final int[] emptyArray = {};
+        when(mResources.getBoolean(R.bool.config_wifi24ghzSupport)).thenReturn(false);
+        when(mWifiNative.getChannelsForBand(anyInt())).thenReturn(emptyArray);
+        mLooper.startAutoDispatch();
+        assertFalse(mWifiServiceImpl.is24GHzBandSupported());
+        mLooper.stopAutoDispatch();
+        verify(mWifiNative).getChannelsForBand(WifiScanner.WIFI_BAND_24_GHZ);
+    }
+
+    /**
+     * Verify isWifiBandSupported for 5GHz with no overlay override config no channels
+     */
+    @Test
+    public void testIsWifiBandSupported5gNoOverrideNoChannels() throws Exception {
+        final int[] emptyArray = {};
+        when(mResources.getBoolean(R.bool.config_wifi5ghzSupport)).thenReturn(false);
+        when(mWifiNative.getChannelsForBand(anyInt())).thenReturn(emptyArray);
+        mLooper.startAutoDispatch();
+        assertFalse(mWifiServiceImpl.is5GHzBandSupported());
+        mLooper.stopAutoDispatch();
+        verify(mWifiNative).getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ);
+    }
+
+    /**
+     * Verify isWifiBandSupported for 24GHz with no overlay override config with channels
+     */
+    @Test
+    public void testIsWifiBandSupported24gNoOverrideWithChannels() throws Exception {
+        final int[] channelArray = {2412};
+        when(mResources.getBoolean(R.bool.config_wifi24ghzSupport)).thenReturn(false);
+        when(mWifiNative.getChannelsForBand(anyInt())).thenReturn(channelArray);
+        mLooper.startAutoDispatch();
+        assertTrue(mWifiServiceImpl.is24GHzBandSupported());
+        mLooper.stopAutoDispatch();
+        verify(mWifiNative).getChannelsForBand(WifiScanner.WIFI_BAND_24_GHZ);
+    }
+
+    /**
+     * Verify isWifiBandSupported for 5GHz with no overlay override config with channels
+     */
+    @Test
+    public void testIsWifiBandSupported5gNoOverrideWithChannels() throws Exception {
+        final int[] channelArray = {5170};
+        when(mResources.getBoolean(R.bool.config_wifi5ghzSupport)).thenReturn(false);
+        when(mWifiNative.getChannelsForBand(anyInt())).thenReturn(channelArray);
+        mLooper.startAutoDispatch();
+        assertTrue(mWifiServiceImpl.is5GHzBandSupported());
+        mLooper.stopAutoDispatch();
+        verify(mWifiNative).getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ);
+    }
+
+    /**
+     * Verify isWifiBandSupported for 6GHz with no overlay override config no channels
+     */
+    @Test
+    public void testIsWifiBandSupported6gNoOverrideNoChannels() throws Exception {
+        final int[] emptyArray = {};
+        when(mResources.getBoolean(R.bool.config_wifi6ghzSupport)).thenReturn(false);
+        when(mWifiNative.getChannelsForBand(anyInt())).thenReturn(emptyArray);
+        mLooper.startAutoDispatch();
+        assertFalse(mWifiServiceImpl.is6GHzBandSupported());
+        mLooper.stopAutoDispatch();
+        verify(mWifiNative).getChannelsForBand(WifiScanner.WIFI_BAND_6_GHZ);
+    }
+
+    /**
+     * Verify isWifiBandSupported for 6GHz with no overlay override config with channels
+     */
+    @Test
+    public void testIsWifiBandSupported6gNoOverrideWithChannels() throws Exception {
+        final int[] channelArray = {6420};
+        when(mResources.getBoolean(R.bool.config_wifi6ghzSupport)).thenReturn(false);
+        when(mWifiNative.getChannelsForBand(anyInt())).thenReturn(channelArray);
+        mLooper.startAutoDispatch();
+        assertTrue(mWifiServiceImpl.is6GHzBandSupported());
+        mLooper.stopAutoDispatch();
+        verify(mWifiNative).getChannelsForBand(WifiScanner.WIFI_BAND_6_GHZ);
+    }
+
+    private void setup24GhzSupported() {
+        when(mResources.getBoolean(R.bool.config_wifi24ghzSupport)).thenReturn(true);
+        when(mResources.getBoolean(R.bool.config_wifiSoftap24ghzSupported)).thenReturn(true);
+    }
+
+    private void setup24GhzUnsupported(boolean isOnlyUnsupportedSoftAp) {
+        when(mResources.getBoolean(R.bool.config_wifiSoftap24ghzSupported)).thenReturn(false);
+        if (!isOnlyUnsupportedSoftAp) {
+            when(mResources.getBoolean(R.bool.config_wifi24ghzSupport)).thenReturn(false);
+            when(mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_24_GHZ))
+                    .thenReturn(new int[0]);
+        }
+    }
+
+    private void setup5GhzSupported() {
+        when(mResources.getBoolean(R.bool.config_wifi5ghzSupport)).thenReturn(true);
+        when(mResources.getBoolean(R.bool.config_wifiSoftap5ghzSupported)).thenReturn(true);
+    }
+
+    private void setup5GhzUnsupported(boolean isOnlyUnsupportedSoftAp) {
+        when(mResources.getBoolean(R.bool.config_wifiSoftap5ghzSupported)).thenReturn(false);
+        if (!isOnlyUnsupportedSoftAp) {
+            when(mResources.getBoolean(R.bool.config_wifi5ghzSupport)).thenReturn(false);
+            when(mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ))
+                    .thenReturn(new int[0]);
+        }
+    }
+
+    private void setup6GhzSupported() {
+        when(mResources.getBoolean(R.bool.config_wifi6ghzSupport)).thenReturn(true);
+        when(mResources.getBoolean(R.bool.config_wifiSoftap6ghzSupported)).thenReturn(true);
+    }
+
+    private void setup6GhzUnsupported(boolean isOnlyUnsupportedSoftAp) {
+        when(mResources.getBoolean(R.bool.config_wifiSoftap6ghzSupported)).thenReturn(false);
+        if (!isOnlyUnsupportedSoftAp) {
+            when(mResources.getBoolean(R.bool.config_wifi6ghzSupport)).thenReturn(false);
+            when(mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_6_GHZ))
+                    .thenReturn(new int[0]);
+        }
+    }
+
+    private void setup60GhzSupported() {
+        when(mResources.getBoolean(R.bool.config_wifi60ghzSupport)).thenReturn(true);
+        when(mResources.getBoolean(R.bool.config_wifiSoftap60ghzSupported)).thenReturn(true);
+    }
+
+    private void setup60GhzUnsupported(boolean isOnlyUnsupportedSoftAp) {
+        when(mResources.getBoolean(R.bool.config_wifiSoftap60ghzSupported)).thenReturn(false);
+        if (!isOnlyUnsupportedSoftAp) {
+            when(mResources.getBoolean(R.bool.config_wifi60ghzSupport)).thenReturn(false);
+            when(mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_60_GHZ))
+                    .thenReturn(new int[0]);
+        }
+    }
+
+    /**
+     * Verify attempt to start softAp with a supported 24GHz band succeeds.
+     */
+    @Test
+    public void testStartTetheredHotspotWithSupported24gBand() {
+        setup24GhzSupported();
+
+        SoftApConfiguration config = new SoftApConfiguration.Builder()
+                .setSsid("TestAp")
+                .setPassphrase("thisIsABadPassword", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
+                .setBand(SoftApConfiguration.BAND_2GHZ)
+                .build();
+
+        mLooper.startAutoDispatch();
+        boolean result = mWifiServiceImpl.startTetheredHotspot(config, TEST_PACKAGE_NAME);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+
+        assertTrue(result);
+        verify(mActiveModeWarden).startSoftAp(mSoftApModeConfigCaptor.capture(),
+                eq(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME)));
+        assertThat(config).isEqualTo(mSoftApModeConfigCaptor.getValue().getSoftApConfiguration());
+    }
+
+    /**
+     * Verify attempt to start softAp with a non-supported 2.4GHz band fails.
+     */
+    @Test
+    public void testStartTetheredHotspotWithUnSupported24gBand() {
+        setup24GhzUnsupported(false);
+
+        SoftApConfiguration config = new SoftApConfiguration.Builder()
+                .setSsid("TestAp")
+                .setPassphrase("thisIsABadPassword", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
+                .setBand(SoftApConfiguration.BAND_2GHZ)
+                .build();
+
+        mLooper.startAutoDispatch();
+        boolean result = mWifiServiceImpl.startTetheredHotspot(config, TEST_PACKAGE_NAME);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+
+        assertFalse(result);
+        verify(mActiveModeWarden, never()).startSoftAp(any(), any());
+    }
+
+    /**
+     * Verify attempt to start softAp with a non-supported 2.4GHz band fails.
+     */
+    @Test
+    public void testStartTetheredHotspotWithUnSupportedSoftAp24gBand() {
+        setup24GhzSupported();
+        setup24GhzUnsupported(true);
+
+        SoftApConfiguration config = new SoftApConfiguration.Builder()
+                .setSsid("TestAp")
+                .setPassphrase("thisIsABadPassword", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
+                .setBand(SoftApConfiguration.BAND_2GHZ)
+                .build();
+
+        mLooper.startAutoDispatch();
+        boolean result = mWifiServiceImpl.startTetheredHotspot(config, TEST_PACKAGE_NAME);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+
+        assertFalse(result);
+        verify(mActiveModeWarden, never()).startSoftAp(any(), any());
+    }
+
+    /**
      * Verify attempt to start softAp with a supported 5GHz band succeeds.
      */
     @Test
     public void testStartTetheredHotspotWithSupported5gBand() {
-        when(mClientModeImpl.isWifiBandSupported(WifiScanner.WIFI_BAND_5_GHZ)).thenReturn(true);
+        setup5GhzSupported();
 
         SoftApConfiguration config = new SoftApConfiguration.Builder()
                 .setSsid("TestAp")
@@ -1391,10 +1991,12 @@
                 .build();
 
         mLooper.startAutoDispatch();
-        boolean result = mWifiServiceImpl.startTetheredHotspot(config);
+        boolean result = mWifiServiceImpl.startTetheredHotspot(config, TEST_PACKAGE_NAME);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
 
         assertTrue(result);
-        verify(mActiveModeWarden).startSoftAp(mSoftApModeConfigCaptor.capture());
+        verify(mActiveModeWarden).startSoftAp(mSoftApModeConfigCaptor.capture(),
+                eq(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME)));
         assertThat(config).isEqualTo(mSoftApModeConfigCaptor.getValue().getSoftApConfiguration());
     }
 
@@ -1403,7 +2005,7 @@
      */
     @Test
     public void testStartTetheredHotspotWithUnSupported5gBand() {
-        when(mClientModeImpl.isWifiBandSupported(WifiScanner.WIFI_BAND_5_GHZ)).thenReturn(false);
+        setup5GhzUnsupported(false);
 
         SoftApConfiguration config = new SoftApConfiguration.Builder()
                 .setSsid("TestAp")
@@ -1412,10 +2014,33 @@
                 .build();
 
         mLooper.startAutoDispatch();
-        boolean result = mWifiServiceImpl.startTetheredHotspot(config);
+        boolean result = mWifiServiceImpl.startTetheredHotspot(config, TEST_PACKAGE_NAME);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
 
         assertFalse(result);
-        verify(mActiveModeWarden, never()).startSoftAp(any());
+        verify(mActiveModeWarden, never()).startSoftAp(any(), any());
+    }
+
+    /**
+     * Verify attempt to start softAp with a non-supported 5GHz band fails.
+     */
+    @Test
+    public void testStartTetheredHotspotWithUnSupportedSoftAp5gBand() {
+        setup5GhzSupported();
+        setup5GhzUnsupported(true);
+
+        SoftApConfiguration config = new SoftApConfiguration.Builder()
+                .setSsid("TestAp")
+                .setPassphrase("thisIsABadPassword", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
+                .setBand(SoftApConfiguration.BAND_5GHZ)
+                .build();
+
+        mLooper.startAutoDispatch();
+        boolean result = mWifiServiceImpl.startTetheredHotspot(config, TEST_PACKAGE_NAME);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+
+        assertFalse(result);
+        verify(mActiveModeWarden, never()).startSoftAp(any(), any());
     }
 
     /**
@@ -1423,10 +2048,7 @@
      */
     @Test
     public void testStartTetheredHotspotWithSupported6gBand() {
-        when(mResources.getBoolean(
-                eq(R.bool.config_wifiSoftap6ghzSupported)))
-                .thenReturn(true);
-        when(mClientModeImpl.isWifiBandSupported(WifiScanner.WIFI_BAND_6_GHZ)).thenReturn(true);
+        setup6GhzSupported();
 
         SoftApConfiguration config = new SoftApConfiguration.Builder()
                 .setSsid("TestAp")
@@ -1435,10 +2057,12 @@
                 .build();
 
         mLooper.startAutoDispatch();
-        boolean result = mWifiServiceImpl.startTetheredHotspot(config);
+        boolean result = mWifiServiceImpl.startTetheredHotspot(config, TEST_PACKAGE_NAME);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
 
         assertTrue(result);
-        verify(mActiveModeWarden).startSoftAp(mSoftApModeConfigCaptor.capture());
+        verify(mActiveModeWarden).startSoftAp(mSoftApModeConfigCaptor.capture(),
+                eq(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME)));
         assertThat(config).isEqualTo(mSoftApModeConfigCaptor.getValue().getSoftApConfiguration());
     }
 
@@ -1447,10 +2071,7 @@
      */
     @Test
     public void testStartTetheredHotspotWithUnSupported6gBand() {
-        when(mResources.getBoolean(
-                eq(R.bool.config_wifiSoftap6ghzSupported)))
-                .thenReturn(false);
-        when(mClientModeImpl.isWifiBandSupported(WifiScanner.WIFI_BAND_6_GHZ)).thenReturn(true);
+        setup6GhzUnsupported(false);
 
         SoftApConfiguration config = new SoftApConfiguration.Builder()
                 .setSsid("TestAp")
@@ -1459,10 +2080,100 @@
                 .build();
 
         mLooper.startAutoDispatch();
-        boolean result = mWifiServiceImpl.startTetheredHotspot(config);
+        boolean result = mWifiServiceImpl.startTetheredHotspot(config, TEST_PACKAGE_NAME);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
 
         assertFalse(result);
-        verify(mActiveModeWarden, never()).startSoftAp(any());
+        verify(mActiveModeWarden, never()).startSoftAp(any(), any());
+    }
+
+    /**
+     * Verify attempt to start softAp with a non-supported 6GHz band fails.
+     */
+    @Test
+    public void testStartTetheredHotspotWithUnSupportedSoftAp6gBand() {
+        setup6GhzSupported();
+        setup6GhzUnsupported(true);
+
+        SoftApConfiguration config = new SoftApConfiguration.Builder()
+                .setSsid("TestAp")
+                .setPassphrase("thisIsABadPassword", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
+                .setBand(SoftApConfiguration.BAND_6GHZ)
+                .build();
+
+        mLooper.startAutoDispatch();
+        boolean result = mWifiServiceImpl.startTetheredHotspot(config, TEST_PACKAGE_NAME);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+
+        assertFalse(result);
+        verify(mActiveModeWarden, never()).startSoftAp(any(), any());
+    }
+
+
+    /**
+     * Verify attempt to start softAp with a supported 60GHz band succeeds.
+     */
+    @Test
+    public void testStartTetheredHotspotWithSupported60gBand() {
+        setup60GhzSupported();
+
+        SoftApConfiguration config = new SoftApConfiguration.Builder()
+                .setSsid("TestAp")
+                .setPassphrase("thisIsABadPassword", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
+                .setBand(SoftApConfiguration.BAND_60GHZ)
+                .build();
+
+        mLooper.startAutoDispatch();
+        boolean result = mWifiServiceImpl.startTetheredHotspot(config, TEST_PACKAGE_NAME);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+
+        assertTrue(result);
+        verify(mActiveModeWarden).startSoftAp(mSoftApModeConfigCaptor.capture(),
+                eq(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME)));
+        assertThat(config).isEqualTo(mSoftApModeConfigCaptor.getValue().getSoftApConfiguration());
+    }
+
+    /**
+     * Verify attempt to start softAp with a non-supported 60GHz band fails.
+     */
+    @Test
+    public void testStartTetheredHotspotWithUnSupported60gBand() {
+        setup60GhzUnsupported(false);
+
+        SoftApConfiguration config = new SoftApConfiguration.Builder()
+                .setSsid("TestAp")
+                .setPassphrase("thisIsABadPassword", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
+                .setBand(SoftApConfiguration.BAND_60GHZ)
+                .build();
+
+        mLooper.startAutoDispatch();
+        boolean result = mWifiServiceImpl.startTetheredHotspot(config, TEST_PACKAGE_NAME);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+
+        assertFalse(result);
+        verify(mActiveModeWarden, never()).startSoftAp(any(), any());
+    }
+
+    /**
+     * Verify attempt to start softAp with a non-supported 60GHz band fails.
+     */
+    @Test
+    public void testStartTetheredHotspotWithUnSupportedSoftAp60gBand() {
+        setup60GhzSupported();
+        setup60GhzUnsupported(true);
+
+        SoftApConfiguration config = new SoftApConfiguration.Builder()
+                .setSsid("TestAp")
+                .setPassphrase("thisIsABadPassword", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
+                .setBand(SoftApConfiguration.BAND_60GHZ)
+                .build();
+
+        mLooper.startAutoDispatch();
+        boolean result = mWifiServiceImpl.startTetheredHotspot(config, TEST_PACKAGE_NAME);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+
+        assertFalse(result);
+        verify(mActiveModeWarden, never()).startSoftAp(any(), any());
     }
 
     /**
@@ -1470,7 +2181,7 @@
      */
     @Test
     public void testStartTetheredHotspotWithSupportedBand() {
-        when(mClientModeImpl.isWifiBandSupported(WifiScanner.WIFI_BAND_5_GHZ)).thenReturn(true);
+        setup5GhzSupported();
 
         SoftApConfiguration config = new SoftApConfiguration.Builder()
                 .setSsid("TestAp")
@@ -1479,10 +2190,12 @@
                 .build();
 
         mLooper.startAutoDispatch();
-        boolean result = mWifiServiceImpl.startTetheredHotspot(config);
+        boolean result = mWifiServiceImpl.startTetheredHotspot(config, TEST_PACKAGE_NAME);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
 
         assertTrue(result);
-        verify(mActiveModeWarden).startSoftAp(mSoftApModeConfigCaptor.capture());
+        verify(mActiveModeWarden).startSoftAp(mSoftApModeConfigCaptor.capture(),
+                eq(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME)));
         assertThat(config).isEqualTo(mSoftApModeConfigCaptor.getValue().getSoftApConfiguration());
     }
 
@@ -1496,7 +2209,7 @@
                 .thenReturn(PackageManager.PERMISSION_DENIED);
         doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
                 eq(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK), any());
-        mWifiServiceImpl.startTetheredHotspot(null);
+        mWifiServiceImpl.startTetheredHotspot(null, TEST_PACKAGE_NAME);
     }
 
     /**
@@ -1508,9 +2221,12 @@
         when(mContext.checkCallingOrSelfPermission(android.Manifest.permission.NETWORK_STACK))
                 .thenReturn(PackageManager.PERMISSION_DENIED);
         SoftApConfiguration config = createValidSoftApConfiguration();
-        boolean result = mWifiServiceImpl.startTetheredHotspot(config);
+        mLooper.startAutoDispatch();
+        boolean result = mWifiServiceImpl.startTetheredHotspot(config, TEST_PACKAGE_NAME);
         assertTrue(result);
-        verify(mActiveModeWarden).startSoftAp(mSoftApModeConfigCaptor.capture());
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+        verify(mActiveModeWarden).startSoftAp(mSoftApModeConfigCaptor.capture(),
+                eq(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME)));
         assertThat(config).isEqualTo(mSoftApModeConfigCaptor.getValue().getSoftApConfiguration());
         verify(mContext).enforceCallingOrSelfPermission(
                 eq(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK), any());
@@ -1522,13 +2238,18 @@
     @Test
     public void testStartTetheredHotspotWithValidConfigSucceedsAfterFailedCall() {
         // First issue an invalid call
+        mLooper.startAutoDispatch();
         assertFalse(mWifiServiceImpl.startTetheredHotspot(
-                new SoftApConfiguration.Builder().build()));
-        verify(mActiveModeWarden, never()).startSoftAp(any());
+                new SoftApConfiguration.Builder().build(), TEST_PACKAGE_NAME));
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+        verify(mActiveModeWarden, never()).startSoftAp(any(), any());
 
         // Now attempt a successful call
-        assertTrue(mWifiServiceImpl.startTetheredHotspot(null));
-        verify(mActiveModeWarden).startSoftAp(mSoftApModeConfigCaptor.capture());
+        mLooper.startAutoDispatch();
+        assertTrue(mWifiServiceImpl.startTetheredHotspot(null, TEST_PACKAGE_NAME));
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+        verify(mActiveModeWarden).startSoftAp(mSoftApModeConfigCaptor.capture(),
+                eq(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME)));
         assertNull(mSoftApModeConfigCaptor.getValue().getSoftApConfiguration());
     }
 
@@ -1540,6 +2261,8 @@
         boolean result = mWifiServiceImpl.stopSoftAp();
         assertTrue(result);
         verify(mActiveModeWarden).stopSoftAp(WifiManager.IFACE_IP_MODE_TETHERED);
+        verify(mLastCallerInfoManager).put(eq(LastCallerInfoManager.SOFT_AP), anyInt(),
+                anyInt(), anyInt(), anyString(), eq(false));
     }
 
     /**
@@ -1633,19 +2356,27 @@
     @Test
     public void testConnectedIdsAreHiddenFromAppWithoutPermission() throws Exception {
         WifiInfo wifiInfo = setupForGetConnectionInfo();
-        when(mClientModeImpl.syncRequestConnectionInfo()).thenReturn(wifiInfo);
+        when(mClientModeManager.syncRequestConnectionInfo()).thenReturn(wifiInfo);
 
         doThrow(new SecurityException()).when(mWifiPermissionsUtil).enforceCanAccessScanResults(
                 anyString(), nullable(String.class), anyInt(), nullable(String.class));
 
+        mLooper.startAutoDispatch();
         WifiInfo connectionInfo = parcelingRoundTrip(
                 mWifiServiceImpl.getConnectionInfo(TEST_PACKAGE, TEST_FEATURE_ID));
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
 
         assertEquals(WifiManager.UNKNOWN_SSID, connectionInfo.getSSID());
         assertEquals(WifiInfo.DEFAULT_MAC_ADDRESS, connectionInfo.getBSSID());
         assertEquals(WifiConfiguration.INVALID_NETWORK_ID, connectionInfo.getNetworkId());
         assertNull(connectionInfo.getPasspointFqdn());
         assertNull(connectionInfo.getPasspointProviderFriendlyName());
+        if (SdkLevel.isAtLeastS()) {
+            try {
+                connectionInfo.isPrimary();
+                fail();
+            } catch (SecurityException e) { /* pass */ }
+        }
     }
 
     /**
@@ -1655,13 +2386,15 @@
     @Test
     public void testConnectedIdsAreHiddenOnSecurityException() throws Exception {
         WifiInfo wifiInfo = setupForGetConnectionInfo();
-        when(mClientModeImpl.syncRequestConnectionInfo()).thenReturn(wifiInfo);
+        when(mClientModeManager.syncRequestConnectionInfo()).thenReturn(wifiInfo);
 
         doThrow(new SecurityException()).when(mWifiPermissionsUtil).enforceCanAccessScanResults(
                 anyString(), nullable(String.class), anyInt(), nullable(String.class));
 
+        mLooper.startAutoDispatch();
         WifiInfo connectionInfo = parcelingRoundTrip(
                 mWifiServiceImpl.getConnectionInfo(TEST_PACKAGE, TEST_FEATURE_ID));
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
 
         assertEquals(WifiManager.UNKNOWN_SSID, connectionInfo.getSSID());
         assertEquals(WifiInfo.DEFAULT_MAC_ADDRESS, connectionInfo.getBSSID());
@@ -1677,14 +2410,75 @@
     @Test
     public void testConnectedIdsAreVisibleFromPermittedApp() throws Exception {
         WifiInfo wifiInfo = setupForGetConnectionInfo();
-        when(mClientModeImpl.syncRequestConnectionInfo()).thenReturn(wifiInfo);
+        when(mClientModeManager.syncRequestConnectionInfo()).thenReturn(wifiInfo);
 
+        mLooper.startAutoDispatch();
         WifiInfo connectionInfo = parcelingRoundTrip(
                 mWifiServiceImpl.getConnectionInfo(TEST_PACKAGE, TEST_FEATURE_ID));
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
 
         assertEquals(TEST_SSID_WITH_QUOTES, connectionInfo.getSSID());
         assertEquals(TEST_BSSID, connectionInfo.getBSSID());
-        assertEquals(TEST_NETWORK_ID, connectionInfo.getNetworkId());
+        assertEquals(TEST_NETWORK_ID, WifiConfigurationUtil.removeSecurityTypeFromNetworkId(
+                connectionInfo.getNetworkId()));
+        assertEquals(TEST_FQDN, connectionInfo.getPasspointFqdn());
+        assertEquals(TEST_FRIENDLY_NAME, connectionInfo.getPasspointProviderFriendlyName());
+    }
+
+    /**
+     * Test that connected SSID and BSSID for secondary CMM are exposed to an app that requests
+     * the second STA on a device that supports STA + STA.
+     */
+    @Test
+    public void testConnectedIdsFromSecondaryCmmAreVisibleFromAppRequestingSecondaryCmm()
+            throws Exception {
+        WifiInfo wifiInfo = setupForGetConnectionInfo();
+        ConcreteClientModeManager secondaryCmm = mock(ConcreteClientModeManager.class);
+        when(secondaryCmm.getRequestorWs())
+                .thenReturn(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE));
+        when(secondaryCmm.syncRequestConnectionInfo()).thenReturn(wifiInfo);
+        when(mActiveModeWarden.getClientModeManagersInRoles(
+                ROLE_CLIENT_LOCAL_ONLY, ROLE_CLIENT_SECONDARY_LONG_LIVED))
+                .thenReturn(Arrays.asList(secondaryCmm));
+
+        mLooper.startAutoDispatch();
+        WifiInfo connectionInfo = parcelingRoundTrip(
+                mWifiServiceImpl.getConnectionInfo(TEST_PACKAGE, TEST_FEATURE_ID));
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+
+        assertEquals(TEST_SSID_WITH_QUOTES, connectionInfo.getSSID());
+        assertEquals(TEST_BSSID, connectionInfo.getBSSID());
+        assertEquals(TEST_NETWORK_ID, WifiConfigurationUtil.removeSecurityTypeFromNetworkId(
+                connectionInfo.getNetworkId()));
+        assertEquals(TEST_FQDN, connectionInfo.getPasspointFqdn());
+        assertEquals(TEST_FRIENDLY_NAME, connectionInfo.getPasspointProviderFriendlyName());
+    }
+
+    /**
+     * Test that connected SSID and BSSID for primary CMM are exposed to an app that is not the one
+     * that requests the second STA on a device that supports STA + STA.
+     */
+    @Test
+    public void testConnectedIdsFromPrimaryCmmAreVisibleFromAppNotRequestingSecondaryCmm()
+            throws Exception {
+        WifiInfo wifiInfo = setupForGetConnectionInfo();
+        when(mClientModeManager.syncRequestConnectionInfo()).thenReturn(wifiInfo);
+        ConcreteClientModeManager secondaryCmm = mock(ConcreteClientModeManager.class);
+        when(secondaryCmm.getRequestorWs())
+                .thenReturn(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME_OTHER));
+        when(mActiveModeWarden.getClientModeManagersInRoles(
+                ROLE_CLIENT_LOCAL_ONLY, ROLE_CLIENT_SECONDARY_LONG_LIVED))
+                .thenReturn(Arrays.asList(secondaryCmm));
+
+        mLooper.startAutoDispatch();
+        WifiInfo connectionInfo = parcelingRoundTrip(
+                mWifiServiceImpl.getConnectionInfo(TEST_PACKAGE, TEST_FEATURE_ID));
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+
+        assertEquals(TEST_SSID_WITH_QUOTES, connectionInfo.getSSID());
+        assertEquals(TEST_BSSID, connectionInfo.getBSSID());
+        assertEquals(TEST_NETWORK_ID, WifiConfigurationUtil.removeSecurityTypeFromNetworkId(
+                connectionInfo.getNetworkId()));
         assertEquals(TEST_FQDN, connectionInfo.getPasspointFqdn());
         assertEquals(TEST_FRIENDLY_NAME, connectionInfo.getPasspointProviderFriendlyName());
     }
@@ -1703,7 +2497,7 @@
                 TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
 
         ParceledListSlice<WifiConfiguration> configs =
-                mWifiServiceImpl.getConfiguredNetworks(TEST_PACKAGE, TEST_FEATURE_ID);
+                mWifiServiceImpl.getConfiguredNetworks(TEST_PACKAGE, TEST_FEATURE_ID, false);
 
         assertEquals(0, configs.getList().size());
     }
@@ -1721,7 +2515,7 @@
                 anyString(), nullable(String.class), anyInt(), nullable(String.class));
 
         ParceledListSlice<WifiConfiguration> configs =
-                mWifiServiceImpl.getConfiguredNetworks(TEST_PACKAGE, TEST_FEATURE_ID);
+                mWifiServiceImpl.getConfiguredNetworks(TEST_PACKAGE, TEST_FEATURE_ID, false);
 
         assertEquals(0, configs.getList().size());
 
@@ -1739,11 +2533,9 @@
         when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
                 anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
 
-        mWifiServiceImpl.mClientModeImplChannel = mAsyncChannel;
-
         mLooper.startAutoDispatch();
         ParceledListSlice<WifiConfiguration> configs =
-                mWifiServiceImpl.getConfiguredNetworks(TEST_PACKAGE, TEST_FEATURE_ID);
+                mWifiServiceImpl.getConfiguredNetworks(TEST_PACKAGE, TEST_FEATURE_ID, false);
         mLooper.stopAutoDispatchAndIgnoreExceptions();
 
         verify(mWifiConfigManager).getSavedNetworks(eq(Process.WIFI_UID));
@@ -1751,6 +2543,44 @@
                 TEST_WIFI_CONFIGURATION_LIST, configs.getList());
     }
 
+    @Test(expected = SecurityException.class)
+    public void testGetCallerConfiguredNetworks_ThrowExceptionIfNotDoOrPO() {
+        when(mWifiPermissionsUtil.isDeviceOwner(Binder.getCallingUid(), TEST_PACKAGE_NAME))
+                .thenReturn(false);
+        when(mWifiPermissionsUtil.isProfileOwner(Binder.getCallingUid(), TEST_PACKAGE_NAME))
+                .thenReturn(false);
+
+        mWifiServiceImpl.getConfiguredNetworks(TEST_PACKAGE_NAME, TEST_FEATURE_ID, true);
+    }
+
+    @Test
+    public void testGetCallerConfiguredNetworks_ReturnsCallerNetworks() {
+        final int callerUid = Binder.getCallingUid();
+        WifiConfiguration callerNetwork0 = WifiConfigurationTestUtil.generateWifiConfig(
+                0, callerUid, "\"red\"", true, true, null, null, SECURITY_NONE);
+        WifiConfiguration callerNetwork1 = WifiConfigurationTestUtil.generateWifiConfig(
+                1, callerUid, "\"red\"", true, true, null, null, SECURITY_NONE);
+        WifiConfiguration nonCallerNetwork0 = WifiConfigurationTestUtil.generateWifiConfig(
+                2, 1200000, "\"blue\"", false, true, null, null, SECURITY_NONE);
+        WifiConfiguration nonCallerNetwork1 = WifiConfigurationTestUtil.generateWifiConfig(
+                3, 1100000, "\"cyan\"", true, true, null, null, SECURITY_NONE);
+        when(mWifiConfigManager.getSavedNetworks(anyInt())).thenReturn(Arrays.asList(
+                callerNetwork0, callerNetwork1, nonCallerNetwork0, nonCallerNetwork1));
+
+        // Caller does NOT need to have location permission to be able to retrieve its own networks.
+        doThrow(new SecurityException()).when(mWifiPermissionsUtil).enforceCanAccessScanResults(
+                anyString(), nullable(String.class), anyInt(), nullable(String.class));
+        when(mWifiPermissionsUtil.isProfileOwner(Binder.getCallingUid(), TEST_PACKAGE_NAME))
+                .thenReturn(true);
+
+        mLooper.startAutoDispatch();
+        ParceledListSlice<WifiConfiguration> configs =
+                mWifiServiceImpl.getConfiguredNetworks(TEST_PACKAGE_NAME, TEST_FEATURE_ID, true);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+
+        WifiConfigurationTestUtil.assertConfigurationsEqualForBackup(
+                Arrays.asList(callerNetwork0, callerNetwork1), configs.getList());
+    }
 
     /**
      * Test that privileged network list are exposed null to an app that does not have the
@@ -1983,7 +2813,12 @@
      */
     @Test
     public void testStartLocalOnlyHotspotSingleRegistrationReturnsRequestRegistered() {
+        mLooper.startAutoDispatch();
         registerLOHSRequestFull();
+        stopAutoDispatchWithDispatchAllBeforeStopAndIgnoreExceptions(mLooper);
+        // Use settings worksouce.
+        verify(mActiveModeWarden).startSoftAp(mSoftApModeConfigCaptor.capture(),
+                eq(TEST_SETTINGS_WORKSOURCE));
     }
 
     /**
@@ -2043,8 +2878,11 @@
     @Test
     public void testTetheringDoesNotStartWhenAlreadyTetheringActive() throws Exception {
         WifiConfiguration config = createValidWifiApConfiguration();
-        assertTrue(mWifiServiceImpl.startSoftAp(config));
-        verify(mActiveModeWarden).startSoftAp(mSoftApModeConfigCaptor.capture());
+        mLooper.startAutoDispatch();
+        assertTrue(mWifiServiceImpl.startSoftAp(config, TEST_PACKAGE_NAME));
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+        verify(mActiveModeWarden).startSoftAp(mSoftApModeConfigCaptor.capture(),
+                eq(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME)));
         WifiConfigurationTestUtil.assertConfigurationEqualForSoftAp(
                 config,
                 mSoftApModeConfigCaptor.getValue().getSoftApConfiguration().toWifiConfiguration());
@@ -2055,7 +2893,10 @@
         reset(mActiveModeWarden);
 
         // Start another session without a stop, that should fail.
-        assertFalse(mWifiServiceImpl.startSoftAp(createValidWifiApConfiguration()));
+        mLooper.startAutoDispatch();
+        assertFalse(mWifiServiceImpl.startSoftAp(
+                createValidWifiApConfiguration(), TEST_PACKAGE_NAME));
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
 
         verifyNoMoreInteractions(mActiveModeWarden);
     }
@@ -2066,8 +2907,11 @@
     @Test
     public void testStartTetheredHotspotDoesNotStartWhenAlreadyTetheringActive() throws Exception {
         SoftApConfiguration config = createValidSoftApConfiguration();
-        assertTrue(mWifiServiceImpl.startTetheredHotspot(config));
-        verify(mActiveModeWarden).startSoftAp(mSoftApModeConfigCaptor.capture());
+        mLooper.startAutoDispatch();
+        assertTrue(mWifiServiceImpl.startTetheredHotspot(config, TEST_PACKAGE_NAME));
+        mLooper.stopAutoDispatch();
+        verify(mActiveModeWarden).startSoftAp(mSoftApModeConfigCaptor.capture(),
+                eq(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME)));
         assertThat(config).isEqualTo(mSoftApModeConfigCaptor.getValue().getSoftApConfiguration());
         mStateMachineSoftApCallback.onStateChanged(WIFI_AP_STATE_ENABLED, 0);
         mWifiServiceImpl.updateInterfaceIpState(WIFI_IFACE_NAME, IFACE_IP_MODE_TETHERED);
@@ -2076,7 +2920,9 @@
         reset(mActiveModeWarden);
 
         // Start another session without a stop, that should fail.
-        assertFalse(mWifiServiceImpl.startTetheredHotspot(config));
+        mLooper.startAutoDispatch();
+        assertFalse(mWifiServiceImpl.startTetheredHotspot(config, TEST_PACKAGE_NAME));
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
 
         verifyNoMoreInteractions(mActiveModeWarden);
     }
@@ -2087,9 +2933,11 @@
     @Test
     public void testHotspotDoesNotStartWhenAlreadyTethering() throws Exception {
         WifiConfiguration config = createValidWifiApConfiguration();
-        assertTrue(mWifiServiceImpl.startSoftAp(config));
+        mLooper.startAutoDispatch();
+        assertTrue(mWifiServiceImpl.startSoftAp(config, TEST_PACKAGE_NAME));
         mStateMachineSoftApCallback.onStateChanged(WIFI_AP_STATE_ENABLED, 0);
         mWifiServiceImpl.updateInterfaceIpState(WIFI_IFACE_NAME, IFACE_IP_MODE_TETHERED);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
 
         when(mWifiPermissionsUtil.isLocationModeEnabled()).thenReturn(true);
         when(mFrameworkFacade.isAppForeground(any(), anyInt())).thenReturn(true);
@@ -2119,7 +2967,9 @@
      */
     @Test(expected = IllegalStateException.class)
     public void testStartLocalOnlyHotspotThrowsExceptionWhenCallerAlreadyRegistered() {
+        mLooper.startAutoDispatch();
         registerLOHSRequestFull();
+        stopAutoDispatchWithDispatchAllBeforeStopAndIgnoreExceptions(mLooper);
 
         // now do the second request that will fail
         mWifiServiceImpl.startLocalOnlyHotspot(
@@ -2145,11 +2995,12 @@
      */
     @Test
     public void testStopLocalOnlyHotspotDoesNothingWithRemainingRequest() throws Exception {
+        mLooper.startAutoDispatch();
         // register a request that will remain after the stopLOHS call
         mWifiServiceImpl.registerLOHSForTest(mPid, mRequestInfo);
 
         setupLocalOnlyHotspot();
-
+        stopAutoDispatchWithDispatchAllBeforeStopAndIgnoreExceptions(mLooper);
         // Since we are calling with the same pid, the second register call will be removed
         mWifiServiceImpl.stopLocalOnlyHotspot();
         mLooper.dispatchAll();
@@ -2165,7 +3016,7 @@
     public void testStopLocalOnlyHotspotTriggersStopWithOneRegisteredRequest() throws Exception {
         setupLocalOnlyHotspot();
 
-        verify(mActiveModeWarden).startSoftAp(any());
+        verify(mActiveModeWarden).startSoftAp(any(), any());
 
         mWifiServiceImpl.stopLocalOnlyHotspot();
         mLooper.dispatchAll();
@@ -2183,7 +3034,14 @@
      */
     @Test
     public void testStartLocalOnlyHotspotAt2Ghz() {
+        SoftApConfiguration lohsConfig = createValidSoftApConfiguration();
+        when(mWifiApConfigStore.generateLocalOnlyHotspotConfig(
+                eq(mContext), eq(SoftApConfiguration.BAND_2GHZ), eq(null))).thenReturn(lohsConfig);
+        mLooper.startAutoDispatch();
         registerLOHSRequestFull();
+        stopAutoDispatchWithDispatchAllBeforeStopAndIgnoreExceptions(mLooper);
+        verify(mWifiApConfigStore).generateLocalOnlyHotspotConfig(
+                eq(mContext), eq(SoftApConfiguration.BAND_2GHZ), eq(null));
         verifyLohsBand(SoftApConfiguration.BAND_2GHZ);
     }
 
@@ -2199,20 +3057,24 @@
         when(mResources.getBoolean(
                 eq(R.bool.config_wifiLocalOnlyHotspot6ghz)))
                 .thenReturn(true);
-        when(mClientModeImpl.isWifiBandSupported(WifiScanner.WIFI_BAND_5_GHZ)).thenReturn(true);
-        when(mClientModeImpl.isWifiBandSupported(WifiScanner.WIFI_BAND_6_GHZ)).thenReturn(true);
+        setup5GhzSupported();
+        setup6GhzSupported();
+
         when(mResources.getBoolean(
                 eq(R.bool.config_wifiSoftap6ghzSupported)))
                 .thenReturn(true);
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)).thenReturn(true);
-
-        verify(mAsyncChannel).connect(any(), mHandlerCaptor.capture(), any(Handler.class));
-        final Handler handler = mHandlerCaptor.getValue();
-        handler.handleMessage(handler.obtainMessage(
-                AsyncChannel.CMD_CHANNEL_HALF_CONNECTED, AsyncChannel.STATUS_SUCCESSFUL, 0));
-
+        SoftApConfiguration lohsConfig = createValidSoftApConfiguration();
+        SoftApConfiguration customizedConfig = new SoftApConfiguration.Builder(lohsConfig)
+                .setBand(SoftApConfiguration.BAND_6GHZ).build();
+        when(mWifiApConfigStore.generateLocalOnlyHotspotConfig(
+                eq(mContext), eq(SoftApConfiguration.BAND_6GHZ), eq(null)))
+                .thenReturn(customizedConfig);
         mLooper.startAutoDispatch();
         registerLOHSRequestFull();
+        stopAutoDispatchWithDispatchAllBeforeStopAndIgnoreExceptions(mLooper);
+        verify(mWifiApConfigStore).generateLocalOnlyHotspotConfig(
+                eq(mContext), eq(SoftApConfiguration.BAND_6GHZ), eq(null));
         verifyLohsBand(SoftApConfiguration.BAND_6GHZ);
     }
 
@@ -2225,28 +3087,35 @@
         when(mResources.getBoolean(
                 eq(R.bool.config_wifi_local_only_hotspot_5ghz)))
                 .thenReturn(true);
-        when(mResources.getBoolean(
-                eq(R.bool.config_wifiLocalOnlyHotspot6ghz)))
-                .thenReturn(true);
-        when(mClientModeImpl.isWifiBandSupported(WifiScanner.WIFI_BAND_5_GHZ)).thenReturn(true);
-        when(mClientModeImpl.isWifiBandSupported(WifiScanner.WIFI_BAND_6_GHZ)).thenReturn(true);
+
+        setup5GhzSupported();
+        setup6GhzSupported();
+
         when(mResources.getBoolean(
                 eq(R.bool.config_wifiSoftap6ghzSupported)))
                 .thenReturn(false);
+        when(mResources.getBoolean(
+                eq(R.bool.config_wifiLocalOnlyHotspot6ghz)))
+                .thenReturn(true);
+
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)).thenReturn(true);
-
-        verify(mAsyncChannel).connect(any(), mHandlerCaptor.capture(), any(Handler.class));
-        final Handler handler = mHandlerCaptor.getValue();
-        handler.handleMessage(handler.obtainMessage(
-                AsyncChannel.CMD_CHANNEL_HALF_CONNECTED, AsyncChannel.STATUS_SUCCESSFUL, 0));
-
+        SoftApConfiguration lohsConfig = createValidSoftApConfiguration();
+        SoftApConfiguration customizedConfig = new SoftApConfiguration.Builder(lohsConfig)
+                .setBand(SoftApConfiguration.BAND_5GHZ).build();
+        when(mWifiApConfigStore.generateLocalOnlyHotspotConfig(
+                eq(mContext), eq(SoftApConfiguration.BAND_5GHZ), eq(null)))
+                .thenReturn(customizedConfig);
         mLooper.startAutoDispatch();
         registerLOHSRequestFull();
+        stopAutoDispatchWithDispatchAllBeforeStopAndIgnoreExceptions(mLooper);
+        verify(mWifiApConfigStore).generateLocalOnlyHotspotConfig(
+                eq(mContext), eq(SoftApConfiguration.BAND_5GHZ), eq(null));
         verifyLohsBand(SoftApConfiguration.BAND_5GHZ);
     }
 
     private void verifyLohsBand(int expectedBand) {
-        verify(mActiveModeWarden).startSoftAp(mSoftApModeConfigCaptor.capture());
+        verify(mActiveModeWarden).startSoftAp(mSoftApModeConfigCaptor.capture(),
+                eq(TEST_SETTINGS_WORKSOURCE));
         final SoftApConfiguration configuration =
                 mSoftApModeConfigCaptor.getValue().getSoftApConfiguration();
         assertNotNull(configuration);
@@ -2288,7 +3157,7 @@
             changeLohsState(WIFI_AP_STATE_ENABLED, WIFI_AP_STATE_DISABLED, HOTSPOT_NO_ERROR);
             mWifiServiceImpl.updateInterfaceIpState(mLohsInterfaceName, IFACE_IP_MODE_LOCAL_ONLY);
             return null;
-        }).when(mActiveModeWarden).startSoftAp(any());
+        }).when(mActiveModeWarden).startSoftAp(any(), any());
     }
 
     @Test(expected = SecurityException.class)
@@ -2308,6 +3177,7 @@
 
     @Test
     public void testCustomLohs_ExclusiveAfterShared() {
+        mLooper.startAutoDispatch();
         FakeLohsCallback sharedCallback = new FakeLohsCallback();
         FakeLohsCallback exclusiveCallback = new FakeLohsCallback();
         SoftApConfiguration exclusiveConfig = new SoftApConfiguration.Builder()
@@ -2316,17 +3186,17 @@
 
         setupForCustomLohs();
         mWifiServiceImpl.registerLOHSForTest(mPid, new LocalOnlyHotspotRequestInfo(
-                sharedCallback, WifiServiceImplTest::nopDeathCallback, null));
+                new WorkSource(), sharedCallback, WifiServiceImplTest::nopDeathCallback, null));
         assertThat(mWifiServiceImpl.startLocalOnlyHotspot(exclusiveCallback, TEST_PACKAGE_NAME,
                 TEST_FEATURE_ID, exclusiveConfig)).isEqualTo(ERROR_GENERIC);
-        mLooper.dispatchAll();
-
+        stopAutoDispatchWithDispatchAllBeforeStopAndIgnoreExceptions(mLooper);
         assertThat(sharedCallback.mIsStarted).isTrue();
         assertThat(exclusiveCallback.mIsStarted).isFalse();
     }
 
     @Test
     public void testCustomLohs_ExclusiveBeforeShared() {
+        mLooper.startAutoDispatch();
         FakeLohsCallback sharedCallback = new FakeLohsCallback();
         FakeLohsCallback exclusiveCallback = new FakeLohsCallback();
         SoftApConfiguration exclusiveConfig = new SoftApConfiguration.Builder()
@@ -2335,11 +3205,11 @@
 
         setupForCustomLohs();
         mWifiServiceImpl.registerLOHSForTest(mPid, new LocalOnlyHotspotRequestInfo(
-                exclusiveCallback, WifiServiceImplTest::nopDeathCallback, exclusiveConfig));
+                new WorkSource(), exclusiveCallback, WifiServiceImplTest::nopDeathCallback,
+                exclusiveConfig));
+        stopAutoDispatchWithDispatchAllBeforeStopAndIgnoreExceptions(mLooper);
         assertThat(mWifiServiceImpl.startLocalOnlyHotspot(sharedCallback, TEST_PACKAGE_NAME,
                 TEST_FEATURE_ID, null)).isEqualTo(ERROR_GENERIC);
-        mLooper.dispatchAll();
-
         assertThat(exclusiveCallback.mIsStarted).isTrue();
         assertThat(sharedCallback.mIsStarted).isFalse();
     }
@@ -2350,14 +3220,20 @@
                 .setSsid("customSsid")
                 .setPassphrase("passphrase", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
                 .build();
+        when(mWifiApConfigStore.generateLocalOnlyHotspotConfig(
+                eq(mContext), eq(SoftApConfiguration.BAND_2GHZ), eq(config))).thenReturn(config);
         FakeLohsCallback callback = new FakeLohsCallback();
-
+        mLooper.startAutoDispatch();
         setupForCustomLohs();
         assertThat(
                 mWifiServiceImpl.startLocalOnlyHotspot(callback, TEST_PACKAGE_NAME, TEST_FEATURE_ID,
                         config)).isEqualTo(REQUEST_REGISTERED);
-        mLooper.dispatchAll();
-
+        stopAutoDispatchWithDispatchAllBeforeStopAndIgnoreExceptions(mLooper);
+        verify(mWifiApConfigStore).generateLocalOnlyHotspotConfig(
+                eq(mContext), eq(SoftApConfiguration.BAND_2GHZ), eq(config));
+        // Use app's worksouce.
+        verify(mActiveModeWarden).startSoftAp(any(),
+                eq(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME)));
         assertThat(callback.mIsStarted).isTrue();
         assertThat(callback.mSoftApConfig.getSsid()).isEqualTo("customSsid");
         assertThat(callback.mSoftApConfig.getSecurityType())
@@ -2370,14 +3246,20 @@
         SoftApConfiguration config = new SoftApConfiguration.Builder()
                 .setSsid("customSsid")
                 .build();
+        when(mWifiApConfigStore.generateLocalOnlyHotspotConfig(
+                eq(mContext), eq(SoftApConfiguration.BAND_2GHZ), eq(config))).thenReturn(config);
         FakeLohsCallback callback = new FakeLohsCallback();
-
+        mLooper.startAutoDispatch();
         setupForCustomLohs();
         assertThat(
                 mWifiServiceImpl.startLocalOnlyHotspot(callback, TEST_PACKAGE_NAME, TEST_FEATURE_ID,
                         config)).isEqualTo(REQUEST_REGISTERED);
-        mLooper.dispatchAll();
-
+        stopAutoDispatchWithDispatchAllBeforeStopAndIgnoreExceptions(mLooper);
+        verify(mWifiApConfigStore).generateLocalOnlyHotspotConfig(
+                eq(mContext), eq(SoftApConfiguration.BAND_2GHZ), eq(config));
+        // Use app's worksouce.
+        verify(mActiveModeWarden).startSoftAp(any(),
+                eq(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME)));
         assertThat(callback.mIsStarted).isTrue();
         assertThat(callback.mSoftApConfig.getSsid()).isEqualTo("customSsid");
         assertThat(callback.mSoftApConfig.getSecurityType())
@@ -2387,34 +3269,53 @@
 
     @Test
     public void testCustomLohs_GeneratesSsidIfAbsent() {
-        SoftApConfiguration config = new SoftApConfiguration.Builder()
+        SoftApConfiguration lohsConfig = createValidSoftApConfiguration();
+        SoftApConfiguration customizedConfig = new SoftApConfiguration.Builder()
                 .setPassphrase("passphrase", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
                 .build();
+        when(mWifiApConfigStore.generateLocalOnlyHotspotConfig(
+                eq(mContext), eq(SoftApConfiguration.BAND_2GHZ), eq(customizedConfig)))
+                .thenReturn(lohsConfig);
+        mLooper.startAutoDispatch();
         FakeLohsCallback callback = new FakeLohsCallback();
 
         setupForCustomLohs();
         assertThat(
                 mWifiServiceImpl.startLocalOnlyHotspot(callback, TEST_PACKAGE_NAME, TEST_FEATURE_ID,
-                        config)).isEqualTo(REQUEST_REGISTERED);
-        mLooper.dispatchAll();
-
+                        customizedConfig)).isEqualTo(REQUEST_REGISTERED);
+        stopAutoDispatchWithDispatchAllBeforeStopAndIgnoreExceptions(mLooper);
+        verify(mWifiApConfigStore).generateLocalOnlyHotspotConfig(
+                eq(mContext), eq(SoftApConfiguration.BAND_2GHZ), eq(customizedConfig));
+        // Use app's worksouce.
+        verify(mActiveModeWarden).startSoftAp(any(),
+                eq(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME)));
         assertThat(callback.mIsStarted).isTrue();
         assertThat(callback.mSoftApConfig.getSsid()).isNotEmpty();
     }
 
     @Test
     public void testCustomLohs_ForwardsBssid() {
-        SoftApConfiguration config = new SoftApConfiguration.Builder()
+        mLooper.startAutoDispatch();
+        SoftApConfiguration lohsConfig = createValidSoftApConfiguration();
+        SoftApConfiguration customizedConfig = new SoftApConfiguration.Builder(lohsConfig)
                 .setBssid(MacAddress.fromString("aa:bb:cc:dd:ee:ff"))
                 .build();
+        when(mWifiApConfigStore.generateLocalOnlyHotspotConfig(
+                eq(mContext), eq(SoftApConfiguration.BAND_2GHZ), eq(customizedConfig)))
+                .thenReturn(customizedConfig);
         FakeLohsCallback callback = new FakeLohsCallback();
 
         setupForCustomLohs();
         assertThat(
                 mWifiServiceImpl.startLocalOnlyHotspot(callback, TEST_PACKAGE_NAME, TEST_FEATURE_ID,
-                        config)).isEqualTo(REQUEST_REGISTERED);
-        mLooper.dispatchAll();
+                        customizedConfig)).isEqualTo(REQUEST_REGISTERED);
+        stopAutoDispatchWithDispatchAllBeforeStopAndIgnoreExceptions(mLooper);
 
+        // Use app's worksouce.
+        verify(mWifiApConfigStore).generateLocalOnlyHotspotConfig(
+                eq(mContext), eq(SoftApConfiguration.BAND_2GHZ), eq(customizedConfig));
+        verify(mActiveModeWarden).startSoftAp(any(),
+                eq(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME)));
         assertThat(callback.mIsStarted).isTrue();
         assertThat(callback.mSoftApConfig.getBssid().toString())
                 .ignoringCase().isEqualTo("aa:bb:cc:dd:ee:ff");
@@ -2441,6 +3342,7 @@
      */
     @Test
     public void testServiceImplNotCalledWhenBinderDeathTriggeredWithRequests() throws Exception {
+        mLooper.startAutoDispatch();
         LocalOnlyRequestorCallback binderDeathCallback =
                 mWifiServiceImpl.new LocalOnlyRequestorCallback();
 
@@ -2458,8 +3360,8 @@
         // now stop as the second request and confirm CMD_SET_AP will be sent to make sure binder
         // death requestor was removed
         mWifiServiceImpl.stopLocalOnlyHotspot();
-        mLooper.dispatchAll();
         verify(mActiveModeWarden).stopSoftAp(WifiManager.IFACE_IP_MODE_LOCAL_ONLY);
+        stopAutoDispatchWithDispatchAllBeforeStopAndIgnoreExceptions(mLooper);
     }
 
     /**
@@ -2468,13 +3370,12 @@
      */
     @Test(expected = SecurityException.class)
     public void registerSoftApCallbackThrowsSecurityExceptionOnMissingPermissions() {
-        when(mContext.checkCallingOrSelfPermission(android.Manifest.permission.NETWORK_SETTINGS))
-                .thenReturn(PackageManager.PERMISSION_DENIED);
-        when(mContext.checkCallingOrSelfPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK))
-                .thenReturn(PackageManager.PERMISSION_DENIED);
-        final int callbackIdentifier = 1;
-        mWifiServiceImpl.registerSoftApCallback(
-                mAppBinder, mClientSoftApCallback, callbackIdentifier);
+        when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_DENIED);
+        when(mContext.checkPermission(eq(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_DENIED);
+        when(mWifiPermissionsUtil.checkConfigOverridePermission(anyInt())).thenReturn(false);
+        mWifiServiceImpl.registerSoftApCallback(mClientSoftApCallback);
     }
 
     /**
@@ -2484,8 +3385,7 @@
     @Test
     public void registerSoftApCallbackThrowsIllegalArgumentExceptionOnInvalidArguments() {
         try {
-            final int callbackIdentifier = 1;
-            mWifiServiceImpl.registerSoftApCallback(mAppBinder, null, callbackIdentifier);
+            mWifiServiceImpl.registerSoftApCallback(null);
             fail("expected IllegalArgumentException");
         } catch (IllegalArgumentException expected) {
         }
@@ -2497,12 +3397,12 @@
      */
     @Test(expected = SecurityException.class)
     public void unregisterSoftApCallbackThrowsSecurityExceptionOnMissingPermissions() {
-        when(mContext.checkCallingOrSelfPermission(android.Manifest.permission.NETWORK_SETTINGS))
-                .thenReturn(PackageManager.PERMISSION_DENIED);
-        when(mContext.checkCallingOrSelfPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK))
-                .thenReturn(PackageManager.PERMISSION_DENIED);
-        final int callbackIdentifier = 1;
-        mWifiServiceImpl.unregisterSoftApCallback(callbackIdentifier);
+        when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_DENIED);
+        when(mContext.checkPermission(eq(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_DENIED);
+        when(mWifiPermissionsUtil.checkConfigOverridePermission(anyInt())).thenReturn(false);
+        mWifiServiceImpl.unregisterSoftApCallback(mClientSoftApCallback);
     }
 
     /**
@@ -2513,103 +3413,62 @@
     public void registerSoftApCallbackFailureOnLinkToDeath() throws Exception {
         doThrow(new RemoteException())
                 .when(mAppBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
-        mWifiServiceImpl.registerSoftApCallback(mAppBinder, mClientSoftApCallback, 1);
+        mWifiServiceImpl.registerSoftApCallback(mClientSoftApCallback);
         mLooper.dispatchAll();
         verify(mClientSoftApCallback, never()).onStateChanged(WIFI_AP_STATE_DISABLED, 0);
-        verify(mClientSoftApCallback, never()).onConnectedClientsChanged(any());
-        verify(mClientSoftApCallback, never()).onInfoChanged(any());
+        verify(mClientSoftApCallback, never()).onConnectedClientsOrInfoChanged(
+                any(), any(), anyBoolean(), anyBoolean());
         verify(mClientSoftApCallback, never()).onCapabilityChanged(any());
     }
 
-
     /**
      * Registers a soft AP callback, then verifies that the current soft AP state and num clients
      * are sent to caller immediately after callback is registered.
      */
-    private void registerSoftApCallbackAndVerify(ISoftApCallback callback, int callbackIdentifier)
-            throws Exception {
-        registerSoftApCallbackAndVerify(mAppBinder, callback, callbackIdentifier);
-    }
-
-    /**
-     * Registers a soft AP callback, then verifies that the current soft AP state and num clients
-     * are sent to caller immediately after callback is registered.
-     */
-    private void registerSoftApCallbackAndVerify(IBinder binder, ISoftApCallback callback,
-                                                 int callbackIdentifier) throws Exception {
-        mWifiServiceImpl.registerSoftApCallback(binder, callback, callbackIdentifier);
+    private void registerSoftApCallbackAndVerify(ISoftApCallback callback) throws Exception {
+        mWifiServiceImpl.registerSoftApCallback(callback);
         mLooper.dispatchAll();
         verify(callback).onStateChanged(WIFI_AP_STATE_DISABLED, 0);
-        verify(callback).onConnectedClientsChanged(Mockito.<WifiClient>anyList());
-        verify(callback).onInfoChanged(new SoftApInfo());
+        verify(callback).onConnectedClientsOrInfoChanged(new HashMap<String, SoftApInfo>(),
+                new HashMap<String, List<WifiClient>>(), false, true);
         verify(callback).onCapabilityChanged(ApConfigUtil.updateCapabilityFromResource(mContext));
         // Don't need to invoke callback when register.
         verify(callback, never()).onBlockedClientConnecting(any(), anyInt());
     }
 
     /**
-     * Verify that registering twice with same callbackIdentifier will replace the first callback.
-     */
-    @Test
-    public void replacesOldCallbackWithNewCallbackWhenRegisteringTwice() throws Exception {
-        final int callbackIdentifier = 1;
-        registerSoftApCallbackAndVerify(mAppBinder, mClientSoftApCallback, callbackIdentifier);
-        registerSoftApCallbackAndVerify(
-                mAnotherAppBinder, mAnotherSoftApCallback, callbackIdentifier);
-
-        verify(mAppBinder).linkToDeath(any(), anyInt());
-        verify(mAppBinder).unlinkToDeath(any(), anyInt());
-        verify(mAnotherAppBinder).linkToDeath(any(), anyInt());
-        verify(mAnotherAppBinder, never()).unlinkToDeath(any(), anyInt());
-
-        final WifiClient testClient = new WifiClient(TEST_FACTORY_MAC_ADDR);
-        final List<WifiClient> testClients = new ArrayList();
-        testClients.add(testClient);
-        mStateMachineSoftApCallback.onConnectedClientsChanged(testClients);
-        mLooper.dispatchAll();
-        // Verify only the second callback is being called
-        verify(mClientSoftApCallback, never()).onConnectedClientsChanged(testClients);
-        verify(mAnotherSoftApCallback).onConnectedClientsChanged(testClients);
-    }
-
-    /**
      * Verify that unregisterSoftApCallback removes callback from registered callbacks list
      */
     @Test
     public void unregisterSoftApCallbackRemovesCallback() throws Exception {
-        final int callbackIdentifier = 1;
-        registerSoftApCallbackAndVerify(mClientSoftApCallback, callbackIdentifier);
+        registerSoftApCallbackAndVerify(mClientSoftApCallback);
 
-        mWifiServiceImpl.unregisterSoftApCallback(callbackIdentifier);
+        mWifiServiceImpl.unregisterSoftApCallback(mClientSoftApCallback);
         mLooper.dispatchAll();
 
-        final WifiClient testClient = new WifiClient(TEST_FACTORY_MAC_ADDR);
-        final List<WifiClient> testClients = new ArrayList();
-        testClients.add(testClient);
-        mStateMachineSoftApCallback.onConnectedClientsChanged(testClients);
+        reset(mClientSoftApCallback);
+        mStateMachineSoftApCallback.onConnectedClientsOrInfoChanged(
+                mTestSoftApInfos, mTestSoftApClients, false);
         mLooper.dispatchAll();
-        verify(mClientSoftApCallback, never()).onConnectedClientsChanged(testClients);
+        verify(mClientSoftApCallback, never()).onConnectedClientsOrInfoChanged(
+                any(), any(), anyBoolean(), anyBoolean());
     }
 
     /**
-     * Verify that unregisterSoftApCallback is no-op if callbackIdentifier not registered.
+     * Verify that unregisterSoftApCallback is no-op if callback not registered.
      */
     @Test
-    public void unregisterSoftApCallbackDoesNotRemoveCallbackIfCallbackIdentifierNotMatching()
+    public void unregisterSoftApCallbackDoesNotRemoveCallbackIfCallbackNotMatching()
             throws Exception {
-        final int callbackIdentifier = 1;
-        registerSoftApCallbackAndVerify(mClientSoftApCallback, callbackIdentifier);
+        registerSoftApCallbackAndVerify(mClientSoftApCallback);
 
-        final int differentCallbackIdentifier = 2;
-        mWifiServiceImpl.unregisterSoftApCallback(differentCallbackIdentifier);
+        mWifiServiceImpl.unregisterSoftApCallback(mAnotherSoftApCallback);
         mLooper.dispatchAll();
-
-        final WifiClient testClient = new WifiClient(TEST_FACTORY_MAC_ADDR);
-        final List<WifiClient> testClients = new ArrayList();
-        testClients.add(testClient);
-        mStateMachineSoftApCallback.onConnectedClientsChanged(testClients);
+        mStateMachineSoftApCallback.onConnectedClientsOrInfoChanged(
+                mTestSoftApInfos, mTestSoftApClients, false);
         mLooper.dispatchAll();
-        verify(mClientSoftApCallback).onConnectedClientsChanged(testClients);
+        verify(mClientSoftApCallback).onConnectedClientsOrInfoChanged(
+                eq(mTestSoftApInfos), eq(mTestSoftApClients), eq(false), eq(false));
     }
 
     /**
@@ -2617,34 +3476,33 @@
      */
     @Test
     public void correctCallbackIsCalledAfterAddingTwoCallbacksAndRemovingOne() throws Exception {
-        final int callbackIdentifier = 1;
-        WifiClient testWifiClient = new WifiClient(MacAddress.fromString("22:33:44:55:66:77"));
-        mWifiServiceImpl.registerSoftApCallback(mAppBinder, mClientSoftApCallback,
-                callbackIdentifier);
+        WifiClient testWifiClient = new WifiClient(MacAddress.fromString("22:33:44:55:66:77"),
+                WIFI_IFACE_NAME2);
+        mWifiServiceImpl.registerSoftApCallback(mClientSoftApCallback);
         mLooper.dispatchAll();
 
+        reset(mClientSoftApCallback);
+        when(mClientSoftApCallback.asBinder()).thenReturn(mAppBinder);
         // Change state from default before registering the second callback
-        final List<WifiClient> testClients = new ArrayList();
         mStateMachineSoftApCallback.onStateChanged(WIFI_AP_STATE_ENABLED, 0);
-        mStateMachineSoftApCallback.onConnectedClientsChanged(testClients);
-        mStateMachineSoftApCallback.onInfoChanged(mTestSoftApInfo);
+        mStateMachineSoftApCallback.onConnectedClientsOrInfoChanged(
+                mTestSoftApInfos, mTestSoftApClients, false);
         mStateMachineSoftApCallback.onBlockedClientConnecting(testWifiClient, 0);
 
 
         // Register another callback and verify the new state is returned in the immediate callback
-        final int anotherUid = 2;
-        mWifiServiceImpl.registerSoftApCallback(mAppBinder, mAnotherSoftApCallback, anotherUid);
+        mWifiServiceImpl.registerSoftApCallback(mAnotherSoftApCallback);
         mLooper.dispatchAll();
         verify(mAnotherSoftApCallback).onStateChanged(WIFI_AP_STATE_ENABLED, 0);
-        verify(mAnotherSoftApCallback).onConnectedClientsChanged(testClients);
-        verify(mAnotherSoftApCallback).onInfoChanged(mTestSoftApInfo);
+        verify(mAnotherSoftApCallback).onConnectedClientsOrInfoChanged(
+                eq(mTestSoftApInfos), eq(mTestSoftApClients), eq(false), eq(true));
         // Verify only first callback will receive onBlockedClientConnecting since it call after
         // first callback register but before another callback register.
         verify(mClientSoftApCallback).onBlockedClientConnecting(testWifiClient, 0);
         verify(mAnotherSoftApCallback, never()).onBlockedClientConnecting(testWifiClient, 0);
 
         // unregister the fisrt callback
-        mWifiServiceImpl.unregisterSoftApCallback(callbackIdentifier);
+        mWifiServiceImpl.unregisterSoftApCallback(mClientSoftApCallback);
         mLooper.dispatchAll();
 
         // Update soft AP state and verify the remaining callback receives the event
@@ -2662,8 +3520,7 @@
      */
     @Test
     public void registersForBinderDeathOnRegisterSoftApCallback() throws Exception {
-        final int callbackIdentifier = 1;
-        registerSoftApCallbackAndVerify(mClientSoftApCallback, callbackIdentifier);
+        registerSoftApCallbackAndVerify(mClientSoftApCallback);
         verify(mAppBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
     }
 
@@ -2674,21 +3531,20 @@
     public void unregistersSoftApCallbackOnBinderDied() throws Exception {
         ArgumentCaptor<IBinder.DeathRecipient> drCaptor =
                 ArgumentCaptor.forClass(IBinder.DeathRecipient.class);
-        final int callbackIdentifier = 1;
-        registerSoftApCallbackAndVerify(mClientSoftApCallback, callbackIdentifier);
+        registerSoftApCallbackAndVerify(mClientSoftApCallback);
         verify(mAppBinder).linkToDeath(drCaptor.capture(), anyInt());
 
         drCaptor.getValue().binderDied();
         mLooper.dispatchAll();
-        verify(mAppBinder).unlinkToDeath(drCaptor.getValue(), 0);
-
+        reset(mClientSoftApCallback);
         // Verify callback is removed from the list as well
-        final WifiClient testClient = new WifiClient(TEST_FACTORY_MAC_ADDR);
-        final List<WifiClient> testClients = new ArrayList();
-        testClients.add(testClient);
-        mStateMachineSoftApCallback.onConnectedClientsChanged(testClients);
+        Map<String, List<WifiClient>> mTestSoftApClients = mock(Map.class);
+        Map<String, SoftApInfo> mTestSoftApInfos = mock(Map.class);
+        mStateMachineSoftApCallback.onConnectedClientsOrInfoChanged(
+                mTestSoftApInfos, mTestSoftApClients, false);
         mLooper.dispatchAll();
-        verify(mClientSoftApCallback, never()).onConnectedClientsChanged(testClients);
+        verify(mClientSoftApCallback, never()).onConnectedClientsOrInfoChanged(
+                any(), any(), anyBoolean(), anyBoolean());
     }
 
     /**
@@ -2696,15 +3552,13 @@
      */
     @Test
     public void callsRegisteredCallbacksOnConnectedClientsChangedEvent() throws Exception {
-        final int callbackIdentifier = 1;
-        registerSoftApCallbackAndVerify(mClientSoftApCallback, callbackIdentifier);
+        registerSoftApCallbackAndVerify(mClientSoftApCallback);
 
-        final WifiClient testClient = new WifiClient(TEST_FACTORY_MAC_ADDR);
-        final List<WifiClient> testClients = new ArrayList();
-        testClients.add(testClient);
-        mStateMachineSoftApCallback.onConnectedClientsChanged(testClients);
+        mStateMachineSoftApCallback.onConnectedClientsOrInfoChanged(
+                mTestSoftApInfos, mTestSoftApClients, false);
         mLooper.dispatchAll();
-        verify(mClientSoftApCallback).onConnectedClientsChanged(testClients);
+        verify(mClientSoftApCallback).onConnectedClientsOrInfoChanged(
+                eq(mTestSoftApInfos), eq(mTestSoftApClients), eq(false), eq(false));
     }
 
     /**
@@ -2712,8 +3566,7 @@
      */
     @Test
     public void callsRegisteredCallbacksOnSoftApStateChangedEvent() throws Exception {
-        final int callbackIdentifier = 1;
-        registerSoftApCallbackAndVerify(mClientSoftApCallback, callbackIdentifier);
+        registerSoftApCallbackAndVerify(mClientSoftApCallback);
 
         mStateMachineSoftApCallback.onStateChanged(WIFI_AP_STATE_ENABLED, 0);
         mLooper.dispatchAll();
@@ -2726,21 +3579,19 @@
      */
     @Test
     public void updatesSoftApStateAndConnectedClientsOnSoftApEvents() throws Exception {
-        final List<WifiClient> testClients = new ArrayList();
-        WifiClient testWifiClient = new WifiClient(MacAddress.fromString("22:33:44:55:66:77"));
+        WifiClient testWifiClient = new WifiClient(MacAddress.fromString("22:33:44:55:66:77"),
+                WIFI_IFACE_NAME2);
         mStateMachineSoftApCallback.onStateChanged(WIFI_AP_STATE_ENABLED, 0);
-        mStateMachineSoftApCallback.onConnectedClientsChanged(testClients);
-        mStateMachineSoftApCallback.onInfoChanged(mTestSoftApInfo);
+        mStateMachineSoftApCallback.onConnectedClientsOrInfoChanged(
+                mTestSoftApInfos, mTestSoftApClients, false);
         mStateMachineSoftApCallback.onBlockedClientConnecting(testWifiClient, 0);
 
         // Register callback after num clients and soft AP are changed.
-        final int callbackIdentifier = 1;
-        mWifiServiceImpl.registerSoftApCallback(mAppBinder, mClientSoftApCallback,
-                callbackIdentifier);
+        mWifiServiceImpl.registerSoftApCallback(mClientSoftApCallback);
         mLooper.dispatchAll();
         verify(mClientSoftApCallback).onStateChanged(WIFI_AP_STATE_ENABLED, 0);
-        verify(mClientSoftApCallback).onConnectedClientsChanged(testClients);
-        verify(mClientSoftApCallback).onInfoChanged(mTestSoftApInfo);
+        verify(mClientSoftApCallback).onConnectedClientsOrInfoChanged(
+                eq(mTestSoftApInfos), eq(mTestSoftApClients), eq(false), eq(true));
         // Don't need to invoke callback when register.
         verify(mClientSoftApCallback, never()).onBlockedClientConnecting(any(), anyInt());
     }
@@ -2991,8 +3842,14 @@
     @Test
     public void testRegisteredLocalOnlyHotspotRequestorsGetOnStartedCallbackWhenReady()
             throws Exception {
+        SoftApConfiguration lohsConfig = createValidSoftApConfiguration();
+        when(mWifiApConfigStore.generateLocalOnlyHotspotConfig(
+                eq(mContext), eq(SoftApConfiguration.BAND_2GHZ), eq(null))).thenReturn(lohsConfig);
+        mLooper.startAutoDispatch();
         registerLOHSRequestFull();
-
+        stopAutoDispatchWithDispatchAllBeforeStopAndIgnoreExceptions(mLooper);
+        verify(mWifiApConfigStore).generateLocalOnlyHotspotConfig(
+                eq(mContext), eq(SoftApConfiguration.BAND_2GHZ), eq(null));
         mWifiServiceImpl.registerLOHSForTest(TEST_PID, mRequestInfo);
 
         mWifiServiceImpl.updateInterfaceIpState(WIFI_IFACE_NAME, IFACE_IP_MODE_LOCAL_ONLY);
@@ -3010,16 +3867,14 @@
     @Test
     public void testRegisterLocalOnlyHotspotRequestAfterAlreadyStartedGetsOnStartedCallback()
             throws Exception {
+        mLooper.startAutoDispatch();
         mWifiServiceImpl.registerLOHSForTest(TEST_PID, mRequestInfo);
 
         changeLohsState(WIFI_AP_STATE_ENABLED, WIFI_AP_STATE_DISABLED, HOTSPOT_NO_ERROR);
         mWifiServiceImpl.updateInterfaceIpState(WIFI_IFACE_NAME, IFACE_IP_MODE_LOCAL_ONLY);
-        mLooper.dispatchAll();
 
         registerLOHSRequestFull();
-
-        mLooper.dispatchAll();
-
+        stopAutoDispatchWithDispatchAllBeforeStopAndIgnoreExceptions(mLooper);
         verify(mLohsCallback).onHotspotStarted(any());
     }
 
@@ -3067,6 +3922,7 @@
      */
     @Test
     public void testCallOnFailedLocalOnlyHotspotRequestWhenTetheringStarts() throws Exception {
+        mLooper.startAutoDispatch();
         registerLOHSRequestFull();
 
         mWifiServiceImpl.updateInterfaceIpState(WIFI_IFACE_NAME, IFACE_IP_MODE_LOCAL_ONLY);
@@ -3083,8 +3939,8 @@
         clearInvocations(mLohsCallback);
 
         mWifiServiceImpl.updateInterfaceIpState(WIFI_IFACE_NAME, IFACE_IP_MODE_TETHERED);
-        mLooper.dispatchAll();
         verifyZeroInteractions(ignoreStubs(mLohsCallback));
+        stopAutoDispatchWithDispatchAllBeforeStopAndIgnoreExceptions(mLooper);
     }
 
     /**
@@ -3094,10 +3950,10 @@
     @Test
     public void testRegisterLocalOnlyHotspotRequestWhenStoppedDoesNotGetOnStoppedCallback()
             throws Exception {
+        mLooper.startAutoDispatch();
         registerLOHSRequestFull();
-        mLooper.dispatchAll();
-
         verifyZeroInteractions(ignoreStubs(mLohsCallback));
+        stopAutoDispatchWithDispatchAllBeforeStopAndIgnoreExceptions(mLooper);
     }
 
     /**
@@ -3212,7 +4068,8 @@
                 any(PasspointConfiguration.class), anyInt(), eq(TEST_PACKAGE_NAME), eq(false),
                 eq(true))).thenReturn(true);
         mLooper.startAutoDispatch();
-        assertEquals(0, mWifiServiceImpl.addOrUpdateNetwork(config, TEST_PACKAGE_NAME));
+        assertEquals(0, WifiConfigurationUtil.removeSecurityTypeFromNetworkId(
+                mWifiServiceImpl.addOrUpdateNetwork(config, TEST_PACKAGE_NAME)));
         mLooper.stopAutoDispatchAndIgnoreExceptions();
         verifyCheckChangePermission(TEST_PACKAGE_NAME);
         verify(mPasspointManager).addOrUpdateProvider(
@@ -3251,8 +4108,9 @@
     public void testGetAllMatchingPasspointProfilesForScanResultsWithPermissions() {
         when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
                 anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+        mLooper.startAutoDispatch();
         mWifiServiceImpl.getAllMatchingPasspointProfilesForScanResults(createScanResultList());
-        mLooper.dispatchAll();
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
         verify(mPasspointManager).getAllMatchingPasspointProfilesForScanResults(any());
     }
 
@@ -3265,8 +4123,9 @@
     public void testGetAllMatchingPasspointProfilesForScanResultsWithInvalidScanResult() {
         when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
                 anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+        mLooper.startAutoDispatch();
         mWifiServiceImpl.getAllMatchingPasspointProfilesForScanResults(new ArrayList<>());
-        mLooper.dispatchAll();
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
         verify(mPasspointManager, never()).getAllMatchingPasspointProfilesForScanResults(any());
     }
 
@@ -3299,8 +4158,9 @@
     public void testGetMatchingOsuProvidersWithPermissions() {
         when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
                 anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+        mLooper.startAutoDispatch();
         mWifiServiceImpl.getMatchingOsuProviders(createScanResultList());
-        mLooper.dispatchAll();
+        mLooper.stopAutoDispatch();
         verify(mPasspointManager).getMatchingOsuProviders(any());
     }
 
@@ -3338,9 +4198,11 @@
         when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETUP_WIZARD),
                 anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
 
+        mLooper.startAutoDispatch();
         mWifiServiceImpl.startSubscriptionProvisioning(mOsuProvider, mProvisioningCallback);
-        verify(mClientModeImpl).syncStartSubscriptionProvisioning(anyInt(),
-                eq(mOsuProvider), eq(mProvisioningCallback), any());
+        mLooper.stopAutoDispatch();
+        verify(mClientModeManager).syncStartSubscriptionProvisioning(anyInt(),
+                eq(mOsuProvider), eq(mProvisioningCallback));
     }
 
     /**
@@ -3526,6 +4388,27 @@
     }
 
     /**
+     * Verify that a call to {@link WifiServiceImpl#restoreSoftApBackupData(byte[])}
+     * will call WifiApConfigStore#upgradeSoftApConfiguration and
+     * WifiApConfigStore#resetToDefaultForUnsupportedConfig.
+     */
+    @Test
+    public void testRestoreSoftApBackupData() {
+        when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+            anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+        InOrder inorder = inOrder(mWifiApConfigStore);
+        SoftApConfiguration testConfig = new SoftApConfiguration.Builder().setSsid("test").build();
+        byte[] testData = testConfig.toString().getBytes();
+        when(mSoftApBackupRestore.retrieveSoftApConfigurationFromBackupData(testData))
+                .thenReturn(testConfig);
+        mWifiServiceImpl.restoreSoftApBackupData(testData);
+        mLooper.dispatchAll();
+        inorder.verify(mWifiApConfigStore).upgradeSoftApConfiguration(testConfig);
+        inorder.verify(mWifiApConfigStore).resetToDefaultForUnsupportedConfig(any());
+        inorder.verify(mWifiApConfigStore).setApConfiguration(any());
+    }
+
+    /**
      * Verify that a call to {@link WifiServiceImpl#retrieveSoftApBackupData()} is only allowed from
      * callers with the signature only NETWORK_SETTINGS permission.
      */
@@ -3539,21 +4422,150 @@
                 .retrieveBackupDataFromSoftApConfiguration(any(SoftApConfiguration.class));
     }
 
+    class TestWifiVerboseLoggingStatusChangedListener extends
+            IWifiVerboseLoggingStatusChangedListener.Stub {
+        public int numStatusChangedCounts;
+        public boolean lastReceivedValue;
+        @Override
+        public void onStatusChanged(boolean enabled) throws RemoteException {
+            numStatusChangedCounts++;
+            lastReceivedValue = enabled;
+        }
+    }
+
+    /**
+     * Verify that a call to {@link WifiServiceImpl#enableVerboseLogging(int)} is propagated to
+     * registered {@link IWifiVerboseLoggingStatusChangedListener}. Then, verify that changes are no
+     * longer propagated when the listener gets unregistered.
+     */
+    @Test
+    public void testVerboseLoggingListener() throws Exception {
+        doNothing().when(mContext)
+                .enforceCallingOrSelfPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                        eq("WifiService"));
+        // Verbose logging is enabled first in the constructor for WifiServiceImpl, so reset
+        // before invocation.
+        reset(mClientModeManager);
+        TestWifiVerboseLoggingStatusChangedListener listener =
+                new TestWifiVerboseLoggingStatusChangedListener();
+        mWifiServiceImpl.addWifiVerboseLoggingStatusChangedListener(listener);
+        mLooper.dispatchAll();
+        mWifiServiceImpl.enableVerboseLogging(WifiManager.VERBOSE_LOGGING_LEVEL_ENABLED);
+        verify(mWifiSettingsConfigStore).put(WIFI_VERBOSE_LOGGING_ENABLED, true);
+        verify(mActiveModeWarden).enableVerboseLogging(anyBoolean());
+        assertEquals(1, listener.numStatusChangedCounts);
+        assertTrue(listener.lastReceivedValue);
+
+        mWifiServiceImpl.enableVerboseLogging(WifiManager.VERBOSE_LOGGING_LEVEL_DISABLED);
+        assertEquals(2, listener.numStatusChangedCounts);
+        assertFalse(listener.lastReceivedValue);
+
+        // unregister the callback and verify no more updates happen.
+        mWifiServiceImpl.removeWifiVerboseLoggingStatusChangedListener(listener);
+        mLooper.dispatchAll();
+        mWifiServiceImpl.enableVerboseLogging(WifiManager.VERBOSE_LOGGING_LEVEL_ENABLED);
+        assertEquals(2, listener.numStatusChangedCounts);
+        assertFalse(listener.lastReceivedValue);
+    }
+
+    /**
+     * Verify an exception is thrown for invalid inputs to
+     * addWifiVerboseLoggingStatusChangedListener and removeWifiVerboseLoggingStatusChangedListener.
+     */
+    @Test
+    public void testVerboseLoggingListenerInvalidInput() throws Exception {
+        try {
+            mWifiServiceImpl.addWifiVerboseLoggingStatusChangedListener(null);
+            fail("expected IllegalArgumentException in addWifiVerboseLoggingStatusChangedListener");
+        } catch (IllegalArgumentException e) {
+        }
+        try {
+            mWifiServiceImpl.removeWifiVerboseLoggingStatusChangedListener(null);
+            fail("expected IllegalArgumentException in "
+                    + "removeWifiVerboseLoggingStatusChangedListener");
+        } catch (IllegalArgumentException e) {
+        }
+    }
+
+    /**
+     * Verify a SecurityException if the caller doesn't have sufficient permissions.
+     */
+    @Test
+    public void testVerboseLoggingListenerNoPermission() throws Exception {
+        doThrow(new SecurityException()).when(mContext)
+                .enforceCallingOrSelfPermission(eq(ACCESS_WIFI_STATE),
+                        eq("WifiService"));
+        TestWifiVerboseLoggingStatusChangedListener listener =
+                new TestWifiVerboseLoggingStatusChangedListener();
+        try {
+            mWifiServiceImpl.addWifiVerboseLoggingStatusChangedListener(listener);
+            fail("expected IllegalArgumentException in addWifiVerboseLoggingStatusChangedListener");
+        } catch (SecurityException e) {
+        }
+        try {
+            mWifiServiceImpl.removeWifiVerboseLoggingStatusChangedListener(listener);
+            fail("expected IllegalArgumentException in "
+                    + "removeWifiVerboseLoggingStatusChangedListener");
+        } catch (SecurityException e) {
+        }
+    }
+
     /**
      * Verify that a call to {@link WifiServiceImpl#enableVerboseLogging(int)} is allowed from
      * callers with the signature only NETWORK_SETTINGS permission.
      */
     @Test
-    public void testEnableVerboseLoggingWithNetworkSettingsPermission() {
+    public void testEnableVerboseLoggingWithNetworkSettingsPermission() throws Exception {
         doNothing().when(mContext)
                 .enforceCallingOrSelfPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
                         eq("WifiService"));
-        // Vebose logging is enabled first in the constructor for WifiServiceImpl, so reset
+        // Verbose logging is enabled first in the constructor for WifiServiceImpl, so reset
         // before invocation.
-        reset(mClientModeImpl);
-        mWifiServiceImpl.enableVerboseLogging(1);
+        reset(mClientModeManager);
+        mWifiServiceImpl.enableVerboseLogging(WifiManager.VERBOSE_LOGGING_LEVEL_ENABLED);
         verify(mWifiSettingsConfigStore).put(WIFI_VERBOSE_LOGGING_ENABLED, true);
-        verify(mClientModeImpl).enableVerboseLogging(anyInt());
+        verify(mActiveModeWarden).enableVerboseLogging(anyBoolean());
+    }
+
+    /**
+     * Verify that setting verbose logging mode to
+     * {@link WifiManager#VERBOSE_LOGGING_LEVEL_ENABLED_SHOW_KEY)} is allowed from
+     * callers with the signature only NETWORK_SETTINGS permission.
+     */
+    @Test
+    public void testEnableShowKeyVerboseLoggingWithNetworkSettingsPermission() throws Exception {
+        doNothing().when(mContext)
+                .enforceCallingOrSelfPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                        eq("WifiService"));
+        // Verbose logging is enabled first in the constructor for WifiServiceImpl, so reset
+        // before invocation.
+        reset(mClientModeManager);
+        mWifiServiceImpl.enableVerboseLogging(WifiManager.VERBOSE_LOGGING_LEVEL_ENABLED_SHOW_KEY);
+        verify(mWifiSettingsConfigStore).put(WIFI_VERBOSE_LOGGING_ENABLED, true);
+        verify(mActiveModeWarden).enableVerboseLogging(anyBoolean());
+        verify(mWifiGlobals).setShowKeyVerboseLoggingModeEnabled(eq(true));
+
+        // After auto disable show key mode after the countdown
+        mLooper.moveTimeForward(WifiServiceImpl.AUTO_DISABLE_SHOW_KEY_COUNTDOWN_MILLIS + 1);
+        mLooper.dispatchAll();
+        verify(mWifiGlobals).setShowKeyVerboseLoggingModeEnabled(eq(false));
+    }
+
+    /**
+     * Verify that setting verbose logging level to
+     * {@link WifiManager#VERBOSE_LOGGING_LEVEL_ENABLED_SHOW_KEY)} is not allowed for
+     * the user build.
+     */
+    @Test(expected = SecurityException.class)
+    public void testEnableShowKeyVerboseLoggingNotAllowedForUserBuild() throws Exception {
+        when(mBuildProperties.isUserBuild()).thenReturn(true);
+        doNothing().when(mContext)
+                .enforceCallingOrSelfPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                        eq("WifiService"));
+        // Verbose logging is enabled first in the constructor for WifiServiceImpl, so reset
+        // before invocation.
+        reset(mClientModeManager);
+        mWifiServiceImpl.enableVerboseLogging(WifiManager.VERBOSE_LOGGING_LEVEL_ENABLED_SHOW_KEY);
     }
 
     /**
@@ -3567,11 +4579,11 @@
                         eq("WifiService"));
         // Vebose logging is enabled first in the constructor for WifiServiceImpl, so reset
         // before invocation.
-        reset(mClientModeImpl);
-        mWifiServiceImpl.enableVerboseLogging(1);
+        reset(mClientModeManager);
+        mWifiServiceImpl.enableVerboseLogging(WifiManager.VERBOSE_LOGGING_LEVEL_ENABLED);
         verify(mWifiSettingsConfigStore, never()).put(
                 WIFI_VERBOSE_LOGGING_ENABLED, anyBoolean());
-        verify(mClientModeImpl, never()).enableVerboseLogging(anyInt());
+        verify(mActiveModeWarden, never()).enableVerboseLogging(anyBoolean());
     }
 
     /**
@@ -3582,12 +4594,11 @@
     public void testConnectNetworkWithoutPrivilegedPermission() throws Exception {
         try {
             mWifiServiceImpl.connect(mock(WifiConfiguration.class), TEST_NETWORK_ID,
-                    mock(Binder.class),
-                    mock(IActionListener.class), 0);
+                    mock(IActionListener.class));
             fail();
         } catch (SecurityException e) {
-            verify(mClientModeImpl, never()).connect(any(WifiConfiguration.class), anyInt(),
-                    any(Binder.class), any(IActionListener.class), anyInt(), anyInt());
+            mLooper.dispatchAll();
+            verify(mConnectHelper, never()).connectToNetwork(any(), any(), anyInt());
         }
     }
 
@@ -3598,12 +4609,11 @@
     @Test
     public void testForgetNetworkWithoutPrivilegedPermission() throws Exception {
         try {
-            mWifiServiceImpl.forget(TEST_NETWORK_ID, mock(Binder.class),
-                    mock(IActionListener.class), 0);
+            mWifiServiceImpl.forget(TEST_NETWORK_ID, mock(IActionListener.class));
             fail();
         } catch (SecurityException e) {
-            verify(mClientModeImpl, never()).forget(anyInt(), any(Binder.class),
-                    any(IActionListener.class), anyInt(), anyInt());
+            mLooper.dispatchAll();
+            verify(mWifiConfigManager, never()).removeNetwork(anyInt(), anyInt(), any());
         }
     }
 
@@ -3614,67 +4624,346 @@
     @Test
     public void testSaveNetworkWithoutPrivilegedPermission() throws Exception {
         try {
-            mWifiServiceImpl.save(mock(WifiConfiguration.class), mock(Binder.class),
-                    mock(IActionListener.class), 0);
+            mWifiServiceImpl.save(mock(WifiConfiguration.class), mock(IActionListener.class));
             fail();
         } catch (SecurityException e) {
-            verify(mClientModeImpl, never()).save(any(WifiConfiguration.class),
-                    any(Binder.class), any(IActionListener.class), anyInt(), anyInt());
+            mLooper.dispatchAll();
+            verify(mWifiConfigManager, never()).updateBeforeSaveNetwork(any(), anyInt());
         }
     }
 
     /**
      * Verify that the CONNECT_NETWORK message received from an app with
-     * one of the privileged permission is forwarded to ClientModeImpl.
+     * one of the privileged permission is forwarded to ClientModeManager.
      */
     @Test
     public void testConnectNetworkWithPrivilegedPermission() throws Exception {
         when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
             anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
         when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(true);
-        mWifiServiceImpl.connect(mock(WifiConfiguration.class), TEST_NETWORK_ID,
-                mock(Binder.class),
-                mock(IActionListener.class), 0);
-        verify(mClientModeImpl).connect(any(WifiConfiguration.class), anyInt(),
-                any(Binder.class), any(IActionListener.class), anyInt(), anyInt());
+        when(mWifiConfigManager.addOrUpdateNetwork(any(), anyInt()))
+                .thenReturn(new NetworkUpdateResult(TEST_NETWORK_ID));
+        WifiConfiguration config = new WifiConfiguration();
+        config.SSID = TEST_SSID;
+        when(mWifiConfigManager.getConfiguredNetwork(TEST_NETWORK_ID)).thenReturn(config);
+        mWifiServiceImpl.connect(config, TEST_NETWORK_ID, mock(IActionListener.class));
+        mLooper.dispatchAll();
+        verify(mWifiConfigManager).addOrUpdateNetwork(eq(config), anyInt());
+        verify(mConnectHelper).connectToNetwork(any(NetworkUpdateResult.class),
+                any(ActionListenerWrapper.class), anyInt());
         verify(mWifiMetrics).logUserActionEvent(eq(UserActionEvent.EVENT_ADD_OR_UPDATE_NETWORK),
                 anyInt());
     }
 
+    @Test
+    public void connectToNewNetwork_success() throws Exception {
+        when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+        when(mWifiPermissionsUtil.isLocationModeEnabled()).thenReturn(true);
+        NetworkUpdateResult result = new NetworkUpdateResult(TEST_NETWORK_ID);
+        when(mWifiConfigManager.addOrUpdateNetwork(eq(mWifiConfig), anyInt()))
+                .thenReturn(result);
+        when(mWifiConfigManager.getConfiguredNetwork(TEST_NETWORK_ID)).thenReturn(mWifiConfig);
+
+        mWifiServiceImpl.connect(mWifiConfig, WifiConfiguration.INVALID_NETWORK_ID,
+                mActionListener);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<WifiConfiguration> configCaptor =
+                ArgumentCaptor.forClass(WifiConfiguration.class);
+        verify(mWifiConfigManager).addOrUpdateNetwork(configCaptor.capture(), anyInt());
+        assertThat(configCaptor.getValue().networkId).isEqualTo(TEST_NETWORK_ID);
+
+        verify(mConnectHelper).connectToNetwork(eq(result), any(), anyInt());
+        verify(mContextAsUser).sendBroadcastWithMultiplePermissions(
+                mIntentCaptor.capture(),
+                aryEq(new String[]{
+                        android.Manifest.permission.RECEIVE_WIFI_CREDENTIAL_CHANGE,
+                        android.Manifest.permission.ACCESS_FINE_LOCATION,
+                }));
+
+        Intent intent = mIntentCaptor.getValue();
+        assertThat(intent.getAction()).isEqualTo(WifiManager.WIFI_CREDENTIAL_CHANGED_ACTION);
+        assertThat(intent.getStringExtra(WifiManager.EXTRA_WIFI_CREDENTIAL_SSID))
+                .isEqualTo(TEST_SSID);
+        assertThat(intent.getIntExtra(WifiManager.EXTRA_WIFI_CREDENTIAL_EVENT_TYPE, -1))
+                .isEqualTo(WifiManager.WIFI_CREDENTIAL_SAVED);
+    }
+
+    @Test
+    public void connectToNewNetwork_failure() throws Exception {
+        when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+        when(mWifiConfigManager.addOrUpdateNetwork(eq(mWifiConfig), anyInt()))
+                .thenReturn(NetworkUpdateResult.makeFailed());
+        when(mWifiPermissionsUtil.isLocationModeEnabled()).thenReturn(true);
+
+        mWifiServiceImpl.connect(mWifiConfig, WifiConfiguration.INVALID_NETWORK_ID,
+                mActionListener);
+        mLooper.dispatchAll();
+
+        verify(mWifiConfigManager).addOrUpdateNetwork(eq(mWifiConfig), anyInt());
+
+        verify(mClientModeManager, never()).connectNetwork(any(), any(), anyInt());
+        verify(mContextAsUser, never()).sendBroadcastWithMultiplePermissions(any(), any());
+        verify(mActionListener).onFailure(WifiManager.ERROR);
+        verify(mActionListener, never()).onSuccess();
+    }
+
+    @Test
+    public void connectToExistingNetwork() throws Exception {
+        when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(true);
+        when(mWifiPermissionsUtil.isLocationModeEnabled()).thenReturn(true);
+        when(mWifiConfigManager.getConfiguredNetwork(TEST_NETWORK_ID)).thenReturn(mWifiConfig);
+
+        mWifiServiceImpl.connect(null, TEST_NETWORK_ID, mActionListener);
+        mLooper.dispatchAll();
+
+        verify(mWifiConfigManager, never()).addOrUpdateNetwork(any(), anyInt());
+
+        verify(mConnectHelper).connectToNetwork(
+                eq(new NetworkUpdateResult(TEST_NETWORK_ID)), any(), anyInt());
+        verify(mContextAsUser, never()).sendBroadcastWithMultiplePermissions(any(), any());
+        verify(mWifiMetrics).logUserActionEvent(eq(UserActionEvent.EVENT_MANUAL_CONNECT), anyInt());
+    }
+
+    @Test
+    public void connectToSimBasedNetworkWhenSimPresent() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork(
+                WifiEnterpriseConfig.Eap.SIM, WifiEnterpriseConfig.Phase2.NONE);
+        when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(true);
+        when(mWifiPermissionsUtil.isLocationModeEnabled()).thenReturn(true);
+        when(mWifiConfigManager.getConfiguredNetwork(TEST_NETWORK_ID)).thenReturn(config);
+        when(mWifiCarrierInfoManager.getBestMatchSubscriptionId(any())).thenReturn(TEST_SUB_ID);
+        when(mWifiCarrierInfoManager.isSimReady(TEST_SUB_ID)).thenReturn(true);
+
+        mWifiServiceImpl.connect(null, TEST_NETWORK_ID, mActionListener);
+        mLooper.dispatchAll();
+
+        verify(mWifiConfigManager, never()).addOrUpdateNetwork(any(), anyInt());
+
+        verify(mConnectHelper).connectToNetwork(
+                eq(new NetworkUpdateResult(TEST_NETWORK_ID)), any(), anyInt());
+        verify(mContextAsUser, never()).sendBroadcastWithMultiplePermissions(any(), any());
+        verify(mWifiMetrics).logUserActionEvent(eq(UserActionEvent.EVENT_MANUAL_CONNECT), anyInt());
+    }
+
+    @Test
+    public void connectToSimBasedNetworkWhenSimAbsent() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork(
+                WifiEnterpriseConfig.Eap.SIM, WifiEnterpriseConfig.Phase2.NONE);
+        when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(true);
+        when(mWifiPermissionsUtil.isLocationModeEnabled()).thenReturn(true);
+        when(mWifiConfigManager.getConfiguredNetwork(TEST_NETWORK_ID)).thenReturn(config);
+        when(mWifiCarrierInfoManager.getBestMatchSubscriptionId(any())).thenReturn(TEST_SUB_ID);
+        when(mWifiCarrierInfoManager.isSimReady(TEST_SUB_ID)).thenReturn(false);
+
+        mWifiServiceImpl.connect(null, TEST_NETWORK_ID, mActionListener);
+        mLooper.dispatchAll();
+
+        verify(mWifiConfigManager, never()).addOrUpdateNetwork(any(), anyInt());
+
+        verify(mConnectHelper, never()).connectToNetwork(any(), any(), anyInt());
+        verify(mContextAsUser, never()).sendBroadcastWithMultiplePermissions(any(), any());
+        verify(mWifiMetrics).logUserActionEvent(eq(UserActionEvent.EVENT_MANUAL_CONNECT), anyInt());
+    }
+
+    @Test
+    public void connectToSimBasedNetworkRequiresImsiEncryptionButNotReady() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork(
+                WifiEnterpriseConfig.Eap.SIM, WifiEnterpriseConfig.Phase2.NONE);
+        when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(true);
+        when(mWifiPermissionsUtil.isLocationModeEnabled()).thenReturn(true);
+        when(mWifiConfigManager.getConfiguredNetwork(TEST_NETWORK_ID)).thenReturn(config);
+        when(mWifiCarrierInfoManager.getBestMatchSubscriptionId(any())).thenReturn(TEST_SUB_ID);
+        when(mWifiCarrierInfoManager.isSimReady(TEST_SUB_ID)).thenReturn(false);
+        when(mWifiCarrierInfoManager.requiresImsiEncryption(TEST_SUB_ID)).thenReturn(true);
+        when(mWifiCarrierInfoManager.isImsiEncryptionInfoAvailable(TEST_SUB_ID)).thenReturn(false);
+
+        mWifiServiceImpl.connect(null, TEST_NETWORK_ID, mActionListener);
+        mLooper.dispatchAll();
+
+        verify(mWifiConfigManager, never()).addOrUpdateNetwork(any(), anyInt());
+
+        verify(mConnectHelper, never()).connectToNetwork(any(), any(), anyInt());
+        verify(mContextAsUser, never()).sendBroadcastWithMultiplePermissions(any(), any());
+        verify(mWifiMetrics).logUserActionEvent(eq(UserActionEvent.EVENT_MANUAL_CONNECT), anyInt());
+    }
+
     /**
      * Verify that the SAVE_NETWORK message received from an app with
-     * one of the privileged permission is forwarded to ClientModeImpl.
+     * one of the privileged permission is forwarded to ClientModeManager.
      */
     @Test
     public void testSaveNetworkWithPrivilegedPermission() throws Exception {
         when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
             anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
         when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(true);
-        mWifiServiceImpl.save(mock(WifiConfiguration.class), mock(Binder.class),
-                mock(IActionListener.class), 0);
+        when(mWifiConfigManager.updateBeforeSaveNetwork(any(), anyInt()))
+                .thenReturn(new NetworkUpdateResult(TEST_NETWORK_ID));
+        mWifiServiceImpl.save(mock(WifiConfiguration.class), mock(IActionListener.class));
+        mLooper.dispatchAll();
+        verify(mWifiConfigManager).updateBeforeSaveNetwork(any(WifiConfiguration.class), anyInt());
         verify(mWifiMetrics).logUserActionEvent(eq(UserActionEvent.EVENT_ADD_OR_UPDATE_NETWORK),
                 anyInt());
-        verify(mClientModeImpl).save(any(WifiConfiguration.class),
-                any(Binder.class), any(IActionListener.class), anyInt(), anyInt());
+    }
+
+    @Test
+    public void saveNetwork_success() throws Exception {
+        when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+        when(mWifiPermissionsUtil.isLocationModeEnabled()).thenReturn(true);
+
+        NetworkUpdateResult result = new NetworkUpdateResult(TEST_NETWORK_ID);
+        when(mWifiConfigManager.updateBeforeSaveNetwork(eq(mWifiConfig), anyInt()))
+                .thenReturn(result);
+
+        mWifiServiceImpl.save(mWifiConfig, mActionListener);
+        mLooper.dispatchAll();
+
+        verify(mWifiConfigManager).updateBeforeSaveNetwork(eq(mWifiConfig), anyInt());
+
+        verify(mClientModeManager).saveNetwork(eq(result), any(), anyInt());
+        verify(mContextAsUser).sendBroadcastWithMultiplePermissions(
+                mIntentCaptor.capture(),
+                aryEq(new String[]{
+                        android.Manifest.permission.RECEIVE_WIFI_CREDENTIAL_CHANGE,
+                        android.Manifest.permission.ACCESS_FINE_LOCATION,
+                }));
+
+        Intent intent = mIntentCaptor.getValue();
+        assertThat(intent.getAction()).isEqualTo(WifiManager.WIFI_CREDENTIAL_CHANGED_ACTION);
+        assertThat(intent.getStringExtra(WifiManager.EXTRA_WIFI_CREDENTIAL_SSID))
+                .isEqualTo(TEST_SSID);
+        assertThat(intent.getIntExtra(WifiManager.EXTRA_WIFI_CREDENTIAL_EVENT_TYPE, -1))
+                .isEqualTo(WifiManager.WIFI_CREDENTIAL_SAVED);
+    }
+
+    @Test
+    public void saveNetwork_failure() throws Exception {
+        when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+        when(mWifiConfigManager.updateBeforeSaveNetwork(eq(mWifiConfig), anyInt()))
+                .thenReturn(NetworkUpdateResult.makeFailed());
+        when(mWifiPermissionsUtil.isLocationModeEnabled()).thenReturn(true);
+
+        mWifiServiceImpl.save(mWifiConfig, mActionListener);
+        mLooper.dispatchAll();
+
+        verify(mWifiConfigManager).updateBeforeSaveNetwork(eq(mWifiConfig), anyInt());
+
+        verify(mClientModeManager, never()).saveNetwork(any(), any(), anyInt());
+        verify(mContext, never()).sendBroadcastWithMultiplePermissions(any(), any());
+        verify(mActionListener).onFailure(WifiManager.ERROR);
+        verify(mActionListener, never()).onSuccess();
     }
 
     /**
      * Verify that the FORGET_NETWORK message received from an app with
-     * one of the privileged permission is forwarded to ClientModeImpl.
+     * one of the privileged permission is forwarded to ClientModeManager.
      */
     @Test
     public void testForgetNetworkWithPrivilegedPermission() throws Exception {
         when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
             anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
         when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(true);
-        mWifiServiceImpl.forget(TEST_NETWORK_ID, mock(Binder.class), mock(IActionListener.class),
-                0);
+        when(mWifiConfigManager.removeNetwork(anyInt(), anyInt(), any())).thenReturn(true);
+        mWifiServiceImpl.forget(TEST_NETWORK_ID, mock(IActionListener.class));
 
-        InOrder inOrder = inOrder(mClientModeImpl, mWifiMetrics);
+        InOrder inOrder = inOrder(mWifiConfigManager, mWifiMetrics);
         inOrder.verify(mWifiMetrics).logUserActionEvent(
                 UserActionEvent.EVENT_FORGET_WIFI, TEST_NETWORK_ID);
-        inOrder.verify(mClientModeImpl).forget(anyInt(), any(Binder.class),
-                any(IActionListener.class), anyInt(), anyInt());
+
+        mLooper.dispatchAll();
+        inOrder.verify(mWifiConfigManager).removeNetwork(anyInt(), anyInt(), any());
+    }
+
+    @Test
+    public void forgetNetwork_success() throws Exception {
+        when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+        when(mWifiConfigManager.getConfiguredNetwork(TEST_NETWORK_ID)).thenReturn(mWifiConfig);
+        when(mWifiConfigManager.removeNetwork(eq(TEST_NETWORK_ID), anyInt(), any()))
+                .thenReturn(true);
+        when(mWifiPermissionsUtil.isLocationModeEnabled()).thenReturn(true);
+
+        mWifiServiceImpl.forget(TEST_NETWORK_ID, mActionListener);
+        mLooper.dispatchAll();
+
+        verify(mWifiConfigManager).removeNetwork(eq(TEST_NETWORK_ID), anyInt(), any());
+        verify(mActionListener).onSuccess();
+        verify(mActionListener, never()).onFailure(WifiManager.ERROR);
+
+        verify(mContextAsUser).sendBroadcastWithMultiplePermissions(
+                mIntentCaptor.capture(),
+                aryEq(new String[]{
+                        android.Manifest.permission.RECEIVE_WIFI_CREDENTIAL_CHANGE,
+                        android.Manifest.permission.ACCESS_FINE_LOCATION,
+                }));
+
+        Intent intent = mIntentCaptor.getValue();
+        assertThat(intent.getAction()).isEqualTo(WifiManager.WIFI_CREDENTIAL_CHANGED_ACTION);
+        assertThat(intent.getStringExtra(WifiManager.EXTRA_WIFI_CREDENTIAL_SSID))
+                .isEqualTo(TEST_SSID);
+        assertThat(intent.getIntExtra(WifiManager.EXTRA_WIFI_CREDENTIAL_EVENT_TYPE, -1))
+                .isEqualTo(WifiManager.WIFI_CREDENTIAL_FORGOT);
+    }
+
+    @Test
+    public void forgetNetwork_successNoLocation_dontBroadcastSsid() throws Exception {
+        when(mWifiPermissionsUtil.isLocationModeEnabled()).thenReturn(false);
+
+        when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+        when(mWifiConfigManager.getConfiguredNetwork(TEST_NETWORK_ID)).thenReturn(mWifiConfig);
+        when(mWifiConfigManager.removeNetwork(eq(TEST_NETWORK_ID), anyInt(), any()))
+                .thenReturn(true);
+
+        mWifiServiceImpl.forget(TEST_NETWORK_ID, mActionListener);
+        mLooper.dispatchAll();
+
+        verify(mWifiConfigManager).removeNetwork(eq(TEST_NETWORK_ID), anyInt(), any());
+        verify(mActionListener).onSuccess();
+        verify(mActionListener, never()).onFailure(WifiManager.ERROR);
+
+        verify(mContextAsUser).sendBroadcastWithMultiplePermissions(
+                mIntentCaptor.capture(),
+                aryEq(new String[]{
+                        android.Manifest.permission.RECEIVE_WIFI_CREDENTIAL_CHANGE,
+                        android.Manifest.permission.ACCESS_FINE_LOCATION,
+                }));
+
+        Intent intent = mIntentCaptor.getValue();
+        assertThat(intent.getAction()).isEqualTo(WifiManager.WIFI_CREDENTIAL_CHANGED_ACTION);
+        // SSID is null if location is disabled
+        assertThat(intent.getStringExtra(WifiManager.EXTRA_WIFI_CREDENTIAL_SSID)).isNull();
+        assertThat(intent.getIntExtra(WifiManager.EXTRA_WIFI_CREDENTIAL_EVENT_TYPE, -1))
+                .isEqualTo(WifiManager.WIFI_CREDENTIAL_FORGOT);
+    }
+
+    @Test
+    public void forgetNetwork_failed() throws Exception {
+        when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+        when(mWifiConfigManager.removeNetwork(eq(TEST_NETWORK_ID), anyInt(), any()))
+                .thenReturn(false);
+        when(mWifiPermissionsUtil.isLocationModeEnabled()).thenReturn(true);
+
+        mWifiServiceImpl.forget(TEST_NETWORK_ID, mActionListener);
+        mLooper.dispatchAll();
+
+        verify(mActionListener, never()).onSuccess();
+        verify(mActionListener).onFailure(WifiManager.ERROR);
+        verify(mWifiConfigManager).removeNetwork(eq(TEST_NETWORK_ID), anyInt(), any());
+        verify(mContextAsUser, never()).sendBroadcastWithMultiplePermissions(any(), any());
     }
 
     /**
@@ -3686,29 +4975,31 @@
         when(mSettingsStore.isWifiToggleEnabled()).thenReturn(false);
         when(mWifiInjector.getPasspointProvisionerHandlerThread())
                 .thenReturn(mock(HandlerThread.class));
+        mLooper.startAutoDispatch();
         mWifiServiceImpl.checkAndStartWifi();
-        mLooper.dispatchAll();
         mWifiServiceImpl.handleBootCompleted();
-        mLooper.dispatchAll();
+        mLooper.stopAutoDispatch();
         verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
                 (IntentFilter) argThat(new IdleModeIntentMatcher()));
 
         // Tell the wifi service that the device became idle.
         when(mPowerManager.isDeviceIdleMode()).thenReturn(true);
         TestUtil.sendIdleModeChanged(mBroadcastReceiverCaptor.getValue(), mContext);
-        mLooper.dispatchAll();
 
         // Send a scan request while the device is idle.
+        mWifiThreadRunner.prepareForAutoDispatch();
         mLooper.startAutoDispatch();
         assertFalse(mWifiServiceImpl.startScan(SCAN_PACKAGE_NAME, TEST_FEATURE_ID));
-        mLooper.stopAutoDispatchAndIgnoreExceptions();
+        mLooper.stopAutoDispatch();
         // No scans must be made yet as the device is idle.
         verify(mScanRequestProxy, never()).startScan(Process.myUid(), SCAN_PACKAGE_NAME);
 
         // Tell the wifi service that idle mode ended.
         when(mPowerManager.isDeviceIdleMode()).thenReturn(false);
+        mWifiThreadRunner.prepareForAutoDispatch();
+        mLooper.startAutoDispatch();
         TestUtil.sendIdleModeChanged(mBroadcastReceiverCaptor.getValue(), mContext);
-        mLooper.dispatchAll();
+        mLooper.stopAutoDispatch();
 
         // Must scan now.
         verify(mScanRequestProxy).startScan(Process.myUid(), TEST_PACKAGE_NAME);
@@ -3719,9 +5010,10 @@
 
         // Send another scan request. The device is not idle anymore, so it must be executed
         // immediately.
+        mWifiThreadRunner.prepareForAutoDispatch();
         mLooper.startAutoDispatch();
         assertTrue(mWifiServiceImpl.startScan(SCAN_PACKAGE_NAME, TEST_FEATURE_ID));
-        mLooper.stopAutoDispatchAndIgnoreExceptions();
+        mLooper.stopAutoDispatch();
         verify(mScanRequestProxy).startScan(Process.myUid(), SCAN_PACKAGE_NAME);
     }
 
@@ -3738,7 +5030,8 @@
         doThrow(new SecurityException()).when(mAppOpsManager)
                 .noteOp(AppOpsManager.OPSTR_CHANGE_WIFI_STATE, Process.myUid(), TEST_PACKAGE_NAME);
         assertTrue(mWifiServiceImpl.disconnect(TEST_PACKAGE_NAME));
-        verify(mClientModeImpl).disconnectCommand();
+        mLooper.dispatchAll();
+        verify(mClientModeManager).disconnect();
     }
 
     /**
@@ -3748,8 +5041,9 @@
     @Test
     public void testDisconnectWithChangeWifiStatePerm() throws Exception {
         assertFalse(mWifiServiceImpl.disconnect(TEST_PACKAGE_NAME));
+        mLooper.dispatchAll();
         verifyCheckChangePermission(TEST_PACKAGE_NAME);
-        verify(mClientModeImpl, never()).disconnectCommand();
+        verify(mClientModeManager, never()).disconnect();
     }
 
     /**
@@ -3767,7 +5061,7 @@
 
         }
         verifyCheckChangePermission(TEST_PACKAGE_NAME);
-        verify(mClientModeImpl, never()).disconnectCommand();
+        verify(mClientModeManager, never()).disconnect();
     }
 
     @Test
@@ -3798,7 +5092,7 @@
 
         verify(mScanRequestProxy).clearScanRequestTimestampsForApp(packageName, uid);
         verify(mWifiNetworkSuggestionsManager).removeApp(packageName);
-        verify(mClientModeImpl).removeNetworkRequestUserApprovedAccessPointsForApp(packageName);
+        verify(mWifiNetworkFactory).removeUserApprovedAccessPointsForApp(packageName);
         verify(mPasspointManager).removePasspointProviderWithPackage(packageName);
     }
 
@@ -3828,7 +5122,7 @@
 
         verify(mScanRequestProxy).clearScanRequestTimestampsForApp(packageName, uid);
         verify(mWifiNetworkSuggestionsManager).removeApp(packageName);
-        verify(mClientModeImpl).removeNetworkRequestUserApprovedAccessPointsForApp(packageName);
+        verify(mWifiNetworkFactory).removeUserApprovedAccessPointsForApp(packageName);
         verify(mPasspointManager).removePasspointProviderWithPackage(packageName);
     }
 
@@ -3860,7 +5154,7 @@
 
         verify(mScanRequestProxy).clearScanRequestTimestampsForApp(packageName, uid);
         verify(mWifiNetworkSuggestionsManager).removeApp(packageName);
-        verify(mClientModeImpl).removeNetworkRequestUserApprovedAccessPointsForApp(packageName);
+        verify(mWifiNetworkFactory).removeUserApprovedAccessPointsForApp(packageName);
         verify(mPasspointManager).removePasspointProviderWithPackage(packageName);
     }
 
@@ -3883,8 +5177,7 @@
         mLooper.dispatchAll();
         verify(mScanRequestProxy, never()).clearScanRequestTimestampsForApp(anyString(), anyInt());
         verify(mWifiNetworkSuggestionsManager, never()).removeApp(anyString());
-        verify(mClientModeImpl, never()).removeNetworkRequestUserApprovedAccessPointsForApp(
-                packageName);
+        verify(mWifiNetworkFactory, never()).removeUserApprovedAccessPointsForApp(anyString());
         verify(mPasspointManager, never()).removePasspointProviderWithPackage(anyString());
     }
 
@@ -3907,8 +5200,7 @@
         mLooper.dispatchAll();
         verify(mScanRequestProxy, never()).clearScanRequestTimestampsForApp(anyString(), anyInt());
         verify(mWifiNetworkSuggestionsManager, never()).removeApp(anyString());
-        verify(mClientModeImpl, never()).removeNetworkRequestUserApprovedAccessPointsForApp(
-                anyString());
+        verify(mWifiNetworkFactory, never()).removeUserApprovedAccessPointsForApp(anyString());
         verify(mPasspointManager, never()).removePasspointProviderWithPackage(anyString());
     }
 
@@ -3916,10 +5208,10 @@
     public void testUserRemovedBroadcastHandling() {
         when(mWifiInjector.getPasspointProvisionerHandlerThread())
                 .thenReturn(mock(HandlerThread.class));
+        mLooper.startAutoDispatch();
         mWifiServiceImpl.checkAndStartWifi();
-        mLooper.dispatchAll();
         mWifiServiceImpl.handleBootCompleted();
-        mLooper.dispatchAll();
+        mLooper.stopAutoDispatch();
         verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
                 argThat((IntentFilter filter) ->
                         filter.hasAction(Intent.ACTION_USER_REMOVED)));
@@ -3935,13 +5227,77 @@
     }
 
     @Test
+    public void testBluetoothBroadcastHandling() {
+        when(mWifiInjector.getPasspointProvisionerHandlerThread())
+                .thenReturn(mock(HandlerThread.class));
+        mLooper.startAutoDispatch();
+        mWifiServiceImpl.checkAndStartWifi();
+        mWifiServiceImpl.handleBootCompleted();
+        mLooper.stopAutoDispatch();
+        verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
+                argThat((IntentFilter filter) ->
+                        filter.hasAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)
+                                && filter.hasAction(BluetoothAdapter.ACTION_STATE_CHANGED)));
+
+        {
+            Intent intent = new Intent(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
+            intent.putExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
+                    BluetoothAdapter.STATE_DISCONNECTED);
+            mBroadcastReceiverCaptor.getValue().onReceive(mContext, intent);
+            mLooper.dispatchAll();
+
+            verify(mWifiGlobals).setBluetoothConnected(false);
+            for (ClientModeManager cmm : mClientModeManagers) {
+                verify(cmm).onBluetoothConnectionStateChanged();
+            }
+        }
+
+        {
+            Intent intent = new Intent(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
+            intent.putExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
+                    BluetoothAdapter.STATE_CONNECTED);
+            mBroadcastReceiverCaptor.getValue().onReceive(mContext, intent);
+            mLooper.dispatchAll();
+
+            verify(mWifiGlobals).setBluetoothConnected(true);
+            for (ClientModeManager cmm : mClientModeManagers) {
+                verify(cmm, times(2)).onBluetoothConnectionStateChanged();
+            }
+        }
+
+        {
+            Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED);
+            intent.putExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF);
+            mBroadcastReceiverCaptor.getValue().onReceive(mContext, intent);
+            mLooper.dispatchAll();
+
+            verify(mWifiGlobals).setBluetoothEnabled(false);
+            for (ClientModeManager cmm : mClientModeManagers) {
+                verify(cmm, times(3)).onBluetoothConnectionStateChanged();
+            }
+        }
+
+        {
+            Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED);
+            intent.putExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_ON);
+            mBroadcastReceiverCaptor.getValue().onReceive(mContext, intent);
+            mLooper.dispatchAll();
+
+            verify(mWifiGlobals).setBluetoothEnabled(true);
+            for (ClientModeManager cmm : mClientModeManagers) {
+                verify(cmm, times(4)).onBluetoothConnectionStateChanged();
+            }
+        }
+    }
+
+    @Test
     public void testUserRemovedBroadcastHandlingWithWrongIntentAction() {
         when(mWifiInjector.getPasspointProvisionerHandlerThread())
                 .thenReturn(mock(HandlerThread.class));
+        mLooper.startAutoDispatch();
         mWifiServiceImpl.checkAndStartWifi();
-        mLooper.dispatchAll();
         mWifiServiceImpl.handleBootCompleted();
-        mLooper.dispatchAll();
+        mLooper.stopAutoDispatch();
         verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
                 argThat((IntentFilter filter) ->
                         filter.hasAction(Intent.ACTION_USER_REMOVED)));
@@ -4010,7 +5366,7 @@
         Intent intent = new Intent(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED);
         intent.putExtra(Intent.EXTRA_USER_HANDLE, userHandle);
         mBroadcastReceiverCaptor.getValue().onReceive(mContext, intent);
-        verifyNoMoreInteractions(mWifiCountryCode);
+        verify(mWifiCountryCode, never()).setTelephonyCountryCodeAndUpdate(any());
     }
 
     /**
@@ -4030,27 +5386,85 @@
         intent.putExtra(Intent.EXTRA_USER_HANDLE, userHandle);
         intent.putExtra(TelephonyManager.EXTRA_SIM_STATE, Intent.SIM_STATE_ABSENT);
         mBroadcastReceiverCaptor.getValue().onReceive(mContext, intent);
-        verifyNoMoreInteractions(mWifiCountryCode);
+        verify(mWifiCountryCode, never()).setTelephonyCountryCodeAndUpdate(any());
     }
 
     /**
-     * Verifies that entering airplane mode does not reset country code.
+     * Verify removing sim will also remove an ephemeral Passpoint Provider. And reset carrier
+     * privileged suggestor apps.
      */
     @Test
-    public void testEnterAirplaneModeNotResetCountryCode() {
+    public void testResetSimNetworkWhenRemovingSim() throws Exception {
         mWifiServiceImpl.checkAndStartWifi();
         mLooper.dispatchAll();
         verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
-                (IntentFilter) argThat((IntentFilter filter) ->
-                        filter.hasAction(Intent.ACTION_AIRPLANE_MODE_CHANGED)));
+                argThat((IntentFilter filter) ->
+                        filter.hasAction(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED)));
 
-        when(mSettingsStore.isAirplaneModeOn()).thenReturn(true);
-
-        // Send the broadcast
-        Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        Intent intent = new Intent(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED);
+        intent.putExtra(TelephonyManager.EXTRA_SIM_STATE, TelephonyManager.SIM_STATE_ABSENT);
         mBroadcastReceiverCaptor.getValue().onReceive(mContext, intent);
+        mLooper.dispatchAll();
 
-        verifyNoMoreInteractions(mWifiCountryCode);
+        verify(mWifiConfigManager).resetSimNetworks();
+        verify(mWifiConfigManager).stopRestrictingAutoJoinToSubscriptionId();
+        verify(mSimRequiredNotifier, never()).dismissSimRequiredNotification();
+        verify(mWifiNetworkSuggestionsManager).resetCarrierPrivilegedApps();
+        verify(mWifiConfigManager, never()).removeAllEphemeralOrPasspointConfiguredNetworks();
+        verify(mWifiNetworkSuggestionsManager).resetSimNetworkSuggestions();
+        verify(mPasspointManager).resetSimPasspointNetwork();
+    }
+
+    /**
+     * Verify inserting sim will reset carrier privileged suggestor apps.
+     * and remove any previous notifications due to sim removal
+     */
+    @Test
+    public void testResetCarrierPrivilegedAppsWhenInsertingSim() throws Exception {
+        mWifiServiceImpl.checkAndStartWifi();
+        mLooper.dispatchAll();
+        verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
+                argThat((IntentFilter filter) ->
+                        filter.hasAction(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED)));
+
+        Intent intent = new Intent(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED);
+        intent.putExtra(TelephonyManager.EXTRA_SIM_STATE, TelephonyManager.SIM_STATE_LOADED);
+        mBroadcastReceiverCaptor.getValue().onReceive(mContext, intent);
+        mLooper.dispatchAll();
+
+        verify(mWifiConfigManager, never()).resetSimNetworks();
+        verify(mPasspointManager, never()).resetSimPasspointNetwork();
+        verify(mWifiNetworkSuggestionsManager, never()).resetSimNetworkSuggestions();
+        verify(mWifiConfigManager, never()).stopRestrictingAutoJoinToSubscriptionId();
+        verify(mSimRequiredNotifier).dismissSimRequiredNotification();
+        verify(mWifiNetworkSuggestionsManager).resetCarrierPrivilegedApps();
+        verify(mWifiConfigManager, never()).removeAllEphemeralOrPasspointConfiguredNetworks();
+        verify(mWifiConfigManager).enableTemporaryDisabledNetworks();
+        verify(mWifiConnectivityManager).forceConnectivityScan(any());
+    }
+
+    @Test
+    public void testResetSimNetworkWhenDefaultDataSimChanged() throws Exception {
+        mWifiServiceImpl.checkAndStartWifi();
+        mLooper.dispatchAll();
+        verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
+                argThat((IntentFilter filter) ->
+                        filter.hasAction(
+                                TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)));
+
+        Intent intent = new Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
+        intent.putExtra("subscription", 1);
+        mBroadcastReceiverCaptor.getValue().onReceive(mContext, intent);
+        mLooper.dispatchAll();
+
+        verify(mWifiConfigManager).resetSimNetworks();
+        verify(mWifiConfigManager).stopRestrictingAutoJoinToSubscriptionId();
+        verify(mSimRequiredNotifier).dismissSimRequiredNotification();
+        verify(mWifiNetworkSuggestionsManager).resetCarrierPrivilegedApps();
+        verify(mWifiConfigManager).removeEphemeralCarrierNetworks();
+        verify(mWifiNetworkSuggestionsManager).resetSimNetworkSuggestions();
+        verify(mPasspointManager).resetSimPasspointNetwork();
+        verify(mWifiDataStall).resetPhoneStateListener();
     }
 
     /**
@@ -4063,8 +5477,7 @@
                 .enforceCallingOrSelfPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
                         eq("WifiService"));
         try {
-            mWifiServiceImpl.registerTrafficStateCallback(mAppBinder, mTrafficStateCallback,
-                    TEST_TRAFFIC_STATE_CALLBACK_IDENTIFIER);
+            mWifiServiceImpl.registerTrafficStateCallback(mTrafficStateCallback);
             fail("expected SecurityException");
         } catch (SecurityException expected) {
         }
@@ -4077,8 +5490,7 @@
     @Test
     public void registerTrafficStateCallbackThrowsIllegalArgumentExceptionOnInvalidArguments() {
         try {
-            mWifiServiceImpl.registerTrafficStateCallback(
-                    mAppBinder, null, TEST_TRAFFIC_STATE_CALLBACK_IDENTIFIER);
+            mWifiServiceImpl.registerTrafficStateCallback(null);
             fail("expected IllegalArgumentException");
         } catch (IllegalArgumentException expected) {
         }
@@ -4094,7 +5506,7 @@
                 .enforceCallingOrSelfPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
                         eq("WifiService"));
         try {
-            mWifiServiceImpl.unregisterTrafficStateCallback(TEST_TRAFFIC_STATE_CALLBACK_IDENTIFIER);
+            mWifiServiceImpl.unregisterTrafficStateCallback(mTrafficStateCallback);
             fail("expected SecurityException");
         } catch (SecurityException expected) {
         }
@@ -4105,11 +5517,9 @@
      */
     @Test
     public void registerTrafficStateCallbackAndVerify() throws Exception {
-        mWifiServiceImpl.registerTrafficStateCallback(
-                mAppBinder, mTrafficStateCallback, TEST_TRAFFIC_STATE_CALLBACK_IDENTIFIER);
+        mWifiServiceImpl.registerTrafficStateCallback(mTrafficStateCallback);
         mLooper.dispatchAll();
-        verify(mWifiTrafficPoller).addCallback(
-                mAppBinder, mTrafficStateCallback, TEST_TRAFFIC_STATE_CALLBACK_IDENTIFIER);
+        verify(mWifiTrafficPoller).addCallback(mTrafficStateCallback);
     }
 
     /**
@@ -4117,9 +5527,9 @@
      */
     @Test
     public void unregisterTrafficStateCallbackAndVerify() throws Exception {
-        mWifiServiceImpl.unregisterTrafficStateCallback(0);
+        mWifiServiceImpl.unregisterTrafficStateCallback(mTrafficStateCallback);
         mLooper.dispatchAll();
-        verify(mWifiTrafficPoller).removeCallback(0);
+        verify(mWifiTrafficPoller).removeCallback(mTrafficStateCallback);
     }
 
     /**
@@ -4132,9 +5542,7 @@
                 .enforceCallingOrSelfPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
                         eq("WifiService"));
         try {
-            mWifiServiceImpl.registerNetworkRequestMatchCallback(mAppBinder,
-                    mNetworkRequestMatchCallback,
-                    TEST_NETWORK_REQUEST_MATCH_CALLBACK_IDENTIFIER);
+            mWifiServiceImpl.registerNetworkRequestMatchCallback(mNetworkRequestMatchCallback);
             fail("expected SecurityException");
         } catch (SecurityException expected) {
         }
@@ -4148,8 +5556,7 @@
     public void
             registerNetworkRequestMatchCallbackThrowsIllegalArgumentExceptionOnInvalidArguments() {
         try {
-            mWifiServiceImpl.registerNetworkRequestMatchCallback(
-                    mAppBinder, null, TEST_NETWORK_REQUEST_MATCH_CALLBACK_IDENTIFIER);
+            mWifiServiceImpl.registerNetworkRequestMatchCallback(null);
             fail("expected IllegalArgumentException");
         } catch (IllegalArgumentException expected) {
         }
@@ -4165,8 +5572,7 @@
                 .enforceCallingOrSelfPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
                         eq("WifiService"));
         try {
-            mWifiServiceImpl.unregisterNetworkRequestMatchCallback(
-                    TEST_NETWORK_REQUEST_MATCH_CALLBACK_IDENTIFIER);
+            mWifiServiceImpl.unregisterNetworkRequestMatchCallback(mNetworkRequestMatchCallback);
             fail("expected SecurityException");
         } catch (SecurityException expected) {
         }
@@ -4174,30 +5580,24 @@
 
     /**
      * Verify that registerNetworkRequestMatchCallback adds callback to
-     * {@link ClientModeImpl}.
+     * {@link ClientModeManager}.
      */
     @Test
     public void registerNetworkRequestMatchCallbackAndVerify() throws Exception {
-        mWifiServiceImpl.registerNetworkRequestMatchCallback(
-                mAppBinder, mNetworkRequestMatchCallback,
-                TEST_NETWORK_REQUEST_MATCH_CALLBACK_IDENTIFIER);
+        mWifiServiceImpl.registerNetworkRequestMatchCallback(mNetworkRequestMatchCallback);
         mLooper.dispatchAll();
-        verify(mClientModeImpl).addNetworkRequestMatchCallback(
-                mAppBinder, mNetworkRequestMatchCallback,
-                TEST_NETWORK_REQUEST_MATCH_CALLBACK_IDENTIFIER);
+        verify(mWifiNetworkFactory).addCallback(mNetworkRequestMatchCallback);
     }
 
     /**
      * Verify that unregisterNetworkRequestMatchCallback removes callback from
-     * {@link ClientModeImpl}.
+     * {@link ClientModeManager}.
      */
     @Test
     public void unregisterNetworkRequestMatchCallbackAndVerify() throws Exception {
-        mWifiServiceImpl.unregisterNetworkRequestMatchCallback(
-                TEST_NETWORK_REQUEST_MATCH_CALLBACK_IDENTIFIER);
+        mWifiServiceImpl.unregisterNetworkRequestMatchCallback(mNetworkRequestMatchCallback);
         mLooper.dispatchAll();
-        verify(mClientModeImpl).removeNetworkRequestMatchCallback(
-                TEST_NETWORK_REQUEST_MATCH_CALLBACK_IDENTIFIER);
+        verify(mWifiNetworkFactory).removeCallback(mNetworkRequestMatchCallback);
     }
 
     /**
@@ -4218,7 +5618,6 @@
         credential.setRealm("example.com");
         config.setCredential(credential);
 
-        mWifiServiceImpl.mClientModeImplChannel = mAsyncChannel;
         when(mWifiConfigManager.getSavedNetworks(anyInt()))
                 .thenReturn(Arrays.asList(network));
         when(mPasspointManager.getProviderConfigs(anyInt(), anyBoolean()))
@@ -4238,7 +5637,7 @@
         verify(mPasspointManager).clearAnqpRequestsAndFlushCache();
         verify(mWifiConfigManager).clearUserTemporarilyDisabledList();
         verify(mWifiConfigManager).removeAllEphemeralOrPasspointConfiguredNetworks();
-        verify(mClientModeImpl).clearNetworkRequestUserApprovedAccessPoints();
+        verify(mWifiNetworkFactory).clear();
         verify(mWifiNetworkSuggestionsManager).clear();
         verify(mWifiScoreCard).clear();
         verify(mWifiHealthMonitor).clear();
@@ -4297,7 +5696,8 @@
 
         WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
         mLooper.startAutoDispatch();
-        assertEquals(0, mWifiServiceImpl.addOrUpdateNetwork(config, TEST_PACKAGE_NAME));
+        assertEquals(0, WifiConfigurationUtil.removeSecurityTypeFromNetworkId(
+                mWifiServiceImpl.addOrUpdateNetwork(config, TEST_PACKAGE_NAME)));
         mLooper.stopAutoDispatchAndIgnoreExceptions();
 
         verifyCheckChangePermission(TEST_PACKAGE_NAME);
@@ -4318,7 +5718,8 @@
 
         WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
         mLooper.startAutoDispatch();
-        assertEquals(0, mWifiServiceImpl.addOrUpdateNetwork(config, TEST_PACKAGE_NAME));
+        assertEquals(0, WifiConfigurationUtil.removeSecurityTypeFromNetworkId(
+                mWifiServiceImpl.addOrUpdateNetwork(config, TEST_PACKAGE_NAME)));
         mLooper.stopAutoDispatchAndIgnoreExceptions();
 
         // Ensure that we don't check for change permission.
@@ -4337,13 +5738,14 @@
     public void testAddOrUpdateNetworkIsAllowedForSystemApp() throws Exception {
         doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager)
                 .noteOp(AppOpsManager.OPSTR_CHANGE_WIFI_STATE, Process.myUid(), TEST_PACKAGE_NAME);
-        mApplicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
+        when(mWifiPermissionsUtil.isSystem(anyString(), anyInt())).thenReturn(true);
         when(mWifiConfigManager.addOrUpdateNetwork(any(),  anyInt(), any())).thenReturn(
                 new NetworkUpdateResult(0));
 
         WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
         mLooper.startAutoDispatch();
-        assertEquals(0, mWifiServiceImpl.addOrUpdateNetwork(config, TEST_PACKAGE_NAME));
+        assertEquals(0, WifiConfigurationUtil.removeSecurityTypeFromNetworkId(
+                mWifiServiceImpl.addOrUpdateNetwork(config, TEST_PACKAGE_NAME)));
         mLooper.stopAutoDispatchAndIgnoreExceptions();
 
         verifyCheckChangePermission(TEST_PACKAGE_NAME);
@@ -4366,7 +5768,8 @@
 
         WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
         mLooper.startAutoDispatch();
-        assertEquals(0, mWifiServiceImpl.addOrUpdateNetwork(config, TEST_PACKAGE_NAME));
+        assertEquals(0, WifiConfigurationUtil.removeSecurityTypeFromNetworkId(
+                mWifiServiceImpl.addOrUpdateNetwork(config, TEST_PACKAGE_NAME)));
         mLooper.stopAutoDispatchAndIgnoreExceptions();
 
         verifyCheckChangePermission(TEST_PACKAGE_NAME);
@@ -4389,7 +5792,8 @@
 
         WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
         mLooper.startAutoDispatch();
-        assertEquals(0, mWifiServiceImpl.addOrUpdateNetwork(config, TEST_PACKAGE_NAME));
+        assertEquals(0, WifiConfigurationUtil.removeSecurityTypeFromNetworkId(
+                mWifiServiceImpl.addOrUpdateNetwork(config, TEST_PACKAGE_NAME)));
         mLooper.stopAutoDispatchAndIgnoreExceptions();
 
         verifyCheckChangePermission(TEST_PACKAGE_NAME);
@@ -4411,7 +5815,8 @@
 
         WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
         mLooper.startAutoDispatch();
-        assertEquals(0, mWifiServiceImpl.addOrUpdateNetwork(config, TEST_PACKAGE_NAME));
+        assertEquals(0, WifiConfigurationUtil.removeSecurityTypeFromNetworkId(
+                mWifiServiceImpl.addOrUpdateNetwork(config, TEST_PACKAGE_NAME)));
         mLooper.stopAutoDispatchAndIgnoreExceptions();
 
         verifyCheckChangePermission(TEST_PACKAGE_NAME);
@@ -4419,6 +5824,92 @@
         verify(mWifiMetrics).incrementNumAddOrUpdateNetworkCalls();
     }
 
+    private void verifyAddOrUpdateNetworkPrivilegedDoesNotThrowException() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+        when(mWifiConfigManager.addOrUpdateNetwork(any(), anyInt(), anyString()))
+                .thenReturn(new NetworkUpdateResult(0));
+        mLooper.startAutoDispatch();
+        mWifiServiceImpl.addOrUpdateNetworkPrivileged(config, TEST_PACKAGE_NAME);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+
+        verify(mWifiConfigManager).addOrUpdateNetwork(any(),  anyInt(), any());
+        verify(mWifiMetrics).incrementNumAddOrUpdateNetworkCalls();
+    }
+
+    /**
+     * Verify that addOrUpdateNetworkPrivileged throws a SecurityException if the calling app
+     * has no permissions.
+     */
+    @Test
+    public void testAddOrUpdateNetworkPrivilegedNotAllowedForNormalApps() throws Exception {
+        try {
+            WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+            mWifiServiceImpl.addOrUpdateNetworkPrivileged(config, TEST_PACKAGE_NAME);
+            fail("Expected SecurityException for apps without permission");
+        } catch (SecurityException e) {
+        }
+    }
+
+    /**
+     * Verify that a privileged app with NETWORK_SETTINGS permission is allowed to call
+     * addOrUpdateNetworkPrivileged.
+     */
+    @Test
+    public void testAddOrUpdateNetworkPrivilegedIsAllowedForPrivilegedApp() throws Exception {
+        when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+        verifyAddOrUpdateNetworkPrivilegedDoesNotThrowException();
+    }
+
+    /**
+     * Verify that a system app is allowed to call addOrUpdateNetworkPrivileged.
+     */
+    @Test
+    public void testAddOrUpdateNetworkPrivilegedIsAllowedForSystemApp() throws Exception {
+        when(mWifiPermissionsUtil.isSystem(anyString(), anyInt())).thenReturn(true);
+        verifyAddOrUpdateNetworkPrivilegedDoesNotThrowException();
+    }
+
+    /**
+     * Verify that a Device Owner (DO) app is allowed to call addOrUpdateNetworkPrivileged.
+     */
+    @Test
+    public void testAddOrUpdateNetworkPrivilegedIsAllowedForDOApp() throws Exception {
+        when(mWifiPermissionsUtil.isDeviceOwner(Binder.getCallingUid(), TEST_PACKAGE_NAME))
+                .thenReturn(true);
+        verifyAddOrUpdateNetworkPrivilegedDoesNotThrowException();
+    }
+
+    /**
+     * Verify that a Profile Owner (PO) app is allowed to call addOrUpdateNetworkPrivileged.
+     */
+    @Test
+    public void testAddOrUpdateNetworkPrivilegedIsAllowedForPOApp() throws Exception {
+        when(mWifiPermissionsUtil.isProfileOwner(Binder.getCallingUid(), TEST_PACKAGE_NAME))
+                .thenReturn(true);
+        verifyAddOrUpdateNetworkPrivilegedDoesNotThrowException();
+    }
+
+    /**
+     * Verify the proper status code is returned when addOrUpdateNetworkPrivileged failed due to
+     * a failure in WifiConfigManager.addOrUpdateNetwork().
+     */
+    @Test
+    public void testAddOrUpdateNetworkInvalidConfiguration() throws Exception {
+        when(mWifiPermissionsUtil.isSystem(anyString(), anyInt())).thenReturn(true);
+        when(mWifiConfigManager.addOrUpdateNetwork(any(), anyInt(), anyString()))
+                .thenReturn(new NetworkUpdateResult(-1));
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+        mLooper.startAutoDispatch();
+        WifiManager.AddNetworkResult result = mWifiServiceImpl.addOrUpdateNetworkPrivileged(
+                config, TEST_PACKAGE_NAME);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+
+        assertEquals(WifiManager.AddNetworkResult.STATUS_ADD_WIFI_CONFIG_FAILURE,
+                result.statusCode);
+        assertEquals(-1, result.networkId);
+    }
+
     /**
      * Verify that enableNetwork is allowed for privileged Apps
      */
@@ -4429,20 +5920,20 @@
         doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager)
                 .noteOp(AppOpsManager.OPSTR_CHANGE_WIFI_STATE, Process.myUid(), TEST_PACKAGE_NAME);
 
-        doAnswer(new MockAnswerUtil.AnswerWithArguments() {
-            public void answer(WifiConfiguration config, int netId, IBinder binder,
-                    IActionListener callback, int callbackIdentifier, int callingUid) {
-                try {
-                    callback.onSuccess(); // return success
-                } catch (RemoteException e) { }
+        doAnswer(new AnswerWithArguments() {
+            public void answer(NetworkUpdateResult result, ActionListenerWrapper callback,
+                    int callingUid) {
+                callback.sendSuccess(); // return success
             }
-        }).when(mClientModeImpl).connect(
-                isNull(), eq(TEST_NETWORK_ID), any(), any(), anyInt(), anyInt());
+        }).when(mConnectHelper).connectToNetwork(
+                eq(new NetworkUpdateResult(TEST_NETWORK_ID)), any(), anyInt());
 
+        mLooper.startAutoDispatch();
         assertTrue(mWifiServiceImpl.enableNetwork(TEST_NETWORK_ID, true, TEST_PACKAGE_NAME));
+        mLooper.stopAutoDispatch();
 
-        verify(mClientModeImpl).connect(isNull(), eq(TEST_NETWORK_ID), any(), any(), anyInt(),
-                anyInt());
+        verify(mConnectHelper).connectToNetwork(
+                eq(new NetworkUpdateResult(TEST_NETWORK_ID)), any(), anyInt());
         verify(mWifiMetrics).incrementNumEnableNetworkCalls();
     }
 
@@ -4459,20 +5950,20 @@
         when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(),
                 eq(Build.VERSION_CODES.Q), anyInt())).thenReturn(true);
 
-        doAnswer(new MockAnswerUtil.AnswerWithArguments() {
-            public void answer(WifiConfiguration config, int netId, IBinder binder,
-                    IActionListener callback, int callbackIdentifier, int callingUid) {
-                try {
-                    callback.onSuccess(); // return success
-                } catch (RemoteException e) { }
+        doAnswer(new AnswerWithArguments() {
+            public void answer(NetworkUpdateResult result, ActionListenerWrapper callback,
+                    int callingUid) {
+                callback.sendSuccess(); // return success
             }
-        }).when(mClientModeImpl).connect(
-                isNull(), eq(TEST_NETWORK_ID), any(), any(), anyInt(), anyInt());
+        }).when(mConnectHelper).connectToNetwork(
+                eq(new NetworkUpdateResult(TEST_NETWORK_ID)), any(), anyInt());
 
+        mLooper.startAutoDispatch();
         assertTrue(mWifiServiceImpl.enableNetwork(TEST_NETWORK_ID, true, TEST_PACKAGE_NAME));
+        mLooper.stopAutoDispatch();
 
-        verify(mClientModeImpl).connect(isNull(), eq(TEST_NETWORK_ID), any(), any(), anyInt(),
-                anyInt());
+        verify(mConnectHelper).connectToNetwork(
+                eq(new NetworkUpdateResult(TEST_NETWORK_ID)), any(), anyInt());
         verify(mWifiMetrics).incrementNumEnableNetworkCalls();
     }
 
@@ -4507,10 +5998,11 @@
         doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager)
                 .noteOp(AppOpsManager.OPSTR_CHANGE_WIFI_STATE, Process.myUid(), TEST_PACKAGE_NAME);
 
+        mLooper.startAutoDispatch();
         mWifiServiceImpl.enableNetwork(TEST_NETWORK_ID, true, TEST_PACKAGE_NAME);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
 
-        verify(mClientModeImpl, never()).connect(isNull(), anyInt(), any(), any(), anyInt(),
-                anyInt());
+        verify(mConnectHelper, never()).connectToNetwork(any(), any(), anyInt());
         verify(mWifiMetrics, never()).incrementNumEnableNetworkCalls();
     }
 
@@ -4600,6 +6092,31 @@
                 eq(TEST_PACKAGE_NAME));
     }
 
+    @Test(expected = SecurityException.class)
+    public void testRemoveNonCallerConfiguredNetworks_NormalAppCaller_ThrowsException() {
+        mWifiServiceImpl.removeNonCallerConfiguredNetworks(TEST_PACKAGE_NAME);
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testRemoveNonCallerConfiguredNetworks_CallerIsProfileOwner_ThrowsException() {
+        when(mWifiPermissionsUtil.isProfileOwner(Binder.getCallingUid(), TEST_PACKAGE_NAME))
+                .thenReturn(true);
+        mWifiServiceImpl.removeNonCallerConfiguredNetworks(TEST_PACKAGE_NAME);
+    }
+
+    @Test
+    public void testRemoveNonCallerConfiguredNetworks_NetworksRemoved() {
+        final int callerUid = Binder.getCallingUid();
+        when(mWifiPermissionsUtil.isDeviceOwner(Binder.getCallingUid(), TEST_PACKAGE_NAME))
+                .thenReturn(true);
+
+        mLooper.startAutoDispatch();
+        mWifiServiceImpl.removeNonCallerConfiguredNetworks(TEST_PACKAGE_NAME);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+
+        verify(mWifiConfigManager).removeNonCallerConfiguredNetwork(eq(callerUid));
+    }
+
     /**
      * Ensure that we invoke {@link WifiNetworkSuggestionsManager} to get network
      * suggestions.
@@ -4661,14 +6178,14 @@
      */
     @Test
     public void testGetFactoryMacAddresses() throws Exception {
-        when(mClientModeImpl.getFactoryMacAddress()).thenReturn(TEST_FACTORY_MAC);
+        when(mClientModeManager.getFactoryMacAddress()).thenReturn(TEST_FACTORY_MAC);
         when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(true);
         mLooper.startAutoDispatch();
         final String[] factoryMacs = mWifiServiceImpl.getFactoryMacAddresses();
         mLooper.stopAutoDispatchAndIgnoreExceptions();
         assertEquals(1, factoryMacs.length);
         assertEquals(TEST_FACTORY_MAC, factoryMacs[0]);
-        verify(mClientModeImpl).getFactoryMacAddress();
+        verify(mClientModeManager).getFactoryMacAddress();
     }
 
     /**
@@ -4683,7 +6200,7 @@
         mLooper.startAutoDispatch();
         assertArrayEquals(new String[0], mWifiServiceImpl.getFactoryMacAddresses());
         mLooper.stopAutoDispatchAndIgnoreExceptions();
-        verify(mClientModeImpl, never()).getFactoryMacAddress();
+        verify(mClientModeManager, never()).getFactoryMacAddress();
     }
 
     /**
@@ -4691,12 +6208,12 @@
      */
     @Test
     public void testGetFactoryMacAddressesFail() throws Exception {
-        when(mClientModeImpl.getFactoryMacAddress()).thenReturn(null);
+        when(mClientModeManager.getFactoryMacAddress()).thenReturn(null);
         when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(true);
         mLooper.startAutoDispatch();
         assertArrayEquals(new String[0], mWifiServiceImpl.getFactoryMacAddresses());
         mLooper.stopAutoDispatchAndIgnoreExceptions();
-        verify(mClientModeImpl).getFactoryMacAddress();
+        verify(mClientModeManager).getFactoryMacAddress();
     }
 
     /**
@@ -4705,7 +6222,7 @@
      */
     @Test
     public void testGetFactoryMacAddressesFailNoNetworkSettingsPermission() throws Exception {
-        when(mClientModeImpl.getFactoryMacAddress()).thenReturn(TEST_FACTORY_MAC);
+        when(mClientModeManager.getFactoryMacAddress()).thenReturn(TEST_FACTORY_MAC);
         when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(false);
         try {
             mLooper.startAutoDispatch();
@@ -4722,17 +6239,13 @@
      * Verify that a call to setDeviceMobilityState throws a SecurityException if the
      * caller does not have WIFI_SET_DEVICE_MOBILITY_STATE permission.
      */
-    @Test
+    @Test(expected = SecurityException.class)
     public void setDeviceMobilityStateThrowsSecurityExceptionOnMissingPermissions() {
         doThrow(new SecurityException()).when(mContext)
                 .enforceCallingOrSelfPermission(
                         eq(android.Manifest.permission.WIFI_SET_DEVICE_MOBILITY_STATE),
                         eq("WifiService"));
-        try {
-            mWifiServiceImpl.setDeviceMobilityState(DEVICE_MOBILITY_STATE_STATIONARY);
-            fail("expected SecurityException");
-        } catch (SecurityException expected) {
-        }
+        mWifiServiceImpl.setDeviceMobilityState(DEVICE_MOBILITY_STATE_STATIONARY);
     }
 
     /**
@@ -4741,9 +6254,16 @@
     @Test
     public void setDeviceMobilityStateRunsOnHandler() {
         mWifiServiceImpl.setDeviceMobilityState(DEVICE_MOBILITY_STATE_STATIONARY);
-        verify(mClientModeImpl, never()).setDeviceMobilityState(anyInt());
+        verify(mWifiConnectivityManager, never())
+                .setDeviceMobilityState(DEVICE_MOBILITY_STATE_STATIONARY);
+        verify(mWifiHealthMonitor, never())
+                .setDeviceMobilityState(DEVICE_MOBILITY_STATE_STATIONARY);
+        verify(mWifiDataStall, never())
+                .setDeviceMobilityState(DEVICE_MOBILITY_STATE_STATIONARY);
         mLooper.dispatchAll();
-        verify(mClientModeImpl).setDeviceMobilityState(eq(DEVICE_MOBILITY_STATE_STATIONARY));
+        verify(mWifiConnectivityManager).setDeviceMobilityState(DEVICE_MOBILITY_STATE_STATIONARY);
+        verify(mWifiHealthMonitor).setDeviceMobilityState(DEVICE_MOBILITY_STATE_STATIONARY);
+        verify(mWifiDataStall).setDeviceMobilityState(DEVICE_MOBILITY_STATE_STATIONARY);
     }
 
     /**
@@ -4757,8 +6277,7 @@
                         eq(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE),
                         eq("WifiService"));
         try {
-            mWifiServiceImpl.addOnWifiUsabilityStatsListener(mAppBinder,
-                    mOnWifiUsabilityStatsListener, TEST_WIFI_USABILITY_STATS_LISTENER_IDENTIFIER);
+            mWifiServiceImpl.addOnWifiUsabilityStatsListener(mOnWifiUsabilityStatsListener);
             fail("expected SecurityException");
         } catch (SecurityException expected) {
         }
@@ -4771,8 +6290,7 @@
     @Test
     public void testAddStatsListenerThrowsIllegalArgumentExceptionOnInvalidArguments() {
         try {
-            mWifiServiceImpl.addOnWifiUsabilityStatsListener(
-                    mAppBinder, null, TEST_WIFI_USABILITY_STATS_LISTENER_IDENTIFIER);
+            mWifiServiceImpl.addOnWifiUsabilityStatsListener(null);
             fail("expected IllegalArgumentException");
         } catch (IllegalArgumentException expected) {
         }
@@ -4789,8 +6307,7 @@
                         eq(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE),
                         eq("WifiService"));
         try {
-            mWifiServiceImpl.removeOnWifiUsabilityStatsListener(
-                    TEST_WIFI_USABILITY_STATS_LISTENER_IDENTIFIER);
+            mWifiServiceImpl.removeOnWifiUsabilityStatsListener(mOnWifiUsabilityStatsListener);
             fail("expected SecurityException");
         } catch (SecurityException expected) {
         }
@@ -4801,11 +6318,9 @@
      */
     @Test
     public void testAddOnWifiUsabilityStatsListenerAndVerify() throws Exception {
-        mWifiServiceImpl.addOnWifiUsabilityStatsListener(mAppBinder, mOnWifiUsabilityStatsListener,
-                TEST_WIFI_USABILITY_STATS_LISTENER_IDENTIFIER);
+        mWifiServiceImpl.addOnWifiUsabilityStatsListener(mOnWifiUsabilityStatsListener);
         mLooper.dispatchAll();
-        verify(mWifiMetrics).addOnWifiUsabilityListener(mAppBinder, mOnWifiUsabilityStatsListener,
-                TEST_WIFI_USABILITY_STATS_LISTENER_IDENTIFIER);
+        verify(mWifiMetrics).addOnWifiUsabilityListener(mOnWifiUsabilityStatsListener);
     }
 
     /**
@@ -4814,9 +6329,9 @@
      */
     @Test
     public void testRemoveOnWifiUsabilityStatsListenerAndVerify() throws Exception {
-        mWifiServiceImpl.removeOnWifiUsabilityStatsListener(0);
+        mWifiServiceImpl.removeOnWifiUsabilityStatsListener(mOnWifiUsabilityStatsListener);
         mLooper.dispatchAll();
-        verify(mWifiMetrics).removeOnWifiUsabilityListener(0);
+        verify(mWifiMetrics).removeOnWifiUsabilityListener(mOnWifiUsabilityStatsListener);
     }
 
     /**
@@ -4837,19 +6352,21 @@
     }
 
     /**
-     * Verify that mClientModeImpl in WifiServiceImpl is being updated on Wifi usability score
+     * Verify that mClientModeManager in WifiServiceImpl is being updated on Wifi usability score
      * update event.
      */
     @Test
     public void testWifiUsabilityScoreUpdateAfterScoreEvent() {
-        mWifiServiceImpl.updateWifiUsabilityScore(anyInt(), anyInt(), 15);
+        mWifiServiceImpl.updateWifiUsabilityScore(5, 10, 15);
         mLooper.dispatchAll();
-        verify(mClientModeImpl).updateWifiUsabilityScore(anyInt(), anyInt(), anyInt());
+        verify(mWifiMetrics).incrementWifiUsabilityScoreCount(WIFI_IFACE_NAME, 5, 10, 15);
     }
 
     private void startLohsAndTethering(boolean isApConcurrencySupported) throws Exception {
         // initialization
-        when(mActiveModeWarden.canRequestMoreSoftApManagers()).thenReturn(isApConcurrencySupported);
+        when(mActiveModeWarden.canRequestMoreSoftApManagers(
+                eq(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME))))
+                .thenReturn(isApConcurrencySupported);
         // For these tests, always use distinct interface names for LOHS and tethered.
         mLohsInterfaceName = WIFI_IFACE_NAME2;
 
@@ -4858,14 +6375,17 @@
         mLooper.stopAutoDispatchAndIgnoreExceptions();
         reset(mActiveModeWarden);
 
-        when(mActiveModeWarden.canRequestMoreSoftApManagers()).thenReturn(isApConcurrencySupported);
+        when(mActiveModeWarden.canRequestMoreSoftApManagers(
+                eq(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME))))
+                .thenReturn(isApConcurrencySupported);
 
         // start tethering
         mLooper.startAutoDispatch();
-        boolean tetheringResult = mWifiServiceImpl.startSoftAp(null);
+        boolean tetheringResult = mWifiServiceImpl.startSoftAp(null, TEST_PACKAGE_NAME);
         mLooper.stopAutoDispatchAndIgnoreExceptions();
         assertTrue(tetheringResult);
-        verify(mActiveModeWarden).startSoftAp(any());
+        verify(mActiveModeWarden).startSoftAp(any(),
+                eq(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME)));
         mWifiServiceImpl.updateInterfaceIpState(WIFI_IFACE_NAME, IFACE_IP_MODE_TETHERED);
         mLooper.dispatchAll();
     }
@@ -4902,7 +6422,7 @@
      */
     @Test(expected = SecurityException.class)
     public void testStartDppAsConfiguratorInitiatorWithoutPermissions() {
-        mWifiServiceImpl.startDppAsConfiguratorInitiator(mAppBinder, DPP_URI,
+        mWifiServiceImpl.startDppAsConfiguratorInitiator(mAppBinder, TEST_PACKAGE_NAME, DPP_URI,
                 1, 1, mDppCallback);
     }
 
@@ -4916,6 +6436,52 @@
     }
 
     /**
+     * Verify that the call to startDppAsEnrolleeResponder throws a security exception when the
+     * caller doesn't have NETWORK_SETTINGS permissions or NETWORK_SETUP_WIZARD.
+     */
+    @Test(expected = SecurityException.class)
+    public void testStartDppAsEnrolleeResponderWithoutPermissions() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        mWifiServiceImpl.startDppAsEnrolleeResponder(mAppBinder, DPP_PRODUCT_INFO,
+                EASY_CONNECT_CRYPTOGRAPHY_CURVE_PRIME256V1, mDppCallback);
+    }
+
+    /**
+     * Verify that a call to StartDppAsEnrolleeResponder throws an IllegalArgumentException
+     * if the deviceInfo length exceeds the max allowed length.
+     */
+    @Test(expected = SecurityException.class)
+    public void testStartDppAsEnrolleeResponderThrowsIllegalArgumentExceptionOnDeviceInfoMaxLen() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        try {
+            StringBuilder sb = new StringBuilder();
+            sb.append(Strings.repeat("a",
+                    WifiManager.getEasyConnectMaxAllowedResponderDeviceInfoLength() + 2));
+            String deviceInfo = sb.toString();
+            mWifiServiceImpl.startDppAsEnrolleeResponder(mAppBinder, deviceInfo,
+                    EASY_CONNECT_CRYPTOGRAPHY_CURVE_PRIME256V1, mDppCallback);
+            fail("expected IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    /**
+     * Verify that a call to StartDppAsEnrolleeResponder throws an IllegalArgumentException
+     * if the deviceInfo contains characters which are not allowed as per spec (For example
+     * semicolon)
+     */
+    @Test(expected = SecurityException.class)
+    public void testStartDppAsEnrolleeResponderThrowsIllegalArgumentExceptionOnWrongDeviceInfo() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        try {
+            mWifiServiceImpl.startDppAsEnrolleeResponder(mAppBinder, "DPP;TESTER",
+                    EASY_CONNECT_CRYPTOGRAPHY_CURVE_PRIME256V1, mDppCallback);
+            fail("expected IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    /**
      * Verify that the call to stopDppSession throws a security exception when the
      * caller doesn't have NETWORK_SETTINGS permissions or NETWORK_SETUP_WIZARD.
      */
@@ -5032,7 +6598,7 @@
                 .noteOp(AppOpsManager.OPSTR_CHANGE_WIFI_STATE, Process.myUid(), TEST_PACKAGE_NAME);
         when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(),
                 eq(Build.VERSION_CODES.R), anyInt())).thenReturn(false);
-        mApplicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
+        when(mWifiPermissionsUtil.isSystem(anyString(), anyInt())).thenReturn(true);
         PasspointConfiguration config = new PasspointConfiguration();
         HomeSp homeSp = new HomeSp();
         homeSp.setFqdn("test.com");
@@ -5288,11 +6854,15 @@
     public void testHandleBootCompleted() throws Exception {
         when(mWifiInjector.getPasspointProvisionerHandlerThread())
                 .thenReturn(mock(HandlerThread.class));
+        mLooper.startAutoDispatch();
         mWifiServiceImpl.handleBootCompleted();
-        mLooper.dispatchAll();
+        mLooper.stopAutoDispatch();
 
+        verify(mWifiNetworkFactory).register();
+        verify(mUntrustedWifiNetworkFactory).register();
         verify(mPasspointManager).initializeProvisioner(any());
-        verify(mClientModeImpl).handleBootCompleted();
+        verify(mWifiP2pConnection).handleBootCompleted();
+        verify(mWifiCountryCode).registerListener(any(WifiCountryCode.ChangeListener.class));
     }
 
     /**
@@ -5303,6 +6873,11 @@
         mWifiServiceImpl.handleUserSwitch(5);
         mLooper.dispatchAll();
         verify(mWifiConfigManager).handleUserSwitch(5);
+        verify(mWifiNotificationManager).createNotificationChannels();
+        verify(mWifiNetworkSuggestionsManager).resetNotification();
+        verify(mWifiCarrierInfoManager).resetNotification();
+        verify(mOpenNetworkNotifier).clearPendingNotification(false);
+        verify(mWakeupController).resetNotification();
     }
 
     /**
@@ -5373,9 +6948,8 @@
     public void testRegisterSuggestionNetworkCallbackWithMissingPermission() {
         doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
                 eq(ACCESS_WIFI_STATE), eq("WifiService"));
-        mWifiServiceImpl.registerSuggestionConnectionStatusListener(mAppBinder,
-                mSuggestionConnectionStatusListener, NETWORK_CALLBACK_ID, TEST_PACKAGE_NAME,
-                TEST_FEATURE_ID);
+        mWifiServiceImpl.registerSuggestionConnectionStatusListener(
+                mSuggestionConnectionStatusListener, TEST_PACKAGE_NAME, TEST_FEATURE_ID);
     }
 
     /**
@@ -5383,8 +6957,8 @@
      */
     @Test(expected = IllegalArgumentException.class)
     public void testRegisterSuggestionNetworkCallbackWithIllegalArgument() {
-        mWifiServiceImpl.registerSuggestionConnectionStatusListener(mAppBinder, null,
-                NETWORK_CALLBACK_ID, TEST_PACKAGE_NAME, TEST_FEATURE_ID);
+        mWifiServiceImpl.registerSuggestionConnectionStatusListener(null, TEST_PACKAGE_NAME,
+                TEST_FEATURE_ID);
     }
 
     /**
@@ -5395,7 +6969,7 @@
         doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
                 eq(ACCESS_WIFI_STATE), eq("WifiService"));
         mWifiServiceImpl.unregisterSuggestionConnectionStatusListener(
-                NETWORK_CALLBACK_ID, TEST_PACKAGE_NAME);
+                mSuggestionConnectionStatusListener, TEST_PACKAGE_NAME);
     }
 
     /**
@@ -5403,18 +6977,16 @@
      */
     @Test
     public void testRegisterUnregisterSuggestionNetworkCallback() throws Exception {
-        mWifiServiceImpl.registerSuggestionConnectionStatusListener(mAppBinder,
-                mSuggestionConnectionStatusListener, NETWORK_CALLBACK_ID, TEST_PACKAGE_NAME,
-                TEST_FEATURE_ID);
+        mWifiServiceImpl.registerSuggestionConnectionStatusListener(
+                mSuggestionConnectionStatusListener, TEST_PACKAGE_NAME, TEST_FEATURE_ID);
         mLooper.dispatchAll();
         verify(mWifiNetworkSuggestionsManager).registerSuggestionConnectionStatusListener(
-                eq(mAppBinder), eq(mSuggestionConnectionStatusListener), eq(NETWORK_CALLBACK_ID),
-                eq(TEST_PACKAGE_NAME), anyInt());
-        mWifiServiceImpl.unregisterSuggestionConnectionStatusListener(NETWORK_CALLBACK_ID,
-                TEST_PACKAGE_NAME);
+                eq(mSuggestionConnectionStatusListener), eq(TEST_PACKAGE_NAME), anyInt());
+        mWifiServiceImpl.unregisterSuggestionConnectionStatusListener(
+                mSuggestionConnectionStatusListener, TEST_PACKAGE_NAME);
         mLooper.dispatchAll();
         verify(mWifiNetworkSuggestionsManager).unregisterSuggestionConnectionStatusListener(
-                eq(NETWORK_CALLBACK_ID), eq(TEST_PACKAGE_NAME), anyInt());
+                eq(mSuggestionConnectionStatusListener), eq(TEST_PACKAGE_NAME), anyInt());
     }
 
 
@@ -5438,7 +7010,7 @@
         stats.rx_time = 2;
         stats.tx_time_per_level = new int[] {3, 4, 5};
         stats.on_time_scan = 6;
-        when(mClientModeImpl.syncGetLinkLayerStats(any())).thenReturn(stats);
+        when(mClientModeManager.getWifiLinkLayerStats()).thenReturn(stats);
         when(mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE))
                 .thenReturn(7.0);
         when(mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX))
@@ -5477,8 +7049,10 @@
      */
     @Test
     public void getWifiActivityEnergyInfoAsyncFeatureUnsupported() throws Exception {
-        when(mClientModeImpl.syncGetSupportedFeatures(any())).thenReturn(0L);
+        when(mClientModeManager.getSupportedFeatures()).thenReturn(0L);
+        mLooper.startAutoDispatch();
         mWifiServiceImpl.getWifiActivityEnergyInfoAsync(mOnWifiActivityEnergyInfoListener);
+        mLooper.stopAutoDispatch();
         verify(mOnWifiActivityEnergyInfoListener).onWifiActivityEnergyInfo(null);
     }
 
@@ -5488,9 +7062,11 @@
      */
     @Test
     public void getWifiActivityEnergyInfoAsyncSuccess() throws Exception {
-        when(mClientModeImpl.syncGetSupportedFeatures(any())).thenReturn(Long.MAX_VALUE);
+        when(mClientModeManager.getSupportedFeatures()).thenReturn(Long.MAX_VALUE);
         setupReportActivityInfo();
+        mLooper.startAutoDispatch();
         mWifiServiceImpl.getWifiActivityEnergyInfoAsync(mOnWifiActivityEnergyInfoListener);
+        mLooper.stopAutoDispatch();
         ArgumentCaptor<WifiActivityEnergyInfo> infoCaptor =
                 ArgumentCaptor.forClass(WifiActivityEnergyInfo.class);
         verify(mOnWifiActivityEnergyInfoListener).onWifiActivityEnergyInfo(infoCaptor.capture());
@@ -5561,9 +7137,10 @@
     public void testGetWifiConfigsForMatchedNetworkSuggestionsWithSettingPermissions() {
         when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
                 anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+        mLooper.startAutoDispatch();
         mWifiServiceImpl
                 .getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(createScanResultList());
-        mLooper.dispatchAll();
+        mLooper.stopAutoDispatch();
         verify(mWifiNetworkSuggestionsManager)
                 .getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(any());
     }
@@ -5577,9 +7154,10 @@
     public void testGetWifiConfigsForMatchedNetworkSuggestionsWithSetupWizardPermissions() {
         when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETUP_WIZARD),
                 anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+        mLooper.startAutoDispatch();
         mWifiServiceImpl
                 .getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(createScanResultList());
-        mLooper.dispatchAll();
+        mLooper.stopAutoDispatch();
         verify(mWifiNetworkSuggestionsManager)
                 .getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(any());
     }
@@ -5647,10 +7225,11 @@
      */
     @Test
     public void testSetWifiConnectedNetworkScorerAndVerify() throws Exception {
+        mLooper.startAutoDispatch();
         mWifiServiceImpl.setWifiConnectedNetworkScorer(mAppBinder, mWifiConnectedNetworkScorer);
-        mLooper.dispatchAll();
-        verify(mWifiScoreReport).setWifiConnectedNetworkScorer(mAppBinder,
-                mWifiConnectedNetworkScorer);
+        mLooper.stopAutoDispatch();
+        verify(mActiveModeWarden).setWifiConnectedNetworkScorer(
+                mAppBinder, mWifiConnectedNetworkScorer);
     }
 
     /**
@@ -5660,15 +7239,15 @@
     public void testClearWifiConnectedNetworkScorerAndVerify() throws Exception {
         mWifiServiceImpl.clearWifiConnectedNetworkScorer();
         mLooper.dispatchAll();
-        verify(mWifiScoreReport).clearWifiConnectedNetworkScorer();
+        verify(mActiveModeWarden).clearWifiConnectedNetworkScorer();
     }
 
     private long testGetSupportedFeaturesCaseForRtt(
-            long supportedFeaturesFromClientModeImpl, boolean rttDisabled) {
+            long supportedFeaturesFromClientModeManager, boolean rttDisabled) {
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_RTT)).thenReturn(
                 !rttDisabled);
-        when(mClientModeImpl.syncGetSupportedFeatures(any()))
-                .thenReturn(supportedFeaturesFromClientModeImpl);
+        when(mClientModeManager.getSupportedFeatures())
+                .thenReturn(supportedFeaturesFromClientModeManager);
         mLooper.startAutoDispatch();
         long supportedFeatures = mWifiServiceImpl.getSupportedFeatures();
         mLooper.stopAutoDispatchAndIgnoreExceptions();
@@ -5714,7 +7293,7 @@
     }
 
     private long testGetSupportedFeaturesCaseForMacRandomization(
-            long supportedFeaturesFromClientModeImpl, boolean apMacRandomizationEnabled,
+            long supportedFeaturesFromClientModeManager, boolean apMacRandomizationEnabled,
             boolean staConnectedMacRandomizationEnabled, boolean p2pMacRandomizationEnabled) {
         when(mResources.getBoolean(
                 R.bool.config_wifi_connected_mac_randomization_supported))
@@ -5725,8 +7304,8 @@
         when(mResources.getBoolean(
                 R.bool.config_wifi_p2p_mac_randomization_supported))
                 .thenReturn(p2pMacRandomizationEnabled);
-        when(mClientModeImpl.syncGetSupportedFeatures(
-                any())).thenReturn(supportedFeaturesFromClientModeImpl);
+        when(mClientModeManager.getSupportedFeatures())
+                .thenReturn(supportedFeaturesFromClientModeManager);
         mLooper.startAutoDispatch();
         long supportedFeatures = mWifiServiceImpl.getSupportedFeatures();
         mLooper.stopAutoDispatchAndIgnoreExceptions();
@@ -5755,27 +7334,150 @@
                 testGetSupportedFeaturesCaseForMacRandomization(0, true, true, false));
     }
 
+    @Test
+    public void getSupportedFeaturesVerboseLoggingThrottled() {
+        mWifiServiceImpl.enableVerboseLogging(
+                WifiManager.VERBOSE_LOGGING_LEVEL_ENABLED); // this logs
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(1000L);
+        testGetSupportedFeaturesCaseForMacRandomization(0, true, true, false);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(1001L);
+        testGetSupportedFeaturesCaseForMacRandomization(0, true, true, false); // should not log
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(5000L);
+        testGetSupportedFeaturesCaseForMacRandomization(0, true, true, false);
+        testGetSupportedFeaturesCaseForMacRandomization(0, true, false, false);
+        verify(mLog, times(4)).info(any());
+    }
+
     /**
      * Verifies that syncGetSupportedFeatures() adds capabilities based on interface
      * combination.
      */
     @Test
     public void syncGetSupportedFeaturesForStaApConcurrency() {
-        long supportedFeaturesFromClientModeImpl = WifiManager.WIFI_FEATURE_OWE;
-        when(mClientModeImpl.syncGetSupportedFeatures(
-                any())).thenReturn(supportedFeaturesFromClientModeImpl);
+        long supportedFeaturesFromClientModeManager = WifiManager.WIFI_FEATURE_OWE;
+        when(mClientModeManager.getSupportedFeatures())
+                .thenReturn(supportedFeaturesFromClientModeManager);
 
         when(mActiveModeWarden.isStaApConcurrencySupported())
                 .thenReturn(false);
         mLooper.startAutoDispatch();
-        assertEquals(supportedFeaturesFromClientModeImpl,
+        assertEquals(supportedFeaturesFromClientModeManager,
                         mWifiServiceImpl.getSupportedFeatures());
         mLooper.stopAutoDispatchAndIgnoreExceptions();
 
         when(mActiveModeWarden.isStaApConcurrencySupported())
                 .thenReturn(true);
         mLooper.startAutoDispatch();
-        assertEquals(supportedFeaturesFromClientModeImpl | WifiManager.WIFI_FEATURE_AP_STA,
+        assertEquals(supportedFeaturesFromClientModeManager | WifiManager.WIFI_FEATURE_AP_STA,
+                mWifiServiceImpl.getSupportedFeatures());
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+    }
+
+    /**
+     * Verify startRestrictingAutoJoinToSubscriptionId is guarded by NETWORK_SETTINGS
+     * permission.
+     */
+    @Test
+    public void testStartTemporarilyDisablingAllNonCarrierMergedWifiPermission() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_DENIED);
+        when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETUP_WIZARD),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_DENIED);
+        try {
+            mWifiServiceImpl.startRestrictingAutoJoinToSubscriptionId(1);
+            fail();
+        } catch (SecurityException e) {
+            // pass
+        }
+    }
+
+    /**
+     * Verify startRestrictingAutoJoinToSubscriptionId works properly with permission.
+     */
+    @Test
+    public void testStartTemporarilyDisablingAllNonCarrierMergedWifi() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETUP_WIZARD),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+        mWifiServiceImpl.startRestrictingAutoJoinToSubscriptionId(1);
+        mLooper.dispatchAll();
+        verify(mWifiConfigManager).startRestrictingAutoJoinToSubscriptionId(1);
+        verify(mClientModeManager).disconnect();
+    }
+
+    /**
+     * Verify stopRestrictingAutoJoinToSubscriptionId is guarded by NETWORK_SETTINGS
+     * and NETWORK_SETUP_WIZARD permission.
+     */
+    @Test
+    public void testStopTemporarilyDisablingAllNonCarrierMergedWifiPermission() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_DENIED);
+        when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETUP_WIZARD),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_DENIED);
+        try {
+            mWifiServiceImpl.stopRestrictingAutoJoinToSubscriptionId();
+            fail();
+        } catch (SecurityException e) {
+            // pass
+        }
+    }
+
+    /**
+     * Verify stopRestrictingAutoJoinToSubscriptionId works properly with permission.
+     */
+    @Test
+    public void testStopTemporarilyDisablingAllNonCarrierMergedWifi() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+        mWifiServiceImpl.stopRestrictingAutoJoinToSubscriptionId();
+        mLooper.dispatchAll();
+        verify(mWifiConfigManager).stopRestrictingAutoJoinToSubscriptionId();
+    }
+
+    /**
+     * Verifies that syncGetSupportedFeatures() adds capabilities based on interface
+     * combination.
+     */
+    @Test
+    public void syncGetSupportedFeaturesForStaStaConcurrency() {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        long supportedFeaturesFromClientModeManager = WifiManager.WIFI_FEATURE_OWE;
+        when(mClientModeManager.getSupportedFeatures())
+                .thenReturn(supportedFeaturesFromClientModeManager);
+
+        mLooper.startAutoDispatch();
+        assertEquals(supportedFeaturesFromClientModeManager,
+                mWifiServiceImpl.getSupportedFeatures());
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+
+        when(mActiveModeWarden.isStaStaConcurrencySupportedForLocalOnlyConnections())
+                .thenReturn(true);
+        mLooper.startAutoDispatch();
+        assertEquals(supportedFeaturesFromClientModeManager
+                        | WifiManager.WIFI_FEATURE_ADDITIONAL_STA_LOCAL_ONLY,
+                mWifiServiceImpl.getSupportedFeatures());
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+
+        when(mActiveModeWarden.isStaStaConcurrencySupportedForMbb()).thenReturn(true);
+        mLooper.startAutoDispatch();
+        assertEquals(supportedFeaturesFromClientModeManager
+                        | WifiManager.WIFI_FEATURE_ADDITIONAL_STA_LOCAL_ONLY
+                        | WifiManager.WIFI_FEATURE_ADDITIONAL_STA_MBB,
+                mWifiServiceImpl.getSupportedFeatures());
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+
+        when(mActiveModeWarden.isStaStaConcurrencySupportedForRestrictedConnections())
+                .thenReturn(true);
+        mLooper.startAutoDispatch();
+        assertEquals(supportedFeaturesFromClientModeManager
+                        | WifiManager.WIFI_FEATURE_ADDITIONAL_STA_LOCAL_ONLY
+                        | WifiManager.WIFI_FEATURE_ADDITIONAL_STA_MBB
+                        | WifiManager.WIFI_FEATURE_ADDITIONAL_STA_RESTRICTED,
                 mWifiServiceImpl.getSupportedFeatures());
         mLooper.stopAutoDispatchAndIgnoreExceptions();
     }
@@ -5853,11 +7555,11 @@
         doNothing().when(mContext)
                 .enforceCallingOrSelfPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
                         eq("WifiService"));
-        mWifiServiceImpl.setScanAlwaysAvailable(true);
+        mWifiServiceImpl.setScanAlwaysAvailable(true, TEST_PACKAGE_NAME);
         verify(mSettingsStore).handleWifiScanAlwaysAvailableToggled(true);
         verify(mActiveModeWarden).scanAlwaysModeChanged();
 
-        mWifiServiceImpl.setScanAlwaysAvailable(false);
+        mWifiServiceImpl.setScanAlwaysAvailable(false, TEST_PACKAGE_NAME);
         verify(mSettingsStore).handleWifiScanAlwaysAvailableToggled(false);
         verify(mActiveModeWarden, times(2)).scanAlwaysModeChanged();
     }
@@ -5868,13 +7570,744 @@
                 .enforceCallingOrSelfPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
                         eq("WifiService"));
 
-        mWifiServiceImpl.setScanAlwaysAvailable(true);
+        mWifiServiceImpl.setScanAlwaysAvailable(true, TEST_PACKAGE_NAME);
         verify(mSettingsStore, never()).handleWifiScanAlwaysAvailableToggled(anyBoolean());
         verify(mActiveModeWarden, never()).scanAlwaysModeChanged();
     }
 
+    @Test
+    public void testIsScanAlwaysAvailable() {
+        when(mSettingsStore.isScanAlwaysAvailableToggleEnabled()).thenReturn(true);
+        assertTrue(mWifiServiceImpl.isScanAlwaysAvailable());
+        verify(mSettingsStore).isScanAlwaysAvailableToggleEnabled();
+    }
+
     private List<ScanResult> createScanResultList() {
         return Collections.singletonList(new ScanResult(WifiSsid.createFromAsciiEncoded(TEST_SSID),
                 TEST_SSID, TEST_BSSID, 1245, 0, TEST_CAP, -78, 2450, 1025, 22, 33, 20, 0, 0, true));
     }
+
+    private void sendCountryCodeChangedBroadcast(String countryCode) {
+        Intent intent = new Intent(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED);
+        intent.putExtra(TelephonyManager.EXTRA_NETWORK_COUNTRY, countryCode);
+        assertNotNull(mBroadcastReceiverCaptor.getValue());
+        mBroadcastReceiverCaptor.getValue().onReceive(mContext, intent);
+    }
+
+    @Test
+    public void testCountryCodeBroadcastHanding() {
+        mWifiServiceImpl.checkAndStartWifi();
+        mLooper.dispatchAll();
+        verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
+                argThat((IntentFilter filter) ->
+                        filter.hasAction(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED)));
+        sendCountryCodeChangedBroadcast("US");
+        verify(mWifiCountryCode).setTelephonyCountryCodeAndUpdate(any());
+    }
+
+    @Test
+    public void testDumpShouldDumpWakeupController() {
+        mLooper.startAutoDispatch();
+        mWifiServiceImpl.dump(new FileDescriptor(), new PrintWriter(new StringWriter()), null);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+        verify(mWakeupController).dump(any(), any(), any());
+    }
+
+    /**
+     * Test register listener without permission.
+     */
+    @Test(expected = SecurityException.class)
+    public void testAddSuggestionUserApprovalStatusListenerWithMissingPermission() {
+        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+                eq(ACCESS_WIFI_STATE), eq("WifiService"));
+        mWifiServiceImpl.addSuggestionUserApprovalStatusListener(
+                mSuggestionUserApprovalStatusListener, TEST_PACKAGE_NAME);
+    }
+
+    /**
+     * Test register listener from background user.
+     */
+    @Test(expected = SecurityException.class)
+    public void testAddSuggestionUserApprovalStatusListenerFromBackgroundUser() {
+        when(mWifiPermissionsUtil.doesUidBelongToCurrentUser(anyInt())).thenReturn(false);
+        mWifiServiceImpl.addSuggestionUserApprovalStatusListener(
+                mSuggestionUserApprovalStatusListener, TEST_PACKAGE_NAME);
+    }
+
+    /**
+     * Test unregister listener from background user.
+     */
+    @Test(expected = SecurityException.class)
+    public void testRemoveSuggestionUserApprovalStatusListenerFromBackgroundUser() {
+        when(mWifiPermissionsUtil.doesUidBelongToCurrentUser(anyInt())).thenReturn(false);
+        mWifiServiceImpl.removeSuggestionUserApprovalStatusListener(
+                mSuggestionUserApprovalStatusListener, TEST_PACKAGE_NAME);
+    }
+
+    /**
+     * Test register listener without listener
+     */
+    @Test(expected = NullPointerException.class)
+    public void testAddSuggestionUserApprovalStatusListenerWithIllegalArgument() {
+        mWifiServiceImpl.addSuggestionUserApprovalStatusListener(null, TEST_PACKAGE_NAME);
+    }
+
+    /**
+     * Test unregister callback without permission.
+     */
+    @Test(expected = SecurityException.class)
+    public void testUnregisterSuggestionUserApprovalStatusListenerWithMissingPermission() {
+        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+                eq(ACCESS_WIFI_STATE), eq("WifiService"));
+        mWifiServiceImpl.removeSuggestionUserApprovalStatusListener(
+                mSuggestionUserApprovalStatusListener, TEST_PACKAGE_NAME);
+    }
+
+    /**
+     * Test add and remove listener will go to WifiNetworkSuggestionManager
+     */
+    @Test
+    public void testAddRemoveSuggestionUserApprovalStatusListener() {
+        mWifiServiceImpl.addSuggestionUserApprovalStatusListener(
+                mSuggestionUserApprovalStatusListener, TEST_PACKAGE_NAME);
+        mLooper.dispatchAll();
+        verify(mWifiNetworkSuggestionsManager).addSuggestionUserApprovalStatusListener(
+                eq(mSuggestionUserApprovalStatusListener), eq(TEST_PACKAGE_NAME), anyInt());
+
+        mWifiServiceImpl.removeSuggestionUserApprovalStatusListener(
+                mSuggestionUserApprovalStatusListener, TEST_PACKAGE_NAME);
+        mLooper.dispatchAll();
+        verify(mWifiNetworkSuggestionsManager).removeSuggestionUserApprovalStatusListener(
+                eq(mSuggestionUserApprovalStatusListener), eq(TEST_PACKAGE_NAME), anyInt());
+    }
+
+    @Test
+    public void testGetDhcpInfo() throws Exception {
+        DhcpResultsParcelable dhcpResultsParcelable = new DhcpResultsParcelable();
+        dhcpResultsParcelable.leaseDuration = 100;
+        when(mClientModeManager.syncGetDhcpResultsParcelable()).thenReturn(dhcpResultsParcelable);
+
+        mLooper.startAutoDispatch();
+        DhcpInfo dhcpInfo = mWifiServiceImpl.getDhcpInfo(TEST_PACKAGE);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+
+        assertEquals(dhcpResultsParcelable.leaseDuration, dhcpInfo.leaseDuration);
+    }
+
+    @Test
+    public void testGetDhcpInfoFromSecondaryCmmFromAppRequestingSecondaryCmm() throws Exception {
+        DhcpResultsParcelable dhcpResultsParcelable = new DhcpResultsParcelable();
+        dhcpResultsParcelable.leaseDuration = 100;
+        ConcreteClientModeManager secondaryCmm = mock(ConcreteClientModeManager.class);
+        when(secondaryCmm.getRequestorWs())
+                .thenReturn(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE));
+        when(secondaryCmm.syncGetDhcpResultsParcelable()).thenReturn(dhcpResultsParcelable);
+        when(mActiveModeWarden.getClientModeManagersInRoles(
+                ROLE_CLIENT_LOCAL_ONLY, ROLE_CLIENT_SECONDARY_LONG_LIVED))
+                .thenReturn(Arrays.asList(secondaryCmm));
+
+        mLooper.startAutoDispatch();
+        DhcpInfo dhcpInfo = mWifiServiceImpl.getDhcpInfo(TEST_PACKAGE);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+
+        assertEquals(dhcpResultsParcelable.leaseDuration, dhcpInfo.leaseDuration);
+    }
+
+    @Test
+    public void testGetDhcpInfoFromPrimaryCmmFromAppNotRequestingSecondaryCmm() throws Exception {
+        DhcpResultsParcelable dhcpResultsParcelable = new DhcpResultsParcelable();
+        dhcpResultsParcelable.leaseDuration = 100;
+        when(mClientModeManager.syncGetDhcpResultsParcelable()).thenReturn(dhcpResultsParcelable);
+        ConcreteClientModeManager secondaryCmm = mock(ConcreteClientModeManager.class);
+        when(secondaryCmm.getRequestorWs())
+                .thenReturn(new WorkSource(Binder.getCallingUid(), TEST_PACKAGE_NAME_OTHER));
+        when(mActiveModeWarden.getClientModeManagersInRoles(
+                ROLE_CLIENT_LOCAL_ONLY, ROLE_CLIENT_SECONDARY_LONG_LIVED))
+                .thenReturn(Arrays.asList(secondaryCmm));
+
+        mLooper.startAutoDispatch();
+        DhcpInfo dhcpInfo = mWifiServiceImpl.getDhcpInfo(TEST_PACKAGE);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+
+        assertEquals(dhcpResultsParcelable.leaseDuration, dhcpInfo.leaseDuration);
+    }
+
+    @Test
+    public void testSetEmergencyScanRequestInProgress() throws Exception {
+        mWifiServiceImpl.setEmergencyScanRequestInProgress(true);
+        verify(mActiveModeWarden).setEmergencyScanRequestInProgress(true);
+
+        mWifiServiceImpl.setEmergencyScanRequestInProgress(false);
+        verify(mActiveModeWarden).setEmergencyScanRequestInProgress(false);
+    }
+
+    @Test
+    public void testSetEmergencyScanRequestWithoutPermissionThrowsException() throws Exception {
+        when(mContext.checkCallingOrSelfPermission(android.Manifest.permission.NETWORK_STACK))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+                eq(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK), any());
+        try {
+            mWifiServiceImpl.setEmergencyScanRequestInProgress(true);
+            fail();
+        } catch (SecurityException e) { }
+    }
+
+    @Test
+    public void testRemoveAppState() throws Exception {
+        mWifiServiceImpl.removeAppState(TEST_UID, TEST_PACKAGE_NAME);
+        mLooper.dispatchAll();
+
+        verify(mScanRequestProxy).clearScanRequestTimestampsForApp(TEST_PACKAGE_NAME, TEST_UID);
+        verify(mWifiNetworkSuggestionsManager).removeApp(TEST_PACKAGE_NAME);
+        verify(mWifiNetworkFactory).removeUserApprovedAccessPointsForApp(TEST_PACKAGE_NAME);
+        verify(mPasspointManager).removePasspointProviderWithPackage(TEST_PACKAGE_NAME);
+    }
+
+    @Test
+    public void testRemoveAppStateWithoutPermissionThrowsException() throws Exception {
+        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+                eq(Manifest.permission.NETWORK_SETTINGS), any());
+        try {
+            mWifiServiceImpl.removeAppState(TEST_UID, TEST_PACKAGE_NAME);
+            fail();
+        } catch (SecurityException e) { }
+    }
+
+    @Test
+    public void testNotificationResetWithLocaleChange() {
+        mWifiServiceImpl.checkAndStartWifi();
+        mLooper.dispatchAll();
+        verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
+                argThat((IntentFilter filter) ->
+                        filter.hasAction(Intent.ACTION_LOCALE_CHANGED)));
+        verify(mWifiNotificationManager).createNotificationChannels();
+        clearInvocations(mWifiNotificationManager);
+
+        Intent intent = new Intent(Intent.ACTION_LOCALE_CHANGED);
+        mBroadcastReceiverCaptor.getValue().onReceive(mContext, intent);
+        verify(mWifiNotificationManager).createNotificationChannels();
+        verify(mWifiNetworkSuggestionsManager).resetNotification();
+        verify(mWifiCarrierInfoManager).resetNotification();
+        verify(mOpenNetworkNotifier).clearPendingNotification(false);
+        verify(mWakeupController).resetNotification();
+    }
+
+    /**
+     * Verify that a call to setWifiScoringEnabled throws a SecurityException if the caller does
+     * not have NETWORK_SETTINGS permission.
+     */
+    @Test
+    public void testSetWifiScoringEnabledThrowsSecurityExceptionOnMissingPermissions() {
+        doThrow(new SecurityException()).when(mContext)
+                .enforceCallingOrSelfPermission(
+                eq(android.Manifest.permission.NETWORK_SETTINGS),
+                eq("WifiService"));
+        try {
+            mWifiServiceImpl.setWifiScoringEnabled(true);
+            fail("expected SecurityException");
+        } catch (SecurityException expected) {
+        }
+    }
+
+    /**
+     * Verify that setWifiScoringEnabled sets the boolean to {@link WifiSettingsStore}.
+     */
+    @Test
+    public void testSetWifiScoringEnabledGoesToSettingsStore() {
+        mLooper.startAutoDispatch();
+        mWifiServiceImpl.setWifiScoringEnabled(true);
+        mLooper.stopAutoDispatch();
+        verify(mSettingsStore).handleWifiScoringEnabled(true);
+    }
+
+    @Test
+    public void testEnabledTdlsWithMacAddress() {
+        mWifiServiceImpl.enableTdlsWithMacAddress(TEST_BSSID, true);
+        mLooper.dispatchAll();
+        verify(mClientModeManager).enableTdls(TEST_BSSID, true);
+
+        mWifiServiceImpl.enableTdlsWithMacAddress(TEST_BSSID, false);
+        mLooper.dispatchAll();
+        verify(mClientModeManager).enableTdls(TEST_BSSID, false);
+    }
+
+    /**
+     * Verify that a call to setOverrideCountryCode() throws a SecurityException if the caller does
+     * not have the MANAGE_WIFI_COUNTRY_CODE permission.
+     */
+    @Test
+    public void testSetOverrideCountryCodeThrowsSecurityExceptionOnMissingPermissions() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        doThrow(new SecurityException()).when(mContext)
+                .enforceCallingOrSelfPermission(eq(MANAGE_WIFI_COUNTRY_CODE),
+                        eq("WifiService"));
+        try {
+            mWifiServiceImpl.setOverrideCountryCode(TEST_COUNTRY_CODE);
+            fail("expected SecurityException");
+        } catch (SecurityException expected) { }
+    }
+
+    /**
+     * Verify the call to setOverrideCountryCode() goes to WifiCountryCode
+     */
+    @Test
+    public void testSetOverrideCountryCode() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        mWifiServiceImpl.setOverrideCountryCode(TEST_COUNTRY_CODE);
+        mLooper.dispatchAll();
+        verify(mWifiCountryCode).setOverrideCountryCode(TEST_COUNTRY_CODE);
+    }
+
+    /**
+     * Verify that a call to clearOverrideCountryCode() throws a SecurityException if the caller
+     * does not have the MANAGE_WIFI_COUNTRY_CODE permission.
+     */
+    @Test
+    public void testClearOverrideCountryCodeThrowsSecurityExceptionOnMissingPermissions() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        doThrow(new SecurityException()).when(mContext)
+                .enforceCallingOrSelfPermission(eq(MANAGE_WIFI_COUNTRY_CODE),
+                        eq("WifiService"));
+        try {
+            mWifiServiceImpl.clearOverrideCountryCode();
+            fail("expected SecurityException");
+        } catch (SecurityException expected) { }
+    }
+
+    /**
+     * Verify the call to clearOverrideCountryCode() goes to WifiCountryCode
+     */
+    @Test
+    public void testClearOverrideCountryCode() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        mWifiServiceImpl.clearOverrideCountryCode();
+        mLooper.dispatchAll();
+        verify(mWifiCountryCode).clearOverrideCountryCode();
+    }
+
+    /**
+     * Verify that a call to setDefaultCountryCode() throws a SecurityException if the caller does
+     * not have the MANAGE_WIFI_COUNTRY_CODE permission.
+     */
+    @Test
+    public void testSetDefaultCountryCodeThrowsSecurityExceptionOnMissingPermissions() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        doThrow(new SecurityException()).when(mContext)
+                .enforceCallingOrSelfPermission(eq(MANAGE_WIFI_COUNTRY_CODE),
+                        eq("WifiService"));
+        try {
+            mWifiServiceImpl.setDefaultCountryCode(TEST_COUNTRY_CODE);
+            fail("expected SecurityException");
+        } catch (SecurityException expected) { }
+    }
+
+    /**
+     * Verify the call to setDefaultCountryCode() goes to WifiCountryCode
+     */
+    @Test
+    public void testSetDefaultCountryCode() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        mWifiServiceImpl.setDefaultCountryCode(TEST_COUNTRY_CODE);
+        mLooper.dispatchAll();
+        verify(mWifiCountryCode).setDefaultCountryCode(TEST_COUNTRY_CODE);
+    }
+
+    /**
+     * Verify that a call to flushPasspointAnqpCache throws a SecurityException if the
+     * caller does not have any permission.
+     */
+    @Test (expected = SecurityException.class)
+    public void testFlushPasspointAnqpCacheThrowsSecurityExceptionOnMissingPermissions() {
+        when(mContext.checkCallingOrSelfPermission(anyString()))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+        when(mWifiPermissionsUtil.isDeviceOwner(anyInt(), anyString())).thenReturn(false);
+        when(mWifiPermissionsUtil.isProfileOwner(anyInt(), anyString())).thenReturn(false);
+
+        mWifiServiceImpl.flushPasspointAnqpCache(mContext.getPackageName());
+    }
+
+    /**
+     * Verifies that the call to testFlushPasspointAnqpCache with DO permission calls Passpoint
+     * manager to flush the ANQP cache and clear all pending requests.
+     */
+    @Test
+    public void testFlushPasspointAnqpCacheWithDoPermissions() {
+        when(mContext.checkCallingOrSelfPermission(anyString()))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+
+        when(mWifiPermissionsUtil.isDeviceOwner(anyInt(), eq(TEST_PACKAGE_NAME))).thenReturn(true);
+        when(mWifiPermissionsUtil.isProfileOwner(anyInt(),
+                eq(TEST_PACKAGE_NAME))).thenReturn(false);
+        mWifiServiceImpl.flushPasspointAnqpCache(TEST_PACKAGE_NAME);
+        mLooper.dispatchAll();
+        verify(mPasspointManager).clearAnqpRequestsAndFlushCache();
+    }
+
+    /**
+     * Verifies that the call to testFlushPasspointAnqpCache with PO permission calls Passpoint
+     * manager to flush the ANQP cache and clear all pending requests.
+     */
+    @Test
+    public void testFlushPasspointAnqpCacheWithPoPermissions() {
+        when(mContext.checkCallingOrSelfPermission(anyString()))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+
+        when(mWifiPermissionsUtil.isDeviceOwner(anyInt(), eq(TEST_PACKAGE_NAME))).thenReturn(false);
+        when(mWifiPermissionsUtil.isProfileOwner(anyInt(), eq(TEST_PACKAGE_NAME))).thenReturn(true);
+        mWifiServiceImpl.flushPasspointAnqpCache(TEST_PACKAGE_NAME);
+        mLooper.dispatchAll();
+        verify(mPasspointManager).clearAnqpRequestsAndFlushCache();
+    }
+
+    /**
+     * Verifies that the call to testFlushPasspointAnqpCache calls Passpoint manager to flush the
+     * ANQP cache and clear all pending requests.
+     */
+    @Test
+    public void testFlushPasspointAnqpCache() {
+        when(mContext.checkCallingOrSelfPermission(android.Manifest.permission.NETWORK_SETTINGS))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+        when(mWifiPermissionsUtil.isDeviceOwner(anyInt(), eq(TEST_PACKAGE_NAME))).thenReturn(false);
+        when(mWifiPermissionsUtil.isProfileOwner(anyInt(),
+                eq(TEST_PACKAGE_NAME))).thenReturn(false);
+        mWifiServiceImpl.flushPasspointAnqpCache(TEST_PACKAGE_NAME);
+        mLooper.dispatchAll();
+        verify(mPasspointManager).clearAnqpRequestsAndFlushCache();
+    }
+
+    /**
+     * Verify that a call to getUsableChannels() throws a SecurityException if the caller does
+     * not have the LOCATION_HARDWARE permission.
+     */
+    @Test
+    public void testGetUsableChannelsThrowsSecurityExceptionOnMissingPermissions() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        when(mWifiPermissionsUtil.isLocationModeEnabled()).thenReturn(true);
+        doThrow(new SecurityException()).when(mWifiPermissionsUtil)
+                .checkCallersHardwareLocationPermission(anyInt());
+        try {
+            mWifiServiceImpl.getUsableChannels(WIFI_BAND_24_GHZ, OP_MODE_STA, FILTER_REGULATORY);
+            fail("expected SecurityException");
+        } catch (SecurityException expected) { }
+    }
+
+    /**
+     * Verify the call to isValidBandForGetUsableChannels()
+     */
+    @Test
+    public void testIsValidBandForGetUsableChannels() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        assertEquals(WifiServiceImpl.isValidBandForGetUsableChannels(
+                WifiScanner.WIFI_BAND_UNSPECIFIED), true);
+        assertEquals(WifiServiceImpl.isValidBandForGetUsableChannels(
+                WifiScanner.WIFI_BAND_24_GHZ), true);
+        assertEquals(WifiServiceImpl.isValidBandForGetUsableChannels(
+                WifiScanner.WIFI_BAND_5_GHZ), false);
+        assertEquals(WifiServiceImpl.isValidBandForGetUsableChannels(
+                WifiScanner.WIFI_BAND_BOTH), false);
+        assertEquals(WifiServiceImpl.isValidBandForGetUsableChannels(
+                WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY), false);
+        assertEquals(WifiServiceImpl.isValidBandForGetUsableChannels(
+                WifiScanner.WIFI_BAND_24_GHZ_WITH_5GHZ_DFS), false);
+        assertEquals(WifiServiceImpl.isValidBandForGetUsableChannels(
+                WifiScanner.WIFI_BAND_5_GHZ_WITH_DFS), true);
+        assertEquals(WifiServiceImpl.isValidBandForGetUsableChannels(
+                WifiScanner.WIFI_BAND_BOTH_WITH_DFS), true);
+        assertEquals(WifiServiceImpl.isValidBandForGetUsableChannels(
+                WifiScanner.WIFI_BAND_6_GHZ), true);
+        assertEquals(WifiServiceImpl.isValidBandForGetUsableChannels(
+                WifiScanner.WIFI_BAND_24_5_6_GHZ), false);
+        assertEquals(WifiServiceImpl.isValidBandForGetUsableChannels(
+                WifiScanner.WIFI_BAND_24_5_WITH_DFS_6_GHZ), true);
+        assertEquals(WifiServiceImpl.isValidBandForGetUsableChannels(
+                WifiScanner.WIFI_BAND_60_GHZ), true);
+        assertEquals(WifiServiceImpl.isValidBandForGetUsableChannels(
+                WifiScanner.WIFI_BAND_24_5_6_60_GHZ), false);
+        assertEquals(WifiServiceImpl.isValidBandForGetUsableChannels(
+                WifiScanner.WIFI_BAND_24_5_WITH_DFS_6_60_GHZ), true);
+    }
+
+    /**
+     * Verify that a call to getUsableChannels() throws an IllegalArgumentException
+     * if the band specified is invalid for getAllowedChannels() method.
+     */
+    @Test
+    public void testGetUsableChannelsThrowsIllegalArgumentExceptionOnInValidBand() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        when(mWifiPermissionsUtil.isLocationModeEnabled()).thenReturn(true);
+        when(mWifiPermissionsUtil.checkCallersHardwareLocationPermission(anyInt()))
+                .thenReturn(true);
+        try {
+            mWifiServiceImpl.getUsableChannels(WIFI_BAND_5_GHZ, OP_MODE_STA, FILTER_REGULATORY);
+            fail("expected IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    /**
+     * Verify the call to getUsableChannels() goes to WifiNative
+     */
+    @Test
+    public void testGetUsableChannels() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        when(mWifiPermissionsUtil.isLocationModeEnabled()).thenReturn(true);
+        when(mWifiPermissionsUtil.checkCallersHardwareLocationPermission(anyInt()))
+                .thenReturn(true);
+        mLooper.startAutoDispatch();
+        mWifiServiceImpl.getUsableChannels(WIFI_BAND_24_GHZ, OP_MODE_STA, FILTER_REGULATORY);
+        mLooper.stopAutoDispatch();
+        verify(mWifiNative).getUsableChannels(anyInt(), anyInt(), anyInt());
+    }
+
+    /**
+     * Verify that if the caller has NETWORK_SETTINGS permission, and the overlay
+     * config_wifiAllowInsecureEnterpriseConfigurationsForSettingsAndSUW is set, then it can add an
+     * insecure Enterprise network, with Root CA certificate not set and/or domain name not set.
+     */
+    @Test
+    public void testAddInsecureEnterpirseNetworkWithNetworkSettingsPerm() throws Exception {
+        when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+
+        // First set flag to not allow
+        when(mResources.getBoolean(
+                R.bool.config_wifiAllowInsecureEnterpriseConfigurationsForSettingsAndSUW))
+                .thenReturn(false);
+        when(mWifiConfigManager.addOrUpdateNetwork(any(),  anyInt(), any())).thenReturn(
+                new NetworkUpdateResult(0));
+
+        // Create an insecure Enterprise network
+        WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork();
+        config.enterpriseConfig.setCaPath(null);
+        config.enterpriseConfig.setDomainSuffixMatch(null);
+
+        // Verify operation fails
+        mLooper.startAutoDispatch();
+        assertEquals(-1, mWifiServiceImpl.addOrUpdateNetwork(config, TEST_PACKAGE_NAME));
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+        verify(mWifiConfigManager, never()).addOrUpdateNetwork(any(),  anyInt(), any());
+
+        // Set flag to allow
+        when(mResources.getBoolean(
+                R.bool.config_wifiAllowInsecureEnterpriseConfigurationsForSettingsAndSUW))
+                .thenReturn(true);
+
+        // Verify operation succeeds
+        mLooper.startAutoDispatch();
+        assertEquals(0, WifiConfigurationUtil.removeSecurityTypeFromNetworkId(
+                mWifiServiceImpl.addOrUpdateNetwork(config, TEST_PACKAGE_NAME)));
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+        verify(mWifiConfigManager).addOrUpdateNetwork(any(),  anyInt(), any());
+    }
+
+
+    /**
+     * Verify that if the caller does NOT have NETWORK_SETTINGS permission, then it cannot add an
+     * insecure Enterprise network, with Root CA certificate not set and/or domain name not set,
+     * regardless of the overlay config_wifiAllowInsecureEnterpriseConfigurationsForSettingsAndSUW
+     * value.
+     */
+    @Test
+    public void testAddInsecureEnterpirseNetworkWithNoNetworkSettingsPerm() throws Exception {
+        // First set flag to not allow
+        when(mResources.getBoolean(
+                R.bool.config_wifiAllowInsecureEnterpriseConfigurationsForSettingsAndSUW))
+                .thenReturn(false);
+
+        // Create an insecure Enterprise network
+        WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork();
+        config.enterpriseConfig.setCaPath(null);
+        config.enterpriseConfig.setDomainSuffixMatch(null);
+
+        // Verify operation fails
+        mLooper.startAutoDispatch();
+        assertEquals(-1, mWifiServiceImpl.addOrUpdateNetwork(config, TEST_PACKAGE_NAME));
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+        verify(mWifiConfigManager, never()).addOrUpdateNetwork(any(),  anyInt(), any());
+
+        // Set flag to allow
+        when(mResources.getBoolean(
+                R.bool.config_wifiAllowInsecureEnterpriseConfigurationsForSettingsAndSUW))
+                .thenReturn(true);
+
+        // Verify operation still fails
+        mLooper.startAutoDispatch();
+        assertEquals(-1, mWifiServiceImpl.addOrUpdateNetwork(config, TEST_PACKAGE_NAME));
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+        verify(mWifiConfigManager, never()).addOrUpdateNetwork(any(),  anyInt(), any());
+    }
+
+    private List<WifiConfiguration> setupMultiTypeConfigs(
+            long featureFlags, boolean saeAutoUpgradeEnabled, boolean oweAutoUpgradeEnabled) {
+        when(mClientModeManager.getSupportedFeatures()).thenReturn(featureFlags);
+        when(mWifiGlobals.isWpa3SaeUpgradeEnabled()).thenReturn(saeAutoUpgradeEnabled);
+        when(mWifiGlobals.isOweUpgradeEnabled()).thenReturn(oweAutoUpgradeEnabled);
+
+        List<WifiConfiguration> multiTypeConfigs  = new ArrayList<>();
+        multiTypeConfigs.add(WifiConfigurationTestUtil.createOpenOweNetwork());
+        multiTypeConfigs.add(WifiConfigurationTestUtil.createPskSaeNetwork());
+        multiTypeConfigs.add(WifiConfigurationTestUtil.createWpa2Wpa3EnterpriseNetwork());
+        // Add a valid network ID for each multi-type config to verify the network IDs of the
+        // single-type configs.
+        int i = 0;
+        for (WifiConfiguration config : multiTypeConfigs) {
+            config.networkId = i++;
+        }
+        return multiTypeConfigs;
+    }
+
+    private boolean isSecurityParamsSupported(SecurityParams params, long wifiFeatures) {
+        switch (params.getSecurityType()) {
+            case WifiConfiguration.SECURITY_TYPE_SAE:
+                return 0 != (wifiFeatures & WifiManager.WIFI_FEATURE_WPA3_SAE);
+            case WifiConfiguration.SECURITY_TYPE_OWE:
+                return 0 != (wifiFeatures & WifiManager.WIFI_FEATURE_OWE);
+        }
+        return true;
+    }
+
+    private List<WifiConfiguration> generateExpectedConfigs(
+            List<WifiConfiguration> testConfigs,
+            boolean saeAutoUpgradeEnabled, boolean oweAutoUpgradeEnabled) {
+        WifiConfiguration tmpConfig;
+        List<WifiConfiguration> expectedConfigs = new ArrayList<>();
+        tmpConfig = new WifiConfiguration(testConfigs.get(0));
+        tmpConfig.setSecurityParams(
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_OPEN));
+        expectedConfigs.add(tmpConfig);
+        if (oweAutoUpgradeEnabled) {
+            tmpConfig = new WifiConfiguration(testConfigs.get(0));
+            tmpConfig.setSecurityParams(
+                    SecurityParams.createSecurityParamsBySecurityType(
+                            WifiConfiguration.SECURITY_TYPE_OWE));
+            expectedConfigs.add(tmpConfig);
+        }
+        tmpConfig = new WifiConfiguration(testConfigs.get(1));
+        tmpConfig.setSecurityParams(
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_PSK));
+        expectedConfigs.add(tmpConfig);
+        if (saeAutoUpgradeEnabled) {
+            tmpConfig = new WifiConfiguration(testConfigs.get(1));
+            tmpConfig.setSecurityParams(
+                    SecurityParams.createSecurityParamsBySecurityType(
+                            WifiConfiguration.SECURITY_TYPE_SAE));
+            expectedConfigs.add(tmpConfig);
+        }
+        tmpConfig = new WifiConfiguration(testConfigs.get(2));
+        tmpConfig.setSecurityParams(
+                SecurityParams.createSecurityParamsBySecurityType(
+                        WifiConfiguration.SECURITY_TYPE_EAP));
+        expectedConfigs.add(tmpConfig);
+        if (SdkLevel.isAtLeastS()) {
+            // WPA2/WPA3-Enterprise config maps only to WPA2-Enterprise for R, but should map to
+            // both WPA2 and WPA3-Enterprise for S and beyond.
+            tmpConfig = new WifiConfiguration(testConfigs.get(2));
+            tmpConfig.setSecurityParams(
+                    SecurityParams.createSecurityParamsBySecurityType(
+                            WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE));
+            expectedConfigs.add(tmpConfig);
+        } else {
+            for (WifiConfiguration config : expectedConfigs) {
+                config.networkId = WifiConfigurationUtil.addSecurityTypeToNetworkId(
+                        config.networkId, config.getDefaultSecurityParams().getSecurityType());
+            }
+        }
+        return expectedConfigs;
+    }
+
+    /**
+     * verify multi-type configs are converted to legacy configs in getConfiguredNetworks
+     * and getPrivilegedConfiguredNetworks when auto-upgrade is enabled.
+     */
+    @Test
+    public void testGetConfiguredNetworksForMultiTypeConfigs() {
+        long featureFlags = WifiManager.WIFI_FEATURE_WPA3_SAE | WifiManager.WIFI_FEATURE_OWE;
+        List<WifiConfiguration> testConfigs = setupMultiTypeConfigs(
+                featureFlags, true, true);
+        when(mWifiConfigManager.getSavedNetworks(anyInt()))
+                .thenReturn(testConfigs);
+        when(mWifiConfigManager.getConfiguredNetworksWithPasswords())
+                .thenReturn(testConfigs);
+        when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+
+        mLooper.startAutoDispatch();
+        ParceledListSlice<WifiConfiguration> configs =
+                mWifiServiceImpl.getConfiguredNetworks(TEST_PACKAGE, TEST_FEATURE_ID, false);
+        ParceledListSlice<WifiConfiguration> privilegedConfigs =
+                mWifiServiceImpl.getPrivilegedConfiguredNetworks(TEST_PACKAGE, TEST_FEATURE_ID);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+
+        List<WifiConfiguration> expectedConfigs = generateExpectedConfigs(
+                testConfigs, true, true);
+        WifiConfigurationTestUtil.assertConfigurationsEqual(
+                expectedConfigs, configs.getList());
+        WifiConfigurationTestUtil.assertConfigurationsEqual(
+                expectedConfigs, privilegedConfigs.getList());
+    }
+
+    /**
+     * verify multi-type configs are converted to legacy configs in getConfiguredNetworks
+     * and getPrivilegedConfiguredNetworks when auto-upgrade is not enabled.
+     */
+    @Test
+    public void testGetConfiguredNetworksForMultiTypeConfigsWithoutAutoUpgradeEnabled() {
+        long featureFlags = WifiManager.WIFI_FEATURE_WPA3_SAE | WifiManager.WIFI_FEATURE_OWE;
+        List<WifiConfiguration> testConfigs = setupMultiTypeConfigs(
+                featureFlags, false, false);
+        when(mWifiConfigManager.getSavedNetworks(anyInt()))
+                .thenReturn(testConfigs);
+        when(mWifiConfigManager.getConfiguredNetworksWithPasswords())
+                .thenReturn(testConfigs);
+        when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+
+        mLooper.startAutoDispatch();
+        ParceledListSlice<WifiConfiguration> configs =
+                mWifiServiceImpl.getConfiguredNetworks(TEST_PACKAGE, TEST_FEATURE_ID, false);
+        ParceledListSlice<WifiConfiguration> privilegedConfigs =
+                mWifiServiceImpl.getPrivilegedConfiguredNetworks(TEST_PACKAGE, TEST_FEATURE_ID);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+
+        List<WifiConfiguration> expectedConfigs = generateExpectedConfigs(
+                testConfigs, false, false);
+        WifiConfigurationTestUtil.assertConfigurationsEqual(
+                expectedConfigs, configs.getList());
+        WifiConfigurationTestUtil.assertConfigurationsEqual(
+                expectedConfigs, privilegedConfigs.getList());
+    }
+
+    /**
+     * verify multi-type configs are converted to legacy configs in getConfiguredNetworks
+     * and getPrivilegedConfiguredNetworks when security types are not supported.
+     */
+    @Test
+    public void testGetConfiguredNetworksForMultiTypeConfigsWithoutHwSupport() {
+        long featureFlags = 0L;
+        List<WifiConfiguration> testConfigs = setupMultiTypeConfigs(
+                featureFlags, true, true);
+        when(mWifiConfigManager.getSavedNetworks(anyInt()))
+                .thenReturn(testConfigs);
+        when(mWifiConfigManager.getConfiguredNetworksWithPasswords())
+                .thenReturn(testConfigs);
+        when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+
+        mLooper.startAutoDispatch();
+        ParceledListSlice<WifiConfiguration> configs =
+                mWifiServiceImpl.getConfiguredNetworks(TEST_PACKAGE, TEST_FEATURE_ID, false);
+        ParceledListSlice<WifiConfiguration> privilegedConfigs =
+                mWifiServiceImpl.getPrivilegedConfiguredNetworks(TEST_PACKAGE, TEST_FEATURE_ID);
+        mLooper.stopAutoDispatchAndIgnoreExceptions();
+
+        List<WifiConfiguration> expectedConfigs = generateExpectedConfigs(
+                testConfigs, true, true);
+        WifiConfigurationTestUtil.assertConfigurationsEqual(
+                expectedConfigs, configs.getList());
+        WifiConfigurationTestUtil.assertConfigurationsEqual(
+                expectedConfigs, privilegedConfigs.getList());
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiShellCommandTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiShellCommandTest.java
index 1f4af5c..211537e 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiShellCommandTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiShellCommandTest.java
@@ -16,11 +16,25 @@
 
 package com.android.server.wifi;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED;
+
+import static com.android.server.wifi.WifiShellCommand.SHELL_PACKAGE_NAME;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.validateMockitoUsage;
@@ -31,11 +45,18 @@
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.wifi.SoftApConfiguration;
+import android.net.wifi.SupplicantState;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiNetworkSuggestion;
 import android.os.Binder;
 import android.os.Process;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.modules.utils.build.SdkLevel;
+import com.android.server.wifi.coex.CoexManager;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -44,6 +65,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.io.FileDescriptor;
+import java.util.Arrays;
 
 /**
  * Unit tests for {@link com.android.server.wifi.WifiShellCommand}.
@@ -53,11 +75,13 @@
     private static final String TEST_PACKAGE = "com.android.test";
 
     @Mock WifiInjector mWifiInjector;
-    @Mock ClientModeImpl mClientModeImpl;
+    @Mock ActiveModeWarden mActiveModeWarden;
+    @Mock ClientModeManager mPrimaryClientModeManager;
     @Mock WifiLockManager mWifiLockManager;
     @Mock WifiNetworkSuggestionsManager mWifiNetworkSuggestionsManager;
     @Mock WifiConfigManager mWifiConfigManager;
     @Mock WifiNative mWifiNative;
+    @Mock CoexManager mCoexManager;
     @Mock HostapdHal mHostapdHal;
     @Mock WifiCountryCode mWifiCountryCode;
     @Mock WifiLastResortWatchdog mWifiLastResortWatchdog;
@@ -65,6 +89,10 @@
     @Mock Context mContext;
     @Mock ConnectivityManager mConnectivityManager;
     @Mock WifiCarrierInfoManager mWifiCarrierInfoManager;
+    @Mock WifiNetworkFactory mWifiNetworkFactory;
+    @Mock WifiGlobals mWifiGlobals;
+    @Mock WifiThreadRunner mWifiThreadRunner;
+    @Mock ScanRequestProxy mScanRequestProxy;
 
     WifiShellCommand mWifiShellCommand;
 
@@ -72,18 +100,26 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        when(mWifiInjector.getClientModeImpl()).thenReturn(mClientModeImpl);
+        when(mWifiInjector.getActiveModeWarden()).thenReturn(mActiveModeWarden);
+        when(mActiveModeWarden.getPrimaryClientModeManager()).thenReturn(mPrimaryClientModeManager);
+        when(mActiveModeWarden.getClientModeManagers())
+                .thenReturn(Arrays.asList(mPrimaryClientModeManager));
         when(mWifiInjector.getWifiLockManager()).thenReturn(mWifiLockManager);
         when(mWifiInjector.getWifiNetworkSuggestionsManager())
                 .thenReturn(mWifiNetworkSuggestionsManager);
         when(mWifiInjector.getWifiConfigManager()).thenReturn(mWifiConfigManager);
         when(mWifiInjector.getHostapdHal()).thenReturn(mHostapdHal);
         when(mWifiInjector.getWifiNative()).thenReturn(mWifiNative);
+        when(mWifiInjector.getCoexManager()).thenReturn(mCoexManager);
         when(mWifiInjector.getWifiCountryCode()).thenReturn(mWifiCountryCode);
         when(mWifiInjector.getWifiLastResortWatchdog()).thenReturn(mWifiLastResortWatchdog);
         when(mWifiInjector.getWifiCarrierInfoManager()).thenReturn(mWifiCarrierInfoManager);
+        when(mWifiInjector.getWifiNetworkFactory()).thenReturn(mWifiNetworkFactory);
+        when(mWifiInjector.getScanRequestProxy()).thenReturn(mScanRequestProxy);
+        when(mContext.getSystemService(ConnectivityManager.class)).thenReturn(mConnectivityManager);
 
-        mWifiShellCommand = new WifiShellCommand(mWifiInjector, mWifiService, mContext);
+        mWifiShellCommand = new WifiShellCommand(mWifiInjector, mWifiService, mContext,
+                mWifiGlobals, mWifiThreadRunner);
 
         // by default emulate shell uid.
         BinderUtil.setUid(Process.SHELL_UID);
@@ -100,7 +136,7 @@
         mWifiShellCommand.exec(
                 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
                 new String[]{"set-ipreach-disconnect", "enabled"});
-        verify(mClientModeImpl, never()).setIpReachabilityDisconnectEnabled(anyBoolean());
+        verify(mWifiGlobals, never()).setIpReachabilityDisconnectEnabled(anyBoolean());
         assertFalse(mWifiShellCommand.getErrPrintWriter().toString().isEmpty());
 
         BinderUtil.setUid(Process.ROOT_UID);
@@ -108,18 +144,18 @@
         mWifiShellCommand.exec(
                 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
                 new String[]{"set-ipreach-disconnect", "enabled"});
-        verify(mClientModeImpl).setIpReachabilityDisconnectEnabled(true);
+        verify(mWifiGlobals).setIpReachabilityDisconnectEnabled(true);
 
         mWifiShellCommand.exec(
                 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
                 new String[]{"set-ipreach-disconnect", "disabled"});
-        verify(mClientModeImpl).setIpReachabilityDisconnectEnabled(false);
+        verify(mWifiGlobals).setIpReachabilityDisconnectEnabled(false);
 
         // invalid arg
         mWifiShellCommand.exec(
                 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
                 new String[]{"set-ipreach-disconnect", "yes"});
-        verifyNoMoreInteractions(mClientModeImpl);
+        verifyNoMoreInteractions(mWifiGlobals);
         assertFalse(mWifiShellCommand.getErrPrintWriter().toString().isEmpty());
     }
 
@@ -129,24 +165,24 @@
         mWifiShellCommand.exec(
                 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
                 new String[]{"get-ipreach-disconnect"});
-        verify(mClientModeImpl, never()).getIpReachabilityDisconnectEnabled();
+        verify(mWifiGlobals, never()).getIpReachabilityDisconnectEnabled();
         assertFalse(mWifiShellCommand.getErrPrintWriter().toString().isEmpty());
 
         BinderUtil.setUid(Process.ROOT_UID);
 
-        when(mClientModeImpl.getIpReachabilityDisconnectEnabled()).thenReturn(true);
+        when(mWifiGlobals.getIpReachabilityDisconnectEnabled()).thenReturn(true);
         mWifiShellCommand.exec(
                 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
                 new String[]{"get-ipreach-disconnect"});
-        verify(mClientModeImpl).getIpReachabilityDisconnectEnabled();
+        verify(mWifiGlobals).getIpReachabilityDisconnectEnabled();
         mWifiShellCommand.getOutPrintWriter().toString().contains(
                 "IPREACH_DISCONNECT state is true");
 
-        when(mClientModeImpl.getIpReachabilityDisconnectEnabled()).thenReturn(false);
+        when(mWifiGlobals.getIpReachabilityDisconnectEnabled()).thenReturn(false);
         mWifiShellCommand.exec(
                 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
                 new String[]{"get-ipreach-disconnect"});
-        verify(mClientModeImpl, times(2)).getIpReachabilityDisconnectEnabled();
+        verify(mWifiGlobals, times(2)).getIpReachabilityDisconnectEnabled();
         mWifiShellCommand.getOutPrintWriter().toString().contains(
                 "IPREACH_DISCONNECT state is false");
     }
@@ -157,7 +193,7 @@
         mWifiShellCommand.exec(
                 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
                 new String[]{"set-poll-rssi-interval-msecs", "5"});
-        verify(mClientModeImpl, never()).setPollRssiIntervalMsecs(anyInt());
+        verify(mWifiGlobals, never()).setPollRssiIntervalMillis(anyInt());
         assertFalse(mWifiShellCommand.getErrPrintWriter().toString().isEmpty());
 
         BinderUtil.setUid(Process.ROOT_UID);
@@ -165,13 +201,13 @@
         mWifiShellCommand.exec(
                 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
                 new String[]{"set-poll-rssi-interval-msecs", "5"});
-        verify(mClientModeImpl).setPollRssiIntervalMsecs(5);
+        verify(mWifiGlobals).setPollRssiIntervalMillis(5);
 
         // invalid arg
         mWifiShellCommand.exec(
                 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
                 new String[]{"set-poll-rssi-interval-msecs", "0"});
-        verifyNoMoreInteractions(mClientModeImpl);
+        verifyNoMoreInteractions(mWifiGlobals);
         assertFalse(mWifiShellCommand.getErrPrintWriter().toString().isEmpty());
     }
 
@@ -181,18 +217,18 @@
         mWifiShellCommand.exec(
                 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
                 new String[]{"get-poll-rssi-interval-msecs"});
-        verify(mClientModeImpl, never()).getPollRssiIntervalMsecs();
+        verify(mWifiGlobals, never()).getPollRssiIntervalMillis();
         assertFalse(mWifiShellCommand.getErrPrintWriter().toString().isEmpty());
 
         BinderUtil.setUid(Process.ROOT_UID);
 
-        when(mClientModeImpl.getPollRssiIntervalMsecs()).thenReturn(5);
+        when(mWifiGlobals.getPollRssiIntervalMillis()).thenReturn(5);
         mWifiShellCommand.exec(
                 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
                 new String[]{"get-poll-rssi-interval-msecs"});
-        verify(mClientModeImpl).getPollRssiIntervalMsecs();
+        verify(mWifiGlobals).getPollRssiIntervalMillis();
         mWifiShellCommand.getOutPrintWriter().toString().contains(
-                "ClientModeImpl.mPollRssiIntervalMsecs = 5");
+                "WifiGlobals.getPollRssiIntervalMillis() = 5");
     }
 
     @Test
@@ -218,6 +254,36 @@
     }
 
     @Test
+    public void testAddFakeScans() {
+        // not allowed for unrooted shell.
+        mWifiShellCommand.exec(new Binder(), new FileDescriptor(), new FileDescriptor(),
+                new FileDescriptor(),
+                new String[]{"add-fake-scan", "ssid", "80:01:02:03:04:05", "\"[ESS]\"", "2412",
+                        "-55"});
+        verify(mWifiNative, never()).addFakeScanDetail(any());
+        assertFalse(mWifiShellCommand.getErrPrintWriter().toString().isEmpty());
+
+        BinderUtil.setUid(Process.ROOT_UID);
+        String ssid = "ssid";
+        String bssid = "80:01:02:03:04:05";
+        String capabilities = "\"[ESS]\"";
+        String freq = "2412";
+        String dbm = "-55";
+        mWifiShellCommand.exec(new Binder(), new FileDescriptor(), new FileDescriptor(),
+                new FileDescriptor(),
+                new String[]{"add-fake-scan", ssid, bssid, capabilities, freq, dbm});
+
+        ArgumentCaptor<ScanDetail> scanDetailCaptor = ArgumentCaptor.forClass(ScanDetail.class);
+        verify(mWifiNative).addFakeScanDetail(scanDetailCaptor.capture());
+        ScanDetail sd = scanDetailCaptor.getValue();
+        assertEquals(capabilities, sd.getScanResult().capabilities);
+        assertEquals(ssid, sd.getSSID());
+        assertEquals(bssid, sd.getBSSIDString());
+        assertEquals(2412, sd.getScanResult().frequency);
+        assertEquals(-55, sd.getScanResult().level);
+    }
+
+    @Test
     public void testForceLowLatencyMode() {
         // not allowed for unrooted shell.
         mWifiShellCommand.exec(
@@ -246,7 +312,7 @@
                 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
                 new String[]{"network-suggestions-set-user-approved", TEST_PACKAGE, "yes"});
         verify(mWifiNetworkSuggestionsManager, never()).setHasUserApprovedForApp(
-                anyBoolean(), anyString());
+                anyBoolean(), anyInt(), anyString());
         assertFalse(mWifiShellCommand.getErrPrintWriter().toString().isEmpty());
 
         BinderUtil.setUid(Process.ROOT_UID);
@@ -255,13 +321,13 @@
                 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
                 new String[]{"network-suggestions-set-user-approved", TEST_PACKAGE, "yes"});
         verify(mWifiNetworkSuggestionsManager).setHasUserApprovedForApp(
-                true, TEST_PACKAGE);
+                eq(true), anyInt(), eq(TEST_PACKAGE));
 
         mWifiShellCommand.exec(
                 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
                 new String[]{"network-suggestions-set-user-approved", TEST_PACKAGE, "no"});
         verify(mWifiNetworkSuggestionsManager).setHasUserApprovedForApp(
-                false, TEST_PACKAGE);
+                eq(false), anyInt(), eq(TEST_PACKAGE));
     }
 
     @Test
@@ -355,7 +421,7 @@
         mWifiShellCommand.exec(
                 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
                 new String[]{"network-requests-set-user-approved", TEST_PACKAGE, "yes"});
-        verify(mClientModeImpl, never()).setNetworkRequestUserApprovedApp(
+        verify(mWifiNetworkFactory, never()).setUserApprovedApp(
                 anyString(), anyBoolean());
         assertFalse(mWifiShellCommand.getErrPrintWriter().toString().isEmpty());
 
@@ -364,12 +430,12 @@
         mWifiShellCommand.exec(
                 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
                 new String[]{"network-requests-set-user-approved", TEST_PACKAGE, "yes"});
-        verify(mClientModeImpl).setNetworkRequestUserApprovedApp(TEST_PACKAGE, true);
+        verify(mWifiNetworkFactory).setUserApprovedApp(TEST_PACKAGE, true);
 
         mWifiShellCommand.exec(
                 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
                 new String[]{"network-requests-set-user-approved", TEST_PACKAGE, "no"});
-        verify(mClientModeImpl).setNetworkRequestUserApprovedApp(TEST_PACKAGE, false);
+        verify(mWifiNetworkFactory).setUserApprovedApp(TEST_PACKAGE, false);
     }
 
     @Test
@@ -378,41 +444,106 @@
         mWifiShellCommand.exec(
                 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
                 new String[]{"network-requests-has-user-approved", TEST_PACKAGE});
-        verify(mClientModeImpl, never()).hasNetworkRequestUserApprovedApp(anyString());
+        verify(mWifiNetworkFactory, never()).hasUserApprovedApp(anyString());
         assertFalse(mWifiShellCommand.getErrPrintWriter().toString().isEmpty());
 
         BinderUtil.setUid(Process.ROOT_UID);
 
-        when(mClientModeImpl.hasNetworkRequestUserApprovedApp(TEST_PACKAGE))
+        when(mWifiNetworkFactory.hasUserApprovedApp(TEST_PACKAGE))
                 .thenReturn(true);
         mWifiShellCommand.exec(
                 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
                 new String[]{"network-requests-has-user-approved", TEST_PACKAGE});
-        verify(mClientModeImpl).hasNetworkRequestUserApprovedApp(TEST_PACKAGE);
+        verify(mWifiNetworkFactory).hasUserApprovedApp(TEST_PACKAGE);
         mWifiShellCommand.getOutPrintWriter().toString().contains("yes");
 
-        when(mClientModeImpl.hasNetworkRequestUserApprovedApp(TEST_PACKAGE))
+        when(mWifiNetworkFactory.hasUserApprovedApp(TEST_PACKAGE))
                 .thenReturn(false);
         mWifiShellCommand.exec(
                 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
                 new String[]{"network-requests-has-user-approved", TEST_PACKAGE});
-        verify(mClientModeImpl, times(2)).hasNetworkRequestUserApprovedApp(TEST_PACKAGE);
+        verify(mWifiNetworkFactory, times(2)).hasUserApprovedApp(TEST_PACKAGE);
         mWifiShellCommand.getOutPrintWriter().toString().contains("no");
     }
 
     @Test
+    public void testSetCoexCellChannels() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        mWifiShellCommand.exec(
+                new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                new String[]{"set-coex-cell-channels"});
+        verify(mCoexManager, never()).setMockCellChannels(any());
+        assertFalse(mWifiShellCommand.getErrPrintWriter().toString().isEmpty());
+
+        BinderUtil.setUid(Process.ROOT_UID);
+
+        // invalid arg
+        mWifiShellCommand.exec(
+                new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                new String[]{"set-coex-cell-channel",
+                        "invalid_band", "40", "2300_000", "2000", "2300000", "2000"});
+        verify(mCoexManager, never()).setMockCellChannels(any());
+        assertFalse(mWifiShellCommand.getErrPrintWriter().toString().isEmpty());
+
+        // invalid arg
+        mWifiShellCommand.exec(
+                new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                new String[]{"set-coex-cell-channels",
+                        "invalid_band", "40", "-2300000", "2000", "2300000", "2000"});
+        verify(mCoexManager, never()).setMockCellChannels(any());
+        assertFalse(mWifiShellCommand.getErrPrintWriter().toString().isEmpty());
+
+        mWifiShellCommand.exec(
+                new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                new String[]{"set-coex-cell-channels",
+                        "lte", "40", "2300000", "2000", "2300000", "2000"});
+        verify(mCoexManager, times(1)).setMockCellChannels(any());
+
+        mWifiShellCommand.exec(
+                new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                new String[]{"set-coex-cell-channels",
+                        "lte", "40", "2300000", "2000", "2300000", "2000",
+                        "lte", "46", "5000000", "2000", "5000000", "2000",
+                        "nr", "20", "700000", "2000", "700000", "2000"});
+        verify(mCoexManager, times(2)).setMockCellChannels(any());
+
+        mWifiShellCommand.exec(
+                new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                new String[]{"set-coex-cell-channels"});
+        verify(mCoexManager, times(3)).setMockCellChannels(any());
+    }
+
+    @Test
+    public void testResetCoexCellChannel() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        mWifiShellCommand.exec(
+                new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                new String[]{"reset-coex-cell-channels"});
+        verify(mCoexManager, never()).resetMockCellChannels();
+        assertFalse(mWifiShellCommand.getErrPrintWriter().toString().isEmpty());
+
+        BinderUtil.setUid(Process.ROOT_UID);
+
+        mWifiShellCommand.exec(
+                new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                new String[]{"reset-coex-cell-channels"});
+        verify(mCoexManager).resetMockCellChannels();
+    }
+
+    @Test
     public void testStartSoftAp() {
         mWifiShellCommand.exec(
                 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
                 new String[]{"start-softap", "ap1", "wpa2", "xyzabc321", "-b", "5"});
         ArgumentCaptor<SoftApConfiguration> softApConfigurationCaptor = ArgumentCaptor.forClass(
                 SoftApConfiguration.class);
-        verify(mWifiService).startTetheredHotspot(softApConfigurationCaptor.capture());
+        verify(mWifiService).startTetheredHotspot(
+                softApConfigurationCaptor.capture(), eq(SHELL_PACKAGE_NAME));
         assertEquals(SoftApConfiguration.BAND_5GHZ,
                 softApConfigurationCaptor.getValue().getBand());
         assertEquals(SoftApConfiguration.SECURITY_TYPE_WPA2_PSK,
                 softApConfigurationCaptor.getValue().getSecurityType());
-        assertEquals("\"ap1\"", softApConfigurationCaptor.getValue().getSsid());
+        assertEquals("ap1", softApConfigurationCaptor.getValue().getSsid());
         assertEquals("xyzabc321", softApConfigurationCaptor.getValue().getPassphrase());
     }
 
@@ -423,4 +554,298 @@
                 new String[]{"stop-softap"});
         verify(mWifiService).stopSoftAp();
     }
+
+
+    @Test
+    public void testSetScanAlwaysAvailable() {
+        mWifiShellCommand.exec(
+                new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                new String[]{"set-scan-always-available", "enabled"});
+        verify(mWifiService).setScanAlwaysAvailable(true, SHELL_PACKAGE_NAME);
+
+        mWifiShellCommand.exec(
+                new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                new String[]{"set-scan-always-available", "disabled"});
+        verify(mWifiService).setScanAlwaysAvailable(false, SHELL_PACKAGE_NAME);
+    }
+
+    @Test
+    public void testAddSuggestionWithUntrusted() {
+        mWifiShellCommand.exec(
+                new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                new String[]{"add-suggestion", "ssid1234", "open", "-u"});
+        verify(mWifiService).addNetworkSuggestions(argThat(sL -> {
+            return (sL.size() == 1)
+                    && (sL.get(0).getSsid().equals("ssid1234"))
+                    && (sL.get(0).isUntrusted());
+        }), eq(SHELL_PACKAGE_NAME), any());
+        verify(mConnectivityManager).requestNetwork(argThat(nR -> {
+            return (nR.hasTransport(TRANSPORT_WIFI))
+                    && (!nR.hasCapability(NET_CAPABILITY_TRUSTED));
+        }), any(ConnectivityManager.NetworkCallback.class));
+
+        when(mWifiService.getNetworkSuggestions(any()))
+                .thenReturn(Arrays.asList(
+                        new WifiNetworkSuggestion.Builder()
+                                .setSsid("ssid1234")
+                                .setUntrusted(true)
+                                .build()));
+        mWifiShellCommand.exec(
+                new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                new String[]{"remove-suggestion", "ssid1234"});
+        verify(mWifiService).removeNetworkSuggestions(argThat(sL -> {
+            return (sL.size() == 1)
+                    && (sL.get(0).getSsid().equals("ssid1234"))
+                    && (sL.get(0).isUntrusted());
+        }), eq(SHELL_PACKAGE_NAME));
+        verify(mConnectivityManager).unregisterNetworkCallback(
+                any(ConnectivityManager.NetworkCallback.class));
+    }
+
+    @Test
+    public void testAddSuggestionWithOemPaid() {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        mWifiShellCommand.exec(
+                new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                new String[]{"add-suggestion", "ssid1234", "open", "-o"});
+        verify(mWifiService).addNetworkSuggestions(argThat(sL -> {
+            return (sL.size() == 1)
+                    && (sL.get(0).getSsid().equals("ssid1234"))
+                    && (sL.get(0).isOemPaid());
+        }), eq(SHELL_PACKAGE_NAME), any());
+        verify(mConnectivityManager).requestNetwork(argThat(nR -> {
+            return (nR.hasTransport(TRANSPORT_WIFI))
+                    && (nR.hasCapability(NET_CAPABILITY_OEM_PAID));
+        }), any(ConnectivityManager.NetworkCallback.class));
+
+        when(mWifiService.getNetworkSuggestions(any()))
+                .thenReturn(Arrays.asList(
+                        new WifiNetworkSuggestion.Builder()
+                                .setSsid("ssid1234")
+                                .setOemPaid(true)
+                                .build()));
+        mWifiShellCommand.exec(
+                new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                new String[]{"remove-suggestion", "ssid1234"});
+        verify(mWifiService).removeNetworkSuggestions(argThat(sL -> {
+            return (sL.size() == 1)
+                    && (sL.get(0).getSsid().equals("ssid1234"))
+                    && (sL.get(0).isOemPaid());
+        }), eq(SHELL_PACKAGE_NAME));
+        verify(mConnectivityManager).unregisterNetworkCallback(
+                any(ConnectivityManager.NetworkCallback.class));
+    }
+
+    @Test
+    public void testAddSuggestionWithOemPrivate() {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        mWifiShellCommand.exec(
+                new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                new String[]{"add-suggestion", "ssid1234", "open", "-p"});
+        verify(mWifiService).addNetworkSuggestions(argThat(sL -> {
+            return (sL.size() == 1)
+                    && (sL.get(0).getSsid().equals("ssid1234"))
+                    && (sL.get(0).isOemPrivate());
+        }), eq(SHELL_PACKAGE_NAME), any());
+        verify(mConnectivityManager).requestNetwork(argThat(nR -> {
+            return (nR.hasTransport(TRANSPORT_WIFI))
+                    && (nR.hasCapability(NET_CAPABILITY_OEM_PRIVATE));
+        }), any(ConnectivityManager.NetworkCallback.class));
+
+        when(mWifiService.getNetworkSuggestions(any()))
+                .thenReturn(Arrays.asList(
+                        new WifiNetworkSuggestion.Builder()
+                                .setSsid("ssid1234")
+                                .setOemPrivate(true)
+                                .build()));
+        mWifiShellCommand.exec(
+                new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                new String[]{"remove-suggestion", "ssid1234"});
+        verify(mWifiService).removeNetworkSuggestions(argThat(sL -> {
+            return (sL.size() == 1)
+                    && (sL.get(0).getSsid().equals("ssid1234"))
+                    && (sL.get(0).isOemPrivate());
+        }), eq(SHELL_PACKAGE_NAME));
+        verify(mConnectivityManager).unregisterNetworkCallback(
+                any(ConnectivityManager.NetworkCallback.class));
+    }
+
+    @Test
+    public void testAddSuggestionWithEnhancedMacRandomization() {
+        // default
+        mWifiShellCommand.exec(
+                new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                new String[]{"add-suggestion", "ssid1234", "open"});
+        verify(mWifiService).addNetworkSuggestions(argThat(sL -> {
+            return (sL.size() == 1)
+                    && (sL.get(0).getSsid().equals("ssid1234"))
+                    && (sL.get(0).getWifiConfiguration().macRandomizationSetting
+                    == WifiConfiguration.RANDOMIZATION_PERSISTENT);
+        }), eq(SHELL_PACKAGE_NAME), any());
+
+        // using enhanced MAC randomization.
+        if (SdkLevel.isAtLeastS()) {
+            mWifiShellCommand.exec(
+                    new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                    new String[]{"add-suggestion", "ssid1234", "open", "-r"});
+            verify(mWifiService).addNetworkSuggestions(argThat(sL -> {
+                return (sL.size() == 1)
+                        && (sL.get(0).getSsid().equals("ssid1234"))
+                        && (sL.get(0).getWifiConfiguration().macRandomizationSetting
+                        == WifiConfiguration.RANDOMIZATION_NON_PERSISTENT);
+            }), eq(SHELL_PACKAGE_NAME), any());
+        }
+    }
+
+    @Test
+    public void testStatus() {
+        when(mWifiService.getWifiEnabledState()).thenReturn(WIFI_STATE_ENABLED);
+
+        // unrooted shell.
+        mWifiShellCommand.exec(
+                new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                new String[]{"status"});
+        verify(mWifiService).getWifiEnabledState();
+        verify(mWifiService).isScanAlwaysAvailable();
+        verify(mWifiService).getConnectionInfo(SHELL_PACKAGE_NAME, null);
+
+        verify(mPrimaryClientModeManager, never()).syncRequestConnectionInfo();
+        verify(mActiveModeWarden, never()).getClientModeManagers();
+
+        // rooted shell.
+        BinderUtil.setUid(Process.ROOT_UID);
+
+        ClientModeManager additionalClientModeManager = mock(ClientModeManager.class);
+        when(mActiveModeWarden.getClientModeManagers()).thenReturn(
+                Arrays.asList(mPrimaryClientModeManager, additionalClientModeManager));
+
+        WifiInfo wifiInfo = new WifiInfo();
+        wifiInfo.setSupplicantState(SupplicantState.COMPLETED);
+        when(mPrimaryClientModeManager.syncRequestConnectionInfo()).thenReturn(wifiInfo);
+        when(additionalClientModeManager.syncRequestConnectionInfo()).thenReturn(wifiInfo);
+
+        mWifiShellCommand.exec(
+                new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                new String[]{"status"});
+        verify(mActiveModeWarden).getClientModeManagers();
+        verify(mPrimaryClientModeManager).syncRequestConnectionInfo();
+        verify(mPrimaryClientModeManager).syncGetCurrentNetwork();
+        verify(additionalClientModeManager).syncRequestConnectionInfo();
+        verify(additionalClientModeManager).syncGetCurrentNetwork();
+    }
+
+    @Test
+    public void testEnableEmergencyCallbackMode() {
+        mWifiShellCommand.exec(
+                new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                new String[]{"set-emergency-callback-mode", "enabled"});
+        verify(mActiveModeWarden, never()).emergencyCallbackModeChanged(anyBoolean());
+
+        BinderUtil.setUid(Process.ROOT_UID);
+
+        mWifiShellCommand.exec(
+                new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                new String[]{"set-emergency-callback-mode", "enabled"});
+        verify(mActiveModeWarden).emergencyCallbackModeChanged(true);
+    }
+
+    @Test
+    public void testDisableEmergencyCallbackMode() {
+        mWifiShellCommand.exec(
+                new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                new String[]{"set-emergency-callback-mode", "disabled"});
+        verify(mActiveModeWarden, never()).emergencyCallbackModeChanged(anyBoolean());
+
+        BinderUtil.setUid(Process.ROOT_UID);
+
+        mWifiShellCommand.exec(
+                new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                new String[]{"set-emergency-callback-mode", "disabled"});
+        verify(mActiveModeWarden).emergencyCallbackModeChanged(false);
+    }
+
+    @Test
+    public void testEnableEmergencyCallState() {
+        mWifiShellCommand.exec(
+                new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                new String[]{"set-emergency-call-state", "enabled"});
+        verify(mActiveModeWarden, never()).emergencyCallStateChanged(anyBoolean());
+
+        BinderUtil.setUid(Process.ROOT_UID);
+
+        mWifiShellCommand.exec(
+                new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                new String[]{"set-emergency-call-state", "enabled"});
+        verify(mActiveModeWarden).emergencyCallStateChanged(true);
+    }
+
+    @Test
+    public void testDisableEmergencyCallState() {
+        mWifiShellCommand.exec(
+                new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                new String[]{"set-emergency-call-state", "disabled"});
+        verify(mActiveModeWarden, never()).emergencyCallStateChanged(anyBoolean());
+
+        BinderUtil.setUid(Process.ROOT_UID);
+
+        mWifiShellCommand.exec(
+                new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                new String[]{"set-emergency-call-state", "disabled"});
+        verify(mActiveModeWarden).emergencyCallStateChanged(false);
+    }
+
+    @Test
+    public void testConnectNetworkWithNoneMacRandomization() {
+        mWifiShellCommand.exec(
+                new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                new String[]{"connect-network", "ssid1234", "open", "-r", "none"});
+        verify(mWifiService).connect(argThat(wifiConfiguration -> {
+            return (wifiConfiguration.SSID.equals("\"ssid1234\"")
+                    && wifiConfiguration.macRandomizationSetting
+                    == WifiConfiguration.RANDOMIZATION_NONE);
+        }), eq(-1), any());
+    }
+
+    @Test
+    public void testConnectNetworkWithNonPersistentMacRandomizationOnSAndAbove() {
+        assumeTrue(SdkLevel.isAtLeastS());
+
+        mWifiShellCommand.exec(
+                new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                new String[]{"connect-network", "ssid1234", "open", "-r", "non_persistent"});
+        verify(mWifiService).connect(argThat(wifiConfiguration -> {
+            return (wifiConfiguration.SSID.equals("\"ssid1234\"")
+                    && wifiConfiguration.macRandomizationSetting
+                    == WifiConfiguration.RANDOMIZATION_NON_PERSISTENT);
+        }), eq(-1), any());
+    }
+
+    @Test
+    public void testConnectNetworkWithNonPersistentMacRandomizationOnR() {
+        assumeFalse(SdkLevel.isAtLeastS());
+
+        assertEquals(-1, mWifiShellCommand.exec(
+                new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                new String[]{"connect-network", "ssid1234", "open", "-r", "non_persistent"}));
+    }
+
+    @Test
+    public void testEnableScanning() {
+        BinderUtil.setUid(Process.ROOT_UID);
+        mWifiShellCommand.exec(
+                new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                new String[]{"enable-scanning", "enabled"});
+        verify(mScanRequestProxy).enableScanning(true, false);
+    }
+
+    @Test
+    public void testEnableScanningWithHiddenNetworkOption() {
+        BinderUtil.setUid(Process.ROOT_UID);
+        mWifiShellCommand.exec(
+                new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                new String[]{"enable-scanning", "enabled", "-h"});
+        verify(mScanRequestProxy).enableScanning(true, true);
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiStateTrackerTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiStateTrackerTest.java
index 8531240..0efd57c 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiStateTrackerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiStateTrackerTest.java
@@ -18,6 +18,7 @@
 
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
@@ -30,15 +31,16 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-/*
- * Unit tests for {@link com.android.server.wifi.WifiStateTracker}.
- */
+/** Unit tests for {@link com.android.server.wifi.WifiStateTracker}. */
 @SmallTest
 public class WifiStateTrackerTest extends WifiBaseTest {
 
     private static final String TAG = "WifiStateTrackerTest";
-    @Mock
-    BatteryStatsManager mBatteryStats;
+
+    private static final String WLAN0 = "wlan0";
+    private static final String WLAN1 = "wlan1";
+
+    @Mock BatteryStatsManager mBatteryStats;
     private WifiStateTracker mWifiStateTracker;
 
     /**
@@ -56,11 +58,14 @@
      */
     @Test
     public void testBatteryStatsUpdated() throws Exception {
-        int[] relevantStates = new int[] { WifiStateTracker.SCAN_MODE,
-                WifiStateTracker.CONNECTED, WifiStateTracker.DISCONNECTED,
-                WifiStateTracker.SOFT_AP};
-        for (int i = 0; i < relevantStates.length; i++) {
-            mWifiStateTracker.updateState(relevantStates[i]);
+        int[] relevantStates = {
+                WifiStateTracker.SCAN_MODE,
+                WifiStateTracker.CONNECTED,
+                WifiStateTracker.DISCONNECTED,
+                WifiStateTracker.SOFT_AP
+        };
+        for (int relevantState : relevantStates) {
+            mWifiStateTracker.updateState(WLAN0, relevantState);
         }
         verify(mBatteryStats, times(relevantStates.length)).reportWifiState(anyInt(), any());
     }
@@ -71,11 +76,48 @@
      */
     @Test
     public void testBatteryStatsNotUpdated() throws Exception {
-        int[] irrelevantStates = new int[] { WifiStateTracker.SCAN_MODE - 1,
-                WifiStateTracker.SOFT_AP + 1};
-        for (int i = 0; i < irrelevantStates.length; i++) {
-            mWifiStateTracker.updateState(irrelevantStates[i]);
+        int[] irrelevantStates = {
+                WifiStateTracker.SCAN_MODE - 1,
+                WifiStateTracker.SOFT_AP + 1
+        };
+        for (int irrelevantState : irrelevantStates) {
+            mWifiStateTracker.updateState(WLAN0, irrelevantState);
         }
         verify(mBatteryStats, times(0)).reportWifiState(anyInt(), any());
     }
+
+    @Test
+    public void updateOnMultipleIfaces() throws Exception {
+        mWifiStateTracker.updateState(WLAN0, WifiStateTracker.INVALID);
+        verify(mBatteryStats, never()).reportWifiState(anyInt(), any());
+
+        mWifiStateTracker.updateState(WLAN0, WifiStateTracker.DISCONNECTED);
+        verify(mBatteryStats).reportWifiState(
+                BatteryStatsManager.WIFI_STATE_ON_DISCONNECTED, null);
+
+        mWifiStateTracker.updateState(WLAN0, WifiStateTracker.CONNECTED);
+        verify(mBatteryStats).reportWifiState(
+                BatteryStatsManager.WIFI_STATE_ON_CONNECTED_STA, null);
+
+        mWifiStateTracker.updateState(WLAN1, WifiStateTracker.DISCONNECTED);
+        verify(mBatteryStats).reportWifiState(
+                BatteryStatsManager.WIFI_STATE_ON_CONNECTED_STA, null);
+
+        mWifiStateTracker.updateState(WLAN1, WifiStateTracker.CONNECTED);
+        verify(mBatteryStats).reportWifiState(
+                BatteryStatsManager.WIFI_STATE_ON_CONNECTED_STA, null);
+
+        mWifiStateTracker.updateState(WLAN0, WifiStateTracker.DISCONNECTED);
+        verify(mBatteryStats).reportWifiState(
+                BatteryStatsManager.WIFI_STATE_ON_CONNECTED_STA, null);
+
+        mWifiStateTracker.updateState(WLAN1, WifiStateTracker.DISCONNECTED);
+        verify(mBatteryStats, times(2)).reportWifiState(
+                BatteryStatsManager.WIFI_STATE_ON_DISCONNECTED, null);
+        verify(mBatteryStats, times(3)).reportWifiState(anyInt(), any());
+
+        mWifiStateTracker.updateState(WLAN1, WifiStateTracker.INVALID);
+        mWifiStateTracker.updateState(WLAN0, WifiStateTracker.INVALID);
+        verify(mBatteryStats, times(3)).reportWifiState(anyInt(), any());
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiTrafficPollerTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiTrafficPollerTest.java
index ecdf490..77be9df 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiTrafficPollerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiTrafficPollerTest.java
@@ -18,21 +18,26 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
+import android.content.Context;
 import android.net.wifi.ITrafficStateCallback;
 import android.net.wifi.WifiManager;
-import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.test.TestLooper;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.wifi.resources.R;
+
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -48,8 +53,6 @@
     private final static long DEFAULT_PACKET_COUNT = 10;
     private final static long TX_PACKET_COUNT = 40;
     private final static long RX_PACKET_COUNT = 50;
-    private static final int TEST_TRAFFIC_STATE_CALLBACK_IDENTIFIER = 14;
-    private static final int TEST_TRAFFIC_STATE_CALLBACK_IDENTIFIER2 = 42;
 
     @Mock IBinder mAppBinder;
     @Mock ITrafficStateCallback mTrafficStateCallback;
@@ -57,6 +60,10 @@
     @Mock IBinder mAppBinder2;
     @Mock ITrafficStateCallback mTrafficStateCallback2;
 
+    @Mock private Context mContext;
+    private MockResources mResources;
+    private InOrder mInOrder;
+
     /**
      * Called before each test
      */
@@ -65,11 +72,18 @@
         // Ensure looper exists
         mLooper = new TestLooper();
         MockitoAnnotations.initMocks(this);
+        mResources = new MockResources();
+        mResources.setInteger(R.integer.config_wifiTrafficPollerTxPacketThreshold, 5);
+        mResources.setInteger(R.integer.config_wifiTrafficPollerRxPacketThreshold, 9);
+        when(mContext.getResources()).thenReturn(mResources);
 
-        mWifiTrafficPoller = new WifiTrafficPoller(new Handler(mLooper.getLooper()));
+        mWifiTrafficPoller = new WifiTrafficPoller(mContext);
 
         // Set the current mTxPkts and mRxPkts to DEFAULT_PACKET_COUNT
         mWifiTrafficPoller.notifyOnDataActivity(DEFAULT_PACKET_COUNT, DEFAULT_PACKET_COUNT);
+
+        when(mTrafficStateCallback.asBinder()).thenReturn(mAppBinder);
+        when(mTrafficStateCallback2.asBinder()).thenReturn(mAppBinder2);
     }
 
     /**
@@ -78,8 +92,7 @@
     @Test
     public void testClientNotification() throws RemoteException {
         // Register Client to verify that Tx/RX packet message is properly received.
-        mWifiTrafficPoller.addCallback(
-                mAppBinder, mTrafficStateCallback, TEST_TRAFFIC_STATE_CALLBACK_IDENTIFIER);
+        mWifiTrafficPoller.addCallback(mTrafficStateCallback);
         mWifiTrafficPoller.notifyOnDataActivity(TX_PACKET_COUNT, RX_PACKET_COUNT);
 
         // Client should get the DATA_ACTIVITY_NOTIFICATION
@@ -88,14 +101,53 @@
     }
 
     /**
+     * Verify that Tx/Rx packet count meets the threshold for updating data activity type
+     */
+    @Test
+    public void testDataActivityUpdatePacketThreshold() throws RemoteException {
+        // Register Client to verify that Tx/RX packet message is properly received.
+        mWifiTrafficPoller.addCallback(mTrafficStateCallback);
+        mWifiTrafficPoller.notifyOnDataActivity(TX_PACKET_COUNT, RX_PACKET_COUNT);
+
+        mInOrder = inOrder(mTrafficStateCallback);
+        // Client should get the DATA_ACTIVITY_NOTIFICATION
+        mInOrder.verify(mTrafficStateCallback).onStateChanged(
+                WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT);
+
+        // TxPacket increase below threshold
+        mWifiTrafficPoller.notifyOnDataActivity(TX_PACKET_COUNT + 3, RX_PACKET_COUNT);
+        // Client should get the no DATA_ACTIVITY_NOTIFICATION
+        mInOrder.verify(mTrafficStateCallback).onStateChanged(
+                WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE);
+
+        // TxPacket increase above threshold
+        mWifiTrafficPoller.notifyOnDataActivity(TX_PACKET_COUNT + 10, RX_PACKET_COUNT);
+        // called once with OUT
+        mInOrder.verify(mTrafficStateCallback)
+                .onStateChanged(WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT);
+
+        // RxPacket increase below threshold
+        mWifiTrafficPoller.notifyOnDataActivity(TX_PACKET_COUNT + 10, RX_PACKET_COUNT + 6);
+        // Client should get the no DATA_ACTIVITY_NOTIFICATION
+        mInOrder.verify(mTrafficStateCallback).onStateChanged(
+                WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE);
+
+        // RxPacket increase above threshold
+        mWifiTrafficPoller.notifyOnDataActivity(TX_PACKET_COUNT + 10, RX_PACKET_COUNT + 20);
+        // called once with IN
+        mInOrder.verify(mTrafficStateCallback)
+                .onStateChanged(WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN);
+    }
+
+
+    /**
      * Verify that remove client should be handled
      */
     @Test
     public void testRemoveClient() throws RemoteException {
         // Register Client to verify that Tx/RX packet message is properly received.
-        mWifiTrafficPoller.addCallback(
-                mAppBinder, mTrafficStateCallback, TEST_TRAFFIC_STATE_CALLBACK_IDENTIFIER);
-        mWifiTrafficPoller.removeCallback(TEST_TRAFFIC_STATE_CALLBACK_IDENTIFIER);
+        mWifiTrafficPoller.addCallback(mTrafficStateCallback);
+        mWifiTrafficPoller.removeCallback(mTrafficStateCallback);
         verify(mAppBinder).unlinkToDeath(any(), anyInt());
 
         mWifiTrafficPoller.notifyOnDataActivity(TX_PACKET_COUNT, RX_PACKET_COUNT);
@@ -105,14 +157,13 @@
     }
 
     /**
-     * Verify that remove client ignores when callback identifier is wrong.
+     * Verify that remove client ignores when callback is wrong.
      */
     @Test
-    public void testRemoveClientWithWrongIdentifier() throws RemoteException {
+    public void testRemoveClientWithWrongCallback() throws RemoteException {
         // Register Client to verify that Tx/RX packet message is properly received.
-        mWifiTrafficPoller.addCallback(
-                mAppBinder, mTrafficStateCallback, TEST_TRAFFIC_STATE_CALLBACK_IDENTIFIER);
-        mWifiTrafficPoller.removeCallback(TEST_TRAFFIC_STATE_CALLBACK_IDENTIFIER + 5);
+        mWifiTrafficPoller.addCallback(mTrafficStateCallback);
+        mWifiTrafficPoller.removeCallback(mTrafficStateCallback2);
         mLooper.dispatchAll();
 
         mWifiTrafficPoller.notifyOnDataActivity(TX_PACKET_COUNT, RX_PACKET_COUNT);
@@ -128,8 +179,7 @@
      */
     @Test
     public void registersForBinderDeathOnAddClient() throws Exception {
-        mWifiTrafficPoller.addCallback(
-                mAppBinder, mTrafficStateCallback, TEST_TRAFFIC_STATE_CALLBACK_IDENTIFIER);
+        mWifiTrafficPoller.addCallback(mTrafficStateCallback);
         verify(mAppBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
     }
 
@@ -141,8 +191,7 @@
     public void addCallbackFailureOnLinkToDeath() throws Exception {
         doThrow(new RemoteException())
                 .when(mAppBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
-        mWifiTrafficPoller.addCallback(
-                mAppBinder, mTrafficStateCallback, TEST_TRAFFIC_STATE_CALLBACK_IDENTIFIER);
+        mWifiTrafficPoller.addCallback(mTrafficStateCallback);
         verify(mAppBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
 
         mWifiTrafficPoller.notifyOnDataActivity(TX_PACKET_COUNT, RX_PACKET_COUNT);
@@ -154,8 +203,7 @@
     /** Test that if the data activity didn't change, the client is not notified. */
     @Test
     public void unchangedDataActivityNotNotified() throws Exception {
-        mWifiTrafficPoller.addCallback(
-                mAppBinder, mTrafficStateCallback, TEST_TRAFFIC_STATE_CALLBACK_IDENTIFIER);
+        mWifiTrafficPoller.addCallback(mTrafficStateCallback);
         mWifiTrafficPoller.notifyOnDataActivity(TX_PACKET_COUNT, RX_PACKET_COUNT);
 
         verify(mTrafficStateCallback).onStateChanged(
@@ -163,7 +211,7 @@
 
         // since TX and RX both increased, should still be INOUT. But since it's the same data
         // activity as before, the callback should not be triggered again.
-        mWifiTrafficPoller.notifyOnDataActivity(TX_PACKET_COUNT + 1, RX_PACKET_COUNT + 1);
+        mWifiTrafficPoller.notifyOnDataActivity(TX_PACKET_COUNT + 10, RX_PACKET_COUNT + 10);
 
         // still only called once
         verify(mTrafficStateCallback).onStateChanged(anyInt());
@@ -175,17 +223,15 @@
      */
     @Test
     public void multipleCallbacksOnlyChangedNotified() throws Exception {
-        mWifiTrafficPoller.addCallback(
-                mAppBinder, mTrafficStateCallback, TEST_TRAFFIC_STATE_CALLBACK_IDENTIFIER);
+        mWifiTrafficPoller.addCallback(mTrafficStateCallback);
         mWifiTrafficPoller.notifyOnDataActivity(TX_PACKET_COUNT, RX_PACKET_COUNT);
 
         verify(mTrafficStateCallback).onStateChanged(
                 WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT);
         verify(mTrafficStateCallback2, never()).onStateChanged(anyInt());
 
-        mWifiTrafficPoller.addCallback(
-                mAppBinder2, mTrafficStateCallback2, TEST_TRAFFIC_STATE_CALLBACK_IDENTIFIER2);
-        mWifiTrafficPoller.notifyOnDataActivity(TX_PACKET_COUNT + 1, RX_PACKET_COUNT + 1);
+        mWifiTrafficPoller.addCallback(mTrafficStateCallback2);
+        mWifiTrafficPoller.notifyOnDataActivity(TX_PACKET_COUNT + 10, RX_PACKET_COUNT + 10);
 
         // still only called once
         verify(mTrafficStateCallback).onStateChanged(anyInt());
@@ -196,7 +242,7 @@
         verify(mTrafficStateCallback2).onStateChanged(anyInt());
 
         // now only TX increased
-        mWifiTrafficPoller.notifyOnDataActivity(TX_PACKET_COUNT + 2, RX_PACKET_COUNT + 1);
+        mWifiTrafficPoller.notifyOnDataActivity(TX_PACKET_COUNT + 20, RX_PACKET_COUNT + 10);
 
         // called once with OUT
         verify(mTrafficStateCallback)
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java
index cb1ff28..6aa3c8c 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java
@@ -23,10 +23,13 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyByte;
 import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyLong;
 import static org.mockito.Mockito.anyObject;
 import static org.mockito.Mockito.anyShort;
 import static org.mockito.Mockito.doAnswer;
@@ -44,6 +47,9 @@
 import static org.mockito.Mockito.when;
 
 import android.app.test.MockAnswerUtil.AnswerWithArguments;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
 import android.hardware.wifi.V1_0.IWifiApIface;
 import android.hardware.wifi.V1_0.IWifiChip;
 import android.hardware.wifi.V1_0.IWifiChipEventCallback;
@@ -59,6 +65,7 @@
 import android.hardware.wifi.V1_0.StaLinkLayerRadioStats;
 import android.hardware.wifi.V1_0.StaLinkLayerStats;
 import android.hardware.wifi.V1_0.StaRoamingCapabilities;
+import android.hardware.wifi.V1_0.StaRoamingConfig;
 import android.hardware.wifi.V1_0.StaRoamingState;
 import android.hardware.wifi.V1_0.StaScanData;
 import android.hardware.wifi.V1_0.StaScanDataFlagMask;
@@ -78,25 +85,41 @@
 import android.hardware.wifi.V1_2.IWifiChipEventCallback.IfaceInfo;
 import android.hardware.wifi.V1_2.IWifiChipEventCallback.RadioModeInfo;
 import android.hardware.wifi.V1_3.WifiChannelStats;
+import android.hardware.wifi.V1_5.IWifiChip.MultiStaUseCase;
+import android.hardware.wifi.V1_5.StaLinkLayerIfaceContentionTimeStats;
+import android.hardware.wifi.V1_5.StaPeerInfo;
+import android.hardware.wifi.V1_5.StaRateStat;
+import android.hardware.wifi.V1_5.WifiUsableChannel;
+import android.net.InetAddresses;
 import android.net.KeepalivePacketData;
 import android.net.MacAddress;
 import android.net.NattKeepalivePacketData;
 import android.net.apf.ApfCapabilities;
+import android.net.wifi.CoexUnsafeChannel;
 import android.net.wifi.ScanResult;
+import android.net.wifi.SoftApConfiguration;
+import android.net.wifi.WifiAvailableChannel;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiScanner;
 import android.net.wifi.WifiSsid;
 import android.os.Handler;
 import android.os.RemoteException;
+import android.os.WorkSource;
 import android.os.test.TestLooper;
 import android.system.OsConstants;
 import android.util.Pair;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.HalDeviceManager.InterfaceDestroyedListener;
 import com.android.server.wifi.WifiLinkLayerStats.ChannelStats;
+import com.android.server.wifi.WifiLinkLayerStats.RadioStat;
+import com.android.server.wifi.WifiNative.RoamingCapabilities;
+import com.android.server.wifi.WifiNative.RxFateReport;
+import com.android.server.wifi.WifiNative.TxFateReport;
 import com.android.server.wifi.util.NativeUtil;
+import com.android.wifi.resources.R;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -109,6 +132,7 @@
 import java.net.InetAddress;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Random;
@@ -125,6 +149,7 @@
     private static final MacAddress TEST_MAC_ADDRESS = MacAddress.fromString("ee:33:a2:94:10:92");
     private static final int[] TEST_FREQUENCIES =
             {2412, 2417, 2422, 2427, 2432, 2437};
+    private static final WorkSource TEST_WORKSOURCE = new WorkSource();
 
     private WifiVendorHal mWifiVendorHal;
     private WifiStatus mWifiStatusSuccess;
@@ -134,6 +159,12 @@
     private TestLooper mLooper;
     private Handler mHandler;
     @Mock
+    private Resources mResources;
+    @Mock
+    private Context mContext;
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
     private HalDeviceManager mHalDeviceManager;
     @Mock
     private WifiVendorHal.HalDeviceManagerStatusListener mHalDeviceManagerStatusCallbacks;
@@ -142,6 +173,8 @@
     @Mock
     private android.hardware.wifi.V1_4.IWifiApIface mIWifiApIfaceV14;
     @Mock
+    private android.hardware.wifi.V1_5.IWifiApIface mIWifiApIfaceV15;
+    @Mock
     private IWifiChip mIWifiChip;
     @Mock
     private android.hardware.wifi.V1_1.IWifiChip mIWifiChipV11;
@@ -152,11 +185,15 @@
     @Mock
     private android.hardware.wifi.V1_4.IWifiChip mIWifiChipV14;
     @Mock
+    private android.hardware.wifi.V1_5.IWifiChip mIWifiChipV15;
+    @Mock
     private IWifiStaIface mIWifiStaIface;
     @Mock
     private android.hardware.wifi.V1_2.IWifiStaIface mIWifiStaIfaceV12;
     @Mock
     private android.hardware.wifi.V1_3.IWifiStaIface mIWifiStaIfaceV13;
+    @Mock
+    private android.hardware.wifi.V1_5.IWifiStaIface mIWifiStaIfaceV15;
     private IWifiStaIfaceEventCallback mIWifiStaIfaceEventCallback;
     private IWifiChipEventCallback mIWifiChipEventCallback;
     private android.hardware.wifi.V1_2.IWifiChipEventCallback mIWifiChipEventCallbackV12;
@@ -165,14 +202,17 @@
     private WifiNative.VendorHalDeathEventHandler mVendorHalDeathHandler;
     @Mock
     private WifiNative.VendorHalRadioModeChangeEventHandler mVendorHalRadioModeChangeHandler;
+    @Mock
+    private WifiGlobals mWifiGlobals;
 
     /**
      * Spy used to return the V1_1 IWifiChip mock object to simulate the 1.1 HAL running on the
      * device.
      */
     private class WifiVendorHalSpyV1_1 extends WifiVendorHal {
-        WifiVendorHalSpyV1_1(HalDeviceManager halDeviceManager, Handler handler) {
-            super(halDeviceManager, handler);
+        WifiVendorHalSpyV1_1(Context context, HalDeviceManager halDeviceManager, Handler handler,
+                WifiGlobals wifiGlobals) {
+            super(context, halDeviceManager, handler, wifiGlobals);
         }
 
         @Override
@@ -206,20 +246,22 @@
                 String ifaceName) {
             return null;
         }
+
+        @Override
+        protected android.hardware.wifi.V1_5.IWifiStaIface getWifiStaIfaceForV1_5Mockable(
+                String ifaceName) {
+            return null;
+        }
     }
 
     /**
      * Spy used to return the V1_2 IWifiChip and IWifiStaIface mock objects to simulate
      * the 1.2 HAL running on the device.
      */
-    private class WifiVendorHalSpyV1_2 extends WifiVendorHal {
-        WifiVendorHalSpyV1_2(HalDeviceManager halDeviceManager, Handler handler) {
-            super(halDeviceManager, handler);
-        }
-
-        @Override
-        protected android.hardware.wifi.V1_1.IWifiChip getWifiChipForV1_1Mockable() {
-            return mIWifiChipV11;
+    private class WifiVendorHalSpyV1_2 extends WifiVendorHalSpyV1_1 {
+        WifiVendorHalSpyV1_2(Context context, HalDeviceManager halDeviceManager, Handler handler,
+                WifiGlobals wifiGlobals) {
+            super(context, halDeviceManager, handler, wifiGlobals);
         }
 
         @Override
@@ -228,45 +270,20 @@
         }
 
         @Override
-        protected android.hardware.wifi.V1_3.IWifiChip getWifiChipForV1_3Mockable() {
-            return null;
-        }
-
-        @Override
-        protected android.hardware.wifi.V1_4.IWifiChip getWifiChipForV1_4Mockable() {
-            return null;
-        }
-
-        @Override
         protected android.hardware.wifi.V1_2.IWifiStaIface getWifiStaIfaceForV1_2Mockable(
                 String ifaceName) {
             return mIWifiStaIfaceV12;
         }
-
-        @Override
-        protected android.hardware.wifi.V1_3.IWifiStaIface getWifiStaIfaceForV1_3Mockable(
-                String ifaceName) {
-            return null;
-        }
     }
 
     /**
      * Spy used to return the V1_3 IWifiChip and V1_3 IWifiStaIface mock objects to simulate
      * the 1.3 HAL running on the device.
      */
-    private class WifiVendorHalSpyV1_3 extends WifiVendorHal {
-        WifiVendorHalSpyV1_3(HalDeviceManager halDeviceManager, Handler handler) {
-            super(halDeviceManager, handler);
-        }
-
-        @Override
-        protected android.hardware.wifi.V1_1.IWifiChip getWifiChipForV1_1Mockable() {
-            return mIWifiChipV11;
-        }
-
-        @Override
-        protected android.hardware.wifi.V1_2.IWifiChip getWifiChipForV1_2Mockable() {
-            return mIWifiChipV12;
+    private class WifiVendorHalSpyV1_3 extends WifiVendorHalSpyV1_2 {
+        WifiVendorHalSpyV1_3(Context context, HalDeviceManager halDeviceManager, Handler handler,
+                WifiGlobals wifiGlobals) {
+            super(context, halDeviceManager, handler, wifiGlobals);
         }
 
         @Override
@@ -275,17 +292,6 @@
         }
 
         @Override
-        protected android.hardware.wifi.V1_4.IWifiChip getWifiChipForV1_4Mockable() {
-            return null;
-        }
-
-        @Override
-        protected android.hardware.wifi.V1_2.IWifiStaIface getWifiStaIfaceForV1_2Mockable(
-                String ifaceName) {
-            return mIWifiStaIfaceV12;
-        }
-
-        @Override
         protected android.hardware.wifi.V1_3.IWifiStaIface getWifiStaIfaceForV1_3Mockable(
                 String ifaceName) {
             return mIWifiStaIfaceV13;
@@ -296,41 +302,37 @@
      * Spy used to return the V1_4 IWifiChip and V1_4 IWifiStaIface mock objects to simulate
      * the 1.4 HAL running on the device.
      */
-    private class WifiVendorHalSpyV1_4 extends WifiVendorHal {
-        WifiVendorHalSpyV1_4(HalDeviceManager halDeviceManager, Handler handler) {
-            super(halDeviceManager, handler);
-        }
-
-        @Override
-        protected android.hardware.wifi.V1_1.IWifiChip getWifiChipForV1_1Mockable() {
-            return mIWifiChipV11;
-        }
-
-        @Override
-        protected android.hardware.wifi.V1_2.IWifiChip getWifiChipForV1_2Mockable() {
-            return mIWifiChipV12;
-        }
-
-        @Override
-        protected android.hardware.wifi.V1_3.IWifiChip getWifiChipForV1_3Mockable() {
-            return mIWifiChipV13;
+    private class WifiVendorHalSpyV1_4 extends WifiVendorHalSpyV1_3 {
+        WifiVendorHalSpyV1_4(Context context, HalDeviceManager halDeviceManager, Handler handler,
+                WifiGlobals wifiGlobals) {
+            super(context, halDeviceManager, handler, wifiGlobals);
         }
 
         @Override
         protected android.hardware.wifi.V1_4.IWifiChip getWifiChipForV1_4Mockable() {
             return mIWifiChipV14;
         }
+    }
 
-        @Override
-        protected android.hardware.wifi.V1_2.IWifiStaIface getWifiStaIfaceForV1_2Mockable(
-                String ifaceName) {
-            return mIWifiStaIfaceV12;
+    /**
+     * Spy used to return the V1_5 IWifiChip and V1_5 IWifiStaIface mock objects to simulate
+     * the 1.5 HAL running on the device.
+     */
+    private class WifiVendorHalSpyV1_5 extends WifiVendorHalSpyV1_4 {
+        WifiVendorHalSpyV1_5(Context context, HalDeviceManager halDeviceManager, Handler handler,
+                WifiGlobals wifiGlobals) {
+            super(context, halDeviceManager, handler, wifiGlobals);
         }
 
         @Override
-        protected android.hardware.wifi.V1_3.IWifiStaIface getWifiStaIfaceForV1_3Mockable(
+        protected android.hardware.wifi.V1_5.IWifiChip getWifiChipForV1_5Mockable() {
+            return mIWifiChipV15;
+        }
+
+        @Override
+        protected android.hardware.wifi.V1_5.IWifiStaIface getWifiStaIfaceForV1_5Mockable(
                 String ifaceName) {
-            return mIWifiStaIfaceV13;
+            return mIWifiStaIfaceV15;
         }
     }
 
@@ -380,13 +382,14 @@
                 mLooper.dispatchAll();
             }
         }).when(mHalDeviceManager).stop();
-        when(mHalDeviceManager.createStaIface(any(), eq(null)))
+        when(mHalDeviceManager.createStaIface(any(), eq(null), any()))
                 .thenReturn(mIWifiStaIface);
-        when(mHalDeviceManager.createApIface(any(), eq(null)))
+        when(mHalDeviceManager.createApIface(anyLong(), any(), eq(null), any(), anyBoolean()))
                 .thenReturn(mIWifiApIface);
         when(mHalDeviceManager.removeIface(any())).thenReturn(true);
         when(mHalDeviceManager.getChip(any(IWifiIface.class)))
                 .thenReturn(mIWifiChip);
+        when(mHalDeviceManager.isSupported()).thenReturn(true);
         when(mIWifiChip.registerEventCallback(any(IWifiChipEventCallback.class)))
                 .thenReturn(mWifiStatusSuccess);
         mIWifiStaIfaceEventCallback = null;
@@ -433,8 +436,11 @@
             }
         }).when(mIWifiApIface).getName(any(IWifiIface.getNameCallback.class));
 
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getBoolean(R.bool.config_wifiLinkLayerAllRadiosStatsAggregationEnabled))
+                .thenReturn(false);
         // Create the vendor HAL object under test.
-        mWifiVendorHal = new WifiVendorHal(mHalDeviceManager, mHandler);
+        mWifiVendorHal = new WifiVendorHal(mContext, mHalDeviceManager, mHandler, mWifiGlobals);
 
         // Initialize the vendor HAL to capture the registered callback.
         mWifiVendorHal.initialize(mVendorHalDeathHandler);
@@ -456,14 +462,15 @@
         assertTrue(mWifiVendorHal.isHalStarted());
 
         verify(mHalDeviceManager).start();
-        verify(mHalDeviceManager).createStaIface(any(), eq(null));
+        verify(mHalDeviceManager).createStaIface(any(), eq(null), any());
         verify(mHalDeviceManager).getChip(eq(mIWifiStaIface));
         verify(mHalDeviceManager).isReady();
         verify(mHalDeviceManager).isStarted();
         verify(mIWifiStaIface).registerEventCallback(any(IWifiStaIfaceEventCallback.class));
         verify(mIWifiChip).registerEventCallback(any(IWifiChipEventCallback.class));
 
-        verify(mHalDeviceManager, never()).createApIface(any(), eq(null));
+        verify(mHalDeviceManager, never()).createApIface(
+                anyLong(), any(), eq(null), any(), anyBoolean());
     }
 
     /**
@@ -476,12 +483,13 @@
         assertTrue(mWifiVendorHal.isHalStarted());
 
         verify(mHalDeviceManager).start();
-        verify(mHalDeviceManager).createApIface(any(), eq(null));
+        verify(mHalDeviceManager).createApIface(
+                anyLong(), any(), eq(null), any(), anyBoolean());
         verify(mHalDeviceManager).getChip(eq(mIWifiApIface));
         verify(mHalDeviceManager).isReady();
         verify(mHalDeviceManager).isStarted();
 
-        verify(mHalDeviceManager, never()).createStaIface(any(), eq(null));
+        verify(mHalDeviceManager, never()).createStaIface(any(), eq(null), any());
     }
 
     /**
@@ -502,8 +510,9 @@
 
         verify(mHalDeviceManager).start();
 
-        verify(mHalDeviceManager, never()).createStaIface(any(), eq(null));
-        verify(mHalDeviceManager, never()).createApIface(any(), eq(null));
+        verify(mHalDeviceManager, never()).createStaIface(any(), eq(null), any());
+        verify(mHalDeviceManager, never()).createApIface(
+                anyLong(), any(), eq(null), any(), anyBoolean());
         verify(mHalDeviceManager, never()).getChip(any(IWifiIface.class));
         verify(mIWifiStaIface, never())
                 .registerEventCallback(any(IWifiStaIfaceEventCallback.class));
@@ -515,15 +524,16 @@
      */
     @Test
     public void testStartHalFailureInIfaceCreationInStaMode() throws Exception {
-        when(mHalDeviceManager.createStaIface(any(), eq(null))).thenReturn(null);
+        when(mHalDeviceManager.createStaIface(any(), eq(null), any())).thenReturn(null);
         assertFalse(mWifiVendorHal.startVendorHalSta());
         assertFalse(mWifiVendorHal.isHalStarted());
 
         verify(mHalDeviceManager).start();
-        verify(mHalDeviceManager).createStaIface(any(), eq(null));
+        verify(mHalDeviceManager).createStaIface(any(), eq(null), any());
         verify(mHalDeviceManager).stop();
 
-        verify(mHalDeviceManager, never()).createApIface(any(), eq(null));
+        verify(mHalDeviceManager, never()).createApIface(
+                anyLong(), any(), eq(null), any(), anyBoolean());
         verify(mHalDeviceManager, never()).getChip(any(IWifiIface.class));
         verify(mIWifiStaIface, never())
                 .registerEventCallback(any(IWifiStaIfaceEventCallback.class));
@@ -540,12 +550,13 @@
         assertFalse(mWifiVendorHal.isHalStarted());
 
         verify(mHalDeviceManager).start();
-        verify(mHalDeviceManager).createStaIface(any(), eq(null));
+        verify(mHalDeviceManager).createStaIface(any(), eq(null), any());
         verify(mHalDeviceManager).getChip(any(IWifiIface.class));
         verify(mHalDeviceManager).stop();
         verify(mIWifiStaIface).registerEventCallback(any(IWifiStaIfaceEventCallback.class));
 
-        verify(mHalDeviceManager, never()).createApIface(any(), eq(null));
+        verify(mHalDeviceManager, never()).createApIface(
+                anyLong(), any(), eq(null), any(), anyBoolean());
     }
 
     /**
@@ -560,12 +571,13 @@
         assertFalse(mWifiVendorHal.isHalStarted());
 
         verify(mHalDeviceManager).start();
-        verify(mHalDeviceManager).createStaIface(any(), eq(null));
+        verify(mHalDeviceManager).createStaIface(any(), eq(null), any());
         verify(mHalDeviceManager).stop();
         verify(mIWifiStaIface).registerEventCallback(any(IWifiStaIfaceEventCallback.class));
 
         verify(mHalDeviceManager, never()).getChip(any(IWifiIface.class));
-        verify(mHalDeviceManager, never()).createApIface(any(), eq(null));
+        verify(mHalDeviceManager, never()).createApIface(
+                anyLong(), any(), eq(null), any(), anyBoolean());
     }
 
     /**
@@ -580,13 +592,14 @@
         assertFalse(mWifiVendorHal.isHalStarted());
 
         verify(mHalDeviceManager).start();
-        verify(mHalDeviceManager).createStaIface(any(), eq(null));
+        verify(mHalDeviceManager).createStaIface(any(), eq(null), any());
         verify(mHalDeviceManager).getChip(any(IWifiIface.class));
         verify(mHalDeviceManager).stop();
         verify(mIWifiStaIface).registerEventCallback(any(IWifiStaIfaceEventCallback.class));
         verify(mIWifiChip).registerEventCallback(any(IWifiChipEventCallback.class));
 
-        verify(mHalDeviceManager, never()).createApIface(any(), eq(null));
+        verify(mHalDeviceManager, never()).createApIface(
+                anyLong(), any(), eq(null), any(), anyBoolean());
     }
 
     /**
@@ -595,15 +608,16 @@
      */
     @Test
     public void testStartHalFailureInApMode() throws Exception {
-        when(mHalDeviceManager.createApIface(any(), eq(null))).thenReturn(null);
+        when(mHalDeviceManager.createApIface(anyLong(), any(), eq(null), any(), anyBoolean()))
+                .thenReturn(null);
         assertFalse(mWifiVendorHal.startVendorHalAp());
         assertFalse(mWifiVendorHal.isHalStarted());
 
         verify(mHalDeviceManager).start();
-        verify(mHalDeviceManager).createApIface(any(), eq(null));
+        verify(mHalDeviceManager).createApIface(anyLong(), any(), eq(null), any(), anyBoolean());
         verify(mHalDeviceManager).stop();
 
-        verify(mHalDeviceManager, never()).createStaIface(any(), eq(null));
+        verify(mHalDeviceManager, never()).createStaIface(any(), eq(null), any());
         verify(mHalDeviceManager, never()).getChip(any(IWifiIface.class));
     }
 
@@ -621,12 +635,13 @@
 
         verify(mHalDeviceManager).start();
         verify(mHalDeviceManager).stop();
-        verify(mHalDeviceManager).createStaIface(any(), eq(null));
+        verify(mHalDeviceManager).createStaIface(any(), eq(null), any());
         verify(mHalDeviceManager).getChip(eq(mIWifiStaIface));
         verify(mHalDeviceManager, times(2)).isReady();
         verify(mHalDeviceManager, times(2)).isStarted();
 
-        verify(mHalDeviceManager, never()).createApIface(any(), eq(null));
+        verify(mHalDeviceManager, never()).createApIface(
+                anyLong(), any(), eq(null), any(), anyBoolean());
     }
 
     /**
@@ -643,12 +658,13 @@
 
         verify(mHalDeviceManager).start();
         verify(mHalDeviceManager).stop();
-        verify(mHalDeviceManager).createApIface(any(), eq(null));
+        verify(mHalDeviceManager).createApIface(
+                anyLong(), any(), eq(null), any(), anyBoolean());
         verify(mHalDeviceManager).getChip(eq(mIWifiApIface));
         verify(mHalDeviceManager, times(2)).isReady();
         verify(mHalDeviceManager, times(2)).isStarted();
 
-        verify(mHalDeviceManager, never()).createStaIface(any(), eq(null));
+        verify(mHalDeviceManager, never()).createStaIface(any(), eq(null), any());
     }
 
     /**
@@ -661,12 +677,12 @@
         InterfaceDestroyedListener externalLister = mock(InterfaceDestroyedListener.class);
 
         assertTrue(mWifiVendorHal.startVendorHal());
-        assertNotNull(mWifiVendorHal.createStaIface(externalLister));
+        assertNotNull(mWifiVendorHal.createStaIface(externalLister, TEST_WORKSOURCE));
         assertTrue(mWifiVendorHal.isHalStarted());
 
         verify(mHalDeviceManager).start();
         verify(mHalDeviceManager).createStaIface(internalListenerCaptor.capture(),
-                eq(null));
+                eq(null), eq(TEST_WORKSOURCE));
         verify(mHalDeviceManager).getChip(eq(mIWifiStaIface));
         verify(mHalDeviceManager).isReady();
         verify(mHalDeviceManager).isStarted();
@@ -693,11 +709,13 @@
         InterfaceDestroyedListener externalLister = mock(InterfaceDestroyedListener.class);
 
         assertTrue(mWifiVendorHal.startVendorHal());
-        assertNotNull(mWifiVendorHal.createApIface(externalLister));
+        assertNotNull(mWifiVendorHal.createApIface(
+                externalLister, TEST_WORKSOURCE, SoftApConfiguration.BAND_2GHZ, false));
         assertTrue(mWifiVendorHal.isHalStarted());
 
         verify(mHalDeviceManager).start();
-        verify(mHalDeviceManager).createApIface(internalListenerCaptor.capture(), eq(null));
+        verify(mHalDeviceManager).createApIface(anyLong(),
+                internalListenerCaptor.capture(), eq(null), eq(TEST_WORKSOURCE), eq(false));
         verify(mHalDeviceManager).getChip(eq(mIWifiApIface));
         verify(mHalDeviceManager).isReady();
         verify(mHalDeviceManager).isStarted();
@@ -845,6 +863,26 @@
         assertEquals(expected, mWifiVendorHal.wifiFeatureMaskFromChipCapabilities_1_3(caps));
     }
 
+    private void testGetSupportedFeaturesCommon(
+            int staIfaceHidlCaps, int chipHidlCaps, long expectedFeatureSet) throws Exception {
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+
+        Set<Integer> halDeviceManagerSupportedIfaces = new HashSet<Integer>() {{
+                add(IfaceType.STA);
+                add(IfaceType.P2P);
+            }};
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(IWifiStaIface.getCapabilitiesCallback cb) throws RemoteException {
+                cb.onValues(mWifiStatusSuccess, staIfaceHidlCaps);
+            }
+        }).when(mIWifiStaIface).getCapabilities(any(IWifiStaIface.getCapabilitiesCallback.class));
+
+        when(mHalDeviceManager.getSupportedIfaceTypes())
+                .thenReturn(halDeviceManagerSupportedIfaces);
+
+        assertEquals(expectedFeatureSet, mWifiVendorHal.getSupportedFeatureSet(TEST_IFACE_NAME));
+    }
     /**
      * Test get supported features. Tests whether we coalesce information from different sources
      * (IWifiStaIface, IWifiChip and HalDeviceManager) into the bitmask of supported features
@@ -852,6 +890,40 @@
      */
     @Test
     public void testGetSupportedFeatures() throws Exception {
+        int staIfaceHidlCaps = (
+                IWifiStaIface.StaIfaceCapabilityMask.BACKGROUND_SCAN
+                        | IWifiStaIface.StaIfaceCapabilityMask.LINK_LAYER_STATS
+        );
+        int chipHidlCaps =
+                android.hardware.wifi.V1_1.IWifiChip.ChipCapabilityMask.SET_TX_POWER_LIMIT;
+        when(mWifiGlobals.isWpa3SaeH2eSupported()).thenReturn(true);
+        long expectedFeatureSet = (
+                WifiManager.WIFI_FEATURE_SCANNER
+                        | WifiManager.WIFI_FEATURE_LINK_LAYER_STATS
+                        | WifiManager.WIFI_FEATURE_TX_POWER_LIMIT
+                        | WifiManager.WIFI_FEATURE_INFRA
+                        | WifiManager.WIFI_FEATURE_P2P
+                        | WifiManager.WIFI_FEATURE_SAE_H2E
+        );
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(IWifiChip.getCapabilitiesCallback cb) throws RemoteException {
+                cb.onValues(mWifiStatusSuccess, chipHidlCaps);
+            }
+        }).when(mIWifiChip).getCapabilities(any(IWifiChip.getCapabilitiesCallback.class));
+
+        mWifiVendorHal = new WifiVendorHalSpyV1_1(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
+        testGetSupportedFeaturesCommon(staIfaceHidlCaps, chipHidlCaps, expectedFeatureSet);
+    }
+
+    /**
+     * Test get supported features. Tests whether we coalesce information from different sources
+     * (IWifiStaIface, IWifiChip and HalDeviceManager) into the bitmask of supported features
+     * correctly.
+     */
+    @Test
+    public void testGetSupportedFeaturesForHalV1_3() throws Exception {
         assertTrue(mWifiVendorHal.startVendorHalSta());
 
         int staIfaceHidlCaps = (
@@ -859,41 +931,106 @@
                         | IWifiStaIface.StaIfaceCapabilityMask.LINK_LAYER_STATS
         );
         int chipHidlCaps =
-                android.hardware.wifi.V1_1.IWifiChip.ChipCapabilityMask.SET_TX_POWER_LIMIT;
-        Set<Integer>  halDeviceManagerSupportedIfaces = new HashSet<Integer>() {{
-                add(IfaceType.STA);
-                add(IfaceType.P2P);
-            }};
+                android.hardware.wifi.V1_3.IWifiChip.ChipCapabilityMask.SET_LATENCY_MODE
+                        | android.hardware.wifi.V1_3.IWifiChip.ChipCapabilityMask.P2P_RAND_MAC;
         long expectedFeatureSet = (
                 WifiManager.WIFI_FEATURE_SCANNER
                         | WifiManager.WIFI_FEATURE_LINK_LAYER_STATS
-                        | WifiManager.WIFI_FEATURE_TX_POWER_LIMIT
+                        | WifiManager.WIFI_FEATURE_LOW_LATENCY
+                        | WifiManager.WIFI_FEATURE_P2P_RAND_MAC
                         | WifiManager.WIFI_FEATURE_INFRA
                         | WifiManager.WIFI_FEATURE_P2P
         );
 
         doAnswer(new AnswerWithArguments() {
-            public void answer(IWifiStaIface.getCapabilitiesCallback cb) throws RemoteException {
-                cb.onValues(mWifiStatusSuccess, staIfaceHidlCaps);
-            }
-        }).when(mIWifiStaIface).getCapabilities(any(IWifiStaIface.getCapabilitiesCallback.class));
-        doAnswer(new AnswerWithArguments() {
-            public void answer(IWifiChip.getCapabilitiesCallback cb) throws RemoteException {
+            public void answer(
+                    android.hardware.wifi.V1_3.IWifiChip.getCapabilities_1_3Callback cb)
+                    throws RemoteException {
                 cb.onValues(mWifiStatusSuccess, chipHidlCaps);
             }
-        }).when(mIWifiChip).getCapabilities(any(IWifiChip.getCapabilitiesCallback.class));
-        when(mHalDeviceManager.getSupportedIfaceTypes())
-                .thenReturn(halDeviceManagerSupportedIfaces);
+        }).when(mIWifiChipV13).getCapabilities_1_3(
+                any(android.hardware.wifi.V1_3.IWifiChip.getCapabilities_1_3Callback.class));
 
+        mWifiVendorHal = new WifiVendorHalSpyV1_3(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
+        testGetSupportedFeaturesCommon(staIfaceHidlCaps, chipHidlCaps, expectedFeatureSet);
+    }
+
+    /**
+     * Test get supported features. Tests whether we coalesce information from different sources
+     * (IWifiStaIface, IWifiChip and HalDeviceManager) into the bitmask of supported features
+     * correctly.
+     */
+    @Test
+    public void testGetSupportedFeaturesForHalV1_5() throws Exception {
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+
+        int staIfaceHidlCaps = (
+                IWifiStaIface.StaIfaceCapabilityMask.BACKGROUND_SCAN
+                        | IWifiStaIface.StaIfaceCapabilityMask.LINK_LAYER_STATS
+        );
+        int chipHidlCaps = android.hardware.wifi.V1_5.IWifiChip.ChipCapabilityMask.WIGIG;
+        long expectedFeatureSet = (
+                WifiManager.WIFI_FEATURE_SCANNER
+                        | WifiManager.WIFI_FEATURE_LINK_LAYER_STATS
+                        | WifiManager.WIFI_FEATURE_INFRA_60G
+                        | WifiManager.WIFI_FEATURE_INFRA
+                        | WifiManager.WIFI_FEATURE_P2P
+        );
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(
+                    android.hardware.wifi.V1_5.IWifiChip.getCapabilities_1_5Callback cb)
+                    throws RemoteException {
+                cb.onValues(mWifiStatusSuccess, chipHidlCaps);
+            }
+        }).when(mIWifiChipV15).getCapabilities_1_5(
+                any(android.hardware.wifi.V1_5.IWifiChip.getCapabilities_1_5Callback.class));
+
+        mWifiVendorHal = new WifiVendorHalSpyV1_5(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
+        testGetSupportedFeaturesCommon(staIfaceHidlCaps, chipHidlCaps, expectedFeatureSet);
+    }
+
+    /**
+     * Test get supported features. Tests whether we coalesce information from package manager
+     * if vendor hal is not supported.
+     */
+    @Test
+    public void testGetSupportedFeaturesFromPackageManager() throws Exception {
+        doAnswer(new AnswerWithArguments() {
+            public boolean answer() throws Exception {
+                return false;
+            }
+        }).when(mHalDeviceManager).start();
+        when(mHalDeviceManager.isSupported()).thenReturn(false);
+        assertFalse(mWifiVendorHal.startVendorHalSta());
+
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mPackageManager.hasSystemFeature(eq(PackageManager.FEATURE_WIFI)))
+                .thenReturn(true);
+        when(mPackageManager.hasSystemFeature(eq(PackageManager.FEATURE_WIFI_DIRECT)))
+                .thenReturn(true);
+        when(mPackageManager.hasSystemFeature(eq(PackageManager.FEATURE_WIFI_AWARE)))
+                .thenReturn(true);
+
+        long expectedFeatureSet = (
+                WifiManager.WIFI_FEATURE_INFRA
+                        | WifiManager.WIFI_FEATURE_P2P
+                        | WifiManager.WIFI_FEATURE_AWARE
+        );
+
+        mWifiVendorHal = new WifiVendorHalSpyV1_5(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
         assertEquals(expectedFeatureSet, mWifiVendorHal.getSupportedFeatureSet(TEST_IFACE_NAME));
     }
 
-   /**
+    /**
      * Test |getFactoryMacAddress| gets called when the hal version is V1_3
      * @throws Exception
      */
     @Test
-    public void testGetFactoryMacWithHalV1_3() throws Exception {
+    public void testGetStaFactoryMacWithHalV1_3() throws Exception {
         doAnswer(new AnswerWithArguments() {
             public void answer(
                     android.hardware.wifi.V1_3.IWifiStaIface.getFactoryMacAddressCallback cb)
@@ -902,9 +1039,10 @@
             }
         }).when(mIWifiStaIfaceV13).getFactoryMacAddress(any(
                 android.hardware.wifi.V1_3.IWifiStaIface.getFactoryMacAddressCallback.class));
-        mWifiVendorHal = new WifiVendorHalSpyV1_3(mHalDeviceManager, mHandler);
+        mWifiVendorHal = new WifiVendorHalSpyV1_3(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
         assertEquals(MacAddress.BROADCAST_ADDRESS.toString(),
-                mWifiVendorHal.getFactoryMacAddress(TEST_IFACE_NAME).toString());
+                mWifiVendorHal.getStaFactoryMacAddress(TEST_IFACE_NAME).toString());
         verify(mIWifiStaIfaceV13).getFactoryMacAddress(any());
     }
 
@@ -938,12 +1076,24 @@
      */
     @Test
     public void testLinkLayerStatsCorrectVersionWithHalV1_3() throws Exception {
-        mWifiVendorHal = new WifiVendorHalSpyV1_3(mHalDeviceManager, mHandler);
+        mWifiVendorHal = new WifiVendorHalSpyV1_3(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
         mWifiVendorHal.getWifiLinkLayerStats(TEST_IFACE_NAME);
         verify(mIWifiStaIfaceV13).getLinkLayerStats_1_3(any());
     }
 
     /**
+     * Test getLinkLayerStats_1_5 gets called when the hal version is V1_5.
+     */
+    @Test
+    public void testLinkLayerStatsCorrectVersionWithHalV1_5() throws Exception {
+        mWifiVendorHal = new WifiVendorHalSpyV1_5(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
+        mWifiVendorHal.getWifiLinkLayerStats(TEST_IFACE_NAME);
+        verify(mIWifiStaIfaceV15).getLinkLayerStats_1_5(any());
+    }
+
+    /**
      * Test that link layer stats are not enabled and harmless in AP mode
      *
      * Start the HAL in AP mode
@@ -1007,15 +1157,165 @@
         randomizePacketStats(r, stats.iface.wmeBkPktStats);
         randomizePacketStats(r, stats.iface.wmeViPktStats);
         randomizePacketStats(r, stats.iface.wmeVoPktStats);
-        randomizeRadioStats_1_3(r, stats.radios);
+        android.hardware.wifi.V1_3.StaLinkLayerRadioStats rstat =
+                new android.hardware.wifi.V1_3.StaLinkLayerRadioStats();
+        randomizeRadioStats_1_3(r, rstat);
+        stats.radios.add(rstat);
         stats.timeStampInMs = r.nextLong() & 0xFFFFFFFFFFL;
 
         WifiLinkLayerStats converted = WifiVendorHal.frameworkFromHalLinkLayerStats_1_3(stats);
 
         verifyIFaceStats(stats.iface, converted);
-        verifyRadioStats_1_3(stats.radios, converted);
+        verifyRadioStats_1_3(stats.radios.get(0), converted);
         assertEquals(stats.timeStampInMs, converted.timeStampInMs);
         assertEquals(WifiLinkLayerStats.V1_3, converted.version);
+        assertEquals(1, converted.numRadios);
+    }
+
+    /**
+     * Test that the link layer stats V1_5 fields are populated correctly.
+     *
+     * This is done by filling Hal LinkLayerStats (V1_5) with random values, converting it to
+     * WifiLinkLayerStats and then asserting the values in the original structure are equal to the
+     * values in the converted structure.
+     */
+    @Test
+    public void testLinkLayerStatsAssignment_1_5() throws Exception {
+        Random r = new Random(1775968256);
+        android.hardware.wifi.V1_5.StaLinkLayerStats stats =
+                new android.hardware.wifi.V1_5.StaLinkLayerStats();
+        randomizePacketStats(r, stats.iface.V1_0.wmeBePktStats);
+        randomizePacketStats(r, stats.iface.V1_0.wmeBkPktStats);
+        randomizePacketStats(r, stats.iface.V1_0.wmeViPktStats);
+        randomizePacketStats(r, stats.iface.V1_0.wmeVoPktStats);
+        android.hardware.wifi.V1_5.StaLinkLayerRadioStats rstat =
+                new android.hardware.wifi.V1_5.StaLinkLayerRadioStats();
+        randomizeRadioStats_1_5(r, rstat);
+        stats.radios.add(rstat);
+        stats.timeStampInMs = r.nextLong() & 0xFFFFFFFFFFL;
+        randomizeContentionTimeStats(r, stats.iface.wmeBeContentionTimeStats);
+        randomizeContentionTimeStats(r, stats.iface.wmeBkContentionTimeStats);
+        randomizeContentionTimeStats(r, stats.iface.wmeViContentionTimeStats);
+        randomizeContentionTimeStats(r, stats.iface.wmeVoContentionTimeStats);
+        randomizePeerInfoStats(r, stats.iface.peers);
+
+        WifiLinkLayerStats converted = WifiVendorHal.frameworkFromHalLinkLayerStats_1_5(stats);
+
+        verifyIFaceStats(stats.iface.V1_0, converted);
+        verifyIFaceStats_1_5(stats.iface, converted);
+        verifyPerRadioStats(stats.radios, converted);
+        verifyRadioStats_1_5(stats.radios.get(0), converted);
+        assertEquals(stats.timeStampInMs, converted.timeStampInMs);
+        assertEquals(WifiLinkLayerStats.V1_5, converted.version);
+        assertEquals(1, converted.numRadios);
+    }
+
+    /**
+     * Test that the link layer stats V1_3 fields are aggregated correctly for two radios.
+     *
+     * This is done by filling multiple Hal LinkLayerStats (V1_3) with random values,
+     * converting it to WifiLinkLayerStats and then asserting the sum of values from HAL structure
+     * are equal to the values in the converted structure.
+     */
+    @Test
+    public void testTwoRadioStatsAggregation_1_3() throws Exception {
+        when(mResources.getBoolean(R.bool.config_wifiLinkLayerAllRadiosStatsAggregationEnabled))
+                .thenReturn(true);
+        Random r = new Random(245786856);
+        android.hardware.wifi.V1_3.StaLinkLayerStats stats =
+                new android.hardware.wifi.V1_3.StaLinkLayerStats();
+        // Fill stats in two radios
+        for (int i = 0; i < 2; i++) {
+            android.hardware.wifi.V1_3.StaLinkLayerRadioStats rstat =
+                    new android.hardware.wifi.V1_3.StaLinkLayerRadioStats();
+            randomizeRadioStats_1_3(r, rstat);
+            stats.radios.add(rstat);
+        }
+
+        WifiLinkLayerStats converted = WifiVendorHal.frameworkFromHalLinkLayerStats_1_3(stats);
+        verifyTwoRadioStatsAggregation_1_3(stats.radios, converted);
+        assertEquals(2, converted.numRadios);
+    }
+
+    /**
+     * Test that the link layer stats V1_3 fields are not aggregated on setting
+     * config_wifiLinkLayerAllRadiosStatsAggregationEnabled to false(Default value).
+     *
+     * This is done by filling multiple Hal LinkLayerStats (V1_3) with random values,
+     * converting it to WifiLinkLayerStats and then asserting the values from radio 0
+     * are equal to the values in the converted structure.
+     */
+    @Test
+    public void testRadioStatsAggregationDisabled_1_3() throws Exception {
+        Random r = new Random(245786856);
+        android.hardware.wifi.V1_3.StaLinkLayerStats stats =
+                new android.hardware.wifi.V1_3.StaLinkLayerStats();
+        // Fill stats in two radios
+        for (int i = 0; i < 2; i++) {
+            android.hardware.wifi.V1_3.StaLinkLayerRadioStats rstat =
+                    new android.hardware.wifi.V1_3.StaLinkLayerRadioStats();
+            randomizeRadioStats_1_3(r, rstat);
+            stats.radios.add(rstat);
+        }
+
+        WifiLinkLayerStats converted = WifiVendorHal.frameworkFromHalLinkLayerStats_1_3(stats);
+        verifyRadioStats_1_3(stats.radios.get(0), converted);
+        assertEquals(1, converted.numRadios);
+    }
+
+    /**
+     * Test that the link layer stats V1_5 fields are aggregated correctly for two radios.
+     *
+     * This is done by filling multiple Hal LinkLayerStats (V1_5) with random values,
+     * converting it to WifiLinkLayerStats and then asserting the sum of values from HAL structure
+     * are equal to the values in the converted structure.
+     */
+    @Test
+    public void testTwoRadioStatsAggregation_1_5() throws Exception {
+        when(mResources.getBoolean(R.bool.config_wifiLinkLayerAllRadiosStatsAggregationEnabled))
+                .thenReturn(true);
+        Random r = new Random(245786856);
+        android.hardware.wifi.V1_5.StaLinkLayerStats stats =
+                new android.hardware.wifi.V1_5.StaLinkLayerStats();
+        // Fill stats in two radios
+        for (int i = 0; i < 2; i++) {
+            android.hardware.wifi.V1_5.StaLinkLayerRadioStats rstat =
+                    new android.hardware.wifi.V1_5.StaLinkLayerRadioStats();
+            randomizeRadioStats_1_5(r, rstat);
+            stats.radios.add(rstat);
+        }
+
+        WifiLinkLayerStats converted = WifiVendorHal.frameworkFromHalLinkLayerStats_1_5(stats);
+        verifyPerRadioStats(stats.radios, converted);
+        verifyTwoRadioStatsAggregation_1_5(stats.radios, converted);
+        assertEquals(2, converted.numRadios);
+    }
+
+    /**
+     * Test that the link layer stats V1_5 fields are not aggregated on setting
+     * config_wifiLinkLayerAllRadiosStatsAggregationEnabled to false(Default value).
+     *
+     * This is done by filling multiple Hal LinkLayerStats (V1_5) with random values,
+     * converting it to WifiLinkLayerStats and then asserting the values from radio 0
+     * are equal to the values in the converted structure.
+     */
+    @Test
+    public void testRadioStatsAggregationDisabled_1_5() throws Exception {
+        Random r = new Random(245786856);
+        android.hardware.wifi.V1_5.StaLinkLayerStats stats =
+                new android.hardware.wifi.V1_5.StaLinkLayerStats();
+        // Fill stats in two radios
+        for (int i = 0; i < 2; i++) {
+            android.hardware.wifi.V1_5.StaLinkLayerRadioStats rstat =
+                    new android.hardware.wifi.V1_5.StaLinkLayerRadioStats();
+            randomizeRadioStats_1_5(r, rstat);
+            stats.radios.add(rstat);
+        }
+
+        WifiLinkLayerStats converted = WifiVendorHal.frameworkFromHalLinkLayerStats_1_5(stats);
+        verifyPerRadioStats(stats.radios, converted);
+        verifyRadioStats_1_5(stats.radios.get(0), converted);
+        assertEquals(1, converted.numRadios);
     }
 
     private void verifyIFaceStats(StaLinkLayerIfaceStats iface,
@@ -1044,6 +1344,70 @@
         assertEquals(iface.wmeVoPktStats.retries, wifiLinkLayerStats.retries_vo);
     }
 
+    private void verifyIFaceStats_1_5(android.hardware.wifi.V1_5.StaLinkLayerIfaceStats iface,
+            WifiLinkLayerStats wifiLinkLayerStats) {
+        assertEquals(iface.wmeBeContentionTimeStats.contentionTimeMinInUsec,
+                wifiLinkLayerStats.contentionTimeMinBeInUsec);
+        assertEquals(iface.wmeBeContentionTimeStats.contentionTimeMaxInUsec,
+                wifiLinkLayerStats.contentionTimeMaxBeInUsec);
+        assertEquals(iface.wmeBeContentionTimeStats.contentionTimeAvgInUsec,
+                wifiLinkLayerStats.contentionTimeAvgBeInUsec);
+        assertEquals(iface.wmeBeContentionTimeStats.contentionNumSamples,
+                wifiLinkLayerStats.contentionNumSamplesBe);
+
+        assertEquals(iface.wmeBkContentionTimeStats.contentionTimeMinInUsec,
+                wifiLinkLayerStats.contentionTimeMinBkInUsec);
+        assertEquals(iface.wmeBkContentionTimeStats.contentionTimeMaxInUsec,
+                wifiLinkLayerStats.contentionTimeMaxBkInUsec);
+        assertEquals(iface.wmeBkContentionTimeStats.contentionTimeAvgInUsec,
+                wifiLinkLayerStats.contentionTimeAvgBkInUsec);
+        assertEquals(iface.wmeBkContentionTimeStats.contentionNumSamples,
+                wifiLinkLayerStats.contentionNumSamplesBk);
+
+        assertEquals(iface.wmeViContentionTimeStats.contentionTimeMinInUsec,
+                wifiLinkLayerStats.contentionTimeMinViInUsec);
+        assertEquals(iface.wmeViContentionTimeStats.contentionTimeMaxInUsec,
+                wifiLinkLayerStats.contentionTimeMaxViInUsec);
+        assertEquals(iface.wmeViContentionTimeStats.contentionTimeAvgInUsec,
+                wifiLinkLayerStats.contentionTimeAvgViInUsec);
+        assertEquals(iface.wmeViContentionTimeStats.contentionNumSamples,
+                wifiLinkLayerStats.contentionNumSamplesVi);
+
+        assertEquals(iface.wmeVoContentionTimeStats.contentionTimeMinInUsec,
+                wifiLinkLayerStats.contentionTimeMinVoInUsec);
+        assertEquals(iface.wmeVoContentionTimeStats.contentionTimeMaxInUsec,
+                wifiLinkLayerStats.contentionTimeMaxVoInUsec);
+        assertEquals(iface.wmeVoContentionTimeStats.contentionTimeAvgInUsec,
+                wifiLinkLayerStats.contentionTimeAvgVoInUsec);
+        assertEquals(iface.wmeVoContentionTimeStats.contentionNumSamples,
+                wifiLinkLayerStats.contentionNumSamplesVo);
+
+        for (int i = 0; i < iface.peers.size(); i++) {
+            assertEquals(iface.peers.get(i).staCount, wifiLinkLayerStats.peerInfo[i].staCount);
+            assertEquals(iface.peers.get(i).chanUtil, wifiLinkLayerStats.peerInfo[i].chanUtil);
+            for (int j = 0; j < iface.peers.get(i).rateStats.size(); j++) {
+                assertEquals(iface.peers.get(i).rateStats.get(j).rateInfo.preamble,
+                        wifiLinkLayerStats.peerInfo[i].rateStats[j].preamble);
+                assertEquals(iface.peers.get(i).rateStats.get(j).rateInfo.nss,
+                        wifiLinkLayerStats.peerInfo[i].rateStats[j].nss);
+                assertEquals(iface.peers.get(i).rateStats.get(j).rateInfo.bw,
+                        wifiLinkLayerStats.peerInfo[i].rateStats[j].bw);
+                assertEquals(iface.peers.get(i).rateStats.get(j).rateInfo.rateMcsIdx,
+                        wifiLinkLayerStats.peerInfo[i].rateStats[j].rateMcsIdx);
+                assertEquals(iface.peers.get(i).rateStats.get(j).rateInfo.bitRateInKbps,
+                        wifiLinkLayerStats.peerInfo[i].rateStats[j].bitRateInKbps);
+                assertEquals(iface.peers.get(i).rateStats.get(j).txMpdu,
+                        wifiLinkLayerStats.peerInfo[i].rateStats[j].txMpdu);
+                assertEquals(iface.peers.get(i).rateStats.get(j).rxMpdu,
+                        wifiLinkLayerStats.peerInfo[i].rateStats[j].rxMpdu);
+                assertEquals(iface.peers.get(i).rateStats.get(j).mpduLost,
+                        wifiLinkLayerStats.peerInfo[i].rateStats[j].mpduLost);
+                assertEquals(iface.peers.get(i).rateStats.get(j).retries,
+                        wifiLinkLayerStats.peerInfo[i].rateStats[j].retries);
+            }
+        }
+    }
+
     private void verifyRadioStats(List<StaLinkLayerRadioStats> radios,
             WifiLinkLayerStats wifiLinkLayerStats) {
         StaLinkLayerRadioStats radio = radios.get(0);
@@ -1060,9 +1424,8 @@
     }
 
     private void verifyRadioStats_1_3(
-            List<android.hardware.wifi.V1_3.StaLinkLayerRadioStats> radios,
+            android.hardware.wifi.V1_3.StaLinkLayerRadioStats radio,
             WifiLinkLayerStats wifiLinkLayerStats) {
-        android.hardware.wifi.V1_3.StaLinkLayerRadioStats radio = radios.get(0);
         assertEquals(radio.V1_0.onTimeInMs, wifiLinkLayerStats.on_time);
         assertEquals(radio.V1_0.txTimeInMs, wifiLinkLayerStats.tx_time);
         assertEquals(radio.V1_0.rxTimeInMs, wifiLinkLayerStats.rx_time);
@@ -1091,6 +1454,111 @@
         }
     }
 
+    private void verifyPerRadioStats(List<android.hardware.wifi.V1_5.StaLinkLayerRadioStats> radios,
+            WifiLinkLayerStats wifiLinkLayerStats) {
+        assertEquals(radios.size(),
+                wifiLinkLayerStats.radioStats.length);
+        for (int i = 0; i < radios.size(); i++) {
+            android.hardware.wifi.V1_5.StaLinkLayerRadioStats radio = radios.get(i);
+            RadioStat radioStat = wifiLinkLayerStats.radioStats[i];
+            assertEquals(radio.radioId, radioStat.radio_id);
+            assertEquals(radio.V1_3.V1_0.onTimeInMs, radioStat.on_time);
+            assertEquals(radio.V1_3.V1_0.txTimeInMs, radioStat.tx_time);
+            assertEquals(radio.V1_3.V1_0.rxTimeInMs, radioStat.rx_time);
+            assertEquals(radio.V1_3.V1_0.onTimeInMsForScan, radioStat.on_time_scan);
+            assertEquals(radio.V1_3.onTimeInMsForNanScan, radioStat.on_time_nan_scan);
+            assertEquals(radio.V1_3.onTimeInMsForBgScan, radioStat.on_time_background_scan);
+            assertEquals(radio.V1_3.onTimeInMsForRoamScan, radioStat.on_time_roam_scan);
+            assertEquals(radio.V1_3.onTimeInMsForPnoScan, radioStat.on_time_pno_scan);
+            assertEquals(radio.V1_3.onTimeInMsForHs20Scan, radioStat.on_time_hs20_scan);
+
+            assertEquals(radio.V1_3.channelStats.size(),
+                    radioStat.channelStatsMap.size());
+            for (int j = 0; j < radio.V1_3.channelStats.size(); j++) {
+                WifiChannelStats channelStats = radio.V1_3.channelStats.get(j);
+                ChannelStats retrievedChannelStats =
+                        radioStat.channelStatsMap.get(channelStats.channel.centerFreq);
+                assertNotNull(retrievedChannelStats);
+                assertEquals(channelStats.channel.centerFreq, retrievedChannelStats.frequency);
+                assertEquals(channelStats.onTimeInMs, retrievedChannelStats.radioOnTimeMs);
+                assertEquals(channelStats.ccaBusyTimeInMs, retrievedChannelStats.ccaBusyTimeMs);
+            }
+        }
+
+    }
+
+    private void verifyRadioStats_1_5(
+            android.hardware.wifi.V1_5.StaLinkLayerRadioStats radio,
+            WifiLinkLayerStats wifiLinkLayerStats) {
+        verifyRadioStats_1_3(radio.V1_3, wifiLinkLayerStats);
+    }
+
+    private void verifyTwoRadioStatsAggregation(
+            android.hardware.wifi.V1_3.StaLinkLayerRadioStats radio0,
+            android.hardware.wifi.V1_3.StaLinkLayerRadioStats radio1,
+            WifiLinkLayerStats wifiLinkLayerStats) {
+        assertEquals(radio0.V1_0.onTimeInMs + radio1.V1_0.onTimeInMs,
+                wifiLinkLayerStats.on_time);
+        assertEquals(radio0.V1_0.txTimeInMs + radio1.V1_0.txTimeInMs,
+                wifiLinkLayerStats.tx_time);
+        assertEquals(radio0.V1_0.rxTimeInMs + radio1.V1_0.rxTimeInMs,
+                wifiLinkLayerStats.rx_time);
+        assertEquals(radio0.V1_0.onTimeInMsForScan + radio1.V1_0.onTimeInMsForScan,
+                wifiLinkLayerStats.on_time_scan);
+        assertEquals(radio0.V1_0.txTimeInMsPerLevel.size(),
+                radio1.V1_0.txTimeInMsPerLevel.size());
+        assertEquals(radio0.V1_0.txTimeInMsPerLevel.size(),
+                wifiLinkLayerStats.tx_time_per_level.length);
+        for (int i = 0; i < radio0.V1_0.txTimeInMsPerLevel.size(); i++) {
+            assertEquals((int) radio0.V1_0.txTimeInMsPerLevel.get(i)
+                    + (int) radio1.V1_0.txTimeInMsPerLevel.get(i),
+                    wifiLinkLayerStats.tx_time_per_level[i]);
+        }
+        assertEquals(radio0.onTimeInMsForNanScan + radio1.onTimeInMsForNanScan,
+                wifiLinkLayerStats.on_time_nan_scan);
+        assertEquals(radio0.onTimeInMsForBgScan + radio1.onTimeInMsForBgScan,
+                wifiLinkLayerStats.on_time_background_scan);
+        assertEquals(radio0.onTimeInMsForRoamScan + radio1.onTimeInMsForRoamScan,
+                wifiLinkLayerStats.on_time_roam_scan);
+        assertEquals(radio0.onTimeInMsForPnoScan + radio1.onTimeInMsForPnoScan,
+                wifiLinkLayerStats.on_time_pno_scan);
+        assertEquals(radio0.onTimeInMsForHs20Scan + radio1.onTimeInMsForHs20Scan,
+                wifiLinkLayerStats.on_time_hs20_scan);
+        assertEquals(radio0.channelStats.size(), radio1.channelStats.size());
+        assertEquals(radio0.channelStats.size(),
+                wifiLinkLayerStats.channelStatsMap.size());
+        for (int j = 0; j < radio0.channelStats.size(); j++) {
+            WifiChannelStats radio0ChannelStats = radio0.channelStats.get(j);
+            WifiChannelStats radio1ChannelStats = radio1.channelStats.get(j);
+            ChannelStats retrievedChannelStats =
+                    wifiLinkLayerStats.channelStatsMap.get(radio0ChannelStats.channel.centerFreq);
+            assertNotNull(retrievedChannelStats);
+            assertEquals(radio0ChannelStats.channel.centerFreq, retrievedChannelStats.frequency);
+            assertEquals(radio1ChannelStats.channel.centerFreq, retrievedChannelStats.frequency);
+            assertEquals(radio0ChannelStats.onTimeInMs + radio1ChannelStats.onTimeInMs,
+                    retrievedChannelStats.radioOnTimeMs);
+            assertEquals(radio0ChannelStats.ccaBusyTimeInMs
+                    + radio1ChannelStats.ccaBusyTimeInMs, retrievedChannelStats.ccaBusyTimeMs);
+        }
+    }
+
+    private void verifyTwoRadioStatsAggregation_1_3(
+            List<android.hardware.wifi.V1_3.StaLinkLayerRadioStats> radios,
+            WifiLinkLayerStats wifiLinkLayerStats) {
+        assertEquals(2, radios.size());
+        android.hardware.wifi.V1_3.StaLinkLayerRadioStats radio0 = radios.get(0);
+        android.hardware.wifi.V1_3.StaLinkLayerRadioStats radio1 = radios.get(1);
+        verifyTwoRadioStatsAggregation(radio0, radio1, wifiLinkLayerStats);
+    }
+
+    private void verifyTwoRadioStatsAggregation_1_5(
+            List<android.hardware.wifi.V1_5.StaLinkLayerRadioStats> radios,
+            WifiLinkLayerStats wifiLinkLayerStats) {
+        assertEquals(2, radios.size());
+        android.hardware.wifi.V1_5.StaLinkLayerRadioStats radio0 = radios.get(0);
+        android.hardware.wifi.V1_5.StaLinkLayerRadioStats radio1 = radios.get(1);
+        verifyTwoRadioStatsAggregation(radio0.V1_3, radio1.V1_3, wifiLinkLayerStats);
+    }
 
     /**
      * Populate packet stats with non-negative random values
@@ -1103,6 +1571,39 @@
     }
 
     /**
+     * Populate contention time stats with non-negative random values
+     */
+    private static void randomizeContentionTimeStats(Random r,
+            StaLinkLayerIfaceContentionTimeStats cstats) {
+        cstats.contentionTimeMinInUsec = r.nextInt() & 0x7FFFFFFF;
+        cstats.contentionTimeMaxInUsec = r.nextInt() & 0x7FFFFFFF;
+        cstats.contentionTimeAvgInUsec = r.nextInt() & 0x7FFFFFFF;
+        cstats.contentionNumSamples = r.nextInt() & 0x7FFFFFFF;
+    }
+
+    /**
+     * Populate peer info stats with non-negative random values
+     */
+    private static void randomizePeerInfoStats(Random r, ArrayList<StaPeerInfo> pstats) {
+        StaPeerInfo pstat = new StaPeerInfo();
+        pstat.staCount = 2;
+        pstat.chanUtil = 90;
+        pstat.rateStats = new ArrayList<StaRateStat>();
+        StaRateStat rateStat = new StaRateStat();
+        rateStat.rateInfo.preamble = r.nextInt() & 0x7FFFFFFF;
+        rateStat.rateInfo.nss = r.nextInt() & 0x7FFFFFFF;
+        rateStat.rateInfo.bw = r.nextInt() & 0x7FFFFFFF;
+        rateStat.rateInfo.rateMcsIdx = 9;
+        rateStat.rateInfo.bitRateInKbps = 101;
+        rateStat.txMpdu = r.nextInt() & 0x7FFFFFFF;
+        rateStat.rxMpdu = r.nextInt() & 0x7FFFFFFF;
+        rateStat.mpduLost = r.nextInt() & 0x7FFFFFFF;
+        rateStat.retries = r.nextInt() & 0x7FFFFFFF;
+        pstat.rateStats.add(rateStat);
+        pstats.add(pstat);
+    }
+
+    /**
      * Populate radio stats with non-negative random values
      */
     private static void randomizeRadioStats(Random r, ArrayList<StaLinkLayerRadioStats> rstats) {
@@ -1119,15 +1620,13 @@
     }
 
     /**
-     * Populate radio stats with non-negative random values
+     * Populate radio stats V1_3 with non-negative random values
      */
     private static void randomizeRadioStats_1_3(Random r,
-            ArrayList<android.hardware.wifi.V1_3.StaLinkLayerRadioStats> rstats) {
-        android.hardware.wifi.V1_3.StaLinkLayerRadioStats rstat =
-                new android.hardware.wifi.V1_3.StaLinkLayerRadioStats();
+            android.hardware.wifi.V1_3.StaLinkLayerRadioStats rstat) {
         rstat.V1_0.onTimeInMs = r.nextInt() & 0xFFFFFF;
         rstat.V1_0.txTimeInMs = r.nextInt() & 0xFFFFFF;
-        for (int i = 0; i < 4; i++) {
+        for (int j = 0; j < 4; j++) {
             Integer v = r.nextInt() & 0xFFFFFF;
             rstat.V1_0.txTimeInMsPerLevel.add(v);
         }
@@ -1138,14 +1637,22 @@
         rstat.onTimeInMsForRoamScan = r.nextInt() & 0xFFFFFF;
         rstat.onTimeInMsForPnoScan = r.nextInt() & 0xFFFFFF;
         rstat.onTimeInMsForHs20Scan = r.nextInt() & 0xFFFFFF;
-        for (int j = 0; j < TEST_FREQUENCIES.length; j++) {
+        for (int k = 0; k < TEST_FREQUENCIES.length; k++) {
             WifiChannelStats channelStats = new WifiChannelStats();
-            channelStats.channel.centerFreq = TEST_FREQUENCIES[j];
+            channelStats.channel.centerFreq = TEST_FREQUENCIES[k];
             channelStats.onTimeInMs = r.nextInt() & 0xFFFFFF;
             channelStats.ccaBusyTimeInMs = r.nextInt() & 0xFFFFFF;
             rstat.channelStats.add(channelStats);
         }
-        rstats.add(rstat);
+    }
+
+    /**
+     * Populate radio stats V1_5 with non-negative random values
+     */
+    private static void randomizeRadioStats_1_5(Random r,
+            android.hardware.wifi.V1_5.StaLinkLayerRadioStats rstat) {
+        rstat.radioId = r.nextInt() & 0xFFFFFF;
+        randomizeRadioStats_1_3(r, rstat.V1_3);
     }
 
     /**
@@ -1215,8 +1722,8 @@
     public void testStartSendingOffloadedPacket() throws Exception {
         byte[] srcMac = NativeUtil.macAddressToByteArray("4007b2088c81");
         byte[] dstMac = NativeUtil.macAddressToByteArray("4007b8675309");
-        InetAddress src = InetAddress.parseNumericAddress("192.168.13.13");
-        InetAddress dst = InetAddress.parseNumericAddress("93.184.216.34");
+        InetAddress src = InetAddresses.parseNumericAddress("192.168.13.13");
+        InetAddress dst = InetAddresses.parseNumericAddress("93.184.216.34");
         int slot = 13;
         int millis = 16000;
 
@@ -1354,7 +1861,8 @@
     @Test
     public void testReadApf() throws Exception {
         // Expose the 1.2 IWifiStaIface.
-        mWifiVendorHal = new WifiVendorHalSpyV1_2(mHalDeviceManager, mHandler);
+        mWifiVendorHal = new WifiVendorHalSpyV1_2(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
 
         byte[] program = new byte[] {65, 66, 67};
         ArrayList<Byte> expected = new ArrayList<>(3);
@@ -1377,7 +1885,7 @@
      * Test that the country code is set in AP mode (when it should be).
      */
     @Test
-    public void testSetCountryCodeHal() throws Exception {
+    public void testSetApCountryCode() throws Exception {
         byte[] expected = new byte[]{(byte) 'C', (byte) 'A'};
 
         when(mIWifiApIface.setCountryCode(any()))
@@ -1385,12 +1893,12 @@
 
         assertTrue(mWifiVendorHal.startVendorHalAp());
 
-        assertFalse(mWifiVendorHal.setCountryCodeHal(TEST_IFACE_NAME, null));
-        assertFalse(mWifiVendorHal.setCountryCodeHal(TEST_IFACE_NAME, ""));
-        assertFalse(mWifiVendorHal.setCountryCodeHal(TEST_IFACE_NAME, "A"));
+        assertFalse(mWifiVendorHal.setApCountryCode(TEST_IFACE_NAME, null));
+        assertFalse(mWifiVendorHal.setApCountryCode(TEST_IFACE_NAME, ""));
+        assertFalse(mWifiVendorHal.setApCountryCode(TEST_IFACE_NAME, "A"));
         // Only one expected to succeed
-        assertTrue(mWifiVendorHal.setCountryCodeHal(TEST_IFACE_NAME, "CA"));
-        assertFalse(mWifiVendorHal.setCountryCodeHal(TEST_IFACE_NAME, "ZZZ"));
+        assertTrue(mWifiVendorHal.setApCountryCode(TEST_IFACE_NAME, "CA"));
+        assertFalse(mWifiVendorHal.setApCountryCode(TEST_IFACE_NAME, "ZZZ"));
 
         verify(mIWifiApIface).setCountryCode(eq(expected));
     }
@@ -1405,8 +1913,8 @@
         when(mIWifiApIface.setCountryCode(any()))
                 .thenThrow(new RemoteException("oops"));
         assertTrue(mWifiVendorHal.startVendorHalAp());
-        assertFalse(mWifiVendorHal.setCountryCodeHal(TEST_IFACE_NAME, "CA"));
-        assertFalse(mWifiVendorHal.isHalStarted());
+        assertFalse(mWifiVendorHal.setApCountryCode(TEST_IFACE_NAME, "CA"));
+        assertTrue(mWifiVendorHal.isHalStarted());
         verify(mWifiLog).err("% RemoteException in HIDL call %");
     }
 
@@ -1519,7 +2027,8 @@
      */
     @Test
     public void testFlushRingBufferToFile() throws Exception {
-        mWifiVendorHal = new WifiVendorHalSpyV1_3(mHalDeviceManager, mHandler);
+        mWifiVendorHal = new WifiVendorHalSpyV1_3(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
         when(mIWifiChipV13.flushRingBufferToFile()).thenReturn(mWifiStatusSuccess);
 
         assertFalse(mWifiVendorHal.flushRingBufferData());
@@ -1564,32 +2073,31 @@
 
         doAnswer(new AnswerWithArguments() {
             public void answer(IWifiStaIface.getDebugTxPacketFatesCallback cb) {
-                cb.onValues(mWifiStatusSuccess,
-                        new ArrayList<WifiDebugTxPacketFateReport>(Arrays.asList(fateReport)));
+                cb.onValues(mWifiStatusSuccess, new ArrayList<>(Arrays.asList(fateReport)));
             }
         }).when(mIWifiStaIface)
                 .getDebugTxPacketFates(any(IWifiStaIface.getDebugTxPacketFatesCallback.class));
 
-        WifiNative.TxFateReport[] retrievedFates = new WifiNative.TxFateReport[1];
-        assertFalse(mWifiVendorHal.getTxPktFates(TEST_IFACE_NAME, retrievedFates));
+        assertEquals(0, mWifiVendorHal.getTxPktFates(TEST_IFACE_NAME).size());
         verify(mIWifiStaIface, never())
                 .getDebugTxPacketFates(any(IWifiStaIface.getDebugTxPacketFatesCallback.class));
 
         assertTrue(mWifiVendorHal.startVendorHalSta());
 
-        assertTrue(mWifiVendorHal.getTxPktFates(TEST_IFACE_NAME, retrievedFates));
+        List<TxFateReport> retrievedFates = mWifiVendorHal.getTxPktFates(TEST_IFACE_NAME);
+        assertEquals(1, retrievedFates.size());
+        TxFateReport retrievedFate = retrievedFates.get(0);
         verify(mIWifiStaIface)
                 .getDebugTxPacketFates(any(IWifiStaIface.getDebugTxPacketFatesCallback.class));
-        assertEquals(WifiLoggerHal.TX_PKT_FATE_DRV_QUEUED, retrievedFates[0].mFate);
-        assertEquals(fateReport.frameInfo.driverTimestampUsec,
-                retrievedFates[0].mDriverTimestampUSec);
-        assertEquals(WifiLoggerHal.FRAME_TYPE_ETHERNET_II, retrievedFates[0].mFrameType);
-        assertArrayEquals(frameContentBytes, retrievedFates[0].mFrameBytes);
+        assertEquals(WifiLoggerHal.TX_PKT_FATE_DRV_QUEUED, retrievedFate.mFate);
+        assertEquals(fateReport.frameInfo.driverTimestampUsec, retrievedFate.mDriverTimestampUSec);
+        assertEquals(WifiLoggerHal.FRAME_TYPE_ETHERNET_II, retrievedFate.mFrameType);
+        assertArrayEquals(frameContentBytes, retrievedFate.mFrameBytes);
     }
 
     /**
      * Tests the retrieval of tx packet fates when the number of fates retrieved exceeds the
-     * input array.
+     * maximum number of packet fates fetched ({@link WifiLoggerHal#MAX_FATE_LOG_LEN}).
      *
      * Try once before hal start, and once after.
      */
@@ -1606,28 +2114,29 @@
 
         doAnswer(new AnswerWithArguments() {
             public void answer(IWifiStaIface.getDebugTxPacketFatesCallback cb) {
-                cb.onValues(mWifiStatusSuccess,
-                        new ArrayList<WifiDebugTxPacketFateReport>(Arrays.asList(
-                                fateReport, fateReport)));
+                cb.onValues(mWifiStatusSuccess, new ArrayList<>(
+                        // create twice as many as the max size
+                        Collections.nCopies(WifiLoggerHal.MAX_FATE_LOG_LEN * 2, fateReport)));
             }
         }).when(mIWifiStaIface)
                 .getDebugTxPacketFates(any(IWifiStaIface.getDebugTxPacketFatesCallback.class));
 
-        WifiNative.TxFateReport[] retrievedFates = new WifiNative.TxFateReport[1];
-        assertFalse(mWifiVendorHal.getTxPktFates(TEST_IFACE_NAME, retrievedFates));
+        assertEquals(0, mWifiVendorHal.getTxPktFates(TEST_IFACE_NAME).size());
         verify(mIWifiStaIface, never())
                 .getDebugTxPacketFates(any(IWifiStaIface.getDebugTxPacketFatesCallback.class));
 
         assertTrue(mWifiVendorHal.startVendorHalSta());
 
-        assertTrue(mWifiVendorHal.getTxPktFates(TEST_IFACE_NAME, retrievedFates));
+        List<TxFateReport> retrievedFates = mWifiVendorHal.getTxPktFates(TEST_IFACE_NAME);
+        // assert that at most WifiLoggerHal.MAX_FATE_LOG_LEN is retrieved
+        assertEquals(WifiLoggerHal.MAX_FATE_LOG_LEN, retrievedFates.size());
+        TxFateReport retrievedFate = retrievedFates.get(0);
         verify(mIWifiStaIface)
                 .getDebugTxPacketFates(any(IWifiStaIface.getDebugTxPacketFatesCallback.class));
-        assertEquals(WifiLoggerHal.TX_PKT_FATE_FW_DROP_OTHER, retrievedFates[0].mFate);
-        assertEquals(fateReport.frameInfo.driverTimestampUsec,
-                retrievedFates[0].mDriverTimestampUSec);
-        assertEquals(WifiLoggerHal.FRAME_TYPE_80211_MGMT, retrievedFates[0].mFrameType);
-        assertArrayEquals(frameContentBytes, retrievedFates[0].mFrameBytes);
+        assertEquals(WifiLoggerHal.TX_PKT_FATE_FW_DROP_OTHER, retrievedFate.mFate);
+        assertEquals(fateReport.frameInfo.driverTimestampUsec, retrievedFate.mDriverTimestampUSec);
+        assertEquals(WifiLoggerHal.FRAME_TYPE_80211_MGMT, retrievedFate.mFrameType);
+        assertArrayEquals(frameContentBytes, retrievedFate.mFrameBytes);
     }
 
     /**
@@ -1648,27 +2157,26 @@
 
         doAnswer(new AnswerWithArguments() {
             public void answer(IWifiStaIface.getDebugRxPacketFatesCallback cb) {
-                cb.onValues(mWifiStatusSuccess,
-                        new ArrayList<WifiDebugRxPacketFateReport>(Arrays.asList(fateReport)));
+                cb.onValues(mWifiStatusSuccess, new ArrayList<>(Arrays.asList(fateReport)));
             }
         }).when(mIWifiStaIface)
                 .getDebugRxPacketFates(any(IWifiStaIface.getDebugRxPacketFatesCallback.class));
 
-        WifiNative.RxFateReport[] retrievedFates = new WifiNative.RxFateReport[1];
-        assertFalse(mWifiVendorHal.getRxPktFates(TEST_IFACE_NAME, retrievedFates));
+        assertEquals(0, mWifiVendorHal.getRxPktFates(TEST_IFACE_NAME).size());
         verify(mIWifiStaIface, never())
                 .getDebugRxPacketFates(any(IWifiStaIface.getDebugRxPacketFatesCallback.class));
 
         assertTrue(mWifiVendorHal.startVendorHalSta());
 
-        assertTrue(mWifiVendorHal.getRxPktFates(TEST_IFACE_NAME, retrievedFates));
+        List<RxFateReport> retrievedFates = mWifiVendorHal.getRxPktFates(TEST_IFACE_NAME);
+        assertEquals(1, retrievedFates.size());
+        RxFateReport retrievedFate = retrievedFates.get(0);
         verify(mIWifiStaIface)
                 .getDebugRxPacketFates(any(IWifiStaIface.getDebugRxPacketFatesCallback.class));
-        assertEquals(WifiLoggerHal.RX_PKT_FATE_SUCCESS, retrievedFates[0].mFate);
-        assertEquals(fateReport.frameInfo.driverTimestampUsec,
-                retrievedFates[0].mDriverTimestampUSec);
-        assertEquals(WifiLoggerHal.FRAME_TYPE_ETHERNET_II, retrievedFates[0].mFrameType);
-        assertArrayEquals(frameContentBytes, retrievedFates[0].mFrameBytes);
+        assertEquals(WifiLoggerHal.RX_PKT_FATE_SUCCESS, retrievedFate.mFate);
+        assertEquals(fateReport.frameInfo.driverTimestampUsec, retrievedFate.mDriverTimestampUSec);
+        assertEquals(WifiLoggerHal.FRAME_TYPE_ETHERNET_II, retrievedFate.mFrameType);
+        assertArrayEquals(frameContentBytes, retrievedFate.mFrameBytes);
     }
 
     /**
@@ -1690,50 +2198,28 @@
 
         doAnswer(new AnswerWithArguments() {
             public void answer(IWifiStaIface.getDebugRxPacketFatesCallback cb) {
-                cb.onValues(mWifiStatusSuccess,
-                        new ArrayList<WifiDebugRxPacketFateReport>(Arrays.asList(
-                                fateReport, fateReport)));
+                cb.onValues(mWifiStatusSuccess, new ArrayList<>(
+                        // create twice as many as the max size
+                        Collections.nCopies(WifiLoggerHal.MAX_FATE_LOG_LEN * 2, fateReport)));
             }
         }).when(mIWifiStaIface)
                 .getDebugRxPacketFates(any(IWifiStaIface.getDebugRxPacketFatesCallback.class));
 
-        WifiNative.RxFateReport[] retrievedFates = new WifiNative.RxFateReport[1];
-        assertFalse(mWifiVendorHal.getRxPktFates(TEST_IFACE_NAME, retrievedFates));
+        assertEquals(0, mWifiVendorHal.getRxPktFates(TEST_IFACE_NAME).size());
         verify(mIWifiStaIface, never())
                 .getDebugRxPacketFates(any(IWifiStaIface.getDebugRxPacketFatesCallback.class));
 
         assertTrue(mWifiVendorHal.startVendorHalSta());
 
-        assertTrue(mWifiVendorHal.getRxPktFates(TEST_IFACE_NAME, retrievedFates));
+        List<RxFateReport> retrievedFates = mWifiVendorHal.getRxPktFates(TEST_IFACE_NAME);
+        assertEquals(WifiLoggerHal.MAX_FATE_LOG_LEN, retrievedFates.size());
         verify(mIWifiStaIface)
                 .getDebugRxPacketFates(any(IWifiStaIface.getDebugRxPacketFatesCallback.class));
-        assertEquals(WifiLoggerHal.RX_PKT_FATE_FW_DROP_FILTER, retrievedFates[0].mFate);
-        assertEquals(fateReport.frameInfo.driverTimestampUsec,
-                retrievedFates[0].mDriverTimestampUSec);
-        assertEquals(WifiLoggerHal.FRAME_TYPE_80211_MGMT, retrievedFates[0].mFrameType);
-        assertArrayEquals(frameContentBytes, retrievedFates[0].mFrameBytes);
-    }
-
-    /**
-     * Tests the failure to retrieve tx packet fates when the input array is empty.
-     */
-    @Test
-    public void testGetTxPktFatesEmptyInputArray() throws Exception {
-        assertTrue(mWifiVendorHal.startVendorHalSta());
-        assertFalse(mWifiVendorHal.getTxPktFates(TEST_IFACE_NAME, new WifiNative.TxFateReport[0]));
-        verify(mIWifiStaIface, never())
-                .getDebugTxPacketFates(any(IWifiStaIface.getDebugTxPacketFatesCallback.class));
-    }
-
-    /**
-     * Tests the failure to retrieve rx packet fates when the input array is empty.
-     */
-    @Test
-    public void testGetRxPktFatesEmptyInputArray() throws Exception {
-        assertTrue(mWifiVendorHal.startVendorHalSta());
-        assertFalse(mWifiVendorHal.getRxPktFates(TEST_IFACE_NAME, new WifiNative.RxFateReport[0]));
-        verify(mIWifiStaIface, never())
-                .getDebugRxPacketFates(any(IWifiStaIface.getDebugRxPacketFatesCallback.class));
+        RxFateReport retrievedFate = retrievedFates.get(0);
+        assertEquals(WifiLoggerHal.RX_PKT_FATE_FW_DROP_FILTER, retrievedFate.mFate);
+        assertEquals(fateReport.frameInfo.driverTimestampUsec, retrievedFate.mDriverTimestampUSec);
+        assertEquals(WifiLoggerHal.FRAME_TYPE_80211_MGMT, retrievedFate.mFrameType);
+        assertArrayEquals(frameContentBytes, retrievedFate.mFrameBytes);
     }
 
     /**
@@ -1789,7 +2275,6 @@
      */
     @Test
     public void testFirmwareRoamingCapabilityRetrieval() throws Exception {
-        WifiNative.RoamingCapabilities roamingCapabilities = new WifiNative.RoamingCapabilities();
         assertTrue(mWifiVendorHal.startVendorHalSta());
         for (int i = 0; i < 4; i++) {
             int blocklistSize = i + 10;
@@ -1800,9 +2285,10 @@
             doAnswer(new GetRoamingCapabilitiesAnswer(mWifiStatusSuccess, caps))
                     .when(mIWifiStaIface).getRoamingCapabilities(
                             any(IWifiStaIface.getRoamingCapabilitiesCallback.class));
-            assertTrue(mWifiVendorHal.getRoamingCapabilities(TEST_IFACE_NAME, roamingCapabilities));
-            assertEquals(blocklistSize, roamingCapabilities.maxBlocklistSize);
-            assertEquals(allowlistSize, roamingCapabilities.maxAllowlistSize);
+            RoamingCapabilities roamCap = mWifiVendorHal.getRoamingCapabilities(TEST_IFACE_NAME);
+            assertNotNull(roamCap);
+            assertEquals(blocklistSize, roamCap.maxBlocklistSize);
+            assertEquals(allowlistSize, roamCap.maxAllowlistSize);
         }
     }
 
@@ -1812,41 +2298,27 @@
     @Test
     public void testUnsuccessfulFirmwareRoamingCapabilityRetrieval() throws Exception {
         assertTrue(mWifiVendorHal.startVendorHalSta());
-        int blocklistSize = 42;
-        int allowlistSize = 17;
-        WifiNative.RoamingCapabilities roamingCapabilities = new WifiNative.RoamingCapabilities();
-        roamingCapabilities.maxBlocklistSize = blocklistSize;
-        roamingCapabilities.maxAllowlistSize = allowlistSize;
         StaRoamingCapabilities caps = new StaRoamingCapabilities();
-        caps.maxBlacklistSize = blocklistSize + 1; // different value here
-        caps.maxWhitelistSize = allowlistSize + 1;
+        caps.maxBlacklistSize = 43;
+        caps.maxWhitelistSize = 18;
 
         // hal returns a failure status
         doAnswer(new GetRoamingCapabilitiesAnswer(mWifiStatusFailure, null))
                 .when(mIWifiStaIface).getRoamingCapabilities(
                         any(IWifiStaIface.getRoamingCapabilitiesCallback.class));
-        assertFalse(mWifiVendorHal.getRoamingCapabilities(TEST_IFACE_NAME, roamingCapabilities));
-        // in failure cases, result container should not be changed
-        assertEquals(blocklistSize, roamingCapabilities.maxBlocklistSize);
-        assertEquals(allowlistSize, roamingCapabilities.maxAllowlistSize);
+        assertNull(mWifiVendorHal.getRoamingCapabilities(TEST_IFACE_NAME));
 
         // hal returns failure status, but supplies caps anyway
         doAnswer(new GetRoamingCapabilitiesAnswer(mWifiStatusFailure, caps))
                 .when(mIWifiStaIface).getRoamingCapabilities(
-                        any(IWifiStaIface.getRoamingCapabilitiesCallback.class));
-        assertFalse(mWifiVendorHal.getRoamingCapabilities(TEST_IFACE_NAME, roamingCapabilities));
-        // in failure cases, result container should not be changed
-        assertEquals(blocklistSize, roamingCapabilities.maxBlocklistSize);
-        assertEquals(allowlistSize, roamingCapabilities.maxAllowlistSize);
+                any(IWifiStaIface.getRoamingCapabilitiesCallback.class));
+        assertNull(mWifiVendorHal.getRoamingCapabilities(TEST_IFACE_NAME));
 
         // lost connection
         doThrow(new RemoteException())
                 .when(mIWifiStaIface).getRoamingCapabilities(
                         any(IWifiStaIface.getRoamingCapabilitiesCallback.class));
-        assertFalse(mWifiVendorHal.getRoamingCapabilities(TEST_IFACE_NAME, roamingCapabilities));
-        // in failure cases, result container should not be changed
-        assertEquals(blocklistSize, roamingCapabilities.maxBlocklistSize);
-        assertEquals(allowlistSize, roamingCapabilities.maxAllowlistSize);
+        assertNull(mWifiVendorHal.getRoamingCapabilities(TEST_IFACE_NAME));
     }
 
     /**
@@ -1944,6 +2416,33 @@
     }
 
     /**
+     * Tests configureRoaming zero padding success
+     */
+    @Test
+    public void testConfigureRoamingZeroPaddingSuccess() throws Exception {
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+        WifiNative.RoamingConfig roamingConfig = new WifiNative.RoamingConfig();
+        roamingConfig.allowlistSsids = new ArrayList();
+        roamingConfig.allowlistSsids.add("\"xyzzy\"");
+        when(mIWifiStaIface.configureRoaming(any())).thenReturn(mWifiStatusSuccess);
+        assertTrue(mWifiVendorHal.configureRoaming(TEST_IFACE_NAME, roamingConfig));
+        ArgumentCaptor<StaRoamingConfig> staRoamingConfigCaptor = ArgumentCaptor.forClass(
+                StaRoamingConfig.class);
+        verify(mIWifiStaIface).configureRoaming(staRoamingConfigCaptor.capture());
+        byte[] allowlistSsidsPadded = new byte[32];
+        allowlistSsidsPadded[0] = (byte) 0x78;
+        allowlistSsidsPadded[1] = (byte) 0x79;
+        allowlistSsidsPadded[2] = (byte) 0x7a;
+        allowlistSsidsPadded[3] = (byte) 0x7a;
+        allowlistSsidsPadded[4] = (byte) 0x79;
+        assertArrayEquals(staRoamingConfigCaptor.getValue().ssidWhitelist.get(0),
+                allowlistSsidsPadded);
+        allowlistSsidsPadded[5] = (byte) 0x79;
+        assertFalse(Arrays.equals(staRoamingConfigCaptor.getValue().ssidWhitelist.get(0),
+                allowlistSsidsPadded));
+    }
+
+    /**
      * Tests configureRoaming success with null lists
      */
     @Test
@@ -2421,7 +2920,8 @@
         sarInfo.isVoiceCall = true;
 
         // Now expose the 1.1 IWifiChip.
-        mWifiVendorHal = new WifiVendorHalSpyV1_1(mHalDeviceManager, mHandler);
+        mWifiVendorHal = new WifiVendorHalSpyV1_1(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
         when(mIWifiChipV11.selectTxPowerScenario(anyInt())).thenReturn(mWifiStatusSuccess);
 
         assertTrue(mWifiVendorHal.startVendorHalSta());
@@ -2446,7 +2946,8 @@
         sarInfo.isVoiceCall = true;
 
         // Now expose the 1.2 IWifiChip
-        mWifiVendorHal = new WifiVendorHalSpyV1_2(mHalDeviceManager, mHandler);
+        mWifiVendorHal = new WifiVendorHalSpyV1_2(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
         when(mIWifiChipV12.selectTxPowerScenario_1_2(anyInt())).thenReturn(mWifiStatusSuccess);
 
         assertTrue(mWifiVendorHal.startVendorHalSta());
@@ -2485,7 +2986,8 @@
         sarInfo.sarSapSupported = false;
 
         // Now expose the 1.1 IWifiChip.
-        mWifiVendorHal = new WifiVendorHalSpyV1_1(mHalDeviceManager, mHandler);
+        mWifiVendorHal = new WifiVendorHalSpyV1_1(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
         when(mIWifiChipV11.resetTxPowerScenario()).thenReturn(mWifiStatusSuccess);
 
         assertTrue(mWifiVendorHal.startVendorHalSta());
@@ -2510,7 +3012,8 @@
         sarInfo.sarSapSupported = false;
 
         // Now expose the 1.1 IWifiChip.
-        mWifiVendorHal = new WifiVendorHalSpyV1_1(mHalDeviceManager, mHandler);
+        mWifiVendorHal = new WifiVendorHalSpyV1_1(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
         when(mIWifiChipV11.resetTxPowerScenario()).thenReturn(mWifiStatusSuccess);
 
         assertTrue(mWifiVendorHal.startVendorHalSta());
@@ -2541,7 +3044,8 @@
         sarInfo.sarSapSupported = false;
 
         // Now expose the 1.2 IWifiChip.
-        mWifiVendorHal = new WifiVendorHalSpyV1_2(mHalDeviceManager, mHandler);
+        mWifiVendorHal = new WifiVendorHalSpyV1_2(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
         when(mIWifiChipV12.resetTxPowerScenario()).thenReturn(mWifiStatusSuccess);
 
         assertTrue(mWifiVendorHal.startVendorHalSta());
@@ -2566,7 +3070,8 @@
         sarInfo.sarSapSupported = false;
 
         // Now expose the 1.2 IWifiChip.
-        mWifiVendorHal = new WifiVendorHalSpyV1_2(mHalDeviceManager, mHandler);
+        mWifiVendorHal = new WifiVendorHalSpyV1_2(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
         when(mIWifiChipV12.resetTxPowerScenario()).thenReturn(mWifiStatusSuccess);
 
         assertTrue(mWifiVendorHal.startVendorHalSta());
@@ -2599,7 +3104,8 @@
         sarInfo.isWifiSapEnabled = true;
 
         // Expose the 1.2 IWifiChip.
-        mWifiVendorHal = new WifiVendorHalSpyV1_2(mHalDeviceManager, mHandler);
+        mWifiVendorHal = new WifiVendorHalSpyV1_2(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
         when(mIWifiChipV12.selectTxPowerScenario_1_2(anyInt())).thenReturn(mWifiStatusSuccess);
 
         // ON_BODY_CELL_ON
@@ -2626,7 +3132,8 @@
         sarInfo.isVoiceCall = true;
 
         // Expose the 1.2 IWifiChip.
-        mWifiVendorHal = new WifiVendorHalSpyV1_2(mHalDeviceManager, mHandler);
+        mWifiVendorHal = new WifiVendorHalSpyV1_2(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
         when(mIWifiChipV12.selectTxPowerScenario_1_2(anyInt())).thenReturn(mWifiStatusSuccess);
 
         // ON_HEAD_CELL_ON
@@ -2653,7 +3160,8 @@
         sarInfo.isEarPieceActive = true;
 
         // Expose the 1.2 IWifiChip.
-        mWifiVendorHal = new WifiVendorHalSpyV1_2(mHalDeviceManager, mHandler);
+        mWifiVendorHal = new WifiVendorHalSpyV1_2(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
         when(mIWifiChipV12.selectTxPowerScenario_1_2(anyInt())).thenReturn(mWifiStatusSuccess);
 
         // ON_HEAD_CELL_ON
@@ -2680,7 +3188,8 @@
         sarInfo.sarSapSupported = true;
 
         // Now expose the 1.2 IWifiChip.
-        mWifiVendorHal = new WifiVendorHalSpyV1_2(mHalDeviceManager, mHandler);
+        mWifiVendorHal = new WifiVendorHalSpyV1_2(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
         when(mIWifiChipV12.resetTxPowerScenario()).thenReturn(mWifiStatusSuccess);
 
         assertTrue(mWifiVendorHal.startVendorHalSta());
@@ -2715,7 +3224,8 @@
         sarInfo.isVoiceCall = false;
 
         // Expose the 1.2 IWifiChip.
-        mWifiVendorHal = new WifiVendorHalSpyV1_2(mHalDeviceManager, mHandler);
+        mWifiVendorHal = new WifiVendorHalSpyV1_2(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
         when(mIWifiChipV12.selectTxPowerScenario_1_2(anyInt())).thenReturn(mWifiStatusSuccess);
 
         assertTrue(mWifiVendorHal.startVendorHalSta());
@@ -2743,7 +3253,8 @@
         sarInfo.isVoiceCall = true;
 
         // Expose the 1.2 IWifiChip.
-        mWifiVendorHal = new WifiVendorHalSpyV1_2(mHalDeviceManager, mHandler);
+        mWifiVendorHal = new WifiVendorHalSpyV1_2(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
         when(mIWifiChipV12.selectTxPowerScenario_1_2(anyInt())).thenReturn(mWifiStatusSuccess);
 
         assertTrue(mWifiVendorHal.startVendorHalSta());
@@ -2761,7 +3272,8 @@
     @Test
     public void testSetLowLatencyMode_1_2() throws RemoteException {
         // Expose the 1.2 IWifiChip.
-        mWifiVendorHal = new WifiVendorHalSpyV1_2(mHalDeviceManager, mHandler);
+        mWifiVendorHal = new WifiVendorHalSpyV1_2(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
         assertFalse(mWifiVendorHal.setLowLatencyMode(true));
         assertFalse(mWifiVendorHal.setLowLatencyMode(false));
     }
@@ -2774,7 +3286,8 @@
         int mode = android.hardware.wifi.V1_3.IWifiChip.LatencyMode.LOW;
 
         // Expose the 1.3 IWifiChip.
-        mWifiVendorHal = new WifiVendorHalSpyV1_3(mHalDeviceManager, mHandler);
+        mWifiVendorHal = new WifiVendorHalSpyV1_3(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
         when(mIWifiChipV13.setLatencyMode(anyInt())).thenReturn(mWifiStatusSuccess);
         assertTrue(mWifiVendorHal.setLowLatencyMode(true));
         verify(mIWifiChipV13).setLatencyMode(eq(mode));
@@ -2788,7 +3301,8 @@
         int mode = android.hardware.wifi.V1_3.IWifiChip.LatencyMode.NORMAL;
 
         // Expose the 1.3 IWifiChip.
-        mWifiVendorHal = new WifiVendorHalSpyV1_3(mHalDeviceManager, mHandler);
+        mWifiVendorHal = new WifiVendorHalSpyV1_3(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
         when(mIWifiChipV13.setLatencyMode(anyInt())).thenReturn(mWifiStatusSuccess);
         assertTrue(mWifiVendorHal.setLowLatencyMode(false));
         verify(mIWifiChipV13).setLatencyMode(eq(mode));
@@ -2807,8 +3321,8 @@
         }).when(mIWifiStaIface).getName(any(IWifiIface.getNameCallback.class));
 
         assertTrue(mWifiVendorHal.startVendorHal());
-        assertNull(mWifiVendorHal.createStaIface(null));
-        verify(mHalDeviceManager).createStaIface(any(), eq(null));
+        assertNull(mWifiVendorHal.createStaIface(null, TEST_WORKSOURCE));
+        verify(mHalDeviceManager).createStaIface(any(), eq(null), eq(TEST_WORKSOURCE));
     }
 
     /**
@@ -2824,8 +3338,10 @@
         }).when(mIWifiApIface).getName(any(IWifiIface.getNameCallback.class));
 
         assertTrue(mWifiVendorHal.startVendorHal());
-        assertNull(mWifiVendorHal.createApIface(null));
-        verify(mHalDeviceManager).createApIface(any(), eq(null));
+        assertNull(mWifiVendorHal.createApIface(
+                null, TEST_WORKSOURCE, SoftApConfiguration.BAND_2GHZ, false));
+        verify(mHalDeviceManager).createApIface(
+                anyLong(), any(), eq(null), eq(TEST_WORKSOURCE), eq(false));
     }
 
     /**
@@ -2834,8 +3350,8 @@
     @Test
     public void testCreateRemoveStaIface() throws RemoteException {
         assertTrue(mWifiVendorHal.startVendorHal());
-        String ifaceName = mWifiVendorHal.createStaIface(null);
-        verify(mHalDeviceManager).createStaIface(any(), eq(null));
+        String ifaceName = mWifiVendorHal.createStaIface(null, TEST_WORKSOURCE);
+        verify(mHalDeviceManager).createStaIface(any(), eq(null), eq(TEST_WORKSOURCE));
         assertEquals(TEST_IFACE_NAME, ifaceName);
         assertTrue(mWifiVendorHal.removeStaIface(ifaceName));
         verify(mHalDeviceManager).removeIface(eq(mIWifiStaIface));
@@ -2847,20 +3363,53 @@
     @Test
     public void testCreateRemoveApIface() throws RemoteException {
         assertTrue(mWifiVendorHal.startVendorHal());
-        String ifaceName = mWifiVendorHal.createApIface(null);
-        verify(mHalDeviceManager).createApIface(any(), eq(null));
+        String ifaceName = mWifiVendorHal.createApIface(
+                null, TEST_WORKSOURCE, SoftApConfiguration.BAND_2GHZ, false);
+        verify(mHalDeviceManager).createApIface(
+                anyLong(), any(), eq(null), eq(TEST_WORKSOURCE), eq(false));
         assertEquals(TEST_IFACE_NAME, ifaceName);
         assertTrue(mWifiVendorHal.removeApIface(ifaceName));
         verify(mHalDeviceManager).removeIface(eq(mIWifiApIface));
     }
 
     /**
+     * Test removeIfaceInstanceFromBridgedApIface
+     */
+    @Test
+    public void testRemoveIfaceInstanceFromBridgedApIface() throws RemoteException {
+        mWifiVendorHal = new WifiVendorHalSpyV1_5(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
+        when(mIWifiChipV15.removeIfaceInstanceFromBridgedApIface(any(), any()))
+                .thenReturn(mWifiStatusSuccess);
+        assertTrue(mWifiVendorHal.removeIfaceInstanceFromBridgedApIface(any(), any()));
+    }
+
+    /**
+     * Test setCoexUnsafeChannels
+     */
+    @Test
+    public void testSetCoexUnsafeChannels() throws RemoteException {
+        assumeTrue(SdkLevel.isAtLeastS());
+        mWifiVendorHal = new WifiVendorHalSpyV1_5(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
+        when(mIWifiChipV15.setCoexUnsafeChannels(any(), anyInt()))
+                .thenReturn(mWifiStatusSuccess);
+        final List<CoexUnsafeChannel> unsafeChannels = new ArrayList<>();
+        unsafeChannels.add(new CoexUnsafeChannel(WifiScanner.WIFI_BAND_24_GHZ, 6));
+        unsafeChannels.add(new CoexUnsafeChannel(WifiScanner.WIFI_BAND_5_GHZ, 36));
+        final int restrictions = WifiManager.COEX_RESTRICTION_WIFI_DIRECT
+                | WifiManager.COEX_RESTRICTION_WIFI_AWARE | WifiManager.COEX_RESTRICTION_SOFTAP;
+        assertTrue(mWifiVendorHal.setCoexUnsafeChannels(unsafeChannels, restrictions));
+    }
+
+    /**
      * Test the callback handling for the 1.2 HAL.
      */
     @Test
     public void testAlertCallbackUsing_1_2_EventCallback() throws Exception {
         // Expose the 1.2 IWifiChip.
-        mWifiVendorHal = new WifiVendorHalSpyV1_2(mHalDeviceManager, mHandler);
+        mWifiVendorHal = new WifiVendorHalSpyV1_2(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
 
         assertTrue(mWifiVendorHal.startVendorHalSta());
         assertNotNull(mIWifiChipEventCallbackV12);
@@ -2874,11 +3423,12 @@
     @Test
     public void testSetStaMacAddressSuccess() throws Exception {
         // Expose the 1.2 IWifiStaIface.
-        mWifiVendorHal = new WifiVendorHalSpyV1_2(mHalDeviceManager, mHandler);
+        mWifiVendorHal = new WifiVendorHalSpyV1_2(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
         byte[] macByteArray = TEST_MAC_ADDRESS.toByteArray();
         when(mIWifiStaIfaceV12.setMacAddress(macByteArray)).thenReturn(mWifiStatusSuccess);
 
-        assertTrue(mWifiVendorHal.setMacAddress(TEST_IFACE_NAME, TEST_MAC_ADDRESS));
+        assertTrue(mWifiVendorHal.setStaMacAddress(TEST_IFACE_NAME, TEST_MAC_ADDRESS));
         verify(mIWifiStaIfaceV12).setMacAddress(macByteArray);
     }
 
@@ -2888,11 +3438,12 @@
     @Test
     public void testSetStaMacAddressFailDueToStatusFailure() throws Exception {
         // Expose the 1.2 IWifiStaIface.
-        mWifiVendorHal = new WifiVendorHalSpyV1_2(mHalDeviceManager, mHandler);
+        mWifiVendorHal = new WifiVendorHalSpyV1_2(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
         byte[] macByteArray = TEST_MAC_ADDRESS.toByteArray();
         when(mIWifiStaIfaceV12.setMacAddress(macByteArray)).thenReturn(mWifiStatusFailure);
 
-        assertFalse(mWifiVendorHal.setMacAddress(TEST_IFACE_NAME, TEST_MAC_ADDRESS));
+        assertFalse(mWifiVendorHal.setStaMacAddress(TEST_IFACE_NAME, TEST_MAC_ADDRESS));
         verify(mIWifiStaIfaceV12).setMacAddress(macByteArray);
     }
 
@@ -2902,11 +3453,12 @@
     @Test
     public void testSetStaMacAddressFailDueToRemoteException() throws Exception {
         // Expose the 1.2 IWifiStaIface.
-        mWifiVendorHal = new WifiVendorHalSpyV1_2(mHalDeviceManager, mHandler);
+        mWifiVendorHal = new WifiVendorHalSpyV1_2(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
         byte[] macByteArray = TEST_MAC_ADDRESS.toByteArray();
         doThrow(new RemoteException()).when(mIWifiStaIfaceV12).setMacAddress(macByteArray);
 
-        assertFalse(mWifiVendorHal.setMacAddress(TEST_IFACE_NAME, TEST_MAC_ADDRESS));
+        assertFalse(mWifiVendorHal.setStaMacAddress(TEST_IFACE_NAME, TEST_MAC_ADDRESS));
         verify(mIWifiStaIfaceV12).setMacAddress(macByteArray);
     }
 
@@ -2921,7 +3473,7 @@
         byte[] macByteArray = TEST_MAC_ADDRESS.toByteArray();
         when(mIWifiApIfaceV14.setMacAddress(macByteArray)).thenReturn(mWifiStatusSuccess);
 
-        assertTrue(mWifiVendorHal.setMacAddress(TEST_IFACE_NAME_1, TEST_MAC_ADDRESS));
+        assertTrue(mWifiVendorHal.setApMacAddress(TEST_IFACE_NAME_1, TEST_MAC_ADDRESS));
         verify(mIWifiApIfaceV14).setMacAddress(macByteArray);
     }
 
@@ -2936,7 +3488,7 @@
         byte[] macByteArray = TEST_MAC_ADDRESS.toByteArray();
         when(mIWifiApIfaceV14.setMacAddress(macByteArray)).thenReturn(mWifiStatusFailure);
 
-        assertFalse(mWifiVendorHal.setMacAddress(TEST_IFACE_NAME_1, TEST_MAC_ADDRESS));
+        assertFalse(mWifiVendorHal.setApMacAddress(TEST_IFACE_NAME_1, TEST_MAC_ADDRESS));
         verify(mIWifiApIfaceV14).setMacAddress(macByteArray);
     }
 
@@ -2951,7 +3503,7 @@
         byte[] macByteArray = TEST_MAC_ADDRESS.toByteArray();
         doThrow(new RemoteException()).when(mIWifiApIfaceV14).setMacAddress(macByteArray);
 
-        assertFalse(mWifiVendorHal.setMacAddress(TEST_IFACE_NAME_1, TEST_MAC_ADDRESS));
+        assertFalse(mWifiVendorHal.setApMacAddress(TEST_IFACE_NAME_1, TEST_MAC_ADDRESS));
         verify(mIWifiApIfaceV14).setMacAddress(macByteArray);
     }
 
@@ -2961,29 +3513,144 @@
     @Test
     public void testSetMacAddressDoesNotCrashOnOlderHal() throws Exception {
         byte[] macByteArray = TEST_MAC_ADDRESS.toByteArray();
-        assertFalse(mWifiVendorHal.setMacAddress(TEST_IFACE_NAME, TEST_MAC_ADDRESS));
+        assertFalse(mWifiVendorHal.setStaMacAddress(TEST_IFACE_NAME, TEST_MAC_ADDRESS));
+        assertFalse(mWifiVendorHal.setApMacAddress(TEST_IFACE_NAME, TEST_MAC_ADDRESS));
+    }
+
+    /**
+     * Verifies resetApMacToFactoryMacAddress resetToFactoryMacAddress() success.
+     */
+    @Test
+    public void testResetApMacToFactoryMacAddressSuccess() throws Exception {
+        mWifiVendorHal = spy(mWifiVendorHal);
+        when(mWifiVendorHal.getWifiApIfaceForV1_5Mockable(TEST_IFACE_NAME_1))
+                .thenReturn(mIWifiApIfaceV15);
+        when(mIWifiApIfaceV15.resetToFactoryMacAddress()).thenReturn(mWifiStatusSuccess);
+
+        assertTrue(mWifiVendorHal.resetApMacToFactoryMacAddress(TEST_IFACE_NAME_1));
+        verify(mIWifiApIfaceV15).resetToFactoryMacAddress();
+    }
+
+    /**
+     * Verifies resetApMacToFactoryMacAddress() can handle failure status.
+     */
+    @Test
+    public void testResetApMacToFactoryMacAddressFailDueToStatusFailure() throws Exception {
+        mWifiVendorHal = spy(mWifiVendorHal);
+        when(mWifiVendorHal.getWifiApIfaceForV1_5Mockable(TEST_IFACE_NAME_1))
+                .thenReturn(mIWifiApIfaceV15);
+        when(mIWifiApIfaceV15.resetToFactoryMacAddress()).thenReturn(mWifiStatusFailure);
+
+        assertFalse(mWifiVendorHal.resetApMacToFactoryMacAddress(TEST_IFACE_NAME_1));
+        verify(mIWifiApIfaceV15).resetToFactoryMacAddress();
+    }
+
+    /**
+     * Verifies resetApMacToFactoryMacAddress() can handle RemoteException.
+     */
+    @Test
+    public void testResetApMacToFactoryMacAddressFailDueToRemoteException() throws Exception {
+        mWifiVendorHal = spy(mWifiVendorHal);
+        when(mWifiVendorHal.getWifiApIfaceForV1_5Mockable(TEST_IFACE_NAME_1))
+                .thenReturn(mIWifiApIfaceV15);
+        doThrow(new RemoteException()).when(mIWifiApIfaceV15).resetToFactoryMacAddress();
+        assertFalse(mWifiVendorHal.resetApMacToFactoryMacAddress(TEST_IFACE_NAME_1));
+        verify(mIWifiApIfaceV15).resetToFactoryMacAddress();
+    }
+
+    /**
+     * Verifies resetApMacToFactoryMacAddress() does not crash with older HALs.
+     */
+    @Test
+    public void testResetApMacToFactoryMacAddressDoesNotCrashOnOlderHal() throws Exception {
+        assertFalse(mWifiVendorHal.resetApMacToFactoryMacAddress(TEST_IFACE_NAME));
+    }
+
+    /**
+     * Verifies getBridgedApInstances() success.
+     */
+    @Test
+    public void testGetBridgedApInstancesSuccess() throws Exception {
+        doAnswer(new AnswerWithArguments() {
+            public void answer(
+                    android.hardware.wifi.V1_5.IWifiApIface.getBridgedInstancesCallback cb)
+                    throws RemoteException {
+                cb.onValues(mWifiStatusSuccess,
+                        new ArrayList<String>() {{ add(TEST_IFACE_NAME_1); }});
+            }
+        }).when(mIWifiApIfaceV15).getBridgedInstances(any(
+                android.hardware.wifi.V1_5.IWifiApIface.getBridgedInstancesCallback.class));
+        mWifiVendorHal = spy(mWifiVendorHal);
+        when(mWifiVendorHal.getWifiApIfaceForV1_5Mockable(TEST_IFACE_NAME_1))
+                .thenReturn(mIWifiApIfaceV15);
+
+        assertNotNull(mWifiVendorHal.getBridgedApInstances(TEST_IFACE_NAME_1));
+        verify(mIWifiApIfaceV15).getBridgedInstances(any());
+    }
+
+    /**
+     * Verifies getBridgedApInstances() can handle failure status.
+     */
+    @Test
+    public void testGetBridgedApInstancesFailDueToStatusFailure() throws Exception {
+        doAnswer(new AnswerWithArguments() {
+            public void answer(
+                    android.hardware.wifi.V1_5.IWifiApIface.getBridgedInstancesCallback cb)
+                    throws RemoteException {
+                cb.onValues(mWifiStatusFailure, null);
+            }
+        }).when(mIWifiApIfaceV15).getBridgedInstances(any(
+                android.hardware.wifi.V1_5.IWifiApIface.getBridgedInstancesCallback.class));
+
+        mWifiVendorHal = spy(mWifiVendorHal);
+        when(mWifiVendorHal.getWifiApIfaceForV1_5Mockable(TEST_IFACE_NAME_1))
+                .thenReturn(mIWifiApIfaceV15);
+        assertNull(mWifiVendorHal.getBridgedApInstances(TEST_IFACE_NAME_1));
+        verify(mIWifiApIfaceV15).getBridgedInstances(any());
+    }
+
+    /**
+     * Verifies getBridgedApInstances() can handle RemoteException.
+     */
+    @Test
+    public void testGetBridgedApInstancesFailDueToRemoteException() throws Exception {
+        mWifiVendorHal = spy(mWifiVendorHal);
+        when(mWifiVendorHal.getWifiApIfaceForV1_5Mockable(TEST_IFACE_NAME_1))
+                .thenReturn(mIWifiApIfaceV15);
+        doThrow(new RemoteException()).when(mIWifiApIfaceV15).getBridgedInstances(any());
+        assertNull(mWifiVendorHal.getBridgedApInstances(TEST_IFACE_NAME_1));
+        verify(mIWifiApIfaceV15).getBridgedInstances(any());
+    }
+
+    /**
+     * Verifies getBridgedApInstances() does not crash with older HALs.
+     */
+    @Test
+    public void testGetBridgedApInstancesNotCrashOnOlderHal() throws Exception {
+        assertNull(mWifiVendorHal.getBridgedApInstances(TEST_IFACE_NAME));
     }
 
     /**
      * Verifies isSetMacAddressSupported().
      */
     @Test
-    public void testIsSetMacAddressSupportedWhenV1_4Support() throws Exception {
+    public void testIsApSetMacAddressSupportedWhenV1_4Support() throws Exception {
         mWifiVendorHal = spy(mWifiVendorHal);
         when(mWifiVendorHal.getWifiApIfaceForV1_4Mockable(TEST_IFACE_NAME_1))
                 .thenReturn(mIWifiApIfaceV14);
 
-        assertTrue(mWifiVendorHal.isSetMacAddressSupported(TEST_IFACE_NAME_1));
+        assertTrue(mWifiVendorHal.isApSetMacAddressSupported(TEST_IFACE_NAME_1));
     }
 
     /**
      * Verifies isSetMacAddressSupported().
      */
     @Test
-    public void testIsSetMacAddressSupportedWhenV1_2Support() throws Exception {
+    public void testIsStaSetMacAddressSupportedWhenV1_2Support() throws Exception {
         // Expose the 1.2 IWifiStaIface.
-        mWifiVendorHal = new WifiVendorHalSpyV1_2(mHalDeviceManager, mHandler);
-        assertTrue(mWifiVendorHal.isSetMacAddressSupported(TEST_IFACE_NAME));
+        mWifiVendorHal = new WifiVendorHalSpyV1_2(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
+        assertTrue(mWifiVendorHal.isStaSetMacAddressSupported(TEST_IFACE_NAME));
     }
 
     /**
@@ -2991,7 +3658,8 @@
      */
     @Test
     public void testIsSetMacAddressSupportedOnOlderHal() throws Exception {
-        assertFalse(mWifiVendorHal.isSetMacAddressSupported(TEST_IFACE_NAME));
+        assertFalse(mWifiVendorHal.isStaSetMacAddressSupported(TEST_IFACE_NAME));
+        assertFalse(mWifiVendorHal.isApSetMacAddressSupported(TEST_IFACE_NAME));
     }
 
     /**
@@ -3188,40 +3856,62 @@
     }
 
     @Test
-    public void testStaInterfaceAvailableForRequestListeners() throws Exception {
-        WifiNative.InterfaceAvailableForRequestListener staListener =
-                mock(WifiNative.InterfaceAvailableForRequestListener.class);
+    public void testIsItPossibleToCreateIface() {
+        when(mHalDeviceManager.isItPossibleToCreateIface(eq(IfaceType.AP), any())).thenReturn(true);
+        assertTrue(mWifiVendorHal.isItPossibleToCreateApIface(new WorkSource()));
 
-        when(mHalDeviceManager.isStarted()).thenReturn(false);
-        mWifiVendorHal.registerStaIfaceAvailabilityListener(staListener);
-        verify(mHalDeviceManager, never()).registerInterfaceAvailableForRequestListener(
-                eq(IfaceType.STA), any(), any());
-
-        when(mHalDeviceManager.isStarted()).thenReturn(true);
-        mHalDeviceManagerStatusCallbacks.onStatusChanged();
-        verify(mHalDeviceManager).registerInterfaceAvailableForRequestListener(
-                eq(IfaceType.STA), any(), any());
+        when(mHalDeviceManager.isItPossibleToCreateIface(eq(IfaceType.STA), any()))
+                .thenReturn(true);
+        assertTrue(mWifiVendorHal.isItPossibleToCreateStaIface(new WorkSource()));
     }
 
     @Test
-    public void testApInterfaceAvailableForRequestListeners() throws Exception {
-        WifiNative.InterfaceAvailableForRequestListener apListener =
-                mock(WifiNative.InterfaceAvailableForRequestListener.class);
+    public void testIsStaApConcurrencySupported() {
+        when(mHalDeviceManager.canSupportIfaceCombo(
+                argThat(ifaceCombo -> ifaceCombo.get(IfaceType.STA) == 1
+                        && ifaceCombo.get(IfaceType.AP) == 1))).thenReturn(true);
+        assertTrue(mWifiVendorHal.isStaApConcurrencySupported());
+    }
 
-        when(mHalDeviceManager.isStarted()).thenReturn(false);
-        mWifiVendorHal.registerApIfaceAvailabilityListener(apListener);
-        verify(mHalDeviceManager, never()).registerInterfaceAvailableForRequestListener(
-                eq(IfaceType.AP), any(), any());
+    @Test
+    public void testIsStaStaConcurrencySupported() {
+        when(mHalDeviceManager.canSupportIfaceCombo(
+                argThat(ifaceCombo -> ifaceCombo.get(IfaceType.STA) == 2))).thenReturn(true);
+        assertTrue(mWifiVendorHal.isStaStaConcurrencySupported());
+    }
 
-        when(mHalDeviceManager.isStarted()).thenReturn(true);
-        mHalDeviceManagerStatusCallbacks.onStatusChanged();
-        verify(mHalDeviceManager).registerInterfaceAvailableForRequestListener(
-                eq(IfaceType.AP), any(), any());
+    @Test
+    public void testSetMultiStaPrimaryConnection() throws Exception {
+        mWifiVendorHal = new WifiVendorHalSpyV1_5(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
+        when(mIWifiChipV15.setMultiStaPrimaryConnection(any())).thenReturn(mWifiStatusSuccess);
+        assertTrue(mWifiVendorHal.setMultiStaPrimaryConnection(TEST_IFACE_NAME));
+        verify(mIWifiChipV15).setMultiStaPrimaryConnection(TEST_IFACE_NAME);
+    }
+
+    @Test
+    public void testSetMultiStaUseCase() throws Exception {
+        mWifiVendorHal = new WifiVendorHalSpyV1_5(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
+        when(mIWifiChipV15.setMultiStaUseCase(MultiStaUseCase.DUAL_STA_TRANSIENT_PREFER_PRIMARY))
+                .thenReturn(mWifiStatusSuccess);
+        when(mIWifiChipV15.setMultiStaUseCase(MultiStaUseCase.DUAL_STA_NON_TRANSIENT_UNBIASED))
+                .thenReturn(mWifiStatusFailure);
+
+        assertTrue(mWifiVendorHal.setMultiStaUseCase(WifiNative.DUAL_STA_TRANSIENT_PREFER_PRIMARY));
+        verify(mIWifiChipV15).setMultiStaUseCase(MultiStaUseCase.DUAL_STA_TRANSIENT_PREFER_PRIMARY);
+
+        assertFalse(mWifiVendorHal.setMultiStaUseCase(WifiNative.DUAL_STA_NON_TRANSIENT_UNBIASED));
+        verify(mIWifiChipV15).setMultiStaUseCase(MultiStaUseCase.DUAL_STA_NON_TRANSIENT_UNBIASED);
+
+        // illegal value.
+        assertFalse(mWifiVendorHal.setMultiStaUseCase(5));
     }
 
     private void startHalInStaModeAndRegisterRadioModeChangeCallback() {
         // Expose the 1.2 IWifiChip.
-        mWifiVendorHal = new WifiVendorHalSpyV1_2(mHalDeviceManager, mHandler);
+        mWifiVendorHal = new WifiVendorHalSpyV1_2(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
         mWifiVendorHal.registerRadioModeChangeHandler(mVendorHalRadioModeChangeHandler);
         assertTrue(mWifiVendorHal.startVendorHalSta());
         assertNotNull(mIWifiChipEventCallbackV12);
@@ -3229,7 +3919,17 @@
 
     private void startHalInStaModeAndRegisterRadioModeChangeCallback14() {
         // Expose the 1.4 IWifiChip.
-        mWifiVendorHal = new WifiVendorHalSpyV1_4(mHalDeviceManager, mHandler);
+        mWifiVendorHal = new WifiVendorHalSpyV1_4(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
+        mWifiVendorHal.registerRadioModeChangeHandler(mVendorHalRadioModeChangeHandler);
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+        assertNotNull(mIWifiChipEventCallbackV14);
+    }
+
+    private void startHalInStaModeAndRegisterRadioModeChangeCallback15() {
+        // Expose the 1.5 IWifiChip.
+        mWifiVendorHal = new WifiVendorHalSpyV1_5(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
         mWifiVendorHal.registerRadioModeChangeHandler(mVendorHalRadioModeChangeHandler);
         assertTrue(mWifiVendorHal.startVendorHalSta());
         assertNotNull(mIWifiChipEventCallbackV14);
@@ -3367,4 +4067,60 @@
             assertScanDataEqual(expected.get(i), actual.get(i));
         }
     }
+
+    /**
+     * Test setCountryCode gets called when the hal version is V1_5.
+     */
+    @Test
+    public void testSetCountryCodeWithHalV1_5() throws Exception {
+        byte[] expected = new byte[]{(byte) 'U', (byte) 'S'};
+        mWifiVendorHal = new WifiVendorHalSpyV1_5(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
+        when(mIWifiChipV15.setCountryCode(any())).thenReturn(mWifiStatusSuccess);
+
+        // Invalid cases
+        assertFalse(mWifiVendorHal.setChipCountryCode(null));
+        assertFalse(mWifiVendorHal.setChipCountryCode(""));
+        assertFalse(mWifiVendorHal.setChipCountryCode("A"));
+        verify(mIWifiChipV15, never()).setCountryCode(any());
+
+        //valid country code
+        assertTrue(mWifiVendorHal.setChipCountryCode("US"));
+        verify(mIWifiChipV15).setCountryCode(eq(expected));
+    }
+
+    @Test
+    public void testSetScanMode() throws Exception {
+        mWifiVendorHal = new WifiVendorHalSpyV1_5(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
+        when(mIWifiStaIfaceV15.setScanMode(anyBoolean())).thenReturn(mWifiStatusSuccess);
+
+        assertTrue(mWifiVendorHal.setScanMode(TEST_IFACE_NAME, true));
+        verify(mIWifiStaIfaceV15).setScanMode(true);
+
+        assertTrue(mWifiVendorHal.setScanMode(TEST_IFACE_NAME, false));
+        verify(mIWifiStaIfaceV15).setScanMode(false);
+    }
+
+    @Test
+    public void testGetUsableChannels() throws Exception {
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+        mWifiVendorHal = new WifiVendorHalSpyV1_5(mContext, mHalDeviceManager, mHandler,
+                mWifiGlobals);
+        ArrayList<WifiUsableChannel> channels = new ArrayList<>();
+        doAnswer(new AnswerWithArguments() {
+            public void answer(int band, int mode, int filter,
+                    android.hardware.wifi.V1_5.IWifiChip.getUsableChannelsCallback cb)
+                    throws RemoteException {
+                cb.onValues(mWifiStatusSuccess, channels);
+            }
+        }).when(mIWifiChipV15).getUsableChannels(anyInt(), anyInt(), anyInt(),
+                any(android.hardware.wifi.V1_5.IWifiChip.getUsableChannelsCallback.class));
+        mWifiVendorHal.getUsableChannels(
+                WifiScanner.WIFI_BAND_24_GHZ,
+                WifiAvailableChannel.OP_MODE_WIFI_DIRECT_CLI,
+                WifiAvailableChannel.FILTER_CELLULAR_COEXISTENCE);
+        verify(mIWifiChipV15).getUsableChannels(anyInt(), anyInt(), anyInt(),
+                any(android.hardware.wifi.V1_5.IWifiChip.getUsableChannelsCallback.class));
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WrongPasswordNotifierTest.java b/service/tests/wifitests/src/com/android/server/wifi/WrongPasswordNotifierTest.java
index b4d58d0..2656cbb 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WrongPasswordNotifierTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WrongPasswordNotifierTest.java
@@ -21,11 +21,9 @@
 
 import android.app.ActivityManager;
 import android.app.Notification;
-import android.app.NotificationManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.os.UserHandle;
@@ -44,8 +42,6 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.MockitoSession;
 
-import java.util.Arrays;
-
 /**
  * Unit tests for {@link com.android.server.wifi.WrongPasswordNotifier}.
  */
@@ -56,8 +52,7 @@
 
     @Mock WifiContext mContext;
     @Mock Resources mResources;
-    @Mock PackageManager mPackageManager;
-    @Mock NotificationManager mNotificationManager;
+    @Mock WifiNotificationManager mWifiNotificationManager;
     @Mock FrameworkFacade mFrameworkFacade;
     @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Notification.Builder mNotificationBuilder;
     WrongPasswordNotifier mWrongPassNotifier;
@@ -72,16 +67,11 @@
         ResolveInfo settingsResolveInfo = new ResolveInfo();
         settingsResolveInfo.activityInfo = new ActivityInfo();
         settingsResolveInfo.activityInfo.packageName = TEST_SETTINGS_PACKAGE;
-        when(mPackageManager.queryIntentActivitiesAsUser(
-                argThat(((intent) -> intent.getAction().equals(Settings.ACTION_WIFI_SETTINGS))),
-                anyInt(), any()))
-                .thenReturn(Arrays.asList(settingsResolveInfo));
-        when(mContext.getSystemService(Context.NOTIFICATION_SERVICE))
-                .thenReturn(mNotificationManager);
+        when(mFrameworkFacade.getSettingsPackageName(any())).thenReturn(TEST_SETTINGS_PACKAGE);
         when(mContext.getResources()).thenReturn(mResources);
-        when(mContext.getPackageManager()).thenReturn(mPackageManager);
         when(mContext.getWifiOverlayApkPkgName()).thenReturn("test.com.android.wifi.resources");
-        mWrongPassNotifier = new WrongPasswordNotifier(mContext, mFrameworkFacade);
+        mWrongPassNotifier = new WrongPasswordNotifier(mContext, mFrameworkFacade,
+                mWifiNotificationManager);
 
         // static mocking
         mSession = ExtendedMockito.mockitoSession()
@@ -112,7 +102,7 @@
         when(mFrameworkFacade.makeNotificationBuilder(any(),
                 eq(WifiService.NOTIFICATION_NETWORK_ALERTS))).thenReturn(mNotificationBuilder);
         mWrongPassNotifier.onWrongPasswordError(TEST_SSID);
-        verify(mNotificationManager).notify(eq(WrongPasswordNotifier.NOTIFICATION_ID), any());
+        verify(mWifiNotificationManager).notify(eq(WrongPasswordNotifier.NOTIFICATION_ID), any());
         ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
         verify(mFrameworkFacade).getActivity(
                 any(Context.class), anyInt(), intent.capture(), anyInt());
@@ -130,10 +120,10 @@
     @Test
     public void onNewConnectionAttemptWithPreviousWrongPasswordError() throws Exception {
         onWrongPasswordError();
-        reset(mNotificationManager);
+        reset(mWifiNotificationManager);
 
         mWrongPassNotifier.onNewConnectionAttempt();
-        verify(mNotificationManager).cancel(any(), eq(WrongPasswordNotifier.NOTIFICATION_ID));
+        verify(mWifiNotificationManager).cancel(eq(WrongPasswordNotifier.NOTIFICATION_ID));
     }
 
     /**
@@ -145,6 +135,6 @@
     @Test
     public void onNewConnectionAttemptWithoutPreviousWrongPasswordError() throws Exception {
         mWrongPassNotifier.onNewConnectionAttempt();
-        verify(mNotificationManager, never()).cancel(any(), anyInt());
+        verify(mWifiNotificationManager, never()).cancel(anyInt());
     }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/aware/TestUtils.java b/service/tests/wifitests/src/com/android/server/wifi/aware/TestUtils.java
index 351bb98..3c0f23e 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/aware/TestUtils.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/aware/TestUtils.java
@@ -34,7 +34,7 @@
 
         private void addTransactionId(int transactionId) {
             if (transactionId == 0) {
-                return; // transaction ID == 0 is used as a dummy ID in several command - acceptable
+                return; // transaction ID == 0 is used as a placeholder ID in several command
             }
             mTransactionIds.append(transactionId, mTransactionIds.get(transactionId) + 1);
         }
@@ -49,7 +49,7 @@
 
         public boolean enableAndConfigure(short transactionId, ConfigRequest configRequest,
                 boolean notifyIdentityChange, boolean initialConfiguration, boolean isInteractive,
-                boolean isIdle, boolean rangingEnabled) {
+                boolean isIdle, boolean rangingEnabled, boolean isInstantCommunicationEnabled) {
             addTransactionId(transactionId);
             return true;
         }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareDataPathStateManagerTest.java b/service/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareDataPathStateManagerTest.java
index f5d10d3..9282760 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareDataPathStateManagerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareDataPathStateManagerTest.java
@@ -21,6 +21,7 @@
 import static org.hamcrest.core.IsEqual.equalTo;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -33,6 +34,7 @@
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
@@ -43,7 +45,6 @@
 import android.content.pm.PackageManager;
 import android.hardware.wifi.V1_0.NanStatusType;
 import android.net.ConnectivityManager;
-import android.net.INetworkAgent;
 import android.net.MacAddress;
 import android.net.NetworkCapabilities;
 import android.net.NetworkFactory;
@@ -69,7 +70,6 @@
 import android.net.wifi.aware.WifiAwareNetworkSpecifier;
 import android.net.wifi.aware.WifiAwareSession;
 import android.net.wifi.util.HexEncoding;
-import android.os.Build;
 import android.os.Handler;
 import android.os.Message;
 import android.os.Messenger;
@@ -80,10 +80,13 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.server.wifi.Clock;
+import com.android.server.wifi.MockResources;
 import com.android.server.wifi.WifiBaseTest;
+import com.android.server.wifi.aware.WifiAwareDataPathStateManager.WifiAwareNetworkAgent;
 import com.android.server.wifi.util.NetdWrapper;
 import com.android.server.wifi.util.WifiPermissionsUtil;
 import com.android.server.wifi.util.WifiPermissionsWrapper;
+import com.android.wifi.resources.R;
 
 import org.junit.After;
 import org.junit.Before;
@@ -97,8 +100,11 @@
 import org.mockito.Spy;
 
 import java.nio.ByteOrder;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 
@@ -133,6 +139,7 @@
 
     @Rule
     public ErrorCollector collector = new ErrorCollector();
+    private MockResources mResources;
 
     /**
      * Initialize mocks.
@@ -162,7 +169,6 @@
         when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(), anyInt(), anyInt()))
             .thenReturn(true);
         when(mWifiPermissionsUtil.isLocationModeEnabled()).thenReturn(true);
-        when(mMockNativeManager.isAwareNativeAvailable()).thenReturn(true);
 
         mDut = new WifiAwareStateManager();
         mDut.setNative(mMockNativeManager, mMockNative);
@@ -171,8 +177,7 @@
         mDut.startLate();
         mMockLooper.dispatchAll();
 
-        when(mMockNetworkInterface.configureAgentProperties(any(), any(), anyInt(), any(),
-                any())).thenReturn(true);
+        when(mMockNetworkInterface.configureAgentProperties(any(), any(), any())).thenReturn(true);
         when(mMockNetworkInterface.isAddressUsable(any())).thenReturn(true);
 
         when(mMockPowerManager.isDeviceIdleMode()).thenReturn(false);
@@ -180,6 +185,10 @@
 
         mDut.mDataPathMgr.mNetdWrapper = mMockNetdWrapper;
         mDut.mDataPathMgr.mNiWrapper = mMockNetworkInterface;
+
+        mResources = new MockResources();
+        mResources.setBoolean(R.bool.config_wifiAllowMultipleNetworksOnSameAwareNdi, false);
+        when(mMockContext.getResources()).thenReturn(mResources);
     }
 
     /**
@@ -243,6 +252,7 @@
             }
             mMockLooper.dispatchAll();
         }
+        verify(mMockNativeManager, never()).releaseAware();
         for (int i = 0; i < numNdis; ++i) {
             collector.checkThat("interface deleted -- " + i, done[i], equalTo(true));
         }
@@ -299,6 +309,7 @@
         reqNetworkMsg.arg1 = 0;
         res.mMessenger.send(reqNetworkMsg);
         mMockLooper.dispatchAll();
+        inOrderM.verify(mAwareMetricsMock, never()).recordNdpRequestType(anyInt());
 
         // (2) provide a request
         mDut.onDataPathRequestNotification(pubSubId, peerDiscoveryMac, ndpId, null);
@@ -337,6 +348,7 @@
         reqNetworkMsg.arg1 = 0;
         res.mMessenger.send(reqNetworkMsg);
         mMockLooper.dispatchAll();
+        inOrderM.verify(mAwareMetricsMock, never()).recordNdpRequestType(anyInt());
 
         // do not create a data-path!
         verify(mMockNative, never()).initiateDataPath(anyShort(), anyInt(), anyInt(), anyInt(),
@@ -370,6 +382,7 @@
         reqNetworkMsg.arg1 = 0;
         res.mMessenger.send(reqNetworkMsg);
         mMockLooper.dispatchAll();
+        inOrderM.verify(mAwareMetricsMock, never()).recordNdpRequestType(anyInt());
 
         // do not create a data-path!
         verify(mMockNative, never()).initiateDataPath(anyShort(), anyInt(), anyInt(), anyInt(),
@@ -407,6 +420,8 @@
         reqNetworkMsg.arg1 = 0;
         res.mMessenger.send(reqNetworkMsg);
         mMockLooper.dispatchAll();
+        inOrderM.verify(mAwareMetricsMock).recordNdpRequestType(
+                WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB);
 
         // (2) delete interface(s)
         mDut.deleteAllDataPathInterfaces();
@@ -433,9 +448,9 @@
     }
 
     /**
-     * Validate multiple NDPs created on a single NDI. Most importantly that the interface is
-     * set up on first NDP and torn down on last NDP - and not when one or the other is created or
-     * deleted.
+     * Validate multiple NDPs created on a single NDI when overlay set to enabled multiple NDP on
+     * same aware NDI. Most importantly that the interface is set up on first NDP and torn down on
+     * last NDP - and not when one or the other is created or deleted.
      *
      * Procedure:
      * - create NDP 1, 2, and 3 (interface up only on first)
@@ -455,17 +470,18 @@
         final int[] endOrder = {1, 0, 2};
         int networkRequestId = 0;
 
-        ArgumentCaptor<INetworkAgent> agentCaptor = ArgumentCaptor.forClass(INetworkAgent.class);
+        mResources.setBoolean(R.bool.config_wifiAllowMultipleNetworksOnSameAwareNdi, true);
+
+        ArgumentCaptor<WifiAwareNetworkAgent> agentCaptor =
+                ArgumentCaptor.forClass(WifiAwareNetworkAgent.class);
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
-        ArgumentCaptor<NetworkCapabilities> netCapCaptor = ArgumentCaptor.forClass(
-                NetworkCapabilities.class);
         InOrder inOrder = inOrder(mMockNative, mMockCm, mMockCallback, mMockSessionCallback,
-                mMockNetdWrapper);
+                mMockNetdWrapper, mMockNetworkInterface);
         InOrder inOrderM = inOrder(mAwareMetricsMock);
 
         NetworkRequest[] nrs = new NetworkRequest[3];
         DataPathEndPointInfo[] ress = new DataPathEndPointInfo[3];
-        INetworkAgent[] agentBinders = new INetworkAgent[3];
+        WifiAwareNetworkAgent[] agentBinders = new WifiAwareNetworkAgent[3];
         Messenger messenger = null;
         boolean first = true;
         for (int i : startOrder) {
@@ -492,6 +508,8 @@
             reqNetworkMsg.arg1 = 0;
             messenger.send(reqNetworkMsg);
             mMockLooper.dispatchAll();
+            inOrderM.verify(mAwareMetricsMock).recordNdpRequestType(
+                    WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB);
             inOrder.verify(mMockNative).initiateDataPath(transactionId.capture(),
                     eq(requestorId + i),
                     eq(CHANNEL_NOT_REQUESTED), anyInt(), eq(peerDiscoveryMac),
@@ -511,19 +529,19 @@
 
                 first = false;
             }
-            inOrder.verify(mMockCm).registerNetworkAgent(agentCaptor.capture(), any(), any(),
-                    netCapCaptor.capture(), any(), any(), anyInt());
+            inOrder.verify(mMockNetworkInterface).setConnected(agentCaptor.capture());
             agentBinders[i] = agentCaptor.getValue();
             inOrderM.verify(mAwareMetricsMock).recordNdpStatus(eq(NanStatusType.SUCCESS),
                     eq(false), anyLong());
             inOrderM.verify(mAwareMetricsMock).recordNdpCreation(anyInt(), any(), any());
             WifiAwareNetworkInfo netInfo =
-                    (WifiAwareNetworkInfo) netCapCaptor.getValue().getTransportInfo();
+                    (WifiAwareNetworkInfo) agentBinders[i].mDataPathCapabilities.getTransportInfo();
             assertArrayEquals(MacAddress.fromBytes(
                     peerDataPathMac).getLinkLocalIpv6FromEui48Mac().getAddress(),
                     netInfo.getPeerIpv6Addr().getAddress());
             assertEquals(port, netInfo.getPort());
             assertEquals(transportProtocol, netInfo.getTransportProtocol());
+            assertEquals(i + 1, mDut.mDataPathMgr.getNumOfNdps());
         }
 
         // (3) end data-path (unless didn't get confirmation)
@@ -534,7 +552,7 @@
             endNetworkReqMsg.obj = nrs[i];
             messenger.send(endNetworkReqMsg);
 
-            agentBinders[i].onDisconnected();
+            agentBinders[i].onNetworkUnwanted();
             mMockLooper.dispatchAll();
 
             inOrder.verify(mMockNative).endDataPath(transactionId.capture(), eq(ndpId + i));
@@ -570,11 +588,10 @@
         NetworkRequest[] nrs = new NetworkRequest[numRequestsPre + numRequestsPost + 1];
 
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
-        ArgumentCaptor<INetworkAgent> agentCaptor = ArgumentCaptor.forClass(INetworkAgent.class);
-        ArgumentCaptor<NetworkCapabilities> netCapCaptor = ArgumentCaptor.forClass(
-                NetworkCapabilities.class);
+        ArgumentCaptor<WifiAwareNetworkAgent> agentCaptor =
+                ArgumentCaptor.forClass(WifiAwareNetworkAgent.class);
         InOrder inOrder = inOrder(mMockNative, mMockCm, mMockCallback, mMockSessionCallback,
-                mMockNetdWrapper);
+                mMockNetdWrapper, mMockNetworkInterface);
         InOrder inOrderM = inOrder(mAwareMetricsMock);
 
         // (1) initialize all clients
@@ -599,6 +616,7 @@
             messenger.send(reqNetworkMsg);
         }
         mMockLooper.dispatchAll();
+        inOrderM.verify(mAwareMetricsMock).recordNdpRequestType(anyInt());
 
         // (3) verify the start NDP HAL request
         inOrder.verify(mMockNative).initiateDataPath(transactionId.capture(), eq(0),
@@ -634,18 +652,19 @@
 
         inOrder.verify(mMockNetdWrapper).setInterfaceUp(anyString());
         inOrder.verify(mMockNetdWrapper).enableIpv6(anyString());
-        inOrder.verify(mMockCm).registerNetworkAgent(agentCaptor.capture(), any(), any(),
-                netCapCaptor.capture(), any(), any(), anyInt());
+        inOrder.verify(mMockNetworkInterface).setConnected(agentCaptor.capture());
         inOrderM.verify(mAwareMetricsMock).recordNdpStatus(eq(NanStatusType.SUCCESS),
                 eq(true), anyLong());
         inOrderM.verify(mAwareMetricsMock).recordNdpCreation(anyInt(), any(), any());
         WifiAwareNetworkInfo netInfo =
-                (WifiAwareNetworkInfo) netCapCaptor.getValue().getTransportInfo();
+                (WifiAwareNetworkInfo) agentCaptor.getValue().mDataPathCapabilities
+                        .getTransportInfo();
         assertArrayEquals(MacAddress.fromBytes(
                 peerDataPathMac).getLinkLocalIpv6FromEui48Mac().getAddress(),
                 netInfo.getPeerIpv6Addr().getAddress());
         assertEquals(port, netInfo.getPort());
         assertEquals(transportProtocol, netInfo.getTransportProtocol());
+        assertEquals(1, mDut.mDataPathMgr.getNumOfNdps());
 
         // (8) execute 'post' requests
         for (int i = numRequestsPre; i < numRequestsPre + numRequestsPost; ++i) {
@@ -659,6 +678,7 @@
             reqNetworkMsg.arg1 = 0;
             messenger.send(reqNetworkMsg);
         }
+
         nrs[numRequestsPre + numRequestsPost] = getSessionNetworkRequest(
                 clientId + numRequestsPre + numRequestsPost, ddepi.mSessionId, ddepi.mPeerHandle,
                 null, null, false, 11);
@@ -668,6 +688,7 @@
         reqNetworkMsg.arg1 = 0;
         messenger.send(reqNetworkMsg);
         mMockLooper.dispatchAll();
+        inOrderM.verify(mAwareMetricsMock, never()).recordNdpRequestType(anyInt());
 
         // (9) unregister all requests
         for (int i = 2; i < numRequestsPre + numRequestsPost + 1; ++i) {
@@ -678,7 +699,7 @@
             mMockLooper.dispatchAll();
         }
 
-        agentCaptor.getValue().onDisconnected();
+        agentCaptor.getValue().onNetworkUnwanted();
         mMockLooper.dispatchAll();
 
         // (10) verify that NDP torn down
@@ -699,7 +720,7 @@
      * Validate that multiple NDP requests to the same peer target different NDIs.
      */
     @Test
-    public void testMultipleNdi() throws Exception {
+    public void testMultipleNdiToSamePeer() throws Exception {
         final int numNdis = 5;
         final int clientId = 123;
         final int ndpId = 5;
@@ -708,11 +729,11 @@
 
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
         ArgumentCaptor<String> ifNameCaptor = ArgumentCaptor.forClass(String.class);
-        ArgumentCaptor<NetworkCapabilities> netCapCaptor = ArgumentCaptor.forClass(
-                NetworkCapabilities.class);
+        ArgumentCaptor<WifiAwareNetworkAgent> agentCaptor = ArgumentCaptor.forClass(
+                WifiAwareNetworkAgent.class);
 
         InOrder inOrder = inOrder(mMockNative, mMockCm, mMockCallback, mMockSessionCallback,
-                mMockNetdWrapper);
+                mMockNetdWrapper, mMockNetworkInterface);
         InOrder inOrderM = inOrder(mAwareMetricsMock);
 
         // (1) initialize all clients
@@ -737,6 +758,8 @@
             reqNetworkMsg.arg1 = 0;
             messenger.send(reqNetworkMsg);
             mMockLooper.dispatchAll();
+            inOrderM.verify(mAwareMetricsMock).recordNdpRequestType(
+                    WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB);
 
             if (i < numNdis) {
                 inOrder.verify(mMockNative).initiateDataPath(transactionId.capture(), eq(0),
@@ -750,18 +773,19 @@
 
                 inOrder.verify(mMockNetdWrapper).setInterfaceUp(anyString());
                 inOrder.verify(mMockNetdWrapper).enableIpv6(anyString());
-                inOrder.verify(mMockCm).registerNetworkAgent(any(), any(), any(),
-                        netCapCaptor.capture(), any(), any(), anyInt());
+                inOrder.verify(mMockNetworkInterface).setConnected(agentCaptor.capture());
                 inOrderM.verify(mAwareMetricsMock).recordNdpStatus(eq(NanStatusType.SUCCESS),
                         eq(true), anyLong());
                 inOrderM.verify(mAwareMetricsMock).recordNdpCreation(anyInt(), any(), any());
                 WifiAwareNetworkInfo netInfo =
-                        (WifiAwareNetworkInfo) netCapCaptor.getValue().getTransportInfo();
+                        (WifiAwareNetworkInfo) agentCaptor.getValue().mDataPathCapabilities
+                                .getTransportInfo();
                 assertArrayEquals(MacAddress.fromBytes(
                         peerDataPathMac).getLinkLocalIpv6FromEui48Mac().getAddress(),
                         netInfo.getPeerIpv6Addr().getAddress());
                 assertEquals(0, netInfo.getPort()); // uninitialized -> 0
                 assertEquals(-1, netInfo.getTransportProtocol()); // uninitialized -> -1
+                assertEquals(i + 1, mDut.mDataPathMgr.getNumOfNdps());
             } else {
                 verifyRequestDeclaredUnfullfillable(nr);
             }
@@ -774,6 +798,205 @@
                 mAwareMetricsMock, mMockNetdWrapper);
     }
 
+    /**
+     * When overlay set to disabled multiple networks on single Aware NDI, validate that multiple
+     * NDP requests to the different peer target different NDIs. And when number of requests exceeds
+     * the number of NDIs, request will be rejected.
+     */
+    @Test
+    public void testMultipleNdiToDifferentPeer() throws Exception {
+        final int numNdis = 5;
+        final int clientId = 123;
+        final int ndpId = 5;
+
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<String> ifNameCaptor = ArgumentCaptor.forClass(String.class);
+        ArgumentCaptor<WifiAwareNetworkAgent> agentCaptor = ArgumentCaptor.forClass(
+                WifiAwareNetworkAgent.class);
+
+        InOrder inOrder = inOrder(mMockNative, mMockCm, mMockCallback, mMockSessionCallback,
+                mMockNetdWrapper, mMockNetworkInterface);
+        InOrder inOrderM = inOrder(mAwareMetricsMock);
+
+        // (1) initialize all clients
+        Messenger messenger = initOobDataPathEndPoint(true, numNdis, clientId, inOrder, inOrderM);
+        for (int i = 1; i < numNdis + 3; ++i) {
+            initOobDataPathEndPoint(false, numNdis, clientId + i, inOrder, inOrderM);
+        }
+
+        // (2) make N network requests: each unique
+        Set<String> interfaces = new HashSet<>();
+        for (int i = 0; i < numNdis + 1; ++i) {
+            final byte[] peerDiscoveryMac = HexEncoding.decode("000102030405".toCharArray(), false);
+            final byte[] peerDataPathMac = HexEncoding.decode("0A0B0C0D0E0F".toCharArray(), false);
+            peerDiscoveryMac[5] = (byte) (peerDiscoveryMac[5] + i);
+            peerDataPathMac[5] = (byte) (peerDataPathMac[5] + i);
+
+            byte[] pmk = new byte[32];
+            pmk[0] = (byte) i;
+
+            NetworkRequest nr = getDirectNetworkRequest(clientId + i,
+                    WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR, peerDiscoveryMac, pmk,
+                    null, i);
+
+            Message reqNetworkMsg = Message.obtain();
+            reqNetworkMsg.what = NetworkProvider.CMD_REQUEST_NETWORK;
+            reqNetworkMsg.obj = nr;
+            reqNetworkMsg.arg1 = 0;
+            messenger.send(reqNetworkMsg);
+            mMockLooper.dispatchAll();
+            inOrderM.verify(mAwareMetricsMock).recordNdpRequestType(
+                    WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB);
+
+            if (i < numNdis) {
+                inOrder.verify(mMockNative).initiateDataPath(transactionId.capture(), eq(0),
+                        eq(CHANNEL_NOT_REQUESTED), anyInt(), eq(peerDiscoveryMac),
+                        ifNameCaptor.capture(), eq(pmk), eq(null), eq(true), any(), any());
+                interfaces.add(ifNameCaptor.getValue());
+
+                mDut.onInitiateDataPathResponseSuccess(transactionId.getValue(), ndpId + i);
+                mDut.onDataPathConfirmNotification(ndpId + i, peerDataPathMac, true, 0, null, null);
+                mMockLooper.dispatchAll();
+
+                inOrder.verify(mMockNetdWrapper).setInterfaceUp(anyString());
+                inOrder.verify(mMockNetdWrapper).enableIpv6(anyString());
+                inOrder.verify(mMockNetworkInterface).setConnected(agentCaptor.capture());
+                inOrderM.verify(mAwareMetricsMock).recordNdpStatus(eq(NanStatusType.SUCCESS),
+                        eq(true), anyLong());
+                inOrderM.verify(mAwareMetricsMock).recordNdpCreation(anyInt(), any(), any());
+                WifiAwareNetworkInfo netInfo =
+                        (WifiAwareNetworkInfo) agentCaptor.getValue().mDataPathCapabilities
+                                .getTransportInfo();
+                assertArrayEquals(MacAddress.fromBytes(
+                        peerDataPathMac).getLinkLocalIpv6FromEui48Mac().getAddress(),
+                        netInfo.getPeerIpv6Addr().getAddress());
+                assertEquals(0, netInfo.getPort()); // uninitialized -> 0
+                assertEquals(-1, netInfo.getTransportProtocol()); // uninitialized -> -1
+                assertEquals(i + 1, mDut.mDataPathMgr.getNumOfNdps());
+            } else {
+                verifyRequestDeclaredUnfullfillable(nr);
+            }
+        }
+
+        // verify that each interface name is unique
+        assertEquals("Number of unique interface names", numNdis, interfaces.size());
+
+        verifyNoMoreInteractions(mMockNative, mMockCallback, mMockSessionCallback,
+                mAwareMetricsMock, mMockNetdWrapper);
+    }
+
+    /**
+     * When overlay set to enable multiple networks on single Aware NDI, validate that multiple
+     * NDP requests to the different peer target same NDI when only one NDI is available. Also when
+     * requests to a peer that is already accepted by this NDI, the new request should be reject.
+     */
+    @Test
+    public void testMultipleNdpToDifferentPeerOnSingleNdi() throws Exception {
+        final int numNdis = 1;
+        final int clientId = 123;
+        final int ndpId = 5;
+        final int numberNdp = 3;
+
+        mResources.setBoolean(R.bool.config_wifiAllowMultipleNetworksOnSameAwareNdi, true);
+
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<String> ifNameCaptor = ArgumentCaptor.forClass(String.class);
+        ArgumentCaptor<WifiAwareNetworkAgent> agentCaptor = ArgumentCaptor.forClass(
+                WifiAwareNetworkAgent.class);
+
+        InOrder inOrder = inOrder(mMockNative, mMockCm, mMockCallback, mMockSessionCallback,
+                mMockNetdWrapper, mMockNetworkInterface);
+        InOrder inOrderM = inOrder(mAwareMetricsMock);
+
+        // (1) initialize all clients
+        Messenger messenger = initOobDataPathEndPoint(true, numNdis, clientId, inOrder, inOrderM);
+        for (int i = 1; i < numberNdp; ++i) {
+            initOobDataPathEndPoint(false, numNdis, clientId + i, inOrder, inOrderM);
+        }
+
+        // (2) make 2 network requests: each unique
+        Set<String> interfaces = new HashSet<>();
+        boolean first = true;
+        for (int i = 0; i < numberNdp - 1; ++i) {
+            final byte[] peerDiscoveryMac = HexEncoding.decode("000102030405".toCharArray(), false);
+            final byte[] peerDataPathMac = HexEncoding.decode("0A0B0C0D0E0F".toCharArray(), false);
+            peerDiscoveryMac[5] = (byte) (peerDiscoveryMac[5] + i);
+            peerDataPathMac[5] = (byte) (peerDataPathMac[5] + i);
+
+            byte[] pmk = new byte[32];
+            pmk[0] = (byte) i;
+
+            NetworkRequest nr = getDirectNetworkRequest(clientId + i,
+                    WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR, peerDiscoveryMac, pmk,
+                    null, i);
+
+            Message reqNetworkMsg = Message.obtain();
+            reqNetworkMsg.what = NetworkProvider.CMD_REQUEST_NETWORK;
+            reqNetworkMsg.obj = nr;
+            reqNetworkMsg.arg1 = 0;
+            messenger.send(reqNetworkMsg);
+            mMockLooper.dispatchAll();
+            inOrderM.verify(mAwareMetricsMock).recordNdpRequestType(
+                    WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB);
+
+            inOrder.verify(mMockNative).initiateDataPath(transactionId.capture(), eq(0),
+                    eq(CHANNEL_NOT_REQUESTED), anyInt(), eq(peerDiscoveryMac),
+                    ifNameCaptor.capture(), eq(pmk), eq(null), eq(true), any(), any());
+            interfaces.add(ifNameCaptor.getValue());
+
+            mDut.onInitiateDataPathResponseSuccess(transactionId.getValue(), ndpId + i);
+            mDut.onDataPathConfirmNotification(ndpId + i, peerDataPathMac, true, 0, null, null);
+            mMockLooper.dispatchAll();
+            if (first) {
+                inOrder.verify(mMockNetdWrapper).setInterfaceUp(anyString());
+                inOrder.verify(mMockNetdWrapper).enableIpv6(anyString());
+                first = false;
+            }
+            inOrder.verify(mMockNetworkInterface).setConnected(agentCaptor.capture());
+            inOrderM.verify(mAwareMetricsMock).recordNdpStatus(eq(NanStatusType.SUCCESS),
+                    eq(true), anyLong());
+            inOrderM.verify(mAwareMetricsMock).recordNdpCreation(anyInt(), any(), any());
+            WifiAwareNetworkInfo netInfo =
+                    (WifiAwareNetworkInfo) agentCaptor.getValue().mDataPathCapabilities
+                            .getTransportInfo();
+            assertArrayEquals(MacAddress.fromBytes(
+                    peerDataPathMac).getLinkLocalIpv6FromEui48Mac().getAddress(),
+                    netInfo.getPeerIpv6Addr().getAddress());
+            assertEquals(0, netInfo.getPort()); // uninitialized -> 0
+            assertEquals(-1, netInfo.getTransportProtocol()); // uninitialized -> -1
+            assertEquals(i + 1, mDut.mDataPathMgr.getNumOfNdps());
+        }
+
+        // verify that two request all using the same interface
+        assertEquals("Number of unique interface names", numNdis, interfaces.size());
+
+
+        // make the 3rd network request which has the same peer as the first one.
+        final byte[] peerDiscoveryMac = HexEncoding.decode("000102030405".toCharArray(), false);
+
+        byte[] pmk = new byte[32];
+        pmk[0] = (byte) 2;
+
+        NetworkRequest nr = getDirectNetworkRequest(clientId + 2,
+                WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR, peerDiscoveryMac, pmk,
+                null, 2);
+
+        Message reqNetworkMsg = Message.obtain();
+        reqNetworkMsg.what = NetworkProvider.CMD_REQUEST_NETWORK;
+        reqNetworkMsg.obj = nr;
+        reqNetworkMsg.arg1 = 0;
+        messenger.send(reqNetworkMsg);
+        mMockLooper.dispatchAll();
+        inOrderM.verify(mAwareMetricsMock).recordNdpRequestType(
+                WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB);
+
+        // It should be reject as interface already has a request to this peer.
+        verifyRequestDeclaredUnfullfillable(nr);
+
+        verifyNoMoreInteractions(mMockNative, mMockCallback, mMockSessionCallback,
+                mAwareMetricsMock, mMockNetdWrapper);
+    }
+
     /*
      * Initiator tests
      */
@@ -968,29 +1191,6 @@
     }
 
     /**
-     * Validate the failure flow of the Responder: using session network specifier with a
-     * Passphrase and no peer ID (i.e. 0) on a NON-LEGACY device.
-     */
-    @Test
-    public void testDataPathResonderMacPassphraseNoPeerIdSuccessNonLegacy() throws Exception {
-        when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(), anyInt(), anyInt()))
-            .thenReturn(false);
-        testDataPathResponderUtility(false, false, false, true, true);
-    }
-
-    /**
-     * Validate the failure flow of the Responder: using session network specifier with a null
-     * PMK/Passphrase and no peer ID (i.e. 0) on a NON-LEGACY device.
-     */
-    @Test
-    public void testDataPathResonderMacOpenNoPeerIdNoPmkPassphraseSuccessNonLegacy()
-            throws Exception {
-        when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(), anyInt(), anyInt()))
-            .thenReturn(false);
-        testDataPathResponderUtility(false, false, false, false, true);
-    }
-
-    /**
      * Validate the success flow of the Responder: using a direct network specifier with a non-null
      * peer mac and non-null PMK.
      */
@@ -1027,28 +1227,6 @@
     }
 
     /**
-     * Validate the failure flow of the Responder: using a direct network specifier with a null peer
-     * mac and non-null Passphrase on a NON-LEGACY device.
-     */
-    @Test
-    public void testDataPathResonderDirectNoMacPassphraseSuccessNonLegacy() throws Exception {
-        when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(), anyInt(), anyInt()))
-            .thenReturn(false);
-        testDataPathResponderUtility(true, false, false, true, true);
-    }
-
-    /**
-     * Validate the failure flow of the Responder: using a direct network specifier with a null peer
-     * mac and null Pmk/Passphrase on a NON-LEGACY device.
-     */
-    @Test
-    public void testDataPathResonderDirectNoMacNoPmkPassphraseSuccessNonLegacy() throws Exception {
-        when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(), anyInt(), anyInt()))
-            .thenReturn(false);
-        testDataPathResponderUtility(true, false, false, false, true);
-    }
-
-    /**
      * Validate the fail flow of the Responder: use a session network specifier with a non-null
      * PMK, but don't get a confirmation.
      */
@@ -1167,6 +1345,7 @@
         reqNetworkMsg.arg1 = 0;
         res.mMessenger.send(reqNetworkMsg);
         mMockLooper.dispatchAll();
+        inOrderM.verify(mAwareMetricsMock, never()).recordNdpRequestType(anyInt());
 
         // consequences of failure:
         //   Responder (publisher): responds with a rejection to any data-path requests
@@ -1228,6 +1407,8 @@
         reqNetworkMsg.arg1 = 0;
         res.mMessenger.send(reqNetworkMsg);
         mMockLooper.dispatchAll();
+        inOrderM.verify(mAwareMetricsMock, never()).recordNdpRequestType(anyInt());
+
 
         // consequences of failure:
         //   Responder (publisher): responds with a rejection to any data-path requests
@@ -1289,6 +1470,7 @@
         reqNetworkMsg.arg1 = 0;
         res.mMessenger.send(reqNetworkMsg);
         mMockLooper.dispatchAll();
+        inOrderM.verify(mAwareMetricsMock, never()).recordNdpRequestType(anyInt());
 
         // consequences of failure:
         //   Responder (publisher): responds with a rejection to any data-path requests
@@ -1330,13 +1512,13 @@
         final String passphrase = "some passphrase";
         final byte[] peerDiscoveryMac = HexEncoding.decode("000102030405".toCharArray(), false);
 
-        ArgumentCaptor<INetworkAgent> agentCaptor = ArgumentCaptor.forClass(INetworkAgent.class);
+        ArgumentCaptor<WifiAwareNetworkAgent> agentCaptor =
+                ArgumentCaptor.forClass(WifiAwareNetworkAgent.class);
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
-        ArgumentCaptor<NetworkCapabilities> netCapCaptor = ArgumentCaptor.forClass(
-                NetworkCapabilities.class);
         InOrder inOrder = inOrder(mMockNative, mMockCm, mMockCallback, mMockSessionCallback,
                 mMockNetdWrapper, mMockNetworkInterface);
         InOrder inOrderM = inOrder(mAwareMetricsMock);
+        WifiAwareNetworkAgent networkAgent = null;
 
         if (!providePmk) {
             when(mPermissionsWrapperMock.getUidPermission(
@@ -1373,6 +1555,7 @@
         reqNetworkMsg.arg1 = 0;
         res.mMessenger.send(reqNetworkMsg);
         mMockLooper.dispatchAll();
+        inOrderM.verify(mAwareMetricsMock).recordNdpRequestType(anyInt());
         inOrder.verify(mMockNative).initiateDataPath(transactionId.capture(),
                 eq(useDirect ? 0 : requestorId),
                 eq(CHANNEL_NOT_REQUESTED), anyInt(), eq(peerDiscoveryMac),
@@ -1396,8 +1579,8 @@
             int numConfigureAgentPropertiesFail = 0;
             if (numAddrValidationRetries > 0) {
                 when(mMockNetworkInterface.isAddressUsable(any())).thenReturn(false);
-                when(mMockNetworkInterface.configureAgentProperties(any(), any(), anyInt(),
-                        any(), any())).thenReturn(false);
+                when(mMockNetworkInterface.configureAgentProperties(any(), any(), any()))
+                        .thenReturn(false);
                 // First retry will be ConfigureAgentProperties failure.
                 numConfigureAgentPropertiesFail = 1;
             }
@@ -1407,15 +1590,14 @@
             mMockLooper.dispatchAll();
             inOrder.verify(mMockNetdWrapper).setInterfaceUp(anyString());
             inOrder.verify(mMockNetdWrapper).enableIpv6(anyString());
-            inOrder.verify(mMockNetworkInterface).configureAgentProperties(any(), any(), anyInt(),
-                    any(), any());
+            inOrder.verify(mMockNetworkInterface).configureAgentProperties(any(), any(), any());
             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);
+                    when(mMockNetworkInterface.configureAgentProperties(any(), any(), any()))
+                            .thenReturn(true);
                 }
                 if (i == numAddrValidationRetries - 1) {
                     when(mMockNetworkInterface.isAddressUsable(any())).thenReturn(true);
@@ -1426,8 +1608,7 @@
                 mMockLooper.moveTimeForward(
                         WifiAwareDataPathStateManager.ADDRESS_VALIDATION_RETRY_INTERVAL_MS + 1);
                 mMockLooper.dispatchAll();
-                inOrder.verify(mMockNetworkInterface).configureAgentProperties(any(), any(),
-                        anyInt(), any(), any());
+                inOrder.verify(mMockNetworkInterface).configureAgentProperties(any(), any(), any());
                 if (i < numConfigureAgentPropertiesFail) {
                     continue;
                 }
@@ -1442,17 +1623,21 @@
                 inOrder.verify(mMockNative).endDataPath(transactionId.capture(), eq(ndpId));
                 mDut.onEndDataPathResponse(transactionId.getValue(), true, 0);
             } else {
-                inOrder.verify(mMockCm).registerNetworkAgent(agentCaptor.capture(), any(),
-                        any(), netCapCaptor.capture(), any(), any(), anyInt());
-                inOrder.verify(mMockNetworkInterface).setConnected(any());
+                inOrder.verify(mMockNetworkInterface).setConnected(agentCaptor.capture());
+                networkAgent = agentCaptor.getValue();
                 inOrderM.verify(mAwareMetricsMock).recordNdpStatus(eq(NanStatusType.SUCCESS),
                         eq(useDirect), anyLong());
                 inOrderM.verify(mAwareMetricsMock).recordNdpCreation(anyInt(), any(), any());
                 WifiAwareNetworkInfo netInfo =
-                        (WifiAwareNetworkInfo) netCapCaptor.getValue().getTransportInfo();
+                        (WifiAwareNetworkInfo) networkAgent.mDataPathCapabilities
+                                .getTransportInfo();
                 assertEquals(ipv6Address, netInfo.getPeerIpv6Addr().getHostAddress());
                 assertEquals(port, netInfo.getPort());
                 assertEquals(transportProtocol, netInfo.getTransportProtocol());
+                assertEquals(1, mDut.mDataPathMgr.getNumOfNdps());
+                mDut.onDataPathScheduleUpdateNotification(peerDiscoveryMac, new ArrayList<>(ndpId),
+                        Collections.emptyList());
+                mMockLooper.dispatchAll();
             }
         } else {
             assertTrue(mAlarmManager.dispatch(
@@ -1473,13 +1658,13 @@
             endNetworkReqMsg.obj = nr;
             res.mMessenger.send(endNetworkReqMsg);
 
-            agentCaptor.getValue().onDisconnected();
+            networkAgent.onNetworkUnwanted();
             mDut.onEndDataPathResponse(transactionId.getValue(), true, 0);
             mDut.onDataPathEndNotification(ndpId);
             mMockLooper.dispatchAll();
 
-            inOrder.verify(mMockNetdWrapper).setInterfaceDown(anyString());
             inOrder.verify(mMockNative).endDataPath(transactionId.capture(), eq(ndpId));
+            inOrder.verify(mMockNetdWrapper).setInterfaceDown(anyString());
             inOrderM.verify(mAwareMetricsMock).recordNdpSessionDuration(anyLong());
         }
 
@@ -1499,17 +1684,13 @@
         final byte[] peerDiscoveryMac = HexEncoding.decode("000102030405".toCharArray(), false);
         final byte[] peerDataPathMac = HexEncoding.decode("0A0B0C0D0E0F".toCharArray(), false);
 
-        ArgumentCaptor<INetworkAgent> agentCaptor = ArgumentCaptor.forClass(INetworkAgent.class);
+        ArgumentCaptor<WifiAwareNetworkAgent> agentCaptor =
+                ArgumentCaptor.forClass(WifiAwareNetworkAgent.class);
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
-        ArgumentCaptor<NetworkCapabilities> netCapCaptor = ArgumentCaptor.forClass(
-                NetworkCapabilities.class);
         InOrder inOrder = inOrder(mMockNative, mMockCm, mMockCallback, mMockSessionCallback,
-                mMockNetdWrapper);
+                mMockNetdWrapper, mMockNetworkInterface);
         InOrder inOrderM = inOrder(mAwareMetricsMock);
 
-        boolean isLegacy = mWifiPermissionsUtil.isTargetSdkLessThan("anything",
-                Build.VERSION_CODES.P, 0);
-
         if (providePmk) {
             when(mPermissionsWrapperMock.getUidPermission(
                     eq(Manifest.permission.NETWORK_STACK), eq(Process.myUid()))).thenReturn(
@@ -1539,54 +1720,65 @@
         reqNetworkMsg.arg1 = 0;
         res.mMessenger.send(reqNetworkMsg);
         mMockLooper.dispatchAll();
+        inOrderM.verify(mAwareMetricsMock).recordNdpRequestType(anyInt());
 
         // (2) get request & respond (if legacy)
         mDut.onDataPathRequestNotification(pubSubId, peerDiscoveryMac, ndpId, null);
         mMockLooper.dispatchAll();
-        if (isLegacy) {
-            inOrder.verify(mMockNative).respondToDataPathRequest(transactionId.capture(), eq(true),
-                    eq(ndpId), eq(sAwareInterfacePrefix + "0"), eq(providePmk ? pmk : null),
-                    eq(providePassphrase ? passphrase : null), eq(null), eq(useDirect), any());
-            mDut.onRespondToDataPathSetupRequestResponse(transactionId.getValue(), true, 0);
+        inOrder.verify(mMockNative).respondToDataPathRequest(transactionId.capture(), eq(true),
+                eq(ndpId), eq(sAwareInterfacePrefix + "0"), eq(providePmk ? pmk : null),
+                eq(providePassphrase ? passphrase : null), eq(null), eq(useDirect), any());
+        mDut.onRespondToDataPathSetupRequestResponse(transactionId.getValue(), true, 0);
+        mMockLooper.dispatchAll();
+
+        // (3) get confirmation OR timeout
+        if (getConfirmation) {
+            mDut.onDataPathConfirmNotification(ndpId, peerDataPathMac, true, 0, null, null);
             mMockLooper.dispatchAll();
-
-            // (3) get confirmation OR timeout
-            if (getConfirmation) {
-                mDut.onDataPathConfirmNotification(ndpId, peerDataPathMac, true, 0, null, null);
-                mMockLooper.dispatchAll();
-                inOrder.verify(mMockNetdWrapper).setInterfaceUp(anyString());
-                inOrder.verify(mMockNetdWrapper).enableIpv6(anyString());
-                inOrderM.verify(mAwareMetricsMock).recordNdpStatus(eq(NanStatusType.SUCCESS),
-                        eq(useDirect), anyLong());
-                inOrderM.verify(mAwareMetricsMock).recordNdpCreation(anyInt(), any(), any());
-            } else {
-                assertTrue(mAlarmManager.dispatch(
-                        WifiAwareStateManager.HAL_DATA_PATH_CONFIRM_TIMEOUT_TAG));
-                mMockLooper.dispatchAll();
-                verifyRequestDeclaredUnfullfillable(nr);
-                inOrder.verify(mMockNative).endDataPath(transactionId.capture(), eq(ndpId));
-                mDut.onEndDataPathResponse(transactionId.getValue(), true, 0);
-                mMockLooper.dispatchAll();
-                inOrderM.verify(mAwareMetricsMock).recordNdpStatus(
-                        eq(NanStatusType.INTERNAL_FAILURE), eq(useDirect), anyLong());
-            }
-
-            // (4) end data-path (unless didn't get confirmation)
-            if (getConfirmation) {
-                Message endNetworkMsg = Message.obtain();
-                endNetworkMsg.what = NetworkFactory.CMD_CANCEL_REQUEST;
-                endNetworkMsg.obj = nr;
-                res.mMessenger.send(endNetworkMsg);
-
-            }
+            inOrder.verify(mMockNetdWrapper).setInterfaceUp(anyString());
+            inOrder.verify(mMockNetdWrapper).enableIpv6(anyString());
+            inOrder.verify(mMockNetworkInterface).setConnected(agentCaptor.capture());
+            inOrderM.verify(mAwareMetricsMock).recordNdpStatus(eq(NanStatusType.SUCCESS),
+                    eq(useDirect), anyLong());
+            inOrderM.verify(mAwareMetricsMock).recordNdpCreation(anyInt(), any(), any());
+            WifiAwareNetworkInfo netInfo =
+                    (WifiAwareNetworkInfo) agentCaptor.getValue().mDataPathCapabilities
+                            .getTransportInfo();
+            assertArrayEquals(MacAddress.fromBytes(
+                    peerDataPathMac).getLinkLocalIpv6FromEui48Mac().getAddress(),
+                    netInfo.getPeerIpv6Addr().getAddress());
+            assertEquals(0, netInfo.getPort());
+            assertEquals(-1, netInfo.getTransportProtocol());
+            assertEquals(1, mDut.mDataPathMgr.getNumOfNdps());
         } else {
-            verifyRequestDeclaredUnfullfillable(nr);
-            inOrder.verify(mMockNative).respondToDataPathRequest(transactionId.capture(), eq(false),
-                    eq(ndpId), eq(""), eq(null), eq(null), eq(null), eq(false), any());
-            mDut.onRespondToDataPathSetupRequestResponse(transactionId.getValue(), true, 0);
+            assertTrue(mAlarmManager.dispatch(
+                    WifiAwareStateManager.HAL_DATA_PATH_CONFIRM_TIMEOUT_TAG));
             mMockLooper.dispatchAll();
+            verifyRequestDeclaredUnfullfillable(nr);
+            inOrder.verify(mMockNative).endDataPath(transactionId.capture(), eq(ndpId));
+            mDut.onEndDataPathResponse(transactionId.getValue(), true, 0);
+            mMockLooper.dispatchAll();
+            inOrderM.verify(mAwareMetricsMock).recordNdpStatus(
+                    eq(NanStatusType.INTERNAL_FAILURE), eq(useDirect), anyLong());
         }
 
+        // (4) end data-path (unless didn't get confirmation)
+        if (getConfirmation) {
+            Message endNetworkMsg = Message.obtain();
+            endNetworkMsg.what = NetworkFactory.CMD_CANCEL_REQUEST;
+            endNetworkMsg.obj = nr;
+            res.mMessenger.send(endNetworkMsg);
+
+            agentCaptor.getValue().onNetworkUnwanted();
+
+            mDut.onEndDataPathResponse(transactionId.getValue(), true, 0);
+            mDut.onDataPathEndNotification(ndpId);
+            mMockLooper.dispatchAll();
+
+            inOrder.verify(mMockNative).endDataPath(transactionId.capture(), eq(ndpId));
+            inOrder.verify(mMockNetdWrapper).setInterfaceDown(anyString());
+            inOrderM.verify(mAwareMetricsMock).recordNdpSessionDuration(anyLong());
+        }
         verifyNoMoreInteractions(mMockNative, mAwareMetricsMock, mMockNetdWrapper);
     }
 
@@ -1821,7 +2013,8 @@
 
         if (startUpSequence) {
             inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                    eq(configRequest), eq(false), eq(true), eq(true), eq(false), eq(false));
+                    eq(configRequest), eq(false), eq(true), eq(true),
+                    eq(false), eq(false), eq(false));
             mDut.onConfigSuccessResponse(transactionId.getValue());
             mMockLooper.dispatchAll();
         }
@@ -1944,4 +2137,311 @@
 
         return tlvc.getArray();
     }
+
+    @Test
+    public void testAcceptRequestWhenAwareNotReadyWillReleaseRequest() throws Exception {
+        final int clientId = 123;
+        final byte[] peerDiscoveryMac = HexEncoding.decode("000102030405".toCharArray(), false);
+        ArgumentCaptor<NetworkProvider> networkProviderCaptor =
+                ArgumentCaptor.forClass(NetworkProvider.class);
+        verify(mMockCm).registerNetworkProvider(networkProviderCaptor.capture());
+        NetworkProvider awareProvider = networkProviderCaptor.getValue();
+        collector.checkThat("factory name", "WIFI_AWARE_FACTORY",
+                equalTo(awareProvider.getName()));
+        NetworkRequest networkRequest = getDirectNetworkRequest(clientId,
+                WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR, peerDiscoveryMac, null,
+                null, 1);
+        // Aware usage is not enabled, should declare unfullfillable.
+        awareProvider.onNetworkRequested(networkRequest, 0, awareProvider.getProviderId());
+        verifyRequestDeclaredUnfullfillable(networkRequest);
+        reset(mMockCm);
+        // Aware usage is enabled but interface not ready, should declare unfullfillable.
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        awareProvider.onNetworkRequested(networkRequest, 0, awareProvider.getProviderId());
+        verifyRequestDeclaredUnfullfillable(networkRequest);
+    }
+    private void testDataPathAcceptsAnyResponderWithMultipleInitiator(boolean providePmk,
+            boolean providePassphrase, boolean failureOnFirst)
+            throws Exception {
+        final int clientId = 123;
+        final byte pubSubId = 60;
+        final int requestorId = 1341234;
+        final byte[] pmk = "01234567890123456789012345678901".getBytes();
+        final String passphrase = "some passphrase";
+        final byte[] peerDiscoveryMac = HexEncoding.decode("000102030405".toCharArray(), false);
+        final byte[] peerDataPathMac = HexEncoding.decode("0A0B0C0D0E0F".toCharArray(), false);
+        int indexOfFailure = failureOnFirst ? 0 : 1;
+        int ndpAttemptsCount = 4;
+        int ndpId = 2;
+        List<Integer> successNdpIds = new ArrayList<>();
+
+
+        ArgumentCaptor<WifiAwareNetworkAgent> agentCaptor =
+                ArgumentCaptor.forClass(WifiAwareNetworkAgent.class);
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        InOrder inOrder = inOrder(mMockNative, mMockCallback, mMockSessionCallback,
+                mMockNetdWrapper, mMockNetworkInterface, mMockCm);
+        InOrder inOrderM = inOrder(mAwareMetricsMock);
+
+        if (providePmk) {
+            when(mPermissionsWrapperMock.getUidPermission(
+                    eq(Manifest.permission.NETWORK_STACK), eq(Process.myUid()))).thenReturn(
+                    PackageManager.PERMISSION_GRANTED);
+        }
+
+        // (0) initialize
+        DataPathEndPointInfo res = initDataPathEndPoint(true, clientId, pubSubId, requestorId,
+                peerDiscoveryMac, inOrder, inOrderM, true);
+
+        // (1) request network
+        NetworkRequest nr = getSessionNetworkRequest(clientId, res.mSessionId,
+                null, providePmk ? pmk : null,
+                providePassphrase ? passphrase : null, true, 0);
+
+        Message reqNetworkMsg = Message.obtain();
+        reqNetworkMsg.what = NetworkProvider.CMD_REQUEST_NETWORK;
+        reqNetworkMsg.obj = nr;
+        reqNetworkMsg.arg1 = 0;
+        res.mMessenger.send(reqNetworkMsg);
+        mMockLooper.dispatchAll();
+        inOrderM.verify(mAwareMetricsMock).recordNdpRequestType(
+                WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB_ANY_PEER);
+        boolean firstSuccess = true;
+
+        for (int i = 0; i < ndpAttemptsCount; i++) {
+            // (2) get request & respond
+            peerDataPathMac[5] += i;
+            mDut.onDataPathRequestNotification(pubSubId, peerDiscoveryMac, ndpId, null);
+            mMockLooper.dispatchAll();
+            inOrder.verify(mMockNative).respondToDataPathRequest(transactionId.capture(), eq(true),
+                    eq(ndpId), eq(sAwareInterfacePrefix + "0"), eq(providePmk ? pmk : null),
+                    eq(providePassphrase ? passphrase : null), eq(null), eq(false), any());
+            mDut.onRespondToDataPathSetupRequestResponse(transactionId.getValue(), true, 0);
+            mMockLooper.dispatchAll();
+
+            // (3) get confirmation OR timeout
+            if (i != indexOfFailure) {
+                successNdpIds.add(ndpId);
+                mDut.onDataPathConfirmNotification(ndpId, peerDataPathMac, true, 0, null, null);
+                mMockLooper.dispatchAll();
+                if (firstSuccess) {
+                    inOrder.verify(mMockNetdWrapper).setInterfaceUp(anyString());
+                    inOrder.verify(mMockNetdWrapper).enableIpv6(anyString());
+                    inOrder.verify(mMockNetworkInterface).setConnected(agentCaptor.capture());
+                    WifiAwareNetworkInfo netInfo =
+                            (WifiAwareNetworkInfo) agentCaptor.getValue().mDataPathCapabilities
+                                    .getTransportInfo();
+                    assertArrayEquals(MacAddress.fromBytes(
+                            peerDataPathMac).getLinkLocalIpv6FromEui48Mac().getAddress(),
+                            netInfo.getPeerIpv6Addr().getAddress());
+                    assertEquals(0, netInfo.getPort());
+                    assertEquals(-1, netInfo.getTransportProtocol());
+                    assertEquals(1, mDut.mDataPathMgr.getNumOfNdps());
+                }
+                inOrderM.verify(mAwareMetricsMock).recordNdpStatus(eq(NanStatusType.SUCCESS),
+                        eq(false), anyLong());
+                inOrderM.verify(mAwareMetricsMock).recordNdpCreation(anyInt(), any(), any());
+                assertEquals(successNdpIds.size(), mDut.mDataPathMgr.getNumOfNdps());
+
+                firstSuccess = false;
+            } else {
+                assertTrue(mAlarmManager.dispatch(
+                        WifiAwareStateManager.HAL_DATA_PATH_CONFIRM_TIMEOUT_TAG));
+                mMockLooper.dispatchAll();
+                inOrder.verify(mMockNative).endDataPath(transactionId.capture(), eq(ndpId));
+                inOrderM.verify(mAwareMetricsMock).recordNdpStatus(
+                        eq(NanStatusType.INTERNAL_FAILURE), eq(false), anyLong());
+                mDut.onDataPathEndNotification(ndpId);
+                mDut.onEndDataPathResponse(transactionId.getValue(), true, 0);
+                verify(mMockCm, never()).declareNetworkRequestUnfulfillable(any());
+            }
+            ndpId++;
+        }
+
+        // (4) one of the NDP is terminated by the other side
+        int endNdpId = successNdpIds.remove(0);
+        mDut.onDataPathEndNotification(endNdpId);
+        mMockLooper.dispatchAll();
+
+        inOrderM.verify(mAwareMetricsMock).recordNdpSessionDuration(anyLong());
+        assertEquals(successNdpIds.size(), mDut.mDataPathMgr.getNumOfNdps());
+
+
+        // (5) end data-path (unless didn't get confirmation)
+        Message endNetworkMsg = Message.obtain();
+        endNetworkMsg.what = NetworkFactory.CMD_CANCEL_REQUEST;
+        endNetworkMsg.obj = nr;
+        res.mMessenger.send(endNetworkMsg);
+        agentCaptor.getValue().onNetworkUnwanted();
+
+        for (int successNdpId : successNdpIds) {
+            mDut.onEndDataPathResponse(transactionId.getValue(), true, 0);
+            mDut.onDataPathEndNotification(successNdpId);
+            mMockLooper.dispatchAll();
+            inOrder.verify(mMockNative).endDataPath(transactionId.capture(), eq(successNdpId));
+            inOrderM.verify(mAwareMetricsMock).recordNdpSessionDuration(anyLong());
+        }
+        inOrder.verify(mMockNetdWrapper).setInterfaceDown(anyString());
+        verifyNoMoreInteractions(mMockNative, mAwareMetricsMock, mMockNetdWrapper);
+    }
+
+    @Test
+    public void testAcceptAnyResponderWithMultipleInitiatorRequestWithTimeOutAtFirstRequest()
+            throws Exception {
+        testDataPathAcceptsAnyResponderWithMultipleInitiator(false, true, true);
+    }
+
+    @Test
+    public void testAcceptAnyResponderWithMultipleInitiatorRequestWithTimeOutAtFollowingRequest()
+            throws Exception {
+        testDataPathAcceptsAnyResponderWithMultipleInitiator(false, true, false);
+    }
+
+    /**
+     * Validate when multiple request present on a device, request from peer can match to the right
+     * accepts any peer request when no peer specific request matches.
+     */
+    @Test
+    public void testAcceptsAnyRequestMatchesCorrectlyWhenMultipleRequestPresent() throws Exception {
+        final int clientId = 123;
+        final byte pubId = 1;
+        final byte subId = -128;
+        final int requestorId = 1341234;
+        final String passphrase = "SomeSecurePassword";
+        final String passphrase1 = "SomeSecurePassword1";
+        final int ndpId = 1;
+        final int ndpId2 = 2;
+        final byte[] peerDiscoveryMac = HexEncoding.decode("000102030405".toCharArray(), false);
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<String> interfaceName1 = ArgumentCaptor.forClass(String.class);
+        ArgumentCaptor<String> interfaceName2 = ArgumentCaptor.forClass(String.class);
+
+        InOrder inOrder = inOrder(mMockNative, mMockCm, mMockCallback, mMockSessionCallback);
+        InOrder inOrderM = inOrder(mAwareMetricsMock);
+
+        Messenger messenger = initOobDataPathEndPoint(true, 2, clientId, inOrder, inOrderM);
+
+        // (0) initialize Publish
+        DataPathEndPointInfo pubRes = initDataPathEndPoint(false, clientId, pubId, requestorId,
+                peerDiscoveryMac, inOrder, inOrderM, true);
+
+        // (1) request responder network
+        NetworkRequest pubNr = getSessionNetworkRequest(clientId, pubRes.mSessionId, null,
+                null, passphrase, true, requestorId);
+
+        Message reqNetworkMsg = Message.obtain();
+        reqNetworkMsg.what = NetworkProvider.CMD_REQUEST_NETWORK;
+        reqNetworkMsg.obj = pubNr;
+        reqNetworkMsg.arg1 = 0;
+        messenger.send(reqNetworkMsg);
+        mMockLooper.dispatchAll();
+        inOrderM.verify(mAwareMetricsMock).recordNdpRequestType(anyInt());
+
+        // (2) initialize Subscribe
+        DataPathEndPointInfo subRes = initDataPathEndPoint(false, clientId, subId, requestorId,
+                peerDiscoveryMac, inOrder, inOrderM, false);
+
+        // (3) request initiator network
+        NetworkRequest subNr = getSessionNetworkRequest(clientId, subRes.mSessionId,
+                subRes.mPeerHandle, null, passphrase1, false, requestorId);
+
+        Message subReqNetworkMsg = Message.obtain();
+        subReqNetworkMsg.what = NetworkProvider.CMD_REQUEST_NETWORK;
+        subReqNetworkMsg.obj = subNr;
+        subReqNetworkMsg.arg1 = 0;
+        messenger.send(subReqNetworkMsg);
+        mMockLooper.dispatchAll();
+        inOrderM.verify(mAwareMetricsMock).recordNdpRequestType(anyInt());
+
+        // (4) Initiator request succeed
+        verify(mMockNative).initiateDataPath(transactionId.capture(), anyInt(), anyInt(), anyInt(),
+                any(), interfaceName1.capture(), any(), anyString(), anyBoolean(), any(), any());
+        mDut.onInitiateDataPathResponseSuccess(transactionId.getValue(), ndpId);
+
+        // (5) provide a request from peer
+        mDut.onDataPathRequestNotification(pubId, peerDiscoveryMac, ndpId2, null);
+        mMockLooper.dispatchAll();
+
+        // (6) make sure framework respond with the right accepts any peer request.
+        verify(mMockNative).respondToDataPathRequest(anyShort(), eq(true), eq(ndpId2),
+                interfaceName2.capture(), eq(null), eq(passphrase), any(), anyBoolean(), any());
+
+        assertNotEquals(interfaceName1.getValue(), interfaceName2.getValue());
+    }
+
+    /**
+     * Validate when both peer specific and accepts any peer requests are on the device, framework
+     * will response to the matched peer with peer specific request. Other peers with accepts any
+     * request.
+     */
+    @Test
+    public void testPeerSpecificRequestMatchesCorrectlyWhenAcceptsAnyRequestExist()
+            throws Exception {
+        final int clientId = 123;
+        final byte pubId = 1;
+        final int requestorId = 1341234;
+        final String passphrase = "SomeSecurePassword";
+        final String passphrase1 = "SomeSecurePassword1";
+        final int ndpId = 1;
+        final int ndpId2 = 2;
+        final byte[] peerDiscoveryMac = HexEncoding.decode("000102030405".toCharArray(), false);
+        final byte[] peerDiscoveryMac1 = HexEncoding.decode("000102030406".toCharArray(), false);
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<String> interfaceName1 = ArgumentCaptor.forClass(String.class);
+        ArgumentCaptor<String> interfaceName2 = ArgumentCaptor.forClass(String.class);
+
+        InOrder inOrder = inOrder(mMockNative, mMockCm, mMockCallback, mMockSessionCallback);
+        InOrder inOrderM = inOrder(mAwareMetricsMock);
+
+        Messenger messenger = initOobDataPathEndPoint(true, 2, clientId, inOrder, inOrderM);
+
+        // (0) initialize Publish
+        DataPathEndPointInfo pubRes = initDataPathEndPoint(false, clientId, pubId, requestorId,
+                peerDiscoveryMac, inOrder, inOrderM, true);
+
+        // (1) request accepts any responder network
+        NetworkRequest pubNr = getSessionNetworkRequest(clientId, pubRes.mSessionId, null,
+                null, passphrase, true, requestorId);
+
+        Message reqNetworkMsg = Message.obtain();
+        reqNetworkMsg.what = NetworkProvider.CMD_REQUEST_NETWORK;
+        reqNetworkMsg.obj = pubNr;
+        reqNetworkMsg.arg1 = 0;
+        messenger.send(reqNetworkMsg);
+        mMockLooper.dispatchAll();
+        inOrderM.verify(mAwareMetricsMock).recordNdpRequestType(anyInt());
+
+        // (2) request peer specific responder network
+        NetworkRequest subNr = getSessionNetworkRequest(clientId, pubRes.mSessionId,
+                pubRes.mPeerHandle, null, passphrase1, true, requestorId);
+
+        Message subReqNetworkMsg = Message.obtain();
+        subReqNetworkMsg.what = NetworkProvider.CMD_REQUEST_NETWORK;
+        subReqNetworkMsg.obj = subNr;
+        subReqNetworkMsg.arg1 = 0;
+        messenger.send(subReqNetworkMsg);
+        mMockLooper.dispatchAll();
+        inOrderM.verify(mAwareMetricsMock).recordNdpRequestType(anyInt());
+
+        // (3) provide a request from specified peer
+        mDut.onDataPathRequestNotification(pubId, peerDiscoveryMac, ndpId, null);
+        mMockLooper.dispatchAll();
+
+        // (4) make sure framework respond with the peer specific request.
+        verify(mMockNative).respondToDataPathRequest(transactionId.capture(), eq(true), eq(ndpId),
+                interfaceName1.capture(), eq(null), eq(passphrase1), any(), anyBoolean(), any());
+        mDut.onRespondToDataPathSetupRequestResponse(transactionId.getValue(), true, 0);
+        mMockLooper.dispatchAll();
+
+        // (5) provide a request from a not specified peer.
+        mDut.onDataPathRequestNotification(pubId, peerDiscoveryMac1, ndpId2, null);
+        mMockLooper.dispatchAll();
+
+        // (6) make sure framework respond with the right accepts any peer request.
+        verify(mMockNative).respondToDataPathRequest(anyShort(), eq(true), eq(ndpId2),
+                interfaceName2.capture(), eq(null), eq(passphrase), any(), anyBoolean(), any());
+
+        assertNotEquals(interfaceName1.getValue(), interfaceName2.getValue());
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareMetricsTest.java b/service/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareMetricsTest.java
index 824a3e5..f271f77 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareMetricsTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareMetricsTest.java
@@ -16,6 +16,9 @@
 
 package com.android.server.wifi.aware;
 
+import static android.net.wifi.aware.WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB;
+import static android.net.wifi.aware.WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB_ANY_PEER;
+
 import static com.android.server.wifi.aware.WifiAwareMetrics.addNanHalStatusToHistogram;
 import static com.android.server.wifi.aware.WifiAwareMetrics.histogramToProtoArray;
 
@@ -629,6 +632,21 @@
                 WifiMetricsProto.WifiAwareLog.UNKNOWN_HAL_STATUS, 3);
     }
 
+    @Test
+    public void testNdpRequestTypeHistogram() {
+        mDut.recordNdpRequestType(NETWORK_SPECIFIER_TYPE_IB);
+        mDut.recordNdpRequestType(NETWORK_SPECIFIER_TYPE_IB);
+        mDut.recordNdpRequestType(NETWORK_SPECIFIER_TYPE_IB_ANY_PEER);
+
+        WifiMetricsProto.WifiAwareLog log;
+        log = mDut.consolidateProto();
+
+        validateNdpRequestProtoHistBucket("", log.histogramNdpRequestType[0],
+                WifiMetricsProto.WifiAwareLog.NETWORK_SPECIFIER_TYPE_IB, 2);
+        validateNdpRequestProtoHistBucket("", log.histogramNdpRequestType[1],
+                WifiMetricsProto.WifiAwareLog.NETWORK_SPECIFIER_TYPE_IB_ANY_PEER, 1);
+    }
+
     // utilities
 
     /**
@@ -671,6 +689,13 @@
         collector.checkThat(logPrefix + ": count", bucket.count, equalTo(count));
     }
 
+    private void validateNdpRequestProtoHistBucket(String logPrefix,
+            WifiMetricsProto.WifiAwareLog.NdpRequestTypeHistogramBucket bucket, int type,
+            int count) {
+        collector.checkThat(logPrefix + ": type", bucket.ndpRequestType, equalTo(type));
+        collector.checkThat(logPrefix + ": count", bucket.count, equalTo(count));
+    }
+
     private WifiAwareNetworkSpecifier addNetworkInfoToCache(
             Map<WifiAwareNetworkSpecifier, WifiAwareDataPathStateManager
                     .AwareNetworkRequestInformation> networkRequestCache,
diff --git a/service/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareNativeApiTest.java b/service/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareNativeApiTest.java
index 08eae1d..f2d4d0e 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareNativeApiTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareNativeApiTest.java
@@ -20,6 +20,7 @@
 import static android.hardware.wifi.V1_0.NanCipherSuiteType.SHARED_KEY_256_MASK;
 
 import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyShort;
 import static org.mockito.ArgumentMatchers.eq;
@@ -553,6 +554,20 @@
                 /* expectedCipherSuite */ 0);
     }
 
+    /**
+     * Validate disable Aware will pass to the NAN interface, and trigger releaseAware.
+     * @throws Exception
+     */
+    @Test
+    public void testDisableConfigRequest() throws Exception {
+        WifiStatus status = new WifiStatus();
+        status.code = WifiStatusCode.SUCCESS;
+        when(mIWifiNanIfaceMock.disableRequest(anyShort())).thenReturn(status);
+        assertTrue(mDut.disable((short) 10));
+        verify(mIWifiNanIfaceMock).disableRequest((short) 10);
+        verify(mWifiAwareNativeManagerMock).releaseAware();
+    }
+
     // utilities
 
     private void setPowerConfigurationParams(byte interactive5, byte interactive24, byte idle5,
@@ -595,7 +610,7 @@
         mIsInterface12 = isHal12;
 
         mDut.enableAndConfigure(transactionId, configRequest, notifyIdentityChange,
-                initialConfiguration, isInteractive, isIdle, false);
+                initialConfiguration, isInteractive, isIdle, false, false);
 
         ArgumentCaptor<NanEnableRequest> enableReqCaptor = ArgumentCaptor.forClass(
                 NanEnableRequest.class);
diff --git a/service/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareNativeManagerTest.java b/service/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareNativeManagerTest.java
index d97ae06..4019ecd 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareNativeManagerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareNativeManagerTest.java
@@ -18,7 +18,6 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.inOrder;
@@ -27,10 +26,10 @@
 import static org.mockito.Mockito.when;
 
 import android.hardware.wifi.V1_0.IWifiNanIface;
-import android.hardware.wifi.V1_0.IfaceType;
 import android.hardware.wifi.V1_0.WifiStatus;
 import android.hardware.wifi.V1_0.WifiStatusCode;
 import android.os.Handler;
+import android.os.WorkSource;
 
 import androidx.test.filters.SmallTest;
 
@@ -51,21 +50,21 @@
  */
 @SmallTest
 public class WifiAwareNativeManagerTest extends WifiBaseTest {
+    private static final WorkSource TEST_WS = new WorkSource();
+
     private WifiAwareNativeManager mDut;
     @Mock private WifiAwareStateManager mWifiAwareStateManagerMock;
     @Mock private HalDeviceManager mHalDeviceManager;
     @Mock private WifiAwareNativeCallback mWifiAwareNativeCallback;
     @Mock private IWifiNanIface mWifiNanIfaceMock;
     @Mock android.hardware.wifi.V1_2.IWifiNanIface mIWifiNanIface12Mock;
+    @Mock android.hardware.wifi.V1_5.IWifiNanIface mIWifiNanIface15Mock;
     @Mock private Handler mHandlerMock;
     private ArgumentCaptor<HalDeviceManager.ManagerStatusListener> mManagerStatusListenerCaptor =
             ArgumentCaptor.forClass(HalDeviceManager.ManagerStatusListener.class);
     private ArgumentCaptor<HalDeviceManager.InterfaceDestroyedListener>
             mDestroyedListenerCaptor = ArgumentCaptor.forClass(
             HalDeviceManager.InterfaceDestroyedListener.class);
-    private ArgumentCaptor<HalDeviceManager.InterfaceAvailableForRequestListener>
-            mAvailListenerCaptor = ArgumentCaptor.forClass(
-            HalDeviceManager.InterfaceAvailableForRequestListener.class);
     private InOrder mInOrder;
     @Rule public ErrorCollector collector = new ErrorCollector();
 
@@ -82,6 +81,11 @@
         public android.hardware.wifi.V1_2.IWifiNanIface mockableCastTo_1_2(IWifiNanIface iface) {
             return (iface == mIWifiNanIface12Mock) ? mIWifiNanIface12Mock : null;
         }
+
+        @Override
+        public android.hardware.wifi.V1_5.IWifiNanIface mockableCastTo_1_5(IWifiNanIface iface) {
+            return (iface == mIWifiNanIface15Mock) ? mIWifiNanIface15Mock : null;
+        }
     }
 
     @Before
@@ -92,14 +96,14 @@
         mStatusOk.code = WifiStatusCode.SUCCESS;
 
         when(mWifiNanIfaceMock.registerEventCallback(any())).thenReturn(mStatusOk);
-        when(mIWifiNanIface12Mock.registerEventCallback_1_2(any())).thenReturn(mStatusOk);
+        when(mIWifiNanIface15Mock.registerEventCallback_1_5(any())).thenReturn(mStatusOk);
 
         mDut = new MockableWifiAwareNativeManager(mWifiAwareStateManagerMock,
                 mHalDeviceManager, mWifiAwareNativeCallback);
         mDut.start(mHandlerMock);
 
         mInOrder = inOrder(mWifiAwareStateManagerMock, mHalDeviceManager, mWifiNanIfaceMock,
-                mIWifiNanIface12Mock);
+                mIWifiNanIface15Mock);
 
         // validate (and capture) that register manage status callback
         mInOrder.verify(mHalDeviceManager).initialize();
@@ -123,42 +127,26 @@
         // configure HalDeviceManager as ready/wifi started (and to return an interface if
         // requested)
         when(mHalDeviceManager.isStarted()).thenReturn(true);
-        when(mHalDeviceManager.createNanIface(any(), any())).thenReturn(mWifiNanIfaceMock);
+        when(mHalDeviceManager.createNanIface(any(), any(), any())).thenReturn(mWifiNanIfaceMock);
 
         // 1. onStatusChange (ready/started)
         mManagerStatusListenerCaptor.getValue().onStatusChanged();
-        mInOrder.verify(mHalDeviceManager).registerInterfaceAvailableForRequestListener(
-                eq(IfaceType.NAN), mAvailListenerCaptor.capture(), any(Handler.class));
+        mInOrder.verify(mWifiAwareStateManagerMock).tryToGetAwareCapability();
 
-        // 2. NAN is available -> enableUsage
-        mAvailListenerCaptor.getValue().onAvailabilityChanged(true);
-        mInOrder.verify(mWifiAwareStateManagerMock).enableUsage();
-
-        // 3. onStatusChange (not ready) -> disableUsage
+        // 2. onStatusChange (not ready) -> disableUsage
         when(mHalDeviceManager.isStarted()).thenReturn(false);
         mManagerStatusListenerCaptor.getValue().onStatusChanged();
 
-        mInOrder.verify(mWifiAwareStateManagerMock).disableUsage();
+        mInOrder.verify(mWifiAwareStateManagerMock).disableUsage(true);
 
-        // 4. onStatusChange (ready/started) + available -> enableUsage
+        // 3. onStatusChange (ready/started) + available -> enableUsage
         when(mHalDeviceManager.isStarted()).thenReturn(true);
         mManagerStatusListenerCaptor.getValue().onStatusChanged();
+        mInOrder.verify(mWifiAwareStateManagerMock).tryToGetAwareCapability();
 
-        mManagerStatusListenerCaptor.getValue().onStatusChanged();
-        mInOrder.verify(mHalDeviceManager).registerInterfaceAvailableForRequestListener(
-                eq(IfaceType.NAN), mAvailListenerCaptor.capture(), any(Handler.class));
-        mAvailListenerCaptor.getValue().onAvailabilityChanged(true);
-
-        mInOrder.verify(mWifiAwareStateManagerMock).enableUsage();
-
-        // 5. not available -> disableUsage
-        mAvailListenerCaptor.getValue().onAvailabilityChanged(false);
-
-        mInOrder.verify(mWifiAwareStateManagerMock).disableUsage();
-
-        mInOrder.verify(mHalDeviceManager, never()).createNanIface(any(), any());
+        mInOrder.verify(mHalDeviceManager, never()).createNanIface(any(), any(), any());
         verifyNoMoreInteractions(mWifiAwareStateManagerMock, mWifiNanIfaceMock,
-                mIWifiNanIface12Mock);
+                mIWifiNanIface15Mock);
         assertNull("Interface non-null!", mDut.getWifiNanIface());
     }
 
@@ -171,21 +159,17 @@
         // configure HalDeviceManager as ready/wifi started (and to return an interface if
         // requested)
         when(mHalDeviceManager.isStarted()).thenReturn(true);
-        when(mHalDeviceManager.createNanIface(any(), any())).thenReturn(mWifiNanIfaceMock);
+        when(mHalDeviceManager.createNanIface(any(), any(), any())).thenReturn(mWifiNanIfaceMock);
 
         // 1. onStatusChange (ready/started)
         mManagerStatusListenerCaptor.getValue().onStatusChanged();
-        mInOrder.verify(mHalDeviceManager).registerInterfaceAvailableForRequestListener(
-                eq(IfaceType.NAN), mAvailListenerCaptor.capture(), any(Handler.class));
+        mInOrder.verify(mWifiAwareStateManagerMock).tryToGetAwareCapability();
         assertNull("Interface non-null!", mDut.getWifiNanIface());
 
-        mAvailListenerCaptor.getValue().onAvailabilityChanged(true);
-        mInOrder.verify(mWifiAwareStateManagerMock).enableUsage();
-
         // 2. request (interface obtained)
-        mDut.tryToGetAware();
+        mDut.tryToGetAware(TEST_WS);
         mInOrder.verify(mHalDeviceManager).createNanIface(mDestroyedListenerCaptor.capture(),
-                any());
+                any(), eq(TEST_WS));
         mInOrder.verify(mWifiNanIfaceMock).registerEventCallback(any());
         assertEquals("Interface mismatch", mWifiNanIfaceMock, mDut.getWifiNanIface());
 
@@ -197,14 +181,14 @@
         mDestroyedListenerCaptor.getValue().onDestroyed("nan0");
 
         // 4. request (interface obtained)
-        mDut.tryToGetAware();
+        mDut.tryToGetAware(TEST_WS);
         mInOrder.verify(mHalDeviceManager).createNanIface(mDestroyedListenerCaptor.capture(),
-                any());
+                any(), eq(TEST_WS));
         mInOrder.verify(mWifiNanIfaceMock).registerEventCallback(any());
         assertEquals("Interface mismatch", mWifiNanIfaceMock, mDut.getWifiNanIface());
 
         // 5. request (nop - already have interface)
-        mDut.tryToGetAware();
+        mDut.tryToGetAware(TEST_WS);
         assertEquals("Interface mismatch", mWifiNanIfaceMock, mDut.getWifiNanIface());
 
         // 6. release (nop - reference counting requests)
@@ -218,10 +202,10 @@
 
         mDestroyedListenerCaptor.getValue().onDestroyed("nan0");
 
-        mInOrder.verify(mHalDeviceManager, never()).createNanIface(any(), any());
+        mInOrder.verify(mHalDeviceManager, never()).createNanIface(any(), any(), any());
         mInOrder.verify(mHalDeviceManager, never()).removeIface(any());
         verifyNoMoreInteractions(mWifiAwareStateManagerMock, mWifiNanIfaceMock,
-                mIWifiNanIface12Mock);
+                mIWifiNanIface15Mock);
     }
 
     /**
@@ -232,68 +216,57 @@
         // configure HalDeviceManager as ready/wifi started (and to return an interface if
         // requested)
         when(mHalDeviceManager.isStarted()).thenReturn(true);
-        when(mHalDeviceManager.createNanIface(any(), any())).thenReturn(mWifiNanIfaceMock);
+        when(mHalDeviceManager.createNanIface(any(), any(), any())).thenReturn(mWifiNanIfaceMock);
 
         // 1. onStatusChange (ready/started)
         mManagerStatusListenerCaptor.getValue().onStatusChanged();
-        mInOrder.verify(mHalDeviceManager).registerInterfaceAvailableForRequestListener(
-                eq(IfaceType.NAN), mAvailListenerCaptor.capture(), any(Handler.class));
+        mInOrder.verify(mWifiAwareStateManagerMock).tryToGetAwareCapability();
         assertNull("Interface non-null!", mDut.getWifiNanIface());
 
-        mAvailListenerCaptor.getValue().onAvailabilityChanged(true);
-        mInOrder.verify(mWifiAwareStateManagerMock).enableUsage();
-
         // 2. request (interface obtained)
-        mDut.tryToGetAware();
+        mDut.tryToGetAware(TEST_WS);
         mInOrder.verify(mHalDeviceManager).createNanIface(mDestroyedListenerCaptor.capture(),
-                any());
+                any(), eq(TEST_WS));
         mInOrder.verify(mWifiNanIfaceMock).registerEventCallback(any());
         assertEquals("Interface mismatch", mWifiNanIfaceMock, mDut.getWifiNanIface());
 
         // 3. interface gets destroyed
         mDestroyedListenerCaptor.getValue().onDestroyed("nan0");
 
-        mInOrder.verify(mWifiAwareStateManagerMock).disableUsage();
+        mInOrder.verify(mWifiAwareStateManagerMock).disableUsage(true);
         assertNull("Interface non-null!", mDut.getWifiNanIface());
 
         // 4. a release doesn't do much
         mDut.releaseAware();
 
-        mInOrder.verify(mHalDeviceManager, never()).createNanIface(any(), any());
+        mInOrder.verify(mHalDeviceManager, never()).createNanIface(any(), any(), any());
         mInOrder.verify(mHalDeviceManager, never()).removeIface(any());
         verifyNoMoreInteractions(mWifiAwareStateManagerMock, mWifiNanIfaceMock,
-                mIWifiNanIface12Mock);
+                mIWifiNanIface15Mock);
     }
 
     /**
-     * Test the basic control flow for HAL 1.2 - validate that the correct event registration
+     * Test the basic control flow for HAL 1.5 - validate that the correct event registration
      * occurs.
      */
     @Test
-    public void testBasicFlowHal12() throws Exception {
+    public void testBasicFlowHal15() throws Exception {
         // configure HalDeviceManager as ready/wifi started (and to return an interface if
         // requested)
         when(mHalDeviceManager.isStarted()).thenReturn(true);
-        when(mHalDeviceManager.createNanIface(any(), any())).thenReturn(mIWifiNanIface12Mock);
+        when(mHalDeviceManager.createNanIface(any(), any(), any()))
+                .thenReturn(mIWifiNanIface15Mock);
 
         // 1. onStatusChange (ready/started)
         mManagerStatusListenerCaptor.getValue().onStatusChanged();
-        mInOrder.verify(mHalDeviceManager).registerInterfaceAvailableForRequestListener(
-                eq(IfaceType.NAN), mAvailListenerCaptor.capture(), any(Handler.class));
+        mInOrder.verify(mWifiAwareStateManagerMock).tryToGetAwareCapability();
         assertNull("Interface non-null!", mDut.getWifiNanIface());
 
-        mAvailListenerCaptor.getValue().onAvailabilityChanged(true);
-        mInOrder.verify(mWifiAwareStateManagerMock).enableUsage();
-
         // 2. request (interface obtained)
-        mDut.tryToGetAware();
+        mDut.tryToGetAware(TEST_WS);
         mInOrder.verify(mHalDeviceManager).createNanIface(mDestroyedListenerCaptor.capture(),
-                any());
-        mInOrder.verify(mIWifiNanIface12Mock).registerEventCallback_1_2(any());
-        assertEquals("Interface mismatch", mIWifiNanIface12Mock, mDut.getWifiNanIface());
-
-        // 3. receive Availability Change, has interface, should ignore
-        mAvailListenerCaptor.getValue().onAvailabilityChanged(false);
-        assertTrue("AwareNativeAvailable mismatch ", mDut.isAwareNativeAvailable());
+                any(), eq(TEST_WS));
+        mInOrder.verify(mIWifiNanIface15Mock).registerEventCallback_1_5(any());
+        assertEquals("Interface mismatch", mIWifiNanIface15Mock, mDut.getWifiNanIface());
     }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareServiceImplTest.java b/service/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareServiceImplTest.java
index c6c83ba..0f2ce17 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareServiceImplTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareServiceImplTest.java
@@ -19,6 +19,7 @@
 import static com.android.server.wifi.WifiSettingsConfigStore.WIFI_VERBOSE_LOGGING_ENABLED;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -27,6 +28,7 @@
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -51,6 +53,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.WifiBaseTest;
 import com.android.server.wifi.WifiSettingsConfigStore;
 import com.android.server.wifi.util.NetdWrapper;
@@ -170,6 +173,31 @@
         verify(mAwareStateManagerMock).isUsageEnabled();
     }
 
+    @Test
+    public void testGetAwareResources() {
+        mDut.getAvailableAwareResources();
+        verify(mAwareStateManagerMock).getAvailableAwareResources();
+    }
+
+    /**
+     * Validate enableInstantCommunicationMode() and isInstantCommunicationModeEnabled() function
+     */
+    @Test
+    public void testInstantCommunicationMode() {
+        mDut.isInstantCommunicationModeEnabled();
+        verify(mAwareStateManagerMock).isInstantCommunicationModeEnabled();
+
+        // Non-system package could not enable this mode.
+        when(mWifiPermissionsUtil.isSystem(anyString(), anyInt())).thenReturn(false);
+        mDut.enableInstantCommunicationMode(mPackageName, true);
+        verify(mAwareStateManagerMock, never()).enableInstantCommunicationMode(eq(true));
+
+        when(mWifiPermissionsUtil.isSystem(anyString(), anyInt())).thenReturn(true);
+        mDut.enableInstantCommunicationMode(mPackageName, true);
+        verify(mAwareStateManagerMock).enableInstantCommunicationMode(eq(true));
+
+    }
+
 
     /**
      * Validate connect() - returns and uses a client ID.
@@ -187,12 +215,13 @@
         ConfigRequest configRequest = new ConfigRequest.Builder().setMasterPreference(55).build();
         String callingPackage = "com.google.somePackage";
         String callingFeatureId = "com.google.someFeature";
-
+        assertFalse(mDut.isDeviceAttached());
         mDut.connect(mBinderMock, callingPackage, callingFeatureId, mCallbackMock,
                 configRequest, false);
 
         verify(mAwareStateManagerMock).connect(anyInt(), anyInt(), anyInt(), eq(callingPackage),
                 eq(callingFeatureId), eq(mCallbackMock), eq(configRequest), eq(false));
+        assertTrue(mDut.isDeviceAttached());
     }
 
     /**
@@ -203,11 +232,12 @@
     @Test
     public void testDisconnect() throws Exception {
         int clientId = doConnect();
-
+        assertTrue(mDut.isDeviceAttached());
         mDut.disconnect(clientId, mBinderMock);
 
         verify(mAwareStateManagerMock).disconnect(clientId);
         validateInternalStateCleanedUp(clientId);
+        assertFalse(mDut.isDeviceAttached());
     }
 
     /**
@@ -584,7 +614,7 @@
     public void testRequestMacAddress() {
         int uid = 1005;
         List<Integer> list = new ArrayList<>();
-        IWifiAwareMacAddressProvider callback = new IWifiAwareMacAddressProvider() { // dummy
+        IWifiAwareMacAddressProvider callback = new IWifiAwareMacAddressProvider() { // placeholder
             @Override
             public void macAddress(Map peerIdToMacMap) throws RemoteException {
                 // empty
@@ -638,6 +668,7 @@
         cap.maxAppInfoLen = 255;
         cap.maxQueuedTransmitMessages = 6;
         cap.supportedCipherSuites = NanCipherSuiteType.SHARED_KEY_256_MASK;
+        cap.isInstantCommunicationModeSupported = true;
 
         Characteristics characteristics = cap.toPublicCharacteristics();
         assertEquals(characteristics.getMaxServiceNameLength(), maxServiceName);
@@ -645,6 +676,9 @@
         assertEquals(characteristics.getMaxMatchFilterLength(), maxMatchFilter);
         assertEquals(characteristics.getSupportedCipherSuites(),
                 Characteristics.WIFI_AWARE_CIPHER_SUITE_NCS_SK_256);
+        if (SdkLevel.isAtLeastS()) {
+            assertEquals(characteristics.isInstantCommunicationModeSupported(), true);
+        }
     }
 
     /*
@@ -726,6 +760,7 @@
         cap.maxAppInfoLen = 255;
         cap.maxQueuedTransmitMessages = 6;
         cap.supportedCipherSuites = NanCipherSuiteType.SHARED_KEY_256_MASK;
+        cap.isInstantCommunicationModeSupported = false;
         return cap.toPublicCharacteristics();
     }
 
diff --git a/service/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareStateManagerTest.java b/service/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareStateManagerTest.java
index 5217f36..e4ddee8 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareStateManagerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareStateManagerTest.java
@@ -22,6 +22,7 @@
 import static org.hamcrest.core.IsNull.notNullValue;
 import static org.hamcrest.core.IsNull.nullValue;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -32,8 +33,10 @@
 import static org.mockito.ArgumentMatchers.anyShort;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -64,8 +67,10 @@
 import android.os.IBinder;
 import android.os.Message;
 import android.os.PowerManager;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.WorkSource;
 import android.os.test.TestLooper;
 import android.util.Log;
 import android.util.SparseArray;
@@ -162,7 +167,6 @@
         when(mMockPowerManager.isDeviceIdleMode()).thenReturn(false);
         when(mMockPowerManager.isInteractive()).thenReturn(true);
         when(mWifiPermissionsUtil.isLocationModeEnabled()).thenReturn(true);
-        when(mMockNativeManager.isAwareNativeAvailable()).thenReturn(true);
 
         ArgumentCaptor<BroadcastReceiver> bcastRxCaptor = ArgumentCaptor.forClass(
                 BroadcastReceiver.class);
@@ -179,6 +183,14 @@
         mLocationModeReceiver = bcastRxCaptor.getAllValues().get(1);
         mWifiStateChangedReceiver = bcastRxCaptor.getAllValues().get(2);
         installMocksInStateManager(mDut, mMockAwareDataPathStatemanager);
+
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        mDut.tryToGetAwareCapability();
+        mMockLooper.dispatchAll();
+        verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
+        mMockLooper.dispatchAll();
+        when(mMockAwareDataPathStatemanager.getNumOfNdps()).thenReturn(1);
     }
 
     /**
@@ -256,21 +268,24 @@
         ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
         ArgumentCaptor<Integer> peerIdCaptor = ArgumentCaptor.forClass(Integer.class);
         InOrder inOrder = inOrder(mockCallback1, mockCallback2, mockSessionCallback1,
-                mockSessionCallback2, mMockNative);
+                mockSessionCallback2, mMockNative, mMockNativeManager);
+
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNativeManager).start(any(Handler.class));
+        inOrder.verify(mMockNativeManager).tryToGetAware(new WorkSource(Process.WIFI_UID));
+        inOrder.verify(mMockNativeManager).releaseAware();
 
         // (0) enable
         mDut.enableUsage();
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         // (1) connect 2 clients
         mDut.connect(clientId1, uid1, pid1, callingPackage, callingFeature, mockCallback1,
                 configRequest, false);
         mMockLooper.dispatchAll();
+        inOrder.verify(mMockNativeManager).tryToGetAware(new WorkSource(uid1, callingPackage));
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                eq(configRequest), eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(configRequest), eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback1).onConnectSuccess(clientId1);
@@ -279,6 +294,10 @@
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback2).onConnectSuccess(clientId2);
+        // merged requestorWs
+        WorkSource expectedRequestorWs = new WorkSource(uid1, callingPackage);
+        expectedRequestorWs.add(uid2, callingPackage);
+        inOrder.verify(mMockNativeManager).replaceRequestorWs(expectedRequestorWs);
 
         // (2) subscribe both clients
         mDut.subscribe(clientId1, subscribeConfig, mockSessionCallback1);
@@ -361,9 +380,6 @@
         mDut.enableUsage();
         mMockLooper.dispatchAll();
         validateCorrectAwareStatusChangeBroadcast(inOrder);
-        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
         collector.checkThat("usage enabled", mDut.isUsageEnabled(), equalTo(true));
 
         // (2) connect (enable Aware)
@@ -371,7 +387,7 @@
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -380,10 +396,10 @@
         // (3) disconnect (disable Aware)
         mDut.disconnect(clientId);
         mMockLooper.dispatchAll();
+        inOrder.verify(mMockAwareDataPathStatemanager).deleteAllInterfaces();
         inOrder.verify(mMockNative).disable(transactionId.capture());
         mDut.onDisableResponse(transactionId.getValue(), NanStatusType.SUCCESS);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockAwareDataPathStatemanager).deleteAllInterfaces();
 
         verifyNoMoreInteractions(mMockNative, mMockAwareDataPathStatemanager);
     }
@@ -409,18 +425,15 @@
         mDut.enableUsage();
         mMockLooper.dispatchAll();
         validateCorrectAwareStatusChangeBroadcast(inOrder);
-        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
         collector.checkThat("usage enabled", mDut.isUsageEnabled(), equalTo(true));
 
         // (2) disable usage and validate state
-        mDut.disableUsage();
+        mDut.disableUsage(false);
         mMockLooper.dispatchAll();
         collector.checkThat("usage disabled", mDut.isUsageEnabled(), equalTo(false));
+        validateCorrectAwareStatusChangeBroadcast(inOrder);
         inOrder.verify(mMockNative).disable(transactionId.capture());
         mDut.onDisableResponse(transactionId.getValue(), NanStatusType.SUCCESS);
-        validateCorrectAwareStatusChangeBroadcast(inOrder);
 
         // (3) try connecting and validate that get failure callback (though app should be aware of
         // non-availability through state change broadcast and/or query API)
@@ -457,9 +470,6 @@
         mMockLooper.dispatchAll();
         validateCorrectAwareStatusChangeBroadcast(inOrder);
         inOrderM.verify(mAwareMetricsMock).recordEnableUsage();
-        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         collector.checkThat("usage enabled", mDut.isUsageEnabled(), equalTo(true));
 
@@ -468,7 +478,7 @@
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -477,14 +487,14 @@
         collector.checkThat("num of clients", sparseArrayCaptor.getValue().size(), equalTo(1));
 
         // (3) disable usage & verify callbacks
-        mDut.disableUsage();
+        mDut.disableUsage(false);
         mMockLooper.dispatchAll();
         collector.checkThat("usage disabled", mDut.isUsageEnabled(), equalTo(false));
+        validateCorrectAwareStatusChangeBroadcast(inOrder);
         inOrder.verify(mMockNative).disable(transactionId.capture());
         inOrderM.verify(mAwareMetricsMock).recordAttachSessionDuration(anyLong());
         inOrderM.verify(mAwareMetricsMock).recordDisableAware();
         inOrderM.verify(mAwareMetricsMock).recordDisableUsage();
-        validateCorrectAwareStatusChangeBroadcast(inOrder);
         validateInternalClientInfoCleanedUp(clientId);
         mDut.onDisableResponse(transactionId.getValue(), NanStatusType.SUCCESS);
         mMockLooper.dispatchAll();
@@ -498,7 +508,7 @@
         inOrderM.verify(mAwareMetricsMock).recordAttachStatus(NanStatusType.INTERNAL_FAILURE);
 
         // (5) disable usage again and validate that not much happens
-        mDut.disableUsage();
+        mDut.disableUsage(false);
         mMockLooper.dispatchAll();
         collector.checkThat("usage disabled", mDut.isUsageEnabled(), equalTo(false));
 
@@ -514,7 +524,7 @@
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -542,22 +552,19 @@
         InOrder inOrder = inOrder(mMockContext, mMockNative, mockCallback);
 
         when(mMockNative.enableAndConfigure(anyShort(), any(), anyBoolean(),
-                anyBoolean(), eq(true), eq(false), eq(false))).thenReturn(false);
+                anyBoolean(), eq(true), eq(false), eq(false), eq(false))).thenReturn(false);
 
         // (1) check initial state
         mDut.enableUsage();
         mMockLooper.dispatchAll();
         validateCorrectAwareStatusChangeBroadcast(inOrder);
-        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         // (2) connect with HAL failure
         mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         inOrder.verify(mockCallback).onConnectFail(NanStatusType.INTERNAL_FAILURE);
 
         validateInternalClientInfoCleanedUp(clientId);
@@ -595,15 +602,13 @@
         mDut.enableUsage();
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).getCapabilities(transactionIdCapture.capture());
-        mDut.onCapabilitiesUpdateResponse(transactionIdCapture.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         // (1) connect 1st and 2nd clients
         mDut.connect(clientId1, uid, pid, callingPackage, callingFeature, mockCallback1,
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionIdCapture.capture(),
-                eq(configRequest), eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(configRequest), eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         short transactionId = transactionIdCapture.getValue();
         mDut.onConfigSuccessResponse(transactionId);
         mMockLooper.dispatchAll();
@@ -613,7 +618,7 @@
                 configRequest, true);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionIdCapture.capture(),
-                eq(configRequest), eq(true), eq(false), eq(true), eq(false), eq(false));
+                eq(configRequest), eq(true), eq(false), eq(true), eq(false), eq(false), eq(false));
         transactionId = transactionIdCapture.getValue();
         mDut.onConfigSuccessResponse(transactionId);
         mMockLooper.dispatchAll();
@@ -683,15 +688,13 @@
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
         inOrderM.verify(mAwareMetricsMock).recordEnableUsage();
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         // (1) connect (successfully)
         mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -749,15 +752,13 @@
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
         inOrderM.verify(mAwareMetricsMock).recordEnableUsage();
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         // (0) connect
         mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                eq(configRequest), eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(configRequest), eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -822,15 +823,13 @@
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
         inOrderM.verify(mAwareMetricsMock).recordEnableUsage();
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         // (0) connect
         mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                eq(configRequest), eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(configRequest), eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -848,6 +847,9 @@
         inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
         inOrderM.verify(mAwareMetricsMock).recordDiscoverySession(eq(uid), any());
         inOrderM.verify(mAwareMetricsMock).recordDiscoveryStatus(uid, NanStatusType.SUCCESS, true);
+        assertEquals(1, mDut.getAvailableAwareResources().getAvailablePublishSessionsCount());
+        assertEquals(2, mDut.getAvailableAwareResources().getAvailableSubscribeSessionsCount());
+        assertEquals(0, mDut.getAvailableAwareResources().getAvailableDataPathsCount());
 
         // (3) publish termination (from firmware - not app!)
         mDut.onSessionTerminatedNotification(publishId, reasonTerminate, true);
@@ -906,15 +908,13 @@
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
         inOrderM.verify(mAwareMetricsMock).recordEnableUsage();
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         // (0) connect
         mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -936,7 +936,7 @@
         mMockLooper.dispatchAll();
         // Verify reconfigure aware to enable ranging.
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(false), eq(true), eq(false), eq(true));
+                eq(false), eq(false), eq(true), eq(false), eq(true), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
 
@@ -990,7 +990,7 @@
         // Verify reconfigure aware to disable ranging.
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(false), eq(true), eq(false), eq(false));
+                eq(false), eq(false), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
 
@@ -1030,15 +1030,13 @@
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
         inOrderM.verify(mAwareMetricsMock).recordEnableUsage();
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         // (0) connect
         mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -1059,6 +1057,7 @@
         mDut.onSessionConfigSuccessResponse(transactionId.getValue(), true, publishId);
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onSessionStarted(anyInt());
+        inOrder.verify(mockSessionCallback).onSessionTerminated(anyInt());
         inOrder.verify(mMockNative).stopPublish(transactionId.capture(), eq(publishId));
         inOrder.verify(mMockNative).disable(anyShort());
         inOrderM.verify(mAwareMetricsMock).recordDiscoverySession(eq(uid), any());
@@ -1098,15 +1097,13 @@
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
         inOrderM.verify(mAwareMetricsMock).recordEnableUsage();
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         // (0) connect
         mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -1172,15 +1169,13 @@
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
         inOrderM.verify(mAwareMetricsMock).recordEnableUsage();
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         // (0) connect
         mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -1198,6 +1193,9 @@
         inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
         inOrderM.verify(mAwareMetricsMock).recordDiscoverySession(eq(uid), any());
         inOrderM.verify(mAwareMetricsMock).recordDiscoveryStatus(uid, NanStatusType.SUCCESS, false);
+        assertEquals(2, mDut.getAvailableAwareResources().getAvailablePublishSessionsCount());
+        assertEquals(1, mDut.getAvailableAwareResources().getAvailableSubscribeSessionsCount());
+        assertEquals(0, mDut.getAvailableAwareResources().getAvailableDataPathsCount());
 
         // (3) subscribe termination (from firmware - not app!)
         mDut.onSessionTerminatedNotification(subscribeId, reasonTerminate, false);
@@ -1256,15 +1254,13 @@
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
         inOrderM.verify(mAwareMetricsMock).recordEnableUsage();
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         // (0) connect
         mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -1286,7 +1282,7 @@
         // Verify reconfigure aware to enable ranging.
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(false), eq(true), eq(false), eq(true));
+                eq(false), eq(false), eq(true), eq(false), eq(true), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
 
@@ -1354,16 +1350,13 @@
 
         mDut.enableUsage();
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         // (0) connect
         mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -1383,6 +1376,7 @@
         mDut.onSessionConfigSuccessResponse(transactionId.getValue(), false, subscribeId);
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onSessionStarted(anyInt());
+        inOrder.verify(mockSessionCallback).onSessionTerminated(anyInt());
         inOrder.verify(mMockNative).stopSubscribe((short) 0, subscribeId);
         inOrder.verify(mMockNative).disable(anyShort());
 
@@ -1438,15 +1432,13 @@
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
         inOrderM.verify(mAwareMetricsMock).recordEnableUsage();
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         // (0) connect
         mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                eq(configRequest), eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(configRequest), eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -1466,7 +1458,7 @@
         // Verify reconfigure aware to enable ranging.
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(false), eq(true), eq(false), eq(true));
+                eq(false), eq(false), eq(true), eq(false), eq(true), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
 
@@ -1572,16 +1564,13 @@
 
         mDut.enableUsage();
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         // (1) connect
         mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -1676,16 +1665,13 @@
 
         mDut.enableUsage();
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         // (1) connect
         mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -1770,16 +1756,13 @@
 
         mDut.enableUsage();
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         // (1) connect
         mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -1841,16 +1824,13 @@
 
         mDut.enableUsage();
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         // (1) connect
         mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -1969,16 +1949,13 @@
 
         mDut.enableUsage();
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         // (1) connect
         mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -2058,16 +2035,13 @@
 
         mDut.enableUsage();
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         // (1) connect
         mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -2148,16 +2122,13 @@
 
         mDut.enableUsage();
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         // (0) connect
         mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                eq(configRequest), eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(configRequest), eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -2258,20 +2229,19 @@
         ArgumentCaptor<Integer> messageIdCaptorSuccess = ArgumentCaptor.forClass(Integer.class);
         ArgumentCaptor<Integer> peerIdCaptor1 = ArgumentCaptor.forClass(Integer.class);
         ArgumentCaptor<Integer> peerIdCaptor2 = ArgumentCaptor.forClass(Integer.class);
-        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mMockNative);
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mMockNative,
+                mMockNativeManager);
 
         mDut.enableUsage();
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         // (0) connect
         mDut.connect(clientId1, uid1, pid1, callingPackage1, callingFeature, mockCallback,
                 configRequest1, false);
         mMockLooper.dispatchAll();
+        inOrder.verify(mMockNativeManager).tryToGetAware(new WorkSource(uid1, callingPackage1));
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                eq(configRequest1), eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(configRequest1), eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId1);
@@ -2280,6 +2250,10 @@
                 configRequest2, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId2);
+        // merged requestorWs
+        WorkSource expectedRequestorWs = new WorkSource(uid1, callingPackage1);
+        expectedRequestorWs.add(uid2, callingPackage2);
+        inOrder.verify(mMockNativeManager).replaceRequestorWs(expectedRequestorWs);
 
         // (1) subscribe
         mDut.subscribe(clientId1, subscribeConfig1, mockSessionCallback);
@@ -2397,16 +2371,13 @@
 
         mDut.enableUsage();
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         // (0) connect
         mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                eq(configRequest), eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(configRequest), eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -2531,16 +2502,13 @@
 
         mDut.enableUsage();
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         // (0) connect
         mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                eq(configRequest), eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(configRequest), eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -2760,20 +2728,19 @@
         IWifiAwareEventCallback mockCallback2 = mock(IWifiAwareEventCallback.class);
         IWifiAwareEventCallback mockCallback3 = mock(IWifiAwareEventCallback.class);
 
-        InOrder inOrder = inOrder(mMockNative, mockCallback1, mockCallback2, mockCallback3);
+        InOrder inOrder = inOrder(mMockNative, mMockNativeManager, mockCallback1, mockCallback2,
+                mockCallback3);
 
         mDut.enableUsage();
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         // (1) config1 (valid)
         mDut.connect(clientId1, uid, pid, callingPackage, callingFeature, mockCallback1,
                 configRequest1, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                crCapture.capture(), eq(false), eq(true), eq(true), eq(false), eq(false));
+                crCapture.capture(), eq(false), eq(true), eq(true), eq(false),
+                eq(false), eq(false));
         collector.checkThat("merge: stage 1", crCapture.getValue(), equalTo(configRequest1));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
@@ -2791,7 +2758,8 @@
                 configRequest3, true);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                crCapture.capture(), eq(true), eq(false), eq(true), eq(false), eq(false));
+                crCapture.capture(), eq(true), eq(false), eq(true), eq(false),
+                eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback3).onConnectSuccess(clientId3);
@@ -2812,8 +2780,10 @@
         mDut.disconnect(clientId3);
         mMockLooper.dispatchAll();
         validateInternalClientInfoCleanedUp(clientId3);
+        inOrder.verify(mMockNativeManager).replaceRequestorWs(new WorkSource(uid, callingPackage));
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                crCapture.capture(), eq(false), eq(false), eq(true), eq(false), eq(false));
+                crCapture.capture(), eq(false), eq(false), eq(true), eq(false),
+                eq(false), eq(false));
 
         collector.checkThat("configRequest1", configRequest1, equalTo(crCapture.getValue()));
 
@@ -2855,16 +2825,15 @@
 
         mDut.enableUsage();
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         // (1) attach w/o identity
         mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                any(ConfigRequest.class), eq(false), eq(true), eq(true), eq(false), eq(false));
+                any(ConfigRequest.class), eq(false), eq(true), eq(true), eq(false),
+                eq(false),
+                eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -2882,7 +2851,8 @@
                 configRequest, true);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                any(ConfigRequest.class), eq(true), eq(false), eq(true), eq(false), eq(false));
+                any(ConfigRequest.class), eq(true), eq(false), eq(true), eq(false), eq(false),
+                eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -2936,16 +2906,13 @@
 
         mDut.enableUsage();
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         // (1) connect
         mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -2965,6 +2932,7 @@
         mDut.onSessionConfigSuccessResponse(transactionId.getValue(), true, publishId);
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onSessionStarted(anyInt());
+        inOrder.verify(mockSessionCallback).onSessionTerminated(anyInt());
         inOrder.verify(mMockNative).stopPublish((short) 0, publishId);
         inOrder.verify(mMockNative).disable(anyShort());
 
@@ -3013,16 +2981,13 @@
 
         mDut.enableUsage();
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         // (1) connect
         mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -3059,16 +3024,13 @@
 
         mDut.enableUsage();
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         // (1) connect (no response)
         mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
 
         verifyNoMoreInteractions(mMockNative, mockCallback, mockSessionCallback);
     }
@@ -3095,16 +3057,13 @@
 
         mDut.enableUsage();
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         // (1) connect and succeed
         mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         short transactionIdConfig = transactionId.getValue();
         mDut.onConfigSuccessResponse(transactionIdConfig);
         mMockLooper.dispatchAll();
@@ -3153,16 +3112,13 @@
 
         mDut.enableUsage();
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         // (1) connect
         mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -3210,16 +3166,13 @@
 
         mDut.enableUsage();
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         // (1) connect
         mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                eq(configRequest), eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(configRequest), eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -3265,16 +3218,14 @@
 
         mDut.enableUsage();
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         // (1) connect
         mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                eq(configRequest), eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(configRequest), eq(false), eq(true), eq(true), eq(false),
+                eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -3321,16 +3272,14 @@
 
         mDut.enableUsage();
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
 
         // (1) connect
         mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                eq(configRequest), eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(configRequest), eq(false), eq(true), eq(true), eq(false),
+                eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -3339,7 +3288,8 @@
         simulatePowerStateChangeInteractive(false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                eq(configRequest), eq(false), eq(false), eq(false), eq(false), eq(false));
+                eq(configRequest), eq(false), eq(false), eq(false), eq(false),
+                eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
 
@@ -3347,7 +3297,8 @@
         simulatePowerStateChangeDoze(true);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                eq(configRequest), eq(false), eq(false), eq(false), eq(true), eq(false));
+                eq(configRequest), eq(false), eq(false), eq(false), eq(true),
+                eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
 
@@ -3355,7 +3306,8 @@
         simulatePowerStateChangeInteractive(true); // effectively treated as no-doze
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                eq(configRequest), eq(false), eq(false), eq(true), eq(true), eq(false));
+                eq(configRequest), eq(false), eq(false), eq(true), eq(true),
+                eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
 
@@ -3381,24 +3333,21 @@
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
         IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
         InOrder inOrder = inOrder(mMockContext, mMockNativeManager, mMockNative, mockCallback);
+        mMockLooper.dispatchAll();
         inOrder.verify(mMockNativeManager).start(any(Handler.class));
+        inOrder.verify(mMockNativeManager).tryToGetAware(new WorkSource(Process.WIFI_UID));
+        inOrder.verify(mMockNativeManager).releaseAware();
 
         mDut.enableUsage();
-        inOrder.verify(mMockNativeManager).isAwareNativeAvailable();
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNativeManager).tryToGetAware();
-        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
-        inOrder.verify(mMockNativeManager).releaseAware();
 
         // (1) connect
         mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
                 configRequest, false);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNativeManager).tryToGetAware();
+        inOrder.verify(mMockNativeManager).tryToGetAware(new WorkSource(uid, callingPackage));
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                eq(configRequest), eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(configRequest), eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -3406,10 +3355,10 @@
         // (3) power state change: DOZE
         simulatePowerStateChangeDoze(true);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).disable(transactionId.capture());
-        mDut.onDisableResponse(transactionId.getValue(), NanStatusType.SUCCESS);
         collector.checkThat("usage disabled", mDut.isUsageEnabled(), equalTo(false));
         validateCorrectAwareStatusChangeBroadcast(inOrder);
+        inOrder.verify(mMockNative).disable(transactionId.capture());
+        mDut.onDisableResponse(transactionId.getValue(), NanStatusType.SUCCESS);
 
         // (4) power state change: SCREEN ON (but DOZE still on - fakish but expect no changes)
         simulatePowerStateChangeInteractive(false);
@@ -3425,16 +3374,9 @@
         simulateWifiStateChange(true);
         mMockLooper.dispatchAll();
 
-        // when WifiAware Native is not available, DOZE OFF -> no change
-        when(mMockNativeManager.isAwareNativeAvailable()).thenReturn(false);
-        simulatePowerStateChangeDoze(false);
-        mMockLooper.dispatchAll();
-        inOrder.verify(mMockNativeManager).isAwareNativeAvailable();
-        when(mMockNativeManager.isAwareNativeAvailable()).thenReturn(true);
 
         // (5) power state change: DOZE OFF
         simulatePowerStateChangeDoze(false);
-        inOrder.verify(mMockNativeManager).isAwareNativeAvailable();
         mMockLooper.dispatchAll();
         collector.checkThat("usage enabled", mDut.isUsageEnabled(), equalTo(true));
         validateCorrectAwareStatusChangeBroadcast(inOrder);
@@ -3458,24 +3400,21 @@
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
         IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
         InOrder inOrder = inOrder(mMockContext, mMockNativeManager, mMockNative, mockCallback);
+        mMockLooper.dispatchAll();
         inOrder.verify(mMockNativeManager).start(any(Handler.class));
+        inOrder.verify(mMockNativeManager).tryToGetAware(new WorkSource(Process.WIFI_UID));
+        inOrder.verify(mMockNativeManager).releaseAware();
 
         mDut.enableUsage();
-        inOrder.verify(mMockNativeManager).isAwareNativeAvailable();
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNativeManager).tryToGetAware();
-        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
-        inOrder.verify(mMockNativeManager).releaseAware();
 
         // (1) connect
         mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
                 configRequest, false);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNativeManager).tryToGetAware();
+        inOrder.verify(mMockNativeManager).tryToGetAware(new WorkSource(uid, callingPackage));
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                eq(configRequest), eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(configRequest), eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -3483,10 +3422,10 @@
         // (3) location mode change: disable
         simulateLocationModeChange(false);
         mMockLooper.dispatchAll();
+        validateCorrectAwareStatusChangeBroadcast(inOrder);
         inOrder.verify(mMockNative).disable(transactionId.capture());
         mDut.onDisableResponse(transactionId.getValue(), NanStatusType.SUCCESS);
         collector.checkThat("usage disabled", mDut.isUsageEnabled(), equalTo(false));
-        validateCorrectAwareStatusChangeBroadcast(inOrder);
 
         // disable other gating feature -> no change
         simulatePowerStateChangeDoze(true);
@@ -3498,16 +3437,8 @@
         simulateWifiStateChange(true);
         mMockLooper.dispatchAll();
 
-        // when WifiAware Native is not available, enable location -> no change
-        when(mMockNativeManager.isAwareNativeAvailable()).thenReturn(false);
-        simulateLocationModeChange(true);
-        inOrder.verify(mMockNativeManager).isAwareNativeAvailable();
-        mMockLooper.dispatchAll();
-        when(mMockNativeManager.isAwareNativeAvailable()).thenReturn(true);
-
         // (4) location mode change: enable
         simulateLocationModeChange(true);
-        inOrder.verify(mMockNativeManager).isAwareNativeAvailable();
         mMockLooper.dispatchAll();
         collector.checkThat("usage enabled", mDut.isUsageEnabled(), equalTo(true));
         validateCorrectAwareStatusChangeBroadcast(inOrder);
@@ -3531,24 +3462,22 @@
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
         IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
         InOrder inOrder = inOrder(mMockContext, mMockNativeManager, mMockNative, mockCallback);
+        mMockLooper.dispatchAll();
         inOrder.verify(mMockNativeManager).start(any(Handler.class));
+        inOrder.verify(mMockNativeManager).tryToGetAware(new WorkSource(Process.WIFI_UID));
+        inOrder.verify(mMockNativeManager).releaseAware();
+
 
         mDut.enableUsage();
-        inOrder.verify(mMockNativeManager).isAwareNativeAvailable();
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNativeManager).tryToGetAware();
-        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
-        inOrder.verify(mMockNativeManager).releaseAware();
 
         // (1) connect
         mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
                 configRequest, false);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNativeManager).tryToGetAware();
+        inOrder.verify(mMockNativeManager).tryToGetAware(new WorkSource(uid, callingPackage));
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                eq(configRequest), eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(configRequest), eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -3556,10 +3485,11 @@
         // (3) wifi state change: disable
         simulateWifiStateChange(false);
         mMockLooper.dispatchAll();
+        validateCorrectAwareStatusChangeBroadcast(inOrder);
         inOrder.verify(mMockNative).disable(transactionId.capture());
         mDut.onDisableResponse(transactionId.getValue(), NanStatusType.SUCCESS);
         collector.checkThat("usage disabled", mDut.isUsageEnabled(), equalTo(false));
-        validateCorrectAwareStatusChangeBroadcast(inOrder);
+
 
         // disable other gating feature -> no change
         simulatePowerStateChangeDoze(true);
@@ -3571,16 +3501,8 @@
         simulateLocationModeChange(true);
         mMockLooper.dispatchAll();
 
-        // when WifiAware Native is not available, enable Wifi -> no change
-        when(mMockNativeManager.isAwareNativeAvailable()).thenReturn(false);
-        simulateWifiStateChange(true);
-        inOrder.verify(mMockNativeManager).isAwareNativeAvailable();
-        mMockLooper.dispatchAll();
-        when(mMockNativeManager.isAwareNativeAvailable()).thenReturn(true);
-
         // (4) wifi state change: enable
         simulateWifiStateChange(true);
-        inOrder.verify(mMockNativeManager).isAwareNativeAvailable();
         mMockLooper.dispatchAll();
         collector.checkThat("usage enabled", mDut.isUsageEnabled(), equalTo(true));
         validateCorrectAwareStatusChangeBroadcast(inOrder);
@@ -3608,9 +3530,6 @@
         mDut.enableUsage();
         mMockLooper.dispatchAll();
         validateCorrectAwareStatusChangeBroadcast(inOrder);
-        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
         collector.checkThat("usage enabled", mDut.isUsageEnabled(), equalTo(true));
 
         // (1) connect client
@@ -3618,7 +3537,7 @@
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                eq(configRequest), eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(configRequest), eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -3634,7 +3553,7 @@
                 configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                eq(configRequest), eq(false), eq(true), eq(true), eq(false), eq(false));
+                eq(configRequest), eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -3642,6 +3561,306 @@
         verifyNoMoreInteractions(mMockNative, mockCallback);
     }
 
+    /**
+     * Validate (1) subscribe (success), (2) match (i.e. discovery), (3) discovered peer is no
+     * longer visible.
+     */
+    @Test
+    public void testMatchAndLost() throws Exception {
+        final int clientId = 1005;
+        final int uid = 1000;
+        final int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        final String callingFeature = "com.google.someFeature";
+        final String serviceName = "some-service-name";
+        final String ssi = "some much longer and more arbitrary data";
+        final int reasonFail = NanStatusType.INTERNAL_FAILURE;
+        final byte subscribeId = 15;
+        final int requestorId = 22;
+        final byte[] peerMac = HexEncoding.decode("060708090A0B".toCharArray(), false);
+        final String peerSsi = "some peer ssi data";
+        final String peerMatchFilter = "filter binary array represented as string";
+        final String peerMsg = "some message from peer";
+        final int messageId = 6948;
+        final int messageId2 = 6949;
+        final int rangeMin = 0;
+        final int rangeMax = 55;
+        final int rangedDistance = 30;
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().setServiceName(serviceName)
+                .setServiceSpecificInfo(ssi.getBytes())
+                .setSubscribeType(SubscribeConfig.SUBSCRIBE_TYPE_PASSIVE)
+                .setMinDistanceMm(rangeMin)
+                .setMaxDistanceMm(rangeMax)
+                .build();
+
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        IWifiAwareDiscoverySessionCallback mockSessionCallback = mock(
+                IWifiAwareDiscoverySessionCallback.class);
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> peerIdCaptor = ArgumentCaptor.forClass(Integer.class);
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mMockNative);
+        InOrder inOrderM = inOrder(mAwareMetricsMock);
+
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        inOrderM.verify(mAwareMetricsMock).recordEnableUsage();
+
+        // (0) connect
+        mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
+                configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
+                eq(configRequest), eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+        inOrderM.verify(mAwareMetricsMock).recordAttachSession(eq(uid), eq(false), any());
+
+        // (1) subscribe
+        mDut.subscribe(clientId, subscribeConfig, mockSessionCallback);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq((byte) 0),
+                eq(subscribeConfig));
+        mDut.onSessionConfigSuccessResponse(transactionId.getValue(), false, subscribeId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
+        inOrderM.verify(mAwareMetricsMock).recordDiscoverySessionWithRanging(eq(uid), eq(true),
+                eq(rangeMin), eq(rangeMax), any());
+        inOrderM.verify(mAwareMetricsMock).recordDiscoveryStatus(uid, NanStatusType.SUCCESS, false);
+        // Verify reconfigure aware to enable ranging.
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(false), eq(false), eq(true), eq(false), eq(true), eq(false));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+
+
+        // (2) 2 matches : with and w/o range
+        mDut.onMatchNotification(subscribeId, requestorId, peerMac, peerSsi.getBytes(),
+                peerMatchFilter.getBytes(), 0, 0);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onMatch(peerIdCaptor.capture(), eq(peerSsi.getBytes()),
+                eq(peerMatchFilter.getBytes()));
+        inOrderM.verify(mAwareMetricsMock).recordMatchIndicationForRangeEnabledSubscribe(false);
+        int peerId1 = peerIdCaptor.getValue();
+
+        mDut.onMatchNotification(subscribeId, requestorId, peerMac, peerSsi.getBytes(),
+                peerMatchFilter.getBytes(), EGRESS_MET_MASK, rangedDistance);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onMatchWithDistance(peerIdCaptor.capture(),
+                eq(peerSsi.getBytes()), eq(peerMatchFilter.getBytes()), eq(rangedDistance));
+        inOrderM.verify(mAwareMetricsMock).recordMatchIndicationForRangeEnabledSubscribe(true);
+        int peerId2 = peerIdCaptor.getValue();
+
+        assertEquals(peerId1, peerId2);
+
+        // (3) peer is no longer visible.
+        mDut.onMatchExpiredNotification(subscribeId, requestorId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onMatchExpired(peerIdCaptor.capture());
+        assertEquals(peerId1, (int) peerIdCaptor.getValue());
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mMockNative, mAwareMetricsMock);
+    }
+
+    /**
+     * Test enable and disable instant communication mode.
+     * @throws RemoteException
+     */
+    @Test
+    public void testInstantCommunicationMode() throws RemoteException {
+        final int clientId = 1005;
+        final int uid = 1000;
+        final int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        final String callingFeature = "com.google.someFeature";
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        IWifiAwareDiscoverySessionCallback mockSessionCallback = mock(
+                IWifiAwareDiscoverySessionCallback.class);
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> peerIdCaptor = ArgumentCaptor.forClass(Integer.class);
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mMockNative);
+        InOrder inOrderM = inOrder(mAwareMetricsMock);
+
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        inOrderM.verify(mAwareMetricsMock).recordEnableUsage();
+
+        // (0) enable instant communication mode without any client
+        mDut.enableInstantCommunicationMode(true);
+        mMockLooper.dispatchAll();
+        verify(mMockNative, never()).enableAndConfigure(anyShort(), any(), anyBoolean(),
+                anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean());
+        assertTrue(mDut.isInstantCommunicationModeEnabled());
+
+        // (1) connect
+        mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
+                configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
+                eq(configRequest), eq(false), eq(true), eq(true), eq(false), eq(false), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+        inOrderM.verify(mAwareMetricsMock).recordAttachSession(eq(uid), eq(false), any());
+
+        // (2) disable instant communication mode
+        mDut.enableInstantCommunicationMode(false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
+                eq(configRequest), eq(false), eq(false), eq(true), eq(false), eq(false), eq(false));
+        assertFalse(mDut.isInstantCommunicationModeEnabled());
+    }
+
+    /**
+     * Validate consecutive requests Connect-Disconnect-Connect can be processed in the right order,
+     * and the final state is correct.
+     */
+    @Test
+    public void testConnectDisconnectConnectSequence() throws Exception {
+        final int clientId = 12341;
+        final int uid = 1000;
+        final int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        final String callingFeature = "com.google.someFeature";
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<SparseArray> sparseArrayCaptor = ArgumentCaptor.forClass(SparseArray.class);
+        InOrder inOrder = inOrder(mMockContext, mMockNative, mockCallback,
+                mMockAwareDataPathStatemanager);
+        InOrder inOrderM = inOrder(mAwareMetricsMock);
+
+        // (1) check initial state
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        validateCorrectAwareStatusChangeBroadcast(inOrder);
+        inOrderM.verify(mAwareMetricsMock).recordEnableUsage();
+
+        collector.checkThat("usage enabled", mDut.isUsageEnabled(), equalTo(true));
+
+        // (2) Placing a connect -> disconnect -> connect sequence
+        mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
+                configRequest, false);
+        mDut.disconnect(clientId);
+        mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
+                configRequest, false);
+
+        // (3) Verify the command executed in the correct order
+        // (3.1) verify the connect execution
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+        inOrderM.verify(mAwareMetricsMock).recordAttachSession(eq(uid), eq(false),
+                any());
+        // (3.2) verify the disconnect execution
+        inOrder.verify(mMockAwareDataPathStatemanager).deleteAllInterfaces();
+        inOrder.verify(mMockNative).disable(transactionId.capture());
+        mDut.onDisableResponse(transactionId.getValue(), NanStatusType.SUCCESS);
+        mMockLooper.dispatchAll();
+        inOrderM.verify(mAwareMetricsMock).recordAttachSessionDuration(anyLong());
+        inOrderM.verify(mAwareMetricsMock).recordDisableAware();
+        // (3.3) verify the connect execution again
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+        inOrderM.verify(mAwareMetricsMock).recordAttachSession(eq(uid), eq(false),
+                sparseArrayCaptor.capture());
+        collector.checkThat("num of clients", sparseArrayCaptor.getValue().size(), equalTo(1));
+
+        verifyNoMoreInteractions(mMockNative, mockCallback, mAwareMetricsMock);
+    }
+
+    /**
+     * Validate consecutive requests DisableUsage-EnableUsage-Connect can be processed in the right
+     * order, and the final state is correct.
+     */
+    @Test
+    public void testDisableUsageEnableUsageConnectSequence() throws Exception {
+        final int clientId = 12341;
+        final int uid = 1000;
+        final int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        final String callingFeature = "com.google.someFeature";
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<SparseArray> sparseArrayCaptor = ArgumentCaptor.forClass(SparseArray.class);
+        InOrder inOrder = inOrder(mMockContext, mMockNative, mockCallback,
+                mMockAwareDataPathStatemanager);
+        InOrder inOrderM = inOrder(mAwareMetricsMock);
+
+        // (1) check initial state
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        validateCorrectAwareStatusChangeBroadcast(inOrder);
+        inOrderM.verify(mAwareMetricsMock).recordEnableUsage();
+
+        collector.checkThat("usage enabled", mDut.isUsageEnabled(), equalTo(true));
+
+        // (2) Connect
+        mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
+                configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+        inOrderM.verify(mAwareMetricsMock).recordAttachSession(eq(uid), eq(false),
+                sparseArrayCaptor.capture());
+        collector.checkThat("num of clients", sparseArrayCaptor.getValue().size(), equalTo(1));
+
+        // (3) Placing a disableUsage -> enabledUsage -> connect sequence
+        mDut.disableUsage(false);
+        mDut.enableUsage();
+        mDut.connect(clientId, uid, pid, callingPackage, callingFeature, mockCallback,
+                configRequest, false);
+
+
+        // (4) Verify the command executed in the correct order
+        // (4.1) verify the disable usage execution
+        mMockLooper.dispatchAll();
+        validateCorrectAwareStatusChangeBroadcast(inOrder);
+        inOrder.verify(mMockNative).disable(transactionId.capture());
+        inOrderM.verify(mAwareMetricsMock).recordAttachSessionDuration(anyLong());
+        inOrderM.verify(mAwareMetricsMock).recordDisableAware();
+        inOrderM.verify(mAwareMetricsMock).recordDisableUsage();
+        validateInternalClientInfoCleanedUp(clientId);
+        mDut.onDisableResponse(transactionId.getValue(), NanStatusType.SUCCESS);
+        mMockLooper.dispatchAll();
+        inOrderM.verify(mAwareMetricsMock).recordEnableUsage();
+        inOrderM.verify(mAwareMetricsMock).recordDisableAware();
+        // (4.2) verify the usage is enabled
+        collector.checkThat("usage enabled", mDut.isUsageEnabled(), equalTo(true));
+        // (4.3)verify the connect execution again
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(false), eq(true), eq(true), eq(false), eq(false), eq(false));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+        inOrderM.verify(mAwareMetricsMock).recordAttachSession(eq(uid), eq(false),
+                sparseArrayCaptor.capture());
+        collector.checkThat("num of clients", sparseArrayCaptor.getValue().size(), equalTo(1));
+
+        verifyNoMoreInteractions(mMockNative, mockCallback, mAwareMetricsMock);
+    }
+
     /*
      * Tests of internal state of WifiAwareStateManager: very limited (not usually
      * a good idea). However, these test that the internal state is cleaned-up
@@ -3706,7 +3925,8 @@
     private void validateCorrectAwareStatusChangeBroadcast(InOrder inOrder) {
         ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
 
-        inOrder.verify(mMockContext).sendBroadcastAsUser(intent.capture(), eq(UserHandle.ALL));
+        inOrder.verify(mMockContext, atLeastOnce()).sendBroadcastAsUser(intent.capture(),
+                eq(UserHandle.ALL));
 
         collector.checkThat("intent action", intent.getValue().getAction(),
                 equalTo(WifiAwareManager.ACTION_WIFI_AWARE_STATE_CHANGED));
@@ -3856,6 +4076,7 @@
         cap.maxNdpSessions = 1;
         cap.maxAppInfoLen = 255;
         cap.maxQueuedTransmitMessages = 6;
+        cap.isInstantCommunicationModeSupported = true;
         return cap;
     }
 
diff --git a/service/tests/wifitests/src/com/android/server/wifi/coex/CoexManagerTest.java b/service/tests/wifitests/src/com/android/server/wifi/coex/CoexManagerTest.java
new file mode 100644
index 0000000..fbddee8
--- /dev/null
+++ b/service/tests/wifitests/src/com/android/server/wifi/coex/CoexManagerTest.java
@@ -0,0 +1,870 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.coex;
+
+
+import static android.net.wifi.WifiManager.COEX_RESTRICTION_SOFTAP;
+import static android.net.wifi.WifiManager.COEX_RESTRICTION_WIFI_AWARE;
+import static android.net.wifi.WifiManager.COEX_RESTRICTION_WIFI_DIRECT;
+import static android.net.wifi.WifiScanner.WIFI_BAND_24_GHZ;
+import static android.net.wifi.WifiScanner.WIFI_BAND_5_GHZ;
+import static android.telephony.TelephonyManager.NETWORK_TYPE_LTE;
+
+import static com.android.server.wifi.coex.CoexUtils.CHANNEL_SET_5_GHZ;
+import static com.android.server.wifi.coex.CoexUtils.CHANNEL_SET_5_GHZ_20_MHZ;
+import static com.android.server.wifi.coex.CoexUtils.CHANNEL_SET_5_GHZ_40_MHZ;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.net.wifi.CoexUnsafeChannel;
+import android.net.wifi.ICoexCallback;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.Annotation;
+import android.telephony.CarrierConfigManager;
+import android.telephony.PhysicalChannelConfig;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.modules.utils.build.SdkLevel;
+import com.android.server.wifi.WifiBaseTest;
+import com.android.server.wifi.WifiNative;
+import com.android.wifi.resources.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.coex.CoexManager}.
+ */
+@SmallTest
+public class CoexManagerTest extends WifiBaseTest {
+    @Rule
+    public TemporaryFolder tempFolder = new TemporaryFolder();
+    private static final String FILEPATH_MALFORMED = "assets/coex_malformed.xml";
+    private static final String FILEPATH_LTE_40_NEIGHBORING = "assets/coex_lte_40_neighboring.xml";
+    private static final String FILEPATH_LTE_46_NEIGHBORING = "assets/coex_lte_46_neighboring.xml";
+    private static final String FILEPATH_LTE_40_OVERRIDE = "assets/coex_lte_40_override.xml";
+    private static final String FILEPATH_LTE_27_HARMONIC = "assets/coex_lte_27_harmonic.xml";
+    private static final String FILEPATH_LTE_7_INTERMOD = "assets/coex_lte_7_intermod.xml";
+
+    @Mock private Context mMockContext;
+    @Mock private Resources mMockResources;
+    @Mock private WifiNative mMockWifiNative;
+    @Mock private TelephonyManager mMockDefaultTelephonyManager;
+    @Mock private SubscriptionManager mMockSubscriptionManager;
+    private List<SubscriptionInfo> mSubscriptionInfos = new ArrayList<>();
+    @Mock private CarrierConfigManager mMockCarrierConfigManager;
+    private PersistableBundle mUnrestrictedBundle = new PersistableBundle();
+    private PersistableBundle mRestrictedBundle = new PersistableBundle();
+
+    private final ArgumentCaptor<CoexManager.CoexOnSubscriptionsChangedListener>
+            mCoexSubscriptionsListenerCaptor = ArgumentCaptor.forClass(
+                    CoexManager.CoexOnSubscriptionsChangedListener.class);
+    private final ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor =
+            ArgumentCaptor.forClass(BroadcastReceiver.class);
+
+    /**
+     * Set up TelephonyManager and SubscriptionManager mocks for the given subId before creating
+     * a CoexManager. Returns the mock TelephonyManager corresponding to the given subId.
+     */
+    private TelephonyManager setUpSubIdMocks(int subId) {
+        TelephonyManager telephonyManager = mock(TelephonyManager.class);
+        when(mMockDefaultTelephonyManager.createForSubscriptionId(subId))
+                .thenReturn(telephonyManager);
+        SubscriptionInfo subscriptionInfo = mock(SubscriptionInfo.class);
+        when(subscriptionInfo.getSubscriptionId()).thenReturn(subId);
+        mSubscriptionInfos.add(subscriptionInfo);
+        return telephonyManager;
+    }
+
+    private CoexManager createCoexManager() {
+        Looper.prepare();
+        final CoexManager coexManager = new CoexManager(mMockContext, mMockWifiNative,
+                mMockDefaultTelephonyManager, mMockSubscriptionManager, mMockCarrierConfigManager,
+                new Handler(Looper.myLooper()));
+        coexManager.enableVerboseLogging(true);
+        return coexManager;
+    }
+
+    private PhysicalChannelConfig createMockPhysicalChannelConfig(
+            @Annotation.NetworkType int rat, int band,
+            int dlFreqKhz, int dlBandwidthKhz, int ulFreqKhz, int ulBandwidthKhz) {
+        PhysicalChannelConfig config = mock(PhysicalChannelConfig.class);
+        when(config.getNetworkType()).thenReturn(rat);
+        when(config.getBand()).thenReturn(band);
+        when(config.getDownlinkFrequencyKhz()).thenReturn(dlFreqKhz);
+        when(config.getCellBandwidthDownlinkKhz()).thenReturn(dlBandwidthKhz);
+        when(config.getUplinkFrequencyKhz()).thenReturn(ulFreqKhz);
+        when(config.getCellBandwidthUplinkKhz()).thenReturn(ulBandwidthKhz);
+        return config;
+    }
+
+    private File createFileFromResource(String configFile) throws Exception {
+        InputStream in = getClass().getClassLoader().getResourceAsStream(configFile);
+        File file = tempFolder.newFile(configFile.split("/")[1]);
+
+        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+        FileOutputStream out = new FileOutputStream(file);
+
+        String line;
+
+        while ((line = reader.readLine()) != null) {
+            out.write(line.getBytes(StandardCharsets.UTF_8));
+        }
+
+        out.flush();
+        out.close();
+        return file;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        MockitoAnnotations.initMocks(this);
+        when(mMockContext.getResources()).thenReturn(mMockResources);
+        when(mMockResources.getBoolean(R.bool.config_wifiDefaultCoexAlgorithmEnabled))
+                .thenReturn(true);
+        when(mMockResources.getString(R.string.config_wifiCoexTableFilepath))
+                .thenReturn("");
+        when(mMockSubscriptionManager.getAvailableSubscriptionInfoList())
+                .thenReturn(mSubscriptionInfos);
+        mUnrestrictedBundle.putBoolean(CarrierConfigManager.Wifi
+                .KEY_AVOID_5GHZ_SOFTAP_FOR_LAA_BOOL, false);
+        mUnrestrictedBundle.putBoolean(CarrierConfigManager.Wifi
+                .KEY_AVOID_5GHZ_WIFI_DIRECT_FOR_LAA_BOOL, false);
+        mRestrictedBundle.putBoolean(CarrierConfigManager.Wifi
+                .KEY_AVOID_5GHZ_SOFTAP_FOR_LAA_BOOL, true);
+        mRestrictedBundle.putBoolean(CarrierConfigManager.Wifi
+                .KEY_AVOID_5GHZ_WIFI_DIRECT_FOR_LAA_BOOL, true);
+    }
+
+    /**
+     * Verifies that setCoexUnsafeChannels(Set, int) sets values returned in the getter methods
+     * getCoexUnsafeChannels() and getCoexRestrictions().
+     */
+    @Test
+    public void testSetCoexUnsafeChannels_nonNullChannels_returnedInGetters() {
+        assumeTrue(SdkLevel.isAtLeastS());
+        CoexManager coexManager = createCoexManager();
+        List<CoexUnsafeChannel> unsafeChannels = new ArrayList<>();
+        unsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6));
+        unsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 36));
+        final int restrictions = COEX_RESTRICTION_WIFI_DIRECT | COEX_RESTRICTION_SOFTAP
+                | COEX_RESTRICTION_WIFI_AWARE;
+
+        coexManager.setCoexUnsafeChannels(unsafeChannels, restrictions);
+
+        assertThat(coexManager.getCoexUnsafeChannels()).containsExactlyElementsIn(unsafeChannels);
+        assertThat(coexManager.getCoexRestrictions()).isEqualTo(restrictions);
+    }
+
+    /**
+     * Verifies that setCoexUnsafeChannels(Set, int) with an null set results in no change to the
+     * current CoexUnsafeChannels or restrictions
+     */
+    @Test
+    public void testSetCoexUnsafeChannels_nullChannels_setsEmptySet() {
+        CoexManager coexManager = createCoexManager();
+        List<CoexUnsafeChannel> unsafeChannels = new ArrayList<>();
+        unsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6));
+        unsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 36));
+        final int restrictions = COEX_RESTRICTION_WIFI_DIRECT | COEX_RESTRICTION_SOFTAP
+                | COEX_RESTRICTION_WIFI_AWARE;
+        coexManager.setCoexUnsafeChannels(unsafeChannels, restrictions);
+
+        coexManager.setCoexUnsafeChannels(null, 0);
+
+        assertThat(coexManager.getCoexUnsafeChannels()).containsExactlyElementsIn(unsafeChannels);
+        assertThat(coexManager.getCoexRestrictions()).isEqualTo(restrictions);
+    }
+
+    /**
+     * Verifies that setCoexUnsafeChannels(Set, int) with undefined restriction flags results in no
+     * change to the current CoexUnsafeChannels or restrictions
+     */
+    @Test
+    public void testSetCoexUnsafeChannels_undefinedRestrictions_setsEmptySet() {
+        CoexManager coexManager = createCoexManager();
+        List<CoexUnsafeChannel> unsafeChannels = new ArrayList<>();
+        unsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6));
+        unsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 36));
+        final int restrictions = COEX_RESTRICTION_WIFI_DIRECT | COEX_RESTRICTION_SOFTAP
+                | COEX_RESTRICTION_WIFI_AWARE;
+        coexManager.setCoexUnsafeChannels(unsafeChannels, restrictions);
+
+        coexManager.setCoexUnsafeChannels(Collections.emptyList(), ~restrictions);
+
+        assertThat(coexManager.getCoexUnsafeChannels()).containsExactlyElementsIn(unsafeChannels);
+        assertThat(coexManager.getCoexRestrictions()).isEqualTo(restrictions);
+    }
+
+    /**
+     * Verifies that setCoexUnsafeChannels notifies WifiNative with the set values.
+     */
+    @Test
+    public void testSetCoexUnsafeChannels_notifiesWifiVendorHal() {
+        CoexManager coexManager = createCoexManager();
+        List<CoexUnsafeChannel> unsafeChannels = new ArrayList<>();
+        unsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6));
+        unsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 36));
+        final int restrictions = COEX_RESTRICTION_WIFI_DIRECT | COEX_RESTRICTION_SOFTAP
+                | COEX_RESTRICTION_WIFI_AWARE;
+        coexManager.setCoexUnsafeChannels(unsafeChannels, restrictions);
+
+        verify(mMockWifiNative).setCoexUnsafeChannels(unsafeChannels, restrictions);
+    }
+
+    /**
+     * Verifies that the registered CoexListeners are notified when
+     * setCoexUnsafeChannels is called.
+     */
+    @Test
+    public void testRegisteredCoexListener_setCoexUnsafeChannels_listenerIsNotified() {
+        CoexManager.CoexListener listener1 = mock(CoexManager.CoexListener.class);
+        CoexManager.CoexListener listener2 = mock(CoexManager.CoexListener.class);
+        CoexManager coexManager = createCoexManager();
+        coexManager.registerCoexListener(listener1);
+        coexManager.registerCoexListener(listener2);
+        List<CoexUnsafeChannel> unsafeChannels = new ArrayList<>();
+        unsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6));
+        unsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 36));
+        int restrictions = COEX_RESTRICTION_WIFI_DIRECT | COEX_RESTRICTION_SOFTAP
+                | COEX_RESTRICTION_WIFI_AWARE;
+
+        coexManager.setCoexUnsafeChannels(unsafeChannels, restrictions);
+
+        verify(listener1).onCoexUnsafeChannelsChanged();
+        verify(listener2).onCoexUnsafeChannelsChanged();
+    }
+
+    /**
+     * Verifies that unregistered CoexListeners are not notified when
+     * setCoexUnsafeChannels is called.
+     */
+    @Test
+    public void testUnregisteredCoexListener_setCoexUnsafeChannels_listenerIsNotNotified() {
+        CoexManager.CoexListener listener1 = mock(CoexManager.CoexListener.class);
+        CoexManager.CoexListener listener2 = mock(CoexManager.CoexListener.class);
+        CoexManager coexManager = createCoexManager();
+        coexManager.registerCoexListener(listener1);
+        coexManager.registerCoexListener(listener2);
+        coexManager.unregisterCoexListener(listener1);
+        coexManager.unregisterCoexListener(listener2);
+        List<CoexUnsafeChannel> unsafeChannels = new ArrayList<>();
+        unsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6));
+        unsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 36));
+        int restrictions = COEX_RESTRICTION_WIFI_DIRECT | COEX_RESTRICTION_SOFTAP
+                | COEX_RESTRICTION_WIFI_AWARE;
+
+        coexManager.setCoexUnsafeChannels(unsafeChannels, restrictions);
+
+        verify(listener1, never()).onCoexUnsafeChannelsChanged();
+        verify(listener2, never()).onCoexUnsafeChannelsChanged();
+    }
+
+    /**
+     * Verifies that registered remote CoexCallbacks are notified when
+     * setCoexUnsafeChannels is called.
+     */
+    @Test
+    public void testRegisteredRemoteCoexCallback_setCoexUnsafeChannels_callbackIsNotified()
+            throws RemoteException {
+        ICoexCallback remoteCallback1 = mock(ICoexCallback.class);
+        when(remoteCallback1.asBinder()).thenReturn(mock(IBinder.class));
+        ICoexCallback remoteCallback2 = mock(ICoexCallback.class);
+        when(remoteCallback2.asBinder()).thenReturn(mock(IBinder.class));
+        CoexManager coexManager = createCoexManager();
+        coexManager.registerRemoteCoexCallback(remoteCallback1);
+        coexManager.registerRemoteCoexCallback(remoteCallback2);
+        verify(remoteCallback1).onCoexUnsafeChannelsChanged(any(), anyInt());
+        verify(remoteCallback2).onCoexUnsafeChannelsChanged(any(), anyInt());
+        List<CoexUnsafeChannel> unsafeChannels = new ArrayList<>();
+        unsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6));
+        unsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 36));
+        int restrictions = COEX_RESTRICTION_WIFI_DIRECT | COEX_RESTRICTION_SOFTAP
+                | COEX_RESTRICTION_WIFI_AWARE;
+
+        coexManager.setCoexUnsafeChannels(unsafeChannels, restrictions);
+
+        List<CoexUnsafeChannel> unsafeChannelList = new ArrayList<>(unsafeChannels);
+        verify(remoteCallback1).onCoexUnsafeChannelsChanged(unsafeChannelList, restrictions);
+        verify(remoteCallback2).onCoexUnsafeChannelsChanged(unsafeChannelList, restrictions);
+    }
+
+    /**
+     * Verifies that unregistered remote CoexCallbacks are not notified when
+     * setCoexUnsafeChannels is called.
+     */
+    @Test
+    public void testUnregisteredRemoteCoexCallback_setCoexUnsafeChannels_callbackIsNotNotified()
+            throws RemoteException {
+        ICoexCallback remoteCallback1 = mock(ICoexCallback.class);
+        when(remoteCallback1.asBinder()).thenReturn(mock(IBinder.class));
+        ICoexCallback remoteCallback2 = mock(ICoexCallback.class);
+        when(remoteCallback2.asBinder()).thenReturn(mock(IBinder.class));
+        CoexManager coexManager = createCoexManager();
+        coexManager.registerRemoteCoexCallback(remoteCallback1);
+        coexManager.registerRemoteCoexCallback(remoteCallback2);
+        verify(remoteCallback1).onCoexUnsafeChannelsChanged(any(), anyInt());
+        verify(remoteCallback2).onCoexUnsafeChannelsChanged(any(), anyInt());
+        coexManager.unregisterRemoteCoexCallback(remoteCallback1);
+        coexManager.unregisterRemoteCoexCallback(remoteCallback2);
+        List<CoexUnsafeChannel> unsafeChannels = new ArrayList<>();
+        unsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6));
+        unsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 36));
+        int restrictions = COEX_RESTRICTION_WIFI_DIRECT | COEX_RESTRICTION_SOFTAP
+                | COEX_RESTRICTION_WIFI_AWARE;
+
+        coexManager.setCoexUnsafeChannels(unsafeChannels, restrictions);
+
+        verify(remoteCallback1, times(1)).onCoexUnsafeChannelsChanged(any(), anyInt());
+        verify(remoteCallback2, times(1)).onCoexUnsafeChannelsChanged(any(), anyInt());
+    }
+
+    /**
+     * Verifies that CoexManager does register as a TelephonyCallback if the default coex algorithm
+     * is enabled and a coex table xml file exists and could be read.
+     */
+    @Test
+    public void testTelephonyCallback_defaultAlgorithmEnabledXmlExists_registersWithTelephony()
+            throws Exception {
+        // config_wifiDefaultCoexAlgorithm defaults to true
+        when(mMockResources.getString(R.string.config_wifiCoexTableFilepath))
+                .thenReturn(createFileFromResource(FILEPATH_LTE_40_NEIGHBORING).getCanonicalPath());
+        final TelephonyManager telephonyManager = setUpSubIdMocks(0);
+        createCoexManager();
+        verify(mMockSubscriptionManager).addOnSubscriptionsChangedListener(
+                any(), mCoexSubscriptionsListenerCaptor.capture());
+        mCoexSubscriptionsListenerCaptor.getValue().onSubscriptionsChanged();
+
+        verify(telephonyManager, times(1))
+                .registerTelephonyCallback(any(Executor.class), any(TelephonyCallback.class));
+    }
+
+    /**
+     * Verifies that CoexManager does not register as a TelephonyCallback if the default coex
+     * algorithm is disabled.
+     */
+    @Test
+    public void testTelephonyCallback_defaultAlgorithmDisabled_doesNotRegisterWithTelephony()
+            throws Exception {
+        when(mMockResources.getBoolean(R.bool.config_wifiDefaultCoexAlgorithmEnabled))
+                .thenReturn(false);
+        when(mMockResources.getString(R.string.config_wifiCoexTableFilepath))
+                .thenReturn(createFileFromResource(FILEPATH_LTE_40_NEIGHBORING).getCanonicalPath());
+        final TelephonyManager telephonyManager = setUpSubIdMocks(0);
+        createCoexManager();
+
+        verify(telephonyManager, never())
+                .registerTelephonyCallback(any(Executor.class), any(TelephonyCallback.class));
+    }
+
+    /**
+     * Verifies that CoexManager returns the correct 2.4Ghz CoexUnsafeChannels for a cell channel
+     * in the neighboring LTE band 40.
+     */
+    @Test
+    public void testGetCoexUnsafeChannels_neighboringLte40_returns2gNeighboringChannels()
+            throws Exception {
+        when(mMockResources.getString(R.string.config_wifiCoexTableFilepath))
+                .thenReturn(createFileFromResource(FILEPATH_LTE_40_NEIGHBORING).getCanonicalPath());
+        final TelephonyManager telephonyManager = setUpSubIdMocks(0);
+        CoexManager coexManager = createCoexManager();
+        verify(mMockSubscriptionManager).addOnSubscriptionsChangedListener(
+                any(), mCoexSubscriptionsListenerCaptor.capture());
+        mCoexSubscriptionsListenerCaptor.getValue().onSubscriptionsChanged();
+        final ArgumentCaptor<CoexManager.CoexTelephonyCallback> telephonyCallbackCaptor =
+                ArgumentCaptor.forClass(CoexManager.CoexTelephonyCallback.class);
+        verify(telephonyManager).registerTelephonyCallback(any(Executor.class),
+                telephonyCallbackCaptor.capture());
+
+        telephonyCallbackCaptor.getValue().onPhysicalChannelConfigChanged(Arrays.asList(
+                createMockPhysicalChannelConfig(NETWORK_TYPE_LTE, 40, 2399_900, 10_000, 0, 0)
+        ));
+
+        assertThat(coexManager.getCoexUnsafeChannels()).containsExactly(
+                new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 1, -50),
+                new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 2, -50),
+                new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 3, -50),
+                new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 4, -50),
+                new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 5, -50)
+        );
+    }
+
+    /**
+     * Verifies that CoexManager returns the correct 5Ghz CoexUnsafeChannels for a cell channel
+     * in the neighboring LTE band 46.
+     */
+    @Test
+    public void testGetCoexUnsafeChannels_neighboringLte46_returns5gNeighboringChannels()
+            throws Exception {
+        when(mMockResources.getString(R.string.config_wifiCoexTableFilepath))
+                .thenReturn(createFileFromResource(FILEPATH_LTE_46_NEIGHBORING).getCanonicalPath());
+        final TelephonyManager telephonyManager = setUpSubIdMocks(0);
+        CoexManager coexManager = createCoexManager();
+        verify(mMockSubscriptionManager).addOnSubscriptionsChangedListener(
+                any(), mCoexSubscriptionsListenerCaptor.capture());
+        mCoexSubscriptionsListenerCaptor.getValue().onSubscriptionsChanged();
+        final ArgumentCaptor<CoexManager.CoexTelephonyCallback> telephonyCallbackCaptor =
+                ArgumentCaptor.forClass(CoexManager.CoexTelephonyCallback.class);
+        verify(telephonyManager).registerTelephonyCallback(any(Executor.class),
+                telephonyCallbackCaptor.capture());
+
+        telephonyCallbackCaptor.getValue().onPhysicalChannelConfigChanged(Arrays.asList(
+                createMockPhysicalChannelConfig(NETWORK_TYPE_LTE, 46, 5150_000, 10_000, 0, 0)
+        ));
+
+        assertThat(coexManager.getCoexUnsafeChannels()).containsExactly(
+                new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 32, -50),
+                new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 34, -50),
+                new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 36, -50),
+                new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 38, -50),
+                new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 42, -50),
+                new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 50, -50)
+        );
+    }
+
+    /**
+     * Verifies that CoexManager returns the correct subset of CoexUnsafeChannels caused by
+     * harmonic interference from the example channel 27065 of LTE Band 27.
+     */
+    @Test
+    public void testGetCoexUnsafeChannels_channel27065Example_returnsCorrectWifiChannels()
+            throws Exception {
+        when(mMockResources.getString(R.string.config_wifiCoexTableFilepath))
+                .thenReturn(createFileFromResource(FILEPATH_LTE_27_HARMONIC).getCanonicalPath());
+        final TelephonyManager telephonyManager = setUpSubIdMocks(0);
+        CoexManager coexManager = createCoexManager();
+        verify(mMockSubscriptionManager).addOnSubscriptionsChangedListener(
+                any(), mCoexSubscriptionsListenerCaptor.capture());
+        mCoexSubscriptionsListenerCaptor.getValue().onSubscriptionsChanged();
+        final ArgumentCaptor<CoexManager.CoexTelephonyCallback> telephonyCallbackCaptor =
+                ArgumentCaptor.forClass(CoexManager.CoexTelephonyCallback.class);
+        verify(telephonyManager).registerTelephonyCallback(any(Executor.class),
+                telephonyCallbackCaptor.capture());
+
+        telephonyCallbackCaptor.getValue().onPhysicalChannelConfigChanged(Arrays.asList(
+                createMockPhysicalChannelConfig(NETWORK_TYPE_LTE, 27,
+                        854_500, 17_000, 809_500, 17_000)
+        ));
+
+        List<CoexUnsafeChannel> coexUnsafeChannels = new ArrayList<>();
+        for (int channel = 2; channel <= 7; channel += 1) {
+            coexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_24_GHZ, channel, -50));
+        }
+        assertThat(coexManager.getCoexUnsafeChannels())
+                .containsExactlyElementsIn(coexUnsafeChannels);
+    }
+
+    /**
+     * Verifies that CoexManager returns the correct subset of CoexUnsafeChannels caused by
+     * intermod interference with the example channel 3350 of LTE Band 7.
+     */
+    @Test
+    public void testGetCoexUnsafeChannels_channel3350Example_returnsCorrectWifiChannels()
+            throws Exception {
+        when(mMockResources.getString(R.string.config_wifiCoexTableFilepath))
+                .thenReturn(createFileFromResource(FILEPATH_LTE_7_INTERMOD).getCanonicalPath());
+        final TelephonyManager telephonyManager = setUpSubIdMocks(0);
+        CoexManager coexManager = createCoexManager();
+        verify(mMockSubscriptionManager).addOnSubscriptionsChangedListener(
+                any(), mCoexSubscriptionsListenerCaptor.capture());
+        mCoexSubscriptionsListenerCaptor.getValue().onSubscriptionsChanged();
+        final ArgumentCaptor<CoexManager.CoexTelephonyCallback> telephonyCallbackCaptor =
+                ArgumentCaptor.forClass(CoexManager.CoexTelephonyCallback.class);
+        verify(telephonyManager).registerTelephonyCallback(any(Executor.class),
+                telephonyCallbackCaptor.capture());
+
+        telephonyCallbackCaptor.getValue().onPhysicalChannelConfigChanged(Arrays.asList(
+                createMockPhysicalChannelConfig(NETWORK_TYPE_LTE, 7,
+                        2680_000, 10_000, 2560_000, 10_000)
+        ));
+
+        List<CoexUnsafeChannel> coexUnsafeChannels = new ArrayList<>();
+        for (int channel = 4; channel <= 9; channel += 1) {
+            coexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_24_GHZ, channel, -50));
+        }
+        assertThat(coexManager.getCoexUnsafeChannels())
+                .containsExactlyElementsIn(coexUnsafeChannels);
+    }
+
+    /**
+     * Verifies that CoexManager returns the full list of 2.4GHz CoexUnsafeChannels excluding the
+     * default channel if the entire 2.4GHz band is unsafe.
+     */
+    @Test
+    public void testGetCoexUnsafeChannels_entire2gBandUnsafe_excludesDefault2gChannel()
+            throws Exception {
+        when(mMockResources.getString(R.string.config_wifiCoexTableFilepath))
+                .thenReturn(createFileFromResource(FILEPATH_LTE_40_NEIGHBORING).getCanonicalPath());
+        final TelephonyManager telephonyManager = setUpSubIdMocks(0);
+        CoexManager coexManager = createCoexManager();
+        verify(mMockSubscriptionManager).addOnSubscriptionsChangedListener(
+                any(), mCoexSubscriptionsListenerCaptor.capture());
+        mCoexSubscriptionsListenerCaptor.getValue().onSubscriptionsChanged();
+        final ArgumentCaptor<CoexManager.CoexTelephonyCallback> telephonyCallbackCaptor =
+                ArgumentCaptor.forClass(CoexManager.CoexTelephonyCallback.class);
+        verify(telephonyManager).registerTelephonyCallback(any(Executor.class),
+                telephonyCallbackCaptor.capture());
+
+        telephonyCallbackCaptor.getValue().onPhysicalChannelConfigChanged(Arrays.asList(
+                createMockPhysicalChannelConfig(NETWORK_TYPE_LTE, 40, 2399_900, 2000_000, 0, 0)
+        ));
+
+        assertThat(coexManager.getCoexUnsafeChannels()).hasSize(13);
+        assertThat(coexManager.getCoexUnsafeChannels()).doesNotContain(
+                new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6, -50));
+    }
+
+    /**
+     * Verifies that CoexManager returns the full list of 5GHz CoexUnsafeChannels excluding the
+     * default channel if the entire 5GHz band is unsafe.
+     */
+    @Test
+    public void testGetCoexUnsafeChannels_entire5gBandUnsafe_excludesDefault5gChannel()
+            throws Exception {
+        when(mMockResources.getString(R.string.config_wifiCoexTableFilepath))
+                .thenReturn(createFileFromResource(FILEPATH_LTE_46_NEIGHBORING).getCanonicalPath());
+        final TelephonyManager telephonyManager = setUpSubIdMocks(0);
+        CoexManager coexManager = createCoexManager();
+        verify(mMockSubscriptionManager).addOnSubscriptionsChangedListener(
+                any(), mCoexSubscriptionsListenerCaptor.capture());
+        mCoexSubscriptionsListenerCaptor.getValue().onSubscriptionsChanged();
+        final ArgumentCaptor<CoexManager.CoexTelephonyCallback> telephonyCallbackCaptor =
+                ArgumentCaptor.forClass(CoexManager.CoexTelephonyCallback.class);
+        verify(telephonyManager).registerTelephonyCallback(any(Executor.class),
+                telephonyCallbackCaptor.capture());
+
+        telephonyCallbackCaptor.getValue().onPhysicalChannelConfigChanged(Arrays.asList(
+                createMockPhysicalChannelConfig(NETWORK_TYPE_LTE, 46, 5150_000, 2000_000, 0, 0)
+        ));
+
+        assertThat(coexManager.getCoexUnsafeChannels()).hasSize(CHANNEL_SET_5_GHZ.size() - 1);
+        assertThat(coexManager.getCoexUnsafeChannels()).doesNotContain(
+                new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 36, -50));
+    }
+
+    /**
+     * Verifies that added mock cell channels are used instead of real channels to calculate unsafe
+     * channels until the mock cell channels are reset.
+     */
+    @Test
+    public void testGetCoexUnsafeChannels_mockCellChannelsAdded_mockCellChannelsUsed()
+            throws Exception {
+        when(mMockResources.getString(R.string.config_wifiCoexTableFilepath))
+                .thenReturn(createFileFromResource(FILEPATH_LTE_40_NEIGHBORING).getCanonicalPath());
+        final TelephonyManager telephonyManager = setUpSubIdMocks(0);
+        CoexManager coexManager = createCoexManager();
+        verify(mMockSubscriptionManager).addOnSubscriptionsChangedListener(
+                any(), mCoexSubscriptionsListenerCaptor.capture());
+        mCoexSubscriptionsListenerCaptor.getValue().onSubscriptionsChanged();
+        final ArgumentCaptor<CoexManager.CoexTelephonyCallback> telephonyCallbackCaptor =
+                ArgumentCaptor.forClass(CoexManager.CoexTelephonyCallback.class);
+        verify(telephonyManager).registerTelephonyCallback(any(Executor.class),
+                telephonyCallbackCaptor.capture());
+
+        // Mock channels set.
+        coexManager.setMockCellChannels(Arrays.asList(
+                new CoexUtils.CoexCellChannel(NETWORK_TYPE_LTE, 40, 2399_900, 10_000, 0, 0,
+                        SubscriptionManager.INVALID_SUBSCRIPTION_ID)));
+        // Real channels changed.
+        telephonyCallbackCaptor.getValue()
+                .onPhysicalChannelConfigChanged(Collections.emptyList());
+
+        // Real channels should be ignored while mock channels are set/
+        assertThat(coexManager.getCoexUnsafeChannels()).containsExactly(
+                new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 1, -50),
+                new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 2, -50),
+                new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 3, -50),
+                new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 4, -50),
+                new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 5, -50)
+        );
+
+        coexManager.resetMockCellChannels();
+
+        // Real channels should be used when mock channels are reset.
+        assertThat(coexManager.getCoexUnsafeChannels()).isEmpty();
+    }
+
+    /**
+     * Verifies that CoexManager returns the list of channels specified in the override list of a
+     * corresponding cell band.
+     */
+    @Test
+    public void testGetCoexUnsafeChannels_overrideExists_overrideChannelsAdded()
+            throws Exception {
+        when(mMockResources.getString(R.string.config_wifiCoexTableFilepath))
+                .thenReturn(createFileFromResource(FILEPATH_LTE_40_OVERRIDE).getCanonicalPath());
+        final TelephonyManager telephonyManager = setUpSubIdMocks(0);
+        CoexManager coexManager = createCoexManager();
+        verify(mMockSubscriptionManager).addOnSubscriptionsChangedListener(
+                any(), mCoexSubscriptionsListenerCaptor.capture());
+        mCoexSubscriptionsListenerCaptor.getValue().onSubscriptionsChanged();
+        final ArgumentCaptor<CoexManager.CoexTelephonyCallback> telephonyCallbackCaptor =
+                ArgumentCaptor.forClass(CoexManager.CoexTelephonyCallback.class);
+        verify(telephonyManager).registerTelephonyCallback(any(Executor.class),
+                telephonyCallbackCaptor.capture());
+
+        telephonyCallbackCaptor.getValue().onPhysicalChannelConfigChanged(Arrays.asList(
+                createMockPhysicalChannelConfig(NETWORK_TYPE_LTE, 40, 2399_900, 10_000, 0, 0)
+        ));
+
+        List<CoexUnsafeChannel> unsafeChannels = new ArrayList<>();
+        unsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6));
+        unsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 11));
+        for (int channel : CHANNEL_SET_5_GHZ_20_MHZ) {
+            unsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, channel));
+        }
+        for (int channel : CHANNEL_SET_5_GHZ_40_MHZ) {
+            unsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, channel));
+        }
+        unsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 50));
+        unsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 114));
+        assertThat(coexManager.getCoexUnsafeChannels()).containsExactlyElementsIn(unsafeChannels);
+    }
+
+    /**
+     * Verifies that CoexManager returns the full list of 5GHz CoexUnsafeChannels and SoftAP and
+     * Wifi Direct restrictions if LAA is active for a subscription that has LAA restriction
+     * carrier configs set.
+     */
+    @Test
+    public void testGetCoexUnsafeChannels_LAA_restrict5gSoftApAndWifiDirect()
+            throws Exception {
+        when(mMockCarrierConfigManager.getConfigForSubId(0))
+                .thenReturn(mUnrestrictedBundle);
+        when(mMockCarrierConfigManager.getConfigForSubId(1))
+                .thenReturn(mRestrictedBundle);
+        final TelephonyManager telephonyManager0 = setUpSubIdMocks(0);
+        final TelephonyManager telephonyManager1 = setUpSubIdMocks(1);
+        CoexManager coexManager = createCoexManager();
+        verify(mMockSubscriptionManager).addOnSubscriptionsChangedListener(
+                any(), mCoexSubscriptionsListenerCaptor.capture());
+        mCoexSubscriptionsListenerCaptor.getValue().onSubscriptionsChanged();
+        final ArgumentCaptor<CoexManager.CoexTelephonyCallback> telephonyCallbackCaptor0 =
+                ArgumentCaptor.forClass(CoexManager.CoexTelephonyCallback.class);
+        verify(telephonyManager0).registerTelephonyCallback(any(Executor.class),
+                telephonyCallbackCaptor0.capture());
+        final ArgumentCaptor<CoexManager.CoexTelephonyCallback> telephonyCallbackCaptor1 =
+                ArgumentCaptor.forClass(CoexManager.CoexTelephonyCallback.class);
+        verify(telephonyManager1).registerTelephonyCallback(any(Executor.class),
+                telephonyCallbackCaptor1.capture());
+
+        // Unrestricted sub id gets an LAA channel
+        telephonyCallbackCaptor0.getValue().onPhysicalChannelConfigChanged(Arrays.asList(
+                createMockPhysicalChannelConfig(
+                        NETWORK_TYPE_LTE, AccessNetworkConstants.EutranBand.BAND_46,
+                        5150_000, 1_000, 0, 0)
+        ));
+
+        // Unrestricted sub id LAA channel does not trigger the 5g band restriction.
+        assertThat(coexManager.getCoexUnsafeChannels()).isEmpty();
+        assertThat(coexManager.getCoexRestrictions()).isEqualTo(0);
+
+        // Restricted sub id gets an LAA channel
+        telephonyCallbackCaptor1.getValue().onPhysicalChannelConfigChanged(Arrays.asList(
+                createMockPhysicalChannelConfig(
+                        NETWORK_TYPE_LTE, AccessNetworkConstants.EutranBand.BAND_46,
+                        5150_000, 1_000, 0, 0)
+        ));
+
+        // Restricted sub id LAA channel triggers 5g band restriction.
+        assertThat(coexManager.getCoexUnsafeChannels().stream()
+                .filter(unsafeChannel -> unsafeChannel.getBand() == WIFI_BAND_5_GHZ)
+                .map(unsafeChannel -> unsafeChannel.getChannel()).collect(Collectors.toList()))
+                .containsExactlyElementsIn(CHANNEL_SET_5_GHZ);
+        assertThat(coexManager.getCoexRestrictions() & COEX_RESTRICTION_SOFTAP)
+                .isNotEqualTo(0);
+        assertThat(coexManager.getCoexRestrictions() & COEX_RESTRICTION_WIFI_DIRECT)
+                .isNotEqualTo(0);
+    }
+
+    /**
+     * Verifies that carrier configs changing will update the unsafe channels
+     */
+    @Test
+    public void testGetCoexUnsafeChannels_carrierConfigsChanged_updatesUnsafeChannels() {
+        // Start with no restrictions in the carrier config
+        when(mMockCarrierConfigManager.getConfigForSubId(0)).thenReturn(mUnrestrictedBundle);
+        final TelephonyManager telephonyManager = setUpSubIdMocks(0);
+        CoexManager coexManager = createCoexManager();
+        verify(mMockSubscriptionManager).addOnSubscriptionsChangedListener(
+                any(), mCoexSubscriptionsListenerCaptor.capture());
+        mCoexSubscriptionsListenerCaptor.getValue().onSubscriptionsChanged();
+        final ArgumentCaptor<CoexManager.CoexTelephonyCallback> telephonyCallbackCaptor =
+                ArgumentCaptor.forClass(CoexManager.CoexTelephonyCallback.class);
+        verify(telephonyManager).registerTelephonyCallback(
+                any(Executor.class), telephonyCallbackCaptor.capture());
+        verify(mMockContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
+                any(IntentFilter.class), eq(null), any(Handler.class));
+        telephonyCallbackCaptor.getValue().onPhysicalChannelConfigChanged(Arrays.asList(
+                createMockPhysicalChannelConfig(
+                        NETWORK_TYPE_LTE, AccessNetworkConstants.EutranBand.BAND_46,
+                        5150_000, 1_000, 0, 0)
+        ));
+
+        // Update the carrier config to have restrictions
+        when(mMockCarrierConfigManager.getConfigForSubId(0)).thenReturn(mRestrictedBundle);
+        Intent intent = new Intent();
+        intent.setAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        mBroadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
+
+        // New carrier config values should be reflected in the unsafe channels.
+        assertThat(coexManager.getCoexUnsafeChannels().stream()
+                .filter(unsafeChannel -> unsafeChannel.getBand() == WIFI_BAND_5_GHZ)
+                .map(unsafeChannel -> unsafeChannel.getChannel()).collect(Collectors.toList()))
+                .containsExactlyElementsIn(CHANNEL_SET_5_GHZ);
+        assertThat(coexManager.getCoexRestrictions() & COEX_RESTRICTION_SOFTAP)
+                .isNotEqualTo(0);
+        assertThat(coexManager.getCoexRestrictions() & COEX_RESTRICTION_WIFI_DIRECT)
+                .isNotEqualTo(0);
+    }
+
+    /**
+     * Verifies that unsafe channels are calculated on the aggregated cell channels of each sub id.
+     */
+    @Test
+    public void testGetCoexUnsafeChannels_multipleSubIds_combineUnsafeChannels() throws Exception {
+        when(mMockResources.getString(R.string.config_wifiCoexTableFilepath))
+                .thenReturn(createFileFromResource(FILEPATH_LTE_46_NEIGHBORING).getCanonicalPath());
+        final TelephonyManager telephonyManager0 = setUpSubIdMocks(0);
+        final TelephonyManager telephonyManager1 = setUpSubIdMocks(1);
+        CoexManager coexManager = createCoexManager();
+        verify(mMockSubscriptionManager).addOnSubscriptionsChangedListener(
+                any(), mCoexSubscriptionsListenerCaptor.capture());
+        mCoexSubscriptionsListenerCaptor.getValue().onSubscriptionsChanged();
+        final ArgumentCaptor<CoexManager.CoexTelephonyCallback> telephonyCallbackCaptor0 =
+                ArgumentCaptor.forClass(CoexManager.CoexTelephonyCallback.class);
+        verify(telephonyManager0).registerTelephonyCallback(any(Executor.class),
+                telephonyCallbackCaptor0.capture());
+        final ArgumentCaptor<CoexManager.CoexTelephonyCallback> telephonyCallbackCaptor1 =
+                ArgumentCaptor.forClass(CoexManager.CoexTelephonyCallback.class);
+        verify(telephonyManager1).registerTelephonyCallback(any(Executor.class),
+                telephonyCallbackCaptor1.capture());
+
+
+        // Add a PhysicalChannelConfig for one sub id to conflict with the lower half of 5g
+        telephonyCallbackCaptor0.getValue().onPhysicalChannelConfigChanged(Arrays.asList(
+                createMockPhysicalChannelConfig(NETWORK_TYPE_LTE, 46, 5000_000, 1000_000, 0, 0)
+        ));
+        // Add a PhysicalChannelConfig for another sub id to conflict with the upper half of 5g
+        telephonyCallbackCaptor1.getValue().onPhysicalChannelConfigChanged(Arrays.asList(
+                createMockPhysicalChannelConfig(NETWORK_TYPE_LTE, 46, 6000_000, 1000_000, 0, 0)
+        ));
+
+        // The PhysicalChannelConfigs for both sub ids should cause the entire 5g to be unsafe
+        // excluding the default channel.
+        assertThat(coexManager.getCoexUnsafeChannels()).hasSize(CHANNEL_SET_5_GHZ.size() - 1);
+        assertThat(coexManager.getCoexUnsafeChannels()).doesNotContain(
+                new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 36, -50));
+    }
+
+    /**
+     * Verifies that calling onPhysicalChannelConfigChanged with the same list more than once will
+     * only notify listeners the first time.
+     */
+    @Test
+    public void testOnPhysicalChannelConfigChanged_sameChannels_doesNotUpdateListeners()
+            throws Exception {
+        when(mMockResources.getString(R.string.config_wifiCoexTableFilepath))
+                .thenReturn(createFileFromResource(FILEPATH_LTE_40_NEIGHBORING).getCanonicalPath());
+        final TelephonyManager telephonyManager = setUpSubIdMocks(0);
+        CoexManager coexManager = createCoexManager();
+        verify(mMockSubscriptionManager).addOnSubscriptionsChangedListener(
+                any(), mCoexSubscriptionsListenerCaptor.capture());
+        mCoexSubscriptionsListenerCaptor.getValue().onSubscriptionsChanged();
+        final ArgumentCaptor<CoexManager.CoexTelephonyCallback> telephonyCallbackCaptor =
+                ArgumentCaptor.forClass(CoexManager.CoexTelephonyCallback.class);
+        verify(telephonyManager).registerTelephonyCallback(any(Executor.class),
+                telephonyCallbackCaptor.capture());
+        CoexManager.CoexListener listener = mock(CoexManager.CoexListener.class);
+        coexManager.registerCoexListener(listener);
+        ICoexCallback remoteCallback = mock(ICoexCallback.class);
+        when(remoteCallback.asBinder()).thenReturn(mock(IBinder.class));
+        coexManager.registerRemoteCoexCallback(remoteCallback);
+
+        // Update physical channel configs three times with the same channel
+        telephonyCallbackCaptor.getValue().onPhysicalChannelConfigChanged(Arrays.asList(
+                createMockPhysicalChannelConfig(NETWORK_TYPE_LTE, 40, 2399_900, 10_000, 0, 0)
+        ));
+        telephonyCallbackCaptor.getValue().onPhysicalChannelConfigChanged(Arrays.asList(
+                createMockPhysicalChannelConfig(NETWORK_TYPE_LTE, 40, 2399_900, 10_000, 0, 0)
+        ));
+        telephonyCallbackCaptor.getValue().onPhysicalChannelConfigChanged(Arrays.asList(
+                createMockPhysicalChannelConfig(NETWORK_TYPE_LTE, 40, 2399_900, 10_000, 0, 0)
+        ));
+        // Update physical channel configs with a different channel now
+        telephonyCallbackCaptor.getValue().onPhysicalChannelConfigChanged(Arrays.asList(
+                createMockPhysicalChannelConfig(NETWORK_TYPE_LTE, 41, 2399_900, 10_000, 0, 0)
+        ));
+        // Update physical channel configs back to the first channel
+        telephonyCallbackCaptor.getValue().onPhysicalChannelConfigChanged(Arrays.asList(
+                createMockPhysicalChannelConfig(NETWORK_TYPE_LTE, 40, 2399_900, 10_000, 0, 0)
+        ));
+
+        // Callbacks should be notified three times:
+        //     1) no list -> list 1
+        //     2) list 1 -> list 2
+        //     3) list 2 -> list 1
+        verify(mMockWifiNative, times(3)).setCoexUnsafeChannels(any(), anyInt());
+        verify(listener, times(3)).onCoexUnsafeChannelsChanged();
+        // The remote callback has an extra call since it was notified on registration.
+        verify(remoteCallback, times(4)).onCoexUnsafeChannelsChanged(any(), anyInt());
+    }
+}
diff --git a/service/tests/wifitests/src/com/android/server/wifi/coex/CoexUtilsTest.java b/service/tests/wifitests/src/com/android/server/wifi/coex/CoexUtilsTest.java
new file mode 100644
index 0000000..0e43a47
--- /dev/null
+++ b/service/tests/wifitests/src/com/android/server/wifi/coex/CoexUtilsTest.java
@@ -0,0 +1,427 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.coex;
+
+import static android.net.wifi.CoexUnsafeChannel.POWER_CAP_NONE;
+import static android.net.wifi.WifiScanner.WIFI_BAND_24_GHZ;
+import static android.net.wifi.WifiScanner.WIFI_BAND_5_GHZ;
+
+import static com.android.server.wifi.coex.CoexUtils.INVALID_FREQ;
+import static com.android.server.wifi.coex.CoexUtils.get2gHarmonicCoexUnsafeChannels;
+import static com.android.server.wifi.coex.CoexUtils.get5gHarmonicCoexUnsafeChannels;
+import static com.android.server.wifi.coex.CoexUtils.getIntermodCoexUnsafeChannels;
+import static com.android.server.wifi.coex.CoexUtils.getLowerFreqKhz;
+import static com.android.server.wifi.coex.CoexUtils.getNeighboringCoexUnsafeChannels;
+import static com.android.server.wifi.coex.CoexUtils.getOffsetChannel;
+import static com.android.server.wifi.coex.CoexUtils.getUpperFreqKhz;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.net.wifi.CoexUnsafeChannel;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.modules.utils.build.SdkLevel;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.coex.CoexUtils}.
+ */
+@SmallTest
+public class CoexUtilsTest {
+
+    private int getHarmonicUlFreqKhz(int unsafeLowerKhz, int unsafeUpperKhz, int harmonicDeg) {
+        return (unsafeLowerKhz + unsafeUpperKhz) / (harmonicDeg * 2);
+    }
+
+    private int getHarmonicUlBandwidthKhz(int unsafeLowerKhz, int unsafeUpperKhz, int harmonicDeg) {
+        return (unsafeUpperKhz - unsafeLowerKhz) / harmonicDeg;
+    }
+
+    @Before
+    public void setUp() {
+        assumeTrue(SdkLevel.isAtLeastS());
+    }
+
+    /**
+     * Verifies that getNeighboringCoexUnsafeChannels returns an empty set if there is no overlap.
+     */
+    @Test
+    public void testGetNeighboringCoexUnsafeChannels_noOverlap_returnsEmptySet() {
+        // Below/Above 2.4GHz
+        assertThat(getNeighboringCoexUnsafeChannels(getLowerFreqKhz(1, WIFI_BAND_24_GHZ) - 100_000,
+                50_000, 50_000, POWER_CAP_NONE)).isEmpty();
+        assertThat(getNeighboringCoexUnsafeChannels(getUpperFreqKhz(14, WIFI_BAND_24_GHZ) + 100_000,
+                50_000, 50_000, POWER_CAP_NONE)).isEmpty();
+        assertThat(getNeighboringCoexUnsafeChannels(2595_000,
+                50_000, 50_000, POWER_CAP_NONE)).isEmpty();
+
+        // Below/Above 5GHz
+        assertThat(getNeighboringCoexUnsafeChannels(getLowerFreqKhz(32, WIFI_BAND_5_GHZ) - 100_000,
+                50_000, 50_000, POWER_CAP_NONE)).isEmpty();
+        assertThat(getNeighboringCoexUnsafeChannels(getUpperFreqKhz(173, WIFI_BAND_5_GHZ) + 100_000,
+                50_000, 50_000, POWER_CAP_NONE)).isEmpty();
+    }
+
+    /**
+     * Verifies that getNeighboringCoexUnsafeChannels returns the correct subset of 2.4GHz channels
+     * from interference above and below the band.
+     */
+    @Test
+    public void testGetNeighboringCoexUnsafeChannels_2g_returnsCorrectOverlap() {
+        // Test channel 7 from below
+        HashSet<CoexUnsafeChannel> lowerCoexUnsafeChannels = new HashSet<>();
+        for (int i = 1; i <= 7; i++) {
+            lowerCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_24_GHZ, i));
+        }
+        assertThat(getNeighboringCoexUnsafeChannels(2401_000,
+                0, 2431_000 - 2401_000 + 1, POWER_CAP_NONE))
+                .containsExactlyElementsIn(lowerCoexUnsafeChannels);
+
+        // Test channel 7 from above
+        HashSet<CoexUnsafeChannel> upperCoexUnsafeChannels = new HashSet<>();
+        for (int i = 7; i <= 14; i++) {
+            upperCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_24_GHZ, i));
+        }
+        assertThat(getNeighboringCoexUnsafeChannels(2495_000,
+                0, 2495_000 - 2453_000 + 1, POWER_CAP_NONE))
+                .containsExactlyElementsIn(upperCoexUnsafeChannels);
+    }
+
+    /**
+     * Verifies that getNeighboringCoexUnsafeChannels returns the correct subset of 5GHz channels
+     * from interference above and below the band.
+     */
+    @Test
+    public void testGetNeighboringCoexUnsafeChannels_5g_returnsCorrectOverlap() {
+        // Test channel 100 from below
+        HashSet<CoexUnsafeChannel> lowerCoexUnsafeChannels = new HashSet<>();
+        for (int i = 32; i <= 64; i += 2) {
+            lowerCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, i));
+        }
+        lowerCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 68));
+        lowerCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 96));
+        lowerCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 100));
+        // Verify that parent channels above channel 100 are included
+        lowerCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 102));
+        lowerCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 106));
+        lowerCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 114));
+
+        assertThat(getNeighboringCoexUnsafeChannels(5150_000,
+                0, 5490_000 - 5150_000 + 1, POWER_CAP_NONE))
+                .containsExactlyElementsIn(lowerCoexUnsafeChannels);
+
+        // Test channel 64 from above
+        HashSet<CoexUnsafeChannel> upperCoexUnsafeChannels = new HashSet<>();
+        upperCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 64));
+        upperCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 68));
+        upperCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 96));
+        for (int i = 100; i <= 128; i += 2) {
+            upperCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, i));
+        }
+        for (int i = 132; i <= 144; i += 2) {
+            upperCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, i));
+        }
+        for (int i = 149; i <= 161; i += 2) {
+            upperCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, i));
+        }
+        upperCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 165));
+        upperCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 169));
+        upperCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 173));
+        // Verify that parent channels below channel 64 are included
+        upperCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 50));
+        upperCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 58));
+        upperCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 62));
+
+        assertThat(getNeighboringCoexUnsafeChannels(5875_000,
+                0, 5875_000 - 5330_000 + 1, POWER_CAP_NONE))
+                .containsExactlyElementsIn(upperCoexUnsafeChannels);
+    }
+
+    /**
+     * Verifies that getLowerFreqKhz() returns the correct values for an example set of inputs.
+     */
+    @Test
+    public void testGetLowerFreqKhz_returnsCorrectValues() {
+        assertThat(getLowerFreqKhz(1, WIFI_BAND_24_GHZ)).isEqualTo(2401_000);
+        assertThat(getLowerFreqKhz(4, WIFI_BAND_24_GHZ)).isEqualTo(2416_000);
+        assertThat(getLowerFreqKhz(6, WIFI_BAND_24_GHZ)).isEqualTo(2426_000);
+        assertThat(getLowerFreqKhz(9, WIFI_BAND_24_GHZ)).isEqualTo(2441_000);
+        assertThat(getLowerFreqKhz(0, WIFI_BAND_24_GHZ)).isEqualTo(INVALID_FREQ);
+        assertThat(getLowerFreqKhz(14, WIFI_BAND_24_GHZ)).isEqualTo(2473_000);
+        assertThat(getLowerFreqKhz(32, WIFI_BAND_5_GHZ)).isEqualTo(5150_000);
+        assertThat(getLowerFreqKhz(50, WIFI_BAND_5_GHZ)).isEqualTo(5170_000);
+        assertThat(getLowerFreqKhz(64, WIFI_BAND_5_GHZ)).isEqualTo(5310_000);
+        assertThat(getLowerFreqKhz(96, WIFI_BAND_5_GHZ)).isEqualTo(5470_000);
+        assertThat(getLowerFreqKhz(120, WIFI_BAND_5_GHZ)).isEqualTo(5590_000);
+        assertThat(getLowerFreqKhz(0, WIFI_BAND_5_GHZ)).isEqualTo(INVALID_FREQ);
+    }
+
+    /**
+     * Verifies that getUpperFreqKhz() returns the correct values for an example set of inputs.
+     */
+    @Test
+    public void testGetUpperFreqKhz_returnsCorrectValues() {
+        assertThat(getUpperFreqKhz(1, WIFI_BAND_24_GHZ)).isEqualTo(2423_000);
+        assertThat(getUpperFreqKhz(4, WIFI_BAND_24_GHZ)).isEqualTo(2438_000);
+        assertThat(getUpperFreqKhz(6, WIFI_BAND_24_GHZ)).isEqualTo(2448_000);
+        assertThat(getUpperFreqKhz(9, WIFI_BAND_24_GHZ)).isEqualTo(2463_000);
+        assertThat(getUpperFreqKhz(14, WIFI_BAND_24_GHZ)).isEqualTo(2495_000);
+        assertThat(getUpperFreqKhz(32, WIFI_BAND_5_GHZ)).isEqualTo(5170_000);
+        assertThat(getUpperFreqKhz(50, WIFI_BAND_5_GHZ)).isEqualTo(5330_000);
+        assertThat(getUpperFreqKhz(64, WIFI_BAND_5_GHZ)).isEqualTo(5330_000);
+        assertThat(getUpperFreqKhz(96, WIFI_BAND_5_GHZ)).isEqualTo(5490_000);
+        assertThat(getUpperFreqKhz(120, WIFI_BAND_5_GHZ)).isEqualTo(5610_000);
+    }
+
+    /**
+     * Verifies that get2gHarmonicUnsafeChannels returns the correct subset of 2.4GHz channels
+     * from interference above, below, and in the middle of the band.
+     */
+    @Test
+    public void testGet2gHarmonicUnsafeChannels_exampleInputs_returnsCorrectOverlap() {
+        final int harmonicDeg = 2;
+        final int maxOverlap = 50;
+        // Test lower channels channels 1 to 7 with an overlap of 50%.
+        // Channels 6, 7 should not meet the overlap.
+        HashSet<CoexUnsafeChannel> lowerCoexUnsafeChannels = new HashSet<>();
+        for (int i = 1; i <= 5; i += 1) {
+            lowerCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_24_GHZ, i));
+        }
+        int unsafeLowerKhz = getLowerFreqKhz(1, WIFI_BAND_24_GHZ) - 5_000;
+        int unsafeUpperKhz = getLowerFreqKhz(7, WIFI_BAND_24_GHZ) + 5_000;
+        assertThat(get2gHarmonicCoexUnsafeChannels(
+                getHarmonicUlFreqKhz(unsafeLowerKhz, unsafeUpperKhz, harmonicDeg),
+                getHarmonicUlBandwidthKhz(unsafeLowerKhz, unsafeUpperKhz, harmonicDeg),
+                harmonicDeg, maxOverlap, POWER_CAP_NONE))
+                .containsExactlyElementsIn(lowerCoexUnsafeChannels);
+
+        // Test upper channels 7 to 14 with an overlap of 50%.
+        // Channels 7, 8 should not meet the overlap.
+        HashSet<CoexUnsafeChannel> upperCoexUnsafeChannels = new HashSet<>();
+        for (int i = 9; i <= 14; i += 1) {
+            upperCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_24_GHZ, i));
+        }
+        unsafeLowerKhz = getUpperFreqKhz(7, WIFI_BAND_24_GHZ) - 5_000;
+        unsafeUpperKhz = getUpperFreqKhz(14, WIFI_BAND_24_GHZ) + 5_000;
+        assertThat(get2gHarmonicCoexUnsafeChannels(
+                getHarmonicUlFreqKhz(unsafeLowerKhz, unsafeUpperKhz, harmonicDeg),
+                getHarmonicUlBandwidthKhz(unsafeLowerKhz, unsafeUpperKhz, harmonicDeg),
+                harmonicDeg, maxOverlap, POWER_CAP_NONE))
+                .containsExactlyElementsIn(upperCoexUnsafeChannels);
+
+        // Test middle channels 3 to 10 with an overlap of 50%.
+        // Channels 3, 4, 9, 10 should not meet the overlap.
+        HashSet<CoexUnsafeChannel> middleCoexUnsafeChannels = new HashSet<>();
+        for (int i = 5; i <= 8; i += 1) {
+            middleCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_24_GHZ, i));
+        }
+        unsafeLowerKhz = getUpperFreqKhz(3, WIFI_BAND_24_GHZ) - 5_000;
+        unsafeUpperKhz = getLowerFreqKhz(10, WIFI_BAND_24_GHZ) + 5_000;
+        assertThat(get2gHarmonicCoexUnsafeChannels(
+                getHarmonicUlFreqKhz(unsafeLowerKhz, unsafeUpperKhz, harmonicDeg),
+                getHarmonicUlBandwidthKhz(unsafeLowerKhz, unsafeUpperKhz, harmonicDeg),
+                harmonicDeg, maxOverlap, POWER_CAP_NONE))
+                .containsExactlyElementsIn(middleCoexUnsafeChannels);
+    }
+
+    @Test
+    public void testGet2gHarmonicUnsafeChannels_overlapDoesNotMeetThreshold_returnsNoChannels() {
+        final int harmonicDeg = 3;
+        final int maxOverlap = 100;
+        int unsafeUpperKhz = getUpperFreqKhz(14, WIFI_BAND_24_GHZ);
+        int unsafeLowerKhz = unsafeUpperKhz - 5_000;
+        assertThat(get2gHarmonicCoexUnsafeChannels(
+                getHarmonicUlFreqKhz(unsafeLowerKhz, unsafeUpperKhz, harmonicDeg),
+                getHarmonicUlBandwidthKhz(unsafeLowerKhz, unsafeUpperKhz, harmonicDeg),
+                harmonicDeg, maxOverlap, POWER_CAP_NONE))
+                .isEmpty();
+    }
+
+    /**
+     * Verifies that get5gHarmonicCoexUnsafeChannels returns the correct subset of 5GHz channels
+     * from interference above, below, and in the middle of the band.
+     */
+    @Test
+    public void testGet5gHarmonicCoexUnsafeChannels_exampleInputs_returnsCorrectOverlap() {
+        final int harmonicDeg = 2;
+        final int maxOverlap = 50;
+        // Test lower channels 32 to 44 with an overlap of 50%.
+        // Parent channel 50 should not meet the overlap.
+        int unsafeLowerKhz = getLowerFreqKhz(32, WIFI_BAND_5_GHZ);
+        int unsafeUpperKhz = getUpperFreqKhz(44, WIFI_BAND_5_GHZ);
+
+        HashSet<CoexUnsafeChannel> lowerCoexUnsafeChannels = new HashSet<>();
+        for (int i = 32; i <= 46; i += 2) {
+            lowerCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, i));
+        }
+        assertThat(get5gHarmonicCoexUnsafeChannels(
+                getHarmonicUlFreqKhz(unsafeLowerKhz, unsafeUpperKhz, harmonicDeg),
+                getHarmonicUlBandwidthKhz(unsafeLowerKhz, unsafeUpperKhz, harmonicDeg),
+                harmonicDeg, maxOverlap, POWER_CAP_NONE))
+                .containsExactlyElementsIn(lowerCoexUnsafeChannels);
+
+        // Test upper channels 120 to 173 with an overlap of 50%.
+        // Parent channel 114 should not meet the overlap.
+        unsafeLowerKhz = getLowerFreqKhz(120, WIFI_BAND_5_GHZ);
+        unsafeUpperKhz = getUpperFreqKhz(173, WIFI_BAND_5_GHZ);
+
+        HashSet<CoexUnsafeChannel> upperCoexUnsafeChannels = new HashSet<>();
+        for (int i = 118; i <= 128; i += 2) {
+            upperCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, i));
+        }
+        for (int i = 132; i <= 144; i += 2) {
+            upperCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, i));
+        }
+        for (int i = 149; i <= 161; i += 2) {
+            upperCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, i));
+        }
+        upperCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 165));
+        upperCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 169));
+        upperCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 173));
+        assertThat(get5gHarmonicCoexUnsafeChannels(
+                getHarmonicUlFreqKhz(unsafeLowerKhz, unsafeUpperKhz, harmonicDeg),
+                getHarmonicUlBandwidthKhz(unsafeLowerKhz, unsafeUpperKhz, harmonicDeg),
+                harmonicDeg, maxOverlap, POWER_CAP_NONE))
+                .containsExactlyElementsIn(upperCoexUnsafeChannels);
+
+        // Test middle channels 64 to 100 with an overlap of 50%.
+        // Parent channels 50, 58, 106, 114 should not meet the overlap.
+        unsafeLowerKhz = getLowerFreqKhz(64, WIFI_BAND_5_GHZ);
+        unsafeUpperKhz = getUpperFreqKhz(100, WIFI_BAND_5_GHZ);
+
+        HashSet<CoexUnsafeChannel> middleCoexUnsafeChannels = new HashSet<>();
+        middleCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 62));
+        middleCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 64));
+        middleCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 68));
+        middleCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 96));
+        middleCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 100));
+        middleCoexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 102));
+        assertThat(get5gHarmonicCoexUnsafeChannels(
+                getHarmonicUlFreqKhz(unsafeLowerKhz, unsafeUpperKhz, harmonicDeg),
+                getHarmonicUlBandwidthKhz(unsafeLowerKhz, unsafeUpperKhz, harmonicDeg),
+                harmonicDeg, maxOverlap, POWER_CAP_NONE))
+                .containsExactlyElementsIn(middleCoexUnsafeChannels);
+    }
+
+    /**
+     * Verifies that get5gHarmonicCoexUnsafeChannels returns no channels if the interference lands
+     * in between channel 68 and 96, even though it is in the middle of the band.
+     */
+    @Test
+    public void testGet5gHarmonicCoexUnsafeChannels_betweenChan68andChan96_returnsNoChannels() {
+        final int unsafeLowerKhz = getUpperFreqKhz(68, WIFI_BAND_5_GHZ);
+        final int unsafeUpperKhz = getLowerFreqKhz(96, WIFI_BAND_5_GHZ);
+        final int harmonicDeg = 2;
+        final int maxOverlap = 50;
+
+        assertThat(get5gHarmonicCoexUnsafeChannels(
+                getHarmonicUlFreqKhz(unsafeLowerKhz, unsafeUpperKhz, harmonicDeg),
+                getHarmonicUlBandwidthKhz(unsafeLowerKhz, unsafeUpperKhz, harmonicDeg),
+                harmonicDeg, maxOverlap, POWER_CAP_NONE))
+                .isEmpty();
+    }
+
+    /**
+     * Verifies that getIntermodCoexUnsafeChannels returns the correct subset of 2.4GHz channels
+     * for the example channel 3350 of LTE Band 7.
+     */
+    @Test
+    public void testGet2gIntermodUnsafeChannels_channel3350Example_returnsCorrectWifiChannels() {
+        int dlFreqKhz = 2680_000;
+        int ulFreqKhz = 2560_000;
+        int bandwidthKhz = 10_000;
+        int maxOverlap = 100;
+
+        Set<CoexUnsafeChannel> coexUnsafeChannels = new HashSet<>();
+        for (int channel = 4; channel <= 9; channel += 1) {
+            coexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_24_GHZ, channel));
+        }
+        // Includes channel 6 but not channel 11
+        assertThat(getIntermodCoexUnsafeChannels(ulFreqKhz, bandwidthKhz, dlFreqKhz, bandwidthKhz,
+                2, -1, maxOverlap, WIFI_BAND_24_GHZ, POWER_CAP_NONE))
+                .containsExactlyElementsIn(coexUnsafeChannels);
+    }
+
+    /**
+     * Verifies that getIntermodCoexUnsafeChannels returns the correct subset of 5GHz channels
+     * for cell channels below the wifi band.
+     */
+    @Test
+    public void testGet5gIntermodUnsafeChannels_cellBelowWifiBand_returnsCorrectWifiChannels() {
+        int dlFreqKhz = 3280_000;
+        int ulFreqKhz = 2000_000;
+        int bandwidthKhz = 10_000;
+        int maxOverlap = 100;
+
+        Set<CoexUnsafeChannel> coexUnsafeChannels = new HashSet<>();
+        coexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 54));
+        coexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 56));
+        coexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 58));
+        coexUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 50));
+        assertThat(getIntermodCoexUnsafeChannels(ulFreqKhz, bandwidthKhz, dlFreqKhz, bandwidthKhz,
+                -1, 1, maxOverlap, WIFI_BAND_5_GHZ, POWER_CAP_NONE))
+                .containsExactlyElementsIn(coexUnsafeChannels);
+    }
+
+    /**
+     * Verifies the behavior of getOffsetChannel from multiple sample inputs.
+     */
+    @Test
+    public void testGetOffsetChannel_differentChannelStepSize_returnsChannels() {
+        // Positive direction
+        assertThat(getOffsetChannel(10, 1_000, 1))
+                .isEqualTo(10);
+        assertThat(getOffsetChannel(10, 5_000, 1))
+                .isEqualTo(10);
+        assertThat(getOffsetChannel(10, 5_001, 1))
+                .isEqualTo(11);
+        assertThat(getOffsetChannel(10, 12_000, 1))
+                .isEqualTo(12);
+        // Positive direction different step sizes
+        assertThat(getOffsetChannel(10, 21_000, 2))
+                .isEqualTo(14);
+        assertThat(getOffsetChannel(10, 21_000, 3))
+                .isEqualTo(13);
+        assertThat(getOffsetChannel(10, 21_000, 4))
+                .isEqualTo(14);
+
+        // Negative direction
+        assertThat(getOffsetChannel(10, -1_000, 1))
+                .isEqualTo(10);
+        assertThat(getOffsetChannel(10, -5_000, 1))
+                .isEqualTo(10);
+        assertThat(getOffsetChannel(10, -5_001, 1))
+                .isEqualTo(9);
+        assertThat(getOffsetChannel(10, -12_000, 1))
+                .isEqualTo(8);
+        // Negative direction different step sizes
+        assertThat(getOffsetChannel(10, -21_000, 2))
+                .isEqualTo(6);
+        assertThat(getOffsetChannel(10, -21_000, 3))
+                .isEqualTo(7);
+        assertThat(getOffsetChannel(10, -21_000, 4))
+                .isEqualTo(6);
+    }
+}
diff --git a/service/tests/wifitests/src/com/android/server/wifi/hotspot2/ANQPDataTest.java b/service/tests/wifitests/src/com/android/server/wifi/hotspot2/ANQPDataTest.java
index a467f82..7016f65 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/hotspot2/ANQPDataTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/hotspot2/ANQPDataTest.java
@@ -17,6 +17,7 @@
 package com.android.server.wifi.hotspot2;
 
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.when;
 import static org.mockito.MockitoAnnotations.initMocks;
@@ -26,12 +27,25 @@
 import com.android.server.wifi.Clock;
 import com.android.server.wifi.WifiBaseTest;
 import com.android.server.wifi.hotspot2.anqp.ANQPElement;
+import com.android.server.wifi.hotspot2.anqp.CellularNetwork;
 import com.android.server.wifi.hotspot2.anqp.Constants;
+import com.android.server.wifi.hotspot2.anqp.HSWanMetricsElement;
+import com.android.server.wifi.hotspot2.anqp.I18Name;
+import com.android.server.wifi.hotspot2.anqp.ThreeGPPNetworkElement;
+import com.android.server.wifi.hotspot2.anqp.VenueNameElement;
+import com.android.server.wifi.hotspot2.anqp.VenueUrlElement;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
 
+import java.net.URL;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 
 /**
@@ -43,6 +57,12 @@
 @SmallTest
 public class ANQPDataTest extends WifiBaseTest {
     @Mock Clock mClock;
+    private static final String TEST_LANGUAGE = "en";
+    private static final Locale TEST_LOCALE = Locale.forLanguageTag(TEST_LANGUAGE);
+    private static final String TEST_VENUE_NAME1 = "Venue1";
+    private static final String TEST_VENUE_NAME2 = "Venue2";
+    private static final String TEST_VENUE_URL1 = "https://www.google.com/";
+    private static final long TEST_CLOCK_MILLISEOCONDS = 10000L;
 
     /**
      * Sets up test.
@@ -77,4 +97,130 @@
         assertFalse(data.expired(ANQPData.DATA_LIFETIME_MILLISECONDS - 1));
         assertTrue(data.expired(ANQPData.DATA_LIFETIME_MILLISECONDS));
     }
+
+    private URL createUrlFromString(String stringUrl) {
+        URL url;
+        try {
+            url = new URL(stringUrl);
+        } catch (java.net.MalformedURLException e) {
+            return null;
+        }
+        return url;
+    }
+
+    /**
+     * Verify creation of ANQPData with data elements and then update the entry.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void createWithElementsAndUpdate() throws Exception {
+        Map<Constants.ANQPElementType, ANQPElement> anqpList1 = new HashMap<>();
+        List<I18Name> nameList = new ArrayList<>();
+        nameList.add(new I18Name(TEST_LANGUAGE, TEST_LOCALE, TEST_VENUE_NAME1));
+        VenueNameElement venueNameElement = new VenueNameElement(nameList);
+
+        // Add one ANQP element
+        anqpList1.put(Constants.ANQPElementType.ANQPVenueName, venueNameElement);
+        ANQPData data = new ANQPData(mClock, anqpList1);
+        assertNotNull(data);
+        assertFalse(data.getElements().isEmpty());
+        assertTrue(data.getElements().get(Constants.ANQPElementType.ANQPVenueName)
+                .equals(venueNameElement));
+
+        // Add another ANQP element to the same entry
+        Map<Constants.ANQPElementType, ANQPElement> anqpList2 = new HashMap<>();
+        Map<Integer, URL> urlList = new HashMap<>();
+        urlList.put(Integer.valueOf(1), createUrlFromString(TEST_VENUE_URL1));
+        VenueUrlElement venueUrlElement = new VenueUrlElement(urlList);
+        anqpList2.put(Constants.ANQPElementType.ANQPVenueUrl, venueUrlElement);
+
+        // Update the name
+        nameList = new ArrayList<>();
+        nameList.add(new I18Name(TEST_LANGUAGE, TEST_LOCALE, TEST_VENUE_NAME2));
+        venueNameElement = new VenueNameElement(nameList);
+        anqpList2.put(Constants.ANQPElementType.ANQPVenueName, venueNameElement);
+
+        data.update(anqpList2);
+        assertTrue(data.getElements().get(Constants.ANQPElementType.ANQPVenueName)
+                .equals(venueNameElement));
+        assertTrue(data.getElements().get(Constants.ANQPElementType.ANQPVenueUrl)
+                .equals(venueUrlElement));
+    }
+
+    /**
+     * Verify the correct data lifetime of an ANQP entry
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testDataLifetime() throws Exception {
+        Map<Constants.ANQPElementType, ANQPElement> anqpList = new HashMap<>();
+        List<I18Name> nameList = new ArrayList<>();
+        nameList.add(new I18Name(TEST_LANGUAGE, TEST_LOCALE, TEST_VENUE_NAME1));
+        VenueNameElement venueNameElement = new VenueNameElement(nameList);
+        CellularNetwork cellularNetwork =
+                new CellularNetwork(Arrays.asList(new String[]{"123456"}));
+        ThreeGPPNetworkElement threeGPPNetworkElement =
+                new ThreeGPPNetworkElement(Arrays.asList(new CellularNetwork[]{cellularNetwork}));
+        // Setup an uninitialized WAN Metrics element (or initialized with 0's)
+        ByteBuffer buffer = ByteBuffer.allocate(HSWanMetricsElement.EXPECTED_BUFFER_SIZE);
+        buffer.put(new byte[HSWanMetricsElement.EXPECTED_BUFFER_SIZE]);
+        buffer.position(0);
+        HSWanMetricsElement wanMetricsElement = HSWanMetricsElement.parse(buffer);
+
+        // Add ANQP elements
+        anqpList.put(Constants.ANQPElementType.ANQPVenueName, venueNameElement);
+        anqpList.put(Constants.ANQPElementType.ANQP3GPPNetwork, threeGPPNetworkElement);
+        anqpList.put(Constants.ANQPElementType.HSWANMetrics, wanMetricsElement);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(10000L);
+
+        // Data life should be DATA_LIFETIME_MILLISECONDS
+        ANQPData data = new ANQPData(mClock, anqpList);
+        assertNotNull(data);
+        assertFalse(data.getElements().isEmpty());
+        assertFalse(
+                data.expired(TEST_CLOCK_MILLISEOCONDS + ANQPData.DATA_LIFETIME_MILLISECONDS - 1));
+        assertTrue(data.expired(TEST_CLOCK_MILLISEOCONDS + ANQPData.DATA_LIFETIME_MILLISECONDS));
+
+        wanMetricsElement = new HSWanMetricsElement(HSWanMetricsElement.LINK_STATUS_UP, true, false,
+                10000, 10000, 50, 50, 0);
+        anqpList.put(Constants.ANQPElementType.HSWANMetrics, wanMetricsElement);
+
+        // Data life should be DATA_LIFETIME_MILLISECONDS
+        data = new ANQPData(mClock, anqpList);
+        assertNotNull(data);
+        assertFalse(data.getElements().isEmpty());
+        assertFalse(
+                data.expired(TEST_CLOCK_MILLISEOCONDS + ANQPData.DATA_LIFETIME_MILLISECONDS - 1));
+        assertTrue(data.expired(TEST_CLOCK_MILLISEOCONDS + ANQPData.DATA_LIFETIME_MILLISECONDS));
+
+        // The following checks should result in a short data lifetime
+        wanMetricsElement = new HSWanMetricsElement(HSWanMetricsElement.LINK_STATUS_DOWN, true,
+                false, 10000, 10000, 50, 50, 0);
+        anqpList.put(Constants.ANQPElementType.HSWANMetrics, wanMetricsElement);
+
+        // Data life should be DATA_SHORT_LIFETIME_MILLISECONDS
+        data = new ANQPData(mClock, anqpList);
+        assertNotNull(data);
+        assertFalse(data.getElements().isEmpty());
+        assertFalse(data.expired(
+                TEST_CLOCK_MILLISEOCONDS + ANQPData.DATA_SHORT_LIFETIME_MILLISECONDS - 1));
+        assertTrue(
+                data.expired(TEST_CLOCK_MILLISEOCONDS + ANQPData.DATA_SHORT_LIFETIME_MILLISECONDS));
+
+        // The following checks should result in a short data lifetime
+        wanMetricsElement = new HSWanMetricsElement(HSWanMetricsElement.LINK_STATUS_UP, true, true,
+                10000, 10000, 50, 50, 0);
+        anqpList.put(Constants.ANQPElementType.HSWANMetrics, wanMetricsElement);
+
+        // Data life should be DATA_SHORT_LIFETIME_MILLISECONDS
+        data = new ANQPData(mClock, anqpList);
+        assertNotNull(data);
+        assertFalse(data.getElements().isEmpty());
+        assertFalse(data.expired(
+                TEST_CLOCK_MILLISEOCONDS + ANQPData.DATA_SHORT_LIFETIME_MILLISECONDS - 1));
+        assertTrue(
+                data.expired(TEST_CLOCK_MILLISEOCONDS + ANQPData.DATA_SHORT_LIFETIME_MILLISECONDS));
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/hotspot2/ANQPRequestManagerTest.java b/service/tests/wifitests/src/com/android/server/wifi/hotspot2/ANQPRequestManagerTest.java
index 2f84c0d..e13920d 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/hotspot2/ANQPRequestManagerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/hotspot2/ANQPRequestManagerTest.java
@@ -361,4 +361,15 @@
         assertTrue(mManager.requestANQPElements(TEST_BSSID, TEST_ANQP_KEY, true,
                 NetworkDetail.HSRelease.R3));
     }
+
+    /**
+     * Verify that the Venue URL ANQP element is being requested when called.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void requestVenueUrlAnqpElement() throws Exception {
+        when(mHandler.requestVenueUrlAnqp(TEST_BSSID)).thenReturn(true);
+        assertTrue(mManager.requestVenueUrlAnqpElement(TEST_BSSID, TEST_ANQP_KEY));
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/hotspot2/AnqpCacheTest.java b/service/tests/wifitests/src/com/android/server/wifi/hotspot2/AnqpCacheTest.java
index c3c96cd..c36ef31 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/hotspot2/AnqpCacheTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/hotspot2/AnqpCacheTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wifi.hotspot2;
 
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -26,13 +27,23 @@
 
 import com.android.server.wifi.Clock;
 import com.android.server.wifi.WifiBaseTest;
-import com.android.server.wifi.hotspot2.ANQPData;
-import com.android.server.wifi.hotspot2.AnqpCache;
+import com.android.server.wifi.hotspot2.anqp.ANQPElement;
+import com.android.server.wifi.hotspot2.anqp.Constants;
+import com.android.server.wifi.hotspot2.anqp.I18Name;
+import com.android.server.wifi.hotspot2.anqp.VenueNameElement;
+import com.android.server.wifi.hotspot2.anqp.VenueUrlElement;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
 
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
 /**
  * Unit tests for {@link com.android.server.wifi.hotspot2.AnqpCache}.
  *
@@ -42,6 +53,13 @@
 @SmallTest
 public class AnqpCacheTest extends WifiBaseTest {
     private static final ANQPNetworkKey ENTRY_KEY = new ANQPNetworkKey("test", 0L, 0L, 1);
+    private static final String TEST_LANGUAGE = "en";
+    private static final Locale TEST_LOCALE = Locale.forLanguageTag(TEST_LANGUAGE);
+    private static final String TEST_VENUE_NAME1 = "Venue1";
+    private static final String TEST_VENUE_NAME2 = "Venue2";
+    private static final String TEST_VENUE_URL1 = "https://www.google.com/";
+    private static final String TEST_VENUE_URL2 = "https://www.android.com/";
+    private static final String TEST_VENUE_URL3 = "https://support.google.com/";
 
     @Mock Clock mClock;
     AnqpCache mCache;
@@ -112,4 +130,52 @@
         mCache.flush();
         assertNull(mCache.getEntry(ENTRY_KEY));
     }
+
+    private URL createUrlFromString(String stringUrl) {
+        URL url;
+        try {
+            url = new URL(stringUrl);
+        } catch (java.net.MalformedURLException e) {
+            return null;
+        }
+        return url;
+    }
+
+    /**
+     * Verify expectation for addOrUpdateEntry and getEntry.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void addThenUpdateAndGetEntry() throws Exception {
+        Map<Constants.ANQPElementType, ANQPElement> anqpList1 = new HashMap<>();
+        List<I18Name> nameList = new ArrayList<>();
+        nameList.add(new I18Name(TEST_LANGUAGE, TEST_LOCALE, TEST_VENUE_NAME1));
+        nameList.add(new I18Name(TEST_LANGUAGE, TEST_LOCALE, TEST_VENUE_NAME2));
+        VenueNameElement venueNameElement = new VenueNameElement(nameList);
+
+        // Add one ANQP element
+        anqpList1.put(Constants.ANQPElementType.ANQPVenueName, venueNameElement);
+        mCache.addOrUpdateEntry(ENTRY_KEY, anqpList1);
+        ANQPData data = mCache.getEntry(ENTRY_KEY);
+        assertNotNull(data);
+        assertFalse(data.getElements().isEmpty());
+        assertTrue(data.getElements().get(Constants.ANQPElementType.ANQPVenueName)
+                .equals(venueNameElement));
+
+        // Add another ANQP element to the same entry
+        Map<Constants.ANQPElementType, ANQPElement> anqpList2 = new HashMap<>();
+        Map<Integer, URL> urlList = new HashMap<>();
+        urlList.put(Integer.valueOf(1), createUrlFromString(TEST_VENUE_URL1));
+        urlList.put(Integer.valueOf(2), createUrlFromString(TEST_VENUE_URL2));
+        urlList.put(Integer.valueOf(4), createUrlFromString(TEST_VENUE_URL3));
+        VenueUrlElement venueUrlElement = new VenueUrlElement(urlList);
+        anqpList2.put(Constants.ANQPElementType.ANQPVenueUrl, venueUrlElement);
+        mCache.addOrUpdateEntry(ENTRY_KEY, anqpList2);
+        data = mCache.getEntry(ENTRY_KEY);
+        assertTrue(data.getElements().get(Constants.ANQPElementType.ANQPVenueName)
+                .equals(venueNameElement));
+        assertTrue(data.getElements().get(Constants.ANQPElementType.ANQPVenueUrl)
+                .equals(venueUrlElement));
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/hotspot2/OsuNetworkConnectionTest.java b/service/tests/wifitests/src/com/android/server/wifi/hotspot2/OsuNetworkConnectionTest.java
index 8ada4b5..b878802 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/hotspot2/OsuNetworkConnectionTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/hotspot2/OsuNetworkConnectionTest.java
@@ -65,8 +65,6 @@
 @SmallTest
 public class OsuNetworkConnectionTest extends WifiBaseTest {
     private static final String TAG = "OsuNetworkConnectionTest";
-    private static final int ENABLE_LOGGING = 1;
-    private static final int DISABLE_LOGGING = 0;
 
     private static final int TEST_NETWORK_ID = 6;
     private static final String TEST_NAI = null;
@@ -100,7 +98,7 @@
         mLooper = new TestLooper();
         mHandler = new Handler(mLooper.getLooper());
         mNetworkConnection = new OsuNetworkConnection(mContext);
-        mNetworkConnection.enableVerboseLogging(ENABLE_LOGGING);
+        mNetworkConnection.enableVerboseLogging(true);
     }
 
     private LinkProperties createProvisionedLinkProperties() {
diff --git a/service/tests/wifitests/src/com/android/server/wifi/hotspot2/OsuServerConnectionTest.java b/service/tests/wifitests/src/com/android/server/wifi/hotspot2/OsuServerConnectionTest.java
index 4d75b3e..3b60907 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/hotspot2/OsuServerConnectionTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/hotspot2/OsuServerConnectionTest.java
@@ -87,7 +87,6 @@
     private static final String PROVIDER_NAME_VALID = "Boingo";
     private static final String PROVIDER_NAME_INVALID = "Boingo1";
     private static final String TEST_PROVIDER_CHINESE_NAME = "宝音阁";
-    private static final int ENABLE_VERBOSE_LOGGING = 1;
     private static final int TEST_SESSION_ID = 1;
 
     private TestLooper mLooper = new TestLooper();
@@ -117,7 +116,7 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mOsuServerConnection = new OsuServerConnection(mLooper.getLooper());
-        mOsuServerConnection.enableVerboseLogging(ENABLE_VERBOSE_LOGGING);
+        mOsuServerConnection.enableVerboseLogging(true);
         mProviderIdentities.add(Pair.create(Locale.US, PROVIDER_NAME_VALID));
         mServerUrl = new URL(TEST_VALID_URL);
         when(mWfaKeyStore.get()).thenReturn(mKeyStore);
diff --git a/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointConfigUserStoreDataTest.java b/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointConfigUserStoreDataTest.java
index 697f831..5d4c171 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointConfigUserStoreDataTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointConfigUserStoreDataTest.java
@@ -35,6 +35,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.util.FastXmlSerializer;
+import com.android.server.wifi.Clock;
 import com.android.server.wifi.WifiBaseTest;
 import com.android.server.wifi.WifiCarrierInfoManager;
 import com.android.server.wifi.WifiConfigStore;
@@ -79,6 +80,7 @@
     @Mock WifiKeyStore mKeyStore;
     @Mock WifiCarrierInfoManager mWifiCarrierInfoManager;
     @Mock PasspointConfigUserStoreData.DataSource mDataSource;
+    @Mock Clock mClock;
     PasspointConfigUserStoreData mConfigStoreData;
 
     /** Sets up test. */
@@ -86,7 +88,7 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mConfigStoreData = new PasspointConfigUserStoreData(mKeyStore, mWifiCarrierInfoManager,
-                mDataSource);
+                mDataSource, mClock);
     }
 
     /**
@@ -255,13 +257,13 @@
                 mKeyStore, mWifiCarrierInfoManager, TEST_PROVIDER_ID, TEST_CREATOR_UID,
                 TEST_CREATOR_PACKAGE, false, Arrays.asList(TEST_CA_CERTIFICATE_ALIAS),
                 TEST_CLIENT_PRIVATE_KEY_AND_CERT_ALIAS, null,
-                TEST_HAS_EVER_CONNECTED, TEST_SHARED);
+                TEST_HAS_EVER_CONNECTED, TEST_SHARED, mClock);
         PasspointProvider provider2 = new PasspointProvider(createFullPasspointConfiguration(),
                 mKeyStore, mWifiCarrierInfoManager, TEST_PROVIDER_ID_2, TEST_CREATOR_UID,
                 TEST_CREATOR_PACKAGE, true,
                 Arrays.asList(TEST_CA_CERTIFICATE_ALIAS, TEST_CA_CERTIFICATE_ALIAS_2),
                 TEST_CLIENT_PRIVATE_KEY_AND_CERT_ALIAS, TEST_REMEDIATION_CA_CERTIFICATE_ALIAS,
-                TEST_HAS_EVER_CONNECTED, TEST_SHARED);
+                TEST_HAS_EVER_CONNECTED, TEST_SHARED, mClock);
         provider2.setAutojoinEnabled(false);
         provider2.setMacRandomizationEnabled(false);
         provider2.setMeteredOverride(METERED_OVERRIDE_METERED);
diff --git a/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointEventHandlerTest.java b/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointEventHandlerTest.java
index c21697e..588b06b 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointEventHandlerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointEventHandlerTest.java
@@ -18,16 +18,16 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.mockito.MockitoAnnotations.initMocks;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.wifi.ActiveModeWarden;
+import com.android.server.wifi.ClientModeManager;
 import com.android.server.wifi.WifiBaseTest;
-import com.android.server.wifi.WifiNative;
+import com.android.server.wifi.WifiInjector;
 import com.android.server.wifi.hotspot2.anqp.Constants;
 
 import org.junit.Before;
@@ -51,7 +51,9 @@
     private static final String BSSID_STR = "11:22:33:44:55:66";
     private static final String ICON_FILENAME = "icon.test";
 
-    @Mock WifiNative mWifiNative;
+    @Mock WifiInjector mWifiInjector;
+    @Mock ActiveModeWarden mActiveModeWarden;
+    @Mock ClientModeManager mClientModeManager;
     @Mock PasspointEventHandler.Callbacks mCallbacks;
     PasspointEventHandler mHandler;
 
@@ -59,7 +61,9 @@
     @Before
     public void setUp() throws Exception {
         initMocks(this);
-        mHandler = new PasspointEventHandler(mWifiNative, mCallbacks);
+        when(mWifiInjector.getActiveModeWarden()).thenReturn(mActiveModeWarden);
+        when(mActiveModeWarden.getPrimaryClientModeManager()).thenReturn(mClientModeManager);
+        mHandler = new PasspointEventHandler(mWifiInjector, mCallbacks);
     }
 
     /**
@@ -75,12 +79,12 @@
         HashSet<Integer> expHs20Subtypes = new HashSet<>();
 
         // wpa_supplicant succeeded the request.
-        when(mWifiNative.requestAnqp(any(), eq(BSSID_STR), eq(expAnqpIds), eq(expHs20Subtypes)))
+        when(mClientModeManager.requestAnqp(BSSID_STR, expAnqpIds, expHs20Subtypes))
                 .thenReturn(true);
         assertTrue(mHandler.requestANQP(BSSID, elementToRequest));
 
         // wpa_supplicant failed the request.
-        when(mWifiNative.requestAnqp(any(), eq(BSSID_STR), eq(expAnqpIds), eq(expHs20Subtypes)))
+        when(mClientModeManager.requestAnqp(BSSID_STR, expAnqpIds, expHs20Subtypes))
                 .thenReturn(false);
         assertFalse(mHandler.requestANQP(BSSID, elementToRequest));
     }
@@ -98,12 +102,12 @@
                         Constants.ANQPElementType.HSFriendlyName)));
 
         // wpa_supplicant succeeded the request.
-        when(mWifiNative.requestAnqp(any(), eq(BSSID_STR), eq(expAnqpIds), eq(expHs20Subtypes)))
+        when(mClientModeManager.requestAnqp(BSSID_STR, expAnqpIds, expHs20Subtypes))
                 .thenReturn(true);
         assertTrue(mHandler.requestANQP(BSSID, elementToRequest));
 
         // wpa_supplicant failed the request.
-        when(mWifiNative.requestAnqp(any(), eq(BSSID_STR), eq(expAnqpIds), eq(expHs20Subtypes)))
+        when(mClientModeManager.requestAnqp(BSSID_STR, expAnqpIds, expHs20Subtypes))
                 .thenReturn(false);
         assertFalse(mHandler.requestANQP(BSSID, elementToRequest));
     }
@@ -124,12 +128,12 @@
                         Constants.ANQPElementType.HSFriendlyName)));
 
         // wpa_supplicant succeeded the request.
-        when(mWifiNative.requestAnqp(any(), eq(BSSID_STR), eq(expAnqpIds), eq(expHs20Subtypes)))
+        when(mClientModeManager.requestAnqp(BSSID_STR, expAnqpIds, expHs20Subtypes))
                 .thenReturn(true);
         assertTrue(mHandler.requestANQP(BSSID, elementToRequest));
 
         // wpa_supplicant failed the request.
-        when(mWifiNative.requestAnqp(any(), eq(BSSID_STR), eq(expAnqpIds), eq(expHs20Subtypes)))
+        when(mClientModeManager.requestAnqp(BSSID_STR, expAnqpIds, expHs20Subtypes))
                 .thenReturn(false);
         assertFalse(mHandler.requestANQP(BSSID, elementToRequest));
     }
@@ -140,11 +144,11 @@
     @Test
     public void requestIconFile() {
         // wpa_supplicant succeeded the request.
-        when(mWifiNative.requestIcon(any(), eq(BSSID_STR), eq(ICON_FILENAME))).thenReturn(true);
+        when(mClientModeManager.requestIcon(BSSID_STR, ICON_FILENAME)).thenReturn(true);
         assertTrue(mHandler.requestIcon(BSSID, ICON_FILENAME));
 
         // wpa_supplicant failed the request.
-        when(mWifiNative.requestIcon(any(), eq(BSSID_STR), eq(ICON_FILENAME))).thenReturn(false);
+        when(mClientModeManager.requestIcon(BSSID_STR, ICON_FILENAME)).thenReturn(false);
         assertFalse(mHandler.requestIcon(BSSID, ICON_FILENAME));
     }
 
@@ -156,4 +160,21 @@
         mHandler.notifyANQPDone(new AnqpEvent(BSSID, null));
         verify(mCallbacks).onANQPResponse(BSSID, null);
     }
+
+    /**
+     * Test for requesting Hotspot 2.0 R3 Venue URL ANQP-element.
+     */
+    @Test
+    public void requestVenueUrlAnqp() {
+        // wpa_supplicant succeeded the request.
+        when(mClientModeManager.requestVenueUrlAnqp(BSSID_STR)).thenReturn(true);
+        assertTrue(mHandler.requestVenueUrlAnqp(BSSID));
+
+        // wpa_supplicant failed the request.
+        when(mClientModeManager.requestVenueUrlAnqp(BSSID_STR)).thenReturn(false);
+        assertFalse(mHandler.requestVenueUrlAnqp(BSSID));
+
+        // 0 BSSID
+        assertFalse(mHandler.requestVenueUrlAnqp(0));
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java b/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java
index cb45972..f80f785 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java
@@ -28,6 +28,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.argThat;
@@ -76,7 +77,6 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.server.wifi.ClientModeImpl;
 import com.android.server.wifi.Clock;
 import com.android.server.wifi.FakeKeys;
 import com.android.server.wifi.FrameworkFacade;
@@ -99,20 +99,25 @@
 import com.android.server.wifi.hotspot2.anqp.HSOsuProvidersElement;
 import com.android.server.wifi.hotspot2.anqp.I18Name;
 import com.android.server.wifi.hotspot2.anqp.OsuProviderInfo;
+import com.android.server.wifi.hotspot2.anqp.VenueNameElement;
+import com.android.server.wifi.hotspot2.anqp.VenueUrlElement;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.UserActionEvent;
 import com.android.server.wifi.util.InformationElementUtil;
 import com.android.server.wifi.util.InformationElementUtil.RoamingConsortium;
 import com.android.server.wifi.util.WifiPermissionsUtil;
 
 import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoSession;
 
+import java.net.URL;
 import java.nio.charset.StandardCharsets;
 import java.security.KeyStore;
 import java.security.cert.Certificate;
+import java.security.cert.PKIXParameters;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -123,6 +128,7 @@
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * Unit tests for {@link PasspointManager}.
@@ -143,6 +149,17 @@
     private static final String FULL_IMSI = "123456789123456";
     private static final int TEST_CARRIER_ID = 10;
     private static final int TEST_SUBID = 1;
+    private static final String TEST_VENUE_URL_ENG = "https://www.google.com/";
+    private static final String TEST_VENUE_URL_HEB = "https://www.google.co.il/";
+    private static final String TEST_LOCALE_ENGLISH = "eng";
+    private static final String TEST_LOCALE_HEBREW = "heb";
+    private static final String TEST_LOCALE_SPANISH = "spa";
+    private static final String TEST_TERMS_AND_CONDITIONS_URL =
+            "https://policies.google.com/terms?hl=en-US";
+    private static final String TEST_TERMS_AND_CONDITIONS_URL_NON_HTTPS =
+            "http://policies.google.com/terms?hl=en-US";
+    private static final String TEST_TERMS_AND_CONDITIONS_URL_INVALID =
+            "httpps://policies.google.com/terms?hl=en-US";
 
     private static final long TEST_BSSID = 0x112233445566L;
     private static final String TEST_SSID = "TestSSID";
@@ -166,6 +183,10 @@
     private static final int TEST_CREATOR_UID1 = 1235;
     private static final int TEST_UID = 1500;
     private static final int TEST_NETWORK_ID = 2;
+    private static final String TEST_ANONYMOUS_IDENTITY = "AnonymousIdentity";
+    private static final String USER_CONNECT_CHOICE = "SomeNetworkProfileId";
+    private static final int TEST_RSSI = -50;
+    public static PKIXParameters TEST_PKIX_PARAMETERS;
 
     @Mock Context mContext;
     @Mock WifiNative mWifiNative;
@@ -188,7 +209,6 @@
     @Mock KeyStore mKeyStore;
     @Mock AppOpsManager mAppOpsManager;
     @Mock WifiInjector mWifiInjector;
-    @Mock ClientModeImpl mClientModeImpl;
     @Mock TelephonyManager mTelephonyManager;
     @Mock SubscriptionManager mSubscriptionManager;
     @Mock WifiNetworkSuggestionsManager mWifiNetworkSuggestionsManager;
@@ -201,6 +221,18 @@
     ArgumentCaptor<AppOpsManager.OnOpChangedListener> mAppOpChangedListenerCaptor =
             ArgumentCaptor.forClass(AppOpsManager.OnOpChangedListener.class);
     WifiCarrierInfoManager mWifiCarrierInfoManager;
+    ArgumentCaptor<WifiConfigManager.OnNetworkUpdateListener> mNetworkListenerCaptor =
+            ArgumentCaptor.forClass(WifiConfigManager.OnNetworkUpdateListener.class);
+    ArgumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener> mSubscriptionsCaptor =
+            ArgumentCaptor.forClass(SubscriptionManager.OnSubscriptionsChangedListener.class);
+
+    @BeforeClass
+    public static void setUpBeforeClass() throws Exception {
+        KeyStore keyStore = KeyStore.getInstance("AndroidCAStore");
+        keyStore.load(null, null);
+        TEST_PKIX_PARAMETERS = new PKIXParameters(keyStore);
+        TEST_PKIX_PARAMETERS.setRevocationEnabled(false);
+    }
 
     /** Sets up test. */
     @Before
@@ -219,22 +251,26 @@
                 any(PasspointManager.class), any(WifiMetrics.class)))
                 .thenReturn(mPasspointProvisioner);
         when(mContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager);
-        when(mWifiInjector.getClientModeImpl()).thenReturn(mClientModeImpl);
         when(mWifiInjector.getWifiNetworkSuggestionsManager())
                 .thenReturn(mWifiNetworkSuggestionsManager);
         when(mWifiPermissionsUtil.doesUidBelongToCurrentUser(anyInt())).thenReturn(true);
-        mWifiCarrierInfoManager = new WifiCarrierInfoManager(mTelephonyManager,
-                mSubscriptionManager, mWifiInjector, mock(FrameworkFacade.class),
-                mock(WifiContext.class), mWifiConfigStore, mock(Handler.class), mWifiMetrics);
         mLooper = new TestLooper();
         mHandler = new Handler(mLooper.getLooper());
+        mWifiCarrierInfoManager = new WifiCarrierInfoManager(mTelephonyManager,
+                mSubscriptionManager, mWifiInjector, mock(FrameworkFacade.class),
+                mock(WifiContext.class), mWifiConfigStore, mHandler, mWifiMetrics, mClock);
+        verify(mSubscriptionManager).addOnSubscriptionsChangedListener(any(),
+                mSubscriptionsCaptor.capture());
         mManager = new PasspointManager(mContext, mWifiInjector, mHandler, mWifiNative,
                 mWifiKeyStore, mClock, mObjectFactory, mWifiConfigManager,
                 mWifiConfigStore, mWifiMetrics, mWifiCarrierInfoManager, mMacAddressUtil,
                 mWifiPermissionsUtil);
+        mManager.setUseInjectedPKIX(true);
+        mManager.injectPKIXParameters(TEST_PKIX_PARAMETERS);
+
         ArgumentCaptor<PasspointEventHandler.Callbacks> callbacks =
                 ArgumentCaptor.forClass(PasspointEventHandler.Callbacks.class);
-        verify(mObjectFactory).makePasspointEventHandler(any(WifiNative.class),
+        verify(mObjectFactory).makePasspointEventHandler(any(WifiInjector.class),
                                                          callbacks.capture());
         ArgumentCaptor<PasspointConfigSharedStoreData.DataSource> sharedDataSource =
                 ArgumentCaptor.forClass(PasspointConfigSharedStoreData.DataSource.class);
@@ -242,13 +278,14 @@
         ArgumentCaptor<PasspointConfigUserStoreData.DataSource> userDataSource =
                 ArgumentCaptor.forClass(PasspointConfigUserStoreData.DataSource.class);
         verify(mObjectFactory).makePasspointConfigUserStoreData(any(WifiKeyStore.class),
-                any(WifiCarrierInfoManager.class), userDataSource.capture());
+                any(WifiCarrierInfoManager.class), userDataSource.capture(), any(Clock.class));
         mCallbacks = callbacks.getValue();
         mSharedDataSource = sharedDataSource.getValue();
         mUserDataSource = userDataSource.getValue();
         // SIM is absent
         when(mSubscriptionManager.getActiveSubscriptionInfoList())
                 .thenReturn(Collections.emptyList());
+        verify(mWifiConfigManager).addOnNetworkUpdateListener(mNetworkListenerCaptor.capture());
     }
 
     /**
@@ -375,7 +412,7 @@
         PasspointProvider provider = createMockProvider(config, wifiConfig, isSuggestion);
         when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
                 eq(mWifiCarrierInfoManager), anyLong(), eq(TEST_CREATOR_UID), eq(TEST_PACKAGE),
-                eq(isSuggestion))).thenReturn(provider);
+                eq(isSuggestion), eq(mClock))).thenReturn(provider);
         when(provider.getPackageName()).thenReturn(packageName);
         assertTrue(mManager.addOrUpdateProvider(
                 config, TEST_CREATOR_UID, TEST_PACKAGE, isSuggestion, true));
@@ -448,7 +485,7 @@
 
         when(mAnqpRequestManager.onRequestCompleted(TEST_BSSID, true)).thenReturn(TEST_ANQP_KEY);
         mCallbacks.onANQPResponse(TEST_BSSID, anqpElementMap);
-        verify(mAnqpCache).addEntry(TEST_ANQP_KEY, anqpElementMap);
+        verify(mAnqpCache).addOrUpdateEntry(TEST_ANQP_KEY, anqpElementMap);
         verify(mContext, never()).sendBroadcastAsUser(any(Intent.class), any(UserHandle.class),
                 any(String.class));
     }
@@ -467,7 +504,7 @@
 
         when(mAnqpRequestManager.onRequestCompleted(TEST_BSSID, true)).thenReturn(null);
         mCallbacks.onANQPResponse(TEST_BSSID, anqpElementMap);
-        verify(mAnqpCache, never()).addEntry(any(ANQPNetworkKey.class), anyMap());
+        verify(mAnqpCache, never()).addOrUpdateEntry(any(ANQPNetworkKey.class), anyMap());
     }
 
     /**
@@ -479,7 +516,7 @@
     public void anqpResponseFailure() throws Exception {
         when(mAnqpRequestManager.onRequestCompleted(TEST_BSSID, false)).thenReturn(TEST_ANQP_KEY);
         mCallbacks.onANQPResponse(TEST_BSSID, null);
-        verify(mAnqpCache, never()).addEntry(any(ANQPNetworkKey.class), anyMap());
+        verify(mAnqpCache, never()).addOrUpdateEntry(any(ANQPNetworkKey.class), anyMap());
 
     }
 
@@ -542,7 +579,7 @@
         when(provider.getPackageName()).thenReturn(TEST_PACKAGE);
         when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
                 eq(mWifiCarrierInfoManager), anyLong(), eq(TEST_CREATOR_UID), eq(TEST_PACKAGE),
-                eq(false))).thenReturn(provider);
+                eq(false), eq(mClock))).thenReturn(provider);
         assertFalse(mManager.addOrUpdateProvider(config, TEST_CREATOR_UID,
                 TEST_PACKAGE, false, true));
 
@@ -563,7 +600,7 @@
         when(provider.getPackageName()).thenReturn(TEST_PACKAGE);
         when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
                 eq(mWifiCarrierInfoManager), anyLong(), eq(TEST_CREATOR_UID), eq(TEST_PACKAGE),
-                eq(false))).thenReturn(provider);
+                eq(false), eq(mClock))).thenReturn(provider);
         assertTrue(mManager.addOrUpdateProvider(config, TEST_CREATOR_UID, TEST_PACKAGE,
                 false, true));
         verifyInstalledConfig(config);
@@ -597,7 +634,7 @@
 
         verify(provider).uninstallCertsAndKeys();
         verify(mWifiConfigManager, times(3)).removePasspointConfiguredNetwork(
-                provider.getWifiConfig().getKey());
+                provider.getWifiConfig().getProfileKey());
         /**
          * 1 from |removeProvider| + 2 from |setAutojoinEnabled| + 2 from
          * |enableMacRandomization| + 2 from |setMeteredOverride| = 7 calls to |saveToStore|
@@ -607,6 +644,7 @@
         verify(mWifiMetrics).incrementNumPasspointProviderUninstallSuccess();
         verify(mAppOpsManager).stopWatchingMode(any(AppOpsManager.OnOpChangedListener.class));
         assertTrue(mManager.getProviderConfigs(TEST_CREATOR_UID, false).isEmpty());
+        verify(mWifiConfigManager).removeConnectChoiceFromAllNetworks(config.getUniqueId());
 
         // Verify content in the data source.
         assertTrue(mUserDataSource.getProviders().isEmpty());
@@ -682,7 +720,7 @@
         assertTrue(mManager.enableMacRandomization(provider.getConfig().getHomeSp().getFqdn(),
                 true));
         verify(mWifiConfigManager, times(2)).removePasspointConfiguredNetwork(
-                provider.getWifiConfig().getKey());
+                provider.getWifiConfig().getProfileKey());
         verify(mWifiMetrics).logUserActionEvent(
                 UserActionEvent.EVENT_CONFIGURE_MAC_RANDOMIZATION_ON, false, true);
         verify(provider).setMacRandomizationEnabled(true);
@@ -719,7 +757,7 @@
         PasspointProvider provider = createMockProvider(config);
         when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
                 eq(mWifiCarrierInfoManager), anyLong(), eq(TEST_CREATOR_UID), eq(TEST_PACKAGE),
-                eq(false))).thenReturn(provider);
+                eq(false), eq(mClock))).thenReturn(provider);
         assertTrue(mManager.addOrUpdateProvider(config, TEST_CREATOR_UID, TEST_PACKAGE,
                 false, true));
         verifyInstalledConfig(config);
@@ -740,11 +778,12 @@
         assertTrue(mManager.removeProvider(TEST_UID, true, null, TEST_FQDN));
         verify(provider).uninstallCertsAndKeys();
         verify(mWifiConfigManager).removePasspointConfiguredNetwork(
-                provider.getWifiConfig().getKey());
+                provider.getWifiConfig().getProfileKey());
         verify(mWifiConfigManager).saveToStore(true);
         verify(mWifiMetrics).incrementNumPasspointProviderUninstallation();
         verify(mWifiMetrics).incrementNumPasspointProviderUninstallSuccess();
         assertTrue(mManager.getProviderConfigs(TEST_UID, true).isEmpty());
+        verify(mWifiConfigManager).removeConnectChoiceFromAllNetworks(config.getUniqueId());
 
         // Verify content in the data source.
         assertTrue(mUserDataSource.getProviders().isEmpty());
@@ -769,10 +808,12 @@
         TelephonyManager specifiedTm = mock(TelephonyManager.class);
         when(mTelephonyManager.createForSubscriptionId(eq(TEST_SUBID))).thenReturn(specifiedTm);
         when(specifiedTm.getSubscriberId()).thenReturn(FULL_IMSI);
+        when(specifiedTm.getSimApplicationState()).thenReturn(TelephonyManager.SIM_STATE_LOADED);
         List<SubscriptionInfo> subInfoList = new ArrayList<SubscriptionInfo>() {{
                 add(subInfo);
             }};
         when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(subInfoList);
+        mSubscriptionsCaptor.getValue().onSubscriptionsChanged();
         when(mWifiKeyStore.putCaCertInKeyStore(any(String.class), any(Certificate.class)))
                 .thenReturn(true);
         PasspointObjectFactory spyFactory = spy(new PasspointObjectFactory());
@@ -787,12 +828,6 @@
                 true, true));
 
         assertEquals(TEST_CARRIER_ID, config.getCarrierId());
-        List<String> passpointProfilesList = new ArrayList<String>(){{
-                add(config.getUniqueId());
-            }};
-        assertEquals(TEST_CARRIER_ID,
-                ut.getWifiConfigsForPasspointProfiles(passpointProfilesList).get(0).carrierId);
-
     }
 
     /**
@@ -809,13 +844,16 @@
         PasspointProvider origProvider = createMockProvider(origConfig);
         when(mObjectFactory.makePasspointProvider(eq(origConfig), eq(mWifiKeyStore),
                 eq(mWifiCarrierInfoManager), anyLong(), eq(TEST_CREATOR_UID), eq(TEST_PACKAGE),
-                eq(false))).thenReturn(origProvider);
+                eq(false), eq(mClock))).thenReturn(origProvider);
         assertTrue(mManager.addOrUpdateProvider(origConfig, TEST_CREATOR_UID, TEST_PACKAGE,
                 false, true));
         verifyInstalledConfig(origConfig);
         verify(mWifiConfigManager).saveToStore(true);
         verify(mWifiMetrics).incrementNumPasspointProviderInstallation();
         verify(mWifiMetrics).incrementNumPasspointProviderInstallSuccess();
+        verify(origProvider, never()).setUserConnectChoice(any(), anyInt());
+        verify(origProvider, never()).setAutojoinEnabled(anyBoolean());
+        verify(origProvider, never()).setAnonymousIdentity(any());
         reset(mWifiMetrics);
         reset(mWifiConfigManager);
 
@@ -828,15 +866,18 @@
         // Add same provider as existing suggestion provider
         // This should be no WifiConfig deletion
         WifiConfiguration origWifiConfig = origProvider.getWifiConfig();
-        when(mWifiConfigManager.getConfiguredNetwork(origWifiConfig.getKey()))
+        when(mWifiConfigManager.getConfiguredNetwork(origWifiConfig.getProfileKey()))
                 .thenReturn(origWifiConfig);
         when(mWifiConfigManager.addOrUpdateNetwork(
                 origWifiConfig, TEST_CREATOR_UID, TEST_PACKAGE))
                 .thenReturn(new NetworkUpdateResult(TEST_NETWORK_ID));
+        when(origProvider.getAnonymousIdentity()).thenReturn(TEST_ANONYMOUS_IDENTITY);
+        when(origProvider.getConnectChoice()).thenReturn(USER_CONNECT_CHOICE);
+        when(origProvider.getConnectChoiceRssi()).thenReturn(TEST_RSSI);
         assertTrue(mManager.addOrUpdateProvider(origConfig, TEST_CREATOR_UID, TEST_PACKAGE,
                 false, true));
         verify(mWifiConfigManager, never()).removePasspointConfiguredNetwork(
-                origWifiConfig.getKey());
+                origWifiConfig.getProfileKey());
         verify(mWifiConfigManager).addOrUpdateNetwork(
                 argThat((c) -> c.FQDN.equals(TEST_FQDN)), eq(TEST_CREATOR_UID), eq(TEST_PACKAGE));
         verify(mWifiConfigManager).allowAutojoin(TEST_NETWORK_ID, origWifiConfig.allowAutojoin);
@@ -844,6 +885,9 @@
         verify(mWifiMetrics).incrementNumPasspointProviderInstallation();
         verify(mWifiMetrics).incrementNumPasspointProviderInstallSuccess();
         assertEquals(2, mSharedDataSource.getProviderIndex());
+        // Update provider will keep the user settings from the existing provider.
+        verify(origProvider).setUserConnectChoice(eq(USER_CONNECT_CHOICE), eq(TEST_RSSI));
+        verify(origProvider).setAnonymousIdentity(eq(TEST_ANONYMOUS_IDENTITY));
         reset(mWifiMetrics);
         reset(mWifiConfigManager);
 
@@ -854,9 +898,9 @@
         PasspointProvider newProvider = createMockProvider(newConfig);
         when(mObjectFactory.makePasspointProvider(eq(newConfig), eq(mWifiKeyStore),
                 eq(mWifiCarrierInfoManager), anyLong(), eq(TEST_CREATOR_UID), eq(TEST_PACKAGE),
-                eq(false))).thenReturn(newProvider);
-        when(mWifiConfigManager.getConfiguredNetwork(origProvider.getWifiConfig().getKey()))
-                .thenReturn(origWifiConfig);
+                eq(false), eq(mClock))).thenReturn(newProvider);
+        when(mWifiConfigManager.getConfiguredNetwork(origProvider.getWifiConfig()
+                .getProfileKey())).thenReturn(origWifiConfig);
         assertTrue(mManager.addOrUpdateProvider(newConfig, TEST_CREATOR_UID, TEST_PACKAGE,
                 false, true));
 
@@ -892,7 +936,8 @@
         when(provider.installCertsAndKeys()).thenReturn(false);
         when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore), eq(
                 mWifiCarrierInfoManager),
-                anyLong(), eq(TEST_CREATOR_UID), eq(TEST_PACKAGE), eq(false))).thenReturn(provider);
+                anyLong(), eq(TEST_CREATOR_UID), eq(TEST_PACKAGE), eq(false),
+                eq(mClock))).thenReturn(provider);
         assertFalse(mManager.addOrUpdateProvider(config, TEST_CREATOR_UID, TEST_PACKAGE,
                 false, true));
         verify(mWifiMetrics).incrementNumPasspointProviderInstallation();
@@ -912,7 +957,7 @@
         PasspointProvider provider = createMockProvider(config);
         when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
                 eq(mWifiCarrierInfoManager), anyLong(), eq(TEST_CREATOR_UID), eq(TEST_PACKAGE),
-                eq(false))).thenReturn(provider);
+                eq(false), eq(mClock))).thenReturn(provider);
         assertTrue(mManager.addOrUpdateProvider(config, TEST_CREATOR_UID, TEST_PACKAGE,
                 false, true));
         verifyInstalledConfig(config);
@@ -934,7 +979,7 @@
         PasspointProvider provider = createMockProvider(config);
         when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
                 eq(mWifiCarrierInfoManager), anyLong(), eq(TEST_CREATOR_UID), eq(TEST_PACKAGE),
-                eq(false))).thenReturn(provider);
+                eq(false), eq(mClock))).thenReturn(provider);
         assertTrue(mManager.addOrUpdateProvider(config, TEST_CREATOR_UID, TEST_PACKAGE,
                 false, true));
         verifyInstalledConfig(config);
@@ -1009,7 +1054,7 @@
         ANQPData entry = new ANQPData(mClock, null);
 
         when(mAnqpCache.getEntry(TEST_ANQP_KEY)).thenReturn(entry);
-        when(provider.match(anyMap(), any(RoamingConsortium.class)))
+        when(provider.match(anyMap(), any(RoamingConsortium.class), any(ScanResult.class)))
             .thenReturn(PasspointMatch.HomeProvider);
         List<Pair<PasspointProvider, PasspointMatch>> results =
                 mManager.matchProvider(createTestScanResult());
@@ -1030,7 +1075,7 @@
         ANQPData entry = new ANQPData(mClock, null);
 
         when(mAnqpCache.getEntry(TEST_ANQP_KEY)).thenReturn(entry);
-        when(provider.match(anyMap(), any(RoamingConsortium.class)))
+        when(provider.match(anyMap(), any(RoamingConsortium.class), any(ScanResult.class)))
             .thenReturn(PasspointMatch.RoamingProvider);
         List<Pair<PasspointProvider, PasspointMatch>> results =
                 mManager.matchProvider(createTestScanResult());
@@ -1052,9 +1097,9 @@
                 addTestProvider(TEST_FQDN2, TEST_FRIENDLY_NAME2, TEST_PACKAGE1, false, null);
         ANQPData entry = new ANQPData(mClock, null);
         when(mAnqpCache.getEntry(TEST_ANQP_KEY)).thenReturn(entry);
-        when(roamingProvider1.match(anyMap(), any(RoamingConsortium.class)))
+        when(roamingProvider1.match(anyMap(), any(RoamingConsortium.class), any(ScanResult.class)))
                 .thenReturn(PasspointMatch.RoamingProvider);
-        when(roamingProvider2.match(anyMap(), any(RoamingConsortium.class)))
+        when(roamingProvider2.match(anyMap(), any(RoamingConsortium.class), any(ScanResult.class)))
                 .thenReturn(PasspointMatch.RoamingProvider);
         List<Pair<PasspointProvider, PasspointMatch>> results =
                 mManager.matchProvider(createTestScanResult());
@@ -1068,9 +1113,9 @@
                 addTestProvider(TEST_FQDN + "home", TEST_FRIENDLY_NAME, TEST_PACKAGE, false, null);
         PasspointProvider homeProvider2 = addTestProvider(TEST_FQDN2 + "home", TEST_FRIENDLY_NAME2,
                 TEST_PACKAGE1, false, null);
-        when(homeProvider1.match(anyMap(), any(RoamingConsortium.class)))
+        when(homeProvider1.match(anyMap(), any(RoamingConsortium.class), any(ScanResult.class)))
                 .thenReturn(PasspointMatch.HomeProvider);
-        when(homeProvider2.match(anyMap(), any(RoamingConsortium.class)))
+        when(homeProvider2.match(anyMap(), any(RoamingConsortium.class), any(ScanResult.class)))
                 .thenReturn(PasspointMatch.HomeProvider);
         results = mManager.matchProvider(createTestScanResult());
         // When home providers are available, should return all home providers.
@@ -1092,7 +1137,7 @@
         ANQPData entry = new ANQPData(mClock, null);
 
         when(mAnqpCache.getEntry(TEST_ANQP_KEY)).thenReturn(entry);
-        when(provider.match(anyMap(), any(RoamingConsortium.class)))
+        when(provider.match(anyMap(), any(RoamingConsortium.class), any(ScanResult.class)))
             .thenReturn(PasspointMatch.None);
         assertTrue(mManager.matchProvider(createTestScanResult()).isEmpty());
     }
@@ -1157,7 +1202,7 @@
 
             when(mAnqpCache.getEntry(TEST_ANQP_KEY2)).thenReturn(entry);
             when(InformationElementUtil.getHS2VendorSpecificIE(isNull())).thenReturn(vsa);
-            when(provider.match(anyMap(), isNull()))
+            when(provider.match(anyMap(), isNull(), any(ScanResult.class)))
                     .thenReturn(PasspointMatch.HomeProvider);
 
             List<Pair<PasspointProvider, PasspointMatch>> matchedProviders =
@@ -1196,11 +1241,11 @@
 
             when(mAnqpCache.getEntry(TEST_ANQP_KEY2)).thenReturn(entry);
             when(InformationElementUtil.getHS2VendorSpecificIE(isNull())).thenReturn(vsa);
-            when(providerHome.match(anyMap(), isNull()))
+            when(providerHome.match(anyMap(), isNull(), any(ScanResult.class)))
                     .thenReturn(PasspointMatch.HomeProvider);
-            when(providerRoaming.match(anyMap(), isNull()))
+            when(providerRoaming.match(anyMap(), isNull(), any(ScanResult.class)))
                     .thenReturn(PasspointMatch.RoamingProvider);
-            when(providerNone.match(anyMap(), isNull()))
+            when(providerNone.match(anyMap(), isNull(), any(ScanResult.class)))
                     .thenReturn(PasspointMatch.None);
 
             Map<String, Map<Integer, List<ScanResult>>> configs =
@@ -1226,39 +1271,50 @@
 
     /**
      * Verify that an expected list of {@link WifiConfiguration} will be returned when provided
-     * a list of FQDN is matched to installed Passpoint profiles. For suggestion passpoint network,
-     * will check if that suggestion share credential with user to choose from wifi picker.
+     * a list of FQDN is matched to installed Passpoint profiles which is already added into the
+     * WifiConfigManager. For suggestion passpoint network, will check if that suggestion share
+     * credential with user to choose from wifi picker.
+     * - Provider1 and Provider2 are saved passpoint, Provider1 is already added into the
+     * WifiConfigManger
+     * - Provider3 and Provider4 are suggestion passpoint, only Provider4 is shared with user. Both
+     * providers are already added into the WifiConfigManager
+     * - Expected result: Provider1 and Provider4 should be returned .
      */
     @Test
     public void getWifiConfigsForPasspointProfiles() {
         PasspointProvider provider1 = addTestProvider(TEST_FQDN, TEST_FRIENDLY_NAME,
                 TEST_PACKAGE, false, null);
+        WifiConfiguration config1 = provider1.getWifiConfig();
+        when(mWifiConfigManager.getConfiguredNetwork(provider1.getConfig().getUniqueId()))
+                .thenReturn(config1);
         PasspointProvider provider2 = addTestProvider(TEST_FQDN + 1, TEST_FRIENDLY_NAME,
                 TEST_PACKAGE, false, null);
         PasspointProvider provider3 = addTestProvider(TEST_FQDN + 2, TEST_FRIENDLY_NAME,
-                TEST_PACKAGE, false, null);
-
-        assertEquals(3, mManager.getWifiConfigsForPasspointProfiles(
-                Arrays.asList(provider1.getConfig().getUniqueId(),
-                        provider2.getConfig().getUniqueId(), provider3.getConfig().getUniqueId(),
-                        TEST_FQDN + "_353ab8c93", TEST_FQDN + "_83765319aca")).size());
+                TEST_PACKAGE, true, null);
+        when(mWifiNetworkSuggestionsManager
+                .isPasspointSuggestionSharedWithUser(provider3.getWifiConfig())).thenReturn(false);
+        WifiConfiguration config3 = provider3.getWifiConfig();
+        when(mWifiConfigManager.getConfiguredNetwork(provider3.getConfig().getUniqueId()))
+                .thenReturn(config3);
         PasspointProvider provider4 = addTestProvider(TEST_FQDN + 3, TEST_FRIENDLY_NAME,
                 TEST_PACKAGE, true, null);
         when(mWifiNetworkSuggestionsManager
-                .isPasspointSuggestionSharedWithUser(provider4.getWifiConfig())).thenReturn(false);
-        assertEquals(3, mManager.getWifiConfigsForPasspointProfiles(
-                Arrays.asList(provider1.getConfig().getUniqueId(),
-                        provider2.getConfig().getUniqueId(), provider3.getConfig().getUniqueId(),
-                        provider4.getConfig().getUniqueId(), TEST_FQDN + "_83765319aca")).size());
-        PasspointProvider provider5 = addTestProvider(TEST_FQDN + 4, TEST_FRIENDLY_NAME,
-                TEST_PACKAGE, true, null);
-        when(mWifiNetworkSuggestionsManager
-                .isPasspointSuggestionSharedWithUser(provider5.getWifiConfig())).thenReturn(true);
-        assertEquals(4, mManager.getWifiConfigsForPasspointProfiles(
-                Arrays.asList(provider1.getConfig().getUniqueId(),
-                        provider2.getConfig().getUniqueId(), provider3.getConfig().getUniqueId(),
-                        provider4.getConfig().getUniqueId(), provider5.getConfig().getUniqueId()))
-                .size());
+                .isPasspointSuggestionSharedWithUser(provider4.getWifiConfig())).thenReturn(true);
+        WifiConfiguration config4 = provider4.getWifiConfig();
+        when(mWifiConfigManager.getConfiguredNetwork(provider4.getConfig().getUniqueId()))
+                .thenReturn(config4);
+
+        List<WifiConfiguration> wifiConfigurationList = mManager.getWifiConfigsForPasspointProfiles(
+                List.of(provider1.getConfig().getUniqueId(), provider2.getConfig().getUniqueId(),
+                        provider3.getConfig().getUniqueId(), provider4.getConfig().getUniqueId(),
+                        TEST_FQDN + "_353ab8c93", TEST_FQDN + "_83765319aca"));
+        assertEquals(2, wifiConfigurationList.size());
+        Set<String> uniqueIdSet = wifiConfigurationList
+                .stream()
+                .map(WifiConfiguration::getPasspointUniqueId)
+                .collect(Collectors.toSet());
+        assertTrue(uniqueIdSet.contains(provider1.getConfig().getUniqueId()));
+        assertTrue(uniqueIdSet.contains(provider4.getConfig().getUniqueId()));
     }
 
     /**
@@ -1269,12 +1325,17 @@
     public void getWifiConfigsForPasspointProfilesWithoutEnhancedMacRandomization() {
         MacAddress randomizedMacAddress = MacAddress.fromString("01:23:45:67:89:ab");
         when(mMacAddressUtil.calculatePersistentMac(any(), any())).thenReturn(randomizedMacAddress);
-        when(mWifiConfigManager.shouldUseAggressiveRandomization(any())).thenReturn(false);
+        when(mWifiConfigManager.shouldUseEnhancedRandomization(any())).thenReturn(false);
         PasspointProvider provider = addTestProvider(TEST_FQDN, TEST_FRIENDLY_NAME,
                 TEST_PACKAGE, false, null);
+        WifiConfiguration configuration = provider.getWifiConfig();
+        when(mWifiConfigManager.getConfiguredNetwork(provider.getConfig().getUniqueId()))
+                .thenReturn(configuration);
         WifiConfiguration config = mManager.getWifiConfigsForPasspointProfiles(
                 Collections.singletonList(provider.getConfig().getUniqueId())).get(0);
         assertEquals(config.getRandomizedMacAddress(), randomizedMacAddress);
+        verify(mMacAddressUtil).calculatePersistentMac(
+                eq(provider.getConfig().getUniqueId()), any());
     }
 
     /**
@@ -1286,9 +1347,12 @@
     public void getWifiConfigsForPasspointProfilesWithEnhancedMacRandomization() {
         MacAddress randomizedMacAddress = MacAddress.fromString("01:23:45:67:89:ab");
         when(mMacAddressUtil.calculatePersistentMac(any(), any())).thenReturn(randomizedMacAddress);
-        when(mWifiConfigManager.shouldUseAggressiveRandomization(any())).thenReturn(true);
+        when(mWifiConfigManager.shouldUseEnhancedRandomization(any())).thenReturn(true);
         PasspointProvider provider = addTestProvider(TEST_FQDN, TEST_FRIENDLY_NAME,
                 TEST_PACKAGE, false, null);
+        WifiConfiguration configuration = provider.getWifiConfig();
+        when(mWifiConfigManager.getConfiguredNetwork(provider.getConfig().getUniqueId()))
+                .thenReturn(configuration);
         WifiConfiguration config = mManager.getWifiConfigsForPasspointProfiles(
                 Collections.singletonList(provider.getConfig().getUniqueId())).get(0);
         assertEquals(config.getRandomizedMacAddress(), MacAddress.fromString(DEFAULT_MAC_ADDRESS));
@@ -1574,7 +1638,7 @@
         // Verify the provider ID used to create the new provider.
         when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
                 eq(mWifiCarrierInfoManager), eq(providerIndex), eq(TEST_CREATOR_UID),
-                eq(TEST_PACKAGE), eq(false))).thenReturn(provider);
+                eq(TEST_PACKAGE), eq(false), eq(mClock))).thenReturn(provider);
 
         assertTrue(
                 mManager.addOrUpdateProvider(config, TEST_CREATOR_UID, TEST_PACKAGE, false, true));
@@ -1896,7 +1960,6 @@
     public void verifyRemovingPasspointProfilesWhenAppIsDisabled() {
         WifiConfiguration currentConfiguration = WifiConfigurationTestUtil.createPasspointNetwork();
         currentConfiguration.FQDN = TEST_FQDN;
-        when(mClientModeImpl.getCurrentWifiConfiguration()).thenReturn(currentConfiguration);
         PasspointProvider passpointProvider =
                 addTestProvider(TEST_FQDN, TEST_FRIENDLY_NAME, TEST_PACKAGE, false, null);
         currentConfiguration.setPasspointUniqueId(passpointProvider.getConfig().getUniqueId());
@@ -1915,7 +1978,8 @@
         mLooper.dispatchAll();
 
         verify(mAppOpsManager).stopWatchingMode(mAppOpChangedListenerCaptor.getValue());
-        verify(mClientModeImpl).disconnectCommand();
+        verify(mWifiConfigManager).removePasspointConfiguredNetwork(
+                passpointProvider.getWifiConfig().getProfileKey());
         assertTrue(mManager.getProviderConfigs(TEST_CREATOR_UID, true).isEmpty());
     }
 
@@ -1931,7 +1995,7 @@
         PasspointProvider provider = createMockProvider(config);
         when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
                 eq(mWifiCarrierInfoManager), anyLong(), eq(TEST_CREATOR_UID), eq(TEST_PACKAGE),
-                eq(false))).thenReturn(provider);
+                eq(false), eq(mClock))).thenReturn(provider);
         assertTrue(mManager.addOrUpdateProvider(config, TEST_CREATOR_UID, TEST_PACKAGE,
                 false, true));
         verifyInstalledConfig(config);
@@ -1971,7 +2035,7 @@
         PasspointProvider provider = createMockProvider(config);
         when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
                 eq(mWifiCarrierInfoManager), anyLong(), eq(TEST_CREATOR_UID), eq(TEST_PACKAGE),
-                eq(false))).thenReturn(provider);
+                eq(false), eq(mClock))).thenReturn(provider);
         assertTrue(mManager.addOrUpdateProvider(config, TEST_CREATOR_UID, TEST_PACKAGE,
                 false, true));
         verifyInstalledConfig(config);
@@ -1997,7 +2061,7 @@
         when(provider.isFromSuggestion()).thenReturn(true);
         when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
                 eq(mWifiCarrierInfoManager), anyLong(), eq(TEST_CREATOR_UID), eq(TEST_PACKAGE),
-                eq(true))).thenReturn(provider);
+                eq(true), eq(mClock))).thenReturn(provider);
         assertTrue(mManager.addOrUpdateProvider(config, TEST_CREATOR_UID, TEST_PACKAGE,
                 true, true));
         verify(mWifiMetrics).incrementNumPasspointProviderInstallation();
@@ -2019,7 +2083,7 @@
         assertFalse(mManager.removeProvider(TEST_UID, false, null, TEST_FQDN));
         verify(provider, never()).uninstallCertsAndKeys();
         verify(mWifiConfigManager, never()).removePasspointConfiguredNetwork(
-                provider.getWifiConfig().getKey());
+                provider.getWifiConfig().getProfileKey());
         verify(mWifiConfigManager, never()).saveToStore(true);
         verify(mWifiMetrics).incrementNumPasspointProviderUninstallation();
         verify(mWifiMetrics, never()).incrementNumPasspointProviderUninstallSuccess();
@@ -2038,12 +2102,13 @@
         assertTrue(mManager.removeProvider(TEST_CREATOR_UID, false, null, TEST_FQDN));
         verify(provider).uninstallCertsAndKeys();
         verify(mWifiConfigManager).removePasspointConfiguredNetwork(
-                provider.getWifiConfig().getKey());
+                provider.getWifiConfig().getProfileKey());
         verify(mWifiConfigManager).saveToStore(true);
         verify(mWifiMetrics).incrementNumPasspointProviderUninstallation();
         verify(mWifiMetrics).incrementNumPasspointProviderUninstallSuccess();
         verify(mAppOpsManager, never()).stopWatchingMode(
                 any(AppOpsManager.OnOpChangedListener.class));
+        verify(mWifiConfigManager).removeConnectChoiceFromAllNetworks(config.getUniqueId());
 
         // Verify content in the data source.
         assertTrue(mUserDataSource.getProviders().isEmpty());
@@ -2067,7 +2132,7 @@
         when(origProvider.getPackageName()).thenReturn(TEST_PACKAGE);
         when(mObjectFactory.makePasspointProvider(eq(origConfig), eq(mWifiKeyStore),
                 eq(mWifiCarrierInfoManager), anyLong(), eq(TEST_CREATOR_UID), eq(TEST_PACKAGE),
-                eq(true))).thenReturn(origProvider);
+                eq(true), eq(mClock))).thenReturn(origProvider);
         assertTrue(mManager.addOrUpdateProvider(origConfig, TEST_CREATOR_UID, TEST_PACKAGE,
                 true, true));
         verify(mWifiMetrics).incrementNumPasspointProviderInstallation();
@@ -2087,7 +2152,7 @@
         origWifiConfig.fromWifiNetworkSuggestion = true;
         origWifiConfig.creatorUid = TEST_CREATOR_UID;
         origWifiConfig.creatorName = TEST_PACKAGE;
-        when(mWifiConfigManager.getConfiguredNetwork(origWifiConfig.getKey()))
+        when(mWifiConfigManager.getConfiguredNetwork(origWifiConfig.getProfileKey()))
                 .thenReturn(origWifiConfig);
         when(mWifiConfigManager.addOrUpdateNetwork(
                 origWifiConfig, TEST_CREATOR_UID, TEST_PACKAGE))
@@ -2095,7 +2160,7 @@
         assertTrue(mManager.addOrUpdateProvider(origConfig, TEST_CREATOR_UID, TEST_PACKAGE,
                 true, true));
         verify(mWifiConfigManager, never()).removePasspointConfiguredNetwork(
-                origWifiConfig.getKey());
+                origWifiConfig.getProfileKey());
         verify(mWifiConfigManager).addOrUpdateNetwork(
                 argThat((c) -> c.FQDN.equals(TEST_FQDN)), eq(TEST_CREATOR_UID), eq(TEST_PACKAGE));
         verify(mWifiConfigManager).allowAutojoin(TEST_NETWORK_ID, origWifiConfig.allowAutojoin);
@@ -2115,7 +2180,7 @@
         when(newProvider.getPackageName()).thenReturn(TEST_PACKAGE);
         when(mObjectFactory.makePasspointProvider(eq(newConfig), eq(mWifiKeyStore),
                 eq(mWifiCarrierInfoManager), anyLong(), eq(TEST_CREATOR_UID), eq(TEST_PACKAGE),
-                eq(true))).thenReturn(newProvider);
+                eq(true), eq(mClock))).thenReturn(newProvider);
         assertTrue(mManager.addOrUpdateProvider(newConfig, TEST_CREATOR_UID, TEST_PACKAGE,
                 true, true));
         verify(mWifiConfigManager).saveToStore(true);
@@ -2148,7 +2213,7 @@
         when(origProvider.getPackageName()).thenReturn(TEST_PACKAGE);
         when(mObjectFactory.makePasspointProvider(eq(origConfig), eq(mWifiKeyStore),
                 eq(mWifiCarrierInfoManager), anyLong(), eq(TEST_CREATOR_UID), eq(TEST_PACKAGE),
-                eq(true))).thenReturn(origProvider);
+                eq(true), eq(mClock))).thenReturn(origProvider);
         assertTrue(mManager.addOrUpdateProvider(origConfig, TEST_CREATOR_UID, TEST_PACKAGE,
                 true, true));
         verify(mWifiMetrics).incrementNumPasspointProviderInstallation();
@@ -2169,7 +2234,7 @@
         PasspointProvider newProvider = createMockProvider(newConfig);
         when(mObjectFactory.makePasspointProvider(eq(newConfig), eq(mWifiKeyStore),
                 eq(mWifiCarrierInfoManager), anyLong(), eq(TEST_CREATOR_UID), eq(TEST_PACKAGE),
-                eq(false))).thenReturn(newProvider);
+                eq(false), eq(mClock))).thenReturn(newProvider);
         assertTrue(mManager.addOrUpdateProvider(newConfig, TEST_CREATOR_UID, TEST_PACKAGE,
                 false, true));
         verify(mWifiConfigManager).saveToStore(true);
@@ -2201,7 +2266,7 @@
         when(origProvider.getPackageName()).thenReturn(TEST_PACKAGE);
         when(mObjectFactory.makePasspointProvider(eq(origConfig), eq(mWifiKeyStore),
                 eq(mWifiCarrierInfoManager), anyLong(), eq(TEST_CREATOR_UID), eq(TEST_PACKAGE),
-                eq(false))).thenReturn(origProvider);
+                eq(false), eq(mClock))).thenReturn(origProvider);
         assertTrue(mManager.addOrUpdateProvider(origConfig, TEST_CREATOR_UID, TEST_PACKAGE, false,
                 true));
         verifyInstalledConfig(origConfig);
@@ -2226,7 +2291,7 @@
         when(newProvider.getPackageName()).thenReturn(TEST_PACKAGE1);
         when(mObjectFactory.makePasspointProvider(eq(newConfig), eq(mWifiKeyStore),
                 eq(mWifiCarrierInfoManager), anyLong(), eq(TEST_CREATOR_UID), eq(TEST_PACKAGE1),
-                eq(true))).thenReturn(newProvider);
+                eq(true), eq(mClock))).thenReturn(newProvider);
         assertTrue(mManager.addOrUpdateProvider(newConfig, TEST_CREATOR_UID, TEST_PACKAGE1, true,
                 true));
         verify(mWifiConfigManager).saveToStore(true);
@@ -2273,11 +2338,11 @@
 
             when(mAnqpCache.getEntry(TEST_ANQP_KEY)).thenReturn(entry);
             when(InformationElementUtil.getHS2VendorSpecificIE(isNull())).thenReturn(vsa);
-            when(providerHome.match(anyMap(), isNull()))
+            when(providerHome.match(anyMap(), isNull(), any(ScanResult.class)))
                     .thenReturn(PasspointMatch.HomeProvider);
-            when(providerRoaming.match(anyMap(), isNull()))
+            when(providerRoaming.match(anyMap(), isNull(), any(ScanResult.class)))
                     .thenReturn(PasspointMatch.RoamingProvider);
-            when(providerNone.match(anyMap(), isNull()))
+            when(providerNone.match(anyMap(), isNull(), any(ScanResult.class)))
                     .thenReturn(PasspointMatch.None);
 
             List<Pair<PasspointProvider, PasspointMatch>> results =
@@ -2323,11 +2388,11 @@
 
             when(mAnqpCache.getEntry(TEST_ANQP_KEY)).thenReturn(entry);
             when(InformationElementUtil.getHS2VendorSpecificIE(isNull())).thenReturn(vsa);
-            when(providerHome.match(anyMap(), isNull()))
+            when(providerHome.match(anyMap(), isNull(), any(ScanResult.class)))
                     .thenReturn(PasspointMatch.HomeProvider);
-            when(providerRoaming.match(anyMap(), isNull()))
+            when(providerRoaming.match(anyMap(), isNull(), any(ScanResult.class)))
                     .thenReturn(PasspointMatch.RoamingProvider);
-            when(providerNone.match(anyMap(), isNull()))
+            when(providerNone.match(anyMap(), isNull(), any(ScanResult.class)))
                     .thenReturn(PasspointMatch.None);
 
             List<Pair<PasspointProvider, PasspointMatch>> results =
@@ -2375,11 +2440,11 @@
 
             when(mAnqpCache.getEntry(TEST_ANQP_KEY)).thenReturn(entry);
             when(InformationElementUtil.getHS2VendorSpecificIE(isNull())).thenReturn(vsa);
-            when(providerHome.match(anyMap(), isNull()))
+            when(providerHome.match(anyMap(), isNull(), any(ScanResult.class)))
                     .thenReturn(PasspointMatch.HomeProvider);
-            when(providerRoaming.match(anyMap(), isNull()))
+            when(providerRoaming.match(anyMap(), isNull(), any(ScanResult.class)))
                     .thenReturn(PasspointMatch.RoamingProvider);
-            when(providerNone.match(anyMap(), isNull()))
+            when(providerNone.match(anyMap(), isNull(), any(ScanResult.class)))
                     .thenReturn(PasspointMatch.None);
 
             List<Pair<PasspointProvider, PasspointMatch>> results =
@@ -2406,7 +2471,7 @@
         PasspointProvider provider = createMockProvider(config, wifiConfig, true);
         when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
                 eq(mWifiCarrierInfoManager), anyLong(), eq(TEST_CREATOR_UID), eq(TEST_PACKAGE),
-                eq(true))).thenReturn(provider);
+                eq(true), eq(mClock))).thenReturn(provider);
         when(provider.getPackageName()).thenReturn(TEST_PACKAGE);
         assertTrue(mManager.addOrUpdateProvider(
                 config, TEST_CREATOR_UID, TEST_PACKAGE, true, false));
@@ -2425,7 +2490,7 @@
         PasspointProvider provider = createMockProvider(config, wifiConfig, false);
         when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
                 eq(mWifiCarrierInfoManager), anyLong(), eq(TEST_CREATOR_UID), eq(TEST_PACKAGE),
-                eq(true))).thenReturn(provider);
+                eq(true), eq(mClock))).thenReturn(provider);
         when(provider.getPackageName()).thenReturn(TEST_PACKAGE);
         assertFalse(mManager.addOrUpdateProvider(
                 config, TEST_CREATOR_UID, TEST_PACKAGE, false, false));
@@ -2441,12 +2506,12 @@
         PasspointConfiguration config = mock(PasspointConfiguration.class);
         PasspointProvider mockProvider = mock(PasspointProvider.class);
         when(mObjectFactory.makePasspointProvider(config, null,
-                mWifiCarrierInfoManager, 0, 0, null, false))
+                mWifiCarrierInfoManager, 0, 0, null, false, mClock))
                 .thenReturn(mockProvider);
         List<ScanResult> scanResults = new ArrayList<>() {{
                 add(mock(ScanResult.class));
             }};
-        when(mockProvider.match(anyMap(), any(RoamingConsortium.class)))
+        when(mockProvider.match(anyMap(), any(RoamingConsortium.class), any(ScanResult.class)))
                 .thenReturn(PasspointMatch.RoamingProvider);
 
         List<ScanResult> testResults = mManager.getMatchingScanResults(config, scanResults);
@@ -2463,12 +2528,12 @@
         PasspointConfiguration config = mock(PasspointConfiguration.class);
         PasspointProvider mockProvider = mock(PasspointProvider.class);
         when(mObjectFactory.makePasspointProvider(config, null,
-                mWifiCarrierInfoManager, 0, 0, null, false))
+                mWifiCarrierInfoManager, 0, 0, null, false, mClock))
                 .thenReturn(mockProvider);
         List<ScanResult> scanResults = new ArrayList<>() {{
                 add(mock(ScanResult.class));
             }};
-        when(mockProvider.match(anyMap(), any(RoamingConsortium.class)))
+        when(mockProvider.match(anyMap(), any(RoamingConsortium.class), any(ScanResult.class)))
                 .thenReturn(PasspointMatch.HomeProvider);
 
         List<ScanResult> testResults = mManager.getMatchingScanResults(config, scanResults);
@@ -2487,13 +2552,13 @@
         PasspointProvider mockProvider = mock(PasspointProvider.class);
 
         when(mObjectFactory.makePasspointProvider(config, null,
-                mWifiCarrierInfoManager, 0, 0, null, false))
+                mWifiCarrierInfoManager, 0, 0, null, false, mClock))
                 .thenReturn(mockProvider);
 
         List<ScanResult> scanResults = new ArrayList<>() {{
                 add(mock(ScanResult.class));
             }};
-        when(mockProvider.match(anyMap(), any(RoamingConsortium.class)))
+        when(mockProvider.match(anyMap(), any(RoamingConsortium.class), any(ScanResult.class)))
                 .thenReturn(PasspointMatch.None);
 
         List<ScanResult> testResults = mManager.getMatchingScanResults(config, scanResults);
@@ -2535,18 +2600,19 @@
 
         verify(provider1).uninstallCertsAndKeys();
         verify(mWifiConfigManager, times(1)).removePasspointConfiguredNetwork(
-                provider1.getWifiConfig().getKey());
+                provider1.getWifiConfig().getProfileKey());
         verify(provider2).uninstallCertsAndKeys();
         verify(mWifiConfigManager, times(1)).removePasspointConfiguredNetwork(
-                provider2.getWifiConfig().getKey());
+                provider2.getWifiConfig().getProfileKey());
         verify(provider3).uninstallCertsAndKeys();
         verify(mWifiConfigManager, times(1)).removePasspointConfiguredNetwork(
-                provider3.getWifiConfig().getKey());
+                provider3.getWifiConfig().getProfileKey());
 
         verify(mWifiMetrics, times(3)).incrementNumPasspointProviderUninstallation();
         verify(mWifiMetrics, times(3)).incrementNumPasspointProviderUninstallSuccess();
         verify(mAppOpsManager).stopWatchingMode(any(AppOpsManager.OnOpChangedListener.class));
         assertTrue(mManager.getProviderConfigs(TEST_CREATOR_UID, false).isEmpty());
+        verify(mWifiConfigManager, times(3)).removeConnectChoiceFromAllNetworks(any());
 
         // Verify content in the data source.
         assertTrue(mUserDataSource.getProviders().isEmpty());
@@ -2566,7 +2632,7 @@
         PasspointProvider provider = createMockProvider(config, wifiConfig, true);
         when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
             eq(mWifiCarrierInfoManager), anyLong(), eq(TEST_CREATOR_UID), eq(TEST_PACKAGE),
-            eq(true))).thenReturn(provider);
+            eq(true), eq(mClock))).thenReturn(provider);
         when(provider.getPackageName()).thenReturn(TEST_PACKAGE);
         assertTrue(mManager.addOrUpdateProvider(
                 config, TEST_CREATOR_UID, TEST_PACKAGE, true, false));
@@ -2591,7 +2657,7 @@
         PasspointProvider provider = createMockProvider(config, wifiConfig, true);
         when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
             eq(mWifiCarrierInfoManager), anyLong(), eq(TEST_CREATOR_UID), eq(TEST_PACKAGE),
-            eq(true))).thenReturn(provider);
+            eq(true), eq(mClock))).thenReturn(provider);
         when(provider.getPackageName()).thenReturn(TEST_PACKAGE);
         assertTrue(mManager.addOrUpdateProvider(
                 config, TEST_CREATOR_UID, TEST_PACKAGE, true, false));
@@ -2616,7 +2682,7 @@
         PasspointProvider provider = createMockProvider(config, wifiConfig, true);
         when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
             eq(mWifiCarrierInfoManager), anyLong(), eq(TEST_CREATOR_UID), eq(TEST_PACKAGE),
-            eq(true))).thenReturn(provider);
+            eq(true), eq(mClock))).thenReturn(provider);
         when(provider.getPackageName()).thenReturn(TEST_PACKAGE);
         assertTrue(mManager.addOrUpdateProvider(
                 config, TEST_CREATOR_UID, TEST_PACKAGE, true, false));
@@ -2624,4 +2690,345 @@
         verify(mWifiMetrics).incrementNumPasspointProviderInstallation();
         verify(mWifiMetrics).incrementNumPasspointProviderInstallSuccess();
     }
+
+    /**
+     * Verify that venue URL ANQP request is sent correctly.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyRequestVenueUrlAnqpElement() throws Exception {
+        // static mocking
+        MockitoSession session =
+                com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession().mockStatic(
+                        InformationElementUtil.class).startMocking();
+        try {
+            ScanResult scanResult = createTestScanResult();
+            InformationElementUtil.Vsa vsa = new InformationElementUtil.Vsa();
+            vsa.anqpDomainID = scanResult.anqpDomainId;
+            when(InformationElementUtil.getHS2VendorSpecificIE(isNull())).thenReturn(vsa);
+            long bssid = Utils.parseMac(scanResult.BSSID);
+            mManager.requestVenueUrlAnqpElement(scanResult);
+            verify(mAnqpRequestManager).requestVenueUrlAnqpElement(eq(bssid), any());
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+    /**
+     * Verify blocking a matched provider following a Deauthentication-imminent WNM-notification
+     */
+    @Test
+    public void testBlockingProvider() {
+        WifiConfiguration wifiConfig = WifiConfigurationTestUtil.generateWifiConfig(10, TEST_UID,
+                "\"PasspointTestSSID\"", true, true, TEST_FQDN,
+                TEST_FRIENDLY_NAME, SECURITY_EAP);
+        wifiConfig.BSSID = TEST_BSSID_STRING;
+
+        PasspointProvider provider =
+                addTestProvider(TEST_FQDN, TEST_FRIENDLY_NAME, TEST_PACKAGE, wifiConfig, false,
+                        null);
+        WnmData event = WnmData.createDeauthImminentEvent(Utils.parseMac(TEST_BSSID_STRING), "",
+                true, 30);
+
+        mManager.handleDeauthImminentEvent(event, wifiConfig);
+        verify(provider).blockBssOrEss(eq(event.getBssid()), eq(event.isEss()),
+                eq(event.getDelay()));
+    }
+
+    /**
+     * Verify set Anonymous Identity to the right passpoint provider.
+     */
+    @Test
+    public void testSetAnonymousIdentity() {
+        WifiConfiguration wifiConfig = WifiConfigurationTestUtil.generateWifiConfig(10, TEST_UID,
+                "\"PasspointTestSSID\"", true, true, TEST_FQDN,
+                TEST_FRIENDLY_NAME, SECURITY_EAP);
+
+        PasspointProvider provider =
+                addTestProvider(TEST_FQDN, TEST_FRIENDLY_NAME, TEST_PACKAGE, wifiConfig, false,
+                        null);
+
+        wifiConfig.enterpriseConfig.setAnonymousIdentity(TEST_ANONYMOUS_IDENTITY);
+        mManager.setAnonymousIdentity(wifiConfig);
+        verify(provider).setAnonymousIdentity(TEST_ANONYMOUS_IDENTITY);
+
+
+        mManager.resetSimPasspointNetwork();
+        verify(provider).setAnonymousIdentity(null);
+        verify(mWifiConfigManager, times(3)).saveToStore(true);
+    }
+
+    /**
+     * Test set and remove user connect choice.
+     */
+    @Test
+    public void testSetUserConnectChoice() {
+        WifiConfiguration wifiConfig = WifiConfigurationTestUtil.generateWifiConfig(10, TEST_UID,
+                "\"PasspointTestSSID\"", true, true, TEST_FQDN,
+                TEST_FRIENDLY_NAME, SECURITY_EAP);
+
+        PasspointProvider provider =
+                addTestProvider(TEST_FQDN, TEST_FRIENDLY_NAME, TEST_PACKAGE, wifiConfig, false,
+                        null);
+
+        WifiConfiguration wifiConfig2 = WifiConfigurationTestUtil.generateWifiConfig(11, TEST_UID,
+                "\"PasspointTestSSID\"", true, true, TEST_FQDN2,
+                TEST_FRIENDLY_NAME, SECURITY_EAP);
+
+        PasspointProvider provider2 =
+                addTestProvider(TEST_FQDN2, TEST_FRIENDLY_NAME, TEST_PACKAGE, wifiConfig2, false,
+                        null);
+
+        WifiConfigManager.OnNetworkUpdateListener listener = mNetworkListenerCaptor.getValue();
+        reset(mWifiConfigManager);
+
+        // Set user connect choice on this passpoint network
+        listener.onConnectChoiceSet(Collections.singletonList(wifiConfig), USER_CONNECT_CHOICE,
+                TEST_RSSI);
+        verify(provider).setUserConnectChoice(USER_CONNECT_CHOICE, TEST_RSSI);
+
+        // The user connect choice is this psspoint network, its user connect choice should null
+        listener.onConnectChoiceSet(Collections.emptyList(), wifiConfig.getPasspointUniqueId(),
+                TEST_RSSI);
+        verify(provider).setUserConnectChoice(null, 0);
+
+        // Remove the user connect choice, if equals, user connect choice should set to null
+        when(provider.getConnectChoice()).thenReturn(USER_CONNECT_CHOICE);
+        listener.onConnectChoiceRemoved(USER_CONNECT_CHOICE);
+        verify(provider, times(2)).setUserConnectChoice(null, 0);
+
+        verify(provider2, never()).setUserConnectChoice(any(), anyInt());
+        verify(mWifiConfigManager, times(3)).saveToStore(true);
+    }
+
+    /*
+     * Verify that Passpoint manager returns the correct venue URL.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testGetVenueUrl() throws Exception {
+        // static mocking
+        MockitoSession session =
+                com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession().mockStatic(
+                        InformationElementUtil.class).startMocking();
+        try {
+            ScanResult scanResult = createTestScanResult();
+            InformationElementUtil.Vsa vsa = new InformationElementUtil.Vsa();
+            vsa.anqpDomainID = scanResult.anqpDomainId;
+            when(InformationElementUtil.getHS2VendorSpecificIE(isNull())).thenReturn(vsa);
+
+            Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
+            anqpElementMap.put(ANQPElementType.ANQPDomName,
+                    new DomainNameElement(Arrays.asList(new String[]{"test.com"})));
+            List<I18Name> names = new ArrayList<>();
+            names.add(new I18Name(TEST_LOCALE_ENGLISH,
+                    new Locale.Builder().setLanguage(TEST_LOCALE_ENGLISH).build(),
+                    "Passpoint Venue"));
+            names.add(new I18Name(TEST_LOCALE_HEBREW,
+                    new Locale.Builder().setLanguage(TEST_LOCALE_HEBREW).build(), "רשת פאספוינט"));
+            anqpElementMap.put(ANQPElementType.ANQPVenueName, new VenueNameElement(names));
+
+            Map<Integer, URL> venueUrls = new HashMap<>();
+            venueUrls.put(1, new URL(TEST_VENUE_URL_ENG));
+            venueUrls.put(2, new URL(TEST_VENUE_URL_HEB));
+            anqpElementMap.put(ANQPElementType.ANQPVenueUrl, new VenueUrlElement(venueUrls));
+
+            mAnqpCache.addOrUpdateEntry(TEST_ANQP_KEY, anqpElementMap);
+            ANQPData entry = new ANQPData(mClock, anqpElementMap);
+            when(mAnqpCache.getEntry(TEST_ANQP_KEY)).thenReturn(entry);
+
+            // Test language 1
+            Locale.setDefault(new Locale(TEST_LOCALE_ENGLISH));
+            URL venueUrl = mManager.getVenueUrl(scanResult);
+            assertEquals(venueUrl.toString(), TEST_VENUE_URL_ENG);
+
+            // Test language 2
+            Locale.setDefault(new Locale(TEST_LOCALE_HEBREW));
+            venueUrl = mManager.getVenueUrl(scanResult);
+            assertEquals(venueUrl.toString(), TEST_VENUE_URL_HEB);
+
+            // Test default language when no language match
+            Locale.setDefault(new Locale(TEST_LOCALE_SPANISH));
+            venueUrl = mManager.getVenueUrl(scanResult);
+            assertEquals(venueUrl.toString(), TEST_VENUE_URL_ENG);
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+    /**
+     * Verify that Passpoint manager returns null when no ANQP entry is available.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testGetVenueUrlNoAnqpEntry() throws Exception {
+        // static mocking
+        MockitoSession session =
+                com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession().mockStatic(
+                        InformationElementUtil.class).startMocking();
+        try {
+            ScanResult scanResult = createTestScanResult();
+            InformationElementUtil.Vsa vsa = new InformationElementUtil.Vsa();
+            vsa.anqpDomainID = scanResult.anqpDomainId;
+            when(InformationElementUtil.getHS2VendorSpecificIE(isNull())).thenReturn(vsa);
+
+            when(mAnqpCache.getEntry(TEST_ANQP_KEY)).thenReturn(null);
+
+            URL venueUrl = mManager.getVenueUrl(scanResult);
+            assertNull(venueUrl);
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+    /**
+     * Verify that Passpoint manager returns null when no Venue URL ANQP-element is available.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testGetVenueUrlNoVenueUrlAnqpElement() throws Exception {
+        // static mocking
+        MockitoSession session =
+                com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession().mockStatic(
+                        InformationElementUtil.class).startMocking();
+        try {
+            ScanResult scanResult = createTestScanResult();
+            InformationElementUtil.Vsa vsa = new InformationElementUtil.Vsa();
+            vsa.anqpDomainID = scanResult.anqpDomainId;
+            when(InformationElementUtil.getHS2VendorSpecificIE(isNull())).thenReturn(vsa);
+
+            Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
+            anqpElementMap.put(ANQPElementType.ANQPDomName,
+                    new DomainNameElement(Arrays.asList(new String[]{"test.com"})));
+            List<I18Name> names = new ArrayList<>();
+            names.add(new I18Name(TEST_LOCALE_ENGLISH,
+                    new Locale.Builder().setLanguage(TEST_LOCALE_ENGLISH).build(),
+                    "Passpoint Venue"));
+            names.add(new I18Name(TEST_LOCALE_HEBREW,
+                    new Locale.Builder().setLanguage(TEST_LOCALE_HEBREW).build(), "רשת פאספוינט"));
+            anqpElementMap.put(ANQPElementType.ANQPVenueName, new VenueNameElement(names));
+
+            mAnqpCache.addOrUpdateEntry(TEST_ANQP_KEY, anqpElementMap);
+            ANQPData entry = new ANQPData(mClock, anqpElementMap);
+            when(mAnqpCache.getEntry(TEST_ANQP_KEY)).thenReturn(entry);
+
+            URL venueUrl = mManager.getVenueUrl(scanResult);
+            assertNull(venueUrl);
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+    /**
+     * Verify that Passpoint manager returns null when no Venue Name ANQP-element is available.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testGetVenueUrlNoVenueNameAnqpElement() throws Exception {
+        // static mocking
+        MockitoSession session =
+                com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession().mockStatic(
+                        InformationElementUtil.class).startMocking();
+        try {
+            ScanResult scanResult = createTestScanResult();
+            InformationElementUtil.Vsa vsa = new InformationElementUtil.Vsa();
+            vsa.anqpDomainID = scanResult.anqpDomainId;
+            when(InformationElementUtil.getHS2VendorSpecificIE(isNull())).thenReturn(vsa);
+
+            Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
+            anqpElementMap.put(ANQPElementType.ANQPDomName,
+                    new DomainNameElement(Arrays.asList(new String[]{"test.com"})));
+
+            Map<Integer, URL> venueUrls = new HashMap<>();
+            venueUrls.put(1, new URL(TEST_VENUE_URL_ENG));
+            venueUrls.put(2, new URL(TEST_VENUE_URL_HEB));
+            anqpElementMap.put(ANQPElementType.ANQPVenueUrl, new VenueUrlElement(venueUrls));
+
+            mAnqpCache.addOrUpdateEntry(TEST_ANQP_KEY, anqpElementMap);
+            ANQPData entry = new ANQPData(mClock, anqpElementMap);
+            when(mAnqpCache.getEntry(TEST_ANQP_KEY)).thenReturn(entry);
+
+            URL venueUrl = mManager.getVenueUrl(scanResult);
+            assertNull(venueUrl);
+
+            // Now try with an incomplete list of venue names
+            List<I18Name> names = new ArrayList<>();
+            names.add(new I18Name(TEST_LOCALE_ENGLISH,
+                    new Locale.Builder().setLanguage(TEST_LOCALE_ENGLISH).build(),
+                    "Passpoint Venue"));
+            anqpElementMap.put(ANQPElementType.ANQPVenueName, new VenueNameElement(names));
+            entry = new ANQPData(mClock, anqpElementMap);
+            when(mAnqpCache.getEntry(TEST_ANQP_KEY)).thenReturn(entry);
+
+            venueUrl = mManager.getVenueUrl(scanResult);
+            assertNull(venueUrl);
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+    /**
+     * Verify that Passpoint manager handles the terms and conditions URL correctly: Accepts only
+     * HTTPS URLs, and rejects HTTP and invalid URLs.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testHandleTermsAndConditionsEvent() throws Exception {
+        WifiConfiguration config = WifiConfigurationTestUtil.createPasspointNetwork();
+        PasspointProvider passpointProvider = addTestProvider(TEST_FQDN, TEST_FRIENDLY_NAME,
+                TEST_PACKAGE, config, false, null);
+        assertEquals(TEST_TERMS_AND_CONDITIONS_URL, mManager.handleTermsAndConditionsEvent(
+                WnmData.createTermsAndConditionsAccetanceRequiredEvent(TEST_BSSID,
+                        TEST_TERMS_AND_CONDITIONS_URL), config).toString());
+
+        // Verify that this provider is never blocked
+        verify(passpointProvider, never()).blockBssOrEss(anyLong(), anyBoolean(), anyInt());
+
+        assertNull(mManager.handleTermsAndConditionsEvent(
+                WnmData.createTermsAndConditionsAccetanceRequiredEvent(TEST_BSSID,
+                        TEST_TERMS_AND_CONDITIONS_URL_NON_HTTPS), config));
+
+        // Verify that the ESS is blocked for 24 hours, the URL is non-HTTPS and unlikely to change
+        verify(passpointProvider).blockBssOrEss(eq(TEST_BSSID), eq(true), eq(24 * 60 * 60));
+
+        assertNull(mManager.handleTermsAndConditionsEvent(
+                WnmData.createTermsAndConditionsAccetanceRequiredEvent(TEST_BSSID,
+                        TEST_TERMS_AND_CONDITIONS_URL_INVALID), config));
+
+        // Verify that the ESS is blocked for an hour due to a temporary issue with the URL
+        verify(passpointProvider).blockBssOrEss(eq(TEST_BSSID), eq(true), eq(60 * 60));
+
+        // Now try with a non-Passpoint network
+        config = WifiConfigurationTestUtil.createEapNetwork();
+        assertNull(mManager.handleTermsAndConditionsEvent(
+                WnmData.createTermsAndConditionsAccetanceRequiredEvent(TEST_BSSID,
+                        TEST_TERMS_AND_CONDITIONS_URL), config));
+        // and a null configuration
+        assertNull(mManager.handleTermsAndConditionsEvent(
+                WnmData.createTermsAndConditionsAccetanceRequiredEvent(TEST_BSSID,
+                        TEST_TERMS_AND_CONDITIONS_URL), null));
+    }
+
+    /**
+     * Verify that Passpoint manager clears states and flushes caches as expected.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testClearAnqpRequestsAndFlushCache() throws Exception {
+        PasspointProvider provider = addTestProvider(TEST_FQDN, TEST_FRIENDLY_NAME,
+                TEST_PACKAGE, false, TEST_REALM);
+
+        mManager.clearAnqpRequestsAndFlushCache();
+        verify(mAnqpRequestManager).clear();
+        verify(mAnqpCache).flush();
+        verify(provider).clearProviderBlock();
+    }
 }
+
diff --git a/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointNetworkNominateHelperTest.java b/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointNetworkNominateHelperTest.java
index a361a19..a029b19 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointNetworkNominateHelperTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointNetworkNominateHelperTest.java
@@ -59,6 +59,7 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -238,7 +239,54 @@
         verify(mWifiConfigManager).enableNetwork(
                 eq(TEST_NETWORK_ID), eq(false), anyInt(), any());
         verify(mWifiConfigManager).setNetworkCandidateScanResult(
-                eq(TEST_NETWORK_ID), any(ScanResult.class), anyInt());
+                eq(TEST_NETWORK_ID), any(ScanResult.class), anyInt(), any());
+        verify(mWifiConfigManager).updateScanDetailForNetwork(
+                eq(TEST_NETWORK_ID), any(ScanDetail.class));
+    }
+
+    /**
+     * Verify that when a network matches a home provider is found, the correct network
+     * information (WifiConfiguration) is setup and nominated even if the scan result does not
+     * report internet connectivity.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void evaluateScansWithNoInternetBit() throws Exception {
+        List<ScanDetail> scanDetails = Arrays.asList(generateScanDetail(TEST_SSID1, TEST_BSSID1),
+                generateScanDetail(TEST_SSID2, TEST_BSSID2));
+        for (ScanDetail scanDetail : scanDetails) {
+            when(scanDetail.getNetworkDetail().isInternet()).thenReturn(false);
+        }
+
+        // Setup matching providers for ScanDetail with TEST_SSID1.
+        List<Pair<PasspointProvider, PasspointMatch>> homeProvider = new ArrayList<>();
+        homeProvider.add(Pair.create(sTestProvider1, PasspointMatch.HomeProvider));
+
+        // Return homeProvider for the first ScanDetail (TEST_SSID1) and a null (no match) for
+        // for the second (TEST_SSID2);
+        when(mPasspointManager.matchProvider(any(ScanResult.class))).thenReturn(homeProvider)
+                .thenReturn(null);
+        when(mWifiConfigManager.addOrUpdateNetwork(any(WifiConfiguration.class), anyInt(),
+                any())).thenReturn(new NetworkUpdateResult(TEST_NETWORK_ID));
+        when(mWifiConfigManager.getConfiguredNetwork(TEST_NETWORK_ID)).thenReturn(TEST_CONFIG1);
+        List<Pair<ScanDetail, WifiConfiguration>> candidates = mNominateHelper
+                .getPasspointNetworkCandidates(scanDetails, false);
+        assertEquals(1, candidates.size());
+
+        // Verify the content of the WifiConfiguration that was added to WifiConfigManager.
+        ArgumentCaptor<WifiConfiguration> addedConfig =
+                ArgumentCaptor.forClass(WifiConfiguration.class);
+        verify(mWifiConfigManager).addOrUpdateNetwork(addedConfig.capture(), anyInt(), any());
+        assertEquals(ScanResultUtil.createQuotedSSID(TEST_SSID1), addedConfig.getValue().SSID);
+        assertEquals(TEST_FQDN1, addedConfig.getValue().FQDN);
+        assertNotNull(addedConfig.getValue().enterpriseConfig);
+        assertEquals("", addedConfig.getValue().enterpriseConfig.getAnonymousIdentity());
+        assertTrue(addedConfig.getValue().isHomeProviderNetwork);
+        verify(mWifiConfigManager).enableNetwork(
+                eq(TEST_NETWORK_ID), eq(false), anyInt(), any());
+        verify(mWifiConfigManager).setNetworkCandidateScanResult(
+                eq(TEST_NETWORK_ID), any(ScanResult.class), anyInt(), any());
         verify(mWifiConfigManager).updateScanDetailForNetwork(
                 eq(TEST_NETWORK_ID), any(ScanDetail.class));
     }
@@ -279,7 +327,7 @@
         verify(mWifiConfigManager).enableNetwork(
                 eq(TEST_NETWORK_ID), eq(false), anyInt(), any());
         verify(mWifiConfigManager).setNetworkCandidateScanResult(
-                eq(TEST_NETWORK_ID), any(ScanResult.class), anyInt());
+                eq(TEST_NETWORK_ID), any(ScanResult.class), anyInt(), any());
         verify(mWifiConfigManager).updateScanDetailForNetwork(
                 eq(TEST_NETWORK_ID), any(ScanDetail.class));
     }
@@ -385,7 +433,7 @@
         ArgumentCaptor<ScanResult> updatedCandidateScanResult =
                 ArgumentCaptor.forClass(ScanResult.class);
         verify(mWifiConfigManager).setNetworkCandidateScanResult(eq(TEST_NETWORK_ID),
-                updatedCandidateScanResult.capture(), anyInt());
+                updatedCandidateScanResult.capture(), anyInt(), any());
         assertEquals(TEST_BSSID2, updatedCandidateScanResult.getValue().BSSID);
         ArgumentCaptor<ScanDetail> updatedCandidateScanDetail =
                 ArgumentCaptor.forClass(ScanDetail.class);
@@ -477,6 +525,7 @@
         when(mPasspointManager.getANQPElements(any(ScanResult.class)))
                 .thenReturn(anqpElements);
         when(wm.getStatus()).thenReturn(HSWanMetricsElement.LINK_STATUS_DOWN);
+        when(wm.isElementInitialized()).thenReturn(true);
 
         List<Pair<ScanDetail, WifiConfiguration>> candidates = mNominateHelper
                 .getPasspointNetworkCandidates(scanDetails, false);
@@ -641,4 +690,36 @@
         assertEquals(1, candidates.size());
         assertTrue(WifiConfiguration.isMetered(candidates.get(0).second, null));
     }
+
+    /**
+     * Verify that when the WAN Metrics ANQP element is not initialized (all 0's), then the logic
+     * ignores this element.
+     */
+    @Test
+    public void evaluateScansWithNetworkMatchingHomeProviderWithUninitializedWanMetricsAnqpElement()
+            throws Exception {
+        List<ScanDetail> scanDetails = Arrays.asList(generateScanDetail(TEST_SSID1, TEST_BSSID1));
+        // Setup matching providers for ScanDetail with TEST_SSID1.
+        List<Pair<PasspointProvider, PasspointMatch>> homeProvider = new ArrayList<>();
+        homeProvider.add(Pair.create(sTestProvider1, PasspointMatch.HomeProvider));
+
+        when(mPasspointManager.matchProvider(any(ScanResult.class))).thenReturn(homeProvider);
+        when(mWifiConfigManager.addOrUpdateNetwork(any(WifiConfiguration.class), anyInt(),
+                any())).thenReturn(new NetworkUpdateResult(TEST_NETWORK_ID));
+        when(mWifiConfigManager.getConfiguredNetwork(TEST_NETWORK_ID)).thenReturn(TEST_CONFIG1);
+
+        // Setup an uninitialized WAN Metrics element (or initialized with 0's)
+        ByteBuffer buffer = ByteBuffer.allocate(HSWanMetricsElement.EXPECTED_BUFFER_SIZE);
+        buffer.put(new byte[HSWanMetricsElement.EXPECTED_BUFFER_SIZE]);
+        buffer.position(0);
+        HSWanMetricsElement wanMetricsElement = HSWanMetricsElement.parse(buffer);
+        Map<ANQPElementType, ANQPElement> anqpElements = new HashMap<>();
+        anqpElements.put(ANQPElementType.HSWANMetrics, wanMetricsElement);
+        when(mPasspointManager.getANQPElements(any(ScanResult.class)))
+                .thenReturn(anqpElements);
+
+        List<Pair<ScanDetail, WifiConfiguration>> candidates = mNominateHelper
+                .getPasspointNetworkCandidates(scanDetails, false);
+        assertEquals(1, candidates.size());
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProviderTest.java b/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProviderTest.java
index 936a8c4..1722029 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProviderTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProviderTest.java
@@ -19,12 +19,14 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.mockito.MockitoAnnotations.initMocks;
 
 import android.net.wifi.EAPConstants;
+import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.hotspot2.PasspointConfiguration;
@@ -37,7 +39,10 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.modules.utils.build.SdkLevel;
+import com.android.server.wifi.Clock;
 import com.android.server.wifi.FakeKeys;
+import com.android.server.wifi.MboOceConstants;
 import com.android.server.wifi.WifiBaseTest;
 import com.android.server.wifi.WifiCarrierInfoManager;
 import com.android.server.wifi.WifiKeyStore;
@@ -95,13 +100,13 @@
     private static final String TEST_FQDN2 = "test2.com";
     private static final String TEST_FQDN3 = "test3.com";
     private static final String TEST_FRIENDLY_NAME = "Friendly Name";
-    private static final long[] TEST_RC_OIS = new long[] {0x1234L, 0x2345L};
-    private static final long[] TEST_IE_RC_OIS = new long[] {0x1234L, 0x2133L};
-    private static final long[] TEST_IE_NO_MATCHED_RC_OIS = new long[] {0x2255L, 0x2133L};
-    private static final Long[] TEST_ANQP_RC_OIS = new Long[] {0x1234L, 0x2133L};
+    private static final long[] TEST_RC_OIS = new long[]{0x1234L, 0x2345L};
+    private static final long[] TEST_IE_RC_OIS = new long[]{0x1234L, 0x2133L};
+    private static final long[] TEST_IE_NO_MATCHED_RC_OIS = new long[]{0x2255L, 0x2133L};
+    private static final Long[] TEST_ANQP_RC_OIS = new Long[]{0x1234L, 0x2133L};
     private static final String TEST_REALM = "realm.com";
     private static final String[] TEST_TRUSTED_NAME =
-            new String[] {"trusted.fqdn.com", "another.fqdn.com"};
+            new String[]{"trusted.fqdn.com", "another.fqdn.com"};
     // User credential data
     private static final String TEST_USERNAME = "username";
     private static final String TEST_PASSWORD = "password3";
@@ -110,6 +115,18 @@
     private static final int TEST_SIM_CREDENTIAL_TYPE = EAPConstants.EAP_SIM;
     private static final String TEST_IMSI = "1234567890";
     private static final int VALID_CARRIER_ID = 1;
+    private static final int VALID_SUBSCRIPTION_ID = 2;
+
+    private static final String TEST_SSID = "TestSSID";
+    private static final String TEST_BSSID_STRING = "11:22:33:44:55:66";
+    private static final String TEST_BSSID_STRING_2 = "11:22:33:44:55:67";
+    private static final long TEST_HESSID = 0x5678L;
+    private static final int TEST_ANQP_DOMAIN_ID = 0;
+    private static final long TEST_ELAPSED_TIME_SINCE_BOOT = 100000L;
+    private static final String TEST_ANONYMOUS_IDENTITY = "AnonymousIdentity";
+    private static final String USER_CONNECT_CHOICE = "SomeNetworkProfileId";
+    private static final int TEST_RSSI = -50;
+    private static final String TEST_DECORATED_IDENTITY_PREFIX = "androidwifi.dev!";
 
     private enum CredentialType {
         USER,
@@ -117,10 +134,14 @@
         SIM
     }
 
-    @Mock WifiKeyStore mKeyStore;
+    @Mock
+    WifiKeyStore mKeyStore;
     @Mock
     WifiCarrierInfoManager mWifiCarrierInfoManager;
-    @Mock RoamingConsortium mRoamingConsortium;
+    @Mock
+    RoamingConsortium mRoamingConsortium;
+    @Mock
+    Clock mClock;
     PasspointProvider mProvider;
     X509Certificate mRemediationCaCertificate;
     String mExpectedResult;
@@ -139,7 +160,9 @@
         mExpectedResult = expectedResult;
     }
 
-    /** Sets up test. */
+    /**
+     * Sets up test.
+     */
     @Before
     public void setUp() throws Exception {
         initMocks(this);
@@ -154,15 +177,15 @@
      */
     private PasspointProvider createProvider(PasspointConfiguration config) {
         return new PasspointProvider(config, mKeyStore, mWifiCarrierInfoManager, PROVIDER_ID,
-                CREATOR_UID, CREATOR_PACKAGE, false);
+                CREATOR_UID, CREATOR_PACKAGE, false, mClock);
     }
 
     /**
-     * Verify that the configuration associated with the provider is the same or not the same
-     * as the expected configuration.
+     * Verify that the configuration associated with the provider is the same or not the same as the
+     * expected configuration.
      *
      * @param expectedConfig The expected configuration
-     * @param equals Flag indicating equality or inequality check
+     * @param equals         Flag indicating equality or inequality check
      */
     private void verifyInstalledConfig(PasspointConfiguration expectedConfig, boolean equals) {
         PasspointConfiguration actualConfig = mProvider.getConfig();
@@ -186,9 +209,9 @@
     /**
      * Helper function for creating a NAI Realm ANQP element.
      *
-     * @param realm The realm of the network
+     * @param realm       The realm of the network
      * @param eapMethodID EAP Method ID
-     * @param authParam Authentication parameter
+     * @param authParam   Authentication parameter
      * @return {@link NAIRealmElement}
      */
     private NAIRealmElement createNAIRealmElement(String realm, int eapMethodID,
@@ -200,9 +223,9 @@
             authParamMap.put(authParam.getAuthTypeID(), authSet);
         }
         EAPMethod eapMethod = new EAPMethod(eapMethodID, authParamMap);
-        NAIRealmData realmData = new NAIRealmData(Arrays.asList(new String[] {realm}),
-                Arrays.asList(new EAPMethod[] {eapMethod}));
-        return new NAIRealmElement(Arrays.asList(new NAIRealmData[] {realmData}));
+        NAIRealmData realmData = new NAIRealmData(Arrays.asList(new String[]{realm}),
+                Arrays.asList(new EAPMethod[]{eapMethod}));
+        return new NAIRealmElement(Arrays.asList(new NAIRealmData[]{realmData}));
     }
 
     /**
@@ -223,16 +246,16 @@
      */
     private ThreeGPPNetworkElement createThreeGPPNetworkElement(String[] imsiList) {
         CellularNetwork network = new CellularNetwork(Arrays.asList(imsiList));
-        return new ThreeGPPNetworkElement(Arrays.asList(new CellularNetwork[] {network}));
+        return new ThreeGPPNetworkElement(Arrays.asList(new CellularNetwork[]{network}));
     }
 
     /**
      * Helper function for generating test passpoint configuration for test cases
      *
      * @param credentialType which type credential is generated.
-     * @param isLegacy if true, omit some passpoint fields to avoid breaking comparison
-     *                 between passpoint configuration converted from wifi configuration
-     *                 and generated passpoint configuration.
+     * @param isLegacy       if true, omit some passpoint fields to avoid breaking comparison
+     *                       between passpoint configuration converted from wifi configuration and
+     *                       generated passpoint configuration.
      * @return a valid passpoint configuration
      * @throws Exception
      */
@@ -261,7 +284,7 @@
             userCredential.setUsername(TEST_USERNAME);
             userCredential.setPassword(encodedPasswordStr);
             if (!isLegacy) {
-                credential.setCaCertificates(new X509Certificate[] {FakeKeys.CA_CERT0});
+                credential.setCaCertificates(new X509Certificate[]{FakeKeys.CA_CERT0});
             }
             credential.setUserCredential(userCredential);
         } else if (credentialType == CredentialType.CERT) {
@@ -270,10 +293,10 @@
             if (!isLegacy) {
                 certCredential.setCertSha256Fingerprint(
                         MessageDigest.getInstance("SHA-256")
-                        .digest(FakeKeys.CLIENT_CERT.getEncoded()));
-                credential.setCaCertificates(new X509Certificate[] {FakeKeys.CA_CERT0});
+                                .digest(FakeKeys.CLIENT_CERT.getEncoded()));
+                credential.setCaCertificates(new X509Certificate[]{FakeKeys.CA_CERT0});
                 credential.setClientPrivateKey(FakeKeys.RSA_KEY1);
-                credential.setClientCertificateChain(new X509Certificate[] {FakeKeys.CLIENT_CERT});
+                credential.setClientCertificateChain(new X509Certificate[]{FakeKeys.CLIENT_CERT});
             } else {
                 certCredential.setCertType(Credential.CertificateCredential.CERT_TYPE_X509V3);
             }
@@ -293,8 +316,7 @@
      * Helper function for verifying wifi configuration based on Passpoint configuration
      *
      * @param passpointConfig the source of wifi configuration.
-     * @param wifiConfig wifi configuration be verified.
-     *
+     * @param wifiConfig      wifi configuration be verified.
      * @throws Exception
      */
     private void verifyWifiConfigWithTestData(
@@ -323,8 +345,13 @@
         assertFalse(wifiConfig.shared);
         assertEquals(credential.getRealm(), wifiEnterpriseConfig.getRealm());
         if (passpointConfig.isMacRandomizationEnabled()) {
-            assertEquals(WifiConfiguration.RANDOMIZATION_PERSISTENT,
-                    wifiConfig.macRandomizationSetting);
+            if (passpointConfig.isEnhancedMacRandomizationEnabled()) {
+                assertEquals(WifiConfiguration.RANDOMIZATION_NON_PERSISTENT,
+                        wifiConfig.macRandomizationSetting);
+            } else {
+                assertEquals(WifiConfiguration.RANDOMIZATION_PERSISTENT,
+                        wifiConfig.macRandomizationSetting);
+            }
         } else {
             assertEquals(WifiConfiguration.RANDOMIZATION_NONE, wifiConfig.macRandomizationSetting);
         }
@@ -433,14 +460,14 @@
     }
 
     /**
-     * Verify that modification to the configuration used for creating PasspointProvider
-     * will not change the configuration stored inside the PasspointProvider.
+     * Verify that modification to the configuration used for creating PasspointProvider will not
+     * change the configuration stored inside the PasspointProvider.
      *
      * @throws Exception
      */
     @Test
     public void verifyModifyOriginalConfig() throws Exception {
-        // Create a dummy PasspointConfiguration.
+        // Create a placeholder PasspointConfiguration.
         PasspointConfiguration config = generateTestPasspointConfiguration(
                 CredentialType.USER, false);
         mProvider = createProvider(config);
@@ -453,14 +480,14 @@
     }
 
     /**
-     * Verify that modification to the configuration retrieved from the PasspointProvider
-     * will not change the configuration stored inside the PasspointProvider.
+     * Verify that modification to the configuration retrieved from the PasspointProvider will not
+     * change the configuration stored inside the PasspointProvider.
      *
      * @throws Exception
      */
     @Test
     public void verifyModifyRetrievedConfig() throws Exception {
-        // Create a dummy PasspointConfiguration.
+        // Create a placeholder PasspointConfiguration.
         PasspointConfiguration config = generateTestPasspointConfiguration(
                 CredentialType.USER, false);
         mProvider = createProvider(config);
@@ -480,7 +507,7 @@
      */
     @Test
     public void installCertsAndKeysSuccess() throws Exception {
-        // Create a dummy configuration with certificate credential.
+        // Create a placeholder configuration with certificate credential.
         PasspointConfiguration config = generateTestPasspointConfiguration(
                 CredentialType.CERT, false);
         Credential credential = config.getCredential();
@@ -500,7 +527,7 @@
                 .thenReturn(true);
         when(mKeyStore.putUserPrivKeyAndCertsInKeyStore(
                 CLIENT_PRIVATE_KEY_AND_CERT_ALIAS, FakeKeys.RSA_KEY1,
-                new Certificate[] {FakeKeys.CLIENT_CERT}))
+                new Certificate[]{FakeKeys.CLIENT_CERT}))
                 .thenReturn(true);
         when(mKeyStore.putCaCertInKeyStore(REMEDIATION_CA_CERTIFICATE_ALIAS, FakeKeys.CA_CERT0))
                 .thenReturn(true);
@@ -529,7 +556,7 @@
      */
     @Test
     public void installCertsAndKeysFailure() throws Exception {
-        // Create a dummy configuration with certificate credential.
+        // Create a placeholder configuration with certificate credential.
         PasspointConfiguration config = generateTestPasspointConfiguration(
                 CredentialType.CERT, false);
         Credential credential = config.getCredential();
@@ -549,7 +576,7 @@
                 .thenReturn(false);
         when(mKeyStore.putUserPrivKeyAndCertsInKeyStore(
                 CLIENT_PRIVATE_KEY_AND_CERT_ALIAS, FakeKeys.RSA_KEY1,
-                new Certificate[] {FakeKeys.CLIENT_CERT}))
+                new Certificate[]{FakeKeys.CLIENT_CERT}))
                 .thenReturn(true);
         when(mKeyStore.putCaCertInKeyStore(REMEDIATION_CA_CERTIFICATE_ALIAS, FakeKeys.CA_CERT0))
                 .thenReturn(true);
@@ -574,7 +601,7 @@
      */
     @Test
     public void uninstallCertsAndKeys() throws Exception {
-        // Create a dummy configuration with certificate credential.
+        // Create a placeholder configuration with certificate credential.
         PasspointConfiguration config = generateTestPasspointConfiguration(
                 CredentialType.CERT, false);
         Credential credential = config.getCredential();
@@ -595,7 +622,7 @@
                 .thenReturn(true);
         when(mKeyStore.putUserPrivKeyAndCertsInKeyStore(
                 CLIENT_PRIVATE_KEY_AND_CERT_ALIAS, FakeKeys.RSA_KEY1,
-                new Certificate[] {FakeKeys.CLIENT_CERT}))
+                new Certificate[]{FakeKeys.CLIENT_CERT}))
                 .thenReturn(true);
         when(mKeyStore.putCaCertInKeyStore(REMEDIATION_CA_CERTIFICATE_ALIAS, FakeKeys.CA_CERT0))
                 .thenReturn(true);
@@ -622,8 +649,8 @@
     }
 
     /**
-     * Verify that a provider is a home provider when its FQDN matches a domain name in the
-     * Domain Name ANQP element and no NAI realm is provided.
+     * Verify that a provider is a home provider when its FQDN matches a domain name in the Domain
+     * Name ANQP element and no NAI realm is provided.
      *
      * @throws Exception
      */
@@ -637,15 +664,15 @@
         // Setup ANQP elements.
         Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
         anqpElementMap.put(ANQPElementType.ANQPDomName,
-                createDomainNameElement(new String[] {TEST_FQDN}));
+                createDomainNameElement(new String[]{TEST_FQDN}));
 
         assertEquals(PasspointMatch.HomeProvider,
-            mProvider.match(anqpElementMap, mRoamingConsortium));
+                mProvider.match(anqpElementMap, mRoamingConsortium, createTestScanResult()));
     }
 
     /**
-     * Verify that a provider is a home provider when its FQDN matches a domain name in the
-     * Domain Name ANQP element and the provider's credential matches the NAI realm provided.
+     * Verify that a provider is a home provider when its FQDN matches a domain name in the Domain
+     * Name ANQP element and the provider's credential matches the NAI realm provided.
      *
      * @throws Exception
      */
@@ -659,20 +686,20 @@
         // Setup Domain Name ANQP element.
         Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
         anqpElementMap.put(ANQPElementType.ANQPDomName,
-                createDomainNameElement(new String[] {TEST_FQDN}));
+                createDomainNameElement(new String[]{TEST_FQDN}));
         anqpElementMap.put(ANQPElementType.ANQPNAIRealm,
                 createNAIRealmElement(TEST_REALM, EAPConstants.EAP_TTLS,
                         new NonEAPInnerAuth(NonEAPInnerAuth.AUTH_TYPE_MSCHAPV2)));
 
         assertEquals(PasspointMatch.HomeProvider,
-            mProvider.match(anqpElementMap, mRoamingConsortium));
+                mProvider.match(anqpElementMap, mRoamingConsortium, createTestScanResult()));
     }
 
     /**
      * Verify that Home provider is matched even when the provider's FQDN matches a domain name in
      * the Domain Name ANQP element but the provider's credential doesn't match the authentication
-     * method provided in the NAI realm. This can happen when the infrastructure provider is not
-     * the identity provider, and authentication method matching is not required in the spec.
+     * method provided in the NAI realm. This can happen when the infrastructure provider is not the
+     * identity provider, and authentication method matching is not required in the spec.
      *
      * @throws Exception
      */
@@ -686,12 +713,12 @@
         // Setup Domain Name ANQP element.
         Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
         anqpElementMap.put(ANQPElementType.ANQPDomName,
-                createDomainNameElement(new String[] {TEST_FQDN}));
+                createDomainNameElement(new String[]{TEST_FQDN}));
         anqpElementMap.put(ANQPElementType.ANQPNAIRealm,
                 createNAIRealmElement(TEST_REALM, EAPConstants.EAP_TLS, null));
 
         assertEquals(PasspointMatch.HomeProvider,
-                mProvider.match(anqpElementMap, mRoamingConsortium));
+                mProvider.match(anqpElementMap, mRoamingConsortium, createTestScanResult()));
     }
 
     /**
@@ -712,16 +739,16 @@
         // Setup Domain Name ANQP element.
         Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
         anqpElementMap.put(ANQPElementType.ANQPDomName,
-                createDomainNameElement(new String[] {"wlan.mnc456.mcc123.3gppnetwork.org"}));
+                createDomainNameElement(new String[]{"wlan.mnc456.mcc123.3gppnetwork.org"}));
 
         assertEquals(PasspointMatch.HomeProvider,
-            mProvider.match(anqpElementMap, mRoamingConsortium));
+                mProvider.match(anqpElementMap, mRoamingConsortium, createTestScanResult()));
     }
 
     /**
-     * Verify that a provider is a home provider when its FQDN, roaming consortium OI, and
-     * IMSI all matched against the ANQP elements, since we prefer matching home provider over
-     * roaming provider.
+     * Verify that a provider is a home provider when its FQDN, roaming consortium OI, and IMSI all
+     * matched against the ANQP elements, since we prefer matching home provider over roaming
+     * provider.
      *
      * @throws Exception
      */
@@ -737,19 +764,19 @@
         // Setup ANQP elements.
         Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
         anqpElementMap.put(ANQPElementType.ANQPDomName,
-                createDomainNameElement(new String[] {TEST_FQDN}));
+                createDomainNameElement(new String[]{TEST_FQDN}));
         anqpElementMap.put(ANQPElementType.ANQPRoamingConsortium,
                 createRoamingConsortiumElement(TEST_ANQP_RC_OIS));
         anqpElementMap.put(ANQPElementType.ANQP3GPPNetwork,
-                createThreeGPPNetworkElement(new String[] {"123456"}));
+                createThreeGPPNetworkElement(new String[]{"123456"}));
 
         assertEquals(PasspointMatch.HomeProvider,
-                mProvider.match(anqpElementMap, mRoamingConsortium));
+                mProvider.match(anqpElementMap, mRoamingConsortium, createTestScanResult()));
     }
 
     /**
-     * Verify that a provider is a roaming provider when a roaming consortium OI matches an OI
-     * in the roaming consortium ANQP element and no NAI realm is provided.
+     * Verify that a provider is a roaming provider when a roaming consortium OI matches an OI in
+     * the roaming consortium ANQP element and no NAI realm is provided.
      *
      * @throws Exception
      */
@@ -769,13 +796,13 @@
                 createRoamingConsortiumElement(TEST_ANQP_RC_OIS));
 
         assertEquals(PasspointMatch.RoamingProvider,
-            mProvider.match(anqpElementMap, mRoamingConsortium));
+                mProvider.match(anqpElementMap, mRoamingConsortium, createTestScanResult()));
     }
 
     /**
-     * Verify that a provider is a roaming provider when a roaming consortium OI matches an OI
-     * in the roaming consortium ANQP element and the provider's credential matches the
-     * NAI realm provided.
+     * Verify that a provider is a roaming provider when a roaming consortium OI matches an OI in
+     * the roaming consortium ANQP element and the provider's credential matches the NAI realm
+     * provided.
      *
      * @throws Exception
      */
@@ -796,12 +823,12 @@
                         new NonEAPInnerAuth(NonEAPInnerAuth.AUTH_TYPE_MSCHAPV2)));
 
         assertEquals(PasspointMatch.RoamingProvider,
-                mProvider.match(anqpElementMap, mRoamingConsortium));
+                mProvider.match(anqpElementMap, mRoamingConsortium, createTestScanResult()));
     }
 
     /**
-     * Verify that there is Roaming provider match when a roaming consortium OI matches an OI
-     * in the roaming consortium ANQP element and regardless of NAI realm mismatch.
+     * Verify that there is Roaming provider match when a roaming consortium OI matches an OI in the
+     * roaming consortium ANQP element and regardless of NAI realm mismatch.
      *
      * @throws Exception
      */
@@ -821,12 +848,12 @@
                 createNAIRealmElement(TEST_REALM, EAPConstants.EAP_TLS, null));
 
         assertEquals(PasspointMatch.RoamingProvider,
-                mProvider.match(anqpElementMap, mRoamingConsortium));
+                mProvider.match(anqpElementMap, mRoamingConsortium, createTestScanResult()));
     }
 
     /**
-     * Verify that a provider is a roaming provider when a roaming consortium OI matches an OI
-     * in the roaming consortium information element and no NAI realm is provided.
+     * Verify that a provider is a roaming provider when a roaming consortium OI matches an OI in
+     * the roaming consortium information element and no NAI realm is provided.
      *
      * @throws Exception
      */
@@ -844,13 +871,13 @@
         when(mRoamingConsortium.getRoamingConsortiums()).thenReturn(TEST_IE_RC_OIS);
 
         assertEquals(PasspointMatch.RoamingProvider,
-            mProvider.match(anqpElementMap, mRoamingConsortium));
+                mProvider.match(anqpElementMap, mRoamingConsortium, createTestScanResult()));
     }
 
     /**
-     * Verify that a provider is a roaming provider when a roaming consortium OI matches an OI
-     * in the roaming consortium information element and the provider's credential matches the
-     * NAI realm provided.
+     * Verify that a provider is a roaming provider when a roaming consortium OI matches an OI in
+     * the roaming consortium information element and the provider's credential matches the NAI
+     * realm provided.
      *
      * @throws Exception
      */
@@ -871,18 +898,16 @@
                         new NonEAPInnerAuth(NonEAPInnerAuth.AUTH_TYPE_MSCHAPV2)));
 
         assertEquals(PasspointMatch.RoamingProvider,
-                mProvider.match(anqpElementMap, mRoamingConsortium));
+                mProvider.match(anqpElementMap, mRoamingConsortium, createTestScanResult()));
     }
 
     /**
-     * Verify that there is Roaming provider match when a roaming consortium OI matches an OI
-     * in the roaming consortium information element, but NAI realm is not matched.
-     * This can happen in roaming federation where the infrastructure provider is not the
-     * identity provider.
-     * Page 133 in the Hotspot2.0 specification states:
-     * Per subclause 11.25.8 of [2], if the value of HomeOI matches an OI in the Roaming
-     * Consortium advertised by a hotspot operator, successful authentication with that hotspot
-     * is possible.
+     * Verify that there is Roaming provider match when a roaming consortium OI matches an OI in the
+     * roaming consortium information element, but NAI realm is not matched. This can happen in
+     * roaming federation where the infrastructure provider is not the identity provider. Page 133
+     * in the Hotspot2.0 specification states: Per subclause 11.25.8 of [2], if the value of HomeOI
+     * matches an OI in the Roaming Consortium advertised by a hotspot operator, successful
+     * authentication with that hotspot is possible.
      *
      * @throws Exception
      */
@@ -903,13 +928,12 @@
                 createNAIRealmElement(TEST_REALM, EAPConstants.EAP_TLS, null));
 
         assertEquals(PasspointMatch.RoamingProvider,
-                mProvider.match(anqpElementMap, mRoamingConsortium));
+                mProvider.match(anqpElementMap, mRoamingConsortium, createTestScanResult()));
     }
 
     /**
-     * Verify that none of matched providers are found when a roaming consortium OI doesn't
-     * matches an OI in the roaming consortium information element and
-     * none of NAI realms match each other.
+     * Verify that none of matched providers are found when a roaming consortium OI doesn't matches
+     * an OI in the roaming consortium information element and none of NAI realms match each other.
      *
      * @throws Exception
      */
@@ -927,7 +951,7 @@
         when(mRoamingConsortium.getRoamingConsortiums()).thenReturn(TEST_IE_NO_MATCHED_RC_OIS);
 
         assertEquals(PasspointMatch.None,
-            mProvider.match(anqpElementMap, mRoamingConsortium));
+                mProvider.match(anqpElementMap, mRoamingConsortium, createTestScanResult()));
     }
 
     /**
@@ -949,15 +973,15 @@
         // Setup 3GPP Network ANQP element.
         Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
         anqpElementMap.put(ANQPElementType.ANQP3GPPNetwork,
-                createThreeGPPNetworkElement(new String[] {"123456"}));
+                createThreeGPPNetworkElement(new String[]{"123456"}));
 
         // Setup NAI Realm ANQP element with different realm.
         anqpElementMap.put(ANQPElementType.ANQPNAIRealm,
                 createNAIRealmElement(TEST_REALM, EAPConstants.EAP_TTLS,
-                new NonEAPInnerAuth(NonEAPInnerAuth.AUTH_TYPE_MSCHAPV2)));
+                        new NonEAPInnerAuth(NonEAPInnerAuth.AUTH_TYPE_MSCHAPV2)));
 
         assertEquals(PasspointMatch.RoamingProvider,
-            mProvider.match(anqpElementMap, mRoamingConsortium));
+                mProvider.match(anqpElementMap, mRoamingConsortium, createTestScanResult()));
     }
 
     /**
@@ -979,20 +1003,20 @@
         // Setup 3GPP Network ANQP element.
         Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
         anqpElementMap.put(ANQPElementType.ANQP3GPPNetwork,
-                createThreeGPPNetworkElement(new String[] {"123456"}));
+                createThreeGPPNetworkElement(new String[]{"123456"}));
 
         // Setup NAI Realm ANQP element with same realm.
         anqpElementMap.put(ANQPElementType.ANQPNAIRealm,
                 createNAIRealmElement(TEST_REALM, EAPConstants.EAP_AKA, null));
 
         assertEquals(PasspointMatch.RoamingProvider,
-                mProvider.match(anqpElementMap, mRoamingConsortium));
+                mProvider.match(anqpElementMap, mRoamingConsortium, createTestScanResult()));
         assertEquals(VALID_CARRIER_ID, mProvider.getWifiConfig().carrierId);
     }
 
     /**
-     * Verify that when the SIM card matched by carrier ID of profile is absent, it shouldn't
-     * be matched even the profile and ANQP elements are matched.
+     * Verify that when the SIM card matched by carrier ID of profile is absent, it shouldn't be
+     * matched even the profile and ANQP elements are matched.
      *
      * @throws Exception
      */
@@ -1002,26 +1026,28 @@
         PasspointConfiguration config = generateTestPasspointConfiguration(
                 CredentialType.SIM, false);
         config.setCarrierId(VALID_CARRIER_ID);
-        when(mWifiCarrierInfoManager.getMatchingImsi(eq(VALID_CARRIER_ID)))
+        when(mWifiCarrierInfoManager.getMatchingSubId(eq(VALID_CARRIER_ID)))
+                .thenReturn(VALID_SUBSCRIPTION_ID);
+        when(mWifiCarrierInfoManager.getMatchingImsiBySubId(eq(VALID_SUBSCRIPTION_ID)))
                 .thenReturn(null);
         mProvider = createProvider(config);
 
         // Setup 3GPP Network ANQP element.
         Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
         anqpElementMap.put(ANQPElementType.ANQP3GPPNetwork,
-                createThreeGPPNetworkElement(new String[] {"123456"}));
+                createThreeGPPNetworkElement(new String[]{"123456"}));
 
         // Setup NAI Realm ANQP element with same realm.
         anqpElementMap.put(ANQPElementType.ANQPNAIRealm,
                 createNAIRealmElement(TEST_REALM, EAPConstants.EAP_AKA, null));
 
         assertEquals(PasspointMatch.None,
-                mProvider.match(anqpElementMap, mRoamingConsortium));
+                mProvider.match(anqpElementMap, mRoamingConsortium, createTestScanResult()));
     }
 
     /**
-     * Verify that when the SIM card matched by IMSI of profile is absent, it shouldn't be
-     * matched even the profile and ANQP elements are matched.
+     * Verify that when the SIM card matched by IMSI of profile is absent, it shouldn't be matched
+     * even the profile and ANQP elements are matched.
      *
      * @throws Exception
      */
@@ -1037,14 +1063,14 @@
         // Setup 3GPP Network ANQP element.
         Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
         anqpElementMap.put(ANQPElementType.ANQP3GPPNetwork,
-                createThreeGPPNetworkElement(new String[] {"123456"}));
+                createThreeGPPNetworkElement(new String[]{"123456"}));
 
         // Setup NAI Realm ANQP element with same realm.
         anqpElementMap.put(ANQPElementType.ANQPNAIRealm,
                 createNAIRealmElement(TEST_REALM, EAPConstants.EAP_AKA, null));
 
         assertEquals(PasspointMatch.None,
-                mProvider.match(anqpElementMap, mRoamingConsortium));
+                mProvider.match(anqpElementMap, mRoamingConsortium, createTestScanResult()));
     }
 
     /**
@@ -1067,12 +1093,12 @@
                         new NonEAPInnerAuth(NonEAPInnerAuth.AUTH_TYPE_MSCHAPV2)));
 
         assertEquals(PasspointMatch.RoamingProvider,
-            mProvider.match(anqpElementMap, mRoamingConsortium));
+                mProvider.match(anqpElementMap, mRoamingConsortium, createTestScanResult()));
     }
 
     /**
-     * Verify that an expected WifiConfiguration will be returned for a Passpoint provider
-     * with a user credential.
+     * Verify that an expected WifiConfiguration will be returned for a Passpoint provider with a
+     * user credential.
      *
      * @throws Exception
      */
@@ -1100,8 +1126,8 @@
     }
 
     /**
-     * Verify that an expected WifiConfiguration will be returned for a Passpoint provider
-     * with a user credential which has AAA server trusted names provisioned.
+     * Verify that an expected WifiConfiguration will be returned for a Passpoint provider with a
+     * user credential which has AAA server trusted names provisioned.
      *
      * @throws Exception
      */
@@ -1133,8 +1159,8 @@
     }
 
     /**
-     * Verify that an expected WifiConfiguration will be returned for a Passpoint provider
-     * with a user credential which has no CA cert.
+     * Verify that an expected WifiConfiguration will be returned for a Passpoint provider with a
+     * user credential which has no CA cert.
      *
      * @throws Exception
      */
@@ -1152,8 +1178,8 @@
     }
 
     /**
-     * Verify that an expected WifiConfiguration will be returned for a Passpoint provider
-     * with a certificate credential.
+     * Verify that an expected WifiConfiguration will be returned for a Passpoint provider with a
+     * certificate credential.
      *
      * @throws Exception
      */
@@ -1169,7 +1195,7 @@
                 .thenReturn(true);
         when(mKeyStore.putUserPrivKeyAndCertsInKeyStore(
                 CLIENT_PRIVATE_KEY_AND_CERT_ALIAS, FakeKeys.RSA_KEY1,
-                new Certificate[] {FakeKeys.CLIENT_CERT}))
+                new Certificate[]{FakeKeys.CLIENT_CERT}))
                 .thenReturn(true);
         assertTrue(mProvider.installCertsAndKeys());
 
@@ -1179,8 +1205,8 @@
     }
 
     /**
-     * Verify that an expected WifiConfiguration will be returned for a Passpoint provider
-     * with a certificate credential which has AAA server trusted names provisioned.
+     * Verify that an expected WifiConfiguration will be returned for a Passpoint provider with a
+     * certificate credential which has AAA server trusted names provisioned.
      *
      * @throws Exception
      */
@@ -1197,7 +1223,7 @@
                 .thenReturn(true);
         when(mKeyStore.putUserPrivKeyAndCertsInKeyStore(
                 CLIENT_PRIVATE_KEY_AND_CERT_ALIAS, FakeKeys.RSA_KEY1,
-                new Certificate[] {FakeKeys.CLIENT_CERT}))
+                new Certificate[]{FakeKeys.CLIENT_CERT}))
                 .thenReturn(true);
         assertTrue(mProvider.installCertsAndKeys());
 
@@ -1207,8 +1233,8 @@
     }
 
     /**
-     * Verify that an expected WifiConfiguration will be returned for a Passpoint provider
-     * with a certificate credential which has no CA cert.
+     * Verify that an expected WifiConfiguration will be returned for a Passpoint provider with a
+     * certificate credential which has no CA cert.
      *
      * @throws Exception
      */
@@ -1223,7 +1249,7 @@
         // Install certificate.
         when(mKeyStore.putUserPrivKeyAndCertsInKeyStore(
                 CLIENT_PRIVATE_KEY_AND_CERT_ALIAS, FakeKeys.RSA_KEY1,
-                new Certificate[] {FakeKeys.CLIENT_CERT}))
+                new Certificate[]{FakeKeys.CLIENT_CERT}))
                 .thenReturn(true);
         assertTrue(mProvider.installCertsAndKeys());
 
@@ -1233,8 +1259,8 @@
     }
 
     /**
-     * Verify that an expected WifiConfiguration will be returned for a Passpoint provider
-     * with a SIM credential.
+     * Verify that an expected WifiConfiguration will be returned for a Passpoint provider with a
+     * SIM credential.
      *
      * @throws Exception
      */
@@ -1251,7 +1277,7 @@
     }
 
     @Test
-    public void getWifiConfigWithWithAutojoinDisable() throws Exception {
+    public void getWifiConfigWithAutojoinDisable() throws Exception {
         PasspointConfiguration config = generateTestPasspointConfiguration(
                 CredentialType.USER, false);
         mProvider = createProvider(config);
@@ -1261,6 +1287,33 @@
         assertFalse(mProvider.getWifiConfig().allowAutojoin);
     }
 
+    @Test
+    public void getWifiConfigWithCarrierMerged() throws Exception {
+        PasspointConfiguration config = generateTestPasspointConfiguration(
+                CredentialType.USER, false);
+        config.setCarrierMerged(true);
+        mProvider = createProvider(config);
+        assertTrue(mProvider.getWifiConfig().carrierMerged);
+    }
+
+    @Test
+    public void getWifiConfigWithOemPaid() throws Exception {
+        PasspointConfiguration config = generateTestPasspointConfiguration(
+                CredentialType.USER, false);
+        config.setOemPaid(true);
+        mProvider = createProvider(config);
+        assertTrue(mProvider.getWifiConfig().oemPaid);
+    }
+
+    @Test
+    public void getWifiConfigWithOemPrivate() throws Exception {
+        PasspointConfiguration config = generateTestPasspointConfiguration(
+                CredentialType.USER, false);
+        config.setOemPrivate(true);
+        mProvider = createProvider(config);
+        assertTrue(mProvider.getWifiConfig().oemPrivate);
+    }
+
     /**
      * Verify that the mac randomization setting will be included in the generated
      * WifiConfiguration.
@@ -1276,8 +1329,37 @@
     }
 
     /**
-     * Verify that an expected {@link PasspointConfiguration} will be returned when converting
-     * from a {@link WifiConfiguration} containing a user credential.
+     * Verify the generated WifiConfiguration.macRandomizationSetting defaults to
+     * RANDOMIZATION_PERSISTENT.
+     */
+    @Test
+    public void testMacRandomizationSettingDefaultIsPersistent() throws Exception {
+        PasspointConfiguration config = generateTestPasspointConfiguration(
+                CredentialType.SIM, false);
+        mProvider = createProvider(config);
+
+        assertEquals(WifiConfiguration.RANDOMIZATION_PERSISTENT,
+                mProvider.getWifiConfig().macRandomizationSetting);
+    }
+
+    /**
+     * Verify the WifiConfiguration is generated properly with settings to use enhanced MAC
+     * randomization.
+     */
+    @Test
+    public void testMacRandomizationSettingEnhanced() throws Exception {
+        PasspointConfiguration config = generateTestPasspointConfiguration(
+                CredentialType.SIM, false);
+        config.setEnhancedMacRandomizationEnabled(true);
+        mProvider = createProvider(config);
+
+        assertEquals(WifiConfiguration.RANDOMIZATION_NON_PERSISTENT,
+                mProvider.getWifiConfig().macRandomizationSetting);
+    }
+
+    /**
+     * Verify that an expected {@link PasspointConfiguration} will be returned when converting from
+     * a {@link WifiConfiguration} containing a user credential.
      *
      * @throws Exception
      */
@@ -1305,8 +1387,8 @@
     }
 
     /**
-     * Verify that an expected {@link PasspointConfiguration} will be returned when converting
-     * from a {@link WifiConfiguration} containing a SIM credential.
+     * Verify that an expected {@link PasspointConfiguration} will be returned when converting from
+     * a {@link WifiConfiguration} containing a SIM credential.
      *
      * @throws Exception
      */
@@ -1329,8 +1411,8 @@
     }
 
     /**
-     * Verify that an expected {@link PasspointConfiguration} will be returned when converting
-     * from a {@link WifiConfiguration} containing a certificate credential.
+     * Verify that an expected {@link PasspointConfiguration} will be returned when converting from
+     * a {@link WifiConfiguration} containing a certificate credential.
      *
      * @throws Exception
      */
@@ -1382,8 +1464,8 @@
     }
 
     /**
-     * Verify that hasEverConnected flag is set correctly using
-     * {@link PasspointProvider#setHasEverConnected}.
+     * Verify that hasEverConnected flag is set correctly using {@link
+     * PasspointProvider#setHasEverConnected}.
      *
      * @throws Exception
      */
@@ -1437,8 +1519,8 @@
     }
 
     /**
-     * Verify that an expected WifiConfiguration will be returned for a Passpoint provider
-     * with a user credential.
+     * Verify that an expected WifiConfiguration will be returned for a Passpoint provider with a
+     * user credential.
      *
      * @throws Exception
      */
@@ -1451,17 +1533,17 @@
         // Configuration was created with TEST_FQDN as the FQDN, add TEST_FQDN3 as other home
         // partner.
         HomeSp homeSp = config.getHomeSp();
-        homeSp.setOtherHomePartners(new String [] {TEST_FQDN3});
+        homeSp.setOtherHomePartners(new String[]{TEST_FQDN3});
         config.setHomeSp(homeSp);
         mProvider = createProvider(config);
 
         // Setup Domain Name ANQP element to TEST_FQDN2 and TEST_FQDN3
         Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
         anqpElementMap.put(ANQPElementType.ANQPDomName,
-                createDomainNameElement(new String[] {TEST_FQDN2, TEST_FQDN3}));
+                createDomainNameElement(new String[]{TEST_FQDN2, TEST_FQDN3}));
 
         assertEquals(PasspointMatch.HomeProvider,
-                mProvider.match(anqpElementMap, mRoamingConsortium));
+                mProvider.match(anqpElementMap, mRoamingConsortium, createTestScanResult()));
     }
 
     /**
@@ -1474,7 +1556,7 @@
         // Setup test provider.
         PasspointConfiguration config = generateTestPasspointConfiguration(
                 CredentialType.USER, false);
-        Long[] anqpOis = new Long[] {0x1234L, 0xdeadL, 0xf0cdL};
+        Long[] anqpOis = new Long[]{0x1234L, 0xdeadL, 0xf0cdL};
 
         // Configuration was created with TEST_FQDN as the FQDN
         HomeSp homeSp = config.getHomeSp();
@@ -1486,13 +1568,13 @@
         // Setup Domain Name ANQP element to TEST_FQDN2 and TEST_FQDN3
         Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
         anqpElementMap.put(ANQPElementType.ANQPDomName,
-                createDomainNameElement(new String[] {TEST_FQDN2, TEST_FQDN3}));
+                createDomainNameElement(new String[]{TEST_FQDN2, TEST_FQDN3}));
         // Setup RCOIs advertised by the AP
         anqpElementMap.put(ANQPElementType.ANQPRoamingConsortium,
                 createRoamingConsortiumElement(anqpOis));
 
         assertEquals(PasspointMatch.HomeProvider,
-                mProvider.match(anqpElementMap, mRoamingConsortium));
+                mProvider.match(anqpElementMap, mRoamingConsortium, createTestScanResult()));
     }
 
     /**
@@ -1505,7 +1587,7 @@
         // Setup test provider.
         PasspointConfiguration config = generateTestPasspointConfiguration(
                 CredentialType.USER, false);
-        Long[] anqpOis = new Long[] {0x12a4L, 0xceadL, 0xf0cdL};
+        Long[] anqpOis = new Long[]{0x12a4L, 0xceadL, 0xf0cdL};
 
         // Configuration was created with TEST_FQDN as the FQDN
         HomeSp homeSp = config.getHomeSp();
@@ -1517,13 +1599,13 @@
         // Setup Domain Name ANQP element to TEST_FQDN2 and TEST_FQDN3
         Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
         anqpElementMap.put(ANQPElementType.ANQPDomName,
-                createDomainNameElement(new String[] {TEST_FQDN2, TEST_FQDN3}));
+                createDomainNameElement(new String[]{TEST_FQDN2, TEST_FQDN3}));
         // Setup RCOIs advertised by the AP
         anqpElementMap.put(ANQPElementType.ANQPRoamingConsortium,
                 createRoamingConsortiumElement(anqpOis));
 
         assertEquals(PasspointMatch.None,
-                mProvider.match(anqpElementMap, mRoamingConsortium));
+                mProvider.match(anqpElementMap, mRoamingConsortium, createTestScanResult()));
     }
 
     /**
@@ -1536,7 +1618,7 @@
         // Setup test provider.
         PasspointConfiguration config = generateTestPasspointConfiguration(
                 CredentialType.USER, false);
-        Long[] anqpOis = new Long[] {0x1234L, 0x2345L, 0xabcdL, 0xdeadL, 0xf0cdL};
+        Long[] anqpOis = new Long[]{0x1234L, 0x2345L, 0xabcdL, 0xdeadL, 0xf0cdL};
 
         // Configuration was created with TEST_FQDN as the FQDN
         HomeSp homeSp = config.getHomeSp();
@@ -1548,13 +1630,13 @@
         // Setup Domain Name ANQP element to TEST_FQDN2 and TEST_FQDN3
         Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
         anqpElementMap.put(ANQPElementType.ANQPDomName,
-                createDomainNameElement(new String[] {TEST_FQDN2, TEST_FQDN3}));
+                createDomainNameElement(new String[]{TEST_FQDN2, TEST_FQDN3}));
         // Setup RCOIs advertised by the AP
         anqpElementMap.put(ANQPElementType.ANQPRoamingConsortium,
                 createRoamingConsortiumElement(anqpOis));
 
         assertEquals(PasspointMatch.HomeProvider,
-                mProvider.match(anqpElementMap, mRoamingConsortium));
+                mProvider.match(anqpElementMap, mRoamingConsortium, createTestScanResult()));
     }
 
     /**
@@ -1568,7 +1650,7 @@
         PasspointConfiguration config = generateTestPasspointConfiguration(
                 CredentialType.USER, false);
         // 0x1234 matches, but 0x2345 does not
-        Long[] anqpOis = new Long[] {0x1234L, 0x5678L, 0xdeadL, 0xf0cdL};
+        Long[] anqpOis = new Long[]{0x1234L, 0x5678L, 0xdeadL, 0xf0cdL};
 
         // Configuration was created with TEST_FQDN as the FQDN
         HomeSp homeSp = config.getHomeSp();
@@ -1580,12 +1662,298 @@
         // Setup Domain Name ANQP element to TEST_FQDN2 and TEST_FQDN3
         Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
         anqpElementMap.put(ANQPElementType.ANQPDomName,
-                createDomainNameElement(new String[] {TEST_FQDN2, TEST_FQDN3}));
+                createDomainNameElement(new String[]{TEST_FQDN2, TEST_FQDN3}));
         // Setup RCOIs advertised by the AP
         anqpElementMap.put(ANQPElementType.ANQPRoamingConsortium,
                 createRoamingConsortiumElement(anqpOis));
 
         assertEquals(PasspointMatch.None,
-                mProvider.match(anqpElementMap, mRoamingConsortium));
+                mProvider.match(anqpElementMap, mRoamingConsortium, createTestScanResult()));
+    }
+
+    /**
+     * Helper function for creating a ScanResult for testing.
+     *
+     * @return {@link ScanResult}
+     */
+    private ScanResult createTestScanResult() {
+        ScanResult scanResult = new ScanResult();
+        scanResult.SSID = TEST_SSID;
+        scanResult.BSSID = TEST_BSSID_STRING;
+        scanResult.hessid = TEST_HESSID;
+        scanResult.anqpDomainId = TEST_ANQP_DOMAIN_ID;
+        scanResult.flags = ScanResult.FLAG_PASSPOINT_NETWORK;
+        return scanResult;
+    }
+
+    /**
+     * Verify that a home provider is not matched followed by a Deauthentication-imminent WNM
+     * notification from the AP that covers a specific BSS.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void noMatchWithBlockedBss() throws Exception {
+        // Setup test provider.
+        PasspointConfiguration config = generateTestPasspointConfiguration(
+                CredentialType.USER, false);
+        mProvider = createProvider(config);
+
+        // Setup Domain Name ANQP element.
+        Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
+        anqpElementMap.put(ANQPElementType.ANQPDomName,
+                createDomainNameElement(new String[]{TEST_FQDN}));
+        anqpElementMap.put(ANQPElementType.ANQPNAIRealm,
+                createNAIRealmElement(TEST_REALM, EAPConstants.EAP_TTLS,
+                        new NonEAPInnerAuth(NonEAPInnerAuth.AUTH_TYPE_MSCHAPV2)));
+
+        ScanResult scanResult = createTestScanResult();
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(TEST_ELAPSED_TIME_SINCE_BOOT);
+
+        // Confirm this is a match under normal circumstances
+        assertEquals(PasspointMatch.HomeProvider,
+                mProvider.match(anqpElementMap, mRoamingConsortium, scanResult));
+
+        // Now block the BSS (simulate Deauth-imminent notification)
+        mProvider.blockBssOrEss(Utils.parseMac(scanResult.BSSID), false, 300 /* Seconds */);
+
+        // Confirm there is no match
+        assertEquals(PasspointMatch.None,
+                mProvider.match(anqpElementMap, mRoamingConsortium, scanResult));
+
+        // Now modify the BSSID
+        scanResult.BSSID = TEST_BSSID_STRING_2;
+
+        // Confirm there is a match
+        assertEquals(PasspointMatch.HomeProvider,
+                mProvider.match(anqpElementMap, mRoamingConsortium, scanResult));
+
+        // Now clear the block
+        mProvider.clearProviderBlock();
+        scanResult.BSSID = TEST_BSSID_STRING;
+
+        // Confirm this is a match under normal circumstances
+        assertEquals(PasspointMatch.HomeProvider,
+                mProvider.match(anqpElementMap, mRoamingConsortium, scanResult));
+    }
+
+    /**
+     * Verify that a home provider is not matched followed by a Deauthentication-imminent WNM
+     * notification from the AP that covers the entire ESS.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void noMatchWithBlockedEss() throws Exception {
+        // Setup test provider.
+        PasspointConfiguration config = generateTestPasspointConfiguration(
+                CredentialType.USER, false);
+        mProvider = createProvider(config);
+
+        // Setup Domain Name ANQP element.
+        Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
+        anqpElementMap.put(ANQPElementType.ANQPDomName,
+                createDomainNameElement(new String[]{TEST_FQDN}));
+        anqpElementMap.put(ANQPElementType.ANQPNAIRealm,
+                createNAIRealmElement(TEST_REALM, EAPConstants.EAP_TTLS,
+                        new NonEAPInnerAuth(NonEAPInnerAuth.AUTH_TYPE_MSCHAPV2)));
+
+        ScanResult scanResult = createTestScanResult();
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(TEST_ELAPSED_TIME_SINCE_BOOT);
+
+        // Confirm this is a match under normal circumstances
+        assertEquals(PasspointMatch.HomeProvider,
+                mProvider.match(anqpElementMap, mRoamingConsortium, scanResult));
+
+        // Now block the BSS (simulate Deauth-imminent notification)
+        mProvider.blockBssOrEss(Utils.parseMac(scanResult.BSSID), true, 300 /* Seconds */);
+
+        // Confirm there is no match
+        assertEquals(PasspointMatch.None,
+                mProvider.match(anqpElementMap, mRoamingConsortium, scanResult));
+
+        // Now modify the BSSID
+        scanResult.BSSID = TEST_BSSID_STRING_2;
+
+        // Confirm there is no match
+        assertEquals(PasspointMatch.None,
+                mProvider.match(anqpElementMap, mRoamingConsortium, scanResult));
+    }
+
+    /**
+     * Verify that a home provider that was not matched followed by a Deauthentication-imminent WNM
+     * notification from the AP is matched again after the reathentication delay expires.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void blockedBssMatchRestoredAfterDelayExpires() throws Exception {
+        // Setup test provider.
+        PasspointConfiguration config = generateTestPasspointConfiguration(
+                CredentialType.USER, false);
+        mProvider = createProvider(config);
+
+        // Setup Domain Name ANQP element.
+        Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
+        anqpElementMap.put(ANQPElementType.ANQPDomName,
+                createDomainNameElement(new String[]{TEST_FQDN}));
+        anqpElementMap.put(ANQPElementType.ANQPNAIRealm,
+                createNAIRealmElement(TEST_REALM, EAPConstants.EAP_TTLS,
+                        new NonEAPInnerAuth(NonEAPInnerAuth.AUTH_TYPE_MSCHAPV2)));
+
+        ScanResult scanResult = createTestScanResult();
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(TEST_ELAPSED_TIME_SINCE_BOOT);
+
+        // Confirm this is a match under normal circumstances
+        assertEquals(PasspointMatch.HomeProvider,
+                mProvider.match(anqpElementMap, mRoamingConsortium, scanResult));
+
+        // Now block the BSS (simulate Deauth-imminent notification)
+        mProvider.blockBssOrEss(Utils.parseMac(scanResult.BSSID), false, 300 /* Seconds */);
+
+        // Confirm there is no match
+        assertEquals(PasspointMatch.None,
+                mProvider.match(anqpElementMap, mRoamingConsortium, scanResult));
+
+        // Now advance the time to some time in the future, after the delay expires
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(TEST_ELAPSED_TIME_SINCE_BOOT
+                + (300 * 1000) + 5000);
+
+        // Confirm there is a match now
+        assertEquals(PasspointMatch.HomeProvider,
+                mProvider.match(anqpElementMap, mRoamingConsortium, scanResult));
+    }
+
+
+    /**
+     * Verify that a home provider that was not matched followed by a Deauthentication-imminent WNM
+     * notification from the AP is matched again after the reathentication delay expires for WNM
+     * notifications that do not specify an explicit delay (default is 5 minutes in AOSP).
+     *
+     * @throws Exception
+     */
+    @Test
+    public void blockedBssMatchRestoredAfterDelayExpiresNoDelaySpecified() throws Exception {
+        // Setup test provider.
+        PasspointConfiguration config = generateTestPasspointConfiguration(
+                CredentialType.USER, false);
+        mProvider = createProvider(config);
+
+        // Setup Domain Name ANQP element.
+        Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
+        anqpElementMap.put(ANQPElementType.ANQPDomName,
+                createDomainNameElement(new String[]{TEST_FQDN}));
+        anqpElementMap.put(ANQPElementType.ANQPNAIRealm,
+                createNAIRealmElement(TEST_REALM, EAPConstants.EAP_TTLS,
+                        new NonEAPInnerAuth(NonEAPInnerAuth.AUTH_TYPE_MSCHAPV2)));
+
+        ScanResult scanResult = createTestScanResult();
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(TEST_ELAPSED_TIME_SINCE_BOOT);
+
+        // Confirm this is a match under normal circumstances
+        assertEquals(PasspointMatch.HomeProvider,
+                mProvider.match(anqpElementMap, mRoamingConsortium, scanResult));
+
+        // Now block the BSS (simulate Deauth-imminent notification)
+        mProvider.blockBssOrEss(Utils.parseMac(scanResult.BSSID), false, 0 /* Seconds */);
+
+        // Confirm there is no match
+        assertEquals(PasspointMatch.None,
+                mProvider.match(anqpElementMap, mRoamingConsortium, scanResult));
+
+        // Now advance the time to some time in the future, after the delay expires
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(TEST_ELAPSED_TIME_SINCE_BOOT
+                + (MboOceConstants.DEFAULT_BLOCKLIST_DURATION_MS));
+
+        // Confirm there is a match now
+        assertEquals(PasspointMatch.HomeProvider,
+                mProvider.match(anqpElementMap, mRoamingConsortium, scanResult));
+    }
+
+    /**
+     * Verify that a WNM-Notification with some invalid values is ignored and doesn't affect
+     * the provider
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testNoBlockingWithInvalidWnmData() throws Exception {
+        // Setup test provider.
+        PasspointConfiguration config = generateTestPasspointConfiguration(
+                CredentialType.USER, false);
+        mProvider = createProvider(config);
+
+        // Setup Domain Name ANQP element.
+        Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
+        anqpElementMap.put(ANQPElementType.ANQPDomName,
+                createDomainNameElement(new String[]{TEST_FQDN}));
+        anqpElementMap.put(ANQPElementType.ANQPNAIRealm,
+                createNAIRealmElement(TEST_REALM, EAPConstants.EAP_TTLS,
+                        new NonEAPInnerAuth(NonEAPInnerAuth.AUTH_TYPE_MSCHAPV2)));
+
+        ScanResult scanResult = createTestScanResult();
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(TEST_ELAPSED_TIME_SINCE_BOOT);
+
+        // Confirm this is a match under normal circumstances
+        assertEquals(PasspointMatch.HomeProvider,
+                mProvider.match(anqpElementMap, mRoamingConsortium, scanResult));
+
+        // Test some invalid values
+        mProvider.blockBssOrEss(Utils.parseMac(scanResult.BSSID), false, -40 /* Seconds */);
+        assertEquals(PasspointMatch.HomeProvider,
+                mProvider.match(anqpElementMap, mRoamingConsortium, scanResult));
+
+        mProvider.blockBssOrEss(0, false, 300 /* Seconds */);
+        assertEquals(PasspointMatch.HomeProvider,
+                mProvider.match(anqpElementMap, mRoamingConsortium, scanResult));
+    }
+
+    /**
+     * Verify set and get Anonymous Identity on passpoint provider.
+     */
+    @Test
+    public void testSetAnonymousIdentity() throws Exception {
+        PasspointConfiguration config = generateTestPasspointConfiguration(
+                CredentialType.SIM, false);
+        mProvider = createProvider(config);
+        mProvider.setAnonymousIdentity(TEST_ANONYMOUS_IDENTITY);
+        assertEquals(TEST_ANONYMOUS_IDENTITY, mProvider.getAnonymousIdentity());
+    }
+
+    /**
+     * Verify set and get connect choice on passpoint provider.
+     */
+    @Test
+    public void testSetUserConnectChoice() throws Exception {
+        PasspointConfiguration config = generateTestPasspointConfiguration(
+                CredentialType.SIM, false);
+        mProvider = createProvider(config);
+        mProvider.setUserConnectChoice(USER_CONNECT_CHOICE, TEST_RSSI);
+        assertEquals(USER_CONNECT_CHOICE, mProvider.getConnectChoice());
+        assertEquals(TEST_RSSI, mProvider.getConnectChoiceRssi());
+        WifiConfiguration configuration = mProvider.getWifiConfig();
+        assertEquals(USER_CONNECT_CHOICE,
+                configuration.getNetworkSelectionStatus().getConnectChoice());
+        assertEquals(TEST_RSSI, configuration.getNetworkSelectionStatus().getConnectChoiceRssi());
+    }
+
+
+    /**
+     * Verify that an expected decorated identity prefix is received from getWifiConfig()
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testSetDecoratedIdentityPrefix() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        // Create provider for R2.
+        PasspointConfiguration config = generateTestPasspointConfiguration(
+                CredentialType.USER, false);
+        config.getCredential().setCaCertificates(null);
+        config.setDecoratedIdentityPrefix(TEST_DECORATED_IDENTITY_PREFIX);
+        mProvider = createProvider(config);
+
+        assertEquals(TEST_DECORATED_IDENTITY_PREFIX,
+                mProvider.getWifiConfig().enterpriseConfig.getDecoratedIdentityPrefix());
     }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProvisionerTest.java b/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProvisionerTest.java
index 6423f95..c4ba619 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProvisionerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProvisionerTest.java
@@ -224,7 +224,7 @@
         when(mOsuServerConnection.validateProvider(
                 anyMap())).thenReturn(true);
         when(mOsuServerConnection.canValidateServer()).thenReturn(true);
-        mPasspointProvisioner.enableVerboseLogging(1);
+        mPasspointProvisioner.enableVerboseLogging(true);
         mOsuProvider = PasspointProvisioningTestUtil.generateOsuProvider(true);
         mDelegate = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
         mDelegate.init(PasspointProvisioningTestUtil.createFakeKeyStore());
diff --git a/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointXmlUtilsTest.java b/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointXmlUtilsTest.java
index 233a740..f6facb8 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointXmlUtilsTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointXmlUtilsTest.java
@@ -29,6 +29,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.util.FastXmlSerializer;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.WifiBaseTest;
 
 import org.junit.Test;
@@ -53,6 +54,10 @@
 @SmallTest
 public class PasspointXmlUtilsTest extends WifiBaseTest {
 
+    private static final int TEST_CARRIER_ID = 129;
+    private static final int TEST_SUBSCRIPTION_ID = 1;
+    private static final String TEST_DECORATED_IDENTITY_PREFIX = "androidwifi.dev!";
+
     /**
      * Helper function for generating a {@link PasspointConfiguration} for testing the XML
      * serialization/deserialization logic.
@@ -172,6 +177,18 @@
         policyUpdate.setTrustRootCertSha256Fingerprint(certFingerprint);
         policy.setPolicyUpdate(policyUpdate);
         config.setPolicy(policy);
+
+        //Set suggestion related flag
+        config.setCarrierMerged(true);
+        config.setOemPaid(true);
+        config.setOemPrivate(true);
+        config.setCarrierId(TEST_CARRIER_ID);
+        config.setSubscriptionId(TEST_SUBSCRIPTION_ID);
+
+        // Extensions
+        if (SdkLevel.isAtLeastS()) {
+            config.setDecoratedIdentityPrefix(TEST_DECORATED_IDENTITY_PREFIX);
+        }
         return config;
     }
 
diff --git a/service/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/HSWanMetricsElementTest.java b/service/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/HSWanMetricsElementTest.java
index e4b65f3..50a928b 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/HSWanMetricsElementTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/HSWanMetricsElementTest.java
@@ -17,6 +17,8 @@
 package com.android.server.wifi.hotspot2.anqp;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import androidx.test.filters.SmallTest;
 
@@ -68,6 +70,15 @@
         return buffer;
     }
 
+    private ByteBuffer getUninitializedBuffer() {
+        // Setup an uninitialized WAN Metrics element (or initialized with 0's)
+        ByteBuffer buffer = ByteBuffer.allocate(HSWanMetricsElement.EXPECTED_BUFFER_SIZE)
+                .order(ByteOrder.LITTLE_ENDIAN);
+        buffer.put(new byte[HSWanMetricsElement.EXPECTED_BUFFER_SIZE]);
+        buffer.position(0);
+        return buffer;
+    }
+
     @Test
     public void testGetStatus() {
         assertEquals(TEST_LINK_STATUS, TEST_ELEMENT.getStatus());
@@ -79,8 +90,8 @@
     }
 
     @Test
-    public void testIsCapped() {
-        assertEquals(TEST_AT_CAPACITY, TEST_ELEMENT.isCapped());
+    public void testIsAtCapacity() {
+        assertEquals(TEST_AT_CAPACITY, TEST_ELEMENT.isAtCapacity());
     }
 
     @Test
@@ -160,5 +171,18 @@
                 TEST_DOWNLINK_SPEED, TEST_UPLINK_SPEED, TEST_DOWNLINK_LOAD,
                 TEST_UPLINK_LOAD, TEST_LMD);
         assertEquals(expectedElement, HSWanMetricsElement.parse(buffer));
+        assertTrue(expectedElement.isElementInitialized());
+    }
+
+    /**
+     * Verify that an element with all 0's (uninitialized) is detected as uninitialized.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testIsInitialized() throws Exception {
+        ByteBuffer buffer = getUninitializedBuffer();
+        HSWanMetricsElement parsedElement = HSWanMetricsElement.parse(buffer);
+        assertFalse(parsedElement.isElementInitialized());
     }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/VenueUrlElementTest.java b/service/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/VenueUrlElementTest.java
new file mode 100644
index 0000000..d96560b
--- /dev/null
+++ b/service/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/VenueUrlElementTest.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.hotspot2.anqp;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.wifi.WifiBaseTest;
+
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.ProtocolException;
+import java.net.URL;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link VenueUrlElement}.
+ */
+@SmallTest
+public class VenueUrlElementTest extends WifiBaseTest {
+    private static final String TEST_VENUE_URL1 = "https://www.google.com/";
+    private static final String TEST_VENUE_URL2 = "https://www.android.com/";
+    private static final String TEST_VENUE_URL3 = "https://support.google.com/";
+    private static final String TEST_VENUE_URL_INSECURE = "http://support.google.com/";
+    private static final String TEST_VENUE_URL_CAPS = "HTTPS://SUPPORT.GOOGLE.COM/";
+    private static final String TEST_VENUE_URL_INSECURE_CAPS = "HTTP://SUPPORT.GOOGLE.COM/";
+    private static final String TEST_VENUE_URL_INVALID = "htps://invalid.com/";
+
+    /**
+     * Helper function for appending a Venue URL to an output stream.
+     *
+     * @param stream      Stream to write to
+     * @param venueNumber Venue number
+     * @param url         The URL string
+     */
+    private void appendVenue(ByteArrayOutputStream stream, int venueNumber, String url)
+            throws IOException {
+        byte[] venueBytes = url.getBytes(StandardCharsets.UTF_8);
+        int length = venueBytes.length + 1;
+        stream.write((byte) length);
+        stream.write((byte) venueNumber);
+        stream.write(venueBytes);
+    }
+
+    /**
+     * Helper function for generating test data.
+     *
+     * @return byte[] of data
+     */
+    private byte[] getTestData(Map<Integer, URL> urls) throws IOException {
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+
+        for (Map.Entry<Integer, URL> entry : urls.entrySet()) {
+            appendVenue(stream, entry.getKey(), entry.getValue().toString());
+        }
+        return stream.toByteArray();
+    }
+
+    /**
+     * Helper function for generating test data.
+     *
+     * @return byte[] of data
+     */
+    private byte[] getTestData(int venueNumber, String url) throws IOException {
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        appendVenue(stream, venueNumber, url);
+        return stream.toByteArray();
+    }
+
+    /**
+     * Verify that an empty URL list will be returned when parsing an empty buffer.
+     */
+    @Test
+    public void parseEmptyBuffer() throws Exception {
+        assertTrue(VenueUrlElement.parse(ByteBuffer.allocate(0)).getVenueUrls().isEmpty());
+    }
+
+    /**
+     * Verify that BufferUnderflowException will be thrown when parsing a truncated buffer
+     * (missing a byte at the end).
+     */
+    @Test(expected = BufferUnderflowException.class)
+    public void parseTruncatedBuffer() throws Exception {
+        Map<Integer, URL> urlList = new HashMap<>();
+        urlList.put(Integer.valueOf(1), createUrlFromString(TEST_VENUE_URL1));
+        ByteBuffer buffer = ByteBuffer.wrap(getTestData(urlList));
+        // Truncate a byte at the end.
+        buffer.limit(buffer.remaining() - 1);
+        VenueUrlElement.parse(buffer);
+    }
+
+    /**
+     * Verify that ProtocolException is thrown when parsing a buffer with an empty venue URL.
+     */
+    @Test (expected = ProtocolException.class)
+    public void parseBufferWithEmptyVenueUrl() throws Exception {
+        ByteBuffer buffer = ByteBuffer.wrap(getTestData(1, new String()));
+        VenueUrlElement.parse(buffer);
+    }
+
+    /**
+     * Verify that ProtocolException is thrown when parsing a buffer with an invalid venue URL.
+     */
+    @Test (expected = ProtocolException.class)
+    public void parseBufferWithInvalidVenueUrl() throws Exception {
+        ByteBuffer buffer = ByteBuffer.wrap(getTestData(1, TEST_VENUE_URL_INVALID));
+        VenueUrlElement.parse(buffer);
+    }
+
+    /**
+     * Verify that BufferUnderflowException is thrown when parsing a buffer with an invalid length.
+     */
+    @Test (expected = BufferUnderflowException.class)
+    public void parseBufferWithInvalidLength() throws Exception {
+        // Craft a payload with an invalid length that should cause underflow when parsing
+        String url = TEST_VENUE_URL1;
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        byte[] venueBytes = url.getBytes(StandardCharsets.UTF_8);
+        int length = venueBytes.length + 3; // One extra byte
+        stream.write((byte) length);
+        stream.write((byte) 1 /* venueNumber */);
+        stream.write(venueBytes);
+        VenueUrlElement.parse(ByteBuffer.wrap(stream.toByteArray()));
+    }
+
+    /**
+     * Verify that a VenueUrlElement with empty URL list will be returned when parsing a buffer
+     * contained venue number equals to 0 and no venue URL (only contained the venue URL data).
+     */
+    @Test
+    public void parseBufferWithZeroVenueNumber() throws Exception {
+        ByteBuffer buffer = ByteBuffer.wrap(getTestData(0, new String()));
+        assertTrue(VenueUrlElement.parse(buffer).getVenueUrls().isEmpty());
+    }
+
+    /**
+     * Verify that a VenueUrlElement with empty URL list will be returned when parsing a buffer
+     * contained venue number equals to 0 and some venue URL (only contained the venue URL data).
+     */
+    @Test
+    public void parseBufferWithZeroVenueNumberAndUrlData() throws Exception {
+        ByteBuffer buffer = ByteBuffer.wrap(getTestData(0, TEST_VENUE_URL1));
+        assertTrue(VenueUrlElement.parse(buffer).getVenueUrls().isEmpty());
+    }
+
+    /**
+     * Verify that a VenueUrlElement with a single URL will be returned when parsing a buffer
+     * contained a negative venue number and some venue URL. The negative number will be parsed
+     * as a positive number (two's complement).
+     */
+    @Test
+    public void parseBufferWithNegativeVenueNumberAndUrlData() throws Exception {
+        // Setup expected element.
+        Map<Integer, URL> urlList = new HashMap<>();
+        urlList.put(Integer.valueOf(249), createUrlFromString(TEST_VENUE_URL1));
+        VenueUrlElement expectedElement = new VenueUrlElement(urlList);
+
+        ByteBuffer buffer = ByteBuffer.wrap(getTestData(-7, TEST_VENUE_URL1));
+        assertEquals(expectedElement, VenueUrlElement.parse(buffer));
+    }
+
+    private URL createUrlFromString(String stringUrl) {
+        URL url;
+        try {
+            url = new URL(stringUrl);
+        } catch (java.net.MalformedURLException e) {
+            return null;
+        }
+        return url;
+    }
+
+    /**
+     * Verify that an expected VenueUrlElement will be returned when parsing a buffer contained
+     * valid Venue URL data.
+     */
+    @Test
+    public void parseBufferWithValidVenueUrls() throws Exception {
+        // Setup expected element.
+        Map<Integer, URL> urlList = new HashMap<>();
+        urlList.put(Integer.valueOf(1), createUrlFromString(TEST_VENUE_URL1));
+        urlList.put(Integer.valueOf(2), createUrlFromString(TEST_VENUE_URL2));
+        urlList.put(Integer.valueOf(4), createUrlFromString(TEST_VENUE_URL3));
+        VenueUrlElement expectedElement = new VenueUrlElement(urlList);
+
+        ByteBuffer buffer = ByteBuffer.wrap(getTestData(urlList));
+        assertEquals(expectedElement, VenueUrlElement.parse(buffer));
+    }
+
+    /**
+     * Verify that an expected VenueUrlElement will be returned when parsing a buffer contained
+     * valid Venue URL data.
+     */
+    @Test
+    public void parseBufferWithValidVenueUrlsAndCapsUrls() throws Exception {
+        // Setup expected element.
+        Map<Integer, URL> urlList = new HashMap<>();
+        urlList.put(Integer.valueOf(1), createUrlFromString(TEST_VENUE_URL1));
+        urlList.put(Integer.valueOf(2), createUrlFromString(TEST_VENUE_URL2));
+        urlList.put(Integer.valueOf(3), createUrlFromString(TEST_VENUE_URL_CAPS));
+        urlList.put(Integer.valueOf(4), createUrlFromString(TEST_VENUE_URL3));
+        VenueUrlElement expectedElement = new VenueUrlElement(urlList);
+
+        ByteBuffer buffer = ByteBuffer.wrap(getTestData(urlList));
+        assertEquals(expectedElement, VenueUrlElement.parse(buffer));
+    }
+
+    /**
+     * Verify that a VenueUrlElement with empty URL list will be returned when parsing a buffer
+     * with an insecure (Non-HTTPS) URL.
+     */
+    @Test
+    public void parseBufferWithInsecureUrlData() throws Exception {
+        ByteBuffer buffer = ByteBuffer.wrap(getTestData(1, TEST_VENUE_URL_INSECURE));
+        assertTrue(VenueUrlElement.parse(buffer).getVenueUrls().isEmpty());
+    }
+
+    /**
+     * Verify that a VenueUrlElement with empty URL list will be returned when parsing a buffer
+     * with an insecure (Non-HTTPS) URL (letters capitalized).
+     */
+    @Test
+    public void parseBufferWithInsecureCapsUrlData() throws Exception {
+        ByteBuffer buffer = ByteBuffer.wrap(getTestData(1, TEST_VENUE_URL_INSECURE_CAPS));
+        assertTrue(VenueUrlElement.parse(buffer).getVenueUrls().isEmpty());
+    }
+
+    /**
+     * Verify that an expected VenueUrlElement will be returned when parsing a buffer contained
+     * valid Venue URL data and a single insecure URL which will be dropped.
+     */
+    @Test
+    public void parseBufferWithValidVenueUrlsAndOneInsecureUrl() throws Exception {
+        // Setup expected element.
+        Map<Integer, URL> urlList = new HashMap<>();
+        urlList.put(Integer.valueOf(1), createUrlFromString(TEST_VENUE_URL1));
+        urlList.put(Integer.valueOf(2), createUrlFromString(TEST_VENUE_URL2));
+        urlList.put(Integer.valueOf(3), createUrlFromString(TEST_VENUE_URL_INSECURE));
+        urlList.put(Integer.valueOf(4), createUrlFromString(TEST_VENUE_URL3));
+
+        // Create a buffer with a single insecure URL
+        ByteBuffer buffer = ByteBuffer.wrap(getTestData(urlList));
+
+        // Create the expected result
+        urlList.remove(Integer.valueOf(3));
+        VenueUrlElement expectedElement = new VenueUrlElement(urlList);
+
+        assertEquals(expectedElement, VenueUrlElement.parse(buffer));
+    }
+}
diff --git a/service/tests/wifitests/src/com/android/server/wifi/p2p/MockWifiP2pMonitor.java b/service/tests/wifitests/src/com/android/server/wifi/p2p/MockWifiP2pMonitor.java
index d36db2d..6f74c20 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/p2p/MockWifiP2pMonitor.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/p2p/MockWifiP2pMonitor.java
@@ -17,14 +17,11 @@
 package com.android.server.wifi.p2p;
 
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
 
 import android.os.Handler;
 import android.os.Message;
 import android.util.SparseArray;
 
-import com.android.server.wifi.WifiInjector;
-
 import java.util.HashMap;
 import java.util.Map;
 
@@ -36,10 +33,6 @@
 public class MockWifiP2pMonitor extends  WifiP2pMonitor {
     private final Map<String, SparseArray<Handler>> mHandlerMap = new HashMap<>();
 
-    public MockWifiP2pMonitor() {
-        super(mock(WifiInjector.class));
-    }
-
     @Override
     public void registerHandler(String iface, int what, Handler handler) {
         SparseArray<Handler> ifaceHandlers = mHandlerMap.get(iface);
@@ -88,5 +81,4 @@
         }
         return false;
     }
-
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/p2p/SupplicantP2pIfaceCallbackTest.java b/service/tests/wifitests/src/com/android/server/wifi/p2p/SupplicantP2pIfaceCallbackImplTest.java
similarity index 93%
rename from service/tests/wifitests/src/com/android/server/wifi/p2p/SupplicantP2pIfaceCallbackTest.java
rename to service/tests/wifitests/src/com/android/server/wifi/p2p/SupplicantP2pIfaceCallbackImplTest.java
index cd20f47..1a43d3a 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/p2p/SupplicantP2pIfaceCallbackTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/p2p/SupplicantP2pIfaceCallbackImplTest.java
@@ -15,8 +15,12 @@
  */
 package com.android.server.wifi.p2p;
 
-import static org.junit.Assert.*;
-import static org.mockito.Matchers.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -43,6 +47,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
@@ -53,13 +58,14 @@
  * Unit tests for SupplicantP2pIfaceCallback
  */
 @SmallTest
-public class SupplicantP2pIfaceCallbackTest extends WifiBaseTest {
+public class SupplicantP2pIfaceCallbackImplTest extends WifiBaseTest {
     private static final String TAG = "SupplicantP2pIfaceCallbackTest";
 
     private String mIface = "test_p2p0";
     private String mGroupIface = "test_p2p-p2p0-3";
     private WifiP2pMonitor mMonitor;
-    private SupplicantP2pIfaceCallback mDut;
+    private SupplicantP2pIfaceCallbackImpl mDut;
+    @Mock private SupplicantP2pIfaceHal mP2pIfaceHal;
 
     private byte[] mDeviceAddressInvalid1 = { 0x00 };
     private byte[] mDeviceAddressInvalid2 = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66 };
@@ -72,9 +78,10 @@
     private static final int TEST_NETWORK_ID = 9;
     private static final int TEST_GROUP_FREQUENCY = 5400;
 
-    private class SupplicantP2pIfaceCallbackSpy extends SupplicantP2pIfaceCallback {
-        SupplicantP2pIfaceCallbackSpy(String iface, WifiP2pMonitor monitor) {
-            super(iface, monitor);
+    private class SupplicantP2pIfaceCallbackImplSpy extends SupplicantP2pIfaceCallbackImpl {
+        SupplicantP2pIfaceCallbackImplSpy(
+                SupplicantP2pIfaceHal ifaceHal, String iface, WifiP2pMonitor monitor) {
+            super(ifaceHal, iface, monitor);
         }
     }
 
@@ -82,7 +89,7 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mMonitor = mock(WifiP2pMonitor.class);
-        mDut = new SupplicantP2pIfaceCallbackSpy(mIface, mMonitor);
+        mDut = new SupplicantP2pIfaceCallbackImplSpy(mP2pIfaceHal, mIface, mMonitor);
     }
 
     /**
@@ -110,8 +117,7 @@
                 assertEquals(device.deviceAddress, mDeviceAddress2String);
                 assertEquals(device.status, WifiP2pDevice.AVAILABLE);
             }
-        })
-        .when(mMonitor).broadcastP2pDeviceFound(
+        }).when(mMonitor).broadcastP2pDeviceFound(
                 anyString(), any(WifiP2pDevice.class));
 
         mDut.onDeviceFound(
@@ -250,8 +256,7 @@
                 assertEquals(device.deviceAddress, mDeviceAddress1String);
                 assertEquals(device.status, WifiP2pDevice.UNAVAILABLE);
             }
-        })
-        .when(mMonitor).broadcastP2pDeviceLost(
+        }).when(mMonitor).broadcastP2pDeviceLost(
                 anyString(), any(WifiP2pDevice.class));
 
         mDut.onDeviceLost(mDeviceAddress1Bytes);
@@ -293,25 +298,24 @@
                 setups.add(config.wps.setup);
                 assertEquals(config.deviceAddress, mDeviceAddress1String);
             }
-        })
-        .when(mMonitor).broadcastP2pGoNegotiationRequest(
+        }).when(mMonitor).broadcastP2pGoNegotiationRequest(
                 anyString(), any(WifiP2pConfig.class));
 
         mDut.onGoNegotiationRequest(mDeviceAddress1Bytes,
-                (short)ISupplicantP2pIfaceCallback.WpsDevPasswordId.USER_SPECIFIED);
+                (short) ISupplicantP2pIfaceCallback.WpsDevPasswordId.USER_SPECIFIED);
         assertTrue(setups.contains(WpsInfo.DISPLAY));
 
         mDut.onGoNegotiationRequest(mDeviceAddress1Bytes,
-                (short)ISupplicantP2pIfaceCallback.WpsDevPasswordId.PUSHBUTTON);
+                (short) ISupplicantP2pIfaceCallback.WpsDevPasswordId.PUSHBUTTON);
         assertTrue(setups.contains(WpsInfo.PBC));
 
         mDut.onGoNegotiationRequest(mDeviceAddress1Bytes,
-                (short)ISupplicantP2pIfaceCallback.WpsDevPasswordId.REGISTRAR_SPECIFIED);
+                (short) ISupplicantP2pIfaceCallback.WpsDevPasswordId.REGISTRAR_SPECIFIED);
         assertTrue(setups.contains(WpsInfo.KEYPAD));
 
         // Invalid should default to PBC
         setups.clear();
-        mDut.onGoNegotiationRequest(mDeviceAddress1Bytes, (short)0xffff);
+        mDut.onGoNegotiationRequest(mDeviceAddress1Bytes, (short) 0xffff);
         assertTrue(setups.contains(WpsInfo.PBC));
     }
 
@@ -320,15 +324,15 @@
      */
     @Test
     public void testOnGoNegotiationRequest_invalidArguments() throws Exception {
-        mDut.onGoNegotiationRequest(null, (short)0);
+        mDut.onGoNegotiationRequest(null, (short) 0);
         verify(mMonitor, never()).broadcastP2pDeviceLost(
                 anyString(), any(WifiP2pDevice.class));
 
-        mDut.onGoNegotiationRequest(mDeviceAddressInvalid1, (short)0);
+        mDut.onGoNegotiationRequest(mDeviceAddressInvalid1, (short) 0);
         verify(mMonitor, never()).broadcastP2pDeviceLost(
                 anyString(), any(WifiP2pDevice.class));
 
-        mDut.onGoNegotiationRequest(mDeviceAddressInvalid2, (short)0);
+        mDut.onGoNegotiationRequest(mDeviceAddressInvalid2, (short) 0);
         verify(mMonitor, never()).broadcastP2pDeviceLost(
                 anyString(), any(WifiP2pDevice.class));
     }
@@ -341,11 +345,11 @@
         String fakeName = "group name";
         String fakePassphrase = "secret";
         ArrayList<Byte> fakeSsidBytesList = new ArrayList<Byte>() {{
-            add((byte)0x30);
-            add((byte)0x31);
-            add((byte)0x32);
-            add((byte)0x33);
-        }};
+                add((byte) 0x30);
+                add((byte) 0x31);
+                add((byte) 0x32);
+                add((byte) 0x33);
+            }};
         String fakeSsidString = "0123";
         HashSet<String> passwords = new HashSet<String>();
 
@@ -359,8 +363,7 @@
                 assertEquals(group.getInterface(), fakeName);
                 assertEquals(group.getNetworkName(), fakeSsidString);
             }
-        })
-        .when(mMonitor).broadcastP2pGroupStarted(
+        }).when(mMonitor).broadcastP2pGroupStarted(
                 anyString(), any(WifiP2pGroup.class));
 
         mDut.onGroupStarted(
@@ -385,11 +388,11 @@
         String fakeName = "group name";
         String fakePassphrase = "secret";
         ArrayList<Byte> fakeSsidBytesList = new ArrayList<Byte>() {{
-            add((byte)0x30);
-            add((byte)0x31);
-            add((byte)0x32);
-            add((byte)0x33);
-        }};
+                add((byte) 0x30);
+                add((byte) 0x31);
+                add((byte) 0x32);
+                add((byte) 0x33);
+            }};
         String fakeSsidString = "0123";
 
         mDut.onGroupStarted(
diff --git a/service/tests/wifitests/src/com/android/server/wifi/p2p/SupplicantP2pIfaceHalTest.java b/service/tests/wifitests/src/com/android/server/wifi/p2p/SupplicantP2pIfaceHalTest.java
index b4943d7..2b7ef17 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/p2p/SupplicantP2pIfaceHalTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/p2p/SupplicantP2pIfaceHalTest.java
@@ -38,6 +38,7 @@
 import android.hardware.wifi.supplicant.V1_0.SupplicantStatusCode;
 import android.hidl.manager.V1_0.IServiceManager;
 import android.hidl.manager.V1_0.IServiceNotification;
+import android.net.wifi.CoexUnsafeChannel;
 import android.net.wifi.WpsInfo;
 import android.net.wifi.p2p.WifiP2pConfig;
 import android.net.wifi.p2p.WifiP2pDevice;
@@ -66,6 +67,7 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -701,7 +703,7 @@
                 eq(mPeerMacAddressBytes), anyInt(), anyString(), anyBoolean(), anyBoolean(),
                 anyInt(), any(ISupplicantP2pIface.connectCallback.class));
 
-        WifiP2pConfig config = createDummyP2pConfig(mPeerMacAddress, WpsInfo.DISPLAY, "");
+        WifiP2pConfig config = createPlaceholderP2pConfig(mPeerMacAddress, WpsInfo.DISPLAY, "");
 
         // Default value when service is not initialized.
         assertNull(mDut.connect(config, false));
@@ -712,17 +714,17 @@
         assertTrue(methods.contains(ISupplicantP2pIface.WpsProvisionMethod.DISPLAY));
         methods.clear();
 
-        config = createDummyP2pConfig(mPeerMacAddress, WpsInfo.DISPLAY, configPin);
+        config = createPlaceholderP2pConfig(mPeerMacAddress, WpsInfo.DISPLAY, configPin);
         assertTrue(mDut.connect(config, false).isEmpty());
         assertTrue(methods.contains(ISupplicantP2pIface.WpsProvisionMethod.DISPLAY));
         methods.clear();
 
-        config = createDummyP2pConfig(mPeerMacAddress, WpsInfo.PBC, "");
+        config = createPlaceholderP2pConfig(mPeerMacAddress, WpsInfo.PBC, "");
         assertTrue(mDut.connect(config, false).isEmpty());
         assertTrue(methods.contains(ISupplicantP2pIface.WpsProvisionMethod.PBC));
         methods.clear();
 
-        config = createDummyP2pConfig(mPeerMacAddress, WpsInfo.KEYPAD, configPin);
+        config = createPlaceholderP2pConfig(mPeerMacAddress, WpsInfo.KEYPAD, configPin);
         assertTrue(mDut.connect(config, false).isEmpty());
         assertTrue(methods.contains(ISupplicantP2pIface.WpsProvisionMethod.KEYPAD));
     }
@@ -744,7 +746,7 @@
                 any(byte[].class), anyInt(), anyString(), anyBoolean(), anyBoolean(),
                 anyInt(), any(ISupplicantP2pIface.connectCallback.class));
 
-        WifiP2pConfig config = createDummyP2pConfig(mPeerMacAddress, WpsInfo.DISPLAY, "");
+        WifiP2pConfig config = createPlaceholderP2pConfig(mPeerMacAddress, WpsInfo.DISPLAY, "");
 
         // unsupported.
         config.wps.setup = -1;
@@ -774,7 +776,8 @@
     @Test
     public void testConnect_failure() throws Exception {
         final String configPin = "12345";
-        WifiP2pConfig config = createDummyP2pConfig(mPeerMacAddress, WpsInfo.DISPLAY, configPin);
+        WifiP2pConfig config = createPlaceholderP2pConfig(mPeerMacAddress,
+                WpsInfo.DISPLAY, configPin);
 
         executeAndValidateInitializationSequence(false, false, false);
         doAnswer(new AnswerWithArguments() {
@@ -799,7 +802,8 @@
     @Test
     public void testConnect_exception() throws Exception {
         final String configPin = "12345";
-        WifiP2pConfig config = createDummyP2pConfig(mPeerMacAddress, WpsInfo.DISPLAY, configPin);
+        WifiP2pConfig config = createPlaceholderP2pConfig(mPeerMacAddress,
+                WpsInfo.DISPLAY, configPin);
 
         doThrow(mRemoteException)
         .when(mISupplicantP2pIfaceMock).connect(
@@ -858,7 +862,7 @@
      */
     @Test
     public void testProvisionDiscovery_success() throws Exception {
-        WifiP2pConfig config = createDummyP2pConfig(mPeerMacAddress, WpsInfo.PBC, "");
+        WifiP2pConfig config = createPlaceholderP2pConfig(mPeerMacAddress, WpsInfo.PBC, "");
 
         when(mISupplicantP2pIfaceMock.provisionDiscovery(
                 eq(mPeerMacAddressBytes), anyInt()))
@@ -879,7 +883,7 @@
                 .thenReturn(mStatusSuccess);
         executeAndValidateInitializationSequence(false, false, false);
 
-        WifiP2pConfig config = createDummyP2pConfig(mPeerMacAddress, WpsInfo.PBC, "");
+        WifiP2pConfig config = createPlaceholderP2pConfig(mPeerMacAddress, WpsInfo.PBC, "");
 
         // Unsupported method.
         config.wps.setup = -1;
@@ -897,7 +901,7 @@
      */
     @Test
     public void testProvisionDiscovery_failure() throws Exception {
-        WifiP2pConfig config = createDummyP2pConfig(mPeerMacAddress, WpsInfo.PBC, "");
+        WifiP2pConfig config = createPlaceholderP2pConfig(mPeerMacAddress, WpsInfo.PBC, "");
 
         executeAndValidateInitializationSequence(false, false, false);
         when(mISupplicantP2pIfaceMock.provisionDiscovery(
@@ -913,7 +917,7 @@
      */
     @Test
     public void testProvisionDiscovery_exception() throws Exception {
-        WifiP2pConfig config = createDummyP2pConfig(mPeerMacAddress, WpsInfo.PBC, "");
+        WifiP2pConfig config = createPlaceholderP2pConfig(mPeerMacAddress, WpsInfo.PBC, "");
 
         executeAndValidateInitializationSequence(false, false, false);
         when(mISupplicantP2pIfaceMock.provisionDiscovery(
@@ -930,7 +934,7 @@
      */
     @Test
     public void testInvite_success() throws Exception {
-        WifiP2pGroup group = createDummyP2pGroup();
+        WifiP2pGroup group = createPlaceholderP2pGroup();
 
         when(mISupplicantP2pIfaceMock.invite(
                 eq(mIfaceName), eq(mGroupOwnerMacAddressBytes), eq(mPeerMacAddressBytes)))
@@ -946,7 +950,7 @@
      */
     @Test
     public void testInvite_invalidArguments() throws Exception {
-        WifiP2pGroup group = createDummyP2pGroup();
+        WifiP2pGroup group = createPlaceholderP2pGroup();
 
         executeAndValidateInitializationSequence(false, false, false);
         when(mISupplicantP2pIfaceMock.invite(
@@ -972,7 +976,7 @@
      */
     @Test
     public void testInvite_failure() throws Exception {
-        WifiP2pGroup group = createDummyP2pGroup();
+        WifiP2pGroup group = createPlaceholderP2pGroup();
 
         executeAndValidateInitializationSequence(false, false, false);
         when(mISupplicantP2pIfaceMock.invite(
@@ -988,7 +992,7 @@
      */
     @Test
     public void testInvite_exception() throws Exception {
-        WifiP2pGroup group = createDummyP2pGroup();
+        WifiP2pGroup group = createPlaceholderP2pGroup();
 
         executeAndValidateInitializationSequence(false, false, false);
         when(mISupplicantP2pIfaceMock.invite(
@@ -1607,45 +1611,14 @@
      */
     @Test
     public void testSetListenChannel_success() throws Exception {
-        int lc = 4;
-        int oc = 163;
-        ISupplicantP2pIface.FreqRange range1 = new ISupplicantP2pIface.FreqRange();
-        range1.min = 1000;
-        range1.max = 5810;
-        ISupplicantP2pIface.FreqRange range2 = new ISupplicantP2pIface.FreqRange();
-        range2.min = 5820;
-        range2.max = 6000;
-        ArrayList<ISupplicantP2pIface.FreqRange> ranges = new ArrayList<>();
-        ranges.add(range1);
-        ranges.add(range2);
+        int lc = 6;
 
-        when(mISupplicantP2pIfaceMock.setListenChannel(eq(lc),  anyInt()))
-                .thenReturn(mStatusSuccess);
-        when(mISupplicantP2pIfaceMock.setDisallowedFrequencies(eq(ranges)))
+        when(mISupplicantP2pIfaceMock.setListenChannel(eq(lc), anyInt()))
                 .thenReturn(mStatusSuccess);
         // Default value when service is not initialized.
-        assertFalse(mDut.setListenChannel(lc, oc));
+        assertFalse(mDut.setListenChannel(lc));
         executeAndValidateInitializationSequence(false, false, false);
-        assertTrue(mDut.setListenChannel(lc, oc));
-    }
-
-    /**
-     * Sunny day scenario for setListenChannel()
-     */
-    @Test
-    public void testSetListenChannel_successResetDisallowedFreq() throws Exception {
-        int lc = 2;
-        int oc = 0;
-        ArrayList<ISupplicantP2pIface.FreqRange> ranges = new ArrayList<>();
-
-        when(mISupplicantP2pIfaceMock.setListenChannel(eq(lc),  anyInt()))
-                .thenReturn(mStatusSuccess);
-        when(mISupplicantP2pIfaceMock.setDisallowedFrequencies(eq(ranges)))
-                .thenReturn(mStatusSuccess);
-        // Default value when service is not initialized.
-        assertFalse(mDut.setListenChannel(lc, oc));
-        executeAndValidateInitializationSequence(false, false, false);
-        assertTrue(mDut.setListenChannel(lc, oc));
+        assertTrue(mDut.setListenChannel(lc));
     }
 
     /**
@@ -1656,10 +1629,7 @@
         executeAndValidateInitializationSequence(false, false, false);
         when(mISupplicantP2pIfaceMock.setListenChannel(anyInt(), anyInt()))
                 .thenReturn(mStatusSuccess);
-        when(mISupplicantP2pIfaceMock.setDisallowedFrequencies(any(ArrayList.class)))
-                .thenReturn(mStatusSuccess);
-        assertFalse(mDut.setListenChannel(-1, 1));
-        assertFalse(mDut.setListenChannel(1, -1));
+        assertFalse(mDut.setListenChannel(4));
     }
 
     /**
@@ -1670,9 +1640,7 @@
         executeAndValidateInitializationSequence(false, false, false);
         when(mISupplicantP2pIfaceMock.setListenChannel(anyInt(), anyInt()))
                 .thenReturn(mStatusFailure);
-        when(mISupplicantP2pIfaceMock.setDisallowedFrequencies(any(ArrayList.class)))
-                .thenReturn(mStatusSuccess);
-        assertFalse(mDut.setListenChannel(1, 1));
+        assertFalse(mDut.setListenChannel(1));
         // Check that service is still alive.
         assertTrue(mDut.isInitializationComplete());
     }
@@ -1685,18 +1653,81 @@
         executeAndValidateInitializationSequence(false, false, false);
         when(mISupplicantP2pIfaceMock.setListenChannel(anyInt(), anyInt()))
                 .thenThrow(mRemoteException);
-        assertFalse(mDut.setListenChannel(1, 1));
+        assertFalse(mDut.setListenChannel(1));
         // Check service is dead.
         assertFalse(mDut.isInitializationComplete());
     }
 
+    /**
+     * Sunny day scenario for setOperatingChannel()
+     */
+    @Test
+    public void testSetOperatingChannel_success() throws Exception {
+        int oc = 163;
+        ISupplicantP2pIface.FreqRange range1 = new ISupplicantP2pIface.FreqRange();
+        range1.min = 1000;
+        range1.max = 5810;
+        ISupplicantP2pIface.FreqRange range2 = new ISupplicantP2pIface.FreqRange();
+        range2.min = 5820;
+        range2.max = 6000;
+        ArrayList<ISupplicantP2pIface.FreqRange> ranges = new ArrayList<>();
+        ranges.add(range1);
+        ranges.add(range2);
+        List<CoexUnsafeChannel> unsafeChannels = new ArrayList<>();
+
+        when(mISupplicantP2pIfaceMock.setDisallowedFrequencies(eq(ranges)))
+                .thenReturn(mStatusSuccess);
+        // Default value when service is not initialized.
+        assertFalse(mDut.setOperatingChannel(oc, unsafeChannels));
+        executeAndValidateInitializationSequence(false, false, false);
+        assertTrue(mDut.setOperatingChannel(oc, unsafeChannels));
+    }
+
+    /**
+     * Test setOperatingChannel with invalid parameters.
+     */
+    @Test
+    public void testSetOperatingChannel_invalidArguments() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.setDisallowedFrequencies(any(ArrayList.class)))
+                .thenReturn(mStatusSuccess);
+        assertFalse(mDut.setOperatingChannel(1, null));
+    }
+
+    /**
+     * Verify that setOperatingChannel returns false, if status is not SUCCESS.
+     */
+    @Test
+    public void testSetOperatingChannel_failure() throws Exception {
+        List<CoexUnsafeChannel> unsafeChannels = new ArrayList<>();
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.setDisallowedFrequencies(any(ArrayList.class)))
+                .thenReturn(mStatusFailure);
+        assertFalse(mDut.setOperatingChannel(1, unsafeChannels));
+        // Check that service is still alive.
+        assertTrue(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify that setOperatingChannel disconnects and returns false, if HAL throws exception.
+     */
+    @Test
+    public void testSetOperatingChannel_exception() throws Exception {
+        List<CoexUnsafeChannel> unsafeChannels = new ArrayList<>();
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.setDisallowedFrequencies(any(ArrayList.class)))
+                .thenThrow(mRemoteException);
+        assertFalse(mDut.setOperatingChannel(65, unsafeChannels));
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+    }
 
     /**
      * Sunny day scenario for serviceAdd()
      */
     @Test
     public void testServiceAdd_success() throws Exception {
-        WifiP2pServiceInfo info = createDummyP2pServiceInfo(
+        WifiP2pServiceInfo info = createPlaceholderP2pServiceInfo(
                 mValidUpnpService, mValidBonjourService);
         final HashSet<String> services = new HashSet<String>();
 
@@ -1730,7 +1761,7 @@
         assertTrue(services.contains("bonjour"));
 
         // Empty services should cause no trouble.
-        assertTrue(mDut.serviceAdd(createDummyP2pServiceInfo()));
+        assertTrue(mDut.serviceAdd(createPlaceholderP2pServiceInfo()));
     }
 
     /**
@@ -1747,15 +1778,15 @@
                 .thenReturn(mStatusSuccess);
 
         assertFalse(mDut.serviceAdd(null));
-        assertFalse(mDut.serviceAdd(createDummyP2pServiceInfo(mInvalidService1)));
-        assertFalse(mDut.serviceAdd(createDummyP2pServiceInfo(mInvalidService2)));
-        assertFalse(mDut.serviceAdd(createDummyP2pServiceInfo(mInvalidUpnpService1)));
-        assertFalse(mDut.serviceAdd(createDummyP2pServiceInfo(mInvalidUpnpService2)));
-        assertFalse(mDut.serviceAdd(createDummyP2pServiceInfo(mInvalidUpnpService3)));
-        assertFalse(mDut.serviceAdd(createDummyP2pServiceInfo(mInvalidBonjourService1)));
-        assertFalse(mDut.serviceAdd(createDummyP2pServiceInfo(mInvalidBonjourService2)));
-        assertFalse(mDut.serviceAdd(createDummyP2pServiceInfo(mInvalidBonjourService3)));
-        assertFalse(mDut.serviceAdd(createDummyP2pServiceInfo(mInvalidBonjourService4)));
+        assertFalse(mDut.serviceAdd(createPlaceholderP2pServiceInfo(mInvalidService1)));
+        assertFalse(mDut.serviceAdd(createPlaceholderP2pServiceInfo(mInvalidService2)));
+        assertFalse(mDut.serviceAdd(createPlaceholderP2pServiceInfo(mInvalidUpnpService1)));
+        assertFalse(mDut.serviceAdd(createPlaceholderP2pServiceInfo(mInvalidUpnpService2)));
+        assertFalse(mDut.serviceAdd(createPlaceholderP2pServiceInfo(mInvalidUpnpService3)));
+        assertFalse(mDut.serviceAdd(createPlaceholderP2pServiceInfo(mInvalidBonjourService1)));
+        assertFalse(mDut.serviceAdd(createPlaceholderP2pServiceInfo(mInvalidBonjourService2)));
+        assertFalse(mDut.serviceAdd(createPlaceholderP2pServiceInfo(mInvalidBonjourService3)));
+        assertFalse(mDut.serviceAdd(createPlaceholderP2pServiceInfo(mInvalidBonjourService4)));
     }
 
     /**
@@ -1771,8 +1802,8 @@
                 any(ArrayList.class), any(ArrayList.class)))
                 .thenReturn(mStatusFailure);
 
-        assertFalse(mDut.serviceAdd(createDummyP2pServiceInfo(mValidUpnpService)));
-        assertFalse(mDut.serviceAdd(createDummyP2pServiceInfo(mValidBonjourService)));
+        assertFalse(mDut.serviceAdd(createPlaceholderP2pServiceInfo(mValidUpnpService)));
+        assertFalse(mDut.serviceAdd(createPlaceholderP2pServiceInfo(mValidBonjourService)));
 
         // Check that service is still alive.
         assertTrue(mDut.isInitializationComplete());
@@ -1787,7 +1818,7 @@
 
         when(mISupplicantP2pIfaceMock.addUpnpService(anyInt(), anyString()))
                 .thenThrow(mRemoteException);
-        assertFalse(mDut.serviceAdd(createDummyP2pServiceInfo(mValidUpnpService)));
+        assertFalse(mDut.serviceAdd(createPlaceholderP2pServiceInfo(mValidUpnpService)));
         // Check service is dead.
         assertFalse(mDut.isInitializationComplete());
 
@@ -1795,7 +1826,7 @@
         when(mISupplicantP2pIfaceMock.addBonjourService(
                 any(ArrayList.class), any(ArrayList.class)))
                 .thenThrow(mRemoteException);
-        assertFalse(mDut.serviceAdd(createDummyP2pServiceInfo(mValidBonjourService)));
+        assertFalse(mDut.serviceAdd(createPlaceholderP2pServiceInfo(mValidBonjourService)));
         // Check service is dead.
         assertFalse(mDut.isInitializationComplete());
     }
@@ -1806,7 +1837,7 @@
      */
     @Test
     public void testServiceRemove_success() throws Exception {
-        WifiP2pServiceInfo info = createDummyP2pServiceInfo(
+        WifiP2pServiceInfo info = createPlaceholderP2pServiceInfo(
                 mValidUpnpService, mValidBonjourService);
         final HashSet<String> services = new HashSet<String>();
 
@@ -1838,7 +1869,7 @@
         assertTrue(services.contains("bonjour"));
 
         // Empty services should cause no trouble.
-        assertTrue(mDut.serviceRemove(createDummyP2pServiceInfo()));
+        assertTrue(mDut.serviceRemove(createPlaceholderP2pServiceInfo()));
     }
 
     /**
@@ -1854,19 +1885,19 @@
                 .thenReturn(mStatusSuccess);
 
         assertFalse(mDut.serviceRemove(null));
-        assertFalse(mDut.serviceRemove(createDummyP2pServiceInfo(mInvalidService1)));
-        assertFalse(mDut.serviceRemove(createDummyP2pServiceInfo(mInvalidService2)));
-        assertFalse(mDut.serviceRemove(createDummyP2pServiceInfo(mInvalidUpnpService1)));
-        assertFalse(mDut.serviceRemove(createDummyP2pServiceInfo(mInvalidUpnpService2)));
-        assertFalse(mDut.serviceRemove(createDummyP2pServiceInfo(mInvalidUpnpService3)));
-        assertFalse(mDut.serviceRemove(createDummyP2pServiceInfo(mInvalidBonjourService1)));
-        assertFalse(mDut.serviceRemove(createDummyP2pServiceInfo(mInvalidBonjourService2)));
-        assertFalse(mDut.serviceRemove(createDummyP2pServiceInfo(mInvalidBonjourService3)));
+        assertFalse(mDut.serviceRemove(createPlaceholderP2pServiceInfo(mInvalidService1)));
+        assertFalse(mDut.serviceRemove(createPlaceholderP2pServiceInfo(mInvalidService2)));
+        assertFalse(mDut.serviceRemove(createPlaceholderP2pServiceInfo(mInvalidUpnpService1)));
+        assertFalse(mDut.serviceRemove(createPlaceholderP2pServiceInfo(mInvalidUpnpService2)));
+        assertFalse(mDut.serviceRemove(createPlaceholderP2pServiceInfo(mInvalidUpnpService3)));
+        assertFalse(mDut.serviceRemove(createPlaceholderP2pServiceInfo(mInvalidBonjourService1)));
+        assertFalse(mDut.serviceRemove(createPlaceholderP2pServiceInfo(mInvalidBonjourService2)));
+        assertFalse(mDut.serviceRemove(createPlaceholderP2pServiceInfo(mInvalidBonjourService3)));
         // Response parameter is ignored by serviceRemove call, hence the following would pass.
         // The production code would need to parse otherwise redundant parameter to fail on this
         // one.
-        //
-        // assertFalse(mDut.serviceRemove(createDummyP2pServiceInfo(mInvalidBonjourService4)));
+        // assertFalse(
+        //         mDut.serviceRemove(createPlaceholderP2pServiceInfo(mInvalidBonjourService4)));
     }
 
     /**
@@ -1881,8 +1912,8 @@
         when(mISupplicantP2pIfaceMock.removeBonjourService(any(ArrayList.class)))
                 .thenReturn(mStatusFailure);
 
-        assertFalse(mDut.serviceRemove(createDummyP2pServiceInfo(mValidUpnpService)));
-        assertFalse(mDut.serviceRemove(createDummyP2pServiceInfo(mValidBonjourService)));
+        assertFalse(mDut.serviceRemove(createPlaceholderP2pServiceInfo(mValidUpnpService)));
+        assertFalse(mDut.serviceRemove(createPlaceholderP2pServiceInfo(mValidBonjourService)));
 
         // Check that service is still alive.
         assertTrue(mDut.isInitializationComplete());
@@ -1897,14 +1928,14 @@
 
         when(mISupplicantP2pIfaceMock.removeUpnpService(anyInt(), anyString()))
                 .thenThrow(mRemoteException);
-        assertFalse(mDut.serviceRemove(createDummyP2pServiceInfo(mValidUpnpService)));
+        assertFalse(mDut.serviceRemove(createPlaceholderP2pServiceInfo(mValidUpnpService)));
         // Check service is dead.
         assertFalse(mDut.isInitializationComplete());
 
         executeAndValidateInitializationSequence(false, false, false);
         when(mISupplicantP2pIfaceMock.removeBonjourService(any(ArrayList.class)))
                 .thenThrow(mRemoteException);
-        assertFalse(mDut.serviceRemove(createDummyP2pServiceInfo(mValidBonjourService)));
+        assertFalse(mDut.serviceRemove(createPlaceholderP2pServiceInfo(mValidBonjourService)));
         // Check service is dead.
         assertFalse(mDut.isInitializationComplete());
     }
@@ -2872,9 +2903,10 @@
     }
 
     /**
-     * Create new dummy WifiP2pConfig instance.
+     * Create new placeholder WifiP2pConfig instance.
      */
-    private WifiP2pConfig createDummyP2pConfig(String peerAddress, int wpsProvMethod, String pin) {
+    private WifiP2pConfig createPlaceholderP2pConfig(String peerAddress,
+                                                     int wpsProvMethod, String pin) {
         WifiP2pConfig config = new WifiP2pConfig();
         config.wps = new WpsInfo();
         config.deviceAddress = peerAddress;
@@ -2888,9 +2920,9 @@
     }
 
     /**
-     * Create new dummy WifiP2pGroup instance.
+     * Create new placeholder WifiP2pGroup instance.
      */
-    private WifiP2pGroup createDummyP2pGroup() {
+    private WifiP2pGroup createPlaceholderP2pGroup() {
         WifiP2pGroup group = new WifiP2pGroup();
         group.setInterface(mIfaceName);
 
@@ -2902,9 +2934,9 @@
     }
 
     /**
-     * Create new dummy WifiP2pServiceInfo instance.
+     * Create new placeholder WifiP2pServiceInfo instance.
      */
-    private WifiP2pServiceInfo createDummyP2pServiceInfo(String... services) {
+    private WifiP2pServiceInfo createPlaceholderP2pServiceInfo(String... services) {
         class TestP2pServiceInfo extends WifiP2pServiceInfo {
             TestP2pServiceInfo(String[] services) {
                 super(Arrays.asList(services));
diff --git a/service/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pMonitorTest.java b/service/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pMonitorTest.java
index bced05d..1f87722 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pMonitorTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pMonitorTest.java
@@ -17,7 +17,6 @@
 package com.android.server.wifi.p2p;
 
 import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
@@ -28,14 +27,13 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.server.wifi.WifiBaseTest;
-import com.android.server.wifi.WifiInjector;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
 /**
- * Unit tests for {@link com.android.server.wifi.WifiP2pMonitor}.
+ * Unit tests for {@link com.android.server.wifi.p2p.WifiP2pMonitor}.
  */
 @SmallTest
 public class WifiP2pMonitorTest extends WifiBaseTest {
@@ -48,7 +46,7 @@
 
     @Before
     public void setUp() throws Exception {
-        mWifiP2pMonitor = new WifiP2pMonitor(mock(WifiInjector.class));
+        mWifiP2pMonitor = new WifiP2pMonitor();
         mLooper = new TestLooper();
         mHandlerSpy = spy(new Handler(mLooper.getLooper()));
         mSecondHandlerSpy = spy(new Handler(mLooper.getLooper()));
diff --git a/service/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pNativeInterfaceManagementTest.java b/service/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pNativeInterfaceManagementTest.java
index 48a4334..05ddde4 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pNativeInterfaceManagementTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pNativeInterfaceManagementTest.java
@@ -18,7 +18,6 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.never;
@@ -28,48 +27,48 @@
 import android.app.test.MockAnswerUtil;
 import android.hardware.wifi.V1_0.IWifiIface;
 import android.hardware.wifi.V1_0.IWifiP2pIface;
-import android.hardware.wifi.V1_0.IfaceType;
 import android.hardware.wifi.V1_0.WifiStatus;
 import android.hardware.wifi.V1_0.WifiStatusCode;
+import android.net.wifi.nl80211.WifiNl80211Manager;
 import android.os.Handler;
 import android.os.RemoteException;
+import android.os.WorkSource;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.server.wifi.HalDeviceManager;
-import com.android.server.wifi.HalDeviceManager.InterfaceAvailableForRequestListener;
 import com.android.server.wifi.HalDeviceManager.InterfaceDestroyedListener;
 import com.android.server.wifi.HalDeviceManager.ManagerStatusListener;
 import com.android.server.wifi.PropertyService;
 import com.android.server.wifi.WifiBaseTest;
-import com.android.server.wifi.WifiInjector;
+import com.android.server.wifi.WifiNative;
 import com.android.server.wifi.WifiVendorHal;
 
 import org.junit.Before;
 import org.junit.Test;
-import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 /**
  * Unit tests for the interface management operations in
- * {@link com.android.server.wifi.WifiP2pNative}.
+ * {@link com.android.server.wifi.p2p.WifiP2pNative}.
  */
 @SmallTest
 public class WifiP2pNativeInterfaceManagementTest extends WifiBaseTest {
     private static final String P2P_IFACE_NAME = "p2p0";
     private static final String P2P_INTERFACE_PROPERTY = "wifi.direct.interface";
+    private static final WorkSource TEST_WS = new WorkSource();
 
     @Mock private SupplicantP2pIfaceHal mSupplicantP2pIfaceHal;
     @Mock private HalDeviceManager mHalDeviceManager;
     @Mock private PropertyService mPropertyService;
     @Mock private Handler mHandler;
-    @Mock private InterfaceAvailableForRequestListener mInterfaceRequestListener;
     @Mock private InterfaceDestroyedListener mHalDeviceInterfaceDestroyedListener;
     @Mock private IWifiP2pIface mIWifiP2pIface;
     @Mock private IWifiIface mIWifiIface;
     @Mock private WifiVendorHal mWifiVendorHal;
-    @Mock private WifiInjector mWifiInjector;
+    @Mock private WifiNl80211Manager mWifiNl80211Manager;
+    @Mock private WifiNative mWifiNative;
     private WifiP2pNative mWifiP2pNative;
     private WifiStatus mWifiStatusSuccess;
     private ManagerStatusListener mManagerStatusListener;
@@ -85,7 +84,7 @@
 
         when(mHalDeviceManager.isSupported()).thenReturn(true);
         when(mHalDeviceManager.createP2pIface(any(InterfaceDestroyedListener.class),
-                any(Handler.class))).thenReturn(mIWifiP2pIface);
+                any(Handler.class), any(WorkSource.class))).thenReturn(mIWifiP2pIface);
         doAnswer(new MockAnswerUtil.AnswerWithArguments() {
             public void answer(IWifiIface.getNameCallback cb)
                     throws RemoteException {
@@ -100,41 +99,22 @@
         when(mPropertyService.getString(P2P_INTERFACE_PROPERTY, P2P_IFACE_NAME))
               .thenReturn(P2P_IFACE_NAME);
 
-        mWifiP2pNative = new WifiP2pNative(mWifiInjector,
+        mWifiP2pNative = new WifiP2pNative(mWifiNl80211Manager, mWifiNative,
                               mWifiVendorHal, mSupplicantP2pIfaceHal, mHalDeviceManager,
                               mPropertyService);
     }
 
     /**
-     * Verifies the HAL (HIDL) interface listener.
-     */
-    @Test
-    public void testRegisterInterfaceAvailableListener() throws Exception {
-        when(mHalDeviceManager.isStarted()).thenReturn(false);
-
-        mWifiP2pNative.registerInterfaceAvailableListener(mInterfaceRequestListener, mHandler);
-        when(mHalDeviceManager.isStarted()).thenReturn(true);
-
-        ArgumentCaptor<ManagerStatusListener> hdmCallbackCaptor =
-                ArgumentCaptor.forClass(ManagerStatusListener.class);
-        verify(mHalDeviceManager).registerStatusListener(hdmCallbackCaptor.capture(), eq(mHandler));
-        // Simulate to call status callback from device hal manager
-        hdmCallbackCaptor.getValue().onStatusChanged();
-
-        verify(mHalDeviceManager).registerInterfaceAvailableForRequestListener(eq(IfaceType.P2P),
-                any(InterfaceAvailableForRequestListener.class), eq(mHandler));
-    }
-
-    /**
      * Verifies the setup of a p2p interface.
      */
     @Test
     public void testSetUpInterface() throws Exception {
         assertEquals(P2P_IFACE_NAME,
-                mWifiP2pNative.setupInterface(mHalDeviceInterfaceDestroyedListener, mHandler));
+                mWifiP2pNative.setupInterface(
+                        mHalDeviceInterfaceDestroyedListener, mHandler, TEST_WS));
 
         verify(mHalDeviceManager).createP2pIface(any(InterfaceDestroyedListener.class),
-                eq(mHandler));
+                eq(mHandler), eq(TEST_WS));
         verify(mSupplicantP2pIfaceHal).setupIface(eq(P2P_IFACE_NAME));
     }
 
@@ -146,10 +126,11 @@
         when(mHalDeviceManager.isSupported()).thenReturn(false);
 
         assertEquals(P2P_IFACE_NAME, mWifiP2pNative.setupInterface(
-                mHalDeviceInterfaceDestroyedListener, mHandler));
+                mHalDeviceInterfaceDestroyedListener, mHandler, TEST_WS));
 
         verify(mHalDeviceManager, never())
-                .createP2pIface(any(InterfaceDestroyedListener.class), any(Handler.class));
+                .createP2pIface(any(InterfaceDestroyedListener.class), any(Handler.class),
+                        any(WorkSource.class));
         verify(mSupplicantP2pIfaceHal).setupIface(eq(P2P_IFACE_NAME));
     }
 
@@ -160,7 +141,7 @@
     public void testTeardownInterface() throws Exception {
         assertEquals(P2P_IFACE_NAME,
                 mWifiP2pNative.setupInterface(mHalDeviceInterfaceDestroyedListener,
-                    mHandler));
+                    mHandler, TEST_WS));
 
         mWifiP2pNative.teardownInterface();
 
@@ -176,7 +157,7 @@
         when(mHalDeviceManager.isSupported()).thenReturn(false);
 
         assertEquals(P2P_IFACE_NAME, mWifiP2pNative.setupInterface(
-                mHalDeviceInterfaceDestroyedListener, mHandler));
+                mHalDeviceInterfaceDestroyedListener, mHandler, TEST_WS));
 
         mWifiP2pNative.teardownInterface();
 
diff --git a/service/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pNativeTest.java b/service/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pNativeTest.java
index eca30bd..bd75d88 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pNativeTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pNativeTest.java
@@ -45,7 +45,6 @@
 import com.android.server.wifi.HalDeviceManager;
 import com.android.server.wifi.PropertyService;
 import com.android.server.wifi.WifiBaseTest;
-import com.android.server.wifi.WifiInjector;
 import com.android.server.wifi.WifiNative;
 import com.android.server.wifi.WifiVendorHal;
 
@@ -55,6 +54,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -83,7 +83,6 @@
     private static final String TEST_NFC_SELECT_MSG = "select";
     private static final String TEST_CLIENT_LIST = "aa:bb:cc:dd:ee:ff 11:22:33:44:55:66";
 
-    @Mock private WifiInjector mWifiInjector;
     @Mock private WifiNl80211Manager mWifiCondManager;
     @Mock private WifiNative mWifiNative;
     @Mock private WifiVendorHal mWifiVendorHalMock;
@@ -115,15 +114,13 @@
         mWifiClientInterfaceNames.add("wlan1");
 
         mWifiP2pNative = new WifiP2pNative(
-                mWifiInjector,
+                mWifiCondManager,
+                mWifiNative,
                 mWifiVendorHalMock,
                 mSupplicantP2pIfaceHalMock,
                 mHalDeviceManagerMock,
                 mPropertyServiceMock);
 
-        when(mWifiInjector.getWifiCondManager()).thenReturn(mWifiCondManager);
-        when(mWifiInjector.getWifiNative()).thenReturn(mWifiNative);
-
         when(mWifiNative.getClientInterfaceNames()).thenReturn(mWifiClientInterfaceNames);
 
         mWifiP2pGroupList.add(
@@ -349,11 +346,22 @@
      * Verifies setting p2p listen channel.
      */
     @Test
-    public void testP2pSetChannel() {
-        when(mSupplicantP2pIfaceHalMock.setListenChannel(anyInt(), anyInt()))
+    public void testP2pSetListenChannel() {
+        when(mSupplicantP2pIfaceHalMock.setListenChannel(anyInt()))
                 .thenReturn(true);
-        assertTrue(mWifiP2pNative.p2pSetChannel(1, 81));
-        verify(mSupplicantP2pIfaceHalMock).setListenChannel(eq(1), eq(81));
+        assertTrue(mWifiP2pNative.p2pSetListenChannel(1));
+        verify(mSupplicantP2pIfaceHalMock).setListenChannel(eq(1));
+    }
+
+    /**
+     * Verifies setting p2p operating channel.
+     */
+    @Test
+    public void testP2pSetOperatingChannel() {
+        when(mSupplicantP2pIfaceHalMock.setOperatingChannel(anyInt(), any()))
+                .thenReturn(true);
+        assertTrue(mWifiP2pNative.p2pSetOperatingChannel(65, Collections.emptyList()));
+        verify(mSupplicantP2pIfaceHalMock).setOperatingChannel(eq(65), any());
     }
 
     /**
diff --git a/service/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pServiceImplTest.java b/service/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pServiceImplTest.java
index 171dc9b..fea45e7 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pServiceImplTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pServiceImplTest.java
@@ -16,6 +16,9 @@
 
 package com.android.server.wifi.p2p;
 
+import static android.net.NetworkInfo.DetailedState.FAILED;
+import static android.net.NetworkInfo.DetailedState.IDLE;
+
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.server.wifi.WifiSettingsConfigStore.WIFI_P2P_DEVICE_NAME;
 import static com.android.server.wifi.WifiSettingsConfigStore.WIFI_P2P_PENDING_FACTORY_RESET;
@@ -26,9 +29,12 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Matchers.isNull;
@@ -45,6 +51,7 @@
 import static org.mockito.Mockito.when;
 
 import android.annotation.Nullable;
+import android.app.AlarmManager;
 import android.app.test.MockAnswerUtil.AnswerWithArguments;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -54,11 +61,14 @@
 import android.content.res.Resources;
 import android.location.LocationManager;
 import android.net.ConnectivityManager;
+import android.net.InetAddresses;
 import android.net.NetworkInfo;
 import android.net.TetheringManager;
+import android.net.wifi.CoexUnsafeChannel;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
+import android.net.wifi.WifiScanner;
 import android.net.wifi.WpsInfo;
 import android.net.wifi.p2p.WifiP2pConfig;
 import android.net.wifi.p2p.WifiP2pDevice;
@@ -79,28 +89,32 @@
 import android.os.Messenger;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.WorkSource;
 import android.os.test.TestLooper;
+import android.provider.Settings;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.FakeWifiLog;
 import com.android.server.wifi.FrameworkFacade;
-import com.android.server.wifi.HalDeviceManager;
 import com.android.server.wifi.WifiBaseTest;
+import com.android.server.wifi.WifiGlobals;
 import com.android.server.wifi.WifiInjector;
 import com.android.server.wifi.WifiSettingsConfigStore;
+import com.android.server.wifi.coex.CoexManager;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.P2pConnectionEvent;
 import com.android.server.wifi.util.NetdWrapper;
+import com.android.server.wifi.util.StringUtil;
 import com.android.server.wifi.util.WifiPermissionsUtil;
 import com.android.server.wifi.util.WifiPermissionsWrapper;
 import com.android.wifi.resources.R;
 
-import libcore.net.InetAddressUtils;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.MockitoSession;
@@ -125,10 +139,9 @@
     private static final String thisDeviceMac = "11:22:33:44:55:66";
     private static final String thisDeviceName = "thisDeviceName";
     private static final String ANONYMIZED_DEVICE_ADDRESS = "02:00:00:00:00:00";
+    private static final String TEST_PACKAGE_NAME = "com.p2p.test";
+    private static final String TEST_ANDROID_ID = "314Deadbeef";
 
-    private ArgumentCaptor<HalDeviceManager.InterfaceAvailableForRequestListener>
-            mAvailListenerCaptor = ArgumentCaptor.forClass(
-            HalDeviceManager.InterfaceAvailableForRequestListener.class);
     private ArgumentCaptor<BroadcastReceiver> mBcastRxCaptor = ArgumentCaptor.forClass(
             BroadcastReceiver.class);
     private Binder mClient1;
@@ -172,8 +185,12 @@
     @Mock WifiP2pMetrics mWifiP2pMetrics;
     @Mock WifiManager mWifiManager;
     @Mock WifiInfo mWifiInfo;
+    @Mock CoexManager mCoexManager;
     @Spy FakeWifiLog mLog;
     @Spy MockWifiP2pMonitor mWifiMonitor;
+    @Mock WifiGlobals mWifiGlobals;
+    @Mock AlarmManager mAlarmManager;
+    CoexManager.CoexListener mCoexListener;
 
     private void generatorTestData() {
         mTestWifiP2pGroup = new WifiP2pGroup();
@@ -657,7 +674,14 @@
     private void forceP2pEnabled(Binder clientBinder) throws Exception {
         simulateWifiStateChange(true);
         simulateLocationModeChange(true);
-        checkIsP2pInitWhenClientConnected(true, clientBinder);
+        checkIsP2pInitWhenClientConnected(true, false, clientBinder,
+                new WorkSource(clientBinder.getCallingUid(), TEST_PACKAGE_NAME));
+        verify(mContext).sendBroadcastWithMultiplePermissions(
+                argThat(new WifiP2pServiceImplTest
+                       .P2pConnectionChangedIntentMatcherForNetworkState(IDLE)), any());
+        verify(mContext, never()).sendBroadcastWithMultiplePermissions(
+                argThat(new WifiP2pServiceImplTest
+                        .P2pConnectionChangedIntentMatcherForNetworkState(FAILED)), any());
     }
 
     /**
@@ -665,18 +689,32 @@
      *
      * @param expectInit set true if p2p init should succeed as expected, set false when
      *        expected init should not happen
+     * @param expectReplace set true if p2p worksource replace should succeed as expected, set false
+     *        when replace should not happen
      * @param clientBinder client binder to use for p2p channel init
+     * @param expectedRequestorWs Expected merged requestorWs
      */
-    private void checkIsP2pInitWhenClientConnected(boolean expectInit, Binder clientBinder)
+    private void checkIsP2pInitWhenClientConnected(boolean expectInit, boolean expectReplace,
+            Binder clientBinder, WorkSource expectedRequestorWs)
             throws Exception {
-        mWifiP2pServiceImpl.getMessenger(clientBinder);
-        mLooper.dispatchAll();
+        mWifiP2pServiceImpl.getMessenger(clientBinder, TEST_PACKAGE_NAME);
         if (expectInit) {
-            verify(mWifiNative).setupInterface(any(), any());
+            // send a command to force P2P enabled.
+            sendSimpleMsg(mClientMessenger, WifiP2pManager.REQUEST_P2P_STATE);
+        }
+        mLooper.dispatchAll();
+        reset(mClientHandler);
+        if (expectInit) {
+            verify(mWifiNative).setupInterface(any(), any(), eq(expectedRequestorWs));
             verify(mNetdWrapper).setInterfaceUp(anyString());
             verify(mWifiMonitor, atLeastOnce()).registerHandler(anyString(), anyInt(), any());
+            // Verify timer is scheduled
+            verify(mAlarmManager, times(2)).setExact(anyInt(), anyLong(),
+                    eq(mWifiP2pServiceImpl.P2P_IDLE_SHUTDOWN_MESSAGE_TIMEOUT_TAG), any(), any());
+        } else if (expectReplace) {
+            verify(mWifiNative).replaceRequestorWs(expectedRequestorWs);
         } else {
-            verify(mWifiNative, never()).setupInterface(any(), any());
+            verify(mWifiNative, never()).setupInterface(any(), any(), any());
             verify(mNetdWrapper, never()).setInterfaceUp(anyString());
             verify(mWifiMonitor, never()).registerHandler(anyString(), anyInt(), any());
         }
@@ -687,15 +725,21 @@
      *
      * @param expectTearDown set true if p2p teardown should succeed as expected,
      *        set false when expected teardown should not happen
+     * @param expectReplace set true if p2p worksource replace should succeed as expected, set false
+     *        when replace should not happen
      * @param clientBinder client binder to use for p2p channel init
+     * @param expectedRequestorWs Expected merged requestorWs
      */
     private void checkIsP2pTearDownWhenClientDisconnected(
-            boolean expectTearDown, Binder clientBinder) throws Exception {
+            boolean expectTearDown, boolean expectReplace,
+            Binder clientBinder, WorkSource expectedRequestorWs) throws Exception {
         mWifiP2pServiceImpl.close(clientBinder);
         mLooper.dispatchAll();
         if (expectTearDown) {
             verify(mWifiNative).teardownInterface();
             verify(mWifiMonitor).stopMonitoring(anyString());
+        } else if (expectReplace) {
+            verify(mWifiNative).replaceRequestorWs(expectedRequestorWs);
         } else {
             verify(mWifiNative, never()).teardownInterface();
             verify(mWifiMonitor, never()).stopMonitoring(anyString());
@@ -754,6 +798,22 @@
         assertEquals(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT, intent.getFlags());
     }
 
+    private class P2pConnectionChangedIntentMatcherForNetworkState
+            implements ArgumentMatcher<Intent> {
+        private final NetworkInfo.DetailedState mState;
+        P2pConnectionChangedIntentMatcherForNetworkState(NetworkInfo.DetailedState state) {
+            mState = state;
+        }
+        @Override
+        public boolean matches(Intent intent) {
+            if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION != intent.getAction()) {
+                return false;
+            }
+            NetworkInfo networkInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
+            return networkInfo.getDetailedState() == mState;
+        }
+    }
+
     /**
      * Set up the instance of WifiP2pServiceImpl for testing.
      *
@@ -769,6 +829,8 @@
         mClientMessenger =  new Messenger(mClientHandler);
         mLooper = new TestLooper();
 
+        when(mContext.getSystemService(Context.ALARM_SERVICE))
+                .thenReturn(mAlarmManager);
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         when(mContext.getResources()).thenReturn(mResources);
         when(mContext.getSystemService(WifiManager.class)).thenReturn(mWifiManager);
@@ -793,6 +855,8 @@
         when(mWifiInjector.getWifiP2pServiceHandlerThread()).thenReturn(mHandlerThread);
         when(mWifiInjector.getWifiPermissionsUtil()).thenReturn(mWifiPermissionsUtil);
         when(mWifiInjector.getSettingsConfigStore()).thenReturn(mWifiSettingsConfigStore);
+        when(mWifiInjector.getCoexManager()).thenReturn(mCoexManager);
+        when(mWifiInjector.getWifiGlobals()).thenReturn(mWifiGlobals);
         // enable all permissions, disable specific permissions in tests
         when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(true);
         when(mWifiPermissionsUtil.checkNetworkStackPermission(anyInt())).thenReturn(true);
@@ -800,7 +864,7 @@
         when(mWifiPermissionsUtil.checkConfigOverridePermission(anyInt())).thenReturn(true);
         when(mWifiPermissionsUtil.checkCanAccessWifiDirect(anyString(), anyString(), anyInt(),
                 anyBoolean())).thenReturn(true);
-        when(mWifiNative.setupInterface(any(), any())).thenReturn(IFACE_NAME_P2P);
+        when(mWifiNative.setupInterface(any(), any(), any())).thenReturn(IFACE_NAME_P2P);
         when(mWifiNative.p2pGetDeviceAddress()).thenReturn(thisDeviceMac);
         doAnswer(new AnswerWithArguments() {
             public boolean answer(WifiP2pGroupList groups) {
@@ -819,6 +883,14 @@
         }).when(mWifiNative).removeP2pNetwork(anyInt());
         when(mWifiSettingsConfigStore.get(eq(WIFI_VERBOSE_LOGGING_ENABLED))).thenReturn(true);
 
+        doAnswer(new AnswerWithArguments() {
+            public void answer(CoexManager.CoexListener listener) {
+                mCoexListener = listener;
+            }
+        }).when(mCoexManager).registerCoexListener(any(CoexManager.CoexListener.class));
+        when(mCoexManager.getCoexRestrictions()).thenReturn(0);
+        when(mCoexManager.getCoexUnsafeChannels()).thenReturn(Collections.emptyList());
+
         mWifiP2pServiceImpl = new WifiP2pServiceImpl(mContext, mWifiInjector);
         if (supported) {
             // register these event:
@@ -830,9 +902,6 @@
             mWifiStateChangedReceiver = mBcastRxCaptor.getAllValues().get(0);
             mLocationModeReceiver = mBcastRxCaptor.getAllValues().get(1);
             mTetherStateReceiver = mBcastRxCaptor.getAllValues().get(2);
-            verify(mWifiNative).registerInterfaceAvailableListener(
-                    mAvailListenerCaptor.capture(), any(Handler.class));
-            mAvailListenerCaptor.getValue().onAvailabilityChanged(true);
         }
 
         mWifiP2pServiceImpl.mNetdWrapper = mNetdWrapper;
@@ -851,7 +920,7 @@
         lenient().when(NetworkInterface.getByName(eq(IFACE_NAME_P2P)))
                 .thenReturn(mP2pNetworkInterface);
         ArrayList<InetAddress> p2pInetAddresses = new ArrayList<>();
-        p2pInetAddresses.add(InetAddressUtils.parseNumericAddress(P2P_GO_IP));
+        p2pInetAddresses.add(InetAddresses.parseNumericAddress(P2P_GO_IP));
         when(mP2pNetworkInterface.getInetAddresses())
                 .thenReturn(Collections.enumeration(p2pInetAddresses));
 
@@ -907,8 +976,9 @@
     @Test
     public void testP2pInitWhenClientConnectWithWifiEnabled() throws Exception {
         simulateWifiStateChange(true);
-        checkIsP2pInitWhenClientConnected(true, mClient1);
-        checkIsP2pTearDownWhenClientDisconnected(true, mClient1);
+        checkIsP2pInitWhenClientConnected(true, false, mClient1,
+                new WorkSource(mClient1.getCallingUid(), TEST_PACKAGE_NAME));
+        checkIsP2pTearDownWhenClientDisconnected(true, false, mClient1, null);
     }
 
     /**
@@ -916,11 +986,12 @@
      * with wifi disabled
      */
     @Test
-    public void testP2pDoesntInitWhenClientConnectWithWifiDisabledEnabled()
+    public void testP2pDoesntInitWhenClientConnectWithWifiDisabled()
             throws Exception {
         simulateWifiStateChange(false);
-        checkIsP2pInitWhenClientConnected(false, mClient1);
-        checkIsP2pTearDownWhenClientDisconnected(false, mClient1);
+        checkIsP2pInitWhenClientConnected(false, false, mClient1,
+                new WorkSource(mClient1.getCallingUid(), TEST_PACKAGE_NAME));
+        checkIsP2pTearDownWhenClientDisconnected(false, false, mClient1, null);
     }
 
     /**
@@ -938,11 +1009,17 @@
         // Force to back disable state for next test
         mockEnterDisabledState();
 
+        // wifi off / on won't initialize the p2p interface.
         simulateWifiStateChange(true);
         mLooper.dispatchAll();
-        verify(mWifiNative, times(2)).setupInterface(any(), any());
-        verify(mNetdWrapper, times(2)).setInterfaceUp(anyString());
+        verify(mWifiNative, times(1)).setupInterface(any(), any(), any());
+        verify(mNetdWrapper, times(1)).setInterfaceUp(anyString());
         verify(mWifiMonitor, atLeastOnce()).registerHandler(anyString(), anyInt(), any());
+
+        // Lazy initialization is done once receiving a command.
+        sendSimpleMsg(mClientMessenger, WifiP2pManager.REQUEST_P2P_STATE);
+        verify(mWifiNative, times(2)).setupInterface(any(), any(), any());
+        verify(mNetdWrapper, times(2)).setInterfaceUp(anyString());
     }
 
     /**
@@ -951,10 +1028,15 @@
     @Test
     public void checkIsP2pInitForTwoClientsConnection() throws Exception {
         forceP2pEnabled(mClient1);
+        WorkSource expectedRequestorWs =
+                new WorkSource(mClient1.getCallingUid(), TEST_PACKAGE_NAME);
+        expectedRequestorWs.add(mClient2.getCallingUid(), TEST_PACKAGE_NAME);
         // P2pInit check count should keep in once, same as one client connected case.
-        checkIsP2pInitWhenClientConnected(true, mClient2);
-        checkIsP2pTearDownWhenClientDisconnected(false, mClient2);
-        checkIsP2pTearDownWhenClientDisconnected(true, mClient1);
+        checkIsP2pInitWhenClientConnected(false, true, mClient2, expectedRequestorWs);
+        reset(mWifiNative);
+        checkIsP2pTearDownWhenClientDisconnected(false, true, mClient2,
+                new WorkSource(mClient1.getCallingUid(), TEST_PACKAGE_NAME));
+        checkIsP2pTearDownWhenClientDisconnected(true, false, mClient1, null);
     }
 
     /**
@@ -1002,13 +1084,7 @@
                 anyInt(), eq(false));
     }
 
-    /**
-     * Verify the caller with proper permission sends WifiP2pManager.ADD_LOCAL_SERVICE.
-     */
-    @Test
-    public void testAddLocalServiceSuccess() throws Exception {
-        forceP2pEnabled(mClient1);
-        sendChannelInfoUpdateMsg("testPkg1", "testFeature", mClient1, mClientMessenger);
+    private void verifyAddLocalService() throws Exception {
         doNothing().when(mWifiPermissionsUtil).checkPackage(anyInt(), anyString());
         when(mWifiNative.p2pServiceAdd(any())).thenReturn(true);
         sendAddLocalServiceMsg(mClientMessenger);
@@ -1019,6 +1095,16 @@
     }
 
     /**
+     * Verify the caller with proper permission sends WifiP2pManager.ADD_LOCAL_SERVICE.
+     */
+    @Test
+    public void testAddLocalServiceSuccess() throws Exception {
+        forceP2pEnabled(mClient1);
+        sendChannelInfoUpdateMsg("testPkg1", "testFeature", mClient1, mClientMessenger);
+        verifyAddLocalService();
+    }
+
+    /**
      * Verify WifiP2pManager.ADD_LOCAL_SERVICE_FAILED is returned when native call failure.
      */
     @Test
@@ -2166,6 +2252,9 @@
                 anyInt(), eq(false));
 
         sendSimpleMsg(null, WifiP2pMonitor.P2P_GROUP_REMOVED_EVENT);
+        verify(mContext).sendBroadcastWithMultiplePermissions(
+                argThat(new WifiP2pServiceImplTest
+                        .P2pConnectionChangedIntentMatcherForNetworkState(FAILED)), any());
 
         verify(mWifiP2pMetrics).endConnectionEvent(
                 eq(P2pConnectionEvent.CLF_UNKNOWN));
@@ -2263,6 +2352,82 @@
         assertEquals(thisDeviceName, wifiP2pDevice.deviceName);
     }
 
+    private void verifyCustomizeDefaultDeviceName(String expectedName, boolean isRandomPostfix)
+            throws Exception {
+        forceP2pEnabled(mClient1);
+        when(mWifiPermissionsUtil.checkLocalMacAddressPermission(anyInt())).thenReturn(true);
+        sendChannelInfoUpdateMsg("testPkg1", "testFeature", mClient1, mClientMessenger);
+
+        sendSimpleMsg(mClientMessenger, WifiP2pManager.REQUEST_DEVICE_INFO);
+        verify(mClientHandler).sendMessage(mMessageCaptor.capture());
+        assertEquals(WifiP2pManager.RESPONSE_DEVICE_INFO, mMessageCaptor.getValue().what);
+
+        WifiP2pDevice wifiP2pDevice = (WifiP2pDevice) mMessageCaptor.getValue().obj;
+        if (isRandomPostfix) {
+            assertEquals(expectedName,
+                    wifiP2pDevice.deviceName.substring(0, expectedName.length()));
+        } else {
+            assertEquals(expectedName, wifiP2pDevice.deviceName);
+        }
+    }
+
+    private void setupDefaultDeviceNameCustomization(
+            String prefix, int postfixDigit) {
+        when(mWifiSettingsConfigStore.get(eq(WIFI_P2P_DEVICE_NAME))).thenReturn(null);
+        when(mFrameworkFacade.getSecureStringSetting(any(), eq(Settings.Secure.ANDROID_ID)))
+                .thenReturn(TEST_ANDROID_ID);
+        when(mWifiGlobals.getWifiP2pDeviceNamePrefix()).thenReturn(prefix);
+        when(mWifiGlobals.getWifiP2pDeviceNamePostfixNumDigits()).thenReturn(postfixDigit);
+    }
+
+    /** Verify that the default device name is customized by overlay. */
+    @Test
+    public void testCustomizeDefaultDeviceName() throws Exception {
+        setupDefaultDeviceNameCustomization("Niceboat-", -1);
+        verifyCustomizeDefaultDeviceName("Niceboat-" + TEST_ANDROID_ID.substring(0, 4), false);
+    }
+
+    /** Verify that the prefix fallback to Android_ if the prefix is too long. */
+    @Test
+    public void testCustomizeDefaultDeviceNameTooLongPrefix() throws Exception {
+        setupDefaultDeviceNameCustomization(
+                StringUtil.generateRandomNumberString(
+                        WifiP2pServiceImpl.DEVICE_NAME_PREFIX_LENGTH_MAX + 1), 4);
+        verifyCustomizeDefaultDeviceName(WifiP2pServiceImpl.DEFAULT_DEVICE_NAME_PREFIX, true);
+    }
+
+    /** Verify that the prefix fallback to Android_ if the prefix is empty. */
+    @Test
+    public void testCustomizeDefaultDeviceNameEmptyPrefix() throws Exception {
+        setupDefaultDeviceNameCustomization("", 6);
+        verifyCustomizeDefaultDeviceName(WifiP2pServiceImpl.DEFAULT_DEVICE_NAME_PREFIX, true);
+    }
+
+    /** Verify that the postfix fallbacks to 4-digit ANDROID_ID if the length is smaller than 4. */
+    @Test
+    public void testCustomizeDefaultDeviceNamePostfixTooShort() throws Exception {
+        setupDefaultDeviceNameCustomization("Prefix",
+                WifiP2pServiceImpl.DEVICE_NAME_POSTFIX_LENGTH_MIN - 1);
+        verifyCustomizeDefaultDeviceName("Prefix" + TEST_ANDROID_ID.substring(0, 4), true);
+    }
+
+    /** Verify that the postfix fallbacks to 4-digit ANDROID_ID if the length is 0.*/
+    @Test
+    public void testCustomizeDefaultDeviceNamePostfixIsZeroLength() throws Exception {
+        setupDefaultDeviceNameCustomization("Prefix", 0);
+        verifyCustomizeDefaultDeviceName("Prefix" + TEST_ANDROID_ID.substring(0, 4), true);
+    }
+
+    /** Verify that the digit length exceeds the remaining bytes. */
+    @Test
+    public void testCustomizeDefaultDeviceNameWithFewerRemainingBytes() throws Exception {
+        int postfixLength = 6;
+        String prefix = StringUtil.generateRandomNumberString(
+                WifiP2pServiceImpl.DEVICE_NAME_LENGTH_MAX - postfixLength + 1);
+        setupDefaultDeviceNameCustomization(prefix, postfixLength);
+        verifyCustomizeDefaultDeviceName(prefix, true);
+    }
+
     /**
      * Verify the caller sends WifiP2pManager.STOP_DISCOVERY.
      */
@@ -2486,9 +2651,11 @@
         Bundle p2pChannels = new Bundle();
         p2pChannels.putInt("lc", 1);
         p2pChannels.putInt("oc", 2);
-        when(mWifiNative.p2pSetChannel(anyInt(), anyInt())).thenReturn(true);
+        when(mWifiNative.p2pSetListenChannel(anyInt())).thenReturn(true);
+        when(mWifiNative.p2pSetOperatingChannel(anyInt(), any())).thenReturn(true);
         sendSetChannelMsg(mClientMessenger, p2pChannels);
-        verify(mWifiNative).p2pSetChannel(eq(1), eq(2));
+        verify(mWifiNative).p2pSetListenChannel(eq(1));
+        verify(mWifiNative).p2pSetOperatingChannel(eq(2), any());
         verify(mClientHandler).sendMessage(mMessageCaptor.capture());
         Message message = mMessageCaptor.getValue();
         assertEquals(WifiP2pManager.SET_CHANNEL_SUCCEEDED, message.what);
@@ -2498,16 +2665,37 @@
      *  Verify WifiP2pManager.SET_CHANNEL_FAILED is returned when native call failure.
      */
     @Test
-    public void testSetChannelFailureWhenNativeCallFailure() throws Exception {
+    public void testSetChannelFailureWhenNativeCallSetListenChannelFailure() throws Exception {
         // Move to enabled state
         forceP2pEnabled(mClient1);
 
         Bundle p2pChannels = new Bundle();
         p2pChannels.putInt("lc", 1);
         p2pChannels.putInt("oc", 2);
-        when(mWifiNative.p2pSetChannel(anyInt(), anyInt())).thenReturn(false);
+        when(mWifiNative.p2pSetListenChannel(anyInt())).thenReturn(false);
+        when(mWifiNative.p2pSetOperatingChannel(anyInt(), any())).thenReturn(true);
         sendSetChannelMsg(mClientMessenger, p2pChannels);
-        verify(mWifiNative).p2pSetChannel(eq(1), eq(2));
+        verify(mWifiNative).p2pSetListenChannel(eq(1));
+        verify(mClientHandler).sendMessage(mMessageCaptor.capture());
+        Message message = mMessageCaptor.getValue();
+        assertEquals(WifiP2pManager.SET_CHANNEL_FAILED, message.what);
+    }
+
+    /**
+     *  Verify WifiP2pManager.SET_CHANNEL_FAILED is returned when native call failure.
+     */
+    @Test
+    public void testSetChannelFailureWhenNativeCallSetOperatingChannelFailure() throws Exception {
+        // Move to enabled state
+        forceP2pEnabled(mClient1);
+
+        Bundle p2pChannels = new Bundle();
+        p2pChannels.putInt("lc", 1);
+        p2pChannels.putInt("oc", 2);
+        when(mWifiNative.p2pSetListenChannel(anyInt())).thenReturn(true);
+        when(mWifiNative.p2pSetOperatingChannel(anyInt(), any())).thenReturn(false);
+        sendSetChannelMsg(mClientMessenger, p2pChannels);
+        verify(mWifiNative).p2pSetListenChannel(eq(1));
         verify(mClientHandler).sendMessage(mMessageCaptor.capture());
         Message message = mMessageCaptor.getValue();
         assertEquals(WifiP2pManager.SET_CHANNEL_FAILED, message.what);
@@ -2537,16 +2725,22 @@
     }
 
     /**
-     *  Verify p2pSetChannel doesn't been called when message contain null object.
+     *  Verify p2pSetListenChannel doesn't been called when message contain null object.
      */
     @Test
     public void testSetChannelFailureWhenObjectIsNull() throws Exception {
+        when(mWifiNative.p2pSetListenChannel(anyInt())).thenReturn(true);
+        when(mWifiNative.p2pSetOperatingChannel(anyInt(), any())).thenReturn(true);
+
         // Move to enabled state
         forceP2pEnabled(mClient1);
 
-        when(mWifiNative.p2pSetChannel(anyInt(), anyInt())).thenReturn(false);
+        when(mWifiNative.p2pSetListenChannel(anyInt())).thenReturn(false);
+        when(mWifiNative.p2pSetOperatingChannel(anyInt(), any())).thenReturn(true);
         sendSetChannelMsg(mClientMessenger, null);
-        verify(mWifiNative, never()).p2pSetChannel(anyInt(), anyInt());
+        // Should be called only once on entering P2pEnabledState.
+        verify(mWifiNative, times(1)).p2pSetListenChannel(anyInt());
+        verify(mWifiNative, times(1)).p2pSetOperatingChannel(anyInt(), any());
     }
 
     /**
@@ -3607,7 +3801,8 @@
     @Test
     public void testRemoveLocalServiceSuccess() throws Exception {
         forceP2pEnabled(mClient1);
-        testAddLocalServiceSuccess();
+        sendChannelInfoUpdateMsg("testPkg1", "testFeature", mClient1, mClientMessenger);
+        verifyAddLocalService();
 
         sendRemoveLocalServiceMsg(mClientMessenger, mTestWifiP2pServiceInfo);
         verify(mWifiNative).p2pServiceDel(any(WifiP2pServiceInfo.class));
@@ -3634,7 +3829,8 @@
     @Test
     public void testRemoveLocalServiceSuccessWithNullServiceInfo() throws Exception {
         forceP2pEnabled(mClient1);
-        testAddLocalServiceSuccess();
+        sendChannelInfoUpdateMsg("testPkg1", "testFeature", mClient1, mClientMessenger);
+        verifyAddLocalService();
 
         sendRemoveLocalServiceMsg(mClientMessenger, null);
         verify(mWifiNative, never()).p2pServiceDel(any(WifiP2pServiceInfo.class));
@@ -3680,7 +3876,8 @@
     @Test
     public void testClearLocalServiceSuccess() throws Exception {
         forceP2pEnabled(mClient1);
-        testAddLocalServiceSuccess();
+        sendChannelInfoUpdateMsg("testPkg1", "testFeature", mClient1, mClientMessenger);
+        verifyAddLocalService();
 
         sendSimpleMsg(mClientMessenger, WifiP2pManager.CLEAR_LOCAL_SERVICES);
         verify(mWifiNative, atLeastOnce()).p2pServiceDel(any(WifiP2pServiceInfo.class));
@@ -3737,16 +3934,35 @@
      * Verify the caller sends WifiP2pManager.ADD_SERVICE_REQUEST without services discover.
      */
     @Test
-    public void testAddServiceRequestSuccessWithoutServiceDiscover() throws Exception {
+    public void testAddServiceRequestNoOverflow() throws Exception {
         forceP2pEnabled(mClient1);
         sendChannelInfoUpdateMsg("testPkg1", "testFeature", mClient1, mClientMessenger);
 
-        sendAddServiceRequestMsg(mClientMessenger);
+        for (int i = 0; i < 256; i++) {
+            reset(mTestWifiP2pServiceRequest);
+            sendAddServiceRequestMsg(mClientMessenger);
+            ArgumentCaptor<Integer> idCaptor = ArgumentCaptor.forClass(int.class);
+            verify(mTestWifiP2pServiceRequest).setTransactionId(idCaptor.capture());
+            assertTrue(idCaptor.getValue().intValue() > 0);
+        }
+    }
 
+    private void verifyAddServiceRequest() throws Exception {
+        sendAddServiceRequestMsg(mClientMessenger);
         assertTrue(mClientHandler.hasMessages(WifiP2pManager.ADD_SERVICE_REQUEST_SUCCEEDED));
     }
 
     /**
+     * Verify the caller sends WifiP2pManager.ADD_SERVICE_REQUEST without services discover.
+     */
+    @Test
+    public void testAddServiceRequestSuccessWithoutServiceDiscover() throws Exception {
+        forceP2pEnabled(mClient1);
+        sendChannelInfoUpdateMsg("testPkg1", "testFeature", mClient1, mClientMessenger);
+        verifyAddServiceRequest();
+    }
+
+    /**
      * Verify the caller sends WifiP2pManager.ADD_SERVICE_REQUEST with services discover.
      */
     @Test
@@ -3819,7 +4035,8 @@
     @Test
     public void testRemoveServiceRequestSuccess() throws Exception {
         forceP2pEnabled(mClient1);
-        testAddServiceRequestSuccessWithoutServiceDiscover();
+        sendChannelInfoUpdateMsg("testPkg1", "testFeature", mClient1, mClientMessenger);
+        verifyAddServiceRequest();
 
         sendRemoveServiceRequestMsg(mClientMessenger, mTestWifiP2pServiceRequest);
 
@@ -3844,7 +4061,8 @@
     @Test
     public void testRemoveServiceRequestSuccessWithNullServiceInfo() throws Exception {
         forceP2pEnabled(mClient1);
-        testAddLocalServiceSuccess();
+        sendChannelInfoUpdateMsg("testPkg1", "testFeature", mClient1, mClientMessenger);
+        verifyAddLocalService();
 
         sendRemoveServiceRequestMsg(mClientMessenger, null);
 
@@ -3887,7 +4105,8 @@
     @Test
     public void testClearServiceRequestsSuccess() throws Exception {
         forceP2pEnabled(mClient1);
-        testAddServiceRequestSuccessWithoutServiceDiscover();
+        sendChannelInfoUpdateMsg("testPkg1", "testFeature", mClient1, mClientMessenger);
+        verifyAddServiceRequest();
 
         sendSimpleMsg(mClientMessenger, WifiP2pManager.CLEAR_SERVICE_REQUESTS);
 
@@ -4074,4 +4293,210 @@
         assertEquals(WifiP2pServiceImpl.DEFAULT_GROUP_OWNER_INTENT,
                 config.groupOwnerIntent);
     }
+
+    private List<CoexUnsafeChannel> setupCoexMock(int restrictionBits) {
+        assumeTrue(SdkLevel.isAtLeastS());
+        List<CoexUnsafeChannel> unsafeChannels = new ArrayList<>();
+        unsafeChannels.add(new CoexUnsafeChannel(WifiScanner.WIFI_BAND_24_GHZ, 1));
+        unsafeChannels.add(new CoexUnsafeChannel(WifiScanner.WIFI_BAND_24_GHZ, 2));
+        unsafeChannels.add(new CoexUnsafeChannel(WifiScanner.WIFI_BAND_24_GHZ, 3));
+        unsafeChannels.add(new CoexUnsafeChannel(WifiScanner.WIFI_BAND_5_GHZ, 36));
+        unsafeChannels.add(new CoexUnsafeChannel(WifiScanner.WIFI_BAND_5_GHZ, 40));
+        unsafeChannels.add(new CoexUnsafeChannel(WifiScanner.WIFI_BAND_5_GHZ, 165));
+        when(mCoexManager.getCoexRestrictions()).thenReturn(restrictionBits);
+        when(mCoexManager.getCoexUnsafeChannels()).thenReturn(unsafeChannels);
+        when(mWifiNative.p2pSetListenChannel(anyInt())).thenReturn(true);
+        when(mWifiNative.p2pSetOperatingChannel(anyInt(), any())).thenReturn(true);
+        return unsafeChannels;
+    }
+
+    /** Verify P2P unsafe channels are set if P2P bit presents in restriction bits. */
+    @Test
+    public void testCoexCallbackWithWifiP2pUnsafeChannels() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        setupCoexMock(0);
+        assertNotNull(mCoexListener);
+        forceP2pEnabled(mClient1);
+        mLooper.dispatchAll();
+
+        List<CoexUnsafeChannel> unsafeChannels =
+                setupCoexMock(WifiManager.COEX_RESTRICTION_WIFI_DIRECT);
+        mCoexListener.onCoexUnsafeChannelsChanged();
+        mLooper.dispatchAll();
+
+        // On entering P2pEnabledState, these are called once first.
+        verify(mWifiNative, times(2)).p2pSetListenChannel(eq(0));
+        ArgumentCaptor<List<CoexUnsafeChannel>> unsafeChannelsCaptor =
+                ArgumentCaptor.forClass(List.class);
+        verify(mWifiNative, times(2)).p2pSetOperatingChannel(eq(0), unsafeChannelsCaptor.capture());
+        List<List<CoexUnsafeChannel>> capturedUnsafeChannelsList =
+                unsafeChannelsCaptor.getAllValues();
+        // The second one is what we sent.
+        assertEquals(unsafeChannels, capturedUnsafeChannelsList.get(1));
+    }
+
+    /** Verify P2P unsafe channels are cleared if P2P bit does not present in restriction bits. */
+    @Test
+    public void testCoexCallbackWithoutWifiP2pInRestrictionBits() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        setupCoexMock(0);
+        assertNotNull(mCoexListener);
+        forceP2pEnabled(mClient1);
+        mLooper.dispatchAll();
+
+        mCoexListener.onCoexUnsafeChannelsChanged();
+        mLooper.dispatchAll();
+
+        // On entering P2pEnabledState, these are called once first.
+        verify(mWifiNative, times(2)).p2pSetListenChannel(eq(0));
+        ArgumentCaptor<List<CoexUnsafeChannel>> unsafeChannelsCaptor =
+                ArgumentCaptor.forClass(List.class);
+        verify(mWifiNative, times(2)).p2pSetOperatingChannel(eq(0), unsafeChannelsCaptor.capture());
+        List<List<CoexUnsafeChannel>> capturedUnsafeChannelsList =
+                unsafeChannelsCaptor.getAllValues();
+        // The second one is what we sent.
+        assertEquals(0, capturedUnsafeChannelsList.get(1).size());
+    }
+
+    /**
+     * Verify the caller sends WifiP2pManager.SET_WFD_INFO with wfd enabled
+     * and WFD R2 device info.
+     */
+    @Test
+    public void testSetWfdR2InfoSuccessWithWfdEnabled() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        // Move to enabled state
+        forceP2pEnabled(mClient1);
+        mTestThisDevice.status = mTestThisDevice.AVAILABLE;
+
+        mTestThisDevice.wfdInfo = new WifiP2pWfdInfo();
+        mTestThisDevice.wfdInfo.setEnabled(true);
+        mTestThisDevice.wfdInfo.setR2DeviceType(WifiP2pWfdInfo.DEVICE_TYPE_WFD_SOURCE);
+        when(mWifiInjector.getWifiPermissionsWrapper()).thenReturn(mWifiPermissionsWrapper);
+        when(mWifiPermissionsWrapper.getUidPermission(anyString(), anyInt()))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+        when(mWifiNative.setWfdEnable(anyBoolean())).thenReturn(true);
+        when(mWifiNative.setWfdDeviceInfo(anyString())).thenReturn(true);
+        when(mWifiNative.setWfdR2DeviceInfo(anyString())).thenReturn(true);
+        sendSetWfdInfoMsg(mClientMessenger, mTestThisDevice.wfdInfo);
+
+        verify(mWifiInjector).getWifiPermissionsWrapper();
+        verify(mWifiPermissionsWrapper).getUidPermission(
+                eq(android.Manifest.permission.CONFIGURE_WIFI_DISPLAY), anyInt());
+        verify(mWifiNative).setWfdEnable(eq(true));
+        verify(mWifiNative).setWfdDeviceInfo(eq(mTestThisDevice.wfdInfo.getDeviceInfoHex()));
+        verify(mWifiNative).setWfdR2DeviceInfo(eq(mTestThisDevice.wfdInfo.getR2DeviceInfoHex()));
+        checkSendThisDeviceChangedBroadcast();
+        verify(mClientHandler).sendMessage(mMessageCaptor.capture());
+        Message message = mMessageCaptor.getValue();
+        assertEquals(WifiP2pManager.SET_WFD_INFO_SUCCEEDED, message.what);
+    }
+
+    /**
+     * Verify WifiP2pManager.SET_WFD_INFO_FAILED is returned when wfd is enabled,
+     * WFD R2 device, and native call "setWfdR2DeviceInfo" failure.
+     */
+    @Test
+    public void testSetWfdR2InfoFailureWithWfdEnabledWhenNativeCallFailure2() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        // Move to enabled state
+        forceP2pEnabled(mClient1);
+        mTestThisDevice.status = mTestThisDevice.AVAILABLE;
+
+        mTestThisDevice.wfdInfo = new WifiP2pWfdInfo();
+        mTestThisDevice.wfdInfo.setEnabled(true);
+        mTestThisDevice.wfdInfo.setR2DeviceType(WifiP2pWfdInfo.DEVICE_TYPE_WFD_SOURCE);
+        when(mWifiInjector.getWifiPermissionsWrapper()).thenReturn(mWifiPermissionsWrapper);
+        when(mWifiPermissionsWrapper.getUidPermission(anyString(), anyInt()))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+        when(mWifiNative.setWfdEnable(anyBoolean())).thenReturn(true);
+        when(mWifiNative.setWfdDeviceInfo(anyString())).thenReturn(true);
+        when(mWifiNative.setWfdR2DeviceInfo(anyString())).thenReturn(false);
+        sendSetWfdInfoMsg(mClientMessenger, mTestThisDevice.wfdInfo);
+
+        verify(mWifiInjector).getWifiPermissionsWrapper();
+        verify(mWifiPermissionsWrapper).getUidPermission(
+                eq(android.Manifest.permission.CONFIGURE_WIFI_DISPLAY), anyInt());
+        verify(mWifiNative).setWfdEnable(eq(true));
+        verify(mWifiNative).setWfdDeviceInfo(eq(mTestThisDevice.wfdInfo.getDeviceInfoHex()));
+        verify(mWifiNative).setWfdR2DeviceInfo(eq(mTestThisDevice.wfdInfo.getR2DeviceInfoHex()));
+        verify(mClientHandler).sendMessage(mMessageCaptor.capture());
+        Message message = mMessageCaptor.getValue();
+        assertEquals(WifiP2pManager.SET_WFD_INFO_FAILED, message.what);
+        assertEquals(WifiP2pManager.ERROR, message.arg1);
+    }
+
+    /**
+     * Verify that P2P group is removed during group creating failure.
+     */
+    @Test
+    public void testGroupCreatingFailureDueToTethering() throws Exception {
+        when(mWifiNative.p2pGroupAdd(anyBoolean())).thenReturn(true);
+        when(mWifiNative.p2pGroupRemove(eq(IFACE_NAME_P2P))).thenReturn(true);
+        when(mWifiPermissionsUtil.checkCanAccessWifiDirect(eq("testPkg1"), eq("testFeature"),
+                anyInt(), anyBoolean())).thenReturn(true);
+
+        WifiP2pGroup group = new WifiP2pGroup();
+        group.setNetworkId(WifiP2pGroup.NETWORK_ID_PERSISTENT);
+        group.setNetworkName("DIRECT-xy-NEW");
+        group.setOwner(new WifiP2pDevice("thisDeviceMac"));
+        group.setIsGroupOwner(true);
+        group.setInterface(IFACE_NAME_P2P);
+
+        forceP2pEnabled(mClient1);
+        sendChannelInfoUpdateMsg("testPkg1", "testFeature", mClient1, mClientMessenger);
+        mLooper.dispatchAll();
+        sendCreateGroupMsg(mClientMessenger, WifiP2pGroup.NETWORK_ID_TEMPORARY, null);
+        mLooper.dispatchAll();
+
+        sendGroupStartedMsg(group);
+        mLooper.dispatchAll();
+
+        mLooper.moveTimeForward(120 * 1000 * 2);
+        mLooper.dispatchAll();
+
+        verify(mWifiNative).p2pGroupRemove(group.getInterface());
+    }
+
+    /**
+     * Verify the idle timer is cancelled after leaving inactive state.
+     */
+    @Test
+    public void testIdleTimeoutCancelledAfterLeavingInactiveState() throws Exception {
+        forceP2pEnabled(mClient1);
+        sendChannelInfoUpdateMsg("testPkg1", "testFeature", mClient1, mClientMessenger);
+
+        mockPeersList();
+        sendConnectMsg(mClientMessenger, mTestWifiP2pPeerConfig);
+        verify(mWifiPermissionsUtil)
+                .checkCanAccessWifiDirect(eq("testPkg1"), eq("testFeature"), anyInt(), eq(false));
+
+        ArgumentCaptor<WifiP2pConfig> configCaptor =
+                ArgumentCaptor.forClass(WifiP2pConfig.class);
+        verify(mWifiP2pMetrics).startConnectionEvent(
+                eq(P2pConnectionEvent.CONNECTION_FRESH),
+                configCaptor.capture());
+        assertEquals(mTestWifiP2pPeerConfig.toString(), configCaptor.getValue().toString());
+        // Verify timer is cannelled
+        // Includes re-schedule 4 times:
+        // 1. forceP2pEnabled(): enter InactiveState
+        // 2. forceP2pEnabled: REQUEST_P2P_STATE
+        // 3. CONNECT
+        // 4. leave InactiveState
+        verify(mAlarmManager, times(4)).setExact(anyInt(), anyLong(),
+                eq(mWifiP2pServiceImpl.P2P_IDLE_SHUTDOWN_MESSAGE_TIMEOUT_TAG), any(), any());
+        verify(mAlarmManager, times(4)).cancel(eq(mWifiP2pServiceImpl.mP2pIdleShutdownMessage));
+    }
+
+    /**
+     * Verify the interface down after idle timer is triggered.
+     */
+    @Test
+    public void testIdleTimeoutTriggered() throws Exception {
+        forceP2pEnabled(mClient1);
+        mWifiP2pServiceImpl.mP2pIdleShutdownMessage.onAlarm();
+        mLooper.dispatchAll();
+        verify(mWifiNative).teardownInterface();
+        verify(mWifiMonitor).stopMonitoring(anyString());
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/rtt/RttMetricsTest.java b/service/tests/wifitests/src/com/android/server/wifi/rtt/RttMetricsTest.java
index 9375856..2c2f892 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/rtt/RttMetricsTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/rtt/RttMetricsTest.java
@@ -586,16 +586,16 @@
 
     private RangingRequest getDummyRangingRequest(int countAp, int countAware) {
         RangingRequest.Builder builder = new RangingRequest.Builder();
-        byte[] dummyMacBase = {0x0, 0x1, 0x2, 0x3, 0x4, 0x5};
+        byte[] placeholderMacBase = {0x0, 0x1, 0x2, 0x3, 0x4, 0x5};
 
         for (int i = 0; i < countAp; ++i) {
-            dummyMacBase[0]++;
-            builder.addResponder(new ResponderConfig(MacAddress.fromBytes(dummyMacBase),
+            placeholderMacBase[0]++;
+            builder.addResponder(new ResponderConfig(MacAddress.fromBytes(placeholderMacBase),
                     ResponderConfig.RESPONDER_AP, true, 0, 0, 0, 0, 0));
         }
         for (int i = 0; i < countAware; ++i) {
-            dummyMacBase[0]++;
-            builder.addResponder(new ResponderConfig(MacAddress.fromBytes(dummyMacBase),
+            placeholderMacBase[0]++;
+            builder.addResponder(new ResponderConfig(MacAddress.fromBytes(placeholderMacBase),
                     ResponderConfig.RESPONDER_AWARE, true, 0, 0, 0, 0, 0));
         }
 
@@ -610,7 +610,7 @@
         for (ResponderConfig peer : request.mRttPeers) {
 
             RangingResult rttResult = new RangingResult(status, peer.macAddress,
-                    (int) (distance * 1000), 0, 0, 8, 8, null, null, null, 0);
+                    (int) (distance * 1000), 0, 0, 8, 8, null, null, null, 0, true);
             distance += incrDistanceM;
             rangingResults.add(rttResult);
         }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/rtt/RttNativeTest.java b/service/tests/wifitests/src/com/android/server/wifi/rtt/RttNativeTest.java
index d57857d..edf82c2 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/rtt/RttNativeTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/rtt/RttNativeTest.java
@@ -154,6 +154,8 @@
         collector.checkThat("entry 0: peer type", rttConfig.peer, equalTo(RttPeerType.AP));
         collector.checkThat("entry 0: lci", rttConfig.mustRequestLci, equalTo(true));
         collector.checkThat("entry 0: lcr", rttConfig.mustRequestLcr, equalTo(true));
+        collector.checkThat("entry 0: rtt burst size", rttConfig.numFramesPerBurst,
+                    equalTo(RangingRequest.getMaxRttBurstSize()));
 
         rttConfig = halRequest.get(1);
         collector.checkThat("entry 1: MAC", rttConfig.addr,
@@ -162,6 +164,8 @@
         collector.checkThat("entry 1: peer type", rttConfig.peer, equalTo(RttPeerType.AP));
         collector.checkThat("entry 1: lci", rttConfig.mustRequestLci, equalTo(true));
         collector.checkThat("entry 1: lcr", rttConfig.mustRequestLcr, equalTo(true));
+        collector.checkThat("entry 1: rtt burst size", rttConfig.numFramesPerBurst,
+                equalTo(RangingRequest.getMaxRttBurstSize()));
 
         rttConfig = halRequest.get(2);
         collector.checkThat("entry 2: MAC", rttConfig.addr,
@@ -170,48 +174,9 @@
         collector.checkThat("entry 2: peer type", rttConfig.peer, equalTo(RttPeerType.NAN));
         collector.checkThat("entry 2: lci", rttConfig.mustRequestLci, equalTo(false));
         collector.checkThat("entry 2: lcr", rttConfig.mustRequestLcr, equalTo(false));
+        collector.checkThat("entry 2: rtt burst size", rttConfig.numFramesPerBurst,
+                equalTo(RangingRequest.getMaxRttBurstSize()));
 
-        verifyNoMoreInteractions(mockRttController, mockRttServiceImpl);
-    }
-
-    /**
-     * Validate ranging request with a mix of Repsonders with and without IEEE 802.11mc support,
-     * from a non- privileged context.
-     */
-    @Test
-    public void testRangeRequestNotPrivilegedNo80211mcSupportMixed() throws Exception {
-        int cmdId = 66;
-
-        // the request has 3 responders: first AP support 802.11mc, second AP does not, third is
-        // Aware (which supports 802.11mc by default)
-        RangingRequest request = RttTestUtils.getDummyRangingRequest((byte) 0);
-
-        // (1) issue range request
-        mDut.rangeRequest(cmdId, request, false);
-
-        // (2) verify HAL call and parameters
-        verify(mockRttController).rangeRequest(eq(cmdId), mRttConfigCaptor.capture());
-
-        // verify contents of HAL request (hard codes knowledge from getDummyRangingRequest()).
-        ArrayList<RttConfig> halRequest = mRttConfigCaptor.getValue();
-
-        collector.checkThat("number of entries", halRequest.size(), equalTo(2));
-
-        RttConfig rttConfig = halRequest.get(0);
-        collector.checkThat("entry 0: MAC", rttConfig.addr,
-                equalTo(MacAddress.fromString("00:01:02:03:04:00").toByteArray()));
-        collector.checkThat("entry 0: rtt type", rttConfig.type, equalTo(RttType.TWO_SIDED));
-        collector.checkThat("entry 0: peer type", rttConfig.peer, equalTo(RttPeerType.AP));
-        collector.checkThat("entry 0: lci", rttConfig.mustRequestLci, equalTo(true));
-        collector.checkThat("entry 0: lcr", rttConfig.mustRequestLcr, equalTo(true));
-
-        rttConfig = halRequest.get(1);
-        collector.checkThat("entry 1: MAC", rttConfig.addr,
-                equalTo(MacAddress.fromString("08:09:08:07:06:05").toByteArray()));
-        collector.checkThat("entry 1: rtt type", rttConfig.type, equalTo(RttType.TWO_SIDED));
-        collector.checkThat("entry 1: peer type", rttConfig.peer, equalTo(RttPeerType.NAN));
-        collector.checkThat("entry 1: lci", rttConfig.mustRequestLci, equalTo(false));
-        collector.checkThat("entry 1: lcr", rttConfig.mustRequestLcr, equalTo(false));
 
         verifyNoMoreInteractions(mockRttController, mockRttServiceImpl);
     }
@@ -316,26 +281,6 @@
     }
 
     /**
-     * Validate ranging request with all Repsonders without IEEE 802.11mc support, from a non-
-     * privileged context.
-     */
-    @Test
-    public void testRangeRequestNotPrivilegedNo80211mcSupportForAny() throws Exception {
-        int cmdId = 77;
-        RangingRequest request = RttTestUtils.getDummyRangingRequestNo80211mcSupport((byte) 0);
-
-        // (1) issue range request
-        mDut.rangeRequest(cmdId, request, false);
-
-        // (2) verify immediate result callback (empty result set)
-        verify(mockRttServiceImpl).onRangingResults(eq(cmdId), mRttResultCaptor.capture());
-
-        collector.checkThat("Result set", mRttResultCaptor.getValue().size(), equalTo(0));
-
-        verifyNoMoreInteractions(mockRttController, mockRttServiceImpl);
-    }
-
-    /**
      * Validate no range request when Wi-Fi is down
      */
     @Test
diff --git a/service/tests/wifitests/src/com/android/server/wifi/rtt/RttServiceImplTest.java b/service/tests/wifitests/src/com/android/server/wifi/rtt/RttServiceImplTest.java
index 687c24f..09522a6 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/rtt/RttServiceImplTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/rtt/RttServiceImplTest.java
@@ -763,12 +763,12 @@
         RangingResult removed = results.second.remove(1);
         results.second.add(
                 new RangingResult(RangingResult.STATUS_FAIL, removed.getMacAddress(), 0, 0, 0, 0, 0,
-                        null, null, null, 0));
+                        null, null, null, 0, false));
         results.first.remove(0); // remove an AP request
         removed = results.second.remove(0);
         results.second.add(
                 new RangingResult(RangingResult.STATUS_FAIL, removed.getMacAddress(), 0, 0, 0, 0, 0,
-                        null, null, null, 0));
+                        null, null, null, 0, false));
 
         // (1) request ranging operation
         mDut.startRanging(mockIbinder, mPackageName, mFeatureId, null, request, mockCallback);
@@ -810,7 +810,7 @@
         for (RangingResult result : results.second) {
             allFailResults.add(
                     new RangingResult(RangingResult.STATUS_FAIL, result.getMacAddress(), 0, 0, 0, 0,
-                            0, null, null, null, 0));
+                            0, null, null, null, 0, false));
         }
 
         // (1) request ranging operation
@@ -841,59 +841,6 @@
     }
 
     /**
-     * Validate that when the HAL returns results with "missing" entries (i.e. some requests
-     * don't get results) AND these correspond to peers which do not support 802.11mc AND the
-     * request is from a non-privileged context: they are filled-in with FAILED results.
-     */
-    @Test
-    public void testMissingResultsForNonSupportOf80211mc() throws Exception {
-        RangingRequest request = RttTestUtils.getDummyRangingRequest((byte) 0);
-        Pair<List<RangingResult>, List<RangingResult>> results =
-                RttTestUtils.getDummyRangingResults(request);
-        results.first.remove(1); // remove the entry which doesn't support 802.11mc
-        RangingResult removed = results.second.remove(1);
-        results.second.add(
-                new RangingResult(RangingResult.STATUS_RESPONDER_DOES_NOT_SUPPORT_IEEE80211MC,
-                        removed.getMacAddress(), 0, 0, 0, 0, 0, null, null, null, 0));
-        results.first.remove(
-                0); // remove an AP request (i.e. test combo of missing for different reasons)
-        removed = results.second.remove(0);
-        results.second.add(
-                new RangingResult(RangingResult.STATUS_FAIL, removed.getMacAddress(), 0, 0, 0, 0, 0,
-                        null, null, null, 0));
-
-        when(mockContext.checkCallingOrSelfPermission(
-                android.Manifest.permission.LOCATION_HARDWARE)).thenReturn(
-                PackageManager.PERMISSION_DENIED);
-
-        // (1) request ranging operation
-        mDut.startRanging(mockIbinder, mPackageName, mFeatureId, null, request, mockCallback);
-        mMockLooper.dispatchAll();
-
-        // (2) verify that request issued to native
-        verify(mockNative).rangeRequest(mIntCaptor.capture(), eq(request), eq(false));
-        verifyWakeupSet(true, 0);
-
-        // (3) return results with missing entries
-        mDut.onRangingResults(mIntCaptor.getValue(), results.second);
-        mMockLooper.dispatchAll();
-
-        // (5) verify that (full) results dispatched
-        verify(mockCallback).onRangingResults(mListCaptor.capture());
-        assertTrue(compareListContentsNoOrdering(results.second, mListCaptor.getValue()));
-        verifyWakeupCancelled();
-
-        // verify metrics
-        verify(mockMetrics).recordRequest(eq(mDefaultWs), eq(request));
-        verify(mockMetrics).recordResult(eq(request), eq(results.second), anyInt());
-        verify(mockMetrics).recordOverallStatus(WifiMetricsProto.WifiRttLog.OVERALL_SUCCESS);
-
-        verify(mockNative, atLeastOnce()).isReady();
-        verifyNoMoreInteractions(mockNative, mockMetrics, mockCallback,
-                mAlarmManager.getAlarmManager());
-    }
-
-    /**
      * Validate that when the HAL times out we fail, clean-up the queue and move to the next
      * request.
      */
diff --git a/service/tests/wifitests/src/com/android/server/wifi/rtt/RttTestUtils.java b/service/tests/wifitests/src/com/android/server/wifi/rtt/RttTestUtils.java
index a112a7c..7924fed 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/rtt/RttTestUtils.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/rtt/RttTestUtils.java
@@ -51,7 +51,7 @@
     }
 
     /**
-     * Returns a dummy ranging request with 3 requests:
+     * Returns a placeholder ranging request with 3 requests and a non-default in-range burst size:
      * - First: 802.11mc capable
      * - Second: 802.11mc not capable
      * - Third: Aware peer
@@ -69,14 +69,15 @@
         MacAddress mac1 = MacAddress.fromString("08:09:08:07:06:05");
 
         builder.addAccessPoint(scan1);
-        builder.addAccessPoint(scan2);
+        builder.addNon80211mcCapableAccessPoint(scan2);
+        // Changing default RTT burst size to a valid, but maximum, value
+        builder.setRttBurstSize(RangingRequest.getMaxRttBurstSize());
         builder.addWifiAwarePeer(mac1);
-
         return builder.build();
     }
 
     /**
-     * Returns a dummy ranging request with 2 requests:
+     * Returns a placeholder ranging request with 2 requests:
      * - First: 802.11mc capable
      */
     public static RangingRequest getDummyRangingRequestMcOnly(byte lastMacByte) {
@@ -93,7 +94,7 @@
     }
 
     /**
-     * Returns a dummy ranging request with 2 requests - neither of which support 802.11mc.
+     * Returns a placeholder ranging request with 2 requests - neither of which support 802.11mc.
      */
     public static RangingRequest getDummyRangingRequestNo80211mcSupport(byte lastMacByte) {
         RangingRequest.Builder builder = new RangingRequest.Builder();
@@ -103,14 +104,14 @@
         ScanResult scan2 = new ScanResult();
         scan2.BSSID = "0A:0B:0C:0D:0E:" + String.format("%02d", lastMacByte);
 
-        builder.addAccessPoint(scan1);
-        builder.addAccessPoint(scan2);
+        builder.addNon80211mcCapableAccessPoint(scan1);
+        builder.addNon80211mcCapableAccessPoint(scan2);
 
         return builder.build();
     }
 
     /**
-     * Returns a matched set of dummy ranging results: HAL RttResult and the public API
+     * Returns a matched set of placeholder ranging results: HAL RttResult and the public API
      * RangingResult.
      *
      * @param request If non-null will be used as a template (BSSID) for the range results.
@@ -125,15 +126,15 @@
         List<RangingResult> results = new ArrayList<>();
 
         if (request != null) {
-            for (ResponderConfig peer: request.mRttPeers) {
+            for (ResponderConfig peer : request.mRttPeers) {
                 RangingResult rangingResult;
                 halResults.add(new RangingResult(RangingResult.STATUS_SUCCESS,
                         peer.macAddress, rangeCmBase, rangeStdDevCmBase, rssiBase,
-                        8, 5, null, null, null, rangeTimestampBase));
+                        8, 5, null, null, null, rangeTimestampBase, true));
                 if (peer.peerHandle == null) {
                     rangingResult = new RangingResult(RangingResult.STATUS_SUCCESS,
                             peer.macAddress, rangeCmBase++, rangeStdDevCmBase++, rssiBase++,
-                            8, 5, null, null, null, rangeTimestampBase++);
+                            8, 5, null, null, null, rangeTimestampBase++, true);
                 } else {
                     rangingResult = new RangingResult(RangingResult.STATUS_SUCCESS,
                             peer.peerHandle, rangeCmBase++, rangeStdDevCmBase++, rssiBase++,
@@ -146,15 +147,15 @@
             results.add(new RangingResult(RangingResult.STATUS_SUCCESS,
                     MacAddress.fromString("10:01:02:03:04:05"), rangeCmBase++,
                     rangeStdDevCmBase++, rssiBase++, 8, 4, null, null,
-                    null, rangeTimestampBase++));
+                    null, rangeTimestampBase++, true));
             results.add(new RangingResult(RangingResult.STATUS_SUCCESS,
                     MacAddress.fromString("1A:0B:0C:0D:0E:0F"), rangeCmBase++,
                     rangeStdDevCmBase++, rssiBase++, 9, 3, null, null,
-                    null, rangeTimestampBase++));
+                    null, rangeTimestampBase++, true));
             results.add(new RangingResult(RangingResult.STATUS_SUCCESS,
                     MacAddress.fromString("08:09:08:07:06:05"), rangeCmBase++,
                     rangeStdDevCmBase++, rssiBase++, 10, 2, null, null,
-                    null, rangeTimestampBase++));
+                    null, rangeTimestampBase++, true));
             halResults.addAll(results);
         }
 
diff --git a/service/tests/wifitests/src/com/android/server/wifi/scanner/BackgroundScanSchedulerTest.java b/service/tests/wifitests/src/com/android/server/wifi/scanner/BackgroundScanSchedulerTest.java
index 54e0c69..ac15859 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/scanner/BackgroundScanSchedulerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/scanner/BackgroundScanSchedulerTest.java
@@ -67,7 +67,8 @@
                 new int[]{2400, 2450},
                 new int[]{5150, 5175},
                 new int[]{5600, 5650, 5660},
-                new int[]{5945, 5985});
+                new int[]{5945, 5985},
+                new int[]{58320, 60480});
         mScheduler = new BackgroundScanScheduler(mChannelHelper);
         mScheduler.setMaxBuckets(DEFAULT_MAX_BUCKETS);
         mScheduler.setMaxChannelsPerBucket(DEFAULT_MAX_CHANNELS_PER_BUCKET);
@@ -566,7 +567,7 @@
                 WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
         requests.add(createRequest(channelsToSpec(2400, 2450, 5175), 30000, 0, 20,
                 WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
-        requests.add(createRequest(WifiScanner.WIFI_BAND_BOTH_WITH_DFS, 240000, 0, 20,
+        requests.add(createRequest(WifiScanner.WIFI_BAND_24_5_WITH_DFS_6_60_GHZ, 240000, 0, 20,
                 WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
 
         mScheduler.setMaxBuckets(3);
@@ -593,7 +594,7 @@
         assertBucketChannels(schedule.buckets[1], expectedBucketChannelSet);
 
         KnownBandsChannelCollection collection = mChannelHelper.createChannelCollection();
-        collection.addBand(WifiScanner.WIFI_BAND_BOTH_WITH_DFS);
+        collection.addBand(WifiScanner.WIFI_BAND_24_5_WITH_DFS_6_60_GHZ);
         expectedBucketChannelSet = collection.getAllChannels();
         expectedBucketChannelSet.remove(5175);
         expectedBucketChannelSet.remove(2400);
@@ -709,7 +710,8 @@
             } else {
                 assertTrue("Invalid band: " + schedule.buckets[i].band,
                         schedule.buckets[i].band > WifiScanner.WIFI_BAND_UNSPECIFIED
-                        && schedule.buckets[i].band <= WifiScanner.WIFI_BAND_BOTH_WITH_DFS);
+                        && schedule.buckets[i].band
+                                <= WifiScanner.WIFI_BAND_24_5_WITH_DFS_6_60_GHZ);
             }
         }
     }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/scanner/BaseWifiScannerImplTest.java b/service/tests/wifitests/src/com/android/server/wifi/scanner/BaseWifiScannerImplTest.java
index 10f475c..6a8239c 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/scanner/BaseWifiScannerImplTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/scanner/BaseWifiScannerImplTest.java
@@ -85,13 +85,12 @@
         mWifiMonitor = new MockWifiMonitor();
         mResources = new MockResources();
 
-        when(mWifiNative.getClientInterfaceName()).thenReturn(IFACE_NAME);
-
         when(mContext.getSystemService(Context.ALARM_SERVICE))
                 .thenReturn(mAlarmManager.getAlarmManager());
 
         when(mContext.getResources()).thenReturn(mResources);
         when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime());
+        when(mClock.getElapsedSinceBootNanos()).thenReturn(SystemClock.elapsedRealtimeNanos());
     }
 
     protected Set<Integer> expectedBandScanFreqs(int band) {
@@ -122,7 +121,7 @@
                 expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ),
                 new ArrayList<String>(),
                 ScanResults.create(0, WifiScanner.WIFI_BAND_24_GHZ,
-                        2400, 2450, 2450, 2400, 2450, 2450, 2400, 2450, 2450), false);
+                        2400, 2450, 2450, 2400, 2450, 2450, 2400, 2450, 2450), false, false);
     }
 
     @Test
@@ -131,12 +130,13 @@
                 .withBasePeriod(10000)
                 .withMaxApPerScan(10)
                 .addBucketWithChannels(20000, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN, 5650)
+                .withEnable6GhzRnr(true)
                 .build();
 
         doSuccessfulSingleScanTest(settings, createFreqSet(5650),
                 new ArrayList<String>(),
                 ScanResults.create(0, WifiScanner.WIFI_BAND_UNSPECIFIED,
-                        5650, 5650, 5650, 5650, 5650, 5650, 5650, 5650), false);
+                        5650, 5650, 5650, 5650, 5650, 5650, 5650, 5650), false, true);
     }
 
     @Test
@@ -151,7 +151,7 @@
         doSuccessfulSingleScanTest(settings, createFreqSet(5650),
                 new ArrayList<String>(),
                 ScanResults.create(0, WifiScanner.WIFI_BAND_UNSPECIFIED,
-                        5650, 5650, 5650, 5650, 5650, 5650, 5650, 5650), false);
+                        5650, 5650, 5650, 5650, 5650, 5650, 5650, 5650), false, false);
     }
 
     @Test
@@ -168,7 +168,7 @@
         doSuccessfulSingleScanTest(settings, expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ),
                 new ArrayList<String>(),
                 ScanResults.create(0, WifiScanner.WIFI_BAND_24_GHZ,
-                        2400, 2450, 2450, 2400, 2450, 2450, 2400, 2450, 2450), true);
+                        2400, 2450, 2450, 2400, 2450, 2450, 2400, 2450, 2450), true, false);
     }
 
     /**
@@ -192,7 +192,7 @@
         doSuccessfulSingleScanTest(settings, createFreqSet(5650),
                 hiddenNetworkSSIDSet,
                 ScanResults.create(0, WifiScanner.WIFI_BAND_UNSPECIFIED,
-                        5650, 5650, 5650, 5650, 5650, 5650, 5650, 5650), false);
+                        5650, 5650, 5650, 5650, 5650, 5650, 5650, 5650), false, false);
     }
 
     /**
@@ -221,7 +221,7 @@
         doSuccessfulSingleScanTest(settings, createFreqSet(5650),
                 hiddenNetworkSSIDSet,
                 ScanResults.create(0, WifiScanner.WIFI_BAND_UNSPECIFIED,
-                        5650, 5650, 5650, 5650, 5650, 5650, 5650), false);
+                        5650, 5650, 5650, 5650, 5650, 5650, 5650), false, false);
     }
 
     @Test
@@ -243,7 +243,8 @@
         WifiNative.ScanEventHandler eventHandler2 = mock(WifiNative.ScanEventHandler.class);
 
         // scan start succeeds
-        when(mWifiNative.scan(eq(IFACE_NAME), anyInt(), any(), any(List.class))).thenReturn(true);
+        when(mWifiNative.scan(eq(IFACE_NAME), anyInt(), any(), any(List.class), anyBoolean()))
+                .thenReturn(true);
 
         assertTrue(mScanner.startSingleScan(settings, eventHandler));
         assertFalse("second scan while first scan running should fail immediately",
@@ -264,7 +265,8 @@
         InOrder order = inOrder(eventHandler, mWifiNative);
 
         // scan fails
-        when(mWifiNative.scan(eq(IFACE_NAME), anyInt(), any(), any(List.class))).thenReturn(false);
+        when(mWifiNative.scan(eq(IFACE_NAME), anyInt(), any(), any(List.class), anyBoolean()))
+                .thenReturn(false);
 
         // start scan
         assertTrue(mScanner.startSingleScan(settings, eventHandler));
@@ -292,7 +294,8 @@
         InOrder order = inOrder(eventHandler, mWifiNative);
 
         // scan succeeds
-        when(mWifiNative.scan(eq(IFACE_NAME), anyInt(), any(), any(List.class))).thenReturn(true);
+        when(mWifiNative.scan(eq(IFACE_NAME), anyInt(), any(), any(List.class), anyBoolean()))
+                .thenReturn(true);
 
         // start scan
         assertTrue(mScanner.startSingleScan(settings, eventHandler));
@@ -324,7 +327,8 @@
         InOrder order = inOrder(eventHandler, mWifiNative);
 
         // scan succeeds
-        when(mWifiNative.scan(eq(IFACE_NAME), anyInt(), any(), any(List.class))).thenReturn(true);
+        when(mWifiNative.scan(eq(IFACE_NAME), anyInt(), any(), any(List.class), anyBoolean()))
+                .thenReturn(true);
 
         // start scan
         assertTrue(mScanner.startSingleScan(settings, eventHandler));
@@ -372,14 +376,15 @@
                 .withBasePeriod(10000)
                 .withMaxApPerScan(10)
                 .addBucketWithBand(10000, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN,
-                        WifiScanner.WIFI_BAND_BOTH_WITH_DFS)
+                        WifiScanner.WIFI_BAND_24_5_WITH_DFS_6_60_GHZ)
                 .build();
 
         WifiNative.ScanEventHandler eventHandler = mock(WifiNative.ScanEventHandler.class);
         InOrder order = inOrder(eventHandler, mWifiNative);
 
         // scans succeed
-        when(mWifiNative.scan(eq(IFACE_NAME), anyInt(), any(), any(List.class))).thenReturn(true);
+        when(mWifiNative.scan(eq(IFACE_NAME), anyInt(), any(), any(List.class), anyBoolean()))
+                .thenReturn(true);
 
         // start first scan
         assertTrue(mScanner.startSingleScan(settings, eventHandler));
@@ -388,16 +393,16 @@
                 expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ),
                 new ArrayList<String>(),
                 ScanResults.create(0, WifiScanner.WIFI_BAND_24_GHZ,
-                        2400, 2450, 2450), false);
+                        2400, 2450, 2450), false, false);
 
         // start second scan
         assertTrue(mScanner.startSingleScan(settings2, eventHandler));
 
         expectSuccessfulSingleScan(order, WifiScanner.SCAN_TYPE_LOW_POWER, eventHandler,
-                expectedBandScanFreqs(WifiScanner.WIFI_BAND_BOTH_WITH_DFS),
+                expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_5_WITH_DFS_6_60_GHZ),
                 new ArrayList<String>(),
-                ScanResults.create(0, WifiScanner.WIFI_BAND_BOTH_WITH_DFS,
-                        5150, 5175), false);
+                ScanResults.create(0, WifiScanner.WIFI_BAND_24_5_WITH_DFS_6_60_GHZ,
+                        5150, 5175), false, false);
 
         verifyNoMoreInteractions(eventHandler);
     }
@@ -417,24 +422,28 @@
                         WifiScanner.WIFI_BAND_24_GHZ)
                 .build();
 
-        long approxScanStartUs = mClock.getElapsedSinceBootMillis() * 1000;
+        long approxScanStartNanos = mClock.getElapsedSinceBootNanos();
         ArrayList<ScanDetail> rawResults = new ArrayList<>(Arrays.asList(
-                        new ScanDetail(WifiSsid.createFromAsciiEncoded("TEST AP 1"),
-                                "00:00:00:00:00:00", "", -70, 2450,
-                                approxScanStartUs + 2000 * 1000, 0),
-                        new ScanDetail(WifiSsid.createFromAsciiEncoded("TEST AP 2"),
-                                "AA:BB:CC:DD:EE:FF", "", -66, 2400,
-                                approxScanStartUs + 2500 * 1000, 0),
-                        new ScanDetail(WifiSsid.createFromAsciiEncoded("TEST AP 3"),
-                                "00:00:00:00:00:00", "", -80, 2450,
-                                approxScanStartUs - 2000 * 1000, 0), // old result will be filtered
-                        new ScanDetail(WifiSsid.createFromAsciiEncoded("TEST AP 4"),
-                                "AA:BB:CC:11:22:33", "", -65, 2450,
-                                approxScanStartUs + 4000 * 1000, 0)));
+                new ScanDetail(WifiSsid.createFromAsciiEncoded("TEST AP 1"),
+                        "00:00:00:00:00:00", "", -70, 2450,
+                        approxScanStartNanos / 1_000 + 2000 * 1000, 0),
+                new ScanDetail(WifiSsid.createFromAsciiEncoded("TEST AP 2"),
+                        "AA:BB:CC:DD:EE:FF", "", -66, 2400,
+                        approxScanStartNanos / 1_000 + 2500 * 1000, 0),
+                // old result will be filtered
+                new ScanDetail(WifiSsid.createFromAsciiEncoded("TEST AP 3"),
+                        "00:00:00:00:00:00", "", -80, 2450,
+                        approxScanStartNanos / 1_0000 - 2000 * 1000, 0),
+                new ScanDetail(WifiSsid.createFromAsciiEncoded("TEST AP 4"),
+                        "00:00:00:00:00:00", "", -80, 2450,
+                        approxScanStartNanos / 1_000 + 200 , 0),
+                new ScanDetail(WifiSsid.createFromAsciiEncoded("TEST AP 5"),
+                        "AA:BB:CC:11:22:33", "", -65, 2450,
+                        approxScanStartNanos / 1_000 + 4000 * 1000, 0)));
 
         ArrayList<ScanResult> fullResults = new ArrayList<>();
         for (ScanDetail detail : rawResults) {
-            if (detail.getScanResult().timestamp > approxScanStartUs) {
+            if (detail.getScanResult().timestamp * 1_0000 > approxScanStartNanos) {
                 fullResults.add(detail.getScanResult());
             }
         }
@@ -452,12 +461,14 @@
         InOrder order = inOrder(eventHandler, mWifiNative);
 
         // scan succeeds
-        when(mWifiNative.scan(eq(IFACE_NAME), anyInt(), any(), any(List.class))).thenReturn(true);
+        when(mWifiNative.scan(eq(IFACE_NAME), anyInt(), any(), any(List.class), anyBoolean()))
+                .thenReturn(true);
 
         // start scan
         assertTrue(mScanner.startSingleScan(settings, eventHandler));
 
-        order.verify(mWifiNative).scan(eq(IFACE_NAME), anyInt(), eq(expectedScan), any(List.class));
+        order.verify(mWifiNative).scan(eq(IFACE_NAME), anyInt(), eq(expectedScan), any(List.class),
+                anyBoolean());
 
         when(mWifiNative.getScanResults(eq(IFACE_NAME))).thenReturn(rawResults);
 
@@ -498,28 +509,31 @@
 
     protected void doSuccessfulSingleScanTest(WifiNative.ScanSettings settings,
             Set<Integer> expectedScan, List<String> expectedHiddenNetSSIDs, ScanResults results,
-            boolean expectFullResults) {
+            boolean expectFullResults, boolean expectRnr) {
         WifiNative.ScanEventHandler eventHandler = mock(WifiNative.ScanEventHandler.class);
 
         InOrder order = inOrder(eventHandler, mWifiNative);
 
         // scan succeeds
-        when(mWifiNative.scan(eq(IFACE_NAME), anyInt(), any(), any(List.class))).thenReturn(true);
+        when(mWifiNative.scan(eq(IFACE_NAME), anyInt(), any(), any(List.class), anyBoolean()))
+                .thenReturn(true);
 
         // start scan
         assertTrue(mScanner.startSingleScan(settings, eventHandler));
 
         expectSuccessfulSingleScan(order, settings.scanType, eventHandler, expectedScan,
-                expectedHiddenNetSSIDs, results, expectFullResults);
+                expectedHiddenNetSSIDs, results, expectFullResults, expectRnr);
 
         verifyNoMoreInteractions(eventHandler);
     }
 
     protected void expectSuccessfulSingleScan(InOrder order,
             int scanType, WifiNative.ScanEventHandler eventHandler, Set<Integer> expectedScan,
-            List<String> expectedHiddenNetSSIDs, ScanResults results, boolean expectFullResults) {
+            List<String> expectedHiddenNetSSIDs, ScanResults results, boolean expectFullResults,
+            boolean expectRnr) {
         order.verify(mWifiNative).scan(
-                eq(IFACE_NAME), eq(scanType), eq(expectedScan), eq(expectedHiddenNetSSIDs));
+                eq(IFACE_NAME), eq(scanType), eq(expectedScan), eq(expectedHiddenNetSSIDs),
+                eq(expectRnr));
 
         when(mWifiNative.getScanResults(
                 eq(IFACE_NAME))).thenReturn(results.getScanDetailArrayList());
diff --git a/service/tests/wifitests/src/com/android/server/wifi/scanner/ChannelHelperTest.java b/service/tests/wifitests/src/com/android/server/wifi/scanner/ChannelHelperTest.java
index 18c00ba..ce9a761 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/scanner/ChannelHelperTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/scanner/ChannelHelperTest.java
@@ -90,29 +90,33 @@
                     10000, 0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
             assertEquals("[2400,5100]", ChannelHelper.toString(scanSettings));
         }
+    }
 
-        /**
-         * Unit tests for
-         * {@link com.android.server.wifi.scanner.ChannelHelper#bandToString}.
-         */
-        @Test
-        public void bandToString_ShouldReturnApproapriateString() {
-            assertEquals("unspecified", ChannelHelper.bandToString(
-                    WifiScanner.WIFI_BAND_UNSPECIFIED));
-            assertEquals("24Ghz", ChannelHelper.bandToString(WifiScanner.WIFI_BAND_24_GHZ));
-            assertEquals("5Ghz (no DFS)", ChannelHelper.bandToString(WifiScanner.WIFI_BAND_5_GHZ));
-            assertEquals("5Ghz (DFS only)", ChannelHelper.bandToString(
-                    WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY));
-            assertEquals("5Ghz (DFS incl)", ChannelHelper.bandToString(
-                    WifiScanner.WIFI_BAND_5_GHZ_WITH_DFS));
-            assertEquals("24Ghz & 5Ghz (no DFS)", ChannelHelper.bandToString(
-                    WifiScanner.WIFI_BAND_BOTH));
-            assertEquals("24Ghz & 5Ghz (DFS incl)", ChannelHelper.bandToString(
-                    WifiScanner.WIFI_BAND_BOTH_WITH_DFS));
-            assertEquals("24Ghz & 5Ghz (DFS incl) & 6Ghz", ChannelHelper.bandToString(
-                    WifiScanner.WIFI_BAND_24_5_WITH_DFS_6_GHZ));
-            assertEquals("Invalid band", ChannelHelper.bandToString(-235342));
-        }
+    /**
+     * Unit tests for
+     * {@link com.android.server.wifi.scanner.ChannelHelper#bandToString}.
+     */
+    @Test
+    public void bandToString_ShouldReturnApproapriateString() {
+        assertEquals("unspecified", ChannelHelper.bandToString(
+                WifiScanner.WIFI_BAND_UNSPECIFIED));
+        assertEquals("24Ghz", ChannelHelper.bandToString(WifiScanner.WIFI_BAND_24_GHZ));
+        assertEquals("5Ghz (no DFS)", ChannelHelper.bandToString(WifiScanner.WIFI_BAND_5_GHZ));
+        assertEquals("5Ghz (DFS only)", ChannelHelper.bandToString(
+                WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY));
+        assertEquals("5Ghz (DFS incl)", ChannelHelper.bandToString(
+                WifiScanner.WIFI_BAND_5_GHZ_WITH_DFS));
+        assertEquals("24Ghz & 5Ghz (no DFS)", ChannelHelper.bandToString(
+                WifiScanner.WIFI_BAND_BOTH));
+        assertEquals("24Ghz & 5Ghz (DFS incl)", ChannelHelper.bandToString(
+                WifiScanner.WIFI_BAND_BOTH_WITH_DFS));
+        assertEquals("24Ghz & 5Ghz (DFS incl) & 6Ghz", ChannelHelper.bandToString(
+                WifiScanner.WIFI_BAND_24_5_WITH_DFS_6_GHZ));
+        assertEquals("2.4 GHz & 5 GHz (no DFS) & 6GHz & 60GHz", ChannelHelper.bandToString(
+                WifiScanner.WIFI_BAND_24_5_6_60_GHZ));
+        assertEquals("2.4 GHz & 5Ghz (DFS incl) & 6GHz & 60GHz", ChannelHelper.bandToString(
+                WifiScanner.WIFI_BAND_24_5_WITH_DFS_6_60_GHZ));
+        assertEquals("invalid band", ChannelHelper.bandToString(-235342));
     }
 
     /**
diff --git a/service/tests/wifitests/src/com/android/server/wifi/scanner/HalWifiScannerTest.java b/service/tests/wifitests/src/com/android/server/wifi/scanner/HalWifiScannerTest.java
index 7cc0367..af1b736 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/scanner/HalWifiScannerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/scanner/HalWifiScannerTest.java
@@ -34,7 +34,8 @@
                 new int[]{2400, 2450},
                 new int[]{5150, 5175},
                 new int[]{5600, 5650},
-                new int[]{5945, 5985});
+                new int[]{5945, 5985},
+                new int[]{58320, 60480});
         mScanner = new HalWifiScannerImpl(mContext, BaseWifiScannerImplTest.IFACE_NAME,
                 mWifiNative, mWifiMonitor, mLooper.getLooper(), mClock);
     }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/scanner/KnownBandsChannelHelperTest.java b/service/tests/wifitests/src/com/android/server/wifi/scanner/KnownBandsChannelHelperTest.java
index 3e7ad71..760170f 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/scanner/KnownBandsChannelHelperTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/scanner/KnownBandsChannelHelperTest.java
@@ -54,6 +54,7 @@
     private static final int[] CHANNELS_DFS = new int[]{5600, 5650, 5660};
     private static final int[] CHANNELS_DFS_OTHER = new int[]{5600, 5650, 5660, 5680};
     private static final int[] CHANNELS_6_GHZ = new int[]{5945, 5985};
+    private static final int[] CHANNELS_60_GHZ = new int[]{58320, 60480};
 
     /**
      * Unit tests for
@@ -73,7 +74,8 @@
                     CHANNELS_24_GHZ,
                     CHANNELS_5_GHZ,
                     CHANNELS_DFS,
-                    CHANNELS_6_GHZ);
+                    CHANNELS_6_GHZ,
+                    CHANNELS_60_GHZ);
         }
 
         /**
@@ -119,7 +121,8 @@
                     CHANNELS_24_GHZ,
                     CHANNELS_5_GHZ,
                     CHANNELS_DFS,
-                    CHANNELS_6_GHZ);
+                    CHANNELS_6_GHZ,
+                    CHANNELS_60_GHZ);
         }
 
         private void testBand(int[] expectedChannels, int band) {
@@ -181,14 +184,22 @@
         @Test
         public void channelsAll() {
             int[] expectedChannels =
-                    new int[CHANNELS_24_GHZ.length + CHANNELS_5_GHZ.length + CHANNELS_DFS.length];
+                    new int[CHANNELS_24_GHZ.length + CHANNELS_5_GHZ.length + CHANNELS_DFS.length
+                            + CHANNELS_6_GHZ.length + CHANNELS_60_GHZ.length];
             System.arraycopy(CHANNELS_24_GHZ, 0, expectedChannels, 0, CHANNELS_24_GHZ.length);
             System.arraycopy(CHANNELS_5_GHZ, 0, expectedChannels, CHANNELS_24_GHZ.length,
                     CHANNELS_5_GHZ.length);
             System.arraycopy(CHANNELS_DFS, 0, expectedChannels,
                     CHANNELS_24_GHZ.length + CHANNELS_5_GHZ.length,
                     CHANNELS_DFS.length);
-            testBand(expectedChannels, WifiScanner.WIFI_BAND_BOTH_WITH_DFS);
+            System.arraycopy(CHANNELS_6_GHZ, 0, expectedChannels,
+                    CHANNELS_24_GHZ.length + CHANNELS_5_GHZ.length + CHANNELS_DFS.length,
+                    CHANNELS_6_GHZ.length);
+            System.arraycopy(CHANNELS_60_GHZ, 0, expectedChannels,
+                    CHANNELS_24_GHZ.length + CHANNELS_5_GHZ.length + CHANNELS_DFS.length
+                    + CHANNELS_6_GHZ.length,
+                    CHANNELS_60_GHZ.length);
+            testBand(expectedChannels, WifiScanner.WIFI_BAND_24_5_WITH_DFS_6_60_GHZ);
         }
     }
 
@@ -210,7 +221,8 @@
                     CHANNELS_24_GHZ,
                     CHANNELS_5_GHZ,
                     CHANNELS_DFS,
-                    CHANNELS_6_GHZ);
+                    CHANNELS_6_GHZ,
+                    CHANNELS_60_GHZ);
         }
 
         /**
@@ -283,12 +295,14 @@
                     CHANNELS_24_GHZ,
                     CHANNELS_5_GHZ,
                     CHANNELS_DFS,
-                    CHANNELS_6_GHZ);
+                    CHANNELS_6_GHZ,
+                    CHANNELS_60_GHZ);
             KnownBandsChannelHelper channelHelper1 = new PresetKnownBandsChannelHelper(
                     CHANNELS_24_GHZ,
                     CHANNELS_5_GHZ,
                     CHANNELS_DFS,
-                    CHANNELS_6_GHZ);
+                    CHANNELS_6_GHZ,
+                    CHANNELS_60_GHZ);
             assertTrue(channelHelper0.satisfies(channelHelper1));
         }
 
@@ -301,12 +315,14 @@
                     CHANNELS_24_GHZ,
                     CHANNELS_5_GHZ,
                     CHANNELS_DFS,
-                    CHANNELS_6_GHZ);
+                    CHANNELS_6_GHZ,
+                    CHANNELS_60_GHZ);
             KnownBandsChannelHelper channelHelper1 = new PresetKnownBandsChannelHelper(
                     CHANNELS_24_GHZ,
                     CHANNELS_5_GHZ,
                     CHANNELS_DFS_OTHER,
-                    CHANNELS_6_GHZ);
+                    CHANNELS_6_GHZ,
+                    CHANNELS_60_GHZ);
             assertFalse(channelHelper0.satisfies(channelHelper1));
         }
     }
@@ -329,7 +345,8 @@
                     CHANNELS_24_GHZ,
                     CHANNELS_5_GHZ,
                     CHANNELS_DFS,
-                    CHANNELS_6_GHZ);
+                    CHANNELS_6_GHZ,
+                    CHANNELS_60_GHZ);
             mChannelCollection = channelHelper.createChannelCollection();
         }
 
@@ -348,6 +365,7 @@
             assertTrue(mChannelCollection.isEmpty());
             assertFalse(mChannelCollection.containsChannel(2412));
             assertFalse(mChannelCollection.containsChannel(5160));
+            assertFalse(mChannelCollection.containsChannel(58320));
             assertFalse(mChannelCollection.isAllChannels());
         }
 
@@ -369,6 +387,7 @@
             assertTrue(mChannelCollection.isEmpty());
             assertFalse(mChannelCollection.containsChannel(2412));
             assertFalse(mChannelCollection.containsChannel(5160));
+            assertFalse(mChannelCollection.containsChannel(58320));
             assertFalse(mChannelCollection.isAllChannels());
         }
 
@@ -389,6 +408,7 @@
             assertFalse(mChannelCollection.isEmpty());
             assertTrue(mChannelCollection.containsChannel(2412));
             assertFalse(mChannelCollection.containsChannel(5160));
+            assertFalse(mChannelCollection.containsChannel(58320));
             assertFalse(mChannelCollection.isAllChannels());
         }
 
@@ -409,6 +429,7 @@
             assertFalse(mChannelCollection.isEmpty());
             assertTrue(mChannelCollection.containsChannel(2412));
             assertFalse(mChannelCollection.containsChannel(5160));
+            assertFalse(mChannelCollection.containsChannel(58320));
             assertFalse(mChannelCollection.isAllChannels());
         }
 
@@ -430,6 +451,7 @@
             assertFalse(mChannelCollection.isEmpty());
             assertTrue(mChannelCollection.containsChannel(2412));
             assertFalse(mChannelCollection.containsChannel(5160));
+            assertFalse(mChannelCollection.containsChannel(58320));
             assertFalse(mChannelCollection.isAllChannels());
         }
 
@@ -451,6 +473,7 @@
             assertFalse(mChannelCollection.isEmpty());
             assertTrue(mChannelCollection.containsChannel(2412));
             assertFalse(mChannelCollection.containsChannel(5160));
+            assertFalse(mChannelCollection.containsChannel(58320));
             assertFalse(mChannelCollection.isAllChannels());
         }
 
@@ -472,6 +495,7 @@
             assertFalse(mChannelCollection.isEmpty());
             assertTrue(mChannelCollection.containsChannel(2412));
             assertTrue(mChannelCollection.containsChannel(5160));
+            assertFalse(mChannelCollection.containsChannel(58320));
             assertFalse(mChannelCollection.isAllChannels());
         }
 
@@ -480,12 +504,12 @@
          */
         @Test
         public void addChannel_and_addBand_all() {
-            mChannelCollection.addBand(WifiScanner.WIFI_BAND_24_5_WITH_DFS_6_GHZ);
+            mChannelCollection.addBand(WifiScanner.WIFI_BAND_24_5_WITH_DFS_6_60_GHZ);
             mChannelCollection.addChannel(5160);
 
             WifiNative.BucketSettings bucketSettings = new WifiNative.BucketSettings();
             mChannelCollection.fillBucketSettings(bucketSettings, Integer.MAX_VALUE);
-            assertThat(bucketSettings, bandIs(WifiScanner.WIFI_BAND_24_5_WITH_DFS_6_GHZ));
+            assertThat(bucketSettings, bandIs(WifiScanner.WIFI_BAND_24_5_WITH_DFS_6_60_GHZ));
 
             assertNull(mChannelCollection.getScanFreqs());
 
@@ -493,6 +517,8 @@
             assertTrue(mChannelCollection.containsChannel(2412));
             assertTrue(mChannelCollection.containsChannel(5160));
             assertTrue(mChannelCollection.containsChannel(5600));
+            assertTrue(mChannelCollection.containsChannel(58320));
+            assertTrue(mChannelCollection.containsChannel(60480));
             assertTrue(mChannelCollection.isAllChannels());
         }
 
@@ -541,11 +567,15 @@
             mChannelCollection.addChannel(5660);
             mChannelCollection.addChannel(5945);
             mChannelCollection.addChannel(5985);
+            mChannelCollection.addChannel(58320);
+            mChannelCollection.addChannel(60480);
 
             WifiNative.BucketSettings bucketSettings = new WifiNative.BucketSettings();
             mChannelCollection.fillBucketSettings(bucketSettings, Integer.MAX_VALUE);
             assertThat(bucketSettings,
-                    channelsAre(2412, 2450, 5160, 5175, 5600, 5650, 5660, 5945, 5985));
+                    channelsAre(2412, 2450,
+                            5160, 5175, 5600, 5650, 5660, 5945, 5985,
+                            58320, 60480));
             assertTrue(mChannelCollection.isAllChannels());
         }
     }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/scanner/ScanScheduleUtilFilterTest.java b/service/tests/wifitests/src/com/android/server/wifi/scanner/ScanScheduleUtilFilterTest.java
index 429da3f..1eb4331 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/scanner/ScanScheduleUtilFilterTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/scanner/ScanScheduleUtilFilterTest.java
@@ -53,7 +53,8 @@
                 new int[]{2412, 2450},
                 new int[]{5160, 5175},
                 new int[]{5600, 5650},
-                new int[]{5945, 5985});
+                new int[]{5945, 5985},
+                new int[]{58320, 60480});
     }
 
     @Test
diff --git a/service/tests/wifitests/src/com/android/server/wifi/scanner/WifiScanningServiceTest.java b/service/tests/wifitests/src/com/android/server/wifi/scanner/WifiScanningServiceTest.java
index 0264bc2..b7cba32 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/scanner/WifiScanningServiceTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/scanner/WifiScanningServiceTest.java
@@ -27,19 +27,25 @@
 import static com.android.server.wifi.ScanTestUtil.assertScanResultsEquals;
 import static com.android.server.wifi.ScanTestUtil.channelsToSpec;
 import static com.android.server.wifi.ScanTestUtil.computeSingleScanNativeSettings;
+import static com.android.server.wifi.ScanTestUtil.computeSingleScanNativeSettingsWithChannelHelper;
 import static com.android.server.wifi.ScanTestUtil.createRequest;
 import static com.android.server.wifi.ScanTestUtil.createSingleScanNativeSettingsForChannels;
 import static com.android.server.wifi.scanner.WifiScanningServiceImpl.WifiSingleScanStateMachine.CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS;
+import static com.android.server.wifi.scanner.WifiScanningServiceImpl.WifiSingleScanStateMachine.EMERGENCY_SCAN_END_INDICATION_ALARM_TAG;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.eq;
@@ -60,6 +66,7 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.net.wifi.ScanResult;
+import android.net.wifi.WifiManager;
 import android.net.wifi.WifiScanner;
 import android.os.BatteryStatsManager;
 import android.os.Binder;
@@ -77,8 +84,8 @@
 import com.android.internal.util.AsyncChannel;
 import com.android.internal.util.Protocol;
 import com.android.internal.util.test.BidirectionalAsyncChannel;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.Clock;
-import com.android.server.wifi.DppMetrics;
 import com.android.server.wifi.FakeWifiLog;
 import com.android.server.wifi.FrameworkFacade;
 import com.android.server.wifi.MockResources;
@@ -87,11 +94,8 @@
 import com.android.server.wifi.WifiInjector;
 import com.android.server.wifi.WifiMetrics;
 import com.android.server.wifi.WifiNative;
-import com.android.server.wifi.WifiPowerMetrics;
-import com.android.server.wifi.aware.WifiAwareMetrics;
-import com.android.server.wifi.p2p.WifiP2pMetrics;
 import com.android.server.wifi.proto.nano.WifiMetricsProto;
-import com.android.server.wifi.rtt.RttMetrics;
+import com.android.server.wifi.util.LastCallerInfoManager;
 import com.android.server.wifi.util.WifiAsyncChannel;
 import com.android.server.wifi.util.WifiPermissionsUtil;
 
@@ -111,6 +115,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Set;
 import java.util.regex.Pattern;
 
 /**
@@ -125,7 +130,9 @@
     private static final String TEST_FEATURE_ID = "test.feature";
     private static final String TEST_IFACE_NAME_0 = "wlan0";
     private static final String TEST_IFACE_NAME_1 = "wlan1";
-    private static final WifiScanner.ScanData DUMMY_SCAN_DATA =
+    private static final int TEST_PSC_CHANNEL = ScanResult.BAND_6_GHZ_PSC_START_MHZ;
+    private static final int TEST_NON_PSC_CHANNEL = 5985;
+    private static final WifiScanner.ScanData PLACEHOLDER_SCAN_DATA =
             new WifiScanner.ScanData(0, 0, new ScanResult[0]);
 
     @Mock Context mContext;
@@ -139,14 +146,15 @@
     @Mock Clock mClock;
     @Spy FakeWifiLog mLog;
     @Mock WifiPermissionsUtil mWifiPermissionsUtil;
-    @Mock DppMetrics mDppMetrics;
     @Mock WifiNative mWifiNative;
+    @Mock WifiMetrics mWifiMetrics;
+    @Mock WifiMetrics.ScanMetrics mScanMetrics;
+    @Mock WifiManager mWifiManager;
+    @Mock LastCallerInfoManager mLastCallerInfoManager;
     ChannelHelper mChannelHelper0;
     ChannelHelper mChannelHelper1;
-    WifiMetrics mWifiMetrics;
     TestLooper mLooper;
     WifiScanningServiceImpl mWifiScanningServiceImpl;
-    @Mock WifiP2pMetrics mWifiP2pMetrics;
 
     @Before
     public void setUp() throws Exception {
@@ -155,6 +163,7 @@
         mAlarmManager = new TestAlarmManager();
         when(mContext.getSystemService(Context.ALARM_SERVICE))
                 .thenReturn(mAlarmManager.getAlarmManager());
+        when(mContext.getSystemService(WifiManager.class)).thenReturn(mWifiManager);
         when(mContext.getResources()).thenReturn(new MockResources());
         when(mWifiInjector.getWifiPermissionsUtil())
                 .thenReturn(mWifiPermissionsUtil);
@@ -163,26 +172,27 @@
                 new int[]{2412, 2450},
                 new int[]{5160, 5175},
                 new int[]{5600, 5650, 5660},
-                new int[]{5945, 5985});
+                new int[]{TEST_PSC_CHANNEL, TEST_NON_PSC_CHANNEL},
+                new int[]{58320, 60480});
         mChannelHelper1 = new PresetKnownBandsChannelHelper(
                 new int[]{2412, 2450},
                 new int[]{5160, 5175},
                 new int[]{5600, 5660, 5680}, // 5650 is missing from channelHelper0
-                new int[]{5945, 5985});
+                new int[]{5945, 5985},
+                new int[]{58320, 60480});
         mLooper = new TestLooper();
-        mWifiMetrics = new WifiMetrics(mContext, mFrameworkFacade, mClock, mLooper.getLooper(),
-                new WifiAwareMetrics(mClock), new RttMetrics(mClock),
-                new WifiPowerMetrics(mBatteryStats),
-                mWifiP2pMetrics, mDppMetrics);
         when(mWifiScannerImplFactory
                 .create(any(), any(), any(), eq(TEST_IFACE_NAME_0)))
                 .thenReturn(mWifiScannerImpl0);
         when(mWifiScannerImpl0.getChannelHelper()).thenReturn(mChannelHelper0);
+        when(mWifiScannerImpl0.getIfaceName()).thenReturn(TEST_IFACE_NAME_0);
         when(mWifiScannerImplFactory
                 .create(any(), any(), any(), eq(TEST_IFACE_NAME_1)))
                 .thenReturn(mWifiScannerImpl1);
         when(mWifiScannerImpl1.getChannelHelper()).thenReturn(mChannelHelper1);
+        when(mWifiScannerImpl1.getIfaceName()).thenReturn(TEST_IFACE_NAME_1);
         when(mWifiInjector.getWifiMetrics()).thenReturn(mWifiMetrics);
+        when(mWifiMetrics.getScanMetrics()).thenReturn(mScanMetrics);
         when(mWifiInjector.makeLog(anyString())).thenReturn(mLog);
         WifiAsyncChannel mWifiAsyncChannel = new WifiAsyncChannel("ScanningServiceTest");
         mWifiAsyncChannel.setWifiLog(mLog);
@@ -195,6 +205,7 @@
         when(mContext.checkPermission(eq(Manifest.permission.NETWORK_STACK),
                 anyInt(), eq(Binder.getCallingUid())))
                 .thenReturn(PERMISSION_GRANTED);
+        when(mWifiInjector.getLastCallerInfoManager()).thenReturn(mLastCallerInfoManager);
         mWifiScanningServiceImpl = new WifiScanningServiceImpl(mContext, mLooper.getLooper(),
                 mWifiScannerImplFactory, mBatteryStats, mWifiInjector);
     }
@@ -446,23 +457,28 @@
         setupAndLoadDriver(TEST_MAX_SCAN_BUCKETS_IN_CAPABILITIES);
     }
 
-    private void setupAndLoadDriver(int max_scan_buckets) {
+    private void setupAndLoadDriver(
+            BidirectionalAsyncChannel controlChannel, int maxScanBuckets) {
         when(mWifiScannerImpl0.getScanCapabilities(any(WifiNative.ScanCapabilities.class)))
                 .thenAnswer(new AnswerWithArguments() {
-                        public boolean answer(WifiNative.ScanCapabilities capabilities) {
-                            capabilities.max_scan_cache_size = Integer.MAX_VALUE;
-                            capabilities.max_scan_buckets = max_scan_buckets;
-                            capabilities.max_ap_cache_per_scan = MAX_AP_PER_SCAN;
-                            capabilities.max_rssi_sample_size = 8;
-                            capabilities.max_scan_reporting_threshold = 10;
-                            return true;
-                        }
-                    });
-        BidirectionalAsyncChannel controlChannel = connectChannel(mock(Handler.class));
+                    public boolean answer(WifiNative.ScanCapabilities capabilities) {
+                        capabilities.max_scan_cache_size = Integer.MAX_VALUE;
+                        capabilities.max_scan_buckets = maxScanBuckets;
+                        capabilities.max_ap_cache_per_scan = MAX_AP_PER_SCAN;
+                        capabilities.max_rssi_sample_size = 8;
+                        capabilities.max_scan_reporting_threshold = 10;
+                        return true;
+                    }
+                });
         controlChannel.sendMessage(Message.obtain(null, WifiScanner.CMD_ENABLE));
         mLooper.dispatchAll();
     }
 
+    private void setupAndLoadDriver(int maxScanBuckets) {
+        BidirectionalAsyncChannel controlChannel = connectChannel(mock(Handler.class));
+        setupAndLoadDriver(controlChannel, maxScanBuckets);
+    }
+
     private String dumpService() {
         StringWriter stringWriter = new StringWriter();
         mWifiScanningServiceImpl.dump(new FileDescriptor(), new PrintWriter(stringWriter),
@@ -499,7 +515,7 @@
     }
 
     @Test
-    public void startService() throws Exception {
+    public void startServiceAndTriggerSingleScanWithoutDriverLoaded() throws Exception {
         mWifiScanningServiceImpl.startService();
         mLooper.dispatchAll();
         mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
@@ -508,7 +524,8 @@
         Handler handler = mock(Handler.class);
         BidirectionalAsyncChannel controlChannel = connectChannel(handler);
         InOrder order = inOrder(handler);
-        sendBackgroundScanRequest(controlChannel, 122, generateValidScanSettings(), null);
+        sendSingleScanRequest(controlChannel, 122, createRequest(WifiScanner.WIFI_BAND_ALL,
+                0, 0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN), new WorkSource(2292));
         mLooper.dispatchAll();
         verifyFailedResponse(order, handler, 122, WifiScanner.REASON_UNSPECIFIED, "not available");
     }
@@ -681,7 +698,7 @@
         ScanResults expectedResults = resultsForImpl0;
         if (resultsForImpl1 != null) {
             expectedResults = ScanResults.merge(
-                    resultsForImpl0.getScanData().getBandScanned(),
+                    resultsForImpl0.getScanData().getScannedBandsInternal(),
                     resultsForImpl0, resultsForImpl1);
         }
         verifyScanResultsReceived(order, handler, requestId, expectedResults.getScanData());
@@ -698,10 +715,10 @@
      */
     @Test
     public void sendSingleScanBandRequest() throws Exception {
-        WifiScanner.ScanSettings requestSettings = createRequest(WifiScanner.WIFI_BAND_BOTH_WITH_DFS,
+        WifiScanner.ScanSettings requestSettings = createRequest(WifiScanner.WIFI_BAND_ALL,
                 0, 0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
         doSuccessfulSingleScan(requestSettings, computeSingleScanNativeSettings(requestSettings),
-                ScanResults.create(0, WifiScanner.WIFI_BAND_BOTH_WITH_DFS, 2412, 5160, 5175));
+                ScanResults.create(0, WifiScanner.WIFI_BAND_ALL, 2412, 5160, 5175));
     }
 
     /**
@@ -739,6 +756,150 @@
     }
 
     /**
+     * Verify that PSC channels not added when a channel list is explicitly specified for scanning.
+     */
+    @Test
+    public void testPscIsIgnoredForPartialScan() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        WifiScanner.ScanSettings requestSettings = createRequest(
+                channelsToSpec(2412, 5160, 5955),
+                0, 0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
+        int expectedChannelListSize = requestSettings.channels.length;
+        requestSettings.set6GhzPscOnlyEnabled(true);
+        WifiNative.ScanSettings fromChannelList = computeSingleScanNativeSettings(requestSettings);
+        assertEquals(expectedChannelListSize, fromChannelList.buckets[0].channels.length);
+        WifiNative.ScanSettings fromChannelHelper =
+                computeSingleScanNativeSettingsWithChannelHelper(requestSettings, mChannelHelper0);
+        assertNativeScanSettingsEquals(fromChannelList, fromChannelHelper);
+        doSuccessfulSingleScan(requestSettings, fromChannelList,
+                ScanResults.create(0, WifiScanner.WIFI_BAND_BOTH, new int[0]));
+    }
+
+    /**
+     * Verify that when set6GhzPscOnlyEnabled(true) is used, only 6Ghz PSC channels get added
+     * when the 6Ghz band is being scanned.
+     */
+    @Test
+    public void testPscChannelAddedWhenScanning6GhzBand() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        WifiScanner.ScanSettings requestSettings = createRequest(WifiScanner.WIFI_BAND_ALL, 0,
+                0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
+        requestSettings.set6GhzPscOnlyEnabled(true);
+
+        // The expectedChannels should match with all supported channels configured for
+        // mChannelHelper0, less the TEST_NON_PSC_CHANNEL
+        Set<Integer> expectedChannels = new ArraySet<Integer>(
+                new Integer[]{2412, 2450, 5160, 5175, 5600, 5650, 5660, 5600, 5650, 5660,
+                        TEST_PSC_CHANNEL, 58320, 60480});
+
+        // Compute the expected nativeSettings
+        WifiNative.ScanSettings nativeSettings =
+                computeSingleScanNativeSettingsWithChannelHelper(requestSettings, mChannelHelper0);
+        assertEquals("The scan band should be WIFI_BAND_UNSPECIFIED since only a subset of 6Ghz "
+                + "channels need to be scanned", WifiScanner.WIFI_BAND_UNSPECIFIED,
+                nativeSettings.buckets[0].band);
+        Set<Integer> scanTestUtilcomputedExpectedChannels = new ArraySet<>();
+        for (WifiNative.ChannelSettings channelSettings : nativeSettings.buckets[0].channels) {
+            scanTestUtilcomputedExpectedChannels.add(channelSettings.frequency);
+        }
+
+        assertEquals("Computed native settings does not match with expected value",
+                expectedChannels, scanTestUtilcomputedExpectedChannels);
+        doSuccessfulSingleScan(requestSettings, nativeSettings,
+                ScanResults.create(0, WifiScanner.WIFI_BAND_BOTH, new int[0]));
+    }
+
+    /**
+     * Verify that when set6GhzPscOnlyEnabled(true) is used but the 6Ghz band not being scanned,
+     * we ignore the flag.
+     */
+    @Test
+    public void testPscChannelNotAddedWhenNotScanning6GhzBand() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        WifiScanner.ScanSettings requestSettings = createRequest(WifiScanner.WIFI_BAND_BOTH,
+                0, 0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
+        requestSettings.set6GhzPscOnlyEnabled(true);
+        WifiNative.ScanSettings fromChannelList = computeSingleScanNativeSettings(requestSettings);
+        assertNull("channel list should be null", fromChannelList.buckets[0].channels);
+        WifiNative.ScanSettings fromChannelHelper =
+                computeSingleScanNativeSettingsWithChannelHelper(requestSettings, mChannelHelper0);
+        assertNativeScanSettingsEquals(fromChannelList, fromChannelHelper);
+        doSuccessfulSingleScan(requestSettings, fromChannelList,
+                ScanResults.create(0, WifiScanner.WIFI_BAND_BOTH, new int[0]));
+    }
+
+    /**
+     * Verify WifiNative.ScanSettings#enable6GhzRnr is set appropriatly according to
+     * WifiScanner.ScanSettings#band and WifiScanner.ScanSettings#getRnrSetting().
+     */
+    @Test
+    public void testRnrIsDisabledIf6GhzBandIsNotScanned() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        // Verify RNR is disabled by default since WIFI_BAND_BOTH doesn't include the 6Ghz band.
+        WifiScanner.ScanSettings requestSettings = createRequest(WifiScanner.WIFI_BAND_BOTH, 0,
+                0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
+        WifiNative.ScanSettings nativeSettings = computeSingleScanNativeSettings(requestSettings);
+        assertEquals(WifiScanner.WIFI_RNR_ENABLED_IF_WIFI_BAND_6_GHZ_SCANNED,
+                requestSettings.getRnrSetting());
+        assertEquals(false, nativeSettings.enable6GhzRnr);
+        doSuccessfulSingleScan(requestSettings, nativeSettings,
+                ScanResults.create(0, WifiScanner.WIFI_BAND_BOTH, new int[0]));
+    }
+
+    /**
+     * Verify that when WIFI_BAND_ALL is scanned, RNR is automatically enabled when
+     * getRnrSetting() returns WIFI_RNR_ENABLED_IF_WIFI_BAND_6_GHZ_SCANNED.
+     */
+    @Test
+    public void testRnrIsEnabledIf6GhzBandIsScanned() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        WifiScanner.ScanSettings requestSettings = createRequest(WifiScanner.WIFI_BAND_ALL, 0,
+                0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
+        WifiNative.ScanSettings nativeSettings = computeSingleScanNativeSettings(requestSettings);
+        assertEquals(WifiScanner.WIFI_RNR_ENABLED_IF_WIFI_BAND_6_GHZ_SCANNED,
+                requestSettings.getRnrSetting());
+        assertEquals(true, nativeSettings.enable6GhzRnr);
+        doSuccessfulSingleScan(requestSettings, nativeSettings,
+                ScanResults.create(0, WifiScanner.WIFI_BAND_ALL, new int[0]));
+    }
+
+    /**
+     * Verify RNR is enabled even though only 2.4 and 5Ghz channels are being scanned because of
+     * getRnrSetting() returns WIFI_RNR_ENABLED.
+     */
+    @Test
+    public void testForceEnableRnr() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        WifiScanner.ScanSettings requestSettings = createRequest(WifiScanner.WIFI_BAND_BOTH, 0,
+                0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
+        requestSettings.setRnrSetting(WifiScanner.WIFI_RNR_ENABLED);
+        WifiNative.ScanSettings nativeSettings = computeSingleScanNativeSettings(requestSettings);
+        assertEquals(WifiScanner.WIFI_RNR_ENABLED,
+                requestSettings.getRnrSetting());
+        assertEquals(true, nativeSettings.enable6GhzRnr);
+        doSuccessfulSingleScan(requestSettings, nativeSettings,
+                ScanResults.create(0, WifiScanner.WIFI_BAND_BOTH, new int[0]));
+    }
+
+    /**
+     * Verify that when WIFI_BAND_ALL is scanned, RNR is disabled when
+     * getRnrSetting() returns WIFI_RNR_NOT_NEEDED.
+     */
+    @Test
+    public void testRnrIsExplicitlyDisabled() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        WifiScanner.ScanSettings requestSettings = createRequest(WifiScanner.WIFI_BAND_ALL, 0,
+                0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
+        requestSettings.setRnrSetting(WifiScanner.WIFI_RNR_NOT_NEEDED);
+        WifiNative.ScanSettings nativeSettings = computeSingleScanNativeSettings(requestSettings);
+        assertEquals(WifiScanner.WIFI_RNR_NOT_NEEDED,
+                requestSettings.getRnrSetting());
+        assertEquals(false, nativeSettings.enable6GhzRnr);
+        doSuccessfulSingleScan(requestSettings, nativeSettings,
+                ScanResults.create(0, WifiScanner.WIFI_BAND_ALL, new int[0]));
+    }
+
+    /**
      * Do a single scan with results that do not match the requested scan and verify that it is
      * still successful (and returns no results).
      */
@@ -793,9 +954,9 @@
         assertDumpContainsCallbackLog("singleScanInvalidRequest", requestId,
                 "bad request");
 
-        assertEquals(0, mWifiMetrics.getOneshotScanCount());
-        assertEquals(mWifiMetrics.getScanReturnEntry(
-                WifiMetricsProto.WifiLog.SCAN_FAILURE_INVALID_CONFIGURATION), 1);
+        verify(mWifiMetrics, never()).incrementOneshotScanCount();
+        verify(mWifiMetrics).incrementScanReturnEntry(
+                WifiMetricsProto.WifiLog.SCAN_FAILURE_INVALID_CONFIGURATION, 1);
 
         // Ensure that no scan was triggered to the lower layers.
         verify(mBatteryStats, never()).reportWifiScanStoppedFromSource(eq(workSource));
@@ -846,9 +1007,9 @@
         assertDumpContainsCallbackLog("singleScanInvalidRequest", requestId,
                 "bad request");
 
-        assertEquals(0, mWifiMetrics.getOneshotScanCount());
-        assertEquals(mWifiMetrics.getScanReturnEntry(
-                WifiMetricsProto.WifiLog.SCAN_FAILURE_INVALID_CONFIGURATION), 1);
+        verify(mWifiMetrics, never()).incrementOneshotScanCount();
+        verify(mWifiMetrics).incrementScanReturnEntry(
+                WifiMetricsProto.WifiLog.SCAN_FAILURE_INVALID_CONFIGURATION, 1);
 
         // Ensure that no scan was triggered to the lower layers.
         verify(mBatteryStats, never()).reportWifiScanStoppedFromSource(eq(workSource));
@@ -891,9 +1052,9 @@
         assertDumpContainsCallbackLog("singleScanInvalidRequest", requestId,
                 "bad request");
 
-        assertEquals(0, mWifiMetrics.getOneshotScanCount());
-        assertEquals(mWifiMetrics.getScanReturnEntry(
-                WifiMetricsProto.WifiLog.SCAN_FAILURE_INVALID_CONFIGURATION), 1);
+        verify(mWifiMetrics, never()).incrementOneshotScanCount();
+        verify(mWifiMetrics).incrementScanReturnEntry(
+                WifiMetricsProto.WifiLog.SCAN_FAILURE_INVALID_CONFIGURATION, 1);
 
         // Ensure that no scan was triggered to the lower layers.
         verify(mBatteryStats, never()).reportWifiScanStoppedFromSource(eq(workSource));
@@ -948,8 +1109,8 @@
                 "Failed to start single scan", messageCaptor.getAllValues().get(1));
         verifyNoMoreInteractions(mBatteryStats);
 
-        assertEquals(mWifiMetrics.getOneshotScanCount(), 1);
-        assertEquals(mWifiMetrics.getScanReturnEntry(WifiMetricsProto.WifiLog.SCAN_UNKNOWN), 1);
+        verify(mWifiMetrics).incrementOneshotScanCount();
+        verify(mWifiMetrics).incrementScanReturnEntry(WifiMetricsProto.WifiLog.SCAN_UNKNOWN, 1);
         assertDumpContainsRequestLog("addSingleScanRequest", requestId);
     }
 
@@ -991,8 +1152,8 @@
                 WifiScanner.REASON_UNSPECIFIED, "Scan failed");
         assertDumpContainsCallbackLog("singleScanFailed", requestId,
                 "reason=" + WifiScanner.REASON_UNSPECIFIED + ", Scan failed");
-        assertEquals(mWifiMetrics.getOneshotScanCount(), 1);
-        assertEquals(mWifiMetrics.getScanReturnEntry(WifiMetricsProto.WifiLog.SCAN_UNKNOWN), 1);
+        verify(mWifiMetrics).incrementOneshotScanCount();
+        verify(mWifiMetrics).incrementScanReturnEntry(WifiMetricsProto.WifiLog.SCAN_UNKNOWN, 1);
         verify(mBatteryStats).reportWifiScanStoppedFromSource(eq(workSource));
     }
 
@@ -1003,7 +1164,7 @@
     @Test
     public void testMetricsForOneshotScanWithDFSIsIncremented() throws Exception {
         WifiScanner.ScanSettings requestSettings = createRequest(
-                WifiScanner.WIFI_BAND_BOTH_WITH_DFS, 0, 0, 20,
+                WifiScanner.WIFI_BAND_24_5_WITH_DFS_6_60_GHZ, 0, 0, 20,
                 WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
         int requestId = 33;
         WorkSource workSource = new WorkSource(Binder.getCallingUid()); // don't explicitly set
@@ -1021,12 +1182,12 @@
 
         sendSingleScanRequest(controlChannel, requestId, requestSettings, null);
 
-        assertEquals(0, mWifiMetrics.getOneshotScanCount());
-        assertEquals(0, mWifiMetrics.getOneshotScanWithDfsCount());
+        verify(mWifiMetrics, never()).incrementOneshotScanCount();
+        verify(mWifiMetrics, never()).incrementOneshotScanWithDfsCount();
         // Scan is successfully queue
         mLooper.dispatchAll();
-        assertEquals(1, mWifiMetrics.getOneshotScanCount());
-        assertEquals(1, mWifiMetrics.getOneshotScanWithDfsCount());
+        verify(mWifiMetrics).incrementOneshotScanCount();
+        verify(mWifiMetrics).incrementOneshotScanWithDfsCount();
     }
 
     /**
@@ -1054,11 +1215,12 @@
 
         sendSingleScanRequest(controlChannel, requestId, requestSettings, null);
 
-        assertEquals(0, mWifiMetrics.getOneshotScanCount());
+        verify(mWifiMetrics, never()).incrementOneshotScanCount();
+        verify(mWifiMetrics, never()).incrementOneshotScanWithDfsCount();
         // Scan is successfully queue
         mLooper.dispatchAll();
-        assertEquals(1, mWifiMetrics.getOneshotScanCount());
-        assertEquals(0, mWifiMetrics.getOneshotScanWithDfsCount());
+        verify(mWifiMetrics).incrementOneshotScanCount();
+        verify(mWifiMetrics, never()).incrementOneshotScanWithDfsCount();
     }
 
     /**
@@ -1321,8 +1483,9 @@
         mLooper.dispatchAll();
         verifyScanResultsReceived(handlerOrder, handler, requestId2, results2.getScanData());
         verifySingleScanCompletedReceived(handlerOrder, handler, requestId2);
-        assertEquals(mWifiMetrics.getOneshotScanCount(), 2);
-        assertEquals(mWifiMetrics.getScanReturnEntry(WifiMetricsProto.WifiLog.SCAN_SUCCESS), 2);
+        verify(mWifiMetrics, times(2)).incrementOneshotScanCount();
+        verify(mWifiMetrics, times(2)).incrementScanReturnEntry(
+                WifiMetricsProto.WifiLog.SCAN_SUCCESS, 1);
     }
 
     /**
@@ -1391,8 +1554,9 @@
         mLooper.dispatchAll();
         verifyScanResultsReceived(handlerOrder, handler, requestId2, results2.getScanData());
         verifySingleScanCompletedReceived(handlerOrder, handler, requestId2);
-        assertEquals(mWifiMetrics.getOneshotScanCount(), 2);
-        assertEquals(mWifiMetrics.getScanReturnEntry(WifiMetricsProto.WifiLog.SCAN_SUCCESS), 2);
+        verify(mWifiMetrics, times(2)).incrementOneshotScanCount();
+        verify(mWifiMetrics, times(2)).incrementScanReturnEntry(
+                WifiMetricsProto.WifiLog.SCAN_SUCCESS, 1);
     }
 
 
@@ -1495,8 +1659,11 @@
 
         verifyMultipleSingleScanResults(handlerOrder, handler, requestId2, results2, requestId3,
                 results3);
-        assertEquals(mWifiMetrics.getOneshotScanCount(), 3);
-        assertEquals(mWifiMetrics.getScanReturnEntry(WifiMetricsProto.WifiLog.SCAN_SUCCESS), 3);
+        verify(mWifiMetrics, times(3)).incrementOneshotScanCount();
+        verify(mWifiMetrics).incrementScanReturnEntry(
+                WifiMetricsProto.WifiLog.SCAN_SUCCESS, 1);
+        verify(mWifiMetrics).incrementScanReturnEntry(
+                WifiMetricsProto.WifiLog.SCAN_SUCCESS, 2);
 
         verify(mBatteryStats).reportWifiScanStoppedFromSource(eq(workSource2and3));
 
@@ -1573,8 +1740,9 @@
         verifyMultipleSingleScanResults(handlerOrder, handler, requestId1, results1, requestId2,
                 results2);
 
-        assertEquals(mWifiMetrics.getOneshotScanCount(), 2);
-        assertEquals(mWifiMetrics.getScanReturnEntry(WifiMetricsProto.WifiLog.SCAN_SUCCESS), 2);
+        verify(mWifiMetrics, times(2)).incrementOneshotScanCount();
+        verify(mWifiMetrics).incrementScanReturnEntry(
+                WifiMetricsProto.WifiLog.SCAN_SUCCESS, 2);
     }
 
     /**
@@ -1672,8 +1840,11 @@
 
         verifyScanResultsReceived(handlerOrder, handler, requestId2, results2.getScanData());
         verifySingleScanCompletedReceived(handlerOrder, handler, requestId2);
-        assertEquals(mWifiMetrics.getOneshotScanCount(), 3);
-        assertEquals(mWifiMetrics.getScanReturnEntry(WifiMetricsProto.WifiLog.SCAN_SUCCESS), 3);
+        verify(mWifiMetrics, times(3)).incrementOneshotScanCount();
+        verify(mWifiMetrics).incrementScanReturnEntry(
+                WifiMetricsProto.WifiLog.SCAN_SUCCESS, 2);
+        verify(mWifiMetrics).incrementScanReturnEntry(
+                WifiMetricsProto.WifiLog.SCAN_SUCCESS, 1);
 
         verify(mBatteryStats).reportWifiScanStoppedFromSource(eq(workSource2));
 
@@ -2226,7 +2397,7 @@
                         WifiScanner.WIFI_BAND_BOTH)
                 .build();
         doSuccessfulBackgroundScan(requestSettings, nativeSettings);
-        assertEquals(mWifiMetrics.getBackgroundScanCount(), 1);
+        verify(mWifiMetrics).incrementBackgroundScanCount();
     }
 
     /**
@@ -2544,6 +2715,9 @@
         mLooper.dispatchAll();
         verifySuccessfulResponse(order, handler, 192);
         assertDumpContainsRequestLog("addBackgroundScanRequest", 192);
+        verify(mLastCallerInfoManager, atLeastOnce()).put(
+                eq(LastCallerInfoManager.SCANNING_ENABLED), anyInt(), anyInt(), anyInt(), any(),
+                eq(true));
     }
 
     /**
@@ -2595,6 +2769,9 @@
         assertDumpContainsRequestLog("addSingleScanRequest", requestId);
         assertDumpContainsCallbackLog("singleScanResults", requestId,
                 "results=" + results.getScanData().getResults().length);
+        verify(mLastCallerInfoManager, atLeastOnce()).put(
+                eq(LastCallerInfoManager.SCANNING_ENABLED), anyInt(), anyInt(), anyInt(), any(),
+                eq(true));
     }
 
     /**
@@ -2738,16 +2915,24 @@
         startServiceAndLoadDriver();
 
         Handler handler = mock(Handler.class);
+        InOrder order = inOrder(handler, mWifiScannerImpl0);
+        int requestId = 122;
         BidirectionalAsyncChannel controlChannel = connectChannel(handler);
 
         // Client doesn't have NETWORK_STACK permission.
         doThrow(new SecurityException()).when(mContext).enforcePermission(
                 eq(Manifest.permission.NETWORK_STACK), anyInt(), eq(Binder.getCallingUid()), any());
 
+        // successful start
+        when(mWifiScannerImpl0.startSingleScan(any(WifiNative.ScanSettings.class),
+                any(WifiNative.ScanEventHandler.class))).thenReturn(true);
+
         Bundle bundle = new Bundle();
         bundle.putString(WifiScanner.REQUEST_PACKAGE_NAME_KEY, TEST_PACKAGE_NAME);
         bundle.putString(WifiScanner.REQUEST_FEATURE_ID_KEY, TEST_FEATURE_ID);
-        WifiScanner.ScanSettings scanSettings = new WifiScanner.ScanSettings();
+        WifiScanner.ScanSettings scanSettings =
+                createRequest(WifiScanner.WIFI_BAND_ALL, 0, 0, 20,
+                        WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
 
         // send single scan request (ignoreLocationSettings == true).
         scanSettings.ignoreLocationSettings = true;
@@ -2755,6 +2940,7 @@
         Message message = Message.obtain();
         message.what = WifiScanner.CMD_START_SINGLE_SCAN;
         message.obj = bundle;
+        message.arg2 = requestId;
         controlChannel.sendMessage(message);
         mLooper.dispatchAll();
 
@@ -2762,6 +2948,8 @@
         verify(mWifiPermissionsUtil).enforceCanAccessScanResultsForWifiScanner(
                 eq(TEST_PACKAGE_NAME), eq(TEST_FEATURE_ID), eq(Binder.getCallingUid()), eq(true),
                 eq(false));
+        verifySuccessfulResponse(order, handler, requestId);
+        verify(mWifiManager).setEmergencyScanRequestInProgress(true);
 
         // send single scan request (ignoreLocationSettings == false).
         scanSettings.ignoreLocationSettings = false;
@@ -2769,6 +2957,7 @@
         message = Message.obtain();
         message.what = WifiScanner.CMD_START_SINGLE_SCAN;
         message.obj = bundle;
+        message.arg2 = requestId;
         controlChannel.sendMessage(message);
         mLooper.dispatchAll();
 
@@ -2776,6 +2965,8 @@
         verify(mWifiPermissionsUtil).enforceCanAccessScanResultsForWifiScanner(
                 eq(TEST_PACKAGE_NAME), eq(TEST_FEATURE_ID), eq(Binder.getCallingUid()), eq(false),
                 eq(false));
+        verifySuccessfulResponse(order, handler, requestId);
+        verify(mWifiManager, times(1)).setEmergencyScanRequestInProgress(true);
 
         // send background scan request (ignoreLocationSettings == true).
         scanSettings.ignoreLocationSettings = true;
@@ -2931,6 +3122,8 @@
         controlChannel.sendMessage(Message.obtain(null, WifiScanner.CMD_DISABLE));
         mLooper.dispatchAll();
 
+        verify(mLastCallerInfoManager).put(eq(LastCallerInfoManager.SCANNING_ENABLED), anyInt(),
+                anyInt(), anyInt(), any(), eq(false));
         verify(mWifiScannerImpl0).cleanup();
     }
 
@@ -3029,12 +3222,12 @@
         when(mWifiNative.getClientInterfaceNames())
                 .thenReturn(new ArraySet<>(Arrays.asList(TEST_IFACE_NAME_0, TEST_IFACE_NAME_1)));
         WifiScanner.ScanSettings requestSettings = createRequest(
-                WifiScanner.WIFI_BAND_BOTH_WITH_DFS, 0, 0, 20,
+                WifiScanner.WIFI_BAND_ALL, 0, 0, 20,
                 WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
         doSuccessfulSingleScanOnImpls(requestSettings,
                 computeSingleScanNativeSettings(requestSettings),
-                ScanResults.create(0, WifiScanner.WIFI_BAND_BOTH_WITH_DFS, 2412),
-                ScanResults.create(0, WifiScanner.WIFI_BAND_BOTH_WITH_DFS, 5160));
+                ScanResults.create(0, WifiScanner.WIFI_BAND_ALL, 2412),
+                ScanResults.create(0, WifiScanner.WIFI_BAND_ALL, 5160));
     }
 
     /**
@@ -3106,8 +3299,8 @@
                 "Failed to start single scan", messageCaptor.getAllValues().get(1));
         verifyNoMoreInteractions(mBatteryStats);
 
-        assertEquals(mWifiMetrics.getOneshotScanCount(), 1);
-        assertEquals(mWifiMetrics.getScanReturnEntry(WifiMetricsProto.WifiLog.SCAN_UNKNOWN), 1);
+        verify(mWifiMetrics).incrementOneshotScanCount();
+        verify(mWifiMetrics).incrementScanReturnEntry(WifiMetricsProto.WifiLog.SCAN_UNKNOWN, 1);
         assertDumpContainsRequestLog("addSingleScanRequest", requestId);
     }
 
@@ -3154,7 +3347,7 @@
         verify(mBatteryStats).reportWifiScanStartedFromSource(eq(workSource));
 
         when(mWifiScannerImpl0.getLatestSingleScanResults())
-                .thenReturn(new WifiScanner.ScanData(DUMMY_SCAN_DATA));
+                .thenReturn(new WifiScanner.ScanData(PLACEHOLDER_SCAN_DATA));
         // Send scan success on impl1
         when(mWifiScannerImpl1.getLatestSingleScanResults())
                 .thenReturn(results.getRawScanData());
@@ -3218,8 +3411,8 @@
                 WifiScanner.REASON_UNSPECIFIED, "Scan failed");
         assertDumpContainsCallbackLog("singleScanFailed", requestId,
                 "reason=" + WifiScanner.REASON_UNSPECIFIED + ", Scan failed");
-        assertEquals(mWifiMetrics.getOneshotScanCount(), 1);
-        assertEquals(mWifiMetrics.getScanReturnEntry(WifiMetricsProto.WifiLog.SCAN_UNKNOWN), 1);
+        verify(mWifiMetrics).incrementOneshotScanCount();
+        verify(mWifiMetrics).incrementScanReturnEntry(WifiMetricsProto.WifiLog.SCAN_UNKNOWN, 1);
         verify(mBatteryStats).reportWifiScanStoppedFromSource(eq(workSource));
     }
 
@@ -3266,7 +3459,7 @@
 
         // then fails to execute on impl0
         when(mWifiScannerImpl0.getLatestSingleScanResults())
-                .thenReturn(new WifiScanner.ScanData(DUMMY_SCAN_DATA));
+                .thenReturn(new WifiScanner.ScanData(PLACEHOLDER_SCAN_DATA));
         eventHandler0.onScanStatus(WifiNative.WIFI_SCAN_FAILED);
         // but succeeds on impl1
         when(mWifiScannerImpl1.getLatestSingleScanResults())
@@ -3563,4 +3756,126 @@
 
         assertTrue(actual.isEmpty());
     }
+
+    private WifiScanner.ScanSettings triggerEmergencySingleScanAndVerify(WorkSource ws,
+            int requestId, InOrder order, BidirectionalAsyncChannel controlChannel, Handler handler)
+            throws Exception {
+        WifiScanner.ScanSettings requestSettings =
+                createRequest(WifiScanner.WIFI_BAND_ALL, 0, 0, 20,
+                        WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
+        requestSettings.ignoreLocationSettings = true; // set emergency scan flag.
+        sendSingleScanRequest(controlChannel, requestId, requestSettings, ws);
+        mLooper.dispatchAll();
+
+        // Scan is successfully queued
+        verifySuccessfulResponse(order, handler, requestId);
+        return requestSettings;
+    }
+
+    private void sendEmergencySingleScanResultsAndVerify(
+            WorkSource ws, int requestId, WifiScanner.ScanSettings requestSettings,
+            ScanResults results, InOrder order, Handler handler) throws Exception {
+        // verify scan start
+        WifiNative.ScanEventHandler eventHandler =
+                verifyStartSingleScan(order, computeSingleScanNativeSettings(requestSettings));
+        verify(mBatteryStats).reportWifiScanStartedFromSource(eq(ws));
+
+        // dispatch scan results
+        when(mWifiScannerImpl0.getLatestSingleScanResults()).thenReturn(results.getScanData());
+        eventHandler.onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
+        mLooper.dispatchAll();
+
+        verifyScanResultsReceived(order, handler, requestId, results.getScanData());
+        verifySingleScanCompletedReceived(order, handler, requestId);
+        verify(mBatteryStats).reportWifiScanStoppedFromSource(eq(ws));
+    }
+
+    @Test
+    public void startServiceAndTriggerEmergencySingleScanWithoutDriverLoaded() throws Exception {
+        mWifiScanningServiceImpl.startService();
+        mLooper.dispatchAll();
+        mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
+
+        Handler handler = mock(Handler.class);
+        BidirectionalAsyncChannel controlChannel = connectChannel(handler);
+        InOrder order = inOrder(handler, mWifiScannerImpl0);
+        int requestId = 122;
+        WorkSource ws = new WorkSource(2292);
+        ScanResults results = ScanResults.create(0, WifiScanner.WIFI_BAND_ALL, 2412);
+
+        // successful start
+        when(mWifiScannerImpl0.startSingleScan(any(WifiNative.ScanSettings.class),
+                any(WifiNative.ScanEventHandler.class))).thenReturn(true);
+
+        // emergency scan request
+        WifiScanner.ScanSettings requestSettings =
+                triggerEmergencySingleScanAndVerify(ws, requestId, order, controlChannel, handler);
+
+        // Indicate start of emergency scan.
+        verify(mWifiManager).setEmergencyScanRequestInProgress(true);
+
+        // Now simulate WifiManager enabling scanning.
+        setupAndLoadDriver(controlChannel, TEST_MAX_SCAN_BUCKETS_IN_CAPABILITIES);
+
+        // Send native results & verify
+        sendEmergencySingleScanResultsAndVerify(
+                ws, requestId, requestSettings, results, order, handler);
+
+        // Ensure that we indicate the end of emergency scan processing after the timeout.
+        mAlarmManager.dispatch(EMERGENCY_SCAN_END_INDICATION_ALARM_TAG);
+        mLooper.dispatchAll();
+        verify(mWifiManager).setEmergencyScanRequestInProgress(false);
+    }
+
+    @Test
+    public void startServiceAndTriggerEmergencySuccessiveSingleScanWithoutDriverLoaded()
+            throws Exception {
+        mWifiScanningServiceImpl.startService();
+        mLooper.dispatchAll();
+        mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
+
+        Handler handler = mock(Handler.class);
+        BidirectionalAsyncChannel controlChannel = connectChannel(handler);
+        InOrder order = inOrder(handler, mWifiScannerImpl0);
+        int requestId = 122;
+        WorkSource ws = new WorkSource(2292);
+        ScanResults results = ScanResults.create(0, WifiScanner.WIFI_BAND_ALL, 2412);
+
+        // successful start
+        when(mWifiScannerImpl0.startSingleScan(any(WifiNative.ScanSettings.class),
+                any(WifiNative.ScanEventHandler.class))).thenReturn(true);
+
+        // emergency scan request 1
+        WifiScanner.ScanSettings requestSettings1 =
+                triggerEmergencySingleScanAndVerify(ws, requestId, order, controlChannel, handler);
+
+        // Indicate start of emergency scan.
+        verify(mWifiManager).setEmergencyScanRequestInProgress(true);
+
+        // Now simulate WifiManager enabling scanning.
+        setupAndLoadDriver(controlChannel, TEST_MAX_SCAN_BUCKETS_IN_CAPABILITIES);
+
+        // Send native results & verify
+        sendEmergencySingleScanResultsAndVerify(
+                ws, requestId, requestSettings1, results, order, handler);
+
+        // emergency scan request 2
+        clearInvocations(mBatteryStats);
+        order = inOrder(handler, mWifiScannerImpl0);
+
+        WifiScanner.ScanSettings requestSettings2 =
+                triggerEmergencySingleScanAndVerify(ws, requestId, order, controlChannel, handler);
+
+        // Indicate start of emergency scan.
+        verify(mWifiManager, times(2)).setEmergencyScanRequestInProgress(true);
+
+        // Send native results 2 & verify
+        sendEmergencySingleScanResultsAndVerify(
+                ws, requestId, requestSettings2, results, order, handler);
+
+        // Ensure that we indicate only 1 end of emergency scan processing after the timeout.
+        mAlarmManager.dispatch(EMERGENCY_SCAN_END_INDICATION_ALARM_TAG);
+        mLooper.dispatchAll();
+        verify(mWifiManager, times(1)).setEmergencyScanRequestInProgress(false);
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/scanner/WificondPnoScannerTest.java b/service/tests/wifitests/src/com/android/server/wifi/scanner/WificondPnoScannerTest.java
index 152095c..5f1ff3b 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/scanner/WificondPnoScannerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/scanner/WificondPnoScannerTest.java
@@ -78,9 +78,9 @@
                 new int[]{2400, 2450},
                 new int[]{5150, 5175},
                 new int[]{5600, 5650},
-                new int[]{5945, 5985});
+                new int[]{5945, 5985},
+                new int[]{58320, 60480});
 
-        when(mWifiNative.getClientInterfaceName()).thenReturn(IFACE_NAME);
         when(mContext.getSystemService(Context.ALARM_SERVICE))
                 .thenReturn(mAlarmManager.getAlarmManager());
         when(mContext.getResources()).thenReturn(mResources);
@@ -188,7 +188,8 @@
                     10000,
                     WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN,
                     allChannelsScanned
-                            ? WifiScanner.WIFI_BAND_BOTH_WITH_DFS : WifiScanner.WIFI_BAND_24_GHZ)
+                            ? WifiScanner.WIFI_BAND_24_5_WITH_DFS_6_60_GHZ
+                            : WifiScanner.WIFI_BAND_24_GHZ)
                 .build();
         return settings;
     }
@@ -202,7 +203,8 @@
             WifiNative.PnoSettings pnoSettings, WifiNative.ScanEventHandler scanEventHandler,
             WifiNative.PnoEventHandler pnoEventHandler) {
         // Scans succeed
-        when(mWifiNative.scan(eq(IFACE_NAME), anyInt(), any(), any(List.class))).thenReturn(true);
+        when(mWifiNative.scan(eq(IFACE_NAME), anyInt(), any(), any(List.class), anyBoolean()))
+                .thenReturn(true);
         when(mWifiNative.startPnoScan(eq(IFACE_NAME), any(WifiNative.PnoSettings.class)))
                 .thenReturn(true);
         when(mWifiNative.stopPnoScan(IFACE_NAME)).thenReturn(true);
diff --git a/service/tests/wifitests/src/com/android/server/wifi/scanner/WificondScannerTest.java b/service/tests/wifitests/src/com/android/server/wifi/scanner/WificondScannerTest.java
index e256e44..b0ea459 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/scanner/WificondScannerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/scanner/WificondScannerTest.java
@@ -64,7 +64,8 @@
                 new int[]{2400, 2450},
                 new int[]{5150, 5175},
                 new int[]{5600, 5650},
-                new int[]{5945, 5985});
+                new int[]{5945, 5985},
+                new int[]{58320, 60480});
         mWifiMonitorSpy = spy(mWifiMonitor);
         mScanner = new WificondScannerImpl(mContext, BaseWifiScannerImplTest.IFACE_NAME,
                 mWifiNative, mWifiMonitorSpy, new WificondChannelHelper(mWifiNative),
@@ -99,12 +100,38 @@
         mLooper.dispatchAll();
 
         // No scan is issued to WifiNative.
-        verify(mWifiNative, never()).scan(any(), anyInt(), any(), any(List.class));
+        verify(mWifiNative, never()).scan(any(), anyInt(), any(), any(List.class), anyBoolean());
         // A scan failed event must be reported.
         verify(eventHandler).onScanStatus(WifiNative.WIFI_SCAN_FAILED);
     }
 
     @Test
+    public void cleanupReportsFailureIfScanInProgress() {
+        when(mWifiNative.scan(eq(IFACE_NAME), anyInt(), any(), any(List.class), anyBoolean()))
+                .thenReturn(true);
+
+        // setup ongoing scan
+        WifiNative.ScanSettings settings = new NativeScanSettingsBuilder()
+                .withBasePeriod(10000) // ms
+                .withMaxApPerScan(10)
+                .addBucketWithBand(10000 /* ms */, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN,
+                        WifiScanner.WIFI_BAND_5_GHZ)
+                .build();
+        WifiNative.ScanEventHandler eventHandler = mock(WifiNative.ScanEventHandler.class);
+        mScanner.startSingleScan(settings, eventHandler);
+        mLooper.dispatchAll();
+
+        // no scan failure message
+        verify(eventHandler, never()).onScanStatus(WifiNative.WIFI_SCAN_FAILED);
+
+        // tear down the iface
+        mScanner.cleanup();
+
+        // verify received scan failure callback
+        verify(eventHandler).onScanStatus(WifiNative.WIFI_SCAN_FAILED);
+    }
+
+    @Test
     public void externalScanResultsDoNotCauseSpuriousTimerCancellationOrCrash() {
         mWifiMonitor.sendMessage(IFACE_NAME, WifiMonitor.SCAN_RESULTS_EVENT);
         mLooper.dispatchAll();
@@ -129,7 +156,7 @@
                 expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ),
                 new ArrayList<String>(),
                 ScanResults.create(0, WifiScanner.WIFI_BAND_24_GHZ,
-                        2400, 2450, 2450, 2400, 2450, 2450, 2400, 2450, 2450), false);
+                        2400, 2450, 2450, 2400, 2450, 2450, 2400, 2450, 2450), false, false);
 
         mWifiMonitor.sendMessage(IFACE_NAME, WifiMonitor.SCAN_RESULTS_EVENT);
         mLooper.dispatchAll();
@@ -151,7 +178,8 @@
                 .build();
 
         // Kick off a scan
-        when(mWifiNative.scan(eq(IFACE_NAME), anyInt(), any(), any(List.class))).thenReturn(true);
+        when(mWifiNative.scan(eq(IFACE_NAME), anyInt(), any(), any(List.class), anyBoolean()))
+                .thenReturn(true);
         WifiNative.ScanEventHandler eventHandler = mock(WifiNative.ScanEventHandler.class);
         assertTrue(mScanner.startSingleScan(settings, eventHandler));
         mLooper.dispatchAll();
@@ -220,7 +248,8 @@
         WifiNative.ScanEventHandler eventHandler = mock(WifiNative.ScanEventHandler.class);
         InOrder order = inOrder(eventHandler, mWifiNative);
         // scan succeeds
-        when(mWifiNative.scan(eq(IFACE_NAME), anyInt(), any(), any(List.class))).thenReturn(true);
+        when(mWifiNative.scan(eq(IFACE_NAME), anyInt(), any(), any(List.class), anyBoolean()))
+                .thenReturn(true);
         when(mWifiNative.getScanResults(eq(IFACE_NAME))).thenReturn(rawScanResults);
 
         int ap_count = rawScanResults.size();
@@ -255,7 +284,8 @@
         // Trigger a scan to update mNativeScanResults in WificondScannerImpl.
         assertTrue(mScanner.startSingleScan(settings, eventHandler));
         Set<Integer> expectedScan = expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ);
-        order.verify(mWifiNative).scan(eq(IFACE_NAME), anyInt(), eq(expectedScan), any(List.class));
+        order.verify(mWifiNative).scan(eq(IFACE_NAME), anyInt(), eq(expectedScan), any(List.class),
+                anyBoolean());
 
         // Notify scan has finished
         mWifiMonitor.sendMessage(eq(IFACE_NAME), WifiMonitor.SCAN_RESULTS_EVENT);
diff --git a/service/tests/wifitests/src/com/android/server/wifi/util/ApConfigUtilTest.java b/service/tests/wifitests/src/com/android/server/wifi/util/ApConfigUtilTest.java
index df85aa9..6d75580 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/util/ApConfigUtilTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/util/ApConfigUtilTest.java
@@ -20,22 +20,30 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.content.res.Resources;
 import android.net.MacAddress;
+import android.net.wifi.CoexUnsafeChannel;
 import android.net.wifi.ScanResult;
 import android.net.wifi.SoftApCapability;
 import android.net.wifi.SoftApConfiguration;
 import android.net.wifi.SoftApConfiguration.Builder;
 import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
 import android.net.wifi.WifiScanner;
+import android.util.SparseIntArray;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.WifiBaseTest;
 import com.android.server.wifi.WifiNative;
+import com.android.server.wifi.coex.CoexManager;
 import com.android.wifi.resources.R;
 
 import org.junit.Before;
@@ -114,17 +122,26 @@
             /* Now some 6GHz channels */
             5955, SoftApConfiguration.BAND_6GHZ, 1,
             5970, SoftApConfiguration.BAND_6GHZ, 4,
-            6110, SoftApConfiguration.BAND_6GHZ, 32
+            6110, SoftApConfiguration.BAND_6GHZ, 32,
+            /* some 60GHz channels */
+            58320, SoftApConfiguration.BAND_60GHZ, 1,
+            60480, SoftApConfiguration.BAND_60GHZ, 2,
+            62640, SoftApConfiguration.BAND_60GHZ, 3,
+            64800, SoftApConfiguration.BAND_60GHZ, 4,
+            66960, SoftApConfiguration.BAND_60GHZ, 5,
+            69120, SoftApConfiguration.BAND_60GHZ, 6,
     };
 
     private static final int[] EMPTY_CHANNEL_LIST = {};
     private static final int[] ALLOWED_2G_FREQS = {2462}; //ch# 11
     private static final int[] ALLOWED_5G_FREQS = {5745, 5765}; //ch# 149, 153
     private static final int[] ALLOWED_6G_FREQS = {5945, 5965};
+    private static final int[] ALLOWED_60G_FREQS = {58320, 60480}; // ch# 1, 2
 
     @Mock Context mContext;
     @Mock Resources mResources;
     @Mock WifiNative mWifiNative;
+    @Mock CoexManager mCoexManager;
 
     /**
      * Setup test.
@@ -218,7 +235,7 @@
     public void isBandValidFailure() throws Exception {
         assertFalse(ApConfigUtil.isBandValid(0));
         assertFalse(ApConfigUtil.isBandValid(SoftApConfiguration.BAND_2GHZ
-                  | SoftApConfiguration.BAND_6GHZ | 0x0F));
+                  | SoftApConfiguration.BAND_6GHZ | 0x1F));
     }
 
     /**
@@ -276,9 +293,9 @@
         when(mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_24_GHZ))
                 .thenReturn(allowed2gChannels);
         int freq = ApConfigUtil.chooseApChannel(SoftApConfiguration.BAND_2GHZ, mWifiNative,
-                mResources);
+                mCoexManager, mResources);
         assertEquals(ApConfigUtil.DEFAULT_AP_CHANNEL,
-                ScanResult.convertFrequencyMhzToChannel(freq));
+                ScanResult.convertFrequencyMhzToChannelIfSupported(freq));
     }
 
     /**
@@ -292,7 +309,7 @@
                 .thenReturn(ALLOWED_2G_FREQS); // ch#11
 
         int freq = ApConfigUtil.chooseApChannel(SoftApConfiguration.BAND_2GHZ, mWifiNative,
-                mResources);
+                mCoexManager, mResources);
         assertEquals(2462, freq);
     }
 
@@ -307,11 +324,26 @@
                 .thenReturn(ALLOWED_5G_FREQS); //ch# 149, 153
 
         int freq = ApConfigUtil.chooseApChannel(
-                SoftApConfiguration.BAND_5GHZ, mWifiNative, mResources);
+                SoftApConfiguration.BAND_5GHZ, mWifiNative, mCoexManager, mResources);
         assertTrue(ArrayUtils.contains(ALLOWED_5G_FREQS, freq));
     }
 
     /**
+     * Verify a 60G channel is selected from the list of allowed channels.
+     */
+    @Test
+    public void chooseApChannel60GBandWithAllowedChannels() throws Exception {
+        when(mResources.getString(R.string.config_wifiSoftap60gChannelList))
+                .thenReturn("1-2");
+        when(mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_60_GHZ))
+                .thenReturn(ALLOWED_60G_FREQS); //ch# 1, 2
+
+        int freq = ApConfigUtil.chooseApChannel(
+                SoftApConfiguration.BAND_60GHZ, mWifiNative, mCoexManager, mResources);
+        assertTrue(ArrayUtils.contains(ALLOWED_60G_FREQS, freq));
+    }
+
+    /**
      * Verify chooseApChannel failed when selecting a channel in 5GHz band
      * with no channels allowed.
      */
@@ -320,7 +352,7 @@
         when(mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ))
                 .thenReturn(EMPTY_CHANNEL_LIST);
         assertEquals(-1, ApConfigUtil.chooseApChannel(SoftApConfiguration.BAND_5GHZ, mWifiNative,
-                mResources));
+                mCoexManager, mResources));
     }
 
     /**
@@ -339,10 +371,107 @@
 
         int freq = ApConfigUtil.chooseApChannel(
                 SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ,
-                mWifiNative, mResources);
+                mWifiNative, mCoexManager, mResources);
         assertTrue(ArrayUtils.contains(ALLOWED_5G_FREQS, freq));
     }
 
+    /**
+     * Verify chooseSoftAp will select a high band safe channel over a higher band unsafe channel.
+     */
+    @Test
+    public void chooseApChannelWithUnsafeChannelsPreferSafe() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        when(mResources.getString(R.string.config_wifiSoftap2gChannelList))
+            .thenReturn("1, 6, 11");
+        when(mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_24_GHZ))
+                .thenReturn(ALLOWED_2G_FREQS); // ch#11
+        when(mResources.getString(R.string.config_wifiSoftap5gChannelList))
+                .thenReturn("149, 153");
+        when(mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ))
+                .thenReturn(ALLOWED_5G_FREQS); //ch# 149, 153
+        when(mCoexManager.getCoexUnsafeChannels()).thenReturn(Arrays.asList(
+                new CoexUnsafeChannel(WifiScanner.WIFI_BAND_5_GHZ, 149),
+                new CoexUnsafeChannel(WifiScanner.WIFI_BAND_5_GHZ, 153)
+        ));
+        // Test with soft unsafe channels
+        when(mCoexManager.getCoexRestrictions()).thenReturn(0);
+
+        int freq = ApConfigUtil.chooseApChannel(
+                SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ,
+                mWifiNative, mCoexManager, mResources);
+
+        assertTrue(ArrayUtils.contains(ALLOWED_2G_FREQS, freq));
+
+        // Test with hard unsafe channels
+        when(mCoexManager.getCoexRestrictions()).thenReturn(WifiManager.COEX_RESTRICTION_SOFTAP);
+
+        freq = ApConfigUtil.chooseApChannel(
+                SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ,
+                mWifiNative, mCoexManager, mResources);
+
+        assertTrue(ArrayUtils.contains(ALLOWED_2G_FREQS, freq));
+    }
+
+    /**
+     * Verify chooseSoftAp will select a high band unsafe channel if all channels are soft unsafe.
+     */
+    @Test
+    public void chooseApChannelWithAllSoftUnsafePreferHighBand() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        when(mResources.getString(R.string.config_wifiSoftap2gChannelList))
+                .thenReturn("1, 6, 11");
+        when(mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_24_GHZ))
+                .thenReturn(ALLOWED_2G_FREQS); // ch#11
+        when(mResources.getString(R.string.config_wifiSoftap5gChannelList))
+                .thenReturn("149, 153");
+        when(mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ))
+                .thenReturn(ALLOWED_5G_FREQS); //ch# 149, 153
+        when(mCoexManager.getCoexUnsafeChannels()).thenReturn(Arrays.asList(
+                new CoexUnsafeChannel(WifiScanner.WIFI_BAND_24_GHZ, 1),
+                new CoexUnsafeChannel(WifiScanner.WIFI_BAND_24_GHZ, 6),
+                new CoexUnsafeChannel(WifiScanner.WIFI_BAND_24_GHZ, 11),
+                new CoexUnsafeChannel(WifiScanner.WIFI_BAND_5_GHZ, 149),
+                new CoexUnsafeChannel(WifiScanner.WIFI_BAND_5_GHZ, 153)
+        ));
+        when(mCoexManager.getCoexRestrictions()).thenReturn(0);
+
+        int freq = ApConfigUtil.chooseApChannel(
+                SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ,
+                mWifiNative, mCoexManager, mResources);
+
+        assertTrue(ArrayUtils.contains(ALLOWED_5G_FREQS, freq));
+    }
+
+    /**
+     * Verify chooseSoftAp will select the default channel if all allowed channels are hard unsafe.
+     */
+    @Test
+    public void chooseApChannelWithAllHardUnsafeSelectDefault() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        when(mResources.getString(R.string.config_wifiSoftap2gChannelList))
+                .thenReturn("1, 6, 11");
+        when(mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_24_GHZ))
+                .thenReturn(ALLOWED_2G_FREQS); // ch#11
+        when(mResources.getString(R.string.config_wifiSoftap5gChannelList))
+                .thenReturn("149, 153");
+        when(mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ))
+                .thenReturn(ALLOWED_5G_FREQS); //ch# 149, 153
+        when(mCoexManager.getCoexUnsafeChannels()).thenReturn(Arrays.asList(
+                new CoexUnsafeChannel(WifiScanner.WIFI_BAND_24_GHZ, 1),
+                new CoexUnsafeChannel(WifiScanner.WIFI_BAND_24_GHZ, 6),
+                new CoexUnsafeChannel(WifiScanner.WIFI_BAND_24_GHZ, 11),
+                new CoexUnsafeChannel(WifiScanner.WIFI_BAND_5_GHZ, 149),
+                new CoexUnsafeChannel(WifiScanner.WIFI_BAND_5_GHZ, 153)
+        ));
+        when(mCoexManager.getCoexRestrictions()).thenReturn(WifiManager.COEX_RESTRICTION_SOFTAP);
+
+        int freq = ApConfigUtil.chooseApChannel(
+                SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ,
+                mWifiNative, mCoexManager, mResources);
+
+        assertEquals(freq, ApConfigUtil.convertChannelToFrequency(
+                ApConfigUtil.DEFAULT_AP_CHANNEL, ApConfigUtil.DEFAULT_AP_BAND));
+    }
 
     /**
      * Verify default band and channel is used when HAL support is
@@ -355,8 +484,8 @@
 
         when(mWifiNative.isHalStarted()).thenReturn(false);
         assertEquals(ApConfigUtil.SUCCESS,
-                ApConfigUtil.updateApChannelConfig(mWifiNative, mResources, TEST_COUNTRY_CODE,
-                configBuilder, configBuilder.build(), false));
+                ApConfigUtil.updateApChannelConfig(mWifiNative, mCoexManager, mResources,
+                        TEST_COUNTRY_CODE, configBuilder, configBuilder.build(), false));
         /* Verify default band and channel is used. */
         assertEquals(ApConfigUtil.DEFAULT_AP_BAND, configBuilder.build().getBand());
         assertEquals(ApConfigUtil.DEFAULT_AP_CHANNEL, configBuilder.build().getChannel());
@@ -372,8 +501,8 @@
         configBuilder.setBand(SoftApConfiguration.BAND_5GHZ);
         when(mWifiNative.isHalStarted()).thenReturn(true);
         assertEquals(ApConfigUtil.ERROR_GENERIC,
-                ApConfigUtil.updateApChannelConfig(mWifiNative, mResources, null,
-                configBuilder, configBuilder.build(), false));
+                ApConfigUtil.updateApChannelConfig(mWifiNative, mCoexManager, mResources, null,
+                        configBuilder, configBuilder.build(), false));
     }
 
     /**
@@ -385,8 +514,8 @@
         configBuilder.setChannel(36, SoftApConfiguration.BAND_5GHZ);
         when(mWifiNative.isHalStarted()).thenReturn(true);
         assertEquals(ApConfigUtil.SUCCESS,
-                ApConfigUtil.updateApChannelConfig(mWifiNative, mResources, TEST_COUNTRY_CODE,
-                configBuilder, configBuilder.build(), false));
+                ApConfigUtil.updateApChannelConfig(mWifiNative, mCoexManager, mResources,
+                        TEST_COUNTRY_CODE, configBuilder, configBuilder.build(), false));
         assertEquals(SoftApConfiguration.BAND_5GHZ, configBuilder.build().getBand());
         assertEquals(36, configBuilder.build().getChannel());
     }
@@ -403,8 +532,8 @@
         when(mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ))
                 .thenReturn(EMPTY_CHANNEL_LIST);
         assertEquals(ApConfigUtil.ERROR_NO_CHANNEL,
-                ApConfigUtil.updateApChannelConfig(mWifiNative, mResources, TEST_COUNTRY_CODE,
-                configBuilder, configBuilder.build(), false));
+                ApConfigUtil.updateApChannelConfig(mWifiNative, mCoexManager, mResources,
+                        TEST_COUNTRY_CODE, configBuilder, configBuilder.build(), false));
     }
 
     /**
@@ -426,8 +555,8 @@
                 .thenReturn(ALLOWED_5G_FREQS); // ch# 149, 153
         when(mWifiNative.isHalStarted()).thenReturn(true);
         assertEquals(ApConfigUtil.SUCCESS,
-                ApConfigUtil.updateApChannelConfig(mWifiNative, mResources, TEST_COUNTRY_CODE,
-                configBuilder, configBuilder.build(), false));
+                ApConfigUtil.updateApChannelConfig(mWifiNative, mCoexManager, mResources,
+                        TEST_COUNTRY_CODE, configBuilder, configBuilder.build(), false));
         assertEquals(SoftApConfiguration.BAND_5GHZ, configBuilder.build().getBand());
         assertEquals(149, configBuilder.build().getChannel());
     }
@@ -441,9 +570,10 @@
         Builder configBuilder = new SoftApConfiguration.Builder();
         configBuilder.setBand(SoftApConfiguration.BAND_5GHZ | SoftApConfiguration.BAND_2GHZ);
         when(mWifiNative.isHalStarted()).thenReturn(true);
+        when(mWifiNative.getChannelsForBand(anyInt())).thenReturn(new int[0]);
         assertEquals(ApConfigUtil.SUCCESS,
-                ApConfigUtil.updateApChannelConfig(mWifiNative, mResources, TEST_COUNTRY_CODE,
-                configBuilder, configBuilder.build(), true));
+                ApConfigUtil.updateApChannelConfig(mWifiNative, mCoexManager, mResources,
+                        TEST_COUNTRY_CODE, configBuilder, configBuilder.build(), true));
         assertEquals(SoftApConfiguration.BAND_5GHZ | SoftApConfiguration.BAND_2GHZ,
                 configBuilder.build().getBand());
         assertEquals(0, configBuilder.build().getChannel());
@@ -451,7 +581,9 @@
 
     @Test
     public void testSoftApCapabilityInitWithResourceValue() throws Exception {
-        long testFeatures = SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT;
+        long testFeatures = SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT
+                | SoftApCapability.SOFTAP_FEATURE_BAND_6G_SUPPORTED
+                | SoftApCapability.SOFTAP_FEATURE_BAND_60G_SUPPORTED;
         SoftApCapability capability = new SoftApCapability(testFeatures);
         int test_max_client = 10;
         capability.setMaxSupportedClients(test_max_client);
@@ -463,6 +595,10 @@
                 .thenReturn(false);
         when(mResources.getBoolean(R.bool.config_wifiSofapClientForceDisconnectSupported))
                 .thenReturn(true);
+        when(mResources.getBoolean(R.bool.config_wifi6ghzSupport)).thenReturn(true);
+        when(mResources.getBoolean(R.bool.config_wifi60ghzSupport)).thenReturn(true);
+        when(mResources.getBoolean(R.bool.config_wifiSoftap6ghzSupported)).thenReturn(true);
+        when(mResources.getBoolean(R.bool.config_wifiSoftap60ghzSupported)).thenReturn(true);
         assertEquals(ApConfigUtil.updateCapabilityFromResource(mContext),
                 capability);
     }
@@ -476,12 +612,23 @@
         assertNull(ApConfigUtil.fromWifiConfiguration(wifiConfig));
     }
 
+    @Test
+    public void testConvertInvalidKeyMgmtWifiConfigurationToSoftApConfiguration()
+            throws Exception {
+        WifiConfiguration wifiConfig = new WifiConfiguration();
+        wifiConfig.SSID = "AndroidAP";
+        wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
+        wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA2_PSK);
+        wifiConfig.preSharedKey = "12345678";
+        assertNull(ApConfigUtil.fromWifiConfiguration(wifiConfig));
+    }
+
 
     @Test
     public void testCheckConfigurationChangeNeedToRestart() throws Exception {
+        MacAddress testBssid = MacAddress.fromString("aa:22:33:44:55:66");
         SoftApConfiguration currentConfig = new SoftApConfiguration.Builder()
                 .setSsid("TestSSid")
-                .setBssid(MacAddress.fromString("11:22:33:44:55:66"))
                 .setPassphrase("testpassphrase", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
                 .setBand(SoftApConfiguration.BAND_2GHZ)
                 .setChannel(11, SoftApConfiguration.BAND_2GHZ)
@@ -492,7 +639,6 @@
         // DO NOT use copy constructor to copy to test since it's instance is the same.
         SoftApConfiguration newConfig_noChange = new SoftApConfiguration.Builder()
                 .setSsid("TestSSid")
-                .setBssid(MacAddress.fromString("11:22:33:44:55:66"))
                 .setPassphrase("testpassphrase", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
                 .setBand(SoftApConfiguration.BAND_2GHZ)
                 .setChannel(11, SoftApConfiguration.BAND_2GHZ)
@@ -510,7 +656,7 @@
         // Test BSSID changed
         SoftApConfiguration newConfig_bssidChanged = new SoftApConfiguration
                 .Builder(newConfig_noChange)
-                .setBssid(MacAddress.fromString("aa:bb:cc:dd:ee:ff")).build();
+                .setBssid(testBssid).build();
         assertTrue(ApConfigUtil.checkConfigurationChangeNeedToRestart(currentConfig,
                 newConfig_bssidChanged));
         // Test Passphrase Changed
@@ -539,6 +685,24 @@
                 .setBand(SoftApConfiguration.BAND_5GHZ).build();
         assertTrue(ApConfigUtil.checkConfigurationChangeNeedToRestart(currentConfig,
                 newConfig_bandChanged));
+        if (SdkLevel.isAtLeastS()) {
+            // Test Bands Changed
+            int[] bands = {SoftApConfiguration.BAND_2GHZ , SoftApConfiguration.BAND_5GHZ};
+            SoftApConfiguration newConfig_bandsChanged = new SoftApConfiguration
+                    .Builder(newConfig_noChange)
+                    .setBands(bands).build();
+            assertTrue(ApConfigUtil.checkConfigurationChangeNeedToRestart(currentConfig,
+                    newConfig_bandsChanged));
+            // Test Channels Changed
+            SparseIntArray dual_channels = new SparseIntArray(2);
+            dual_channels.put(SoftApConfiguration.BAND_5GHZ, 149);
+            dual_channels.put(SoftApConfiguration.BAND_2GHZ, 0);
+            SoftApConfiguration newConfig_channelsChanged = new SoftApConfiguration
+                    .Builder(newConfig_noChange)
+                    .setChannels(dual_channels).build();
+            assertTrue(ApConfigUtil.checkConfigurationChangeNeedToRestart(currentConfig,
+                    newConfig_channelsChanged));
+        }
         // Test isHidden Changed
         SoftApConfiguration newConfig_hiddenChanged = new SoftApConfiguration
                 .Builder(newConfig_noChange)
@@ -557,5 +721,125 @@
                 .build();
         assertFalse(ApConfigUtil.checkConfigurationChangeNeedToRestart(currentConfig,
                 newConfig_nonRevalentChanged));
+
+    }
+
+    @Test
+    public void testIsAvailableChannelsOnTargetBands() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastS());
+        SoftApCapability testSoftApCapability = new SoftApCapability(0);
+        testSoftApCapability.setSupportedChannelList(
+                SoftApConfiguration.BAND_2GHZ, new int[] {1, 2});
+        testSoftApCapability.setSupportedChannelList(
+                SoftApConfiguration.BAND_5GHZ, new int[] {36, 149});
+
+        int testBand_2_5 = SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ;
+        int testBand_2_6 = SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_6GHZ;
+        int testBand_2_60 = SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_60GHZ;
+
+        assertEquals(testBand_2_5, ApConfigUtil.removeUnavailableBands(
+                testSoftApCapability, testBand_2_5, mCoexManager));
+        assertEquals(SoftApConfiguration.BAND_2GHZ, ApConfigUtil.removeUnavailableBands(
+                testSoftApCapability, testBand_2_6, mCoexManager));
+        assertEquals(SoftApConfiguration.BAND_2GHZ, ApConfigUtil.removeUnavailableBands(
+                testSoftApCapability, testBand_2_60, mCoexManager));
+        // Test with soft unsafe channels
+        when(mCoexManager.getCoexRestrictions()).thenReturn(0);
+        when(mCoexManager.getCoexUnsafeChannels()).thenReturn(Arrays.asList(
+                new CoexUnsafeChannel(WifiScanner.WIFI_BAND_24_GHZ, 1),
+                new CoexUnsafeChannel(WifiScanner.WIFI_BAND_5_GHZ, 36),
+                new CoexUnsafeChannel(WifiScanner.WIFI_BAND_5_GHZ, 149)
+        ));
+        assertEquals(testBand_2_5, ApConfigUtil.removeUnavailableBands(
+                testSoftApCapability, testBand_2_5, mCoexManager));
+
+        // Test with hard unsafe channels
+        when(mCoexManager.getCoexRestrictions()).thenReturn(WifiManager.COEX_RESTRICTION_SOFTAP);
+        assertEquals(SoftApConfiguration.BAND_2GHZ, ApConfigUtil.removeUnavailableBands(
+                testSoftApCapability, testBand_2_5, mCoexManager));
+
+
+    }
+
+    @Test
+    public void testCheckSupportAllConfiguration() throws Exception {
+        SoftApConfiguration.Builder testConfigBuilder = new SoftApConfiguration.Builder();
+        SoftApCapability mockSoftApCapability = mock(SoftApCapability.class);
+        assertTrue(ApConfigUtil.checkSupportAllConfiguration(testConfigBuilder.build(),
+                  mockSoftApCapability));
+
+
+        // Test client control feature
+        when(mockSoftApCapability.areFeaturesSupported(
+                SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT)).thenReturn(false);
+        // Set max client number
+        testConfigBuilder.setMaxNumberOfClients(1);
+        assertFalse(ApConfigUtil.checkSupportAllConfiguration(testConfigBuilder.build(),
+                mockSoftApCapability));
+        // Reset Max client number
+        testConfigBuilder.setMaxNumberOfClients(0);
+        // Set client control
+        testConfigBuilder.setClientControlByUserEnabled(true);
+        assertFalse(ApConfigUtil.checkSupportAllConfiguration(testConfigBuilder.build(),
+                mockSoftApCapability));
+        // Reset client control
+        testConfigBuilder.setClientControlByUserEnabled(false);
+        //
+        testConfigBuilder.setBlockedClientList(new ArrayList<>() {{
+                add(MacAddress.fromString("aa:bb:cc:dd:ee:ff")); }});
+        assertFalse(ApConfigUtil.checkSupportAllConfiguration(testConfigBuilder.build(),
+                mockSoftApCapability));
+
+        // Allow for client control
+        when(mockSoftApCapability.areFeaturesSupported(
+                SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT)).thenReturn(true);
+        assertTrue(ApConfigUtil.checkSupportAllConfiguration(testConfigBuilder.build(),
+                mockSoftApCapability));
+
+        // Test WPA3-SAE
+        when(mockSoftApCapability.areFeaturesSupported(
+                SoftApCapability.SOFTAP_FEATURE_WPA3_SAE)).thenReturn(false);
+        testConfigBuilder.setPassphrase("passphrase",
+                SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION);
+        assertFalse(ApConfigUtil.checkSupportAllConfiguration(testConfigBuilder.build(),
+                mockSoftApCapability));
+        testConfigBuilder.setPassphrase("passphrase",
+                SoftApConfiguration.SECURITY_TYPE_WPA3_SAE);
+        assertFalse(ApConfigUtil.checkSupportAllConfiguration(testConfigBuilder.build(),
+                mockSoftApCapability));
+
+        // Allow for SAE
+        when(mockSoftApCapability.areFeaturesSupported(
+                SoftApCapability.SOFTAP_FEATURE_WPA3_SAE)).thenReturn(true);
+        assertTrue(ApConfigUtil.checkSupportAllConfiguration(testConfigBuilder.build(),
+                mockSoftApCapability));
+        if (SdkLevel.isAtLeastS()) {
+            // Test 6G or 60G not support
+            testConfigBuilder.setChannels(
+                    new SparseIntArray(){{
+                        put(SoftApConfiguration.BAND_5GHZ, 149);
+                        put(SoftApConfiguration.BAND_6GHZ, 2);
+                    }});
+            assertFalse(ApConfigUtil.checkSupportAllConfiguration(testConfigBuilder.build(),
+                    mockSoftApCapability));
+
+            testConfigBuilder.setChannels(
+                    new SparseIntArray(){{
+                        put(SoftApConfiguration.BAND_5GHZ, 149);
+                        put(SoftApConfiguration.BAND_60GHZ, 1);
+                    }});
+            assertFalse(ApConfigUtil.checkSupportAllConfiguration(testConfigBuilder.build(),
+                    mockSoftApCapability));
+            // Test ACS not support in bridged mode
+            when(mockSoftApCapability.areFeaturesSupported(
+                    SoftApCapability.SOFTAP_FEATURE_ACS_OFFLOAD)).thenReturn(false);
+            testConfigBuilder.setChannels(
+                    new SparseIntArray(){{
+                        put(SoftApConfiguration.BAND_5GHZ, 0);
+                        put(SoftApConfiguration.BAND_2GHZ, 0);
+                    }});
+            assertFalse(ApConfigUtil.checkSupportAllConfiguration(testConfigBuilder.build(),
+                    mockSoftApCapability));
+        }
     }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/util/ExternalCallbackTrackerTest.java b/service/tests/wifitests/src/com/android/server/wifi/util/ExternalCallbackTrackerTest.java
deleted file mode 100644
index 4eee452..0000000
--- a/service/tests/wifitests/src/com/android/server/wifi/util/ExternalCallbackTrackerTest.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wifi.util;
-
-import static org.junit.Assert.*;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.verify;
-
-import android.net.wifi.ISoftApCallback;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.test.TestLooper;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import com.android.server.wifi.WifiBaseTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Unit tests for {@link com.android.server.wifi.util.ExternalCallbackTracker}.
- */
-@SmallTest
-public class ExternalCallbackTrackerTest extends WifiBaseTest {
-    private static final int TEST_CALLBACK_IDENTIFIER = 56;
-    @Mock Handler mHandler;
-    @Mock ISoftApCallback mCallback;
-    @Mock IBinder mBinder;
-    private TestLooper mTestLooper;
-
-    private ExternalCallbackTracker<ISoftApCallback> mExternalCallbackTracker;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mTestLooper = new TestLooper();
-        mHandler = new Handler(mTestLooper.getLooper());
-        mExternalCallbackTracker = new ExternalCallbackTracker<ISoftApCallback>(mHandler);
-    }
-
-    /**
-     * Test adding a callback.
-     */
-    @Test
-    public void testAddCallback() throws Exception {
-        assertTrue(mExternalCallbackTracker.add(mBinder, mCallback, TEST_CALLBACK_IDENTIFIER));
-        assertEquals(1, mExternalCallbackTracker.getNumCallbacks());
-        assertEquals(mCallback, mExternalCallbackTracker.getCallbacks().get(0));
-        verify(mBinder).linkToDeath(any(), anyInt());
-    }
-
-    /**
-     * Test that adding a callback returns failure when binder death linking fails.
-     */
-    @Test
-    public void testAddCallbackFailureOnLinkToDeath() throws Exception {
-        doThrow(new RemoteException()).when(mBinder).linkToDeath(any(), anyInt());
-        assertFalse(mExternalCallbackTracker.add(mBinder, mCallback, TEST_CALLBACK_IDENTIFIER));
-        assertEquals(0, mExternalCallbackTracker.getNumCallbacks());
-        assertTrue(mExternalCallbackTracker.getCallbacks().isEmpty());
-    }
-
-    /**
-     * Test removing a callback.
-     */
-    @Test
-    public void testRemoveCallback() throws Exception {
-        testAddCallback();
-
-        assertNotNull(mExternalCallbackTracker.remove(TEST_CALLBACK_IDENTIFIER));
-        assertEquals(0, mExternalCallbackTracker.getNumCallbacks());
-        assertTrue(mExternalCallbackTracker.getCallbacks().isEmpty());
-        verify(mBinder).unlinkToDeath(any(), anyInt());
-    }
-
-    /**
-     * Test removing a callback returns failure when the identifier provided doesn't match the one
-     * used to add the callback.
-     */
-    @Test
-    public void testRemoveCallbackFailureOnWrongIdentifier() throws Exception {
-        testAddCallback();
-
-        assertNull(mExternalCallbackTracker.remove(TEST_CALLBACK_IDENTIFIER + 5));
-        assertEquals(1, mExternalCallbackTracker.getNumCallbacks());
-        assertEquals(mCallback, mExternalCallbackTracker.getCallbacks().get(0));
-    }
-
-    /**
-     * Test that the callback is automatically removed when the associated binder object is dead.
-     */
-    @Test
-    public void testCallbackRemovalOnDeath() throws Exception {
-        assertTrue(mExternalCallbackTracker.add(mBinder, mCallback, TEST_CALLBACK_IDENTIFIER));
-
-        // Trigger the death.
-        ArgumentCaptor<IBinder.DeathRecipient> deathCaptor =
-                ArgumentCaptor.forClass(IBinder.DeathRecipient.class);
-        verify(mBinder).linkToDeath(deathCaptor.capture(), anyInt());
-        deathCaptor.getValue().binderDied();
-        mTestLooper.dispatchAll();
-
-        assertEquals(0, mExternalCallbackTracker.getNumCallbacks());
-        assertTrue(mExternalCallbackTracker.getCallbacks().isEmpty());
-        verify(mBinder).unlinkToDeath(any(), anyInt());
-    }
-}
diff --git a/service/tests/wifitests/src/com/android/server/wifi/util/InformationElementUtilTest.java b/service/tests/wifitests/src/com/android/server/wifi/util/InformationElementUtilTest.java
index 3d54e69..942adbc 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/util/InformationElementUtilTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/util/InformationElementUtilTest.java
@@ -294,6 +294,36 @@
                 testByteArray[3], results[0].bytes[0]);
     }
 
+    private void verifyCapabilityStringFromIes(
+            InformationElement[] ies, int beaconCap, boolean isOweSupported,
+            String capsStr) {
+        InformationElementUtil.Capabilities capabilities =
+                new InformationElementUtil.Capabilities();
+        capabilities.from(ies, beaconCap, isOweSupported, 2400);
+        String result = capabilities.generateCapabilitiesString();
+
+        assertEquals(capsStr, result);
+    }
+
+    private void verifyCapabilityStringFromIe(
+            InformationElement ie, int beaconCap, boolean isOweSupported,
+            String capsStr) {
+        InformationElement[] ies = new InformationElement[] { ie };
+        verifyCapabilityStringFromIes(new InformationElement[] { ie },
+                beaconCap, isOweSupported, capsStr);
+
+    }
+
+    private void verifyCapabilityStringFromIeWithoutOweSupported(
+            InformationElement ie, String capsStr) {
+        verifyCapabilityStringFromIe(ie, 0x1 << 4, false, capsStr);
+    }
+
+    private void verifyCapabilityStringFromIeWithOweSupported(
+            InformationElement ie, String capsStr) {
+        verifyCapabilityStringFromIe(ie, 0x1 << 4, true, capsStr);
+    }
+
     /**
      * Test Capabilities.generateCapabilitiesString() with a RSN IE.
      * Expect the function to return a string with the proper security information.
@@ -308,16 +338,168 @@
                                 (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x02,
                                 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x0F,
                                 (byte) 0xAC, (byte) 0x02, (byte) 0x00, (byte) 0x00 };
+        verifyCapabilityStringFromIeWithoutOweSupported(ie,
+                "[WPA2-PSK-CCMP+TKIP][RSN-PSK-CCMP+TKIP]");
+    }
 
-        InformationElement[] ies = new InformationElement[] { ie };
-        int beaconCap = 0x1 << 4;
+    /**
+     * Test Capabilities.generateCapabilitiesString() with a RSN IE which contains
+     * an unknown AKM.
+     * Expect the function to return a string with the proper security information.
+     */
+    @Test
+    public void buildCapabilities_rsnElementWithUnknownAkm() {
+        InformationElement ie = new InformationElement();
+        ie.id = InformationElement.EID_RSN;
+        ie.bytes = new byte[] { (byte) 0x01, (byte) 0x00, // Version
+                                (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x02, // TKIP
+                                (byte) 0x02, (byte) 0x00, // Pairwise cipher count
+                                (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x04, // CCMP
+                                (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x02, // TKIP
+                                (byte) 0x01, (byte) 0x00, // AKM count
+                                (byte) 0x00, (byte) 0x0F, (byte) 0x99, (byte) 0x99, // Unknown AKM
+                                (byte) 0x00, (byte) 0x00 // RSN capabilities
+        };
+        verifyCapabilityStringFromIeWithoutOweSupported(ie,
+                "[RSN-?-CCMP+TKIP]");
+    }
 
-        InformationElementUtil.Capabilities capabilities =
-                new InformationElementUtil.Capabilities();
-        capabilities.from(ies, beaconCap, false);
-        String result = capabilities.generateCapabilitiesString();
+    /**
+     * Test Capabilities.generateCapabilitiesString() with a RSN IE.
+     * Expect the function to return a string with the proper security information.
+     */
+    @Test
+    public void buildCapabilities_rsnElementWithGroupManagementCipher() {
+        InformationElement ie = new InformationElement();
+        ie.id = InformationElement.EID_RSN;
+        ie.bytes = new byte[] {
+                // Version
+                (byte) 0x01, (byte) 0x00,
+                // Group cipher suite: TKIP
+                (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x02,
+                // Pairwise cipher count
+                (byte) 0x02, (byte) 0x00,
+                // Pairwise cipher suite: CCMP
+                (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x04,
+                // Pairwise cipher suite: TKIP
+                (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x02,
+                // AKM count
+                (byte) 0x01, (byte) 0x00,
+                // AMK suite: EAP/SHA1
+                (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x01,
+                // RSN capabilities
+                (byte) 0x40, (byte) 0x00,
+                // PMKID count
+                (byte) 0x01, (byte) 0x00,
+                // PMKID
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // Group mgmt cipher suite: BIP_GMAC_256
+                (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x0c,
+        };
+        verifyCapabilityStringFromIeWithoutOweSupported(ie,
+                "[WPA2-EAP/SHA1-CCMP+TKIP][RSN-EAP/SHA1-CCMP+TKIP][MFPR]");
+    }
 
-        assertEquals("[WPA2-PSK-CCMP+TKIP][RSN-PSK-CCMP+TKIP]", result);
+    /**
+     * Test Capabilities.generateCapabilitiesString() with a RSN IE.
+     * Expect the function to return a string with the proper security information.
+     */
+    @Test
+    public void buildCapabilities_rsnElementWithWpa3EnterpriseOnlyNetwork() {
+        InformationElement ie = new InformationElement();
+        ie.id = InformationElement.EID_RSN;
+        ie.bytes = new byte[] {
+                // Version
+                (byte) 0x01, (byte) 0x00,
+                // Group cipher suite: TKIP
+                (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x02,
+                // Pairwise cipher count
+                (byte) 0x01, (byte) 0x00,
+                // Pairwise cipher suite: CCMP
+                (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x04,
+                // AKM count
+                (byte) 0x01, (byte) 0x00,
+                // AMK suite: EAP/SHA256
+                (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x05,
+                // RSN capabilities
+                (byte) 0xc0, (byte) 0x00,
+                // PMKID count
+                (byte) 0x00, (byte) 0x00,
+                // Group mgmt cipher suite: BIP_GMAC_256
+                (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x0c,
+        };
+        verifyCapabilityStringFromIeWithoutOweSupported(ie,
+                "[WPA2-EAP/SHA256-CCMP]"
+                        + "[RSN-EAP/SHA256-CCMP][MFPR][MFPC]");
+    }
+
+    /**
+     * Test Capabilities.generateCapabilitiesString() with a RSN IE.
+     * Expect the function to return a string with the proper security information.
+     */
+    @Test
+    public void buildCapabilities_rsnElementWithWpa3EnterpriseTransitionNetwork() {
+        InformationElement ie = new InformationElement();
+        ie.id = InformationElement.EID_RSN;
+        ie.bytes = new byte[] {
+                // Version
+                (byte) 0x01, (byte) 0x00,
+                // Group cipher suite: TKIP
+                (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x02,
+                // Pairwise cipher count
+                (byte) 0x01, (byte) 0x00,
+                // Pairwise cipher suite: CCMP
+                (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x04,
+                // AKM count
+                (byte) 0x02, (byte) 0x00,
+                // AMK suite: EAP/SHA1
+                (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x01,
+                // AMK suite: EAP/SHA256
+                (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x05,
+                // RSN capabilities
+                (byte) 0x80, (byte) 0x00,
+                // PMKID count
+                (byte) 0x00, (byte) 0x00,
+                // Group mgmt cipher suite: BIP_GMAC_256
+                (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x0c,
+        };
+        verifyCapabilityStringFromIeWithoutOweSupported(ie,
+                "[WPA2-EAP/SHA1+EAP/SHA256-CCMP]"
+                        + "[RSN-EAP/SHA1+EAP/SHA256-CCMP][MFPC]");
+    }
+
+    /**
+     * Test Capabilities.generateCapabilitiesString() with a RSN IE.
+     * Expect the function to return a string with the proper security information.
+     * If there is no group management cipher set, ignore the MFPR capability.
+     */
+    @Test
+    public void buildCapabilities_rsnElementWithoutGroupManagementCipherButSetMfpr() {
+        InformationElement ie = new InformationElement();
+        ie.id = InformationElement.EID_RSN;
+        ie.bytes = new byte[] {
+                // Version
+                (byte) 0x01, (byte) 0x00,
+                // Group cipher suite: TKIP
+                (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x02,
+                // Pairwise cipher count
+                (byte) 0x02, (byte) 0x00,
+                // Pairwise cipher suite: CCMP
+                (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x04,
+                // Pairwise cipher suite: TKIP
+                (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x02,
+                // AKM count
+                (byte) 0x01, (byte) 0x00,
+                // AMK suite: EAP/SHA1
+                (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x01,
+                // RSN capabilities
+                (byte) 0x40, (byte) 0x00,
+        };
+        verifyCapabilityStringFromIeWithoutOweSupported(ie,
+                "[WPA2-EAP/SHA1-CCMP+TKIP][RSN-EAP/SHA1-CCMP+TKIP]");
     }
 
     /**
@@ -332,16 +514,7 @@
         ie.bytes = new byte[] { (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x0F,
                 (byte) 0xAC, (byte) 0x02, (byte) 0x02, (byte) 0x00,
                 (byte) 0x00, (byte) 0x0F, (byte) 0xAC };
-
-        InformationElement[] ies = new InformationElement[] { ie };
-        int beaconCap = 0x1 << 4;
-
-        InformationElementUtil.Capabilities capabilities =
-                new InformationElementUtil.Capabilities();
-        capabilities.from(ies, beaconCap, false);
-        String result = capabilities.generateCapabilitiesString();
-
-        assertEquals("[RSN]", result);
+        verifyCapabilityStringFromIeWithoutOweSupported(ie, "[RSN]");
     }
 
     /**
@@ -359,16 +532,28 @@
                                 (byte) 0x00, (byte) 0x50, (byte) 0xF2, (byte) 0x02,
                                 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x50,
                                 (byte) 0xF2, (byte) 0x02, (byte) 0x00, (byte) 0x00 };
+        verifyCapabilityStringFromIeWithoutOweSupported(ie, "[WPA-PSK-CCMP+TKIP]");
+    }
 
-        InformationElement[] ies = new InformationElement[] { ie };
-        int beaconCap = 0x1 << 4;
-
-        InformationElementUtil.Capabilities capabilities =
-                new InformationElementUtil.Capabilities();
-        capabilities.from(ies, beaconCap, false);
-        String result = capabilities.generateCapabilitiesString();
-
-        assertEquals("[WPA-PSK-CCMP+TKIP]", result);
+    /**
+     * Test Capabilities.generateCapabilitiesString() with a WPA type 1 IE which
+     * contains an unknown AKM.
+     * Expect the function to return a string with the proper security information.
+     */
+    @Test
+    public void buildCapabilities_wpa1ElementWithUnknownAkm() {
+        InformationElement ie = new InformationElement();
+        ie.id = InformationElement.EID_VSA;
+        ie.bytes = new byte[] { (byte) 0x00, (byte) 0x50, (byte) 0xF2, (byte) 0x01, // OUI & type
+                                (byte) 0x01, (byte) 0x00, // Version
+                                (byte) 0x00, (byte) 0x50, (byte) 0xF2, (byte) 0x02, // TKIP
+                                (byte) 0x02, (byte) 0x00, // Pairwise cipher count
+                                (byte) 0x00, (byte) 0x50, (byte) 0xF2, (byte) 0x04, // CCMP
+                                (byte) 0x00, (byte) 0x50, (byte) 0xF2, (byte) 0x02, // TKIP
+                                (byte) 0x01, (byte) 0x00, // AKM count
+                                (byte) 0x00, (byte) 0x50, (byte) 0x99, (byte) 0x99, // Unknown AKM
+                                (byte) 0x00, (byte) 0x00};
+        verifyCapabilityStringFromIeWithoutOweSupported(ie, "[WPA-?-CCMP+TKIP]");
     }
 
     /**
@@ -382,16 +567,7 @@
         ie.id = InformationElement.EID_VSA;
         ie.bytes = new byte[] { (byte) 0x00, (byte) 0x50, (byte) 0xF2, (byte) 0x01,
                 (byte) 0x01, (byte) 0x00 };
-
-        InformationElement[] ies = new InformationElement[] { ie };
-        int beaconCap = 0x1 << 4;
-
-        InformationElementUtil.Capabilities capabilities =
-                new InformationElementUtil.Capabilities();
-        capabilities.from(ies, beaconCap, false);
-        String result = capabilities.generateCapabilitiesString();
-
-        assertEquals("[WPA]", result);
+        verifyCapabilityStringFromIeWithoutOweSupported(ie, "[WPA]");
     }
 
     /**
@@ -420,14 +596,10 @@
                                    (byte) 0xF2, (byte) 0x02, (byte) 0x00, (byte) 0x00 };
 
         InformationElement[] ies = new InformationElement[] { ieWpa, ieRsn };
-        int beaconCap = 0x1 << 4;
-
-        InformationElementUtil.Capabilities capabilities =
-                new InformationElementUtil.Capabilities();
-        capabilities.from(ies, beaconCap, false);
-        String result = capabilities.generateCapabilitiesString();
-
-        assertEquals("[WPA-PSK-CCMP+TKIP][WPA2-PSK-CCMP+TKIP][RSN-PSK-CCMP+TKIP]", result);
+        verifyCapabilityStringFromIes(ies,
+                0x1 << 4,
+                false,
+                "[WPA-PSK-CCMP+TKIP][WPA2-PSK-CCMP+TKIP][RSN-PSK-CCMP+TKIP]");
     }
 
     /**
@@ -455,16 +627,8 @@
                 (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x08,
                 // Padding
                 (byte) 0x00, (byte) 0x00 };
-
-        InformationElement[] ies = new InformationElement[] { ieRsn };
-        int beaconCap = 0x1 << 4;
-
-        InformationElementUtil.Capabilities capabilities =
-                new InformationElementUtil.Capabilities();
-        capabilities.from(ies, beaconCap, true);
-        String result = capabilities.generateCapabilitiesString();
-
-        assertEquals("[WPA2-PSK-CCMP][RSN-PSK+SAE-CCMP]", result);
+        verifyCapabilityStringFromIeWithOweSupported(ieRsn,
+                "[WPA2-PSK-CCMP][RSN-PSK+SAE-CCMP]");
     }
 
     /**
@@ -492,16 +656,8 @@
                 (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x09,
                 // Padding
                 (byte) 0x00, (byte) 0x00 };
-
-        InformationElement[] ies = new InformationElement[] { ieRsn };
-        int beaconCap = 0x1 << 4;
-
-        InformationElementUtil.Capabilities capabilities =
-                new InformationElementUtil.Capabilities();
-        capabilities.from(ies, beaconCap, true);
-        String result = capabilities.generateCapabilitiesString();
-
-        assertEquals("[RSN-SAE+FT/SAE-CCMP]", result);
+        verifyCapabilityStringFromIeWithOweSupported(ieRsn,
+                "[RSN-SAE+FT/SAE-CCMP]");
     }
 
     /**
@@ -527,16 +683,8 @@
                 (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x12,
                 // Padding
                 (byte) 0x00, (byte) 0x00 };
-
-        InformationElement[] ies = new InformationElement[] { ieRsn };
-        int beaconCap = 0x1 << 4;
-
-        InformationElementUtil.Capabilities capabilities =
-                new InformationElementUtil.Capabilities();
-        capabilities.from(ies, beaconCap, true);
-        String result = capabilities.generateCapabilitiesString();
-
-        assertEquals("[RSN-OWE-CCMP]", result);
+        verifyCapabilityStringFromIeWithOweSupported(ieRsn,
+                "[RSN-OWE-CCMP]");
     }
 
     /**
@@ -552,17 +700,8 @@
                 (byte) 0x50, (byte) 0x6F, (byte) 0x9A, (byte) 0x1C,
                 // OWE IE contains BSSID, SSID and channel of other BSS, but we don't parse it.
                 (byte) 0x00, (byte) 0x000, (byte) 0x00, (byte) 0x00 };
-
-        InformationElement[] ies = new InformationElement[] { ieOwe };
-
-        int beaconCap = 0x1 << 0;
-
-        InformationElementUtil.Capabilities capabilities =
-                new InformationElementUtil.Capabilities();
-        capabilities.from(ies, beaconCap, true);
-        String result = capabilities.generateCapabilitiesString();
-
-        assertEquals("[RSN-OWE_TRANSITION-CCMP][ESS]", result);
+        verifyCapabilityStringFromIe(ieOwe, 0x1 << 0, true,
+                "[RSN-OWE_TRANSITION-CCMP][ESS]");
     }
 
     /**
@@ -578,17 +717,8 @@
                 (byte) 0x50, (byte) 0x6F, (byte) 0x9A, (byte) 0x1C,
                 // OWE IE contains BSSID, SSID and channel of other BSS, but we don't parse it.
                 (byte) 0x00, (byte) 0x000, (byte) 0x00, (byte) 0x00 };
-
-        InformationElement[] ies = new InformationElement[] { ieOwe };
-
-        int beaconCap = 0x1 << 0;
-
-        InformationElementUtil.Capabilities capabilities =
-                new InformationElementUtil.Capabilities();
-        capabilities.from(ies, beaconCap, false);
-        String result = capabilities.generateCapabilitiesString();
-
-        assertEquals("[ESS]", result);
+        verifyCapabilityStringFromIe(ieOwe, 0x1 << 0, false,
+                "[ESS]");
     }
 
     /**
@@ -612,18 +742,15 @@
                 (byte) 0x01, (byte) 0x00,
                 // SUITE_B_192 AKM
                 (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x0C,
-                // Padding
-                (byte) 0x00, (byte) 0x00 };
-
-        InformationElement[] ies = new InformationElement[] { ieRsn };
-        int beaconCap = 0x1 << 4;
-
-        InformationElementUtil.Capabilities capabilities =
-                new InformationElementUtil.Capabilities();
-        capabilities.from(ies, beaconCap, false);
-        String result = capabilities.generateCapabilitiesString();
-
-        assertEquals("[RSN-EAP_SUITE_B_192-GCMP-256]", result);
+                // RSN capabilities
+                (byte) 0x40, (byte) 0x00,
+                // PMKID count
+                (byte) 0x00, (byte) 0x00,
+                // Group mgmt cipher suite: BIP_GMAC_256
+                (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x0c,
+        };
+        verifyCapabilityStringFromIeWithoutOweSupported(ieRsn,
+                "[RSN-EAP_SUITE_B_192-GCMP-256][MFPR]");
     }
 
     /**
@@ -654,17 +781,9 @@
                 (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x0E,
                 // RSN capabilities
                 (byte) 0x00, (byte) 0x00 };
-
-        InformationElement[] ies = new InformationElement[] { ieRsn };
-        int beaconCap = 0x1 << 4;
-
-        InformationElementUtil.Capabilities capabilities =
-                new InformationElementUtil.Capabilities();
-        capabilities.from(ies, beaconCap, true);
-        String result = capabilities.generateCapabilitiesString();
-
-        assertEquals("[WPA2-EAP+EAP-SHA256+EAP-FILS-SHA256-CCMP]"
-                + "[RSN-EAP+EAP-SHA256+EAP-FILS-SHA256-CCMP]", result);
+        verifyCapabilityStringFromIeWithOweSupported(ieRsn,
+                "[WPA2-EAP/SHA1+EAP/SHA256+EAP-FILS-SHA256-CCMP]"
+                        + "[RSN-EAP/SHA1+EAP/SHA256+EAP-FILS-SHA256-CCMP]");
     }
 
     /**
@@ -695,17 +814,9 @@
                 (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x0F,
                 // RSN capabilities
                 (byte) 0x00, (byte) 0x00 };
-
-        InformationElement[] ies = new InformationElement[] { ieRsn };
-        int beaconCap = 0x1 << 4;
-
-        InformationElementUtil.Capabilities capabilities =
-                new InformationElementUtil.Capabilities();
-        capabilities.from(ies, beaconCap, true);
-        String result = capabilities.generateCapabilitiesString();
-
-        assertEquals("[WPA2-EAP+EAP-SHA256+EAP-FILS-SHA384-CCMP]"
-                + "[RSN-EAP+EAP-SHA256+EAP-FILS-SHA384-CCMP]", result);
+        verifyCapabilityStringFromIeWithOweSupported(ieRsn,
+                "[WPA2-EAP/SHA1+EAP/SHA256+EAP-FILS-SHA384-CCMP]"
+                    + "[RSN-EAP/SHA1+EAP/SHA256+EAP-FILS-SHA384-CCMP]");
     }
 
     /**
@@ -726,16 +837,11 @@
                 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x50,
                 (byte) 0xF2, (byte) 0x02, (byte) 0x02, (byte) 0x00,
                 (byte) 0x00, (byte) 0x50 };
-
         InformationElement[] ies = new InformationElement[] { ieWpa, ieRsn };
-        int beaconCap = 0x1 << 4;
-
-        InformationElementUtil.Capabilities capabilities =
-                new InformationElementUtil.Capabilities();
-        capabilities.from(ies, beaconCap, false);
-        String result = capabilities.generateCapabilitiesString();
-
-        assertEquals("[WPA][RSN]", result);
+        verifyCapabilityStringFromIes(ies,
+                0x1 << 4,
+                false,
+                "[WPA][RSN]");
     }
 
     /**
@@ -759,15 +865,10 @@
         ieWps.bytes = new byte[] { (byte) 0x00, (byte) 0x50, (byte) 0xF2, (byte) 0x04 };
 
         InformationElement[] ies = new InformationElement[] { ieWpa, ieWps };
-        int beaconCap = 0x1 << 4;
-
-
-        InformationElementUtil.Capabilities capabilities =
-                 new InformationElementUtil.Capabilities();
-        capabilities.from(ies, beaconCap, false);
-        String result = capabilities.generateCapabilitiesString();
-
-        assertEquals("[WPA-PSK-CCMP+TKIP][WPS]", result);
+        verifyCapabilityStringFromIes(ies,
+                0x1 << 4,
+                false,
+                "[WPA-PSK-CCMP+TKIP][WPS]");
     }
 
     /**
@@ -784,17 +885,7 @@
         ie.bytes = new byte[] { (byte) 0x00, (byte) 0x04, (byte) 0x0E, (byte) 0x01,
                                 (byte) 0x01, (byte) 0x02, (byte) 0x01, (byte) 0x00,
                                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 };
-
-        InformationElement[] ies = new InformationElement[] { ie };
-        int beaconCap = 0x1 << 4;
-
-        InformationElementUtil.Capabilities capabilities =
-                new InformationElementUtil.Capabilities();
-        capabilities.from(ies, beaconCap, false);
-        String result = capabilities.generateCapabilitiesString();
-
-
-        assertEquals("[WEP]", result);
+        verifyCapabilityStringFromIeWithoutOweSupported(ie, "[WEP]");
     }
 
     /**
@@ -811,17 +902,7 @@
         ie.bytes = new byte[] { (byte) 0x00, (byte) 0x04, (byte) 0x0E, (byte) 0x01,
                                 (byte) 0x01, (byte) 0x02, (byte) 0x01, (byte) 0x00,
                                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 };
-
-        InformationElement[] ies = new InformationElement[] { ie };
-        int beaconCap = 0;
-
-        InformationElementUtil.Capabilities capabilities =
-                new InformationElementUtil.Capabilities();
-        capabilities.from(ies, beaconCap, false);
-        String result = capabilities.generateCapabilitiesString();
-
-
-        assertEquals("", result);
+        verifyCapabilityStringFromIe(ie, 0, false, "");
     }
 
     /**
@@ -837,17 +918,7 @@
         ie.bytes = new byte[] { (byte) 0x00, (byte) 0x04, (byte) 0x0E, (byte) 0x01,
                                 (byte) 0x01, (byte) 0x02, (byte) 0x01, (byte) 0x00,
                                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 };
-
-        InformationElement[] ies = new InformationElement[] { ie };
-        int beaconCap = 0x1 << 0;
-
-        InformationElementUtil.Capabilities capabilities =
-                new InformationElementUtil.Capabilities();
-        capabilities.from(ies, beaconCap, false);
-        String result = capabilities.generateCapabilitiesString();
-
-
-        assertEquals("[ESS]", result);
+        verifyCapabilityStringFromIe(ie, 0x1 << 0, false, "[ESS]");
     }
 
     /**
@@ -864,16 +935,7 @@
         ie.bytes = new byte[] { (byte) 0x00, (byte) 0x04, (byte) 0x0E, (byte) 0x01,
                                 (byte) 0x01, (byte) 0x02, (byte) 0x01, (byte) 0x00,
                                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 };
-
-        InformationElement[] ies = new InformationElement[] { ie };
-        int beaconCap = 0;
-
-        InformationElementUtil.Capabilities capabilities =
-                new InformationElementUtil.Capabilities();
-        capabilities.from(ies, beaconCap, false);
-        String result = capabilities.generateCapabilitiesString();
-
-        assertEquals("", result);
+        verifyCapabilityStringFromIe(ie, 0, false, "");
     }
 
     /**
@@ -887,13 +949,47 @@
 
         InformationElementUtil.Capabilities capabilities =
                 new InformationElementUtil.Capabilities();
-        capabilities.from(new InformationElement[0], beaconCap, false);
+        capabilities.from(new InformationElement[0], beaconCap, false, 2400);
         String result = capabilities.generateCapabilitiesString();
 
         assertEquals("[IBSS]", result);
     }
 
     /**
+     * Test Capabilities.generateCapabilitiesString() with the IBSS capability bit set for DMG.
+     *
+     * Expect the function to return a string with [IBSS] there.
+     */
+    @Test
+    public void buildCapabilities_DmgIbssCapabilitySet() {
+        int beaconCap = 0x1;
+
+        InformationElementUtil.Capabilities capabilities =
+                new InformationElementUtil.Capabilities();
+        capabilities.from(new InformationElement[0], beaconCap, false, 58320);
+        String result = capabilities.generateCapabilitiesString();
+
+        assertEquals("[IBSS]", result);
+    }
+
+    /**
+     * Test Capabilities.generateCapabilitiesString() with the ESS capability bit set for DMG.
+     *
+     * Expect the function to return a string with [IBSS] there.
+     */
+    @Test
+    public void buildCapabilities_DmgEssCapabilitySet() {
+        int beaconCap = 0x3;
+
+        InformationElementUtil.Capabilities capabilities =
+                new InformationElementUtil.Capabilities();
+        capabilities.from(new InformationElement[0], beaconCap, false, 58320);
+        String result = capabilities.generateCapabilitiesString();
+
+        assertEquals("[ESS]", result);
+    }
+
+    /**
      * Verify the expectations when building an ExtendedCapabilites IE from data with no bits set.
      * Both ExtendedCapabilities#isStrictUtf8() and ExtendedCapabilites#is80211McRTTResponder()
      * should return false.
diff --git a/service/tests/wifitests/src/com/android/server/wifi/util/LastCallerInfoManagerTest.java b/service/tests/wifitests/src/com/android/server/wifi/util/LastCallerInfoManagerTest.java
new file mode 100644
index 0000000..7dd4318
--- /dev/null
+++ b/service/tests/wifitests/src/com/android/server/wifi/util/LastCallerInfoManagerTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.util;
+
+import static org.junit.Assert.*;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.wifi.WifiBaseTest;
+
+import org.junit.Test;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.regex.Pattern;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.util.LastCallerInfoManager}.
+ */
+@SmallTest
+public class LastCallerInfoManagerTest extends WifiBaseTest {
+
+    private LastCallerInfoManager mLastCallerInfoManager = new LastCallerInfoManager();
+    /**
+     * Test that the dump matches the values put
+     */
+    @Test
+    public void testPutAndDump() throws Exception {
+        mLastCallerInfoManager.put(LastCallerInfoManager.SOFT_AP, 10, 11, 12, "Package", true);
+
+        StringWriter sw = new StringWriter();
+        mLastCallerInfoManager.dump(new PrintWriter(sw));
+        String serviceDump = sw.toString();
+        Pattern logLineRegex = Pattern.compile(
+                "SoftAp: tid=10 uid=11 pid=12 packageName=Package toggleState=true");
+        assertTrue("dump did not contain the expected log"
+                + ": " + serviceDump + "\n", logLineRegex.matcher(serviceDump).find());
+    }
+
+}
diff --git a/service/tests/wifitests/src/com/android/server/wifi/util/MissingCounterTimerLockListTest.java b/service/tests/wifitests/src/com/android/server/wifi/util/MissingCounterTimerLockListTest.java
index c145f7f..ddf7e0c 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/util/MissingCounterTimerLockListTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/util/MissingCounterTimerLockListTest.java
@@ -36,6 +36,7 @@
 public class MissingCounterTimerLockListTest extends WifiBaseTest {
     private static final int TEST_MISSING_COUNT = 2;
     private static final long BLOCKING_DURATION = 1000;
+    private static final long MAX_DISABLE_DURATION = BLOCKING_DURATION * 10;
     private static final String TEST_SSID = "testSSID";
     private static final String TEST_FQDN = "testFQDN";
 
@@ -52,9 +53,9 @@
 
     @Test
     public void testAddRemove() {
-        mMissingCounterTimerLockList.add(TEST_SSID, BLOCKING_DURATION);
-        mMissingCounterTimerLockList.add(TEST_FQDN, BLOCKING_DURATION);
-        mMissingCounterTimerLockList.add(null, BLOCKING_DURATION);
+        mMissingCounterTimerLockList.add(TEST_SSID, BLOCKING_DURATION, MAX_DISABLE_DURATION);
+        mMissingCounterTimerLockList.add(TEST_FQDN, BLOCKING_DURATION, MAX_DISABLE_DURATION);
+        mMissingCounterTimerLockList.add(null, BLOCKING_DURATION, MAX_DISABLE_DURATION);
         assertEquals(2, mMissingCounterTimerLockList.size());
         assertTrue(mMissingCounterTimerLockList.isLocked(TEST_SSID));
         assertTrue(mMissingCounterTimerLockList.isLocked(TEST_FQDN));
@@ -68,8 +69,8 @@
 
     @Test
     public void testUpdateAndTimer() {
-        mMissingCounterTimerLockList.add(TEST_SSID, BLOCKING_DURATION);
-        mMissingCounterTimerLockList.add(TEST_FQDN, BLOCKING_DURATION);
+        mMissingCounterTimerLockList.add(TEST_SSID, BLOCKING_DURATION, MAX_DISABLE_DURATION);
+        mMissingCounterTimerLockList.add(TEST_FQDN, BLOCKING_DURATION, MAX_DISABLE_DURATION);
         assertEquals(2, mMissingCounterTimerLockList.size());
         when(mClock.getWallClockMillis()).thenReturn((long) 0);
         Set<String> updateSet = new HashSet<>();
@@ -83,4 +84,23 @@
         assertFalse(mMissingCounterTimerLockList.isLocked(TEST_SSID));
         assertTrue(mMissingCounterTimerLockList.isLocked(TEST_FQDN));
     }
+
+    @Test
+    public void testMaxDisableDurationTimer() {
+        // Disable 2 networks at time == 0
+        when(mClock.getWallClockMillis()).thenReturn((long) 0);
+        mMissingCounterTimerLockList.add(TEST_SSID, BLOCKING_DURATION, MAX_DISABLE_DURATION);
+        mMissingCounterTimerLockList.add(TEST_FQDN, BLOCKING_DURATION, MAX_DISABLE_DURATION);
+        assertEquals(2, mMissingCounterTimerLockList.size());
+
+        // verify that at time == MAX_DISABLE_DURATION the networks are still disabled.
+        when(mClock.getWallClockMillis()).thenReturn(MAX_DISABLE_DURATION);
+        assertTrue(mMissingCounterTimerLockList.isLocked(TEST_SSID));
+        assertTrue(mMissingCounterTimerLockList.isLocked(TEST_FQDN));
+
+        // verify that at time == MAX_DISABLE_DURATION the networks are re-enabled.
+        when(mClock.getWallClockMillis()).thenReturn(MAX_DISABLE_DURATION + 1);
+        assertFalse(mMissingCounterTimerLockList.isLocked(TEST_SSID));
+        assertFalse(mMissingCounterTimerLockList.isLocked(TEST_FQDN));
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/util/ScanResultUtilTest.java b/service/tests/wifitests/src/com/android/server/wifi/util/ScanResultUtilTest.java
index 00e3a0b..a7585c8 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/util/ScanResultUtilTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/util/ScanResultUtilTest.java
@@ -27,6 +27,7 @@
 
 import com.android.server.wifi.ScanDetail;
 import com.android.server.wifi.WifiBaseTest;
+import com.android.server.wifi.hotspot2.NetworkDetail;
 
 import org.junit.Test;
 
@@ -93,40 +94,92 @@
         final String ssid = "Another SSid";
         ScanResult scanResult = new ScanResult(ssid, "ab:cd:01:ef:45:89", 1245, 0, "",
                 -78, 2450, 1025, 22, 33, 20, 0, 0, true);
+        scanResult.informationElements = new InformationElement[] {
+                createIE(InformationElement.EID_SSID, ssid.getBytes(StandardCharsets.UTF_8))
+        };
         WifiConfiguration config;
 
         scanResult.capabilities = "";
         config = ScanResultUtil.createNetworkFromScanResult(scanResult);
         assertEquals(config.SSID, ScanResultUtil.createQuotedSSID(ssid));
-        assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE));
+        assertEquals(WifiConfiguration.SECURITY_TYPE_OPEN,
+                config.getDefaultSecurityParams().getSecurityType());
 
         scanResult.capabilities = "WEP";
         config = ScanResultUtil.createNetworkFromScanResult(scanResult);
         assertEquals(config.SSID, ScanResultUtil.createQuotedSSID(ssid));
-        assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE));
-        assertTrue(config.allowedAuthAlgorithms.get(WifiConfiguration.AuthAlgorithm.OPEN));
-        assertTrue(config.allowedAuthAlgorithms.get(WifiConfiguration.AuthAlgorithm.SHARED));
+        assertEquals(WifiConfiguration.SECURITY_TYPE_WEP,
+                config.getDefaultSecurityParams().getSecurityType());
 
         scanResult.capabilities = "PSK";
         config = ScanResultUtil.createNetworkFromScanResult(scanResult);
         assertEquals(config.SSID, ScanResultUtil.createQuotedSSID(ssid));
-        assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK));
+        assertEquals(WifiConfiguration.SECURITY_TYPE_PSK,
+                config.getDefaultSecurityParams().getSecurityType());
 
-        scanResult.capabilities = "EAP";
+        // WPA2 Enterprise network with none MFP capability.
+        scanResult.capabilities = "[EAP/SHA1]";
         config = ScanResultUtil.createNetworkFromScanResult(scanResult);
         assertEquals(config.SSID, ScanResultUtil.createQuotedSSID(ssid));
-        assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP));
-        assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X));
+        assertEquals(WifiConfiguration.SECURITY_TYPE_EAP,
+                config.getDefaultSecurityParams().getSecurityType());
+
+        // WPA2 Enterprise network with MFPC.
+        scanResult.capabilities = "[EAP/SHA1][MFPC]";
+        config = ScanResultUtil.createNetworkFromScanResult(scanResult);
+        assertEquals(config.SSID, ScanResultUtil.createQuotedSSID(ssid));
+        assertEquals(WifiConfiguration.SECURITY_TYPE_EAP,
+                config.getDefaultSecurityParams().getSecurityType());
+
+        // WPA2 Enterprise network with MFPR.
+        scanResult.capabilities = "[EAP/SHA1][MFPR]";
+        config = ScanResultUtil.createNetworkFromScanResult(scanResult);
+        assertEquals(config.SSID, ScanResultUtil.createQuotedSSID(ssid));
+        assertEquals(WifiConfiguration.SECURITY_TYPE_EAP,
+                config.getDefaultSecurityParams().getSecurityType());
+
+        // WPA3 Enterprise transition network
+        scanResult.capabilities = "[RSN-EAP/SHA1+EAP/SHA256][MFPC]";
+        config = ScanResultUtil.createNetworkFromScanResult(scanResult);
+        assertEquals(config.SSID, ScanResultUtil.createQuotedSSID(ssid));
+        assertEquals(WifiConfiguration.SECURITY_TYPE_EAP,
+                config.getDefaultSecurityParams().getSecurityType());
+        assertTrue(config.isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP));
+        assertTrue(config.isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE));
+
+        // WPA3 Enterprise only network
+        scanResult.capabilities = "[RSN-EAP/SHA256][MFPC][MFPR]";
+        config = ScanResultUtil.createNetworkFromScanResult(scanResult);
+        assertEquals(config.SSID, ScanResultUtil.createQuotedSSID(ssid));
+        assertEquals(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE,
+                config.getDefaultSecurityParams().getSecurityType());
+
+        // Neither a valid WPA3 Enterprise transition network nor WPA3 Enterprise only network
+        // Fallback to WPA2 Enterprise
+        scanResult.capabilities = "[RSN-EAP/SHA1+EAP/SHA256][MFPC][MFPR]";
+        config = ScanResultUtil.createNetworkFromScanResult(scanResult);
+        assertEquals(config.SSID, ScanResultUtil.createQuotedSSID(ssid));
+        assertEquals(WifiConfiguration.SECURITY_TYPE_EAP,
+                config.getDefaultSecurityParams().getSecurityType());
+
+        // WPA3 Enterprise only network
+        scanResult.capabilities = "[RSN-SUITE_B_192][MFPR]";
+        config = ScanResultUtil.createNetworkFromScanResult(scanResult);
+        assertEquals(config.SSID, ScanResultUtil.createQuotedSSID(ssid));
+        assertEquals(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT,
+                config.getDefaultSecurityParams().getSecurityType());
 
         scanResult.capabilities = "WAPI-PSK";
         config = ScanResultUtil.createNetworkFromScanResult(scanResult);
         assertEquals(config.SSID, ScanResultUtil.createQuotedSSID(ssid));
-        assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WAPI_PSK));
+        assertEquals(WifiConfiguration.SECURITY_TYPE_WAPI_PSK,
+                config.getDefaultSecurityParams().getSecurityType());
 
         scanResult.capabilities = "WAPI-CERT";
         config = ScanResultUtil.createNetworkFromScanResult(scanResult);
         assertEquals(config.SSID, ScanResultUtil.createQuotedSSID(ssid));
-        assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WAPI_CERT));
+        assertEquals(WifiConfiguration.SECURITY_TYPE_WAPI_CERT,
+                config.getDefaultSecurityParams().getSecurityType());
     }
 
     /**
@@ -206,13 +259,12 @@
     }
 
     /**
-     * Test that provided network supports FILS SHA256 AKM.
+     * Test that provided network supports FT/EAP AKM.
      */
     @Test
-    public void testFilsSha256AkmSupportedNetwork() {
-        final String ssid = "FILS-AP";
-        String caps = "[WPA2-EAP+EAP-SHA256+EAP-FILS-SHA256-CCMP]"
-                + "[RSN-EAP+EAP-SHA256+EAP-FILS-SHA256-CCMP][ESS]";
+    public void testFtEapAkmSupportedNetwork() {
+        final String ssid = "FT-EAP-AP";
+        String caps = " [WPA2-FT/EAP-CCMP][RSN-FT/EAP-CCMP][ESS]";
 
         ScanResult input = new ScanResult(WifiSsid.createFromAsciiEncoded(ssid), ssid,
                 "ab:cd:01:ef:45:89", 1245, 0, caps, -78, 2450, 1025, 22, 33, 20, 0,
@@ -222,6 +274,27 @@
                 createIE(InformationElement.EID_SSID, ssid.getBytes(StandardCharsets.UTF_8))
         };
 
+        assertTrue(ScanResultUtil.isScanResultForEapNetwork(input));
+    }
+
+    /**
+     * Test that provided network supports FILS SHA256 AKM.
+     */
+    @Test
+    public void testFilsSha256AkmSupportedNetwork() {
+        final String ssid = "FILS-AP";
+        String caps = "[WPA2-EAP-FILS-SHA256-CCMP]"
+                + "[RSN-EAP-FILS-SHA256-CCMP][ESS]";
+
+        ScanResult input = new ScanResult(WifiSsid.createFromAsciiEncoded(ssid), ssid,
+                "ab:cd:01:ef:45:89", 1245, 0, caps, -78, 2450, 1025, 22, 33, 20, 0,
+                0, true);
+
+        input.informationElements = new InformationElement[] {
+                createIE(InformationElement.EID_SSID, ssid.getBytes(StandardCharsets.UTF_8))
+        };
+
+        assertTrue(ScanResultUtil.isScanResultForEapNetwork(input));
         assertTrue(ScanResultUtil.isScanResultForFilsSha256Network(input));
     }
 
@@ -231,8 +304,8 @@
     @Test
     public void testFilsSha384AkmSupportedNetwork() {
         final String ssid = "FILS-AP";
-        String caps = "[WPA2-EAP+EAP-SHA384+EAP-FILS-SHA384-CCMP]"
-                + "[RSN-EAP+EAP-SHA384+EAP-FILS-SHA384-CCMP][ESS]";
+        String caps = "[WPA2-EAP-FILS-SHA384-CCMP]"
+                + "[RSN-EAP-FILS-SHA384-CCMP][ESS]";
 
         ScanResult input = new ScanResult(WifiSsid.createFromAsciiEncoded(ssid), ssid,
                 "ab:cd:01:ef:45:89", 1245, 0, caps, -78, 2450, 1025, 22, 33, 20, 0,
@@ -242,10 +315,135 @@
                 createIE(InformationElement.EID_SSID, ssid.getBytes(StandardCharsets.UTF_8))
         };
 
+        assertTrue(ScanResultUtil.isScanResultForEapNetwork(input));
         assertTrue(ScanResultUtil.isScanResultForFilsSha384Network(input));
     }
 
     /**
+     * Test that an EAP network is not detected as a Pasppoint network.
+     */
+    @Test
+    public void testEapNetworkNotPasspointNetwork() {
+        final String ssid = "EAP-NETWORK";
+        String caps = "[EAP/SHA1][ESS]";
+
+        ScanResult input = new ScanResult(WifiSsid.createFromAsciiEncoded(ssid), ssid,
+                "ab:cd:01:ef:45:89", 1245, 0, caps, -78, 2450, 1025, 22, 33, 20, 0,
+                0, true);
+
+        input.informationElements = new InformationElement[] {
+                createIE(InformationElement.EID_SSID, ssid.getBytes(StandardCharsets.UTF_8))
+        };
+
+        assertTrue(ScanResultUtil.isScanResultForEapNetwork(input));
+        assertFalse(ScanResultUtil.isScanResultForPasspointR1R2Network(input));
+        assertFalse(ScanResultUtil.isScanResultForPasspointR3Network(input));
+    }
+
+    private void verifyPasspointNetwork(
+            String caps, boolean isInterworking, NetworkDetail.HSRelease hsRel,
+            boolean isR1R2Network, boolean isR3Network) {
+        final String ssid = "PASSPOINT-NETWORK";
+        List<InformationElement> ies = new ArrayList<>();
+        ies.add(createIE(InformationElement.EID_SSID, ssid.getBytes(StandardCharsets.UTF_8)));
+        if (isInterworking) {
+            ies.add(createIE(InformationElement.EID_INTERWORKING, new byte[] {(byte) 0x10}));
+        }
+        if (null != hsRel) {
+            byte releaseByte = (byte) 0xff;
+            switch (hsRel) {
+                case R1:
+                    releaseByte = (byte) 0x0;
+                    break;
+                case R2:
+                    releaseByte = (byte) 0x10;
+                    break;
+                case R3:
+                    releaseByte = (byte) 0x20;
+                    break;
+            }
+            ies.add(
+                    createIE(InformationElement.EID_VSA, new byte[] {
+                            (byte) 0x50, (byte) 0x6f, (byte) 0x9a, (byte) 0x10, releaseByte}));
+        }
+
+        ScanResult input = new ScanResult(WifiSsid.createFromAsciiEncoded(ssid), ssid,
+                "ab:cd:01:ef:45:89", 1245, 0, caps, -78, 2450, 1025, 22, 33, 20, 0,
+                0, true);
+        input.informationElements = new InformationElement[ies.size()];
+        input.informationElements = ies.toArray(input.informationElements);
+
+        assertTrue(ScanResultUtil.isScanResultForEapNetwork(input));
+        assertEquals(isR1R2Network, ScanResultUtil.isScanResultForPasspointR1R2Network(input));
+        assertEquals(isR3Network, ScanResultUtil.isScanResultForPasspointR3Network(input));
+    }
+
+    /**
+     * Test that a Passpoint R1 network is detected correctly.
+     */
+    @Test
+    public void testPasspointR1NetworkCheck() {
+        String caps = "[EAP/SHA1][ESS]";
+        verifyPasspointNetwork(caps, true, NetworkDetail.HSRelease.R1, true, false);
+    }
+
+    /**
+     * Test that a Passpoint R2 network is detected correctly.
+     */
+    @Test
+    public void testPasspointR2NetworkCheck() {
+        String caps = "[EAP/SHA1][ESS]";
+        verifyPasspointNetwork(caps, true, NetworkDetail.HSRelease.R2, true, false);
+    }
+
+    /**
+     * Test that a Passpoint R3 network is detected correctly.
+     */
+    @Test
+    public void testPasspointR3NetworkCheck() {
+        String caps = "[EAP/SHA1][ESS][MFPR]";
+        // R3 network is also a valid R1/R2 network.
+        verifyPasspointNetwork(caps, true, NetworkDetail.HSRelease.R3, true, true);
+    }
+
+    /**
+     * Test that an EAP network with HS release, but no interwork set.
+     */
+    @Test
+    public void testEapNetworkWithoutInterworkNotAPasspointNetwork() {
+        String caps = "[EAP/SHA1][ESS]";
+        verifyPasspointNetwork(caps, false, NetworkDetail.HSRelease.R3, false, false);
+    }
+
+    /**
+     * Test that an EAP network with interwork set, but no HS release.
+     */
+    @Test
+    public void testEapNetworkWithoutHsReleaseNotAPasspointNetwork() {
+        String caps = "[EAP/SHA1][ESS]";
+        verifyPasspointNetwork(caps, true, null, false, false);
+    }
+
+    /**
+     * Test that an unknown AMK network is not detected as an open network.
+     */
+    @Test
+    public void testUnknownAkmNotOpenNetwork() {
+        final String ssid = "UnknownAkm-Network";
+        String caps = "[RSN-?-TKIP+CCMP][ESS][WPS]";
+
+        ScanResult input = new ScanResult(WifiSsid.createFromAsciiEncoded(ssid), ssid,
+                "ab:cd:01:ef:45:89", 1245, 0, caps, -78, 2450, 1025, 22, 33, 20, 0,
+                0, true);
+
+        input.informationElements = new InformationElement[] {
+                createIE(InformationElement.EID_SSID, ssid.getBytes(StandardCharsets.UTF_8))
+        };
+
+        assertFalse(ScanResultUtil.isScanResultForOpenNetwork(input));
+    }
+
+    /**
      * Verify ScanResultList validation.
      */
     @Test
@@ -265,6 +463,24 @@
         assertTrue(ScanResultUtil.validateScanResultList(scanResults));
     }
 
+    /**
+     * Verify that unknown AKM in the scan result
+     */
+    @Test
+    public void testUnknownAkmForSecurityParamsGeneration() {
+        final String ssid = "Another SSid";
+        ScanResult scanResult = new ScanResult(ssid, "ab:cd:01:ef:45:89", 1245, 0, "",
+                -78, 2450, 1025, 22, 33, 20, 0, 0, true);
+        scanResult.informationElements = new InformationElement[] {
+                createIE(InformationElement.EID_SSID, ssid.getBytes(StandardCharsets.UTF_8))
+        };
+        WifiConfiguration config;
+
+        scanResult.capabilities = "[RSN-?-CCMP]";
+        config = ScanResultUtil.createNetworkFromScanResult(scanResult);
+        assertNull(config);
+    }
+
     private static InformationElement createIE(int id, byte[] bytes) {
         InformationElement ie = new InformationElement();
         ie.id = id;
diff --git a/service/tests/wifitests/src/com/android/server/wifi/util/WifiPermissionsUtilTest.java b/service/tests/wifitests/src/com/android/server/wifi/util/WifiPermissionsUtilTest.java
index 799df54..5da5582 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/util/WifiPermissionsUtilTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/util/WifiPermissionsUtilTest.java
@@ -48,6 +48,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.BinderUtil;
 import com.android.server.wifi.FakeWifiLog;
 import com.android.server.wifi.FrameworkFacade;
@@ -84,6 +85,7 @@
     @Mock private WifiInjector mWifiInjector;
     @Mock private LocationManager mLocationManager;
     @Mock private DevicePolicyManager mDevicePolicyManager;
+    @Mock private PackageManager mPackageManager;
     @Spy private FakeWifiLog mWifiLog;
 
     private static final String TEST_WIFI_STACK_APK_NAME = "com.android.wifi";
@@ -859,7 +861,7 @@
      * Verifies the helper method exposed for checking if the app is a DeviceOwner.
      */
     @Test
-    public void testIsDeviceOwnerApp() throws Exception {
+    public void testIsDeviceOwnerByPackageName() throws Exception {
         setupMocks();
         WifiPermissionsUtil wifiPermissionsUtil = new WifiPermissionsUtil(mMockPermissionsWrapper,
                 mMockContext, mMockUserManager, mWifiInjector);
@@ -905,6 +907,62 @@
     }
 
     /**
+     * Verifies the helper method exposed for checking if UID is a DeviceOwner.
+     */
+    @Test
+    public void testIsDeviceOwnerByUid() throws Exception {
+        setupMocks();
+        WifiPermissionsUtil wifiPermissionsUtil = new WifiPermissionsUtil(mMockPermissionsWrapper,
+                mMockContext, mMockUserManager, mWifiInjector);
+
+        when(mMockContext.getSystemService(DevicePolicyManager.class))
+                .thenReturn(mDevicePolicyManager);
+        when(mMockContext.getPackageManager()).thenReturn(mPackageManager);
+
+        when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser())
+                .thenReturn(new ComponentName(TEST_PACKAGE_NAME, new String()));
+        when(mDevicePolicyManager.getDeviceOwnerUser())
+                .thenReturn(UserHandle.getUserHandleForUid(MANAGED_PROFILE_UID));
+        when(mPackageManager.getPackagesForUid(MANAGED_PROFILE_UID)).thenReturn(
+                new String[] { TEST_PACKAGE_NAME });
+        assertTrue(wifiPermissionsUtil.isDeviceOwner(MANAGED_PROFILE_UID));
+
+        // userId does not match
+        when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser())
+                .thenReturn(new ComponentName(TEST_PACKAGE_NAME, new String()));
+        when(mDevicePolicyManager.getDeviceOwnerUser())
+                .thenReturn(UserHandle.getUserHandleForUid(OTHER_USER_UID));
+        assertFalse(wifiPermissionsUtil.isDeviceOwner(MANAGED_PROFILE_UID));
+
+        // uid does not match
+        when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser())
+                .thenReturn(new ComponentName(TEST_PACKAGE_NAME, new String()));
+        when(mDevicePolicyManager.getDeviceOwnerUser())
+                .thenReturn(UserHandle.getUserHandleForUid(MANAGED_PROFILE_UID));
+        when(mPackageManager.getPackagesForUid(MANAGED_PROFILE_UID)).thenReturn(
+                new String[] { TEST_FEATURE_ID });
+        assertFalse(wifiPermissionsUtil.isDeviceOwner(MANAGED_PROFILE_UID));
+
+        // no packages for uid
+        when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser())
+                .thenReturn(new ComponentName(TEST_PACKAGE_NAME, new String()));
+        when(mDevicePolicyManager.getDeviceOwnerUser())
+                .thenReturn(UserHandle.getUserHandleForUid(MANAGED_PROFILE_UID));
+        when(mPackageManager.getPackagesForUid(MANAGED_PROFILE_UID)).thenReturn(null);
+        assertFalse(wifiPermissionsUtil.isDeviceOwner(MANAGED_PROFILE_UID));
+
+        // No device owner.
+        when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser())
+                .thenReturn(null);
+        assertFalse(wifiPermissionsUtil.isDeviceOwner(MANAGED_PROFILE_UID));
+
+        // DevicePolicyManager does not exist.
+        when(mMockContext.getSystemService(Context.DEVICE_POLICY_SERVICE))
+                .thenReturn(null);
+        assertFalse(wifiPermissionsUtil.isDeviceOwner(MANAGED_PROFILE_UID));
+    }
+
+    /**
      * Verifies the helper method exposed for checking if the app is a ProfileOwner.
      */
     @Test
@@ -1258,6 +1316,14 @@
     private void setupMocks() throws Exception {
         when(mMockPkgMgr.getApplicationInfoAsUser(eq(TEST_PACKAGE_NAME), eq(0), any()))
             .thenReturn(mMockApplInfo);
+        when(mMockContext.createPackageContextAsUser(any(), anyInt(), any()))
+                .thenReturn(mMockContext);
+        if (SdkLevel.isAtLeastS()) {
+            when(mMockPkgMgr.getTargetSdkVersion(TEST_PACKAGE_NAME))
+                    .thenReturn(mMockApplInfo.targetSdkVersion);
+        }
+        when(mMockPkgMgr.getApplicationInfoAsUser(eq(TEST_PACKAGE_NAME), eq(0), any()))
+                .thenReturn(mMockApplInfo);
         when(mMockContext.getPackageManager()).thenReturn(mMockPkgMgr);
         when(mMockAppOps.noteOp(AppOpsManager.OPSTR_WIFI_SCAN, mUid, TEST_PACKAGE_NAME,
                 TEST_FEATURE_ID, null)).thenReturn(mWifiScanAllowApps);
diff --git a/service/tests/wifitests/src/com/android/server/wifi/util/WorkSourceHelperTest.java b/service/tests/wifitests/src/com/android/server/wifi/util/WorkSourceHelperTest.java
new file mode 100644
index 0000000..1eb8cd2
--- /dev/null
+++ b/service/tests/wifitests/src/com/android/server/wifi/util/WorkSourceHelperTest.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.wifi.util;
+
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Process;
+import android.os.WorkSource;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.wifi.WifiBaseTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Unit tests for {@link WorkSourceHelper}. */
+@RunWith(JUnit4.class)
+@SmallTest
+public class WorkSourceHelperTest extends WifiBaseTest {
+    private static final int TEST_UID_1 = 456456;
+    private static final int TEST_UID_2 = 456456;
+    private static final String TEST_PACKAGE_1 = "com.android.test.1";
+    private static final String TEST_PACKAGE_2 = "com.android.test.2";
+
+    @Mock private WifiPermissionsUtil mWifiPermissionsUtil;
+    @Mock private ActivityManager mActivityManager;
+    @Mock private PackageManager mPackageManager;
+
+    private WorkSource mWorkSource;
+    private WorkSourceHelper mWorkSourceHelper;
+
+    @Before public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        // Create a test worksource with 2 app's request.
+        mWorkSource = new WorkSource();
+        mWorkSource.add(new WorkSource(TEST_UID_1, TEST_PACKAGE_1));
+        mWorkSource.add(new WorkSource(TEST_UID_2, TEST_PACKAGE_2));
+
+        mWorkSourceHelper = new WorkSourceHelper(
+                mWorkSource, mWifiPermissionsUtil, mActivityManager, mPackageManager);
+    }
+
+    @Test
+    public void testHasAnyPrivilegedRequest() {
+        assertFalse(mWorkSourceHelper.hasAnyPrivilegedAppRequest());
+
+        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(TEST_UID_1)).thenReturn(true);
+        assertTrue(mWorkSourceHelper.hasAnyPrivilegedAppRequest());
+    }
+
+    @Test
+    public void testHasAnySystemRequest() throws Exception {
+        ApplicationInfo appInfo = new ApplicationInfo();
+        when(mPackageManager.getApplicationInfoAsUser(any(), anyInt(), any())).thenReturn(appInfo);
+
+        assertFalse(mWorkSourceHelper.hasAnySystemAppRequest());
+
+        appInfo.flags = ApplicationInfo.FLAG_SYSTEM;
+        assertTrue(mWorkSourceHelper.hasAnySystemAppRequest());
+    }
+
+    @Test
+    public void testHasAnyForegroundAppRequest() throws Exception {
+        // 2 from bg app.
+        when(mActivityManager.getPackageImportance(TEST_PACKAGE_1))
+                .thenReturn(IMPORTANCE_BACKGROUND);
+        when(mActivityManager.getPackageImportance(TEST_PACKAGE_2))
+                .thenReturn(IMPORTANCE_BACKGROUND);
+        assertFalse(mWorkSourceHelper.hasAnyForegroundAppRequest());
+
+        // 1 request from fg app, 1 from bg app.
+        when(mActivityManager.getPackageImportance(TEST_PACKAGE_1))
+                .thenReturn(IMPORTANCE_FOREGROUND);
+        assertTrue(mWorkSourceHelper.hasAnyForegroundAppRequest());
+    }
+
+    @Test
+    public void testHasAnyForegroundServiceRequest() throws Exception {
+        // 2 from bg app.
+        when(mActivityManager.getPackageImportance(TEST_PACKAGE_1))
+                .thenReturn(IMPORTANCE_BACKGROUND);
+        when(mActivityManager.getPackageImportance(TEST_PACKAGE_2))
+                .thenReturn(IMPORTANCE_BACKGROUND);
+        assertFalse(mWorkSourceHelper.hasAnyForegroundServiceRequest());
+
+        // 1 request from fg service, 1 from bg app.
+        when(mActivityManager.getPackageImportance(TEST_PACKAGE_1))
+                .thenReturn(IMPORTANCE_FOREGROUND_SERVICE);
+        when(mActivityManager.getPackageImportance(TEST_PACKAGE_2))
+                .thenReturn(IMPORTANCE_BACKGROUND);
+        assertTrue(mWorkSourceHelper.hasAnyForegroundServiceRequest());
+    }
+
+
+    @Test
+    public void testHasAnyInternalRequest() throws Exception {
+        // 2 from bg app.
+        when(mActivityManager.getPackageImportance(TEST_PACKAGE_1))
+                .thenReturn(IMPORTANCE_BACKGROUND);
+        when(mActivityManager.getPackageImportance(TEST_PACKAGE_2))
+                .thenReturn(IMPORTANCE_BACKGROUND);
+        assertFalse(mWorkSourceHelper.hasAnyInternalRequest());
+
+        // add a new internal request.
+        mWorkSource.add(new WorkSource(Process.WIFI_UID, "com.android.wifi"));
+        assertTrue(mWorkSourceHelper.hasAnyInternalRequest());
+    }
+}
diff --git a/service/tests/wifitests/src/com/android/server/wifi/util/XmlUtilTest.java b/service/tests/wifitests/src/com/android/server/wifi/util/XmlUtilTest.java
index c56539d..50bd95e 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/util/XmlUtilTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/util/XmlUtilTest.java
@@ -29,6 +29,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.util.FastXmlSerializer;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.MacAddressUtils;
 import com.android.server.wifi.WifiBaseTest;
 import com.android.server.wifi.WifiConfigurationTestUtil;
@@ -60,7 +61,8 @@
 
     private static final String TEST_PACKAGE_NAME = "XmlUtilPackage";
     private static final String TEST_STATIC_IP_GATEWAY_ADDRESS = "192.168.48.1";
-    private static final String TEST_DUMMY_CONFIG_KEY = "XmlUtilDummyConfigKey";
+    private static final String TEST_PLACEHOLDER_CONFIG_KEY = "XmlUtilPlaceholderConfigKey";
+    private static final int TEST_RSSI = -55;
     private static final String TEST_IDENTITY = "XmlUtilTestIdentity";
     private static final String TEST_ANON_IDENTITY = "XmlUtilTestAnonIdentity";
     private static final String TEST_PASSWORD = "XmlUtilTestPassword";
@@ -73,10 +75,12 @@
     private static final String TEST_ALTSUBJECT_MATCH = "XmlUtilTestAltSubjectMatch";
     private static final String TEST_DOM_SUFFIX_MATCH = "XmlUtilTestDomSuffixMatch";
     private static final String TEST_CA_PATH = "XmlUtilTestCaPath";
+    private static final String TEST_KEYCHAIN_ALIAS = "XmlUtilTestKeyChainAlias";
     private static final int TEST_EAP_METHOD = WifiEnterpriseConfig.Eap.PEAP;
     private static final int TEST_PHASE2_METHOD = WifiEnterpriseConfig.Phase2.MSCHAPV2;
     private final String mXmlDocHeader = "XmlUtilTest";
-
+    private static final String TEST_DECORATED_IDENTITY_PREFIX = "androidwifi.dev!";
+    private static final String ANONYMOUS_IDENTITY = "aaa@bbb.cc.ddd";
     private WifiConfigStoreEncryptionUtil mWifiConfigStoreEncryptionUtil = null;
 
     @Before
@@ -152,8 +156,12 @@
     @Test
     public void testEapWifiConfigurationSerializeDeserialize()
             throws IOException, XmlPullParserException {
-        serializeDeserializeWifiConfigurationForConfigStore(
-                WifiConfigurationTestUtil.createEapNetwork());
+        WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork();
+        config.enterpriseConfig.setAnonymousIdentity(ANONYMOUS_IDENTITY);
+        if (SdkLevel.isAtLeastS()) {
+            config.enterpriseConfig.setDecoratedIdentityPrefix(TEST_DECORATED_IDENTITY_PREFIX);
+        }
+        serializeDeserializeWifiConfigurationForConfigStore(config);
     }
 
     /**
@@ -217,24 +225,22 @@
     public void testEapWifiConfigurationSerializeDeserializeForConfigStore()
             throws IOException, XmlPullParserException {
         WifiConfiguration configuration = WifiConfigurationTestUtil.createEapNetwork();
-        configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104);
-        configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40);
-        configuration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
-        configuration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);
         configuration.status = WifiConfiguration.Status.DISABLED;
         configuration.linkedConfigurations = new HashMap<>();
-        configuration.linkedConfigurations.put(TEST_DUMMY_CONFIG_KEY, Integer.valueOf(1));
+        configuration.linkedConfigurations.put(TEST_PLACEHOLDER_CONFIG_KEY, Integer.valueOf(1));
         configuration.defaultGwMacAddress = TEST_STATIC_IP_GATEWAY_ADDRESS;
-        configuration.requirePmf = true;
         configuration.validatedInternetAccess = true;
         configuration.noInternetAccessExpected = true;
         configuration.meteredHint = true;
         configuration.useExternalScores = true;
         configuration.numAssociation = 5;
+        configuration.oemPaid = true;
+        configuration.oemPrivate = true;
+        configuration.carrierMerged = true;
         configuration.lastUpdateUid = configuration.lastConnectUid = configuration.creatorUid;
         configuration.creatorName = configuration.lastUpdateName = TEST_PACKAGE_NAME;
         configuration.setRandomizedMacAddress(MacAddressUtils.createRandomUnicastAddress());
-        configuration.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_PERSISTENT;
+        configuration.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_AUTO;
 
         serializeDeserializeWifiConfigurationForConfigStore(configuration);
     }
@@ -250,7 +256,7 @@
         configuration.status = WifiConfiguration.Status.CURRENT;
         byte[] xmlData = serializeWifiConfigurationForConfigStore(configuration);
         Pair<String, WifiConfiguration> deserializedConfiguration =
-                deserializeWifiConfiguration(xmlData);
+                deserializeWifiConfiguration(xmlData, false);
         assertEquals(WifiConfiguration.Status.ENABLED, deserializedConfiguration.second.status);
     }
 
@@ -264,7 +270,8 @@
         NetworkSelectionStatus status = new NetworkSelectionStatus();
         status.setNetworkSelectionStatus(NetworkSelectionStatus.NETWORK_SELECTION_ENABLED);
         status.setNetworkSelectionDisableReason(NetworkSelectionStatus.DISABLED_NONE);
-        status.setConnectChoice(TEST_DUMMY_CONFIG_KEY);
+        status.setConnectChoice(TEST_PLACEHOLDER_CONFIG_KEY);
+        status.setConnectChoiceRssi(TEST_RSSI);
         status.setHasEverConnected(true);
         serializeDeserializeNetworkSelectionStatus(status);
     }
@@ -285,6 +292,21 @@
     }
 
     /**
+     * Verify that a permanently disabled network selection status object is serialized &
+     * deserialized correctly.
+     */
+    @Test
+    public void testPermanentlyDisabledNetworkSelectionStatusSerializeDeserialize()
+            throws IOException, XmlPullParserException {
+        NetworkSelectionStatus status = new NetworkSelectionStatus();
+        status.setNetworkSelectionStatus(
+                NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED);
+        status.setNetworkSelectionDisableReason(
+                NetworkSelectionStatus.DISABLED_NO_INTERNET_PERMANENT);
+        serializeDeserializeNetworkSelectionStatus(status);
+    }
+
+    /**
      * Verify that a network selection status deprecation is handled correctly during restore
      * of data after upgrade.
      * This test tries to simulate the scenario where we have a
@@ -295,13 +317,14 @@
     @Test
     public void testDeprecatedNetworkSelectionStatusDeserialize()
             throws IOException, XmlPullParserException {
-        // Create a dummy network selection status.
+        // Create a placeholder network selection status.
         NetworkSelectionStatus status = new NetworkSelectionStatus();
         status.setNetworkSelectionStatus(
                 NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED);
         status.setNetworkSelectionDisableReason(
                 NetworkSelectionStatus.DISABLED_DHCP_FAILURE);
-        status.setConnectChoice(TEST_DUMMY_CONFIG_KEY);
+        status.setConnectChoice(TEST_PLACEHOLDER_CONFIG_KEY);
+        status.setConnectChoiceRssi(TEST_RSSI);
         status.setHasEverConnected(true);
 
         // Serialize this to XML string.
@@ -343,13 +366,14 @@
     @Test
     public void testDeprecatedNetworkSelectionDisableReasonDeserialize()
             throws IOException, XmlPullParserException {
-        // Create a dummy network selection status.
+        // Create a placeholder network selection status.
         NetworkSelectionStatus status = new NetworkSelectionStatus();
         status.setNetworkSelectionStatus(
                 NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED);
         status.setNetworkSelectionDisableReason(
                 NetworkSelectionStatus.DISABLED_DHCP_FAILURE);
-        status.setConnectChoice(TEST_DUMMY_CONFIG_KEY);
+        status.setConnectChoice(TEST_PLACEHOLDER_CONFIG_KEY);
+        status.setConnectChoiceRssi(TEST_RSSI);
         status.setHasEverConnected(true);
 
         // Serialize this to XML string.
@@ -386,21 +410,8 @@
     @Test
     public void testWifiEnterpriseConfigSerializeDeserialize()
             throws IOException, XmlPullParserException {
-        WifiEnterpriseConfig config = new WifiEnterpriseConfig();
-        config.setFieldValue(WifiEnterpriseConfig.IDENTITY_KEY, TEST_IDENTITY);
-        config.setFieldValue(WifiEnterpriseConfig.ANON_IDENTITY_KEY, TEST_ANON_IDENTITY);
-        config.setFieldValue(WifiEnterpriseConfig.PASSWORD_KEY, TEST_PASSWORD);
-        config.setFieldValue(WifiEnterpriseConfig.CLIENT_CERT_KEY, TEST_CLIENT_CERT);
-        config.setFieldValue(WifiEnterpriseConfig.CA_CERT_KEY, TEST_CA_CERT);
-        config.setFieldValue(WifiEnterpriseConfig.SUBJECT_MATCH_KEY, TEST_SUBJECT_MATCH);
-        config.setFieldValue(WifiEnterpriseConfig.ENGINE_KEY, TEST_ENGINE);
-        config.setFieldValue(WifiEnterpriseConfig.ENGINE_ID_KEY, TEST_ENGINE_ID);
-        config.setFieldValue(WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY, TEST_PRIVATE_KEY_ID);
-        config.setFieldValue(WifiEnterpriseConfig.ALTSUBJECT_MATCH_KEY, TEST_ALTSUBJECT_MATCH);
-        config.setFieldValue(WifiEnterpriseConfig.DOM_SUFFIX_MATCH_KEY, TEST_DOM_SUFFIX_MATCH);
-        config.setFieldValue(WifiEnterpriseConfig.CA_PATH_KEY, TEST_CA_PATH);
-        config.setEapMethod(TEST_EAP_METHOD);
-        config.setPhase2Method(TEST_PHASE2_METHOD);
+        WifiEnterpriseConfig config = makeTestWifiEnterpriseConfig();
+
         serializeDeserializeWifiEnterpriseConfig(config);
     }
 
@@ -410,21 +421,7 @@
     @Test
     public void testWifiEnterpriseConfigSerializeDeserializeWithEncryption()
             throws IOException, XmlPullParserException {
-        WifiEnterpriseConfig config = new WifiEnterpriseConfig();
-        config.setFieldValue(WifiEnterpriseConfig.IDENTITY_KEY, TEST_IDENTITY);
-        config.setFieldValue(WifiEnterpriseConfig.ANON_IDENTITY_KEY, TEST_ANON_IDENTITY);
-        config.setFieldValue(WifiEnterpriseConfig.PASSWORD_KEY, TEST_PASSWORD);
-        config.setFieldValue(WifiEnterpriseConfig.CLIENT_CERT_KEY, TEST_CLIENT_CERT);
-        config.setFieldValue(WifiEnterpriseConfig.CA_CERT_KEY, TEST_CA_CERT);
-        config.setFieldValue(WifiEnterpriseConfig.SUBJECT_MATCH_KEY, TEST_SUBJECT_MATCH);
-        config.setFieldValue(WifiEnterpriseConfig.ENGINE_KEY, TEST_ENGINE);
-        config.setFieldValue(WifiEnterpriseConfig.ENGINE_ID_KEY, TEST_ENGINE_ID);
-        config.setFieldValue(WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY, TEST_PRIVATE_KEY_ID);
-        config.setFieldValue(WifiEnterpriseConfig.ALTSUBJECT_MATCH_KEY, TEST_ALTSUBJECT_MATCH);
-        config.setFieldValue(WifiEnterpriseConfig.DOM_SUFFIX_MATCH_KEY, TEST_DOM_SUFFIX_MATCH);
-        config.setFieldValue(WifiEnterpriseConfig.CA_PATH_KEY, TEST_CA_PATH);
-        config.setEapMethod(TEST_EAP_METHOD);
-        config.setPhase2Method(TEST_PHASE2_METHOD);
+        WifiEnterpriseConfig config = makeTestWifiEnterpriseConfig();
 
         mWifiConfigStoreEncryptionUtil = mock(WifiConfigStoreEncryptionUtil.class);
         EncryptedData encryptedData = new EncryptedData(new byte[0], new byte[0]);
@@ -444,21 +441,7 @@
     @Test(expected = IllegalArgumentException.class)
     public void testWifiEnterpriseConfigSerializeDeserializeThrowsIllegalArgException()
             throws Exception {
-        WifiEnterpriseConfig config = new WifiEnterpriseConfig();
-        config.setFieldValue(WifiEnterpriseConfig.IDENTITY_KEY, TEST_IDENTITY);
-        config.setFieldValue(WifiEnterpriseConfig.ANON_IDENTITY_KEY, TEST_ANON_IDENTITY);
-        config.setFieldValue(WifiEnterpriseConfig.PASSWORD_KEY, TEST_PASSWORD);
-        config.setFieldValue(WifiEnterpriseConfig.CLIENT_CERT_KEY, TEST_CLIENT_CERT);
-        config.setFieldValue(WifiEnterpriseConfig.CA_CERT_KEY, TEST_CA_CERT);
-        config.setFieldValue(WifiEnterpriseConfig.SUBJECT_MATCH_KEY, TEST_SUBJECT_MATCH);
-        config.setFieldValue(WifiEnterpriseConfig.ENGINE_KEY, TEST_ENGINE);
-        config.setFieldValue(WifiEnterpriseConfig.ENGINE_ID_KEY, TEST_ENGINE_ID);
-        config.setFieldValue(WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY, TEST_PRIVATE_KEY_ID);
-        config.setFieldValue(WifiEnterpriseConfig.ALTSUBJECT_MATCH_KEY, TEST_ALTSUBJECT_MATCH);
-        config.setFieldValue(WifiEnterpriseConfig.DOM_SUFFIX_MATCH_KEY, TEST_DOM_SUFFIX_MATCH);
-        config.setFieldValue(WifiEnterpriseConfig.CA_PATH_KEY, TEST_CA_PATH);
-        config.setEapMethod(TEST_EAP_METHOD);
-        config.setPhase2Method(TEST_PHASE2_METHOD);
+        WifiEnterpriseConfig config = makeTestWifiEnterpriseConfig();
         String xmlString = new String(serializeWifiEnterpriseConfig(config));
         // Manipulate the XML data to set the EAP method to None, this should raise an Illegal
         // argument exception in WifiEnterpriseConfig.setEapMethod().
@@ -486,6 +469,30 @@
     }
 
     /**
+     * Verify that when XML_TAG_IS_CAPTIVE_PORTAL_NEVER_DETECTED is not found in the XML file, the
+     * corresponding field defaults to false.
+     * @throws IOException
+     * @throws XmlPullParserException
+     */
+    @Test
+    public void testCaptivePortalNeverDetected_DefaultToFalse()
+            throws IOException, XmlPullParserException {
+        // First generate XML data that only has the header filled in
+        final XmlSerializer out = new FastXmlSerializer();
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        out.setOutput(outputStream, StandardCharsets.UTF_8.name());
+        XmlUtil.writeDocumentStart(out, mXmlDocHeader);
+        XmlUtil.writeDocumentEnd(out, mXmlDocHeader);
+
+        // Deserialize the data
+        NetworkSelectionStatus retrieved =
+                deserializeNetworkSelectionStatus(outputStream.toByteArray());
+
+        // Verify that hasNeverDetectedCaptivePortal returns false.
+        assertFalse(retrieved.hasNeverDetectedCaptivePortal());
+    }
+
+    /**
      * Verify that when the macRandomizationSetting field is not found in the XML file,
      * macRandomizationSetting is defaulted to RANDOMIZATION_NONE.
      * @throws IOException
@@ -503,13 +510,94 @@
 
         // Deserialize the data
         Pair<String, WifiConfiguration> retrieved =
-                deserializeWifiConfiguration(outputStream.toByteArray());
+                deserializeWifiConfiguration(outputStream.toByteArray(), false);
 
         // Verify that macRandomizationSetting is set to |RANDOMIZATION_NONE|
         assertEquals(WifiConfiguration.RANDOMIZATION_NONE,
                 retrieved.second.macRandomizationSetting);
     }
 
+    /**
+     * Verify that when deserializing a XML RANDOMIZATION_PERSISTENT is automatically upgraded to
+     * RANDOIMZATION_ENHANCED.
+     * @throws IOException
+     * @throws XmlPullParserException
+     */
+    @Test
+    public void testMacRandomizationSettingUpgradeToRandomizationAuto()
+            throws IOException, XmlPullParserException {
+        // First generate XML data that only has the header filled in
+        final XmlSerializer out = new FastXmlSerializer();
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        out.setOutput(outputStream, StandardCharsets.UTF_8.name());
+        XmlUtil.writeDocumentStart(out, mXmlDocHeader);
+        // Mark the configuration to use persistent MAC randomization.
+        XmlUtil.writeNextValue(out, WifiConfigurationXmlUtil.XML_TAG_MAC_RANDOMIZATION_SETTING,
+                WifiConfiguration.RANDOMIZATION_PERSISTENT);
+        XmlUtil.writeDocumentEnd(out, mXmlDocHeader);
+
+        // Deserialize the saved WifiConfiguration and expect a MAC randomization upgrade.
+        Pair<String, WifiConfiguration> retrieved =
+                deserializeWifiConfiguration(outputStream.toByteArray(), false);
+
+        // Verify that macRandomizationSetting is set to |RANDOMIZATION_AUTO| due to auto upgrade.
+        assertEquals(WifiConfiguration.RANDOMIZATION_AUTO,
+                retrieved.second.macRandomizationSetting);
+    }
+
+    /**
+     * Verify that when deserializing a XML RANDOMIZATION_PERSISTENT is not automatically upgraded
+     * for suggestion networks.
+     * @throws IOException
+     * @throws XmlPullParserException
+     */
+    @Test
+    public void testMacRandomizationSettingNoUpgradeToRandomizationAutoForSuggestion()
+            throws IOException, XmlPullParserException {
+        // First generate XML data that only has the header filled in
+        final XmlSerializer out = new FastXmlSerializer();
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        out.setOutput(outputStream, StandardCharsets.UTF_8.name());
+        XmlUtil.writeDocumentStart(out, mXmlDocHeader);
+        // Mark the configuration to use persistent MAC randomization.
+        XmlUtil.writeNextValue(out, WifiConfigurationXmlUtil.XML_TAG_MAC_RANDOMIZATION_SETTING,
+                WifiConfiguration.RANDOMIZATION_PERSISTENT);
+        XmlUtil.writeDocumentEnd(out, mXmlDocHeader);
+
+        // Deserialize the saved WifiConfiguration. Do not expect an auto upgrade since this is
+        // a suggested network.
+        Pair<String, WifiConfiguration> retrieved =
+                deserializeWifiConfiguration(outputStream.toByteArray(), true);
+
+        // Verify that macRandomizationSetting is still RANDOMIZATION_PERSISTENT.
+        assertEquals(WifiConfiguration.RANDOMIZATION_PERSISTENT,
+                retrieved.second.macRandomizationSetting);
+    }
+
+    private WifiEnterpriseConfig makeTestWifiEnterpriseConfig() {
+        final WifiEnterpriseConfig config = new WifiEnterpriseConfig();
+        config.setFieldValue(WifiEnterpriseConfig.IDENTITY_KEY, TEST_IDENTITY);
+        config.setFieldValue(WifiEnterpriseConfig.ANON_IDENTITY_KEY, TEST_ANON_IDENTITY);
+        config.setFieldValue(WifiEnterpriseConfig.PASSWORD_KEY, TEST_PASSWORD);
+        config.setFieldValue(WifiEnterpriseConfig.CLIENT_CERT_KEY, TEST_CLIENT_CERT);
+        config.setFieldValue(WifiEnterpriseConfig.CA_CERT_KEY, TEST_CA_CERT);
+        config.setFieldValue(WifiEnterpriseConfig.SUBJECT_MATCH_KEY, TEST_SUBJECT_MATCH);
+        config.setFieldValue(WifiEnterpriseConfig.ENGINE_KEY, TEST_ENGINE);
+        config.setFieldValue(WifiEnterpriseConfig.ENGINE_ID_KEY, TEST_ENGINE_ID);
+        config.setFieldValue(WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY, TEST_PRIVATE_KEY_ID);
+        config.setFieldValue(WifiEnterpriseConfig.ALTSUBJECT_MATCH_KEY, TEST_ALTSUBJECT_MATCH);
+        config.setFieldValue(WifiEnterpriseConfig.DOM_SUFFIX_MATCH_KEY, TEST_DOM_SUFFIX_MATCH);
+        config.setFieldValue(WifiEnterpriseConfig.CA_PATH_KEY, TEST_CA_PATH);
+        config.setEapMethod(TEST_EAP_METHOD);
+        config.setPhase2Method(TEST_PHASE2_METHOD);
+        config.initIsAppInstalledDeviceKeyAndCert(true);
+        config.initIsAppInstalledCaCert(true);
+        if (SdkLevel.isAtLeastS()) {
+            config.setClientKeyPairAlias(TEST_KEYCHAIN_ALIAS);
+        }
+        return config;
+    }
+
     private byte[] serializeWifiConfigurationForBackup(WifiConfiguration configuration)
             throws IOException, XmlPullParserException {
         final XmlSerializer out = new FastXmlSerializer();
@@ -534,7 +622,8 @@
         return outputStream.toByteArray();
     }
 
-    private Pair<String, WifiConfiguration> deserializeWifiConfiguration(byte[] data)
+    private Pair<String, WifiConfiguration> deserializeWifiConfiguration(byte[] data,
+            boolean fromSuggestion)
             throws IOException, XmlPullParserException {
         // Deserialize the configuration object.
         final XmlPullParser in = Xml.newPullParser();
@@ -544,7 +633,7 @@
         return WifiConfigurationXmlUtil.parseFromXml(
                 in, in.getDepth(),
                 mWifiConfigStoreEncryptionUtil != null,
-                mWifiConfigStoreEncryptionUtil);
+                mWifiConfigStoreEncryptionUtil, fromSuggestion);
     }
 
     /**
@@ -557,7 +646,7 @@
         // Test serialization/deserialization for config store.
         retrieved =
                 deserializeWifiConfiguration(
-                        serializeWifiConfigurationForBackup(configuration));
+                        serializeWifiConfigurationForBackup(configuration), false);
         assertEquals(retrieved.first, retrieved.second.getKey());
         WifiConfigurationTestUtil.assertConfigurationEqualForBackup(
                 configuration, retrieved.second);
@@ -575,10 +664,16 @@
         // Test serialization/deserialization for config store.
         retrieved =
                 deserializeWifiConfiguration(
-                        serializeWifiConfigurationForConfigStore(configuration));
+                        serializeWifiConfigurationForConfigStore(configuration), false);
         assertEquals(retrieved.first, retrieved.second.getKey());
         WifiConfigurationTestUtil.assertConfigurationEqualForConfigStore(
                 configuration, retrieved.second);
+        // Counter should be non-zero for a disabled network
+        NetworkSelectionStatus status = retrieved.second.getNetworkSelectionStatus();
+        if (!status.isNetworkEnabled()) {
+            assertNotEquals(0, status.getDisableReasonCounter(
+                    status.getNetworkSelectionDisableReason()));
+        }
     }
 
     /**